1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-05 02:39:46 +02:00
Daniel Neto 2023-10-25 10:14:46 -03:00
parent b6d47e94c8
commit 65f15c7e46
2882 changed files with 382239 additions and 10785 deletions

View file

@ -9,6 +9,7 @@ import {
import { stringToMpdXml } from '../src/stringToMpdXml';
import errors from '../src/errors';
import QUnit from 'qunit';
import { stub } from 'sinon';
import { toPlaylists } from '../src/toPlaylists';
import decodeB64ToUint8Array from '@videojs/vhs-utils/es/decode-b64-to-uint8-array';
import { findChildren } from '../src/utils/xml';
@ -16,23 +17,23 @@ import { findChildren } from '../src/utils/xml';
QUnit.module('buildBaseUrls');
QUnit.test('returns reference urls when no BaseURL nodes', function(assert) {
const reference = ['https://example.com/', 'https://foo.com/'];
const reference = [{ baseUrl: 'https://example.com/' }, { baseUrl: 'https://foo.com/' }];
assert.deepEqual(buildBaseUrls(reference, []), reference, 'returns reference urls');
});
QUnit.test('single reference url with single BaseURL node', function(assert) {
const reference = ['https://example.com'];
const reference = [{ baseUrl: 'https://example.com' }];
const node = [{ textContent: 'bar/' }];
const expected = ['https://example.com/bar/'];
const expected = [{ baseUrl: 'https://example.com/bar/' }];
assert.deepEqual(buildBaseUrls(reference, node), expected, 'builds base url');
});
QUnit.test('multiple reference urls with single BaseURL node', function(assert) {
const reference = ['https://example.com/', 'https://foo.com/'];
const reference = [{ baseUrl: 'https://example.com/' }, { baseUrl: 'https://foo.com/' }];
const node = [{ textContent: 'bar/' }];
const expected = ['https://example.com/bar/', 'https://foo.com/bar/'];
const expected = [{ baseUrl: 'https://example.com/bar/' }, { baseUrl: 'https://foo.com/bar/' }];
assert.deepEqual(
buildBaseUrls(reference, node), expected,
@ -41,36 +42,38 @@ QUnit.test('multiple reference urls with single BaseURL node', function(assert)
});
QUnit.test('multiple BaseURL nodes with single reference url', function(assert) {
const reference = ['https://example.com/'];
const reference = [{ baseUrl: 'https://example.com/' }];
const nodes = [{ textContent: 'bar/' }, { textContent: 'baz/' }];
const expected = ['https://example.com/bar/', 'https://example.com/baz/'];
const expected = [{ baseUrl: 'https://example.com/bar/' }, { baseUrl: 'https://example.com/baz/' }];
assert.deepEqual(buildBaseUrls(reference, nodes), expected, 'base url for each node');
});
QUnit.test('multiple reference urls with multiple BaseURL nodes', function(assert) {
const reference = ['https://example.com/', 'https://foo.com/', 'http://example.com'];
const reference = [
{ baseUrl: 'https://example.com/' }, { baseUrl: 'https://foo.com/' }, { baseUrl: 'http://example.com' }
];
const nodes =
[{ textContent: 'bar/' }, { textContent: 'baz/' }, { textContent: 'buzz/' }];
const expected = [
'https://example.com/bar/',
'https://example.com/baz/',
'https://example.com/buzz/',
'https://foo.com/bar/',
'https://foo.com/baz/',
'https://foo.com/buzz/',
'http://example.com/bar/',
'http://example.com/baz/',
'http://example.com/buzz/'
{ baseUrl: 'https://example.com/bar/' },
{ baseUrl: 'https://example.com/baz/' },
{ baseUrl: 'https://example.com/buzz/' },
{ baseUrl: 'https://foo.com/bar/' },
{ baseUrl: 'https://foo.com/baz/' },
{ baseUrl: 'https://foo.com/buzz/' },
{ baseUrl: 'http://example.com/bar/' },
{ baseUrl: 'http://example.com/baz/' },
{ baseUrl: 'http://example.com/buzz/' }
];
assert.deepEqual(buildBaseUrls(reference, nodes), expected, 'creates all base urls');
});
QUnit.test('absolute BaseURL overwrites reference', function(assert) {
const reference = ['https://example.com'];
const reference = [{ baseUrl: 'https://example.com' }];
const node = [{ textContent: 'https://foo.com/bar/' }];
const expected = ['https://foo.com/bar/'];
const expected = [{ baseUrl: 'https://foo.com/bar/'}];
assert.deepEqual(
buildBaseUrls(reference, node), expected,
@ -78,6 +81,40 @@ QUnit.test('absolute BaseURL overwrites reference', function(assert) {
);
});
QUnit.test('reference attributes are ignored when there is a BaseURL node', function(assert) {
const reference = [{ baseUrl: 'https://example.com', attributes: [{ name: 'test', value: 'wow' }] }];
const node = [{ textContent: 'https://foo.com/bar/' }];
const expected = [{ baseUrl: 'https://foo.com/bar/' }];
assert.deepEqual(
buildBaseUrls(reference, node), expected,
'baseURL attributes are not included'
);
});
QUnit.test('BasURL attributes are still added with a reference', function(assert) {
const reference = [{ baseUrl: 'https://example.com' }];
const node = [{ textContent: 'https://foo.com/bar/', attributes: [{ name: 'test', value: 'wow' }] }];
const expected = [{ baseUrl: 'https://foo.com/bar/', test: 'wow' }];
assert.deepEqual(
buildBaseUrls(reference, node), expected,
'baseURL attributes are included'
);
});
QUnit.test('attributes are replaced when both reference and BaseURL have the same attributes', function(assert) {
const reference = [{ baseUrl: 'https://example.com', attributes: [{ name: 'test', value: 'old' }] }];
const node = [{ textContent: 'https://foo.com/bar/', attributes: [{ name: 'test', value: 'new' }] }];
const expected = [{ baseUrl: 'https://foo.com/bar/', test: 'new' }];
assert.deepEqual(
buildBaseUrls(reference, node), expected,
'baseURL attributes are included'
);
});
QUnit.module('getPeriodStart');
QUnit.test('gets period start when available', function(assert) {
@ -546,6 +583,7 @@ QUnit.test('end to end - basic', function(assert) {
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -593,6 +631,82 @@ QUnit.test('end to end - basic', function(assert) {
assert.deepEqual(actual, expected);
});
QUnit.test('end to end - basic using manifest uri', function(assert) {
const NOW = Date.now();
const actual = inheritAttributes(stringToMpdXml(`
<MPD mediaPresentationDuration="PT30S" >
<BaseURL>base/</BaseURL>
<Period>
<AdaptationSet mimeType="video/mp4" >
<Role value="main"></Role>
<SegmentTemplate></SegmentTemplate>
<Representation
bandwidth="5000000"
codecs="avc1.64001e"
height="404"
id="test"
width="720">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="en">
<Representation bandwidth="256" id="en">
<BaseURL>en.vtt</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>
`), { NOW, manifestUri: 'https://www.test.com' });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
attributes: {
bandwidth: 5000000,
baseUrl: 'https://www.test.com/base/',
codecs: 'avc1.64001e',
height: 404,
id: 'test',
mediaPresentationDuration: 30,
mimeType: 'video/mp4',
periodStart: 0,
role: {
value: 'main'
},
sourceDuration: 30,
type: 'static',
width: 720,
NOW,
clientOffset: 0
},
segmentInfo: {
template: {}
}
}, {
attributes: {
bandwidth: 256,
baseUrl: 'https://www.test.com/base/en.vtt',
id: 'en',
lang: 'en',
mediaPresentationDuration: 30,
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 30,
type: 'static',
NOW,
clientOffset: 0
},
segmentInfo: {}
}]
};
assert.equal(actual.representationInfo.length, 2);
assert.deepEqual(actual, expected);
});
QUnit.test('end to end - basic dynamic', function(assert) {
const NOW = Date.now();
@ -621,6 +735,7 @@ QUnit.test('end to end - basic dynamic', function(assert) {
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -666,6 +781,303 @@ QUnit.test('end to end - basic dynamic', function(assert) {
assert.deepEqual(actual, expected);
});
QUnit.test('end to end - content steering - non resolvable base URLs', function(assert) {
const NOW = Date.now();
const actual = inheritAttributes(stringToMpdXml(`
<MPD type="dyanmic">
<ContentSteering defaultServiceLocation="beta" queryBeforeStart="false" proxyServerURL="http://127.0.0.1:3455/steer">https://example.com/app/url</ContentSteering>
<BaseURL serviceLocation="alpha">https://cdn1.example.com/</BaseURL>
<BaseURL serviceLocation="beta">https://cdn2.example.com/</BaseURL>
<Period start="PT0S">
<AdaptationSet mimeType="video/mp4">
<Role value="main"></Role>
<SegmentTemplate></SegmentTemplate>
<Representation
bandwidth="5000000"
codecs="avc1.64001e"
height="404"
id="test"
width="720">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="en">
<Representation bandwidth="256" id="en">
<BaseURL>https://example.com/en.vtt</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>
`), { NOW, manifestUri: 'https://www.test.com' });
// Note that we expect to see the `contentSteeringInfo` object set with the
// proper values. We also expect to see the `serviceLocation` property set to
// the correct values inside of the correct representations.
const expected = {
contentSteeringInfo: {
defaultServiceLocation: 'beta',
proxyServerURL: 'http://127.0.0.1:3455/steer',
queryBeforeStart: false,
serverURL: 'https://example.com/app/url'
},
eventStream: [],
locations: undefined,
representationInfo: [
{
attributes: {
NOW,
bandwidth: 5000000,
baseUrl: 'https://cdn1.example.com/',
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: {
NOW,
bandwidth: 5000000,
baseUrl: 'https://cdn2.example.com/',
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: {
NOW,
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
},
segmentInfo: {}
},
{
attributes: {
NOW,
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
},
segmentInfo: {}
}
]
};
assert.equal(actual.representationInfo.length, 4);
assert.deepEqual(actual, expected);
});
QUnit.test('end to end - content steering - resolvable base URLs', function(assert) {
const NOW = Date.now();
const actual = inheritAttributes(stringToMpdXml(`
<MPD type="dyanmic">
<ContentSteering defaultServiceLocation="beta" queryBeforeStart="false" proxyServerURL="http://127.0.0.1:3455/steer">https://example.com/app/url</ContentSteering>
<BaseURL serviceLocation="alpha">https://cdn1.example.com/</BaseURL>
<BaseURL serviceLocation="beta">https://cdn2.example.com/</BaseURL>
<Period start="PT0S">
<AdaptationSet mimeType="video/mp4">
<Role value="main"></Role>
<SegmentTemplate></SegmentTemplate>
<Representation
bandwidth="5000000"
codecs="avc1.64001e"
height="404"
id="test"
width="720">
</Representation>
<BaseURL>/video</BaseURL>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="en">
<Representation bandwidth="256" id="en">
<BaseURL>/vtt</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>
`), { NOW, manifestUri: 'https://www.test.com' });
// Note that we expect to see the `contentSteeringInfo` object set with the
// proper values. We also expect to see the `serviceLocation` property set to
// the correct values inside of the correct representations.
//
// Also note that some of the representations have '/video' appended
// to the end of the baseUrls
const expected = {
contentSteeringInfo: {
defaultServiceLocation: 'beta',
proxyServerURL: 'http://127.0.0.1:3455/steer',
queryBeforeStart: false,
serverURL: 'https://example.com/app/url'
},
eventStream: [],
locations: undefined,
representationInfo: [
{
attributes: {
NOW,
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: {
NOW,
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: {
NOW,
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: {}
},
{
attributes: {
NOW,
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: {}
}
]
};
assert.equal(actual.representationInfo.length, 4);
assert.deepEqual(actual, expected);
});
QUnit.test('Too many content steering tags sends a warning to the eventHandler', function(assert) {
const handlerStub = stub();
const NOW = Date.now();
inheritAttributes(stringToMpdXml(`
<MPD type="dyanmic">
<ContentSteering defaultServiceLocation="alpha" queryBeforeStart="false" proxyServerURL="http://127.0.0.1:3455/steer">https://example.com/app/url</ContentSteering>
<ContentSteering defaultServiceLocation="beta" queryBeforeStart="false" proxyServerURL="http://127.0.0.1:3455/steer">https://example.com/app/url</ContentSteering>
<BaseURL serviceLocation="alpha">https://cdn1.example.com/</BaseURL>
<BaseURL serviceLocation="beta">https://cdn2.example.com/</BaseURL>
<Period start="PT0S">
<AdaptationSet mimeType="video/mp4">
<Role value="main"></Role>
<SegmentTemplate></SegmentTemplate>
<Representation
bandwidth="5000000"
codecs="avc1.64001e"
height="404"
id="test"
width="720">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="en">
<Representation bandwidth="256" id="en">
<BaseURL>/video</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>
`), { NOW, manifestUri: 'https://www.test.com', eventHandler: handlerStub });
assert.ok(handlerStub.calledWith({
type: 'warn',
message: 'The MPD manifest should contain no more than one ContentSteering tag'
}));
});
QUnit.test('end to end - basic multiperiod', function(assert) {
const NOW = Date.now();
@ -703,6 +1115,7 @@ QUnit.test('end to end - basic multiperiod', function(assert) {
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -790,6 +1203,7 @@ QUnit.test('end to end - inherits BaseURL from all levels', function(assert) {
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -867,6 +1281,7 @@ QUnit.test('end to end - alternate BaseURLs', function(assert) {
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -1035,6 +1450,7 @@ QUnit.test(
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -1148,6 +1564,7 @@ QUnit.test(
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -1272,6 +1689,7 @@ QUnit.test(
`), { NOW });
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -2116,6 +2534,7 @@ QUnit.test('keySystem info for representation - lowercase UUIDs', function(asser
// inconsistent quoting because of quote-props
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -2203,6 +2622,7 @@ QUnit.test('keySystem info for representation - uppercase UUIDs', function(asser
// inconsistent quoting because of quote-props
const expected = {
contentSteeringInfo: null,
eventStream: [],
locations: undefined,
representationInfo: [{
@ -2372,6 +2792,7 @@ QUnit.test('gets eventStream from inheritAttributes', function(assert) {
</Period>
</MPD>`);
const expected = {
contentSteeringInfo: null,
eventStream: [
{
end: 15,
@ -2481,6 +2902,7 @@ QUnit.test('gets eventStream from inheritAttributes with data in Event tags', fu
</Period>
</MPD>`);
const expected = {
contentSteeringInfo: null,
eventStream: [
{
end: 15,

View file

@ -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 }],

View file

@ -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 }],

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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: [
{

View file

@ -36,7 +36,7 @@ export const parsedManifest = {
timeline: 6
}],
targetDuration: 3,
resolvedUri: '',
resolvedUri: 'https://www.example.com/base',
segments: [
{
duration: 3,

View file

@ -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: [
{

View file

@ -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'

View file

@ -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'
},
{
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'
},
presentationTime: 111,
number: 7,
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: 8,
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: 9,
resolvedUri: 'http://example.com/video/F/F864.m4f',
timeline: 111,
uri: 'F864.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: [
{
duration: 1,
map: {
resolvedUri: 'http://example.com/video/E/C_init.mp4',
uri: 'C_init.mp4'
},
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/E/C_init.mp4',
uri: 'C_init.mp4'
},
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/E/C_init.mp4',
uri: 'C_init.mp4'
},
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,13 +930,135 @@ 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',
'BANDWIDTH': 2215557,
'CODECS': 'avc1.640020',
'FRAME-RATE': 60,
'NAME': 'F',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 720,
width: 1280
},
'SUBTITLES': 'subs'
},
discontinuitySequence: 1,
discontinuityStarts: [2, 4],
endList: false,
mediaSequence: 0,
resolvedUri: 'http://example.com/video/F/',
segments: [
{
duration: 2,
map: {
resolvedUri: 'http://example.com/video/F/F_init.mp4',
uri: 'F_init.mp4'
},
presentationTime: 103,
number: 0,
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: 1,
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: 2,
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,
@ -959,63 +1068,20 @@ export const parsedManifest = {
},
'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/C/',
segments: [
{
duration: 1,
map: {
resolvedUri: 'http://example.com/video/E/C_init.mp4',
uri: 'C_init.mp4'
},
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/E/C_init.mp4',
uri: 'C_init.mp4'
},
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/E/C_init.mp4',
uri: 'C_init.mp4'
},
presentationTime: 102,
number: 2,
resolvedUri: 'http://example.com/video/E/C502.m4f',
timeline: 100,
uri: 'C502.m4f'
},
{
discontinuity: true,
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: ''
}
],

View file

@ -25,7 +25,7 @@ export const parsedManifest = {
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
resolvedUri: 'https://www.example.com/1080p.ts',
targetDuration: 6,
mediaSequence: 0,
segments: [

View file

@ -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: [

View file

@ -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: [
{

View file

@ -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: [
{

View file

@ -213,6 +213,591 @@ 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',
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/',
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/',
uri: ''
},
number: 1,
presentationTime: 0,
resolvedUri: 'https://cdn1.example.com/',
timeline: 0,
uri: ''
}
]
}, {
attributes: {
bandwidth: 5000000,
baseUrl: 'https://cdn2.example.com/',
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/',
uri: ''
},
number: 1,
presentationTime: 0,
resolvedUri: 'https://cdn2.example.com/',
timeline: 0,
uri: ''
}
]
}, {
attributes: {
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
}
}, {
attributes: {
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
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
},
discontinuitySequence: 0,
discontinuityStarts: [],
endList: false,
mediaSequence: 0,
resolvedUri: 'https://example.com/en.vtt',
segments: [
{
duration: 0,
number: 0,
resolvedUri: 'https://example.com/en.vtt',
timeline: 0,
uri: 'https://example.com/en.vtt'
}
],
targetDuration: 0,
timeline: 0,
timelineStarts: [
{
start: 0,
timeline: 0
},
{
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/',
segments: [
{
duration: 0,
map: {
resolvedUri: 'https://cdn1.example.com/',
uri: ''
},
number: 0,
presentationTime: 0,
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: [
{
duration: 0,
map: {
resolvedUri: 'https://cdn2.example.com/',
uri: ''
},
number: 0,
presentationTime: 0,
resolvedUri: 'https://cdn2.example.com/',
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 segments', function(assert) {
const dashPlaylists = [{
attributes: {

View file

@ -85,6 +85,181 @@ QUnit.test('segment base', function(assert) {
assert.deepEqual(toPlaylists(representations), playlists);
});
QUnit.test('playlist with content steering BaseURLs', function(assert) {
const representations = [
{
attributes: {
bandwidth: 5000000,
baseUrl: 'https://cdn1.example.com/',
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/',
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://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
},
segmentInfo: {}
},
{
attributes: {
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
},
segmentInfo: {}
}
];
const playlists = [{
attributes: {
bandwidth: 5000000,
baseUrl: 'https://cdn1.example.com/',
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/',
uri: ''
},
number: 1,
presentationTime: 0,
resolvedUri: 'https://cdn1.example.com/',
timeline: 0,
uri: ''
}
]
}, {
attributes: {
bandwidth: 5000000,
baseUrl: 'https://cdn2.example.com/',
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/',
uri: ''
},
number: 1,
presentationTime: 0,
resolvedUri: 'https://cdn2.example.com/',
timeline: 0,
uri: ''
}
]
}, {
attributes: {
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
}
}, {
attributes: {
bandwidth: 256,
baseUrl: 'https://example.com/en.vtt',
clientOffset: 0,
id: 'en',
lang: 'en',
mimeType: 'text/vtt',
periodStart: 0,
role: {},
sourceDuration: 0,
type: 'dyanmic'
}
}];
assert.deepEqual(toPlaylists(representations), playlists);
});
QUnit.test('segment base with sidx', function(assert) {
const representations = [{
attributes: {
@ -235,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);
});