From fee18708927175c1fcdef7549197cc555b884604 Mon Sep 17 00:00:00 2001 From: Walter Seymour Date: Wed, 30 Aug 2023 11:47:25 -0500 Subject: [PATCH] fix: improve serviceLocation for content steering (#177) --- src/inheritAttributes.js | 16 +- src/toM3u8.js | 104 +++-- test/inheritAttributes.test.js | 13 +- test/manifests/608-captions.js | 2 +- test/manifests/708-captions.js | 2 +- test/manifests/audio-only.js | 4 +- test/manifests/location.js | 2 +- test/manifests/locations.js | 2 +- test/manifests/maat_vtt_segmentTemplate.js | 12 +- test/manifests/multiperiod-segment-list.js | 2 +- .../manifests/multiperiod-segment-template.js | 4 +- ...multiperiod-startnumber-removed-periods.js | 38 +- test/manifests/multiperiod-startnumber.js | 431 ++++++++++-------- test/manifests/segmentBase.js | 2 +- test/manifests/segmentList.js | 4 +- test/manifests/vtt_codecs.js | 12 +- test/manifests/webmsegments.js | 4 +- test/toM3u8.test.js | 367 ++++++++++++++- test/toPlaylists.test.js | 201 ++++++++ 19 files changed, 944 insertions(+), 278 deletions(-) diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 84ec1dff..f6983a6e 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -30,7 +30,21 @@ export const buildBaseUrls = (references, baseUrlElements) => { return flatten(references.map(function(reference) { return baseUrlElements.map(function(baseUrlElement) { - return merge(parseAttributes(baseUrlElement), { baseUrl: resolveUrl(reference.baseUrl, getContent(baseUrlElement)) }); + const initialBaseUrl = getContent(baseUrlElement); + const resolvedBaseUrl = resolveUrl(reference.baseUrl, initialBaseUrl); + + const finalBaseUrl = merge( + parseAttributes(baseUrlElement), + { baseUrl: resolvedBaseUrl } + ); + + // If the URL is resolved, we want to get the serviceLocation from the reference + // assuming there is no serviceLocation on the initialBaseUrl + if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) { + finalBaseUrl.serviceLocation = reference.serviceLocation; + } + + return finalBaseUrl; }); })); }; diff --git a/src/toM3u8.js b/src/toM3u8.js index 01ba6255..287f7da9 100644 --- a/src/toM3u8.js +++ b/src/toM3u8.js @@ -11,45 +11,62 @@ export const generateSidxKey = (sidx) => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange); const mergeDiscontiguousPlaylists = playlists => { - const mergedPlaylists = values(playlists.reduce((acc, playlist) => { - // assuming playlist IDs are the same across periods - // TODO: handle multiperiod where representation sets are not the same - // across periods - const name = playlist.attributes.id + (playlist.attributes.lang || ''); - - if (!acc[name]) { - // First Period - acc[name] = playlist; - acc[name].attributes.timelineStarts = []; - } else { - // Subsequent Periods - if (playlist.segments) { - // first segment of subsequent periods signal a discontinuity - if (playlist.segments[0]) { - playlist.segments[0].discontinuity = true; + // Break out playlists into groups based on their baseUrl + const playlistsByBaseUrl = playlists.reduce(function(acc, cur) { + if (!acc[cur.attributes.baseUrl]) { + acc[cur.attributes.baseUrl] = []; + } + + acc[cur.attributes.baseUrl].push(cur); + + return acc; + }, {}); + + let allPlaylists = []; + + Object.values(playlistsByBaseUrl).forEach((playlistGroup) => { + const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => { + // assuming playlist IDs are the same across periods + // TODO: handle multiperiod where representation sets are not the same + // across periods + const name = playlist.attributes.id + (playlist.attributes.lang || ''); + + if (!acc[name]) { + // First Period + acc[name] = playlist; + acc[name].attributes.timelineStarts = []; + } else { + // Subsequent Periods + if (playlist.segments) { + // first segment of subsequent periods signal a discontinuity + if (playlist.segments[0]) { + playlist.segments[0].discontinuity = true; + } + acc[name].segments.push(...playlist.segments); } - acc[name].segments.push(...playlist.segments); - } - // bubble up contentProtection, this assumes all DRM content - // has the same contentProtection - if (playlist.attributes.contentProtection) { - acc[name].attributes.contentProtection = - playlist.attributes.contentProtection; + // bubble up contentProtection, this assumes all DRM content + // has the same contentProtection + if (playlist.attributes.contentProtection) { + acc[name].attributes.contentProtection = + playlist.attributes.contentProtection; + } } - } - acc[name].attributes.timelineStarts.push({ - // Although they represent the same number, it's important to have both to make it - // compatible with HLS potentially having a similar attribute. - start: playlist.attributes.periodStart, - timeline: playlist.attributes.periodStart - }); + acc[name].attributes.timelineStarts.push({ + // Although they represent the same number, it's important to have both to make it + // compatible with HLS potentially having a similar attribute. + start: playlist.attributes.periodStart, + timeline: playlist.attributes.periodStart + }); - return acc; - }, {})); + return acc; + }, {})); - return mergedPlaylists.map(playlist => { + allPlaylists = allPlaylists.concat(mergedPlaylists); + }); + + return allPlaylists.map(playlist => { playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity'); @@ -98,7 +115,7 @@ export const formatAudioPlaylist = ({ uri: '', endList: attributes.type === 'static', timeline: attributes.periodStart, - resolvedUri: '', + resolvedUri: attributes.baseUrl || '', targetDuration: attributes.duration, discontinuitySequence, discontinuityStarts, @@ -111,6 +128,10 @@ export const formatAudioPlaylist = ({ playlist.contentProtection = attributes.contentProtection; } + if (attributes.serviceLocation) { + playlist.attributes.serviceLocation = attributes.serviceLocation; + } + if (sidx) { playlist.sidx = sidx; } @@ -139,6 +160,7 @@ export const formatVttPlaylist = ({ duration: attributes.sourceDuration, number: 0 }]; + // targetDuration should be the same duration as the only segment attributes.duration = attributes.sourceDuration; } @@ -152,7 +174,7 @@ export const formatVttPlaylist = ({ if (attributes.codecs) { m3u8Attributes.CODECS = attributes.codecs; } - return { + const vttPlaylist = { attributes: m3u8Attributes, uri: '', endList: attributes.type === 'static', @@ -165,6 +187,12 @@ export const formatVttPlaylist = ({ mediaSequence, segments }; + + if (attributes.serviceLocation) { + vttPlaylist.attributes.serviceLocation = attributes.serviceLocation; + } + + return vttPlaylist; }; export const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => { @@ -289,7 +317,7 @@ export const formatVideoPlaylist = ({ uri: '', endList: attributes.type === 'static', timeline: attributes.periodStart, - resolvedUri: '', + resolvedUri: attributes.baseUrl || '', targetDuration: attributes.duration, discontinuityStarts, timelineStarts: attributes.timelineStarts, @@ -304,6 +332,10 @@ export const formatVideoPlaylist = ({ playlist.contentProtection = attributes.contentProtection; } + if (attributes.serviceLocation) { + playlist.attributes.serviceLocation = attributes.serviceLocation; + } + if (sidx) { playlist.sidx = sidx; } diff --git a/test/inheritAttributes.test.js b/test/inheritAttributes.test.js index a489ef7c..dae013fe 100644 --- a/test/inheritAttributes.test.js +++ b/test/inheritAttributes.test.js @@ -927,10 +927,11 @@ QUnit.test('end to end - content steering - resolvable base URLs', function(asse id="test" width="720"> + /video - /video + /vtt @@ -957,7 +958,7 @@ QUnit.test('end to end - content steering - resolvable base URLs', function(asse attributes: { NOW, bandwidth: 5000000, - baseUrl: 'https://cdn1.example.com/', + baseUrl: 'https://cdn1.example.com/video', clientOffset: 0, codecs: 'avc1.64001e', height: 404, @@ -980,7 +981,7 @@ QUnit.test('end to end - content steering - resolvable base URLs', function(asse attributes: { NOW, bandwidth: 5000000, - baseUrl: 'https://cdn2.example.com/', + baseUrl: 'https://cdn2.example.com/video', clientOffset: 0, codecs: 'avc1.64001e', height: 404, @@ -1003,13 +1004,14 @@ QUnit.test('end to end - content steering - resolvable base URLs', function(asse attributes: { NOW, bandwidth: 256, - baseUrl: 'https://cdn1.example.com/video', + baseUrl: 'https://cdn1.example.com/vtt', clientOffset: 0, id: 'en', lang: 'en', mimeType: 'text/vtt', periodStart: 0, role: {}, + serviceLocation: 'alpha', sourceDuration: 0, type: 'dyanmic' }, @@ -1019,13 +1021,14 @@ QUnit.test('end to end - content steering - resolvable base URLs', function(asse attributes: { NOW, bandwidth: 256, - baseUrl: 'https://cdn2.example.com/video', + baseUrl: 'https://cdn2.example.com/vtt', clientOffset: 0, id: 'en', lang: 'en', mimeType: 'text/vtt', periodStart: 0, role: {}, + serviceLocation: 'beta', sourceDuration: 0, type: 'dyanmic' }, diff --git a/test/manifests/608-captions.js b/test/manifests/608-captions.js index ae12c995..cf505439 100644 --- a/test/manifests/608-captions.js +++ b/test/manifests/608-captions.js @@ -46,7 +46,7 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: true, - resolvedUri: '', + resolvedUri: 'https://www.example.com/1080p.ts', targetDuration: 6, mediaSequence: 0, timelineStarts: [{ start: 0, timeline: 0 }], diff --git a/test/manifests/708-captions.js b/test/manifests/708-captions.js index 4b3e682a..5df7a048 100644 --- a/test/manifests/708-captions.js +++ b/test/manifests/708-captions.js @@ -47,7 +47,7 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: true, - resolvedUri: '', + resolvedUri: 'https://www.example.com/1080p.ts', targetDuration: 6, mediaSequence: 0, timelineStarts: [{ start: 0, timeline: 0 }], diff --git a/test/manifests/audio-only.js b/test/manifests/audio-only.js index be2d860c..e93327ec 100644 --- a/test/manifests/audio-only.js +++ b/test/manifests/audio-only.js @@ -27,7 +27,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'http://example.com/audio_en_2c_128k_aac.mp4', targetDuration: 60, segments: [], mediaSequence: 0, @@ -78,7 +78,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'http://example.com/audio_es_2c_128k_aac.mp4', targetDuration: 60, segments: [], mediaSequence: 0, diff --git a/test/manifests/location.js b/test/manifests/location.js index c7fe2469..6b9ae7b9 100644 --- a/test/manifests/location.js +++ b/test/manifests/location.js @@ -32,7 +32,7 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: true, - resolvedUri: '', + resolvedUri: 'https://www.example.com/1080p.ts', targetDuration: 6, mediaSequence: 0, discontinuitySequence: 0, diff --git a/test/manifests/locations.js b/test/manifests/locations.js index 6b2ac590..86911edf 100644 --- a/test/manifests/locations.js +++ b/test/manifests/locations.js @@ -33,7 +33,7 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: true, - resolvedUri: '', + resolvedUri: 'https://www.example.com/1080p.ts', targetDuration: 6, mediaSequence: 0, discontinuitySequence: 0, diff --git a/test/manifests/maat_vtt_segmentTemplate.js b/test/manifests/maat_vtt_segmentTemplate.js index b7bbc6e9..38894c9c 100644 --- a/test/manifests/maat_vtt_segmentTemplate.js +++ b/test/manifests/maat_vtt_segmentTemplate.js @@ -25,7 +25,7 @@ export const parsedManifest = { timelineStarts: [{ start: 0, timeline: 0 }], discontinuitySequence: 0, discontinuityStarts: [], - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -100,7 +100,7 @@ export const parsedManifest = { timelineStarts: [{ start: 0, timeline: 0 }], discontinuitySequence: 0, discontinuityStarts: [], - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -183,7 +183,7 @@ export const parsedManifest = { timelineStarts: [{ start: 0, timeline: 0 }], discontinuitySequence: 0, discontinuityStarts: [], - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -258,7 +258,7 @@ export const parsedManifest = { timelineStarts: [{ start: 0, timeline: 0 }], discontinuitySequence: 0, discontinuityStarts: [], - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -421,7 +421,7 @@ export const parsedManifest = { timelineStarts: [{ start: 0, timeline: 0 }], discontinuitySequence: 0, discontinuityStarts: [], - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.9185833333333333, segments: [ { @@ -503,7 +503,7 @@ export const parsedManifest = { timelineStarts: [{ start: 0, timeline: 0 }], discontinuitySequence: 0, discontinuityStarts: [], - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.9185833333333333, segments: [ { diff --git a/test/manifests/multiperiod-segment-list.js b/test/manifests/multiperiod-segment-list.js index aaf8be6e..fefcccad 100644 --- a/test/manifests/multiperiod-segment-list.js +++ b/test/manifests/multiperiod-segment-list.js @@ -36,7 +36,7 @@ export const parsedManifest = { timeline: 6 }], targetDuration: 3, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', segments: [ { duration: 3, diff --git a/test/manifests/multiperiod-segment-template.js b/test/manifests/multiperiod-segment-template.js index 6cc21b98..139a3a75 100644 --- a/test/manifests/multiperiod-segment-template.js +++ b/test/manifests/multiperiod-segment-template.js @@ -30,7 +30,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 5, segments: [ { @@ -145,7 +145,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 5, segments: [ { diff --git a/test/manifests/multiperiod-startnumber-removed-periods.js b/test/manifests/multiperiod-startnumber-removed-periods.js index e81f653f..fdcbf5ca 100644 --- a/test/manifests/multiperiod-startnumber-removed-periods.js +++ b/test/manifests/multiperiod-startnumber-removed-periods.js @@ -25,13 +25,13 @@ export const parsedManifest = { 'PROGRAM-ID': 1 }, endList: false, - mediaSequence: 7, + mediaSequence: 3, discontinuitySequence: 2, discontinuityStarts: [0], timelineStarts: [ { start: 111, timeline: 111} ], - resolvedUri: '', + resolvedUri: 'http://example.com/audio/v0/', segments: [ { discontinuity: true, @@ -41,7 +41,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 111, - number: 7, + number: 3, resolvedUri: 'http://example.com/audio/v0/862.m4f', timeline: 111, uri: '862.m4f' @@ -53,7 +53,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 112, - number: 8, + number: 4, resolvedUri: 'http://example.com/audio/v0/863.m4f', timeline: 111, uri: '863.m4f' @@ -65,7 +65,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 113, - number: 9, + number: 5, resolvedUri: 'http://example.com/audio/v0/864.m4f', timeline: 111, uri: '864.m4f' @@ -107,7 +107,7 @@ export const parsedManifest = { timelineStarts: [ { start: 111, timeline: 111} ], - resolvedUri: '', + resolvedUri: 'http://example.com/video/D/', segments: [ { discontinuity: true, @@ -172,7 +172,7 @@ export const parsedManifest = { { start: 111, timeline: 111} ], discontinuityStarts: [0], - resolvedUri: '', + resolvedUri: 'http://example.com/video/E/', segments: [ { discontinuity: true, @@ -231,13 +231,13 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: false, - mediaSequence: 7, + mediaSequence: 3, discontinuitySequence: 2, timelineStarts: [ { start: 111, timeline: 111} ], discontinuityStarts: [0], - resolvedUri: '', + resolvedUri: 'http://example.com/video/F/', segments: [ { discontinuity: true, @@ -247,7 +247,7 @@ export const parsedManifest = { uri: 'F_init.mp4' }, presentationTime: 111, - number: 7, + number: 3, resolvedUri: 'http://example.com/video/F/F862.m4f', timeline: 111, uri: 'F862.m4f' @@ -259,7 +259,7 @@ export const parsedManifest = { uri: 'F_init.mp4' }, presentationTime: 112, - number: 8, + number: 4, resolvedUri: 'http://example.com/video/F/F863.m4f', timeline: 111, uri: 'F863.m4f' @@ -271,7 +271,7 @@ export const parsedManifest = { uri: 'F_init.mp4' }, presentationTime: 113, - number: 9, + number: 5, resolvedUri: 'http://example.com/video/F/F864.m4f', timeline: 111, uri: 'F864.m4f' @@ -302,7 +302,7 @@ export const parsedManifest = { { start: 111, timeline: 111} ], discontinuityStarts: [0], - resolvedUri: '', + resolvedUri: 'http://example.com/video/A/', segments: [ { discontinuity: true, @@ -367,7 +367,7 @@ export const parsedManifest = { { start: 111, timeline: 111} ], discontinuityStarts: [0], - resolvedUri: '', + resolvedUri: 'http://example.com/video/B/', segments: [ { discontinuity: true, @@ -426,13 +426,13 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: false, - mediaSequence: 7, + mediaSequence: 3, discontinuitySequence: 2, timelineStarts: [ { start: 111, timeline: 111} ], discontinuityStarts: [0], - resolvedUri: '', + resolvedUri: 'http://example.com/video/C/', segments: [ { discontinuity: true, @@ -442,7 +442,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 111, - number: 7, + number: 3, resolvedUri: 'http://example.com/video/C/C862.m4f', timeline: 111, uri: 'C862.m4f' @@ -454,7 +454,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 112, - number: 8, + number: 4, resolvedUri: 'http://example.com/video/C/C863.m4f', timeline: 111, uri: 'C863.m4f' @@ -466,7 +466,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 113, - number: 9, + number: 5, resolvedUri: 'http://example.com/video/C/C864.m4f', timeline: 111, uri: 'C864.m4f' diff --git a/test/manifests/multiperiod-startnumber.js b/test/manifests/multiperiod-startnumber.js index a7adeacb..9d453589 100644 --- a/test/manifests/multiperiod-startnumber.js +++ b/test/manifests/multiperiod-startnumber.js @@ -27,14 +27,8 @@ export const parsedManifest = { endList: false, mediaSequence: 0, discontinuitySequence: 0, - discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + discontinuityStarts: [], + resolvedUri: 'http://example.com/audio/1', segments: [ { duration: 1, @@ -71,16 +65,36 @@ export const parsedManifest = { resolvedUri: 'http://example.com/audio/502.m4f', timeline: 100, uri: '502.m4f' - }, + } + ], + targetDuration: 1, + timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100} + ], + uri: '' + }, + { + attributes: { + 'BANDWIDTH': 128352, + 'CODECS': 'mp4a.40.5', + 'NAME': 'v0', + 'PROGRAM-ID': 1 + }, + discontinuitySequence: 1, + discontinuityStarts: [2, 4], + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/audio/v0/', + segments: [ { - discontinuity: true, duration: 2, map: { resolvedUri: 'http://example.com/audio/v0/init.mp4', uri: 'init.mp4' }, presentationTime: 103, - number: 3, + number: 0, resolvedUri: 'http://example.com/audio/v0/000.m4f', timeline: 103, uri: '000.m4f' @@ -92,7 +106,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 105, - number: 4, + number: 1, resolvedUri: 'http://example.com/audio/v0/001.m4f', timeline: 103, uri: '001.m4f' @@ -105,7 +119,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 107, - number: 5, + number: 2, resolvedUri: 'http://example.com/audio/v0/000.m4f', timeline: 107, uri: '000.m4f' @@ -117,7 +131,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 109, - number: 6, + number: 3, resolvedUri: 'http://example.com/audio/v0/001.m4f', timeline: 107, uri: '001.m4f' @@ -130,7 +144,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 111, - number: 7, + number: 4, resolvedUri: 'http://example.com/audio/v0/862.m4f', timeline: 111, uri: '862.m4f' @@ -142,7 +156,7 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 112, - number: 8, + number: 5, resolvedUri: 'http://example.com/audio/v0/863.m4f', timeline: 111, uri: '863.m4f' @@ -154,14 +168,19 @@ export const parsedManifest = { uri: 'init.mp4' }, presentationTime: 113, - number: 9, + number: 6, resolvedUri: 'http://example.com/audio/v0/864.m4f', timeline: 111, uri: '864.m4f' } ], - targetDuration: 1, - timeline: 100, + targetDuration: 2, + timeline: 103, + timelineStarts: [ + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], uri: '' } ], @@ -189,17 +208,11 @@ export const parsedManifest = { }, 'SUBTITLES': 'subs' }, - endList: false, - mediaSequence: 0, discontinuitySequence: 0, discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/video/D/', segments: [ { duration: 1, @@ -327,6 +340,12 @@ export const parsedManifest = { ], targetDuration: 1, timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100}, + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], uri: '' }, { @@ -343,17 +362,11 @@ export const parsedManifest = { }, 'SUBTITLES': 'subs' }, - endList: false, - mediaSequence: 0, discontinuitySequence: 0, discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/video/E/', segments: [ { duration: 1, @@ -481,6 +494,12 @@ export const parsedManifest = { ], targetDuration: 1, timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100}, + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], uri: '' }, { @@ -497,17 +516,11 @@ export const parsedManifest = { }, 'SUBTITLES': 'subs' }, + discontinuitySequence: 0, + discontinuityStarts: [], endList: false, mediaSequence: 0, - discontinuitySequence: 0, - discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + resolvedUri: 'http://example.com/video/E/', segments: [ { duration: 1, @@ -544,97 +557,77 @@ export const parsedManifest = { resolvedUri: 'http://example.com/video/E/F502.m4f', timeline: 100, uri: 'F502.m4f' + } + ], + targetDuration: 1, + timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100} + ], + uri: '' + }, + { + attributes: { + 'AUDIO': 'audio', + 'BANDWIDTH': 1277155, + 'CODECS': 'avc1.4d001e', + 'FRAME-RATE': 30, + 'NAME': 'C', + 'PROGRAM-ID': 1, + 'RESOLUTION': { + height: 540, + width: 960 }, + 'SUBTITLES': 'subs' + }, + discontinuitySequence: 0, + discontinuityStarts: [], + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/video/E/', + segments: [ { - discontinuity: true, - duration: 2, - map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' - }, - presentationTime: 103, - number: 3, - resolvedUri: 'http://example.com/video/F/F000.m4f', - timeline: 103, - uri: 'F000.m4f' - }, - { - duration: 2, - map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' - }, - presentationTime: 105, - number: 4, - resolvedUri: 'http://example.com/video/F/F001.m4f', - timeline: 103, - uri: 'F001.m4f' - }, - { - discontinuity: true, - duration: 1, - map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' - }, - presentationTime: 107, - number: 5, - resolvedUri: 'http://example.com/video/F/F000.m4f', - timeline: 107, - uri: 'F000.m4f' - }, - { - duration: 1, - map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' - }, - presentationTime: 108, - number: 6, - resolvedUri: 'http://example.com/video/F/F001.m4f', - timeline: 107, - uri: 'F001.m4f' - }, - { - discontinuity: true, duration: 1, map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' + resolvedUri: 'http://example.com/video/E/C_init.mp4', + uri: 'C_init.mp4' }, - presentationTime: 111, - number: 7, - resolvedUri: 'http://example.com/video/F/F862.m4f', - timeline: 111, - uri: 'F862.m4f' + presentationTime: 100, + number: 0, + resolvedUri: 'http://example.com/video/E/C500.m4f', + timeline: 100, + uri: 'C500.m4f' }, { duration: 1, map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' + resolvedUri: 'http://example.com/video/E/C_init.mp4', + uri: 'C_init.mp4' }, - presentationTime: 112, - number: 8, - resolvedUri: 'http://example.com/video/F/F863.m4f', - timeline: 111, - uri: 'F863.m4f' + presentationTime: 101, + number: 1, + resolvedUri: 'http://example.com/video/E/C501.m4f', + timeline: 100, + uri: 'C501.m4f' }, { duration: 1, map: { - resolvedUri: 'http://example.com/video/F/F_init.mp4', - uri: 'F_init.mp4' + resolvedUri: 'http://example.com/video/E/C_init.mp4', + uri: 'C_init.mp4' }, - presentationTime: 113, - number: 9, - resolvedUri: 'http://example.com/video/F/F864.m4f', - timeline: 111, - uri: 'F864.m4f' + presentationTime: 102, + number: 2, + resolvedUri: 'http://example.com/video/E/C502.m4f', + timeline: 100, + uri: 'C502.m4f' } ], targetDuration: 1, timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100} + ], uri: '' }, { @@ -651,17 +644,11 @@ export const parsedManifest = { }, 'SUBTITLES': 'subs' }, - endList: false, - mediaSequence: 0, discontinuitySequence: 0, discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/video/A/', segments: [ { duration: 1, @@ -789,6 +776,12 @@ export const parsedManifest = { ], targetDuration: 1, timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100}, + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], uri: '' }, { @@ -805,17 +798,11 @@ export const parsedManifest = { }, 'SUBTITLES': 'subs' }, - endList: false, - mediaSequence: 0, discontinuitySequence: 0, discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/video/B/', segments: [ { duration: 1, @@ -943,79 +930,158 @@ export const parsedManifest = { ], targetDuration: 1, timeline: 100, + timelineStarts: [ + { start: 100, timeline: 100}, + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], uri: '' }, { attributes: { 'AUDIO': 'audio', - 'BANDWIDTH': 1277155, - 'CODECS': 'avc1.4d001e', - 'FRAME-RATE': 30, - 'NAME': 'C', + 'BANDWIDTH': 2215557, + 'CODECS': 'avc1.640020', + 'FRAME-RATE': 60, + 'NAME': 'F', 'PROGRAM-ID': 1, 'RESOLUTION': { - height: 540, - width: 960 + height: 720, + width: 1280 }, 'SUBTITLES': 'subs' }, + discontinuitySequence: 1, + discontinuityStarts: [2, 4], endList: false, mediaSequence: 0, - discontinuitySequence: 0, - discontinuityStarts: [3, 5, 7], - timelineStarts: [ - { start: 100, timeline: 100}, - { start: 103, timeline: 103}, - { start: 107, timeline: 107}, - { start: 111, timeline: 111} - ], - resolvedUri: '', + resolvedUri: 'http://example.com/video/F/', segments: [ { - duration: 1, + duration: 2, map: { - resolvedUri: 'http://example.com/video/E/C_init.mp4', - uri: 'C_init.mp4' + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' }, - presentationTime: 100, + presentationTime: 103, number: 0, - resolvedUri: 'http://example.com/video/E/C500.m4f', - timeline: 100, - uri: 'C500.m4f' + resolvedUri: 'http://example.com/video/F/F000.m4f', + timeline: 103, + uri: 'F000.m4f' }, { - duration: 1, + duration: 2, map: { - resolvedUri: 'http://example.com/video/E/C_init.mp4', - uri: 'C_init.mp4' + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' }, - presentationTime: 101, + presentationTime: 105, number: 1, - resolvedUri: 'http://example.com/video/E/C501.m4f', - timeline: 100, - uri: 'C501.m4f' + resolvedUri: 'http://example.com/video/F/F001.m4f', + timeline: 103, + uri: 'F001.m4f' }, { + discontinuity: true, duration: 1, map: { - resolvedUri: 'http://example.com/video/E/C_init.mp4', - uri: 'C_init.mp4' + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' }, - presentationTime: 102, + presentationTime: 107, number: 2, - resolvedUri: 'http://example.com/video/E/C502.m4f', - timeline: 100, - uri: 'C502.m4f' + resolvedUri: 'http://example.com/video/F/F000.m4f', + timeline: 107, + uri: 'F000.m4f' + }, + { + duration: 1, + map: { + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' + }, + presentationTime: 108, + number: 3, + resolvedUri: 'http://example.com/video/F/F001.m4f', + timeline: 107, + uri: 'F001.m4f' }, { discontinuity: true, + duration: 1, + map: { + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' + }, + presentationTime: 111, + number: 4, + resolvedUri: 'http://example.com/video/F/F862.m4f', + timeline: 111, + uri: 'F862.m4f' + }, + { + duration: 1, + map: { + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' + }, + presentationTime: 112, + number: 5, + resolvedUri: 'http://example.com/video/F/F863.m4f', + timeline: 111, + uri: 'F863.m4f' + }, + { + duration: 1, + map: { + resolvedUri: 'http://example.com/video/F/F_init.mp4', + uri: 'F_init.mp4' + }, + presentationTime: 113, + number: 6, + resolvedUri: 'http://example.com/video/F/F864.m4f', + timeline: 111, + uri: 'F864.m4f' + } + ], + targetDuration: 2, + timeline: 103, + timelineStarts: [ + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], + uri: '' + }, + { + attributes: { + 'AUDIO': 'audio', + 'BANDWIDTH': 1048480, + 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 30, + 'NAME': 'C', + 'PROGRAM-ID': 1, + 'RESOLUTION': { + height: 540, + width: 960 + }, + 'SUBTITLES': 'subs' + }, + discontinuitySequence: 1, + discontinuityStarts: [2, 4], + endList: false, + mediaSequence: 0, + resolvedUri: 'http://example.com/video/C/', + segments: [ + { duration: 2, map: { resolvedUri: 'http://example.com/video/C/C_init.mp4', uri: 'C_init.mp4' }, presentationTime: 103, - number: 3, + number: 0, resolvedUri: 'http://example.com/video/C/C000.m4f', timeline: 103, uri: 'C000.m4f' @@ -1027,7 +1093,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 105, - number: 4, + number: 1, resolvedUri: 'http://example.com/video/C/C001.m4f', timeline: 103, uri: 'C001.m4f' @@ -1040,7 +1106,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 107, - number: 5, + number: 2, resolvedUri: 'http://example.com/video/C/C000.m4f', timeline: 107, uri: 'C000.m4f' @@ -1052,7 +1118,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 109, - number: 6, + number: 3, resolvedUri: 'http://example.com/video/C/C001.m4f', timeline: 107, uri: 'C001.m4f' @@ -1065,7 +1131,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 111, - number: 7, + number: 4, resolvedUri: 'http://example.com/video/C/C862.m4f', timeline: 111, uri: 'C862.m4f' @@ -1077,7 +1143,7 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 112, - number: 8, + number: 5, resolvedUri: 'http://example.com/video/C/C863.m4f', timeline: 111, uri: 'C863.m4f' @@ -1089,14 +1155,19 @@ export const parsedManifest = { uri: 'C_init.mp4' }, presentationTime: 113, - number: 9, + number: 6, resolvedUri: 'http://example.com/video/C/C864.m4f', timeline: 111, uri: 'C864.m4f' } ], - targetDuration: 1, - timeline: 100, + targetDuration: 2, + timeline: 103, + timelineStarts: [ + { start: 103, timeline: 103}, + { start: 107, timeline: 107}, + { start: 111, timeline: 111} + ], uri: '' } ], diff --git a/test/manifests/segmentBase.js b/test/manifests/segmentBase.js index f45ab909..59258647 100644 --- a/test/manifests/segmentBase.js +++ b/test/manifests/segmentBase.js @@ -25,7 +25,7 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: true, - resolvedUri: '', + resolvedUri: 'https://www.example.com/1080p.ts', targetDuration: 6, mediaSequence: 0, segments: [ diff --git a/test/manifests/segmentList.js b/test/manifests/segmentList.js index 9b7ddce8..3220b4a3 100644 --- a/test/manifests/segmentList.js +++ b/test/manifests/segmentList.js @@ -27,7 +27,7 @@ export const parsedManifest = { endList: true, mediaSequence: 0, targetDuration: 1, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', segments: [ { duration: 1, @@ -123,7 +123,7 @@ export const parsedManifest = { 'SUBTITLES': 'subs' }, endList: true, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', mediaSequence: 0, targetDuration: 60, segments: [ diff --git a/test/manifests/vtt_codecs.js b/test/manifests/vtt_codecs.js index 46dcbae9..aa80010a 100644 --- a/test/manifests/vtt_codecs.js +++ b/test/manifests/vtt_codecs.js @@ -25,7 +25,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -103,7 +103,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -189,7 +189,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -267,7 +267,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.984, segments: [ { @@ -440,7 +440,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.9185833333333333, segments: [ { @@ -525,7 +525,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 1.9185833333333333, segments: [ { diff --git a/test/manifests/webmsegments.js b/test/manifests/webmsegments.js index daae0108..ed376c6a 100644 --- a/test/manifests/webmsegments.js +++ b/test/manifests/webmsegments.js @@ -22,7 +22,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 4, segments: [ { @@ -108,7 +108,7 @@ export const parsedManifest = { uri: '', endList: true, timeline: 0, - resolvedUri: '', + resolvedUri: 'https://www.example.com/base', targetDuration: 4, segments: [ { diff --git a/test/toM3u8.test.js b/test/toM3u8.test.js index 2440371e..6d28e6b4 100644 --- a/test/toM3u8.test.js +++ b/test/toM3u8.test.js @@ -213,6 +213,326 @@ QUnit.test('playlists', function(assert) { assert.deepEqual(toM3u8({ dashPlaylists }), expected); }); +QUnit.test('playlists with content steering and resolvable URLs', function(assert) { + const contentSteering = { + defaultServiceLocation: 'beta', + proxyServerURL: 'http://127.0.0.1:3455/steer', + queryBeforeStart: false, + serverURL: 'https://example.com/app/url' + }; + + const dashPlaylists = [ + { + attributes: { + bandwidth: 5000000, + baseUrl: 'https://cdn1.example.com/video', + clientOffset: 0, + codecs: 'avc1.64001e', + duration: 0, + height: 404, + id: 'test', + mimeType: 'video/mp4', + periodStart: 0, + role: { + value: 'main' + }, + serviceLocation: 'alpha', + sourceDuration: 0, + type: 'dyanmic', + width: 720 + }, + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn1.example.com/video', + uri: '' + }, + number: 1, + presentationTime: 0, + resolvedUri: 'https://cdn1.example.com/video', + timeline: 0, + uri: '' + } + ] + }, + { + attributes: { + bandwidth: 5000000, + baseUrl: 'https://cdn2.example.com/video', + clientOffset: 0, + codecs: 'avc1.64001e', + duration: 0, + height: 404, + id: 'test', + mimeType: 'video/mp4', + periodStart: 0, + role: { + value: 'main' + }, + serviceLocation: 'beta', + sourceDuration: 0, + type: 'dyanmic', + width: 720 + }, + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn2.example.com/video', + uri: '' + }, + number: 1, + presentationTime: 0, + resolvedUri: 'https://cdn2.example.com/video', + timeline: 0, + uri: '' + } + ] + }, + { + attributes: { + bandwidth: 256, + baseUrl: 'https://cdn1.example.com/vtt', + clientOffset: 0, + duration: 0, + id: 'en', + lang: 'en', + mimeType: 'text/vtt', + periodStart: 0, + role: {}, + serviceLocation: 'alpha', + sourceDuration: 0, + type: 'dyanmic' + }, + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn1.example.com/vtt', + uri: '' + }, + number: 1, + presentationTime: 0, + resolvedUri: 'https://cdn1.example.com/vtt', + timeline: 0, + uri: '' + } + ] + }, + { + attributes: { + bandwidth: 256, + baseUrl: 'https://cdn2.example.com/vtt', + clientOffset: 0, + id: 'en', + lang: 'en', + mimeType: 'text/vtt', + periodStart: 0, + role: {}, + serviceLocation: 'beta', + sourceDuration: 0, + type: 'dyanmic' + } + } + ]; + + const expected = { + allowCache: true, + contentSteering: { + defaultServiceLocation: 'beta', + proxyServerURL: 'http://127.0.0.1:3455/steer', + queryBeforeStart: false, + serverURL: 'https://example.com/app/url' + }, + discontinuityStarts: [], + duration: 0, + endList: true, + mediaGroups: { + AUDIO: {}, + ['CLOSED-CAPTIONS']: {}, + SUBTITLES: { + subs: { + en: { + autoselect: false, + default: false, + language: 'en', + playlists: [ + { + attributes: { + BANDWIDTH: 256, + NAME: 'en', + ['PROGRAM-ID']: 1, + serviceLocation: 'alpha' + }, + discontinuitySequence: 0, + discontinuityStarts: [], + endList: false, + mediaSequence: 0, + resolvedUri: 'https://cdn1.example.com/vtt', + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn1.example.com/vtt', + uri: '' + }, + number: 0, + presentationTime: 0, + resolvedUri: 'https://cdn1.example.com/vtt', + timeline: 0, + uri: '' + } + ], + targetDuration: 0, + timeline: 0, + timelineStarts: [ + { + start: 0, + timeline: 0 + } + ], + uri: '' + }, + { + attributes: { + BANDWIDTH: 256, + NAME: 'en', + ['PROGRAM-ID']: 1, + serviceLocation: 'beta' + }, + discontinuitySequence: 0, + discontinuityStarts: [], + endList: false, + mediaSequence: 0, + resolvedUri: 'https://cdn2.example.com/vtt', + segments: [ + { + duration: 0, + number: 0, + resolvedUri: 'https://cdn2.example.com/vtt', + timeline: 0, + uri: 'https://cdn2.example.com/vtt' + } + ], + targetDuration: 0, + timeline: 0, + timelineStarts: [ + { + start: 0, + timeline: 0 + } + ], + uri: '' + } + ], + uri: '' + } + } + }, + VIDEO: {} + }, + playlists: [ + { + attributes: { + AUDIO: 'audio', + BANDWIDTH: 5000000, + CODECS: 'avc1.64001e', + NAME: 'test', + ['PROGRAM-ID']: 1, + RESOLUTION: { + height: 404, + width: 720 + }, + SUBTITLES: 'subs', + serviceLocation: 'alpha' + }, + discontinuitySequence: 0, + discontinuityStarts: [], + endList: false, + mediaSequence: 0, + resolvedUri: 'https://cdn1.example.com/video', + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn1.example.com/video', + uri: '' + }, + number: 0, + presentationTime: 0, + resolvedUri: 'https://cdn1.example.com/video', + timeline: 0, + uri: '' + } + ], + targetDuration: 0, + timeline: 0, + timelineStarts: [ + { + start: 0, + timeline: 0 + } + ], + uri: '' + }, + { + attributes: { + AUDIO: 'audio', + BANDWIDTH: 5000000, + CODECS: 'avc1.64001e', + NAME: 'test', + ['PROGRAM-ID']: 1, + RESOLUTION: { + height: 404, + width: 720 + }, + SUBTITLES: 'subs', + serviceLocation: 'beta' + }, + discontinuitySequence: 0, + discontinuityStarts: [], + endList: false, + mediaSequence: 0, + resolvedUri: 'https://cdn2.example.com/video', + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn2.example.com/video', + uri: '' + }, + number: 0, + presentationTime: 0, + resolvedUri: 'https://cdn2.example.com/video', + timeline: 0, + uri: '' + } + ], + targetDuration: 0, + timeline: 0, + timelineStarts: [ + { + start: 0, + timeline: 0 + } + ], + uri: '' + } + ], + segments: [], + timelineStarts: [ + { + start: 0, + timeline: 0 + } + ], + uri: '' + }; + + assert.deepEqual(toM3u8({ dashPlaylists, contentSteering }), expected); +}); + QUnit.test('playlists with content steering', function(assert) { const contentSteering = { defaultServiceLocation: 'beta', @@ -389,15 +709,14 @@ QUnit.test('playlists with content steering', function(assert) { height: 404, width: 720 }, - SUBTITLES: 'subs' + SUBTITLES: 'subs', + serviceLocation: 'alpha' }, discontinuitySequence: 0, - discontinuityStarts: [ - 1 - ], + discontinuityStarts: [], endList: false, mediaSequence: 0, - resolvedUri: '', + resolvedUri: 'https://cdn1.example.com/', segments: [ { duration: 0, @@ -410,15 +729,45 @@ QUnit.test('playlists with content steering', function(assert) { resolvedUri: 'https://cdn1.example.com/', timeline: 0, uri: '' + } + ], + targetDuration: 0, + timeline: 0, + timelineStarts: [ + { + start: 0, + timeline: 0 + } + ], + uri: '' + }, + { + attributes: { + AUDIO: 'audio', + BANDWIDTH: 5000000, + CODECS: 'avc1.64001e', + NAME: 'test', + ['PROGRAM-ID']: 1, + RESOLUTION: { + height: 404, + width: 720 }, + SUBTITLES: 'subs', + serviceLocation: 'beta' + }, + discontinuitySequence: 0, + discontinuityStarts: [], + endList: false, + mediaSequence: 0, + resolvedUri: 'https://cdn2.example.com/', + segments: [ { - discontinuity: true, duration: 0, map: { resolvedUri: 'https://cdn2.example.com/', uri: '' }, - number: 1, + number: 0, presentationTime: 0, resolvedUri: 'https://cdn2.example.com/', timeline: 0, @@ -428,10 +777,6 @@ QUnit.test('playlists with content steering', function(assert) { targetDuration: 0, timeline: 0, timelineStarts: [ - { - start: 0, - timeline: 0 - }, { start: 0, timeline: 0 diff --git a/test/toPlaylists.test.js b/test/toPlaylists.test.js index fd0528ed..0331c267 100644 --- a/test/toPlaylists.test.js +++ b/test/toPlaylists.test.js @@ -410,3 +410,204 @@ QUnit.test('presentationTime accounts for presentationTimeOffset', function(asse assert.deepEqual(toPlaylists(representations), playlists); }); + +QUnit.test('playlist with content steering and resolvable BaseURLs', function(assert) { + const representations = [ + { + attributes: { + bandwidth: 5000000, + baseUrl: 'https://cdn1.example.com/video', + clientOffset: 0, + codecs: 'avc1.64001e', + height: 404, + id: 'test', + mimeType: 'video/mp4', + periodStart: 0, + role: { + value: 'main' + }, + serviceLocation: 'alpha', + sourceDuration: 0, + type: 'dyanmic', + width: 720 + }, + segmentInfo: { + template: {} + } + }, + { + attributes: { + bandwidth: 5000000, + baseUrl: 'https://cdn2.example.com/video', + clientOffset: 0, + codecs: 'avc1.64001e', + height: 404, + id: 'test', + mimeType: 'video/mp4', + periodStart: 0, + role: { + value: 'main' + }, + serviceLocation: 'beta', + sourceDuration: 0, + type: 'dyanmic', + width: 720 + }, + segmentInfo: { + template: {} + } + }, + { + attributes: { + bandwidth: 256, + baseUrl: 'https://cdn1.example.com/vtt', + clientOffset: 0, + id: 'en', + lang: 'en', + mimeType: 'text/vtt', + periodStart: 0, + role: {}, + serviceLocation: 'alpha', + sourceDuration: 0, + type: 'dyanmic' + }, + segmentInfo: { + template: {} + } + }, + { + attributes: { + bandwidth: 256, + baseUrl: 'https://cdn2.example.com/vtt', + clientOffset: 0, + id: 'en', + lang: 'en', + mimeType: 'text/vtt', + periodStart: 0, + role: {}, + serviceLocation: 'beta', + sourceDuration: 0, + type: 'dyanmic' + }, + segmentInfo: {} + } + ]; + + const playlists = [ + { + attributes: { + bandwidth: 5000000, + baseUrl: 'https://cdn1.example.com/video', + clientOffset: 0, + codecs: 'avc1.64001e', + duration: 0, + height: 404, + id: 'test', + mimeType: 'video/mp4', + periodStart: 0, + role: { + value: 'main' + }, + serviceLocation: 'alpha', + sourceDuration: 0, + type: 'dyanmic', + width: 720 + }, + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn1.example.com/video', + uri: '' + }, + number: 1, + presentationTime: 0, + resolvedUri: 'https://cdn1.example.com/video', + timeline: 0, + uri: '' + } + ] + }, + { + attributes: { + bandwidth: 5000000, + baseUrl: 'https://cdn2.example.com/video', + clientOffset: 0, + codecs: 'avc1.64001e', + duration: 0, + height: 404, + id: 'test', + mimeType: 'video/mp4', + periodStart: 0, + role: { + value: 'main' + }, + serviceLocation: 'beta', + sourceDuration: 0, + type: 'dyanmic', + width: 720 + }, + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn2.example.com/video', + uri: '' + }, + number: 1, + presentationTime: 0, + resolvedUri: 'https://cdn2.example.com/video', + timeline: 0, + uri: '' + } + ] + }, + { + attributes: { + bandwidth: 256, + baseUrl: 'https://cdn1.example.com/vtt', + clientOffset: 0, + duration: 0, + id: 'en', + lang: 'en', + mimeType: 'text/vtt', + periodStart: 0, + role: {}, + serviceLocation: 'alpha', + sourceDuration: 0, + type: 'dyanmic' + }, + segments: [ + { + duration: 0, + map: { + resolvedUri: 'https://cdn1.example.com/vtt', + uri: '' + }, + number: 1, + presentationTime: 0, + resolvedUri: 'https://cdn1.example.com/vtt', + timeline: 0, + uri: '' + } + ] + }, + { + attributes: { + bandwidth: 256, + baseUrl: 'https://cdn2.example.com/vtt', + clientOffset: 0, + id: 'en', + lang: 'en', + mimeType: 'text/vtt', + periodStart: 0, + role: {}, + serviceLocation: 'beta', + sourceDuration: 0, + type: 'dyanmic' + } + } + ]; + + assert.deepEqual(toPlaylists(representations), playlists); +});