Skip to content

Commit 1ce7838

Browse files
fix: live dash segment changes should be considered a playlist update (#1065)
Currently dash content goes off of the number attribute for segments to determine the mediaSequence. This is problematic as the first segment is almost always listed as 1. So in essence when we request the playlist again the media sequence will be the same, and likely the amount of segments will be the same, so we will determine that this playlist update didn't update anything at all.
1 parent cf2efcb commit 1ce7838

File tree

4 files changed

+291
-11
lines changed

4 files changed

+291
-11
lines changed

src/dash-playlist-loader.js

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
} from 'mpd-parser';
77
import {
88
refreshDelay,
9-
updateMaster as updatePlaylist
9+
updateMaster as updatePlaylist,
10+
isPlaylistUnchanged
1011
} from './playlist-loader';
1112
import { resolveUrl, resolveManifestRedirect } from './resolve-url';
1213
import parseSidx from 'mux.js/lib/tools/parse-sidx';
@@ -21,6 +22,67 @@ import {toUint8} from '@videojs/vhs-utils/es/byte-helpers';
2122

2223
const { EventTarget, mergeOptions } = videojs;
2324

25+
const dashPlaylistUnchanged = function(a, b) {
26+
if (!isPlaylistUnchanged(a, b)) {
27+
return false;
28+
}
29+
30+
// for dash the above check will often return true in scenarios where
31+
// the playlist actually has changed because mediaSequence isn't a
32+
// dash thing, and we often set it to 1. So if the playlists have the same amount
33+
// of segments we return true.
34+
// So for dash we need to make sure that the underlying segments are different.
35+
36+
// if sidx changed then the playlists are different.
37+
if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
38+
return false;
39+
} else if ((!a.sidx && b.sidx) || (a.sidx && !b.sidx)) {
40+
return false;
41+
}
42+
43+
// one or the other does not have segments
44+
// there was a change.
45+
if (a.segments && !b.segments || !a.segments && b.segments) {
46+
return false;
47+
}
48+
49+
// neither has segments nothing changed
50+
if (!a.segments && !b.segments) {
51+
return true;
52+
}
53+
54+
// check segments themselves
55+
for (let i = 0; i < a.segments.length; i++) {
56+
const aSegment = a.segments[i];
57+
const bSegment = b.segments[i];
58+
59+
// if uris are different between segments there was a change
60+
if (aSegment.uri !== bSegment.uri) {
61+
return false;
62+
}
63+
64+
// neither segment has a byterange, there will be no byterange change.
65+
if (!aSegment.byterange && !bSegment.byterange) {
66+
continue;
67+
}
68+
const aByterange = aSegment.byterange;
69+
const bByterange = bSegment.byterange;
70+
71+
// if byterange only exists on one of the segments, there was a change.
72+
if ((aByterange && !bByterange) || (!aByterange && bByterange)) {
73+
return false;
74+
}
75+
76+
// if both segments have byterange with different offsets, there was a change.
77+
if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
78+
return false;
79+
}
80+
}
81+
82+
// if everything was the same with segments, this is the same playlist.
83+
return true;
84+
};
85+
2486
/**
2587
* Parses the master XML string and updates playlist URI references.
2688
*
@@ -92,7 +154,7 @@ export const updateMaster = (oldMaster, newMaster, sidxMapping) => {
92154
addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
93155
}
94156
}
95-
const playlistUpdate = updatePlaylist(update, playlist);
157+
const playlistUpdate = updatePlaylist(update, playlist, dashPlaylistUnchanged);
96158

97159
if (playlistUpdate) {
98160
update = playlistUpdate;
@@ -104,7 +166,7 @@ export const updateMaster = (oldMaster, newMaster, sidxMapping) => {
104166
forEachMediaGroup(newMaster, (properties, type, group, label) => {
105167
if (properties.playlists && properties.playlists.length) {
106168
const id = properties.playlists[0].id;
107-
const playlistUpdate = updatePlaylist(update, properties.playlists[0]);
169+
const playlistUpdate = updatePlaylist(update, properties.playlists[0], dashPlaylistUnchanged);
108170

109171
if (playlistUpdate) {
110172
update = playlistUpdate;

src/playlist-loader.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ export const resolveSegmentUris = (segment, baseUri) => {
5757
}
5858
};
5959

60+
// consider the playlist unchanged if the playlist object is the same or
61+
// the number of segments is equal, the media sequence number is unchanged,
62+
// and this playlist hasn't become the end of the playlist
63+
export const isPlaylistUnchanged = (a, b) => a === b ||
64+
(a.segments && b.segments && a.segments.length === b.segments.length &&
65+
a.endList === b.endList &&
66+
a.mediaSequence === b.mediaSequence);
67+
6068
/**
6169
* Returns a new master playlist that is the result of merging an
6270
* updated media playlist into the original version. If the
@@ -69,21 +77,15 @@ export const resolveSegmentUris = (segment, baseUri) => {
6977
* master playlist with the updated media playlist merged in, or
7078
* null if the merge produced no change.
7179
*/
72-
export const updateMaster = (master, media) => {
80+
export const updateMaster = (master, media, unchangedCheck = isPlaylistUnchanged) => {
7381
const result = mergeOptions(master, {});
7482
const playlist = result.playlists[media.id];
7583

7684
if (!playlist) {
7785
return null;
7886
}
7987

80-
// consider the playlist unchanged if the number of segments is equal, the media
81-
// 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) {
88+
if (unchangedCheck(playlist, media)) {
8789
return null;
8890
}
8991

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)