Skip to content

Commit ec4c212

Browse files
committed
fix: live dash segment changes should be considered a playlist update
1 parent b01ab72 commit ec4c212

File tree

4 files changed

+272
-8
lines changed

4 files changed

+272
-8
lines changed

src/dash-playlist-loader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const updateMaster = (oldMaster, newMaster, sidxMapping) => {
9292
addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
9393
}
9494
}
95-
const playlistUpdate = updatePlaylist(update, playlist);
95+
const playlistUpdate = updatePlaylist(update, playlist, true);
9696

9797
if (playlistUpdate) {
9898
update = playlistUpdate;
@@ -104,7 +104,7 @@ export const updateMaster = (oldMaster, newMaster, sidxMapping) => {
104104
forEachMediaGroup(newMaster, (properties, type, group, label) => {
105105
if (properties.playlists && properties.playlists.length) {
106106
const id = properties.playlists[0].id;
107-
const playlistUpdate = updatePlaylist(update, properties.playlists[0]);
107+
const playlistUpdate = updatePlaylist(update, properties.playlists[0], true);
108108

109109
if (playlistUpdate) {
110110
update = playlistUpdate;

src/playlist-loader.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const resolveSegmentUris = (segment, baseUri) => {
6969
* master playlist with the updated media playlist merged in, or
7070
* null if the merge produced no change.
7171
*/
72-
export const updateMaster = (master, media) => {
72+
export const updateMaster = (master, media, dash = false) => {
7373
const result = mergeOptions(master, {});
7474
const playlist = result.playlists[media.id];
7575

@@ -79,11 +79,59 @@ export const updateMaster = (master, media) => {
7979

8080
// consider the playlist unchanged if the number of segments is equal, the media
8181
// sequence number is unchanged, and this playlist hasn't become the end of the playlist
82-
if (playlist.segments &&
83-
media.segments &&
84-
playlist.segments.length === media.segments.length &&
85-
playlist.endList === media.endList &&
86-
playlist.mediaSequence === media.mediaSequence) {
82+
let unchanged = playlist.segments &&
83+
media.segments &&
84+
playlist.segments.length === media.segments.length &&
85+
playlist.endList === media.endList &&
86+
playlist.mediaSequence === media.mediaSequence;
87+
88+
// for dash mediaSequence and segment lengths will often match as
89+
// mediaSequence is almost always 1 and the number of segments generated for a
90+
// given time is often the same. So we need to make sure that the underlying segments are
91+
// different.
92+
if (dash && unchanged) {
93+
const oldSidx = playlist.sidx;
94+
const newSidx = media.sidx;
95+
96+
// if sidx changed then the playlists are different.
97+
if (oldSidx && newSidx && (oldSidx.offset !== newSidx.offset || oldSidx.length !== newSidx.length)) {
98+
unchanged = false;
99+
} else if ((!oldSidx && newSidx) || (oldSidx && !newSidx)) {
100+
unchanged = false;
101+
} else {
102+
for (let i = 0; i < playlist.segments.length; i++) {
103+
const oldSegment = playlist.segments[i];
104+
const newSegment = media.segments[i];
105+
106+
// if uris are different between segments there was a change
107+
if (oldSegment.uri !== newSegment.uri) {
108+
unchanged = false;
109+
continue;
110+
}
111+
112+
// neither segment has a byternage, there will be no byterange change.
113+
if (!oldSegment.byterange && !newSegment.byterange) {
114+
continue;
115+
}
116+
const oldByterange = oldSegment.byterange;
117+
const newByterange = newSegment.byterange;
118+
119+
// if byterange only exists on one of the segments, there was a change.
120+
if ((oldByterange && !newByterange) || (!oldByterange && newByterange)) {
121+
unchanged = false;
122+
continue;
123+
}
124+
125+
// if both segments have byterange with different offsets, there was a change.
126+
if (oldByterange.offset !== newByterange.offset || oldByterange.length !== newByterange.length) {
127+
unchanged = false;
128+
continue;
129+
}
130+
}
131+
}
132+
}
133+
134+
if (unchanged) {
87135
return null;
88136
}
89137

test/dash-playlist-loader.test.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,164 @@ QUnit.test('refreshXml_: updates media playlist reference if master changed', fu
16841684
);
16851685
});
16861686

1687+
QUnit.test('refreshXml_: updates playlists if segment uri changed, but media sequence did not', function(assert) {
1688+
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
1689+
1690+
loader.load();
1691+
this.standardXHRResponse(this.requests.shift());
1692+
1693+
const oldMaster = loader.master;
1694+
const oldMedia = loader.media();
1695+
1696+
// change segment uris
1697+
const newMasterXml = loader.masterXml_
1698+
.replace(/\$RepresentationID\$/g, '$RepresentationID$-foo')
1699+
.replace('media="segment-$Number$.mp4"', 'media="segment-foo$Number$.mp4"');
1700+
1701+
loader.refreshXml_();
1702+
1703+
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
1704+
1705+
this.requests.shift().respond(200, null, newMasterXml);
1706+
1707+
const newMaster = loader.master;
1708+
const newMedia = loader.media();
1709+
1710+
assert.notEqual(newMaster, oldMaster, 'master changed');
1711+
assert.notEqual(newMedia, oldMedia, 'media changed');
1712+
assert.equal(
1713+
newMedia,
1714+
newMaster.playlists[newMedia.id],
1715+
'media from updated master'
1716+
);
1717+
});
1718+
1719+
QUnit.test('refreshXml_: updates playlists if sidx changed', function(assert) {
1720+
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
1721+
1722+
loader.load();
1723+
this.standardXHRResponse(this.requests.shift());
1724+
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
1725+
this.standardXHRResponse(this.requests.shift(), sidxResponse());
1726+
1727+
const oldMaster = loader.master;
1728+
const oldMedia = loader.media();
1729+
1730+
const newMasterXml = loader.masterXml_
1731+
.replace(/indexRange="200-399"/g, 'indexRange="500-699"');
1732+
1733+
loader.refreshXml_();
1734+
1735+
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
1736+
1737+
this.standardXHRResponse(this.requests.shift(), newMasterXml);
1738+
1739+
const newMaster = loader.master;
1740+
const newMedia = loader.media();
1741+
1742+
assert.notEqual(newMaster, oldMaster, 'master changed');
1743+
assert.notEqual(newMedia, oldMedia, 'media changed');
1744+
assert.equal(
1745+
newMedia,
1746+
newMaster.playlists[newMedia.id],
1747+
'media from updated master'
1748+
);
1749+
});
1750+
1751+
QUnit.test('refreshXml_: updates playlists if sidx removed', function(assert) {
1752+
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
1753+
1754+
loader.load();
1755+
this.standardXHRResponse(this.requests.shift());
1756+
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
1757+
this.standardXHRResponse(this.requests.shift(), sidxResponse());
1758+
1759+
const oldMaster = loader.master;
1760+
const oldMedia = loader.media();
1761+
1762+
const newMasterXml = loader.masterXml_
1763+
.replace(/indexRange="200-399"/g, '');
1764+
1765+
loader.refreshXml_();
1766+
1767+
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
1768+
1769+
this.standardXHRResponse(this.requests.shift(), newMasterXml);
1770+
1771+
const newMaster = loader.master;
1772+
const newMedia = loader.media();
1773+
1774+
assert.notEqual(newMaster, oldMaster, 'master changed');
1775+
assert.notEqual(newMedia, oldMedia, 'media changed');
1776+
assert.equal(
1777+
newMedia,
1778+
newMaster.playlists[newMedia.id],
1779+
'media from updated master'
1780+
);
1781+
});
1782+
1783+
QUnit.test('refreshXml_: updates playlists if only segment byteranges change', function(assert) {
1784+
const loader = new DashPlaylistLoader('dashByterange.mpd', this.fakeVhs);
1785+
1786+
loader.load();
1787+
this.standardXHRResponse(this.requests.shift());
1788+
1789+
const oldMaster = loader.master;
1790+
const oldMedia = loader.media();
1791+
1792+
const newMasterXml = loader.masterXml_
1793+
.replace('mediaRange="12883295-13124492"', 'mediaRange="12883296-13124492"');
1794+
1795+
loader.refreshXml_();
1796+
1797+
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
1798+
1799+
this.standardXHRResponse(this.requests.shift(), newMasterXml);
1800+
1801+
const newMaster = loader.master;
1802+
const newMedia = loader.media();
1803+
1804+
assert.notEqual(newMaster, oldMaster, 'master changed');
1805+
assert.notEqual(newMedia, oldMedia, 'media changed');
1806+
assert.equal(
1807+
newMedia,
1808+
newMaster.playlists[newMedia.id],
1809+
'media from updated master'
1810+
);
1811+
});
1812+
1813+
QUnit.test('refreshXml_: updates playlists if sidx removed', function(assert) {
1814+
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
1815+
1816+
loader.load();
1817+
this.standardXHRResponse(this.requests.shift());
1818+
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
1819+
this.standardXHRResponse(this.requests.shift(), sidxResponse());
1820+
1821+
const oldMaster = loader.master;
1822+
const oldMedia = loader.media();
1823+
1824+
const newMasterXml = loader.masterXml_
1825+
.replace(/indexRange="200-399"/g, '');
1826+
1827+
loader.refreshXml_();
1828+
1829+
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
1830+
1831+
this.standardXHRResponse(this.requests.shift(), newMasterXml);
1832+
1833+
const newMaster = loader.master;
1834+
const newMedia = loader.media();
1835+
1836+
assert.notEqual(newMaster, oldMaster, 'master changed');
1837+
assert.notEqual(newMedia, oldMedia, 'media changed');
1838+
assert.equal(
1839+
newMedia,
1840+
newMaster.playlists[newMedia.id],
1841+
'media from updated master'
1842+
);
1843+
});
1844+
16871845
QUnit.test('addSidxSegments_: updates master with sidx information', function(assert) {
16881846
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
16891847
const sidxData = sidxResponse();

test/manifests/dashByterange.mpd

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="urn:mpeg:DASH:schema:MPD:2011"
4+
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011"
5+
profiles="urn:mpeg:dash:profile:isoff-main:2011"
6+
type="static"
7+
mediaPresentationDuration="PT0H9M56.46S"
8+
minBufferTime="PT15.0S">
9+
<BaseURL>http://www-itec.uni-klu.ac.at/ftp/datasets/mmsys12/BigBuckBunny/bunny_15s/</BaseURL>
10+
<Period start="PT0S">
11+
<AdaptationSet bitstreamSwitching="true">
12+
<Representation id="3" codecs="avc1" mimeType="video/mp4" width="480" height="360" startWithSAP="1" bandwidth="176031">
13+
<SegmentList duration="15">
14+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="868-347185" />
15+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="347186-664464" />
16+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="664465-1027685" />
17+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="1027686-1367784" />
18+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="1367785-1677710" />
19+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="1677711-2001517" />
20+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="2001518-2290173" />
21+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="2290174-2634238" />
22+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="2634239-2985994" />
23+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="2985995-3323725" />
24+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="3323726-3650264" />
25+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="3650265-3978004" />
26+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="3978005-4304349" />
27+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="4304350-4629741" />
28+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="4629742-4951671" />
29+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="4951672-5282910" />
30+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="5282911-5629211" />
31+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="5629212-5963914" />
32+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="5963915-6312646" />
33+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="6312647-6612703" />
34+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="6612704-6923786" />
35+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="6923787-7272547" />
36+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="7272548-7590097" />
37+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="7590098-7947017" />
38+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="7947018-8276044" />
39+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="8276045-8551338" />
40+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="8551339-8866104" />
41+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="8866105-9171839" />
42+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="9171840-9515898" />
43+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="9515899-9849503" />
44+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="9849504-10161047" />
45+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="10161048-10494887" />
46+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="10494888-10786011" />
47+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="10786012-11142570" />
48+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="11142571-11503643" />
49+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="11503644-11859498" />
50+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="11859499-12213313" />
51+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="12213314-12578158" />
52+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="12578159-12883294" />
53+
<SegmentURL media="bunny_15s_200kbit/bunny_200kbit_dashNonSeg.mp4" mediaRange="12883295-13124492" />
54+
</SegmentList>
55+
</Representation>
56+
</AdaptationSet>
57+
</Period>
58+
</MPD>

0 commit comments

Comments
 (0)