Skip to content
Open
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
31 changes: 31 additions & 0 deletions src/main_thread/api/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,12 @@ class Player extends EventEmitter<IPublicAPIEvent> {
playerStateRef.onUpdate(
(newState: IPlayerState) => {
updateReloadingMetadata(newState);
if (newState === "ENDED") {
this._priv_loadChainedManifests();
if (currentContentCanceller.isUsed()) {
return;
}
}
this._priv_setPlayerState(newState);

if (currentContentCanceller.isUsed()) {
Expand Down Expand Up @@ -3800,6 +3806,31 @@ class Player extends EventEmitter<IPublicAPIEvent> {
}
}

/**
* Load the chained Manifest if one with roughly the same options than the
* previous `loadVideo` call (beside `startAt`).
* Do nothing if no Manifest to chain to exist or if it's not possible to do
* Manifest chaining in the current context.
*/
private _priv_loadChainedManifests(): void {
const nextManifestUrls = this._priv_contentInfos?.manifest?.chainedManifests;
if (isNullOrUndefined(nextManifestUrls) || nextManifestUrls.length === 0) {
return;
}
const options = this._priv_reloadingMetadata.options;
if (options === undefined) {
log.error("API", "Chaining Manifest despite not having any load option");
return;
}

const nextUrl = nextManifestUrls[0];
log.info("API", "Going to chained Manifest", { nextUrl });
const newOptions = { ...options, url: nextUrl, startAt: undefined };
this._priv_reloadingMetadata = { options: newOptions };
this._priv_initializeContentPlayback(newOptions);
this._priv_lastAutoPlay = newOptions.autoPlay;
}

/**
* Returns `true` if the content concerned by those options should load in
* multithread mode.
Expand Down
10 changes: 10 additions & 0 deletions src/manifest/classes/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,14 @@ export default class Manifest
};
};

/**
* Manifests can have a reference to another Manifest that should be played
* once the previous one is finished.
* If that's the case, `chainedManifests` is set to the wanted Manifests' URL,
* by order of preference.
*/
public chainedManifests: string[] | null;

/**
* Caches the information if a codec is supported or not in the context of the
* current content.
Expand Down Expand Up @@ -360,6 +368,7 @@ export default class Manifest
this.suggestedPresentationDelay = parsedManifest.suggestedPresentationDelay;
this.availabilityStartTime = parsedManifest.availabilityStartTime;
this.publishTime = parsedManifest.publishTime;
this.chainedManifests = parsedManifest.chainedManifests;
}

/**
Expand Down Expand Up @@ -653,6 +662,7 @@ export default class Manifest
uris: this.uris,
availabilityStartTime: this.availabilityStartTime,
timeBounds: this.timeBounds,
chainedManifests: this.chainedManifests,
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/manifest/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ export interface IManifestMetadata {
time: number;
};
};
/**
* Manifests can have a reference to another Manifest that should be played
* once the previous one is finished.
* If that's the case, `chainedManifests` is set to the wanted Manifests' URL,
* by order of preference.
*/
chainedManifests: string[] | null;
}

/**
Expand Down
20 changes: 20 additions & 0 deletions src/parsers/manifest/dash/common/parse_mpd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,25 @@ function parseCompleteIntermediateRepresentation(
(parsedPeriods[parsedPeriods.length - 1]?.end !== undefined ||
mpdIR.attributes.mediaPresentationDuration !== undefined));

let chainedManifests: null | string[] = null;
for (const prop of ["EssentialProperty", "SupplementalProperty"] as const) {
if (Array.isArray(mpdIR.children[prop])) {
const propChainedManifests = mpdIR.children[prop].reduce((acc: string[], s) => {
if (
s.attributes.schemeIdUri === "urn:mpeg:dash:chaining:2016" &&
s.attributes.value !== undefined
) {
acc.push(s.attributes.value);
}
return acc;
}, []);
if (propChainedManifests.length > 0) {
chainedManifests = chainedManifests ?? [];
chainedManifests.push(...propChainedManifests);
}
}
}

const parsedMPD: IParsedManifest = {
availabilityStartTime,
clockOffset: args.externalClockOffset,
Expand All @@ -437,6 +456,7 @@ function parseCompleteIntermediateRepresentation(
uris: isNullOrUndefined(args.url)
? rootChildren.Location.map((l) => l.value)
: [args.url, ...rootChildren.Location.map((l) => l.value)],
chainedManifests,
};

return { type: "done", value: { parsed: parsedMPD, warnings } };
Expand Down
10 changes: 10 additions & 0 deletions src/parsers/manifest/dash/js-parser/node_parsers/MPD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ function parseMPDChildren(
Period: [],
UTCTiming: [],
ContentProtection: [],
EssentialProperty: [],
SupplementalProperty: [],
};

let warnings: Error[] = [];
Expand Down Expand Up @@ -97,6 +99,14 @@ function parseMPDChildren(
}
break;
}

case "EssentialProperty":
ret.EssentialProperty.push(parseScheme(currentNode));
break;

case "SupplementalProperty":
ret.SupplementalProperty.push(parseScheme(currentNode));
break;
}
}
return [ret, warnings];
Expand Down
4 changes: 4 additions & 0 deletions src/parsers/manifest/dash/node_parser_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export interface IMPDChildren {
UTCTiming: ISchemeIntermediateRepresentation[];
/** Encryption-related metadata. */
ContentProtection: IContentProtectionIntermediateRepresentation[];
/** DASH `EssentialProperties` elements at the MPD level. */
EssentialProperty: ISchemeIntermediateRepresentation[];
/** DASH `SupplementalProperties` elements at the MPD level. */
SupplementalProperty: ISchemeIntermediateRepresentation[];
}

/* Intermediate representation for the root's attributes. */
Expand Down
23 changes: 23 additions & 0 deletions src/parsers/manifest/dash/wasm-parser/ts/generators/MPD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
IMPDAttributes,
IMPDChildren,
IPeriodIntermediateRepresentation,
ISchemeAttributes,
} from "../../../node_parser_types.ts";
import type { IAttributeParser, IChildrenParser } from "../parsers_stack.ts";
import type ParsersStack from "../parsers_stack.ts";
Expand Down Expand Up @@ -106,6 +107,28 @@ export function generateMPDChildrenParser(
break;
}

