You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1040 lines
26 KiB
1040 lines
26 KiB
import { |
|
getUniqueTimelineStarts, |
|
findPlaylistWithName, |
|
getMediaGroupPlaylists, |
|
updateMediaSequenceForPlaylist, |
|
updateSequenceNumbers, |
|
positionManifestOnTimeline |
|
} from '../src/playlist-merge'; |
|
import { merge } from '../src/utils/object'; |
|
import QUnit from 'qunit'; |
|
|
|
QUnit.module('getUniqueTimelineStarts'); |
|
|
|
QUnit.test('handles multiple playlists', function(assert) { |
|
const listOfTimelineStartLists = [ |
|
[{ start: 0, timeline: 0 }, { start: 10, timeline: 10 }, { start: 20, timeline: 20 }], |
|
[{ start: 0, timeline: 0 }, { start: 10, timeline: 10 }, { start: 20, timeline: 20 }], |
|
[{ start: 10, timeline: 10 }, { start: 20, timeline: 20 }], |
|
[{ start: 0, timeline: 0 }, { start: 20, timeline: 20 }], |
|
[{ start: 30, timeline: 30 }] |
|
]; |
|
|
|
assert.deepEqual( |
|
getUniqueTimelineStarts(listOfTimelineStartLists), |
|
[{ |
|
start: 0, |
|
timeline: 0 |
|
}, { |
|
start: 10, |
|
timeline: 10 |
|
}, { |
|
start: 20, |
|
timeline: 20 |
|
}, { |
|
start: 30, |
|
timeline: 30 |
|
}], |
|
'handled multiple playlists with differing timeline starts' |
|
); |
|
}); |
|
|
|
QUnit.module('findPlaylistWithName'); |
|
|
|
QUnit.test('returns nothing when no playlists', function(assert) { |
|
assert.notOk(findPlaylistWithName([], 'A'), 'nothing when no playlists'); |
|
}); |
|
|
|
QUnit.test('returns nothing when no match', function(assert) { |
|
const playlists = [ |
|
{ attributes: { NAME: 'B' } } |
|
]; |
|
|
|
assert.notOk(findPlaylistWithName(playlists, 'A'), 'nothing when no match'); |
|
}); |
|
|
|
QUnit.test('returns matching playlist', function(assert) { |
|
const playlists = [ |
|
{ attributes: { NAME: 'A' } }, |
|
{ attributes: { NAME: 'B' } }, |
|
{ attributes: { NAME: 'C' } } |
|
]; |
|
|
|
assert.deepEqual( |
|
findPlaylistWithName(playlists, 'B'), |
|
playlists[1], |
|
'returns matching playlist' |
|
); |
|
}); |
|
|
|
QUnit.module('getMediaGroupPlaylists'); |
|
|
|
QUnit.test('returns nothing when no media group playlists', function(assert) { |
|
const manifest = { |
|
mediaGroups: { |
|
AUDIO: {} |
|
} |
|
}; |
|
|
|
assert.deepEqual( |
|
getMediaGroupPlaylists(manifest), |
|
[], |
|
'nothing when no media group playlists' |
|
); |
|
}); |
|
|
|
QUnit.test('returns media group playlists', function(assert) { |
|
const playlistEnA = { attributes: { NAME: 'A' } }; |
|
const playlistEnB = { attributes: { NAME: 'B' } }; |
|
const playlistEnC = { attributes: { NAME: 'C' } }; |
|
const playlistFrA = { attributes: { NAME: 'A' } }; |
|
const playlistFrB = { attributes: { NAME: 'B' } }; |
|
const manifest = { |
|
mediaGroups: { |
|
AUDIO: { |
|
audio: { |
|
en: { |
|
playlists: [playlistEnA, playlistEnB, playlistEnC] |
|
}, |
|
fr: { |
|
playlists: [playlistFrA, playlistFrB] |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
|
|
assert.deepEqual( |
|
getMediaGroupPlaylists(manifest), |
|
[playlistEnA, playlistEnB, playlistEnC, playlistFrA, playlistFrB], |
|
'returns media group playlists' |
|
); |
|
}); |
|
|
|
QUnit.module('updateMediaSequenceForPlaylist'); |
|
|
|
QUnit.test('no segments means only top level mediaSequence is updated', function(assert) { |
|
const playlist = { mediaSequence: 1, segments: [] }; |
|
|
|
updateMediaSequenceForPlaylist({ playlist, mediaSequence: 3 }); |
|
|
|
assert.deepEqual( |
|
playlist, |
|
{ mediaSequence: 3, segments: [] }, |
|
'updated only top level mediaSequence' |
|
); |
|
}); |
|
|
|
QUnit.test('updates top level mediaSequence and segments', function(assert) { |
|
const playlist = { |
|
mediaSequence: 1, |
|
segments: [{ number: 1 }, { number: 2 }, { number: 3 }] |
|
}; |
|
|
|
updateMediaSequenceForPlaylist({ playlist, mediaSequence: 3 }); |
|
|
|
assert.deepEqual( |
|
playlist, |
|
{ mediaSequence: 3, segments: [{ number: 3 }, { number: 4 }, { number: 5 }] }, |
|
'updated top level mediaSequence and segments' |
|
); |
|
}); |
|
|
|
QUnit.module('updateSequenceNumbers'); |
|
|
|
QUnit.test('no playlists, no update', function(assert) { |
|
const oldPlaylists = []; |
|
const newPlaylists = []; |
|
const timelineStarts = []; |
|
|
|
updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts }); |
|
|
|
assert.deepEqual(newPlaylists, [], 'new playlists unchanged'); |
|
}); |
|
|
|
QUnit.test('no matching playlists only updates discontinuity sequence', function(assert) { |
|
const oldPlaylists = [{ |
|
discontinuitySequence: 1, |
|
discontinuityStarts: [], |
|
timeline: 5, |
|
attributes: { NAME: 'A' } |
|
}, { |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [], |
|
timeline: 10, |
|
attributes: { NAME: 'B' } |
|
}]; |
|
const newPlaylists = [{ |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timeline: 5, |
|
attributes: { NAME: 'C' } |
|
}, { |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timeline: 10, |
|
attributes: { NAME: 'D' } |
|
}]; |
|
const timelineStarts = [ |
|
{ start: 0, timeline: 0 }, |
|
{ start: 5, timeline: 5 }, |
|
{ start: 10, timeline: 10 } |
|
]; |
|
|
|
updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts }); |
|
|
|
assert.deepEqual( |
|
newPlaylists, |
|
[{ |
|
discontinuitySequence: 1, |
|
discontinuityStarts: [], |
|
timeline: 5, |
|
attributes: { NAME: 'C' } |
|
}, { |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [], |
|
timeline: 10, |
|
attributes: { NAME: 'D' } |
|
}], |
|
'new playlist discontinuity sequence numbers updated' |
|
); |
|
}); |
|
|
|
QUnit.test('segment match of matching playlist', function(assert) { |
|
const oldPlaylists = [{ |
|
discontinuitySequence: 1, |
|
discontinuityStarts: [], |
|
mediaSequence: 5, |
|
timeline: 5, |
|
attributes: { NAME: 'C' }, |
|
segments: [{ |
|
presentationTime: 10, |
|
timeline: 5, |
|
number: 5 |
|
}, { |
|
presentationTime: 12, |
|
timeline: 5, |
|
number: 6 |
|
}, { |
|
presentationTime: 14, |
|
timeline: 5, |
|
number: 7 |
|
}] |
|
}]; |
|
const newPlaylists = [{ |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
mediaSequence: 0, |
|
timeline: 5, |
|
attributes: { NAME: 'C' }, |
|
segments: [{ |
|
presentationTime: 12, |
|
timeline: 5, |
|
number: 0 |
|
}, { |
|
presentationTime: 14, |
|
timeline: 5, |
|
number: 1 |
|
}] |
|
}]; |
|
const timelineStarts = [ |
|
{ start: 0, timeline: 0 }, |
|
{ start: 5, timeline: 5 } |
|
]; |
|
|
|
updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts }); |
|
|
|
assert.deepEqual( |
|
newPlaylists, |
|
[{ |
|
discontinuitySequence: 1, |
|
discontinuityStarts: [], |
|
mediaSequence: 6, |
|
timeline: 5, |
|
attributes: { NAME: 'C' }, |
|
segments: [{ |
|
presentationTime: 12, |
|
timeline: 5, |
|
number: 6 |
|
}, { |
|
presentationTime: 14, |
|
timeline: 5, |
|
number: 7 |
|
}] |
|
}], |
|
'new playlist updated' |
|
); |
|
}); |
|
|
|
QUnit.test('complete refresh of matching playlist', function(assert) { |
|
const oldPlaylists = [{ |
|
discontinuitySequence: 1, |
|
discontinuityStarts: [], |
|
mediaSequence: 5, |
|
timeline: 5, |
|
attributes: { NAME: 'C' }, |
|
segments: [{ |
|
presentationTime: 10, |
|
timeline: 5, |
|
number: 5 |
|
}, { |
|
presentationTime: 12, |
|
timeline: 5, |
|
number: 6 |
|
}, { |
|
presentationTime: 14, |
|
timeline: 5, |
|
number: 7 |
|
}] |
|
}]; |
|
const newPlaylists = [{ |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
mediaSequence: 0, |
|
timeline: 5, |
|
attributes: { NAME: 'C' }, |
|
segments: [{ |
|
presentationTime: 16, |
|
timeline: 5, |
|
number: 0 |
|
}, { |
|
presentationTime: 18, |
|
timeline: 5, |
|
number: 1 |
|
}] |
|
}]; |
|
const timelineStarts = [ |
|
{ start: 0, timeline: 0 }, |
|
{ start: 5, timeline: 5 } |
|
]; |
|
|
|
updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts }); |
|
|
|
assert.deepEqual( |
|
newPlaylists, |
|
[{ |
|
discontinuitySequence: 1, |
|
discontinuityStarts: [0], |
|
mediaSequence: 8, |
|
timeline: 5, |
|
attributes: { NAME: 'C' }, |
|
segments: [{ |
|
discontinuity: true, |
|
presentationTime: 16, |
|
timeline: 5, |
|
number: 8 |
|
}, { |
|
presentationTime: 18, |
|
timeline: 5, |
|
number: 9 |
|
}] |
|
}], |
|
'new playlist updated after complete refresh' |
|
); |
|
}); |
|
|
|
QUnit.module('positionManifestOnTimeline'); |
|
|
|
QUnit.test('handles multiple playlists, including added and removed', function(assert) { |
|
const oldPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 12, |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [1], |
|
timelineStarts: [ |
|
// only this playlist has the 20 timeline |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 20, |
|
segments: [{ |
|
number: 12, |
|
timeline: 20, |
|
presentationTime: 31 |
|
}, { |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const oldPlaylistB = { |
|
attributes: { NAME: 'B' }, |
|
mediaSequence: 13, |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
// same as A just with a different name |
|
const oldPlaylistC = merge(oldPlaylistA, { |
|
attributes: { NAME: 'C' } |
|
}); |
|
const newPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// no discontinuity is marked because from the context of the new playlist, it's the |
|
// first period |
|
number: 0, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 1, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const newPlaylistB = { |
|
attributes: { NAME: 'B' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
number: 0, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
// same as B but with a different name |
|
const newPlaylistD = merge(newPlaylistB, { |
|
attributes: { NAME: 'D' } |
|
}); |
|
const oldManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
// The manifest's timeline starts will account for all seen timeline starts. Since the |
|
// lowest playlist discontinuity sequence is 2, that means there are two additional |
|
// timeline starts here that aren't present in any playlists. |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [oldPlaylistA, oldPlaylistB, oldPlaylistC] |
|
}; |
|
const newManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
// removed 20 timeline |
|
{ start: 33, timeline: 33 } |
|
], |
|
// removed C, added D |
|
playlists: [newPlaylistA, newPlaylistB, newPlaylistD] |
|
}; |
|
|
|
assert.deepEqual( |
|
positionManifestOnTimeline({ oldManifest, newManifest }), |
|
{ |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [{ |
|
attributes: { NAME: 'A' }, |
|
// updated mediaSequence |
|
mediaSequence: 13, |
|
// updated discontinuitySequence |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [0], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
discontinuity: true, |
|
// updated sequence number |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
// updated sequence number |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}, { |
|
attributes: { NAME: 'B' }, |
|
// updated mediaSequence |
|
mediaSequence: 14, |
|
// updated discontinuitySequence, note that it's one greater than A |
|
discontinuitySequence: 3, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// updated sequence number |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}, { |
|
attributes: { NAME: 'D' }, |
|
// since D is a new playlist, media sequence is 0 |
|
mediaSequence: 0, |
|
// updated discontinuitySequence, note that it's one greater than A |
|
discontinuitySequence: 3, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// updated sequence number |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}] |
|
}, |
|
'handled updated, removed, and added playlists' |
|
); |
|
}); |
|
|
|
QUnit.test('handles playlist and media group playlist', function(assert) { |
|
const oldPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 12, |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [1], |
|
timelineStarts: [ |
|
// only this playlist has the 20 timeline |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 20, |
|
segments: [{ |
|
number: 12, |
|
timeline: 20, |
|
presentationTime: 31 |
|
}, { |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const oldPlaylistB = { |
|
attributes: { NAME: 'B' }, |
|
mediaSequence: 13, |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const newPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// no discontinuity is marked because from the context of the new playlist, it's the |
|
// first period |
|
number: 0, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 1, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const newPlaylistB = { |
|
attributes: { NAME: 'B' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
number: 0, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const oldManifest = { |
|
mediaGroups: { |
|
AUDIO: { |
|
audio: { |
|
en: { |
|
playlists: [oldPlaylistA] |
|
} |
|
} |
|
}, |
|
VIDEO: {}, |
|
['CLOSED-CAPTIONS']: {}, |
|
SUBTITLES: {} |
|
}, |
|
// The manifest's timeline starts will account for all seen timeline starts. Since the |
|
// lowest playlist discontinuity sequence is 2, that means there are two additional |
|
// timeline starts here that aren't present in any playlists. |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [oldPlaylistB] |
|
}; |
|
const newManifest = { |
|
mediaGroups: { |
|
AUDIO: { |
|
audio: { |
|
en: { |
|
playlists: [newPlaylistA] |
|
} |
|
} |
|
}, |
|
VIDEO: {}, |
|
['CLOSED-CAPTIONS']: {}, |
|
SUBTITLES: {} |
|
}, |
|
timelineStarts: [ |
|
// removed 20 timeline |
|
{ start: 33, timeline: 33 } |
|
], |
|
// removed C, added D |
|
playlists: [newPlaylistB] |
|
}; |
|
|
|
assert.deepEqual( |
|
positionManifestOnTimeline({ oldManifest, newManifest }), |
|
{ |
|
mediaGroups: { |
|
AUDIO: { |
|
audio: { |
|
en: { |
|
playlists: [{ |
|
attributes: { NAME: 'A' }, |
|
// updated mediaSequence |
|
mediaSequence: 13, |
|
// updated discontinuitySequence |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [0], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
discontinuity: true, |
|
// updated sequence number |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
// updated sequence number |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}] |
|
} |
|
} |
|
}, |
|
VIDEO: {}, |
|
['CLOSED-CAPTIONS']: {}, |
|
SUBTITLES: {} |
|
}, |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [{ |
|
attributes: { NAME: 'B' }, |
|
// updated mediaSequence |
|
mediaSequence: 14, |
|
// updated discontinuitySequence, note that it's one greater than A |
|
discontinuitySequence: 3, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// updated sequence number |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}] |
|
}, |
|
'handled playlist and media group playlist' |
|
); |
|
}); |
|
|
|
QUnit.test('complete refresh same timeline', function(assert) { |
|
const oldPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 12, |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [1], |
|
timelineStarts: [ |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 20, |
|
segments: [{ |
|
number: 12, |
|
timeline: 20, |
|
presentationTime: 31 |
|
}, { |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const newPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
number: 0, |
|
timeline: 33, |
|
// missed a large portion of time, but still within same timeline |
|
presentationTime: 50 |
|
}, { |
|
number: 1, |
|
timeline: 33, |
|
presentationTime: 52 |
|
}] |
|
}; |
|
const oldManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
// The manifest's timeline starts will account for all seen timeline starts. Since the |
|
// lowest playlist discontinuity sequence is 2, that means there are two additional |
|
// timeline starts here that aren't present in any playlists. |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [oldPlaylistA] |
|
}; |
|
const newManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
// removed 20 timeline |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [newPlaylistA] |
|
}; |
|
|
|
assert.deepEqual( |
|
positionManifestOnTimeline({ oldManifest, newManifest }), |
|
{ |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [{ |
|
attributes: { NAME: 'A' }, |
|
// updated mediaSequence to one greater than last seen |
|
mediaSequence: 15, |
|
// updated discontinuitySequence to account for timeline 33 discontinuity falling |
|
// off |
|
discontinuitySequence: 3, |
|
// added discontinuity to beginning |
|
discontinuityStarts: [0], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
discontinuity: true, |
|
// updated sequence number |
|
number: 15, |
|
timeline: 33, |
|
presentationTime: 50 |
|
}, { |
|
// updated sequence number |
|
number: 16, |
|
timeline: 33, |
|
presentationTime: 52 |
|
}] |
|
}] |
|
}, |
|
'handled complete refresh on same timeline' |
|
); |
|
}); |
|
|
|
QUnit.test('complete refresh different timeline', function(assert) { |
|
const oldPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 12, |
|
// timeline 10 is the start (thus no disco) |
|
// removed 1 disco at timeline 15, and another for the start of timeline 20 |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [1], |
|
timelineStarts: [ |
|
// only this playlist has the 20 timeline |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 20, |
|
segments: [{ |
|
number: 12, |
|
timeline: 20, |
|
presentationTime: 31 |
|
}, { |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const newPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 50, timeline: 50 } |
|
], |
|
timeline: 50, |
|
segments: [{ |
|
number: 0, |
|
// new timeline |
|
timeline: 50, |
|
presentationTime: 50 |
|
}, { |
|
number: 1, |
|
timeline: 50, |
|
presentationTime: 52 |
|
}] |
|
}; |
|
const oldManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
// The manifest's timeline starts will account for all seen timeline starts. Since the |
|
// lowest playlist discontinuity sequence is 2, that means there are two additional |
|
// timeline starts here that aren't present in any playlists. |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [oldPlaylistA] |
|
}; |
|
const newManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
// removed 20 and 33 timelines, added 50 |
|
{ start: 50, timeline: 50 } |
|
], |
|
playlists: [newPlaylistA] |
|
}; |
|
|
|
assert.deepEqual( |
|
positionManifestOnTimeline({ oldManifest, newManifest }), |
|
{ |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 }, |
|
// added new timeline and retained the ones from before |
|
{ start: 50, timeline: 50 } |
|
], |
|
playlists: [{ |
|
attributes: { NAME: 'A' }, |
|
// updated mediaSequence to one greater than last seen |
|
mediaSequence: 15, |
|
// updated discontinuitySequence to account for removed discos (previously the |
|
// disco at 15 and 20, plus the disco starting timeline 33 on the refresh) |
|
discontinuitySequence: 3, |
|
// added discontinuity to beginning |
|
discontinuityStarts: [0], |
|
timelineStarts: [ |
|
{ start: 50, timeline: 50 } |
|
], |
|
timeline: 50, |
|
segments: [{ |
|
discontinuity: true, |
|
// updated sequence number |
|
number: 15, |
|
timeline: 50, |
|
presentationTime: 50 |
|
}, { |
|
// updated sequence number |
|
number: 16, |
|
timeline: 50, |
|
presentationTime: 52 |
|
}] |
|
}] |
|
}, |
|
'handled complete refresh on different timeline' |
|
); |
|
}); |
|
|
|
QUnit.test('no change, first segment a discontinuity', function(assert) { |
|
const oldPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 13, |
|
discontinuitySequence: 2, |
|
discontinuityStarts: [0], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
discontinuity: true, |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const newPlaylistA = { |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 0, |
|
discontinuitySequence: 0, |
|
discontinuityStarts: [], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// no discontinuity marked for the refresh, should be added by merging logic |
|
number: 0, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
number: 1, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}; |
|
const oldManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
// The manifest's timeline starts will account for all seen timeline starts. Since the |
|
// lowest playlist discontinuity sequence is 3, that means there are three additional |
|
// timeline starts here that aren't present in any playlists, plus the original at 0. |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [oldPlaylistA] |
|
}; |
|
const newManifest = { |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [{ start: 33, timeline: 33 }], |
|
playlists: [newPlaylistA] |
|
}; |
|
|
|
assert.deepEqual( |
|
positionManifestOnTimeline({ oldManifest, newManifest }), |
|
{ |
|
mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, |
|
timelineStarts: [ |
|
{ start: 10, timeline: 10 }, |
|
{ start: 15, timeline: 15 }, |
|
{ start: 20, timeline: 20 }, |
|
{ start: 33, timeline: 33 } |
|
], |
|
playlists: [{ |
|
attributes: { NAME: 'A' }, |
|
mediaSequence: 13, |
|
discontinuitySequence: 2, |
|
// discontinuity at beginning |
|
discontinuityStarts: [0], |
|
timelineStarts: [ |
|
{ start: 33, timeline: 33 } |
|
], |
|
timeline: 33, |
|
segments: [{ |
|
// discontinuity added to new playlist |
|
discontinuity: true, |
|
// updated sequence number |
|
number: 13, |
|
timeline: 33, |
|
presentationTime: 33 |
|
}, { |
|
// updated sequence number |
|
number: 14, |
|
timeline: 33, |
|
presentationTime: 35 |
|
}] |
|
}] |
|
}, |
|
'handled no change with first segment a discontinuity' |
|
); |
|
});
|
|
|