Skip to content

Commit

Permalink
Merge pull request video-dev#1846 from yandex-pcode/master
Browse files Browse the repository at this point in the history
Support for #EXT-X-SESSION-DATA tag in master playlist
  • Loading branch information
robwalch authored Apr 14, 2020
2 parents df6eda1 + bc06304 commit 87bc986
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 79 deletions.
2 changes: 1 addition & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,7 @@ Full list of Events is available below:
- `Hls.Events.MANIFEST_LOADING` - fired to signal that a manifest loading starts
- data: { url : manifestURL }
- `Hls.Events.MANIFEST_LOADED` - fired after manifest has been loaded
- data: { levels : [available quality levels], audioTracks : [ available audio tracks], url : manifestURL, stats : { trequest, tfirst, tload, mtime}}
- data: { levels : [available quality levels], audioTracks : [ available audio tracks], url : manifestURL, stats : { trequest, tfirst, tload, mtime}, sessionData: [parsed #EXT-X-SESSION-DATA]}
- `Hls.Events.MANIFEST_PARSED` - fired after manifest has been parsed
- data: { levels : [ available quality levels ], firstLevel : index of first quality level appearing in Manifest }
- `Hls.Events.LEVEL_SWITCHING` - fired when a level switch is requested
Expand Down
52 changes: 34 additions & 18 deletions src/loader/m3u8-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { PlaylistLevelType } from '../types/loader';
*/

// https://regex101.com is your friend
const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\n\r]*)[\r\n]+([^\r\n]+)/g;
const MASTER_PLAYLIST_REGEX = /(?:#EXT-X-STREAM-INF:([^\n\r]*)[\r\n]+([^\r\n]+)|#EXT-X-SESSION-DATA:([^\n\r]*)[\r\n]+)/g;
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;

const LEVEL_PLAYLIST_REGEX_FAST = new RegExp([
Expand Down Expand Up @@ -61,6 +61,8 @@ export default class M3U8Parser {
static parseMasterPlaylist (string: string, baseurl: string) {
// TODO(typescript-level)
let levels: Array<any> = [];
let sessionData: Record<string, AttrList> = {};
let hasSessionData = false;
MASTER_PLAYLIST_REGEX.lastIndex = 0;

// TODO(typescript-level)
Expand All @@ -83,29 +85,43 @@ export default class M3U8Parser {

let result: RegExpExecArray | null;
while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
// TODO(typescript-level)
const level: any = {};
if (result[1]) {
// '#EXT-X-STREAM-INF' is found, parse level tag in group 1

const attrs = level.attrs = new AttrList(result[1]);
level.url = M3U8Parser.resolve(result[2], baseurl);
// TODO(typescript-level)
const level: any = {};

const resolution = attrs.decimalResolution('RESOLUTION');
if (resolution) {
level.width = resolution.width;
level.height = resolution.height;
}
level.bitrate = attrs.decimalInteger('AVERAGE-BANDWIDTH') || attrs.decimalInteger('BANDWIDTH');
level.name = attrs.NAME;
const attrs = level.attrs = new AttrList(result[1]);
level.url = M3U8Parser.resolve(result[2], baseurl);

setCodecs([].concat((attrs.CODECS || '').split(/[ ,]+/)), level);
const resolution = attrs.decimalResolution('RESOLUTION');
if (resolution) {
level.width = resolution.width;
level.height = resolution.height;
}
level.bitrate = attrs.decimalInteger('AVERAGE-BANDWIDTH') || attrs.decimalInteger('BANDWIDTH');
level.name = attrs.NAME;

if (level.videoCodec && level.videoCodec.indexOf('avc1') !== -1) {
level.videoCodec = M3U8Parser.convertAVC1ToAVCOTI(level.videoCodec);
}
setCodecs([].concat((attrs.CODECS || '').split(/[ ,]+/)), level);

levels.push(level);
if (level.videoCodec && level.videoCodec.indexOf('avc1') !== -1) {
level.videoCodec = M3U8Parser.convertAVC1ToAVCOTI(level.videoCodec);
}

levels.push(level);
} else if (result[3]) {
// '#EXT-X-SESSION-DATA' is found, parse session data in group 3
let sessionAttrs = new AttrList(result[3]);
if (sessionAttrs['DATA-ID']) {
hasSessionData = true;
sessionData[sessionAttrs['DATA-ID']] = sessionAttrs;
}
}
}
return levels;
return {
levels,
sessionData: hasSessionData ? sessionData : null
};
}

static parseMasterPlaylistMedia (string: string, baseurl: string, type: MediaPlaylistType, audioGroups: Array<AudioGroup> = []): Array<MediaPlaylist> {
Expand Down
8 changes: 5 additions & 3 deletions src/loader/playlist-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class PlaylistLoader extends EventHandler {
const string = response.data as string;

const url = PlaylistLoader.getResponseUrl(response, context);
const levels = M3U8Parser.parseMasterPlaylist(string, url);
const { levels, sessionData } = M3U8Parser.parseMasterPlaylist(string, url);
if (!levels.length) {
this._handleManifestParsingError(response, context, 'no level found in manifest', networkDetails);
return;
Expand Down Expand Up @@ -332,7 +332,8 @@ class PlaylistLoader extends EventHandler {
subtitles,
url,
stats,
networkDetails
networkDetails,
sessionData
});
}

Expand Down Expand Up @@ -381,7 +382,8 @@ class PlaylistLoader extends EventHandler {
audioTracks: [],
url,
stats,
networkDetails
networkDetails,
sessionData: null
});
}

Expand Down
6 changes: 2 additions & 4 deletions tests/unit/controller/stream-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ describe('StreamController', function () {
const manifest = `#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=836280,RESOLUTION=848x360,NAME="480"
http://proxy-62.dailymotion.com/sec(3ae40f708f79ca9471f52b86da76a3a8)/video/107/282/158282701_mp4_h264_aac_hq.m3u8#cell=core`;
const levels = M3U8Parser.parseMasterPlaylist(manifest, 'http://www.dailymotion.com');
const result = M3U8Parser.parseMasterPlaylist(manifest, 'http://www.dailymotion.com');
// load levels data
streamController.onManifestParsed({
levels
});
streamController.onManifestParsed(result);
streamController.startLoad(1);
assertStreamControllerStarted(streamController);
streamController.stopLoad();
Expand Down
Loading

0 comments on commit 87bc986

Please sign in to comment.