Skip to content

Commit

Permalink
Implement Preload feature
Browse files Browse the repository at this point in the history
  • Loading branch information
peaBerberian committed Mar 26, 2024
1 parent 1664e2a commit b4859f8
Show file tree
Hide file tree
Showing 26 changed files with 1,563 additions and 557 deletions.
97 changes: 97 additions & 0 deletions demo/full/scripts/controllers/ContentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,12 @@ function getKeySystemsOption(

function ContentList({
loadVideo,
preloadVideo,
showOptions,
onOptionToggle,
}: {
loadVideo: (opts: ILoadVideoOptions) => void;
preloadVideo: (opts: ILoadVideoOptions) => void;
showOptions: boolean;
onOptionToggle: () => void;
}): JSX.Element {
Expand Down Expand Up @@ -314,6 +316,43 @@ function ContentList({
[loadVideo],
);

/**
* Load the given displayed content through the player.
* @param {Object|null} content
*/
const preloadContent = React.useCallback(
(content: IDisplayedContent) => {
const {
url,
transport,
fallbackKeyError,
fallbackLicenseRequest,
isLowLatency,
drmInfos,
} = content;

getKeySystemsOption(drmInfos ?? [], {
fallbackKeyError: !!fallbackKeyError,
fallbackLicenseRequest: !!fallbackLicenseRequest,
}).then(
(keySystems) => {
preloadVideo({
url: url ?? "",
transport: transport ?? "",
textTrackMode: "html",
lowLatencyMode: isLowLatency,
keySystems,
});
},
() => {
/* eslint-disable-next-line no-console */
console.error("Could not construct key systems option");
},
);
},
[loadVideo],
);

/**
* Load a custom content entered in a custom link.
* @param {string} url
Expand Down Expand Up @@ -349,6 +388,41 @@ function ContentList({
],
);

/**
* Load a custom content entered in a custom link.
* @param {string} url
* @param {Array.<Object>} drmInfos
*/
const preloadUrl = React.useCallback(
(url: string, drmInfos: IDrmInfo[]) => {
getKeySystemsOption(drmInfos, {
fallbackKeyError: !!shouldFallbackOnKeyError,
fallbackLicenseRequest: !!shouldFallbackOnLicenseReqError,
}).then(
(keySystems) => {
preloadVideo({
url: url,
transport: transportType.toLowerCase(),
textTrackMode: "html",
lowLatencyMode: isLowLatencyChecked,
keySystems,
});
},
() => {
/* eslint-disable-next-line no-console */
console.error("Could not construct key systems option");
},
);
},
[
loadVideo,
shouldFallbackOnKeyError,
shouldFallbackOnLicenseReqError,
transportType,
isLowLatencyChecked,
],
);

/**
* Change the content chosen in the list.
* @param {number} index - index in the lsit
Expand Down Expand Up @@ -515,6 +589,22 @@ function ContentList({
}
};

const onClickPreload = () => {
if (contentChoiceIndex === 0) {
const drmInfos = [
{
licenseServerUrl,
serverCertificateUrl,
drm: chosenDRMType,
customKeySystem,
},
];
preloadUrl(currentManifestURL, drmInfos);
} else {
preloadContent(contentsToSelect[contentChoiceIndex]);
}
};

const saveCurrentContent = () => {
const contentToSave: IContentInfo = {
name: contentNameField,
Expand Down Expand Up @@ -751,6 +841,13 @@ function ContentList({
value={String.fromCharCode(0xf144)}
disabled={false}
/>
<Button
className="choice-input-button load-button"
ariaLabel="Preload the selected content now"
onClick={onClickPreload}
value={String.fromCharCode(0x2b07)}
disabled={false}
/>
</div>
</div>
{isCustomContent || (isLocalContent && isSavingOrUpdating) ? (
Expand Down
39 changes: 37 additions & 2 deletions demo/full/scripts/controllers/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,33 @@ function Player(): JSX.Element {
}
}, [playerModule]);

const startContent = useCallback(
const preloadContentCallback = useCallback(
(contentInfo: ILoadVideoOptions) => {
let playerMod = playerModule;

// TODO this risks stopping the previous content if instance options
// have been updated
if (playerMod === null || hasUpdatedPlayerOptions) {
setHasUpdatedPlayerOptions(false);
const created = createNewPlayerModule();
if (created === undefined) {
return;
}
created.actions.updateWorkerMode(relyOnWorker);
playerMod = created;
}
preloadContent(playerMod, contentInfo, loadVideoOpts);
},
[
playerModule,
relyOnWorker,
hasUpdatedPlayerOptions,
createNewPlayerModule,
loadVideoOpts,
],
);

const startContentCallback = useCallback(
(contentInfo: ILoadVideoOptions) => {
let playerMod = playerModule;
if (playerMod === null || hasUpdatedPlayerOptions) {
Expand Down Expand Up @@ -261,7 +287,8 @@ function Player(): JSX.Element {
<section className="video-player-section">
<div className="video-player-content">
<ContentList
loadVideo={startContent}
loadVideo={startContentCallback}
preloadVideo={preloadContentCallback}
showOptions={showOptions}
onOptionToggle={onOptionToggle}
/>
Expand Down Expand Up @@ -344,4 +371,12 @@ function loadContent(
playerModule.actions.load(Object.assign({}, contentInfo, loadVideoOpts));
}

function preloadContent(
playerModule: IPlayerModule,
contentInfo: ILoadVideoOptions,
loadVideoOpts: ILoadVideoSettings,
) {
playerModule.actions.preload(Object.assign({}, contentInfo, loadVideoOpts));
}

export default Player;
47 changes: 43 additions & 4 deletions demo/full/scripts/modules/player/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ export interface IPlayerModuleState {
maximumPosition: null | undefined | number;
minimumPosition: null | undefined | number;
playbackRate: number;
preloads: Array<{
url: string | undefined;
id: string;
}>;
/** Try to play contents in "multithread" mode when possible. */
relyOnWorker: boolean;
/** Currently playing a content in "multithread" mode. */
Expand Down Expand Up @@ -196,6 +200,7 @@ const PlayerModule = declareModule(
maximumPosition: undefined,
minimumPosition: undefined,
playbackRate: 1,
preloads: [],
relyOnWorker: false,
useWorker: false,
subtitle: undefined,
Expand Down Expand Up @@ -274,6 +279,35 @@ const PlayerModule = declareModule(
state.update("relyOnWorker", enabled);
},

preload(arg: ILoadVideoOptions) {
const contentInfo = player.preloadVideo(
Object.assign(
{
mode: state.get("relyOnWorker") ? "auto" : "main",
textTrackElement,
transportOptions: { checkMediaSegmentIntegrity: true },
},
arg,
) as ILoadVideoOptions,
);
const preloads = state.get("preloads");
preloads.push({
id: contentInfo.id,
url: arg.url,
});
state.update("preloads", preloads);
},

loadPreload(preloadId: string) {
player.startPreload(preloadId);
const preloads = state.get("preloads");
const index = preloads.findIndex((p) => p.id === preloadId);
if (index >= 0) {
preloads.splice(index, 1);
}
state.update("preloads", preloads);
},

load(arg: ILoadVideoOptions) {
dettachVideoThumbnailLoader();
player.loadVideo(
Expand Down Expand Up @@ -445,10 +479,15 @@ const PlayerModule = declareModule(

function attachMultithread(player: RxPlayer) {
hasAttachedMultithread = true;
player.attachWorker({
workerUrl: "./worker.js",
dashWasmUrl: "./mpd-parser.wasm",
});
player
.attachWorker({
workerUrl: "./worker.js",
dashWasmUrl: "./mpd-parser.wasm",
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error("Failed to initialize worker", err);
});
}
},
);
Expand Down
26 changes: 18 additions & 8 deletions src/core/main/common/create_content_time_boundaries_observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,24 @@ export interface IContentTimeBoundariesObserverCallbacks {
* Various methods from that class need then to be called at various events
* (see `ContentTimeBoundariesObserver`).
* @param {Object} manifest
* @param {Object} mediaSource
* @param {Object|null} mediaSource
* @param {Object} streamObserver
* @param {Object} segmentSinksStore
* @param {Object} cancelSignal
* @returns {Object}
*/
export default function createContentTimeBoundariesObserver(
manifest: IManifest,
mediaSource: IMediaSourceInterface,
mediaSource: IMediaSourceInterface | null,
streamObserver: IReadOnlyPlaybackObserver<IStreamOrchestratorPlaybackObservation>,
segmentSinksStore: ISegmentSinksStore,
callbacks: IContentTimeBoundariesObserverCallbacks,
cancelSignal: CancellationSignal,
): ContentTimeBoundariesObserver {
cancelSignal.register(() => {
mediaSource.interruptDurationSetting();
if (mediaSource !== null) {
mediaSource.interruptDurationSetting();
}
});
const contentTimeBoundariesObserver = new ContentTimeBoundariesObserver(
manifest,
Expand All @@ -56,16 +58,24 @@ export default function createContentTimeBoundariesObserver(
callbacks.onPeriodChanged(period),
);
contentTimeBoundariesObserver.addEventListener("endingPositionChange", (evt) => {
mediaSource.setDuration(evt.endingPosition, evt.isEnd);
if (mediaSource !== null) {
mediaSource.setDuration(evt.endingPosition, evt.isEnd);
}
});
contentTimeBoundariesObserver.addEventListener("endOfStream", () => {
log.debug("Init: end-of-stream order received.");
mediaSource.maintainEndOfStream();
if (mediaSource !== null) {
log.debug("Init: end-of-stream order received.");
mediaSource.maintainEndOfStream();
}
});
contentTimeBoundariesObserver.addEventListener("resumeStream", () => {
mediaSource.stopEndOfStream();
if (mediaSource !== null) {
mediaSource.stopEndOfStream();
}
});
const obj = contentTimeBoundariesObserver.getCurrentEndingTime();
mediaSource.setDuration(obj.endingPosition, obj.isEnd);
if (mediaSource !== null) {
mediaSource.setDuration(obj.endingPosition, obj.isEnd);
}
return contentTimeBoundariesObserver;
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export default class AudioVideoSegmentSink extends SegmentSink {
* @param {Object} infos
* @returns {Promise}
*/
public async pushChunk(infos: IPushChunkInfos<unknown>): Promise<IRange[]> {
public async pushChunk(infos: IPushChunkInfos<unknown>): Promise<IRange[] | undefined> {
assertDataIsBufferSource(infos.data.chunk);
log.debug(
"AVSB: receiving order to push data to the SourceBuffer",
Expand Down Expand Up @@ -205,12 +205,14 @@ export default class AudioVideoSegmentSink extends SegmentSink {
);
}
const ranges = res[res.length - 1];
this._segmentInventory.synchronizeBuffered(ranges);
if (ranges !== undefined) {
this._segmentInventory.synchronizeBuffered(ranges);
}
return ranges;
}

/** @see SegmentSink */
public async removeBuffer(start: number, end: number): Promise<IRange[]> {
public async removeBuffer(start: number, end: number): Promise<IRange[] | undefined> {
log.debug(
"AVSB: receiving order to remove data from the SourceBuffer",
this.bufferType,
Expand All @@ -223,7 +225,9 @@ export default class AudioVideoSegmentSink extends SegmentSink {
value: { start, end },
});
const ranges = await promise;
this._segmentInventory.synchronizeBuffered(ranges);
if (ranges !== undefined) {
this._segmentInventory.synchronizeBuffered(ranges);
}
return ranges;
}

Expand Down
Loading

0 comments on commit b4859f8

Please sign in to comment.