From 67741f5611b74069a841a9a66daebdffbd34bcd5 Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 16 Jun 2023 15:28:18 +0200 Subject: [PATCH] Update DirectfileContentInitializer documentation --- src/core/api/public_api.ts | 2 + .../init/directfile_content_initializer.ts | 90 +++++++++++++++---- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts index c852ef044a..2f02c60e98 100644 --- a/src/core/api/public_api.ts +++ b/src/core/api/public_api.ts @@ -781,6 +781,8 @@ class Player extends EventEmitter { this.stop(); this._priv_currentError = null; throw new Error("DirectFile feature not activated in your build."); + } else if (isNullOrUndefined(url)) { + throw new Error("No URL for a DirectFile content"); } mediaElementTrackChoiceManager = this._priv_initializeMediaElementTrackChoiceManager( diff --git a/src/core/init/directfile_content_initializer.ts b/src/core/init/directfile_content_initializer.ts index 78fb588b19..660c8c1792 100644 --- a/src/core/init/directfile_content_initializer.ts +++ b/src/core/init/directfile_content_initializer.ts @@ -26,6 +26,7 @@ import { IKeySystemOption, IPlayerError, } from "../../public_types"; +import assert from "../../utils/assert"; import createSharedReference, { IReadOnlySharedReference, } from "../../utils/reference"; @@ -39,35 +40,68 @@ import initializeContentDecryption from "./utils/initialize_content_decryption"; import RebufferingController from "./utils/rebuffering_controller"; import listenToMediaError from "./utils/throw_on_media_error"; +/** + * `ContentIntializer` which will load contents by putting their URL in the + * `src` attribute of the given HTMLMediaElement. + * + * Because such contents are mainly loaded by the browser, those (called + * "directfile" contents in the RxPlayer) needs a simpler logic in-JS when + * compared to a content that relies on the MSE API. + * + * @class DirectFileContentInitializer + */ export default class DirectFileContentInitializer extends ContentInitializer { + /** + * Initial options given to the `DirectFileContentInitializer`. + */ private _settings : IDirectFileOptions; + /** + * Allows to abort and clean everything the `DirectFileContentInitializer` is + * doing. + */ private _initCanceller : TaskCanceller; + /** + * Creates a new `DirectFileContentInitializer` linked to the given settings. + * @param {Object} settings + */ constructor(settings : IDirectFileOptions) { super(); this._settings = settings; this._initCanceller = new TaskCanceller(); } - public prepare(): void { + /** + * "Prepare" content so it can later be played by calling `start`. + */ + public prepare() : void { return; // Directfile contents do not have any preparation } + /** + * Start playback of the content linked to this `DirectFileContentInitializer` + * on the given `HTMLMediaElement` and its associated `PlaybackObserver`. + * @param {HTMLMediaElement} mediaElement - HTMLMediaElement on which the + * content will be played. + * @param {Object} playbackObserver - Object regularly emitting playback + * information. + */ public start( mediaElement : HTMLMediaElement, playbackObserver : PlaybackObserver - ): void { + ) : void { const cancelSignal = this._initCanceller.signal; const { keySystems, speed, url } = this._settings; clearElementSrc(mediaElement); - if (url == null) { - throw new Error("No URL for a DirectFile content"); - } - + /** + * Create dummy encryption data emitter, as those are not sent from the + * RxPlayer for directfile contents. + */ const decryptionRef = createSharedReference(null); decryptionRef.finish(); + const drmInitRef = initializeContentDecryption(mediaElement, keySystems, decryptionRef, { onError: (err) => this._onFatalError(err), @@ -99,7 +133,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { drmInitRef.onUpdate((evt, stopListeningToDrmUpdates) => { if (evt.initializationState.type === "uninitialized") { - return; + return; // nothing done yet } stopListeningToDrmUpdates(); @@ -109,39 +143,57 @@ export default class DirectFileContentInitializer extends ContentInitializer { cancelSignal.register(() => { clearElementSrc(mediaElement); }); + if (evt.initializationState.type === "awaiting-media-link") { evt.initializationState.value.isMediaLinked.setValue(true); drmInitRef.onUpdate((newDrmStatus, stopListeningToDrmUpdatesAgain) => { if (newDrmStatus.initializationState.type === "initialized") { stopListeningToDrmUpdatesAgain(); this._seekAndPlay(mediaElement, playbackObserver); - return; } }, { emitCurrentValue: true, clearSignal: cancelSignal }); } else { + assert(evt.initializationState.type === "initialized"); this._seekAndPlay(mediaElement, playbackObserver); - return; } }, { emitCurrentValue: true, clearSignal: cancelSignal }); } + /** + * Update URL this `ContentIntializer` depends on. + * @param {Array.|undefined} _urls + * @param {boolean} _refreshNow + */ public updateContentUrls(_urls : string[] | undefined, _refreshNow : boolean) : void { throw new Error("Cannot update content URL of directfile contents"); } - public dispose(): void { + /** + * Stop content and free all resources linked to this `ContentIntializer`. + */ + public dispose() : void { this._initCanceller.cancel(); } - private _onFatalError(err : unknown) { + /** + * Logic performed when a fatal error was triggered. + * @param {*} err - The fatal error in question. + */ + private _onFatalError(err : unknown) : void { this._initCanceller.cancel(); this.trigger("error", err); } + /** + * Perform the initial seek (to begin playback at an initially-calculated + * position based on settings) and auto-play if needed when loaded. + * @param {HTMLMediaElement} mediaElement + * @param {Object} playbackObserver + */ private _seekAndPlay( mediaElement : HTMLMediaElement, playbackObserver : PlaybackObserver - ) { + ) : void { const cancelSignal = this._initCanceller.signal; const { autoPlay, startAt } = this._settings; const initialTime = () => { @@ -177,7 +229,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { /** * calculate initial time as a position in seconds. * @param {HTMLMediaElement} mediaElement - * @param {Object|undefined} startAt + * @param {Object|undefined} [startAt] * @returns {number} */ function getDirectFileInitialTime( @@ -219,11 +271,19 @@ function getDirectFileInitialTime( return 0; } -// Argument used by `initializeDirectfileContent` +/** Options used by the `DirectFileContentInitializer` */ export interface IDirectFileOptions { + /** If `true` we will play right after the content is considered "loaded". */ autoPlay : boolean; + /** + * Encryption-related settings. Can be left as an empty array if the content + * isn't encrypted. + */ keySystems : IKeySystemOption[]; + /** Communicate the playback rate wanted by the user. */ speed : IReadOnlySharedReference; + /** Optional initial position to start at. */ startAt? : IInitialTimeOptions | undefined; - url? : string | undefined; + /** URL that should be played. */ + url : string; }