case TagName.EssentialProperty: {
const schemeAttrs: ISchemeAttributes = {};
if (mpdChildren.EssentialProperty === undefined) {
mpdChildren.EssentialProperty = [];
}
mpdChildren.EssentialProperty.push({ attributes: schemeAttrs });
const attributeParser = generateSchemeAttrParser(schemeAttrs, linearMemory);
parsersStack.pushParsers(nodeId, noop, attributeParser);
break;
}

case TagName.SupplementalProperty: {
const schemeAttrs = {};
if (mpdChildren.SupplementalProperty === undefined) {
mpdChildren.SupplementalProperty = [];
}
mpdChildren.SupplementalProperty.push({ attributes: schemeAttrs });
const attributeParser = generateSchemeAttrParser(schemeAttrs, linearMemory);
parsersStack.pushParsers(nodeId, noop, attributeParser);
break;
}

default:
// Allows to make sure we're not mistakenly closing a re-opened
// tag.
Expand Down
2 changes: 2 additions & 0 deletions src/parsers/manifest/dash/wasm-parser/ts/generators/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export function generateRootChildrenParser(
Period: [],
UTCTiming: [],
ContentProtection: [],
EssentialProperty: [],
SupplementalProperty: [],
},
attributes: {},
};
Expand Down
1 change: 1 addition & 0 deletions src/parsers/manifest/local/parse_local_manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default function parseLocalManifest(
},
},
periods: parsedPeriods,
chainedManifests: null,
};
}

