diff --git a/README.md b/README.md index 92914dd..105041a 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,7 @@ Manifest { * [EXT-X-MAP](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.5) * [EXT-X-PROGRAM-DATE-TIME](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.6) * [EXT-X-DATERANGE](https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.2.7) +* [EXT-X-I-FRAMES-ONLY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.6) ### Media Playlist Tags @@ -195,6 +196,7 @@ Manifest { * [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1) * [EXT-X-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.2) +* [EXT-X-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3) * [EXT-X-CONTENT-STEERING](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.6) * [EXT-X-DEFINE](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3) @@ -261,8 +263,6 @@ Example media playlist using `EXT-X-CUE-` tags. ### Not Yet Supported -* [EXT-X-I-FRAMES-ONLY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.6) -* [EXT-X-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3) * [EXT-X-SESSION-DATA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.4) * [EXT-X-SESSION-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.5) diff --git a/src/parse-stream.js b/src/parse-stream.js index 66377c2..7b4eef5 100644 --- a/src/parse-stream.js +++ b/src/parse-stream.js @@ -71,6 +71,29 @@ const parseAttributes = function(attributes) { return result; }; +/** + * Converts a string into a resolution object + * + * @param {string} resolution a string such as 3840x2160 + * + * @return {Object} An object representing the resolution + * + */ +const parseResolution = (resolution) => { + const split = resolution.split('x'); + const result = {}; + + if (split[0]) { + result.width = parseInt(split[0], 10); + } + + if (split[1]) { + result.height = parseInt(split[1], 10); + } + + return result; +}; + /** * A line-level M3U8 parser event stream. It expects to receive input one * line at a time and performs a context-free parse of its contents. A stream @@ -296,16 +319,7 @@ export default class ParseStream extends Stream { event.attributes = parseAttributes(match[1]); if (event.attributes.RESOLUTION) { - const split = event.attributes.RESOLUTION.split('x'); - const resolution = {}; - - if (split[0]) { - resolution.width = parseInt(split[0], 10); - } - if (split[1]) { - resolution.height = parseInt(split[1], 10); - } - event.attributes.RESOLUTION = resolution; + event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION); } if (event.attributes.BANDWIDTH) { event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); @@ -429,7 +443,7 @@ export default class ParseStream extends Stream { this.trigger('data', event); return; } - match = (/^#EXT-X-CUE-IN:(.*)?$/).exec(newLine); + match = (/^#EXT-X-CUE-IN:?(.*)?$/).exec(newLine); if (match) { event = { type: 'tag', @@ -624,6 +638,16 @@ export default class ParseStream extends Stream { }); return; } + + match = (/^#EXT-X-I-FRAMES-ONLY/).exec(newLine); + if (match) { + this.trigger('data', { + type: 'tag', + tagType: 'i-frames-only' + }); + return; + } + match = (/^#EXT-X-CONTENT-STEERING:(.*)$/).exec(newLine); if (match) { event = { @@ -632,6 +656,41 @@ export default class ParseStream extends Stream { }; event.attributes = parseAttributes(match[1]); this.trigger('data', event); + + return; + } + + match = (/^#EXT-X-I-FRAME-STREAM-INF:(.*)$/).exec(newLine); + if (match) { + event = { + type: 'tag', + tagType: 'i-frame-playlist' + }; + + event.attributes = parseAttributes(match[1]); + + if (event.attributes.URI) { + event.uri = event.attributes.URI; + } + + if (event.attributes.BANDWIDTH) { + event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); + } + + if (event.attributes.RESOLUTION) { + event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION); + } + + if (event.attributes['AVERAGE-BANDWIDTH']) { + event.attributes['AVERAGE-BANDWIDTH'] = parseInt(event.attributes['AVERAGE-BANDWIDTH'], 10); + } + + if (event.attributes['FRAME-RATE']) { + event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']); + } + + this.trigger('data', event); + return; } match = (/^#EXT-X-DEFINE:(.*)$/).exec(newLine); diff --git a/src/parser.js b/src/parser.js index d677f02..207ca9c 100644 --- a/src/parser.js +++ b/src/parser.js @@ -131,6 +131,7 @@ export default class Parser extends Stream { allowCache: true, discontinuityStarts: [], dateRanges: [], + iFramePlaylists: [], segments: [] }; // keep track of the last seen segment's byte range end, as segments are not required @@ -743,6 +744,11 @@ export default class Parser extends Stream { 'independent-segments'() { this.manifest.independentSegments = true; }, + 'i-frames-only'() { + this.manifest.iFramesOnly = true; + + this.requiredCompatibilityversion(this.manifest.version, 4); + }, 'content-steering'() { this.manifest.contentSteering = camelCaseKeys(entry.attributes); this.warnOnMissingAttributes_( @@ -751,6 +757,7 @@ export default class Parser extends Stream { ['SERVER-URI'] ); }, + /** @this {Parser} */ define() { this.manifest.definitions = this.manifest.definitions || { }; @@ -842,6 +849,20 @@ export default class Parser extends Stream { this.trigger('error', { message: 'EXT-X-DEFINE: No attribute' }); + }, + + 'i-frame-playlist'() { + this.manifest.iFramePlaylists.push({ + attributes: entry.attributes, + uri: entry.uri, + timeline: currentTimeline + }); + + this.warnOnMissingAttributes_( + '#EXT-X-I-FRAME-STREAM-INF', + entry.attributes, + ['BANDWIDTH', 'URI'] + ); } })[entry.tagType] || noop).call(self); @@ -897,6 +918,14 @@ export default class Parser extends Stream { }); } + requiredCompatibilityversion(currentVersion, targetVersion) { + if (currentVersion < targetVersion || !currentVersion) { + this.trigger('warn', { + message: `manifest must be at least version ${targetVersion}` + }); + } + } + warnOnMissingAttributes_(identifier, attributes, required) { const missing = []; diff --git a/test/fixtures/integration/absoluteUris.js b/test/fixtures/integration/absoluteUris.js index d45c28c..43337e6 100644 --- a/test/fixtures/integration/absoluteUris.js +++ b/test/fixtures/integration/absoluteUris.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/allowCache.js b/test/fixtures/integration/allowCache.js index fb19ce3..10d99c0 100644 --- a/test/fixtures/integration/allowCache.js +++ b/test/fixtures/integration/allowCache.js @@ -1,5 +1,6 @@ module.exports = { allowCache: true, + iFramePlaylists: [], mediaSequence: 0, dateRanges: [], playlistType: 'VOD', diff --git a/test/fixtures/integration/allowCacheInvalid.js b/test/fixtures/integration/allowCacheInvalid.js index e91638a..9029387 100644 --- a/test/fixtures/integration/allowCacheInvalid.js +++ b/test/fixtures/integration/allowCacheInvalid.js @@ -1,5 +1,6 @@ module.exports = { allowCache: true, + iFramePlaylists: [], mediaSequence: 0, dateRanges: [], playlistType: 'VOD', diff --git a/test/fixtures/integration/alternateAudio.js b/test/fixtures/integration/alternateAudio.js index 0886327..3325ee7 100644 --- a/test/fixtures/integration/alternateAudio.js +++ b/test/fixtures/integration/alternateAudio.js @@ -2,6 +2,7 @@ module.exports = { allowCache: true, discontinuityStarts: [], dateRanges: [], + iFramePlaylists: [], mediaGroups: { // TYPE 'AUDIO': { diff --git a/test/fixtures/integration/alternateVideo.js b/test/fixtures/integration/alternateVideo.js index fd7a219..fefd163 100644 --- a/test/fixtures/integration/alternateVideo.js +++ b/test/fixtures/integration/alternateVideo.js @@ -2,6 +2,7 @@ module.exports = { allowCache: true, discontinuityStarts: [], dateRanges: [], + iFramePlaylists: [], mediaGroups: { 'AUDIO': { aac: { diff --git a/test/fixtures/integration/brightcove.js b/test/fixtures/integration/brightcove.js index 595c4c9..524f276 100644 --- a/test/fixtures/integration/brightcove.js +++ b/test/fixtures/integration/brightcove.js @@ -1,5 +1,6 @@ module.exports = { allowCache: true, + iFramePlaylists: [], dateRanges: [], playlists: [ { diff --git a/test/fixtures/integration/byteRange.js b/test/fixtures/integration/byteRange.js index 98bca31..0b2bd5f 100644 --- a/test/fixtures/integration/byteRange.js +++ b/test/fixtures/integration/byteRange.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/dateTime.js b/test/fixtures/integration/dateTime.js index a2e4dbf..89427a6 100644 --- a/test/fixtures/integration/dateTime.js +++ b/test/fixtures/integration/dateTime.js @@ -1,5 +1,6 @@ module.exports = { allowCache: false, + iFramePlaylists: [], mediaSequence: 0, dateRanges: [], playlistType: 'VOD', diff --git a/test/fixtures/integration/diff-init-key.js b/test/fixtures/integration/diff-init-key.js index d29fce6..cd918b1 100644 --- a/test/fixtures/integration/diff-init-key.js +++ b/test/fixtures/integration/diff-init-key.js @@ -3,6 +3,7 @@ module.exports = { discontinuitySequence: 0, discontinuityStarts: [], dateRanges: [], + iFramePlaylists: [], mediaSequence: 7794, segments: [ { diff --git a/test/fixtures/integration/disallowCache.js b/test/fixtures/integration/disallowCache.js index 51c94d3..2455b30 100644 --- a/test/fixtures/integration/disallowCache.js +++ b/test/fixtures/integration/disallowCache.js @@ -1,6 +1,7 @@ module.exports = { allowCache: false, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/disc-sequence.js b/test/fixtures/integration/disc-sequence.js index eae9a9c..788294f 100644 --- a/test/fixtures/integration/disc-sequence.js +++ b/test/fixtures/integration/disc-sequence.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, discontinuitySequence: 3, segments: [ diff --git a/test/fixtures/integration/discontinuity.js b/test/fixtures/integration/discontinuity.js index c6f447d..72dfaf6 100644 --- a/test/fixtures/integration/discontinuity.js +++ b/test/fixtures/integration/discontinuity.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, discontinuitySequence: 0, segments: [ diff --git a/test/fixtures/integration/domainUris.js b/test/fixtures/integration/domainUris.js index f057974..d874539 100644 --- a/test/fixtures/integration/domainUris.js +++ b/test/fixtures/integration/domainUris.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/empty.js b/test/fixtures/integration/empty.js index db4f953..7849a6c 100644 --- a/test/fixtures/integration/empty.js +++ b/test/fixtures/integration/empty.js @@ -2,5 +2,6 @@ module.exports = { allowCache: true, dateRanges: [], discontinuityStarts: [], + iFramePlaylists: [], segments: [] }; diff --git a/test/fixtures/integration/emptyAllowCache.js b/test/fixtures/integration/emptyAllowCache.js index 615efdb..fc49016 100644 --- a/test/fixtures/integration/emptyAllowCache.js +++ b/test/fixtures/integration/emptyAllowCache.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/emptyMediaSequence.js b/test/fixtures/integration/emptyMediaSequence.js index 8c09122..3e3290d 100644 --- a/test/fixtures/integration/emptyMediaSequence.js +++ b/test/fixtures/integration/emptyMediaSequence.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/emptyPlaylistType.js b/test/fixtures/integration/emptyPlaylistType.js index c695740..4ff371d 100644 --- a/test/fixtures/integration/emptyPlaylistType.js +++ b/test/fixtures/integration/emptyPlaylistType.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/emptyTargetDuration.js b/test/fixtures/integration/emptyTargetDuration.js index 595c4c9..b7622af 100644 --- a/test/fixtures/integration/emptyTargetDuration.js +++ b/test/fixtures/integration/emptyTargetDuration.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], playlists: [ { attributes: { diff --git a/test/fixtures/integration/encrypted.js b/test/fixtures/integration/encrypted.js index 295da7f..32675fe 100644 --- a/test/fixtures/integration/encrypted.js +++ b/test/fixtures/integration/encrypted.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 7794, discontinuitySequence: 0, discontinuityStarts: [], diff --git a/test/fixtures/integration/event.js b/test/fixtures/integration/event.js index 7917ea1..2ec7eb0 100644 --- a/test/fixtures/integration/event.js +++ b/test/fixtures/integration/event.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'EVENT', segments: [ diff --git a/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js b/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js index 5856cdf..0b167b8 100644 --- a/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js +++ b/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 1, segments: [ { diff --git a/test/fixtures/integration/extinf.js b/test/fixtures/integration/extinf.js index 2ce0d67..b73c5dd 100644 --- a/test/fixtures/integration/extinf.js +++ b/test/fixtures/integration/extinf.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/fmp4.js b/test/fixtures/integration/fmp4.js index a1497b3..6f783a9 100644 --- a/test/fixtures/integration/fmp4.js +++ b/test/fixtures/integration/fmp4.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 1, playlistType: 'VOD', targetDuration: 6, diff --git a/test/fixtures/integration/headerOnly.js b/test/fixtures/integration/headerOnly.js index db4f953..7849a6c 100644 --- a/test/fixtures/integration/headerOnly.js +++ b/test/fixtures/integration/headerOnly.js @@ -2,5 +2,6 @@ module.exports = { allowCache: true, dateRanges: [], discontinuityStarts: [], + iFramePlaylists: [], segments: [] }; diff --git a/test/fixtures/integration/iFramePlaylist.js b/test/fixtures/integration/iFramePlaylist.js new file mode 100644 index 0000000..fe1b1e6 --- /dev/null +++ b/test/fixtures/integration/iFramePlaylist.js @@ -0,0 +1,288 @@ +module.exports = { + allowCache: true, + dateRanges: [], + discontinuityStarts: [], + iFramePlaylists: [ + { + attributes: { + 'AVERAGE-BANDWIDTH': 248586, + 'BANDWIDTH': 593626, + 'CODECS': 'hvc1.2.4.L123.B0', + 'HDCP-LEVEL': 'NONE', + 'RESOLUTION': { width: 1280, height: 720 }, + 'URI': 'sdr_720/iframe_index.m3u8', + 'VIDEO-RANGE': 'SDR' + }, + timeline: 0, + uri: 'sdr_720/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 399790, + 'BANDWIDTH': 956552, + 'CODECS': 'hvc1.2.4.L123.B0', + 'HDCP-LEVEL': 'TYPE-0', + 'RESOLUTION': { width: 1920, height: 1080 }, + 'URI': 'sdr_1080/iframe_index.m3u8', + 'VIDEO-RANGE': 'SDR' + }, + timeline: 0, + uri: 'sdr_1080/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 826971, + 'BANDWIDTH': 1941397, + 'CODECS': 'hvc1.2.4.L150.B0', + 'HDCP-LEVEL': 'TYPE-1', + 'RESOLUTION': { width: 3840, height: 2160 }, + 'URI': 'sdr_2160/iframe_index.m3u8', + 'VIDEO-RANGE': 'SDR' + }, + timeline: 0, + uri: 'sdr_2160/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 232253, + 'BANDWIDTH': 573073, + 'CODECS': 'dvh1.05.01', + 'HDCP-LEVEL': 'NONE', + 'RESOLUTION': { width: 1280, height: 720 }, + 'URI': 'dolby_720/iframe_index.m3u8', + 'VIDEO-RANGE': 'PQ' + }, + timeline: 0, + uri: 'dolby_720/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 365337, + 'BANDWIDTH': 905037, + 'CODECS': 'dvh1.05.03', + 'HDCP-LEVEL': 'TYPE-0', + 'RESOLUTION': { width: 1920, height: 1080 }, + 'URI': 'dolby_1080/iframe_index.m3u8', + 'VIDEO-RANGE': 'PQ' + }, + timeline: 0, + uri: 'dolby_1080/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 739114, + 'BANDWIDTH': 1893236, + 'CODECS': 'dvh1.05.06', + 'HDCP-LEVEL': 'TYPE-1', + 'RESOLUTION': { width: 3840, height: 2160 }, + 'URI': 'dolby_2160/iframe_index.m3u8', + 'VIDEO-RANGE': 'PQ' + }, + timeline: 0, + uri: 'dolby_2160/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 232511, + 'BANDWIDTH': 572673, + 'CODECS': 'hvc1.2.4.L123.B0', + 'HDCP-LEVEL': 'NONE', + 'RESOLUTION': { width: 1280, height: 720 }, + 'URI': 'hdr10_720/iframe_index.m3u8', + 'VIDEO-RANGE': 'PQ' + }, + timeline: 0, + uri: 'hdr10_720/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 364552, + 'BANDWIDTH': 905053, + 'CODECS': 'hvc1.2.4.L123.B0', + 'HDCP-LEVEL': 'TYPE-0', + 'RESOLUTION': { width: 1920, height: 1080 }, + 'URI': 'hdr10_1080/iframe_index.m3u8', + 'VIDEO-RANGE': 'PQ' + }, + timeline: 0, + uri: 'hdr10_1080/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 739757, + 'BANDWIDTH': 1895477, + 'CODECS': 'hvc1.2.4.L150.B0', + 'HDCP-LEVEL': 'TYPE-1', + 'RESOLUTION': { width: 3840, height: 2160 }, + 'URI': 'hdr10_2160/iframe_index.m3u8', + 'VIDEO-RANGE': 'PQ' + }, + timeline: 0, + uri: 'hdr10_2160/iframe_index.m3u8' + } + ], + independentSegments: true, + mediaGroups: { + 'AUDIO': {}, + 'CLOSED-CAPTIONS': {}, + 'SUBTITLES': {}, + 'VIDEO': {} + }, + playlists: [ + { + attributes: { + 'HDCP-LEVEL': 'NONE', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 1280, + height: 720 + }, + 'CODECS': 'hvc1.2.4.L123.B0', + 'VIDEO-RANGE': 'SDR', + 'BANDWIDTH': 3971374, + 'AVERAGE-BANDWIDTH': '2778321' + }, + uri: 'sdr_720/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'TYPE-0', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 1920, + height: 1080 + }, + 'CODECS': 'hvc1.2.4.L123.B0', + 'VIDEO-RANGE': 'SDR', + 'BANDWIDTH': 10022043, + 'AVERAGE-BANDWIDTH': '6759875' + }, + uri: 'sdr_1080/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'TYPE-1', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 3840, + height: 2160 + }, + 'CODECS': 'hvc1.2.4.L150.B0', + 'VIDEO-RANGE': 'SDR', + 'BANDWIDTH': 28058971, + 'AVERAGE-BANDWIDTH': '20985770' + }, + uri: 'sdr_2160/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'NONE', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 1280, + height: 720 + }, + 'CODECS': 'dvh1.05.01', + 'VIDEO-RANGE': 'PQ', + 'BANDWIDTH': 5327059, + 'AVERAGE-BANDWIDTH': '3385450' + }, + uri: 'dolby_720/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'TYPE-0', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 1920, + height: 1080 + }, + 'CODECS': 'dvh1.05.03', + 'VIDEO-RANGE': 'PQ', + 'BANDWIDTH': 12876596, + 'AVERAGE-BANDWIDTH': '7999361' + }, + uri: 'dolby_1080/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'TYPE-1', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 3840, + height: 2160 + }, + 'CODECS': 'dvh1.05.06', + 'VIDEO-RANGE': 'PQ', + 'BANDWIDTH': 30041698, + 'AVERAGE-BANDWIDTH': '24975091' + }, + uri: 'dolby_2160/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'NONE', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 1280, + height: 720 + }, + 'CODECS': 'hvc1.2.4.L123.B0', + 'VIDEO-RANGE': 'PQ', + 'BANDWIDTH': 5280654, + 'AVERAGE-BANDWIDTH': '3320040' + }, + uri: 'hdr10_720/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'TYPE-0', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 1920, + height: 1080 + }, + 'CODECS': 'hvc1.2.4.L123.B0', + 'VIDEO-RANGE': 'PQ', + 'BANDWIDTH': 12886714, + 'AVERAGE-BANDWIDTH': '7964551' + }, + uri: 'hdr10_1080/prog_index.m3u8', + timeline: 0 + }, + { + attributes: { + 'HDCP-LEVEL': 'TYPE-1', + 'CLOSED-CAPTIONS': 'NONE', + 'FRAME-RATE': 23.976, + 'RESOLUTION': { + width: 3840, + height: 2160 + }, + 'CODECS': 'hvc1.2.4.L150.B0', + 'VIDEO-RANGE': 'PQ', + 'BANDWIDTH': 29983769, + 'AVERAGE-BANDWIDTH': '24833402' + }, + uri: 'hdr10_2160/prog_index.m3u8', + timeline: 0 + } + ], + segments: [], + version: 7 +}; diff --git a/test/fixtures/integration/iFramePlaylist.m3u8 b/test/fixtures/integration/iFramePlaylist.m3u8 new file mode 100644 index 0000000..d337619 --- /dev/null +++ b/test/fixtures/integration/iFramePlaylist.m3u8 @@ -0,0 +1,42 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS + +# https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices-appendixes#Example-playlist + +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=248586,BANDWIDTH=593626,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="sdr_720/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=399790,BANDWIDTH=956552,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="sdr_1080/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=826971,BANDWIDTH=1941397,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="sdr_2160/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232253,BANDWIDTH=573073,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="dolby_720/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=365337,BANDWIDTH=905037,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="dolby_1080/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739114,BANDWIDTH=1893236,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="dolby_2160/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232511,BANDWIDTH=572673,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="hdr10_720/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=364552,BANDWIDTH=905053,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="hdr10_1080/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8" + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2778321,BANDWIDTH=3971374,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE +sdr_720/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=6759875,BANDWIDTH=10022043,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0 +sdr_1080/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=20985770,BANDWIDTH=28058971,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1 +sdr_2160/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3385450,BANDWIDTH=5327059,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE +dolby_720/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7999361,BANDWIDTH=12876596,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0 +dolby_1080/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24975091,BANDWIDTH=30041698,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1 +dolby_2160/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3320040,BANDWIDTH=5280654,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE +hdr10_720/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7964551,BANDWIDTH=12886714,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0 +hdr10_1080/prog_index.m3u8 + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24833402,BANDWIDTH=29983769,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1 +hdr10_2160/prog_index.m3u8 diff --git a/test/fixtures/integration/iFramesOnly.js b/test/fixtures/integration/iFramesOnly.js new file mode 100644 index 0000000..a2eb51a --- /dev/null +++ b/test/fixtures/integration/iFramesOnly.js @@ -0,0 +1,45 @@ +module.exports = { + allowCache: true, + dateRanges: [], + iFramePlaylists: [], + iFramesOnly: true, + mediaSequence: 0, + playlistType: 'VOD', + segments: [ + { + duration: 2.002, + timeline: 0, + uri: '001.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '002.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '003.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '004.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '005.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '006.ts' + } + ], + targetDuration: 3, + endList: true, + discontinuitySequence: 0, + discontinuityStarts: [], + version: 4 +}; diff --git a/test/fixtures/integration/iFramesOnly.m3u8 b/test/fixtures/integration/iFramesOnly.m3u8 new file mode 100644 index 0000000..05927dc --- /dev/null +++ b/test/fixtures/integration/iFramesOnly.m3u8 @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-VERSION:4 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-TARGETDURATION:3 +#EXT-X-I-FRAMES-ONLY +#EXTINF:2.002, +001.ts +#EXTINF:2.002, +002.ts +#EXTINF:2.002, +003.ts +#EXTINF:2.002, +004.ts +#EXTINF:2.002, +005.ts +#EXTINF:2.002, +006.ts +#EXT-X-ENDLIST diff --git a/test/fixtures/integration/invalidAllowCache.js b/test/fixtures/integration/invalidAllowCache.js index 615efdb..fc49016 100644 --- a/test/fixtures/integration/invalidAllowCache.js +++ b/test/fixtures/integration/invalidAllowCache.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/invalidMediaSequence.js b/test/fixtures/integration/invalidMediaSequence.js index 8c09122..3e3290d 100644 --- a/test/fixtures/integration/invalidMediaSequence.js +++ b/test/fixtures/integration/invalidMediaSequence.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/invalidPlaylistType.js b/test/fixtures/integration/invalidPlaylistType.js index c695740..4ff371d 100644 --- a/test/fixtures/integration/invalidPlaylistType.js +++ b/test/fixtures/integration/invalidPlaylistType.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/invalidTargetDuration.js b/test/fixtures/integration/invalidTargetDuration.js index 11f8284..32086e6 100644 --- a/test/fixtures/integration/invalidTargetDuration.js +++ b/test/fixtures/integration/invalidTargetDuration.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/liveMissingSegmentDuration.js b/test/fixtures/integration/liveMissingSegmentDuration.js index 40bb91d..03e2a09 100644 --- a/test/fixtures/integration/liveMissingSegmentDuration.js +++ b/test/fixtures/integration/liveMissingSegmentDuration.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/liveStart30sBefore.js b/test/fixtures/integration/liveStart30sBefore.js index 2e75a3b..fb25f77 100644 --- a/test/fixtures/integration/liveStart30sBefore.js +++ b/test/fixtures/integration/liveStart30sBefore.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/llhls-byte-range.js b/test/fixtures/integration/llhls-byte-range.js index 4aa2ecd..6bf4e0e 100644 --- a/test/fixtures/integration/llhls-byte-range.js +++ b/test/fixtures/integration/llhls-byte-range.js @@ -3,6 +3,7 @@ module.exports = { dateRanges: [], discontinuitySequence: 0, discontinuityStarts: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', preloadSegment: { diff --git a/test/fixtures/integration/llhls-delta-byte-range.js b/test/fixtures/integration/llhls-delta-byte-range.js index e44c898..d093d37 100644 --- a/test/fixtures/integration/llhls-delta-byte-range.js +++ b/test/fixtures/integration/llhls-delta-byte-range.js @@ -3,6 +3,7 @@ module.exports = { dateRanges: [], discontinuitySequence: 0, discontinuityStarts: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', preloadSegment: { diff --git a/test/fixtures/integration/llhls.js b/test/fixtures/integration/llhls.js index ae93cbc..93019c5 100644 --- a/test/fixtures/integration/llhls.js +++ b/test/fixtures/integration/llhls.js @@ -5,6 +5,7 @@ module.exports = { dateRanges: [], discontinuitySequence: 0, discontinuityStarts: [], + iFramePlaylists: [], mediaSequence: 266, preloadSegment: { map: {uri: 'init.mp4'}, diff --git a/test/fixtures/integration/llhlsDelta.js b/test/fixtures/integration/llhlsDelta.js index e965d03..8f5176d 100644 --- a/test/fixtures/integration/llhlsDelta.js +++ b/test/fixtures/integration/llhlsDelta.js @@ -5,6 +5,7 @@ module.exports = { dateRanges: [], discontinuitySequence: 0, discontinuityStarts: [], + iFramePlaylists: [], mediaSequence: 266, preloadSegment: { timeline: 0, diff --git a/test/fixtures/integration/manifestExtTTargetdurationNegative.js b/test/fixtures/integration/manifestExtTTargetdurationNegative.js index 4333f0c..cfbdd92 100644 --- a/test/fixtures/integration/manifestExtTTargetdurationNegative.js +++ b/test/fixtures/integration/manifestExtTTargetdurationNegative.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/manifestExtXEndlistEarly.js b/test/fixtures/integration/manifestExtXEndlistEarly.js index 5add189..7cee4ea 100644 --- a/test/fixtures/integration/manifestExtXEndlistEarly.js +++ b/test/fixtures/integration/manifestExtXEndlistEarly.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/manifestNoExtM3u.js b/test/fixtures/integration/manifestNoExtM3u.js index 217a152..18cf5ab 100644 --- a/test/fixtures/integration/manifestNoExtM3u.js +++ b/test/fixtures/integration/manifestNoExtM3u.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/master-fmp4.js b/test/fixtures/integration/master-fmp4.js index c2c28d1..b518ce5 100644 --- a/test/fixtures/integration/master-fmp4.js +++ b/test/fixtures/integration/master-fmp4.js @@ -2,6 +2,92 @@ module.exports = { allowCache: true, dateRanges: [], discontinuityStarts: [], + iFramePlaylists: [ + { + attributes: { + 'AVERAGE-BANDWIDTH': 163198, + 'BANDWIDTH': 166942, + 'CODECS': 'avc1.64002a', + 'RESOLUTION': { + height: 1080, + width: 1920 + }, + 'URI': 'v6/iframe_index.m3u8' + }, + timeline: 0, + uri: 'v6/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 131314, + 'BANDWIDTH': 139041, + 'CODECS': 'avc1.640020', + 'RESOLUTION': { + height: 720, + width: 1280 + }, + 'URI': 'v5/iframe_index.m3u8' + }, + timeline: 0, + uri: 'v5/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 100233, + 'BANDWIDTH': 101724, + 'CODECS': 'avc1.640020', + 'RESOLUTION': { + height: 540, + width: 960 + }, + 'URI': 'v4/iframe_index.m3u8' + }, + timeline: 0, + uri: 'v4/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 81002, + 'BANDWIDTH': 84112, + 'CODECS': 'avc1.64001e', + 'RESOLUTION': { + height: 432, + width: 768 + }, + 'URI': 'v3/iframe_index.m3u8' + }, + timeline: 0, + uri: 'v3/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 64987, + 'BANDWIDTH': 65835, + 'CODECS': 'avc1.64001e', + 'RESOLUTION': { + height: 360, + width: 640 + }, + 'URI': 'v2/iframe_index.m3u8' + }, + timeline: 0, + uri: 'v2/iframe_index.m3u8' + }, + { + attributes: { + 'AVERAGE-BANDWIDTH': 41547, + 'BANDWIDTH': 42106, + 'CODECS': 'avc1.640015', + 'RESOLUTION': { + height: 270, + width: 480 + }, + 'URI': 'v1/iframe_index.m3u8' + }, + timeline: 0, + uri: 'v1/iframe_index.m3u8' + } + ], mediaGroups: { 'AUDIO': { aud1: { diff --git a/test/fixtures/integration/master.js b/test/fixtures/integration/master.js index cab669a..6747f87 100644 --- a/test/fixtures/integration/master.js +++ b/test/fixtures/integration/master.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], playlists: [ { attributes: { diff --git a/test/fixtures/integration/media.js b/test/fixtures/integration/media.js index fad5fa6..7f379b4 100644 --- a/test/fixtures/integration/media.js +++ b/test/fixtures/integration/media.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/mediaSequence.js b/test/fixtures/integration/mediaSequence.js index 8c09122..3e3290d 100644 --- a/test/fixtures/integration/mediaSequence.js +++ b/test/fixtures/integration/mediaSequence.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/missingEndlist.js b/test/fixtures/integration/missingEndlist.js index 764b13b..68f20e9 100644 --- a/test/fixtures/integration/missingEndlist.js +++ b/test/fixtures/integration/missingEndlist.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, segments: [ { diff --git a/test/fixtures/integration/missingExtinf.js b/test/fixtures/integration/missingExtinf.js index 09c017e..b49b05e 100644 --- a/test/fixtures/integration/missingExtinf.js +++ b/test/fixtures/integration/missingExtinf.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/missingMediaSequence.js b/test/fixtures/integration/missingMediaSequence.js index 8c09122..3e3290d 100644 --- a/test/fixtures/integration/missingMediaSequence.js +++ b/test/fixtures/integration/missingMediaSequence.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/missingSegmentDuration.js b/test/fixtures/integration/missingSegmentDuration.js index bf61d3f..29ab154 100644 --- a/test/fixtures/integration/missingSegmentDuration.js +++ b/test/fixtures/integration/missingSegmentDuration.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/multipleAudioGroups.js b/test/fixtures/integration/multipleAudioGroups.js index 44e0cd1..d20d5b3 100644 --- a/test/fixtures/integration/multipleAudioGroups.js +++ b/test/fixtures/integration/multipleAudioGroups.js @@ -2,6 +2,7 @@ module.exports = { allowCache: true, dateRanges: [], discontinuityStarts: [], + iFramePlaylists: [], mediaGroups: { 'AUDIO': { 'audio-lo': { diff --git a/test/fixtures/integration/multipleAudioGroupsCombinedMain.js b/test/fixtures/integration/multipleAudioGroupsCombinedMain.js index c6dd090..540b253 100644 --- a/test/fixtures/integration/multipleAudioGroupsCombinedMain.js +++ b/test/fixtures/integration/multipleAudioGroupsCombinedMain.js @@ -2,6 +2,7 @@ module.exports = { allowCache: true, dateRanges: [], discontinuityStarts: [], + iFramePlaylists: [], mediaGroups: { 'AUDIO': { 'audio-lo': { diff --git a/test/fixtures/integration/multipleTargetDurations.js b/test/fixtures/integration/multipleTargetDurations.js index a2f1691..a722e8d 100644 --- a/test/fixtures/integration/multipleTargetDurations.js +++ b/test/fixtures/integration/multipleTargetDurations.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, targetDuration: 10, segments: [ diff --git a/test/fixtures/integration/multipleVideo.js b/test/fixtures/integration/multipleVideo.js index 63da1c2..606b4b1 100644 --- a/test/fixtures/integration/multipleVideo.js +++ b/test/fixtures/integration/multipleVideo.js @@ -2,6 +2,7 @@ module.exports = { allowCache: true, dateRanges: [], discontinuityStarts: [], + iFramePlaylists: [], mediaGroups: { 'AUDIO': { aac: { diff --git a/test/fixtures/integration/negativeMediaSequence.js b/test/fixtures/integration/negativeMediaSequence.js index c12be87..72e4f05 100644 --- a/test/fixtures/integration/negativeMediaSequence.js +++ b/test/fixtures/integration/negativeMediaSequence.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: -11, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/playlist.js b/test/fixtures/integration/playlist.js index ac7bf24..e5cf932 100644 --- a/test/fixtures/integration/playlist.js +++ b/test/fixtures/integration/playlist.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/playlistMediaSequenceHigher.js b/test/fixtures/integration/playlistMediaSequenceHigher.js index eb67dbc..793cb7a 100644 --- a/test/fixtures/integration/playlistMediaSequenceHigher.js +++ b/test/fixtures/integration/playlistMediaSequenceHigher.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 17, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/start.js b/test/fixtures/integration/start.js index baba2cd..25d34e6 100644 --- a/test/fixtures/integration/start.js +++ b/test/fixtures/integration/start.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/streamInfInvalid.js b/test/fixtures/integration/streamInfInvalid.js index 8caf144..4d4fb42 100644 --- a/test/fixtures/integration/streamInfInvalid.js +++ b/test/fixtures/integration/streamInfInvalid.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], playlists: [ { attributes: { diff --git a/test/fixtures/integration/twoMediaSequences.js b/test/fixtures/integration/twoMediaSequences.js index 92c6f3b..015268b 100644 --- a/test/fixtures/integration/twoMediaSequences.js +++ b/test/fixtures/integration/twoMediaSequences.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 11, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/versionInvalid.js b/test/fixtures/integration/versionInvalid.js index 0efbbd2..5bb83b0 100644 --- a/test/fixtures/integration/versionInvalid.js +++ b/test/fixtures/integration/versionInvalid.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/whiteSpace.js b/test/fixtures/integration/whiteSpace.js index d45c28c..43337e6 100644 --- a/test/fixtures/integration/whiteSpace.js +++ b/test/fixtures/integration/whiteSpace.js @@ -1,6 +1,7 @@ module.exports = { allowCache: true, dateRanges: [], + iFramePlaylists: [], mediaSequence: 0, playlistType: 'VOD', segments: [ diff --git a/test/fixtures/integration/zeroDuration.js b/test/fixtures/integration/zeroDuration.js index f9b091c..57b19d1 100644 --- a/test/fixtures/integration/zeroDuration.js +++ b/test/fixtures/integration/zeroDuration.js @@ -2,6 +2,7 @@ module.exports = { allowCache: true, mediaSequence: 0, dateRanges: [], + iFramePlaylists: [], playlistType: 'VOD', segments: [ { diff --git a/test/parse-stream.test.js b/test/parse-stream.test.js index ed51fcb..421912d 100644 --- a/test/parse-stream.test.js +++ b/test/parse-stream.test.js @@ -997,3 +997,40 @@ QUnit.test('ignores empty lines', function(assert) { assert.ok(!event, 'no event is triggered'); }); + +QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) { + const manifest = '#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8"\n'; + let element; + + this.parseStream.on('data', function(elem) { + element = elem; + }); + this.lineStream.push(manifest); + + assert.ok(element, 'an event was triggered'); + assert.strictEqual(element.type, 'tag', 'the line type is tag'); + assert.strictEqual(element.tagType, 'i-frame-playlist', 'the tag type is i-frame-playlist'); + assert.strictEqual(element.uri, 'hdr10_2160/iframe_index.m3u8', 'the uri text is parsed'); + assert.strictEqual(element.attributes['AVERAGE-BANDWIDTH'], 739757, 'the average bandwidth is parsed'); + assert.strictEqual(element.attributes.BANDWIDTH, 1895477, 'the bandwidth is parsed'); + assert.strictEqual(element.attributes['VIDEO-RANGE'], 'PQ', 'the video range is parsed'); + assert.strictEqual(element.attributes.CODECS, 'hvc1.2.4.L150.B0', 'the codecs is parsed'); + assert.strictEqual(element.attributes.RESOLUTION.width, 3840, 'the resolution width is parsed'); + assert.strictEqual(element.attributes.RESOLUTION.height, 2160, 'the resolution height is parsed'); + assert.strictEqual(element.attributes['HDCP-LEVEL'], 'TYPE-1', 'the HDCP level is parsed'); + assert.strictEqual(element.attributes.URI, 'hdr10_2160/iframe_index.m3u8', 'the uri text is parsed'); +}); + +QUnit.test('parses #EXT-X-I-FRAMES-ONLY', function(assert) { + const manifest = '#EXT-X-I-FRAMES-ONLY\n'; + let element; + + this.parseStream.on('data', function(elem) { + element = elem; + }); + this.lineStream.push(manifest); + + assert.ok(element, 'an event was triggered'); + assert.strictEqual(element.type, 'tag', 'the line type is tag'); + assert.strictEqual(element.tagType, 'i-frames-only', 'the tag type is i-frames-only'); +}); diff --git a/test/parser.test.js b/test/parser.test.js index 718c400..346ef71 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -1158,6 +1158,75 @@ QUnit.module('m3u8s', function(hooks) { assert.equal(this.parser.manifest.independentSegments, true); }); + QUnit.test('warns when #EXT-X-I-FRAMES-ONLY the minimum version required is not supported', function(assert) { + this.parser.push([ + '#EXTM3U', + '#EXT-X-VERSION:3', + '#EXT-X-PLAYLIST-TYPE:VOD', + '#EXT-X-MEDIA-SEQUENCE:0', + '#EXT-X-TARGETDURATION:3', + '#EXT-X-I-FRAMES-ONLY', + '#EXTINF:2.002,', + '001.ts', + '#EXTINF:2.002,', + '002.ts', + '#EXTINF:2.002,', + '003.ts', + '#EXTINF:2.002,', + '004.ts', + '#EXTINF:2.002,', + '005.ts', + '#EXTINF:2.002,', + '006.ts', + '#EXT-X-ENDLIST' + ].join('\n')); + this.parser.end(); + + const warnings = [ + 'manifest must be at least version 4' + ]; + + assert.deepEqual( + this.warnings, + warnings, + 'warnings as expected' + ); + }); + + QUnit.test('warns when #EXT-X-I-FRAMES-ONLY does not contain a version number', function(assert) { + this.parser.push([ + '#EXTM3U', + '#EXT-X-PLAYLIST-TYPE:VOD', + '#EXT-X-MEDIA-SEQUENCE:0', + '#EXT-X-TARGETDURATION:3', + '#EXT-X-I-FRAMES-ONLY', + '#EXTINF:2.002,', + '001.ts', + '#EXTINF:2.002,', + '002.ts', + '#EXTINF:2.002,', + '003.ts', + '#EXTINF:2.002,', + '004.ts', + '#EXTINF:2.002,', + '005.ts', + '#EXTINF:2.002,', + '006.ts', + '#EXT-X-ENDLIST' + ].join('\n')); + this.parser.end(); + + const warnings = [ + 'manifest must be at least version 4' + ]; + + assert.deepEqual( + this.warnings, + warnings, + 'warnings as expected' + ); + }); + QUnit.test('parses #EXT-X-CONTENT-STEERING', function(assert) { const expectedContentSteeringObject = { serverUri: '/foo?bar=00012', @@ -1336,6 +1405,57 @@ QUnit.module('m3u8s', function(hooks) { assert.equal('eng/prog_index.m3u8?bParam=bValue', this.parser.manifest.mediaGroups.AUDIO.aac.Anglais.uri, 'replacement in uri in attribute'); }); + QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) { + this.parser.push([ + '#EXTM3U', + '#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=86000,URI="low/iframe.m3u8"', + '#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"', + '#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=550000,URI="hi/iframe.m3u8"', + '#EXT-X-STREAM-INF:BANDWIDTH=1280000', + 'low/audio-video.m3u8', + '#EXT-X-STREAM-INF:BANDWIDTH=2560000', + 'mid/audio-video.m3u8', + '#EXT-X-STREAM-INF:BANDWIDTH=7680000', + 'hi/audio-video.m3u8', + '#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"', + 'audio-only.m3u8' + ].join('\n')); + this.parser.end(); + + assert.equal(this.parser.manifest.iFramePlaylists.length, 3); + assert.equal(this.parser.manifest.iFramePlaylists[0].uri, 'low/iframe.m3u8'); + assert.strictEqual(this.parser.manifest.iFramePlaylists[0].attributes.BANDWIDTH, 86000); + }); + + QUnit.test('warns when #EXT-X-I-FRAME-STREAM-INF missing BANDWIDTH/URI attributes', function(assert) { + this.parser.push([ + '#EXTM3U', + '#EXT-X-I-FRAME-STREAM-INF:URI="low/iframe.m3u8"', + '#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"', + '#EXT-X-I-FRAME-STREAM-INF:', + '#EXT-X-STREAM-INF:BANDWIDTH=1280000', + 'low/audio-video.m3u8', + '#EXT-X-STREAM-INF:BANDWIDTH=2560000', + 'mid/audio-video.m3u8', + '#EXT-X-STREAM-INF:BANDWIDTH=7680000', + 'hi/audio-video.m3u8', + '#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"', + 'audio-only.m3u8' + ].join('\n')); + this.parser.end(); + + const warnings = [ + '#EXT-X-I-FRAME-STREAM-INF lacks required attribute(s): BANDWIDTH', + '#EXT-X-I-FRAME-STREAM-INF lacks required attribute(s): BANDWIDTH, URI' + ]; + + assert.deepEqual( + this.warnings, + warnings, + 'warnings as expected' + ); + }); + QUnit.module('integration'); for (const key in testDataExpected) {