Skip to content

Commit

Permalink
fix: Linear DASH multiperiod label issue (#1352)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrums86 committed Feb 23, 2023
1 parent 1c93e79 commit de73795
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 13 deletions.
41 changes: 40 additions & 1 deletion src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ const dashPlaylistUnchanged = function(a, b) {
return true;
};

/**
* Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
* ID in the parser. This allows for continuous playout across periods with the same representation IDs
* (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
* periods without continuous playback this function may need modification as well as the parser.
*/
const dashGroupId = (type, group, label, playlist) => {
// If the manifest somehow does not have an ID (non-dash compliant), use the label.
const playlistId = playlist.attributes.NAME || label;

return `placeholder-uri-${type}-${group}-${playlistId}`;
};

/**
* Parses the master XML string and updates playlist URI references.
*
Expand Down Expand Up @@ -115,11 +128,27 @@ export const parseMasterXml = ({
previousManifest
});

addPropertiesToMaster(manifest, srcUrl);
addPropertiesToMaster(manifest, srcUrl, dashGroupId);

return manifest;
};

/**
* Removes any mediaGroup labels that no longer exist in the newMaster
*
* @param {Object} update
* The previous mpd object being updated
* @param {Object} newMaster
* The new mpd object
*/
const removeOldMediaGroupLabels = (update, newMaster) => {
forEachMediaGroup(update, (properties, type, group, label) => {
if (!(label in newMaster.mediaGroups[type][group])) {
delete update.mediaGroups[type][group][label];
}
});
};

/**
* Returns a new master manifest that is the result of merging an updated master manifest
* into the original version.
Expand Down Expand Up @@ -169,13 +198,23 @@ export const updateMaster = (oldMaster, newMaster, sidxMapping) => {

if (playlistUpdate) {
update = playlistUpdate;

// add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
if (!(label in update.mediaGroups[type][group])) {
update.mediaGroups[type][group][label] = properties;
}

// update the playlist reference within media groups
update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];

noChanges = false;
}
}
});

// remove mediaGroup labels and references that no longer exist in the newMaster
removeOldMediaGroupLabels(update, newMaster);

if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) {
noChanges = false;
}
Expand Down
12 changes: 9 additions & 3 deletions src/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export const createPlaylistID = (index, uri) => {
return `${index}-${uri}`;
};

// default function for creating a group id
const groupID = (type, group, label) => {
return `placeholder-uri-${type}-${group}-${label}`;
};

/**
* Parses a given m3u8 playlist
*
Expand Down Expand Up @@ -265,8 +270,10 @@ export const masterForMedia = (media, uri) => {
* Master manifest object
* @param {string} uri
* The source URI
* @param {function} createGroupID
* A function to determine how to create the groupID for mediaGroups
*/
export const addPropertiesToMaster = (master, uri) => {
export const addPropertiesToMaster = (master, uri, createGroupID = groupID) => {
master.uri = uri;

for (let i = 0; i < master.playlists.length; i++) {
Expand All @@ -282,8 +289,6 @@ export const addPropertiesToMaster = (master, uri) => {
const audioOnlyMaster = isAudioOnly(master);

forEachMediaGroup(master, (properties, mediaType, groupKey, labelKey) => {
const groupId = `placeholder-uri-${mediaType}-${groupKey}-${labelKey}`;

// add a playlist array under properties
if (!properties.playlists || !properties.playlists.length) {
// If the manifest is audio only and this media group does not have a uri, check
Expand All @@ -303,6 +308,7 @@ export const addPropertiesToMaster = (master, uri) => {
}

properties.playlists.forEach(function(p, i) {
const groupId = createGroupID(mediaType, groupKey, labelKey, p);
const id = createPlaylistID(i, groupId);

if (p.uri) {
Expand Down
146 changes: 137 additions & 9 deletions test/dash-playlist-loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ QUnit.test('filterChangedSidxMappings: removes change sidx info from mapping', f
);
const playlists = loader.master.playlists;
const oldVideoKey = generateSidxKey(playlists['0-placeholder-uri-0'].sidx);
const oldAudioEnKey = generateSidxKey(playlists['0-placeholder-uri-AUDIO-audio-en'].sidx);
const oldAudioEnKey = generateSidxKey(playlists['0-placeholder-uri-AUDIO-audio-audio'].sidx);

let masterXml = loader.masterXml_.replace(/(indexRange)=\"\d+-\d+\"/, '$1="201-400"');
// should change the video playlist
Expand Down Expand Up @@ -1393,7 +1393,7 @@ QUnit.test('haveMaster: sets media on child loader', function(assert) {

loader.load();
this.standardXHRResponse(this.requests.shift());
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-audio'];
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);

const mediaStub = sinon.stub(childLoader, 'media');
Expand Down Expand Up @@ -2030,7 +2030,7 @@ QUnit.test('hasPendingRequest: returns true if async code is running in child lo

loader.load();
this.standardXHRResponse(this.requests.shift());
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-audio'];
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);

assert.notOk(childLoader.hasPendingRequest(), 'no pending requests on construction');
Expand Down Expand Up @@ -2205,7 +2205,7 @@ QUnit.test('child loader moves to HAVE_METADATA when initialized with a master p

loader.load();
this.standardXHRResponse(this.requests.shift());
const playlist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
const playlist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-audio'];
const childLoader = new DashPlaylistLoader(playlist, this.fakeVhs, false, loader);

childLoader.on('loadedplaylist', function() {
Expand Down Expand Up @@ -2238,7 +2238,7 @@ QUnit.test('child playlist moves to HAVE_METADATA when initialized with a live m

loader.load();
this.standardXHRResponse(this.requests.shift());
const playlist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
const playlist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-audio'];
const childLoader = new DashPlaylistLoader(playlist, this.fakeVhs, false, loader);

childLoader.on('loadedplaylist', function() {
Expand Down Expand Up @@ -2476,14 +2476,14 @@ QUnit.test(
);
assert.equal(
loader.master.mediaGroups.AUDIO.audio.main.playlists[0].uri,
'placeholder-uri-AUDIO-audio-main', 'setup phony uri for media groups'
'placeholder-uri-AUDIO-audio-audio', 'setup phony uri for media groups'
);
assert.equal(
loader.master.mediaGroups.AUDIO.audio.main.playlists[0].id,
'0-placeholder-uri-AUDIO-audio-main', 'setup phony id for media groups'
'0-placeholder-uri-AUDIO-audio-audio', 'setup phony id for media groups'
);
assert.strictEqual(
loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'],
loader.master.playlists['0-placeholder-uri-AUDIO-audio-audio'],
loader.master.mediaGroups.AUDIO.audio.main.playlists[0],
'set reference by uri for easy access'
);
Expand Down Expand Up @@ -2712,7 +2712,7 @@ QUnit.test('child loaders wait for async action before moving to HAVE_MASTER', f

loader.load();
this.standardXHRResponse(this.requests.shift());
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-audio'];
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);

childLoader.load();
Expand Down Expand Up @@ -2873,3 +2873,131 @@ QUnit.test('updateMaster: merges in top level timelineStarts', function(assert)

assert.deepEqual(update.timelineStarts, [2], 'updated timelineStarts');
});

QUnit.test('updateMaster: updates playlists and mediaGroups when labels change', function(assert) {
const master = {
duration: 10,
minimumUpdatePeriod: 0,
timelineStarts: [],
mediaGroups: {
AUDIO: {
audio: {
main: {
playlists: [{
mediaSequence: 0,
attributes: {},
id: 'audio-0-uri',
uri: 'audio-0-uri',
resolvedUri: urlTo('audio-0-uri'),
segments: [{
duration: 10,
uri: 'audio-segment-0-uri',
resolvedUri: urlTo('audio-segment-0-uri')
}]
}]
}
}
}
},
playlists: [{
mediaSequence: 0,
attributes: {
BANDWIDTH: 9
},
id: 'playlist-0-uri',
uri: 'playlist-0-uri',
resolvedUri: urlTo('playlist-0-uri'),
segments: [{
duration: 10,
uri: 'segment-0-uri',
resolvedUri: urlTo('segment-0-uri')
}]
}]
};
const update = {
duration: 20,
minimumUpdatePeriod: 0,
timelineStarts: [],
mediaGroups: {
AUDIO: {
audio: {
update: {
playlists: [{
mediaSequence: 1,
attributes: {},
id: 'audio-0-uri',
uri: 'audio-0-uri',
resolvedUri: urlTo('audio-0-uri'),
segments: [{
duration: 10,
uri: 'audio-segment-0-uri',
resolvedUri: urlTo('audio-segment-0-uri')
}]
}]
}
}
}
},
playlists: [{
mediaSequence: 1,
attributes: {
BANDWIDTH: 9
},
id: 'playlist-0-uri',
uri: 'playlist-0-uri',
resolvedUri: urlTo('playlist-0-uri'),
segments: [{
duration: 10,
uri: 'segment-0-uri',
resolvedUri: urlTo('segment-0-uri')
}]
}]
};

master.playlists['playlist-0-uri'] = master.playlists[0];
master.playlists['audio-0-uri'] = master.mediaGroups.AUDIO.audio.main.playlists[0];

assert.deepEqual(
updateMaster(master, update),
{
duration: 20,
minimumUpdatePeriod: 0,
timelineStarts: [],
mediaGroups: {
AUDIO: {
audio: {
update: {
playlists: [{
mediaSequence: 1,
attributes: {},
id: 'audio-0-uri',
uri: 'audio-0-uri',
resolvedUri: urlTo('audio-0-uri'),
segments: [{
duration: 10,
uri: 'audio-segment-0-uri',
resolvedUri: urlTo('audio-segment-0-uri')
}]
}]
}
}
}
},
playlists: [{
mediaSequence: 1,
attributes: {
BANDWIDTH: 9
},
id: 'playlist-0-uri',
uri: 'playlist-0-uri',
resolvedUri: urlTo('playlist-0-uri'),
segments: [{
duration: 10,
uri: 'segment-0-uri',
resolvedUri: urlTo('segment-0-uri')
}]
}]
},
'updates playlists and media groups'
);
});

0 comments on commit de73795

Please sign in to comment.