Expand Down
1 change: 1 addition & 0 deletions src/parsers/manifest/metaplaylist/metaplaylist_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ function createManifest(
},
},
lifetime: mplData.pollInterval,
chainedManifests: null,
};

return manifest;
Expand Down
1 change: 1 addition & 0 deletions src/parsers/manifest/smooth/create_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ function createSmoothStreamingParser(
suggestedPresentationDelay,
transportType: "smooth",
uris: isNullOrUndefined(url) ? [] : [url],
chainedManifests: null,
};
checkManifestIDs(manifest);
return manifest;
Expand Down
7 changes: 7 additions & 0 deletions src/parsers/manifest/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,11 @@ export interface IParsedManifest {
suggestedPresentationDelay?: number | undefined;
/** URIs where the manifest can be refreshed by order of importance. */
uris?: string[] | undefined;
/**
* Manifests can have a reference to another Manifest that should be played
* once the previous one is finished.
* If that's the case, `chainedManifests` is set to the wanted Manifests' URL,
* by order of preference.
*/
chainedManifests: string[] | null;
}
1 change: 0 additions & 1 deletion tests/integration/scenarios/dash_fake_live.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ describe("DASH live content (SegmentTimeline)", function () {
manifestLoader,
segmentLoader,
});
expect(player.getAvailableVideoTracks()).to.eql([]);

await sleep(0);
expect(manifestLoaderCalledTimes).to.equal(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function generateFakeManifestWithRepresentations(
id: "manifest1",
isDynamic: false,
isLive: false,
chainedManifests: null,
timeBounds: {
minimumSafePosition: 0,
timeshiftDepth: null,
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/src/manifest/classes/manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ describe("Manifest - Manifest", () => {
isDynamic: false,
isLive: false,
duration: 5,
chainedManifests: null,
timeBounds: {
minimumSafePosition: 0,
timeshiftDepth: null,
Expand Down Expand Up @@ -138,6 +139,7 @@ describe("Manifest - Manifest", () => {
isDynamic: false,
isLive: false,
duration: 5,
chainedManifests: null,
timeBounds: {
minimumSafePosition: 0,
timeshiftDepth: null,
Expand Down Expand Up @@ -189,6 +191,7 @@ describe("Manifest - Manifest", () => {
isLastPeriodKnown: true,
isLive: false,
duration: 5,
chainedManifests: null,
timeBounds: {
minimumSafePosition: 0,
timeshiftDepth: null,
Expand Down Expand Up @@ -243,6 +246,7 @@ describe("Manifest - Manifest", () => {
isLastPeriodKnown: true,
isLive: false,
duration: 5,
chainedManifests: null,
timeBounds: {
minimumSafePosition: 0,
timeshiftDepth: null,
Expand Down Expand Up @@ -291,6 +295,7 @@ describe("Manifest - Manifest", () => {
const oldManifestArgs = {
availabilityStartTime: 5,
duration: 12,
chainedManifests: null,
id: "man",
transportType: "dash",
isLastPeriodKnown: true,
Expand Down Expand Up @@ -359,6 +364,7 @@ describe("Manifest - Manifest", () => {
const oldManifestArgs1 = {
availabilityStartTime: 5,
duration: 12,
chainedManifests: null,
id: "man",
isDynamic: false,
isLive: false,
Expand Down Expand Up @@ -386,6 +392,7 @@ describe("Manifest - Manifest", () => {
const oldManifestArgs2 = {
availabilityStartTime: 5,
duration: 12,
chainedManifests: null,
id: "man",
isDynamic: false,
isLive: false,
Expand Down Expand Up @@ -428,6 +435,7 @@ describe("Manifest - Manifest", () => {
const oldManifestArgs = {
availabilityStartTime: 5,
duration: 12,
chainedManifests: null,
id: "man",
transportType: "dash",
isLastPeriodKnown: true,
Expand Down
Loading