From 1627a5c5430c9d251227ca28007a4e91d7354c42 Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Thu, 8 Jan 2026 17:03:35 +0100 Subject: [PATCH 1/5] Pass arround MediaSourceClass to replace DummyMediaElement's hotfix Back in our `v4.4.0` we brought the `DummyMediaElement` feature allowing to run the RxPlayer's logic even on environments where actual decoding and/or decryption is not possible (incompatible codecs, DRMs, unavailable media API etc.). Our main use case for this are testing purposes. While wrapping up the release, we noted that we still relied on the actual `MediaSource` API for checking codec support, removing a key use case of the feature: testing the RxPlayer on a content even if the current env does not support any of its codec. So we quickly implemented an exception in the corresponding code branch. It works and should catch all cases, but it was nonetheless not satisfactory for long-term maintainance. --- To make it more explicit, I propose here to pass from the `API` to the `Init` the `MediaSource` class that should be used to play the content. In a `DummyMediaElement`'s case, the `MediaSource` implementation linked to it will be provided instead of the favored `MediaSource` implementation found on the device. A remaining issue is that as the class is not transmitted to `core` (not serializable), `core` will still need to rely on the device's favored `MediaSource` implem. I thought that it wasn't an issue as a `DummyMediaElement` is not compatible for now with "MSE-in-worker" which is the only scenario where communicating its linked `MediaSource` would be needed. From a9e380a139efdb071acd376b8c3efda006aa470d Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 7 Feb 2025 16:40:08 +0100 Subject: [PATCH 2/5] CoreInterface: Rename files and classes --- src/main_thread/init/media_source_content_initializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main_thread/init/media_source_content_initializer.ts b/src/main_thread/init/media_source_content_initializer.ts index 44d1f42aa2..36b9810634 100644 --- a/src/main_thread/init/media_source_content_initializer.ts +++ b/src/main_thread/init/media_source_content_initializer.ts @@ -54,7 +54,7 @@ import arrayFind from "../../utils/array_find.ts"; import assert, { assertUnreachable } from "../../utils/assert.ts"; import idGenerator from "../../utils/id_generator.ts"; import isNullOrUndefined from "../../utils/is_null_or_undefined.ts"; -import type { IAcceptedLogValue } from "../../utils/logger.ts"; +import { IAcceptedLogValue } from "../../utils/logger.ts"; import objectAssign from "../../utils/object_assign.ts"; import type { IReadOnlySharedReference } from "../../utils/reference.ts"; import SharedReference from "../../utils/reference.ts"; From 4a09dd0df9b6b1d3fe625b47abf0107574577a00 Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Tue, 12 Mar 2024 18:26:11 +0100 Subject: [PATCH 3/5] Implement Preload feature --- demo/scripts/controllers/ContentList.tsx | 97 ++++ demo/scripts/controllers/Player.tsx | 39 +- demo/scripts/modules/player/index.ts | 34 ++ src/ARCHITECTURE_DOC-PRELOADING.md | 264 +++++++++ src/core/entry/content_preparer.ts | 267 +++++---- src/core/entry/core_entry.ts | 27 +- ...create_content_time_boundaries_observer.ts | 49 +- .../audio_video/audio_video_segment_sink.ts | 182 ++++++- .../segment_sinks/implementations/types.ts | 4 +- src/core/segment_sinks/segment_sinks_store.ts | 87 ++- .../stream/adaptation/adaptation_stream.ts | 7 +- .../representation/representation_stream.ts | 2 +- src/core/stream/representation/types.ts | 2 +- .../utils/append_segment_to_buffer.ts | 2 +- src/main_thread/api/public_api.ts | 514 +++++++++++++----- .../init/directfile_content_initializer.ts | 35 +- .../init/media_source_content_initializer.ts | 386 +++++++------ src/main_thread/init/types.ts | 52 +- .../init/utils/initial_seek_and_play.ts | 14 +- .../utils/initialize_content_decryption.ts | 9 +- .../init/utils/rebuffering_controller.ts | 2 +- .../html/html_text_displayer.ts | 40 +- .../native/native_text_displayer.ts | 72 ++- src/main_thread/text_displayer/types.ts | 3 + src/main_thread/types.ts | 41 ++ src/mse/main_media_source_interface.ts | 3 +- src/mse/types.ts | 18 +- .../media_element_playback_observer.ts | 53 ++ src/playback_observer/types.ts | 3 +- 29 files changed, 1776 insertions(+), 532 deletions(-) create mode 100644 src/ARCHITECTURE_DOC-PRELOADING.md diff --git a/demo/scripts/controllers/ContentList.tsx b/demo/scripts/controllers/ContentList.tsx index 1b91b72517..9ece7b4724 100644 --- a/demo/scripts/controllers/ContentList.tsx +++ b/demo/scripts/controllers/ContentList.tsx @@ -260,10 +260,12 @@ function getKeySystemsOption( function ContentList({ loadVideo, + preloadVideo, showOptions, onOptionToggle, }: { loadVideo: (opts: ILoadVideoOptions) => void; + preloadVideo: (opts: ILoadVideoOptions) => void; showOptions: boolean; onOptionToggle: () => void; }): React.JSX.Element { @@ -327,6 +329,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 @@ -362,6 +401,41 @@ function ContentList({ ], ); + /** + * Load a custom content entered in a custom link. + * @param {string} url + * @param {Array.} 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 @@ -530,6 +604,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, @@ -766,6 +856,13 @@ function ContentList({ value={String.fromCharCode(0xf144)} disabled={false} /> +