Skip to content

Commit

Permalink
fix(DASH): Fix MPD Patch when SegmentTemplate is shared between Repre…
Browse files Browse the repository at this point in the history
…sentations (#7218)

Fixes #7214
  • Loading branch information
tykus160 authored and avelad committed Aug 29, 2024
1 parent b7ebe9e commit 0635c10
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 45 deletions.
66 changes: 26 additions & 40 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Networking');
goog.require('shaka.util.ObjectUtils');
goog.require('shaka.util.OperationManager');
goog.require('shaka.util.PeriodCombiner');
goog.require('shaka.util.PlayerConfiguration');
Expand Down Expand Up @@ -1459,6 +1460,7 @@ shaka.dash.DashParser = class {
const TXml = shaka.util.TXml;
const ContentType = shaka.util.ManifestParserUtils.ContentType;

goog.asserts.assert(periodInfo.node, 'periodInfo.node should exist');
context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
context.periodInfo = periodInfo;
context.period.availabilityTimeOffset = context.availabilityTimeOffset;
Expand Down Expand Up @@ -2247,45 +2249,29 @@ shaka.dash.DashParser = class {
* @private
*/
cloneContext_(context) {
const contextClone = /** @type {!shaka.dash.DashParser.Context} */({});

for (const k of Object.keys(context)) {
if (['period', 'adaptationSet', 'representation'].includes(k)) {
/** @type {shaka.dash.DashParser.InheritanceFrame} */
const frameRef = context[k];
contextClone[k] = {
segmentBase: null,
segmentList: null,
segmentTemplate: frameRef.segmentTemplate,
getBaseUris: frameRef.getBaseUris,
width: frameRef.width,
height: frameRef.height,
contentType: frameRef.contentType,
mimeType: frameRef.mimeType,
language: frameRef.language,
codecs: frameRef.codecs,
frameRate: frameRef.frameRate,
pixelAspectRatio: frameRef.pixelAspectRatio,
emsgSchemeIdUris: frameRef.emsgSchemeIdUris,
id: frameRef.id,
position: frameRef.position,
numChannels: frameRef.numChannels,
audioSamplingRate: frameRef.audioSamplingRate,
availabilityTimeOffset: frameRef.availabilityTimeOffset,
initialization: frameRef.initialization,
};
} else if (k == 'periodInfo') {
/** @type {shaka.dash.DashParser.PeriodInfo} */
const frameRef = context[k];
contextClone[k] = {
start: frameRef.start,
duration: frameRef.duration,
node: null,
isLastPeriod: frameRef.isLastPeriod,
};
} else {
contextClone[k] = context[k];
/**
* @param {?shaka.dash.DashParser.InheritanceFrame} frame
* @return {?shaka.dash.DashParser.InheritanceFrame}
*/
const cloneFrame = (frame) => {
if (!frame) {
return null;
}
const clone = shaka.util.ObjectUtils.shallowCloneObject(frame);
clone.segmentBase = null;
clone.segmentList = null;
clone.segmentTemplate = shaka.util.TXml.cloneNode(clone.segmentTemplate);
return clone;
};
const contextClone = shaka.util.ObjectUtils.shallowCloneObject(context);
contextClone.period = cloneFrame(contextClone.period);
contextClone.adaptationSet = cloneFrame(contextClone.adaptationSet);
contextClone.representation = cloneFrame(contextClone.representation);

if (contextClone.periodInfo) {
contextClone.periodInfo =
shaka.util.ObjectUtils.shallowCloneObject(contextClone.periodInfo);
contextClone.periodInfo.node = null;
}

return contextClone;
Expand Down Expand Up @@ -3131,7 +3117,7 @@ shaka.dash.DashParser.Context;
* @typedef {{
* start: number,
* duration: ?number,
* node: !shaka.extern.xml.Node,
* node: ?shaka.extern.xml.Node,
* isLastPeriod: boolean
* }}
*
Expand All @@ -3143,7 +3129,7 @@ shaka.dash.DashParser.Context;
* @property {?number} duration
* The duration of the period; or null if the duration is not given. This
* will be non-null for all periods except the last.
* @property {!shaka.extern.xml.Node} node
* @property {?shaka.extern.xml.Node} node
* The XML Node for the Period.
* @property {boolean} isLastPeriod
* Whether this Period is the last one in the manifest.
Expand Down
29 changes: 29 additions & 0 deletions lib/util/tXml.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

goog.provide('shaka.util.TXml');

goog.require('shaka.util.ObjectUtils');
goog.require('shaka.util.StringUtils');
goog.require('shaka.log');

Expand Down Expand Up @@ -892,6 +893,34 @@ shaka.util.TXml = class {

return element;
}

/**
* Clones node and its children recursively. Skips parent.
* @param {?shaka.extern.xml.Node} node
* @return {?shaka.extern.xml.Node}
*/
static cloneNode(node) {
if (!node) {
return null;
}
/** @type {!shaka.extern.xml.Node} */
const clone = {
tagName: node.tagName,
attributes: shaka.util.ObjectUtils.shallowCloneObject(node.attributes),
children: [],
parent: null,
};
for (const child of node.children) {
if (typeof child === 'string') {
clone.children.push(child);
} else {
const clonedChild = shaka.util.TXml.cloneNode(child);
clonedChild.parent = clone;
clone.children.push(clonedChild);
}
}
return clone;
}
};

shaka.util.TXml.knownNameSpaces_ = new Map([]);
Expand Down
79 changes: 74 additions & 5 deletions test/dash/dash_parser_patch_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ describe('DashParser Patch', () => {
].join('/');
const patchText = [
`<Patch mpdId="${mpdId}"`,
` originalPublishTime="${publishTime.toUTCString()}"">`,
` originalPublishTime="${publishTime.toUTCString()}">`,
` <add sel="${xPath}" pos="after">`,
' <S d="1" t="1" />',
' </add>',
Expand Down Expand Up @@ -475,7 +475,7 @@ describe('DashParser Patch', () => {
].join('/');
const patchText = [
`<Patch mpdId="${mpdId}"`,
` originalPublishTime="${publishTime.toUTCString()}"">`,
` originalPublishTime="${publishTime.toUTCString()}">`,
` <add sel="${xPath}" pos="after">`,
' <S d="3" t="1" />',
' </add>',
Expand All @@ -493,7 +493,7 @@ describe('DashParser Patch', () => {
].join('/');
const patchText2 = [
`<Patch mpdId="${mpdId}"`,
` originalPublishTime="${publishTime.toUTCString()}"">`,
` originalPublishTime="${publishTime.toUTCString()}">`,
` <add sel="${xPath2}" pos="after">`,
' <S d="3" t="4" />',
' </add>',
Expand Down Expand Up @@ -563,7 +563,7 @@ describe('DashParser Patch', () => {
].join('/');
const patchText = [
`<Patch mpdId="${mpdId}"`,
` originalPublishTime="${publishTime.toUTCString()}"">`,
` originalPublishTime="${publishTime.toUTCString()}">`,
` <add sel="${xPath}" pos="after">`,
' <S d="3" t="1" />',
' </add>',
Expand All @@ -581,7 +581,7 @@ describe('DashParser Patch', () => {
].join('/');
const patchText2 = [
`<Patch mpdId="${mpdId}"`,
` originalPublishTime="${publishTime.toUTCString()}"">`,
` originalPublishTime="${publishTime.toUTCString()}">`,
` <add sel="${xPath2}" pos="after">`,
' <S d="3" t="4" n="3" />',
' </add>',
Expand Down Expand Up @@ -639,5 +639,74 @@ describe('DashParser Patch', () => {
ManifestParser.makeReference('s4.mp4', 7, 10, originalUri),
]);
});

it('extends shared timeline between representations', async () => {
const manifestText = [
`<MPD id="${mpdId}" type="dynamic"`,
' availabilityStartTime="1970-01-01T00:00:00Z"',
` publishTime="${publishTime.toUTCString()}"`,
' suggestedPresentationDelay="PT5S"',
` minimumUpdatePeriod="PT${updateTime}S">`,
` <PatchLocation ttl="${ttl}">dummy://bar</PatchLocation>`,
' <Period id="1">',
' <AdaptationSet id="1" mimeType="video/mp4">',
' <SegmentTemplate media="s$Number$.mp4">',
' <SegmentTimeline>',
' <S d="1" t="0" />',
' </SegmentTimeline>',
' </SegmentTemplate>',
' <Representation id="3" bandwidth="500">',
' <BaseURL>http://example.com/v3/</BaseURL>',
' </Representation>',
' <Representation id="4" bandwidth="1000">',
' <BaseURL>http://example.com/v4/</BaseURL>',
' </Representation>',
' </AdaptationSet>',
' </Period>',
'</MPD>',
].join('\n');
const xPath = '/' + [
'MPD',
'Period[@id=\'1\']',
'AdaptationSet[@id=\'1\']',
'SegmentTemplate',
'SegmentTimeline',
'S',
].join('/');
const patchText = [
`<Patch mpdId="${mpdId}"`,
` originalPublishTime="${publishTime.toUTCString()}">`,
` <add sel="${xPath}" pos="after">`,
' <S d="1" t="1" />',
' </add>',
'</Patch>',
].join('\n');
fakeNetEngine.setResponseText('dummy://foo', manifestText);
fakeNetEngine.setResponseText('dummy://bar', patchText);

const manifest = await parser.start('dummy://foo', playerInterface);
expect(manifest.variants.length).toBe(2);
for (const variant of manifest.variants) {
const stream = variant.video;
expect(stream).toBeTruthy();
// eslint-disable-next-line no-await-in-loop
await stream.createSegmentIndex();
ManifestParser.verifySegmentIndex(stream, [
ManifestParser.makeReference('s1.mp4', 0, 1,
`${originalUri}v${stream.originalId}/`),
]);
}

await updateManifest();
for (const variant of manifest.variants) {
const stream = variant.video;
ManifestParser.verifySegmentIndex(stream, [
ManifestParser.makeReference('s1.mp4', 0, 1,
`${originalUri}v${stream.originalId}/`),
ManifestParser.makeReference('s2.mp4', 1, 2,
`${originalUri}v${stream.originalId}/`),
]);
}
});
});
});
38 changes: 38 additions & 0 deletions test/util/tXml_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,4 +515,42 @@ describe('tXml', () => {
t: null, n: 42, attribute: null},
]);
});

it('cloneNode', () => {
expect(TXml.cloneNode(null)).toBe(null);
const root = {
tagName: 'Parent',
attributes: {},
children: [],
parent: null,
};
const node = {
tagName: 'Test',
attributes: {
'attr1': 'val1',
'attr2': 'val2',
},
children: ['string_child'],
parent: root,
};
const child = {
tagName: 'child',
attributes: {},
children: [],
parent: node,
};
root.children.push(node);
node.children.push(child);

const clone = TXml.cloneNode(node);
expect(clone).not.toBe(node);
expect(clone.tagName).toBe(node.tagName);
expect(clone.attributes).not.toBe(node.attributes);
expect(clone.attributes).toEqual(node.attributes);
expect(clone.parent).toBe(null);
expect(clone.children[0]).toBe('string_child');
expect(clone.children[1]).not.toBe(child);
expect(clone.children[1].tagName).toBe(child.tagName);
expect(clone.children[1].parent).toBe(clone);
});
});

0 comments on commit 0635c10

Please sign in to comment.