Skip to content

Commit

Permalink
Remove MediaSourceContentInitializer, add CoreInterface
Browse files Browse the repository at this point in the history
This is a proof-of-concept where I try to put in common the "content
initialization" logic for our "multithread" mode and our monothreaded
mode.

The idea is to replace that mode-specific code to a very thin layer
(here called `CoreInterface`) between our "init" code (always running in
main thread) and our "core" code (in a WebWorker in "multithread" mode)
which would handle both modes:

  - in multithreaded mode, it would be the part doing `postmessage` calls
    and `onmessage` registering for thread communication

  - in monothreaded mode, it would just do the same thing through a very
    simple EventEmitter-like approach

Or written another way: "multithread" and single-threaded mode would now
share the same logic beside the communication system used at the frontier
between the main-thread and potential worker (respectively
`src/main_thread` code and `src/core` code).

The end goal is to remove a lot of code, and to reduce the difference
between the multithreaded and monothreaded logic, so our tests
(integration, manual tests etc.) actually almost test the two in one go.

There might be some performance lost due to steps we are now performing
unnecessarily when in monothreaded mode (e.g. we serialize the Manifest
structure even though it's not needed when a single thread is used, we
create another PlaybackObserver on the core even though the main thread
one could be re-used etc.). To see if that lead to a visible difference
and if it does, it shouldn't be that hard to work-around.
  • Loading branch information
peaBerberian committed Jan 13, 2025
1 parent 1e4ca54 commit 3db53f1
Show file tree
Hide file tree
Showing 28 changed files with 488 additions and 1,645 deletions.
34 changes: 21 additions & 13 deletions src/core/main/worker/content_preparer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import WorkerMediaSourceInterface from "../../../mse/worker_media_source_interfa
import type {
IAttachMediaSourceWorkerMessagePayload,
IContentInitializationData,
IWorkerMessage,
} from "../../../multithread_types";
import { WorkerMessageType } from "../../../multithread_types";
import type { IPlayerError } from "../../../public_types";
import assert from "../../../utils/assert";
import idGenerator from "../../../utils/id_generator";
import objectAssign from "../../../utils/object_assign";
import type {
Expand All @@ -28,8 +28,8 @@ import SegmentSinksStore from "../../segment_sinks";
import type { INeedsMediaSourceReloadPayload } from "../../stream";
import DecipherabilityFreezeDetector from "../common/DecipherabilityFreezeDetector";
import { limitVideoResolution, throttleVideoBitrate } from "./globals";
import sendMessage, { formatErrorForSender } from "./send_message";
import TrackChoiceSetter from "./track_choice_setter";
import { formatErrorForSender } from "./utils";
import WorkerTextDisplayerInterface from "./worker_text_displayer_interface";

const generateMediaSourceId = idGenerator();
Expand Down Expand Up @@ -74,6 +74,7 @@ export default class ContentPreparer {
}

public initializeNewContent(
sendMessage: (msg: IWorkerMessage, transferables?: Transferable[]) => void,
context: IContentInitializationData,
): Promise<IManifestMetadata> {
return new Promise((res, rej) => {
Expand All @@ -84,19 +85,20 @@ export default class ContentPreparer {

currentMediaSourceCanceller.linkToSignal(contentCanceller.signal);

const { contentId, url, hasText, transportOptions } = context;
const { contentId, url, hasText, transport, transportOptions } = context;
let manifest: IManifest | null = null;

// TODO better way
assert(
features.transports.dash !== undefined,
"Multithread RxPlayer should have access to the DASH feature",
);
const transportFn = features.transports[transport];
if (typeof transportFn !== "function") {
// Stop previous content and reset its state
// XXX TODO: send fatal error
throw new Error(`transport "${transport}" not supported`);
}
const representationFilter =
typeof transportOptions.representationFilter === "string"
? createRepresentationFilterFromFnString(transportOptions.representationFilter)
: undefined;
const dashPipelines = features.transports.dash({
: transportOptions.representationFilter;
const transportPipelines = transportFn({
...transportOptions,
representationFilter,
});
Expand All @@ -105,7 +107,7 @@ export default class ContentPreparer {
context.cmcd === undefined ? null : new CmcdDataBuilder(context.cmcd);
const manifestFetcher = new ManifestFetcher(
url === undefined ? undefined : [url],
dashPipelines,
transportPipelines,
{
cmcdDataBuilder,
...context.manifestRetryOptions,
Expand All @@ -130,7 +132,7 @@ export default class ContentPreparer {
);

const segmentQueueCreator = new SegmentQueueCreator(
dashPipelines,
transportPipelines,
cmcdDataBuilder,
context.segmentRetryOptions,
contentCanceller.signal,
Expand All @@ -140,6 +142,7 @@ export default class ContentPreparer {

const [mediaSource, segmentSinksStore, workerTextSender] =
createMediaSourceAndBuffersStore(
sendMessage,
contentId,
{
hasMseInWorker: this._hasMseInWorker,
Expand Down Expand Up @@ -255,7 +258,10 @@ export default class ContentPreparer {
this._currentContent?.manifestFetcher.scheduleManualRefresh(settings);
}

public reloadMediaSource(reloadInfo: INeedsMediaSourceReloadPayload): Promise<void> {
public reloadMediaSource(
sendMessage: (msg: IWorkerMessage, transferables?: Transferable[]) => void,
reloadInfo: INeedsMediaSourceReloadPayload,
): Promise<void> {
this._currentMediaSourceCanceller.cancel();
if (this._currentContent === null) {
return Promise.reject(new Error("CP: No content anymore"));
Expand All @@ -274,6 +280,7 @@ export default class ContentPreparer {

const [mediaSource, segmentSinksStore, workerTextSender] =
createMediaSourceAndBuffersStore(
sendMessage,
this._currentContent.contentId,
{
hasMseInWorker: this._hasMseInWorker,
Expand Down Expand Up @@ -380,6 +387,7 @@ export interface IPreparedContentData {
* @returns {Array.<Object>}
*/
function createMediaSourceAndBuffersStore(
sendMessage: (msg: IWorkerMessage, transferables?: Transferable[]) => void,
contentId: string,
capabilities: {
hasMseInWorker: boolean;
Expand Down
28 changes: 0 additions & 28 deletions src/core/main/worker/send_message.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/core/main/worker/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { formatError } from "../../../errors";
import type { ISentError } from "../../../multithread_types";

export function formatErrorForSender(error: unknown): ISentError {
const formattedError = formatError(error, {
defaultCode: "NONE",
defaultReason: "An unknown error stopped content playback.",
});

return formattedError.serialize();
}
Loading

0 comments on commit 3db53f1

Please sign in to comment.