Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/playlist-merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,15 @@ export const updateSequenceNumbers = ({ oldPlaylists, newPlaylists, timelineStar
}

// Since we don't yet support early available timelines, we don't need to support
// playlists with no segments.
// playlists with no segments. Playlists with an unresolved SIDX will have an empty
// segments list until the SIDX is parsed and segment references are generated.
if (!playlist.segments || !playlist.segments.length) {
return;
}

const firstNewSegment = playlist.segments[0];
const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function(oldSegment) {
const oldPlaylistSegments = oldPlaylist.segments || [];
const oldMatchingSegmentIndex = oldPlaylistSegments.findIndex(function(oldSegment) {
return (
Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE
);
Expand All @@ -130,7 +136,7 @@ export const updateSequenceNumbers = ({ oldPlaylists, newPlaylists, timelineStar
if (oldMatchingSegmentIndex === -1) {
updateMediaSequenceForPlaylist({
playlist,
mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
mediaSequence: oldPlaylist.mediaSequence + oldPlaylistSegments.length
});
playlist.segments[0].discontinuity = true;
playlist.discontinuityStarts.unshift(0);
Expand All @@ -154,9 +160,9 @@ export const updateSequenceNumbers = ({ oldPlaylists, newPlaylists, timelineStar
//
// A period may also have a duration of zero, so the case of no segments is handled
// here even though we don't yet support early available periods.
if ((!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline) ||
(oldPlaylist.segments.length && playlist.timeline >
oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline)) {
if ((!oldPlaylistSegments.length && playlist.timeline > oldPlaylist.timeline) ||
(oldPlaylistSegments.length && playlist.timeline >
oldPlaylistSegments[oldPlaylistSegments.length - 1].timeline)) {
playlist.discontinuitySequence--;
}
return;
Expand All @@ -173,7 +179,7 @@ export const updateSequenceNumbers = ({ oldPlaylists, newPlaylists, timelineStar
// Period while the "timeline" exists, HLS keeps track of it via the discontinuity
// sequence, and that first segment is an indicator, but can be removed before that
// timeline is gone.
const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
const oldMatchingSegment = oldPlaylistSegments[oldMatchingSegmentIndex];

if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
firstNewSegment.discontinuity = true;
Expand All @@ -183,7 +189,7 @@ export const updateSequenceNumbers = ({ oldPlaylists, newPlaylists, timelineStar

updateMediaSequenceForPlaylist({
playlist,
mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
mediaSequence: oldPlaylistSegments[oldMatchingSegmentIndex].number
});
});
};
Expand Down
5 changes: 5 additions & 0 deletions src/toM3u8.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const formatAudioPlaylist = ({
export const formatVttPlaylist = ({
attributes,
segments,
sidx,
mediaSequence,
discontinuityStarts,
discontinuitySequence
Expand Down Expand Up @@ -192,6 +193,10 @@ export const formatVttPlaylist = ({
vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
}

if (sidx) {
vttPlaylist.sidx = sidx;
}

return vttPlaylist;
};

Expand Down
112 changes: 112 additions & 0 deletions test/playlist-merge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,118 @@ QUnit.test('complete refresh of matching playlist', function(assert) {
);
});

QUnit.test('new playlists without segments only update discontinuity sequence', function(assert) {
const oldPlaylists = [{
discontinuitySequence: 1,
discontinuityStarts: [],
mediaSequence: 5,
timeline: 5,
attributes: { NAME: 'A' },
segments: []
}, {
discontinuitySequence: 2,
discontinuityStarts: [],
mediaSequence: 3,
timeline: 10,
attributes: { NAME: 'B' }
}];
const newPlaylists = [{
discontinuitySequence: 0,
discontinuityStarts: [],
mediaSequence: 0,
timeline: 5,
attributes: { NAME: 'A' },
segments: []
}, {
discontinuitySequence: 0,
discontinuityStarts: [],
mediaSequence: 0,
timeline: 10,
attributes: { NAME: 'B' }
}];
const timelineStarts = [
{ start: 0, timeline: 0 },
{ start: 5, timeline: 5 },
{ start: 10, timeline: 10 }
];

updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts });

assert.deepEqual(
newPlaylists,
[{
discontinuitySequence: 1,
discontinuityStarts: [],
mediaSequence: 0,
timeline: 5,
attributes: { NAME: 'A' },
segments: []
}, {
discontinuitySequence: 2,
discontinuityStarts: [],
mediaSequence: 0,
timeline: 10,
attributes: { NAME: 'B' }
}],
'new playlists unchanged apart from discontinuity sequence'
);
});

QUnit.test('matching old playlist without segments is treated as a refresh', function(assert) {
const oldPlaylists = [{
discontinuitySequence: 1,
discontinuityStarts: [],
mediaSequence: 3,
timeline: 5,
attributes: { NAME: 'A' }
}];
const newPlaylists = [{
discontinuitySequence: 0,
discontinuityStarts: [],
mediaSequence: 0,
timeline: 10,
attributes: { NAME: 'A' },
segments: [{
presentationTime: 10,
timeline: 10,
number: 0
}, {
presentationTime: 12,
timeline: 10,
number: 1
}]
}];
const timelineStarts = [
{ start: 0, timeline: 0 },
{ start: 5, timeline: 5 },
{ start: 10, timeline: 10 }
];

updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts });

assert.deepEqual(
newPlaylists,
[{
discontinuitySequence: 1,
discontinuityStarts: [0],
mediaSequence: 3,
timeline: 10,
attributes: { NAME: 'A' },
segments: [{
discontinuity: true,
presentationTime: 10,
timeline: 10,
number: 3
}, {
presentationTime: 12,
timeline: 10,
number: 4
}]
}],
'new playlist updated as a complete refresh'
);
});

QUnit.module('positionManifestOnTimeline');

QUnit.test('handles multiple playlists, including added and removed', function(assert) {
Expand Down
91 changes: 91 additions & 0 deletions test/toM3u8.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,97 @@ QUnit.test('playlists with sidx and sidxMapping', function(assert) {
assert.deepEqual(toM3u8({ dashPlaylists, sidxMapping }).playlists, expected);
});

QUnit.test('vtt playlists with sidx and sidxMapping', function(assert) {
const dashPlaylists = [{
attributes: {
sourceDuration: 100,
id: 'en',
lang: 'en',
codecs: 'wvtt',
duration: 0,
bandwidth: 10000,
periodStart: 0,
mimeType: 'text/vtt',
type: 'static'
},
segments: [],
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
timeline: 0,
duration: 10
},
uri: 'http://example.com/fmp4.mp4'
}];

const sidxMapping = {
'sidx.mp4-10-19': {
sidx: {
timescale: 1,
firstOffset: 0,
references: [{
referenceType: 0,
referencedSize: 5,
subsegmentDuration: 2
}]
}
}
};

const expected = [{
attributes: {
BANDWIDTH: 10000,
CODECS: 'wvtt',
NAME: 'en',
['PROGRAM-ID']: 1
},
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
timeline: 0,
duration: 10
},
targetDuration: 0,
timeline: 0,
timelineStarts: [{ start: 0, timeline: 0 }],
uri: '',
segments: [{
map: {
resolvedUri: 'http://example.com/sidx.mp4',
uri: ''
},
byterange: {
offset: 20,
length: 5
},
uri: 'http://example.com/sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 2,
number: 0,
presentationTime: 0,
timeline: 0
}],
endList: true,
mediaSequence: 0,
discontinuitySequence: 0,
discontinuityStarts: [],
resolvedUri: ''
}];

assert.deepEqual(
toM3u8({ dashPlaylists, sidxMapping }).mediaGroups.SUBTITLES.subs.en.playlists,
expected
);
});

QUnit.test('playlists without minimumUpdatePeriod dont assign default value', function(assert) {
const dashPlaylists = [{
attributes: {
Expand Down