From b9f870f6817311355cedfaf517079c5aeff322fd Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 26 Jan 2024 15:31:18 +0100 Subject: [PATCH] [Proposal] Solution 1: PlaybackObserver is moved in `src` Root issue ---------- While working on code refactoring for better taking into account the new potentially multithread nature of the RxPlayer code (with some files only running in a WebWorker environment and others only running in a main thread environment), we recently refactored our file hierarchy (#1365) to better reflect that new situation. The idea is to make RxPlayer developpers more aware of what code is intended to run where. In that work, we had a remaining issue concerning the `PlaybackObserver`. This is the part of the code that is monitoring and advertising playback conditions to the rest of the RxPlayer code. Most core RxPlayer modules rely on it, in both main thread and WebWorker environments. The root issue is that the `PlaybackObserver` is a class and thus cannot be easily transmitted in-between environments (as the main thread and WebWorker only exchanges between one another through `postMessage` calls, which follows some rules preventing from doing that). As such, and only on a multithreaded scenario, the RxPlayer is serializing in some way the constructed source PlaybackObserver in the main thread to reconstruct one (the `WorkerPlaybackObserver`) on the worker. Because a whole complex PlaybackObserver-compatible structure has to both be constructed in the main thread and in the worker (yet only the main thread can act as the "true" source one, because the media element can only be accessed in main thread), we were asking ourselves where should we put the common utils (required by both the main thread and worker) needed to construct one (like the `ObservationPosition` class and the `generateReadOnlyObserver` util). Solution 1 ---------- This is a first solution proposal, which moves the `PlaybackObserver` directory outside of the `main_thread` and `core` directories. Instead, its code is now directly in `src/playback_observer`. This solution takes inspiration from the `src/mse` directory, which also exports both: 1. files intended to be imported when MSE API are present in the current environment (when in main thread or when in a WebWorker with the MSE-in-worker feature) and 2. files intended to be imported in environments without MSE. For example the `src/mse/main_media_source_interface.ts` should __ONLY__ be imported in environments with MSE capabilities. If you do not, you should import `src/mse/worker_media_source_interface.ts`. Likewise, I here added a `src/playback_observer/media_element_playback_observer.ts` file exporting a `MediaElementPlaybackObserver` structure (new name of the `PlaybackObserver`) which can only be imported in environements where the media element is available (so, only on main thread) and a `src/playback_observer/worker_playback_observer.ts` file which should only be imported in other environments. Doing this allows to easily share common utils, in a `src/playback_observer/utils` directory, without involving the rest of the RxPlayer code. Result ------ I'm quite happy with the result, though I get it may seem weird to have a complex core frequently-running logic part directly in `src` (and not in `main_thread` or `core` as was the norm) - though it is also the case of directories like `mse` or `transports`. RxPlayer code very rarely imported files inside other directories (which it does here with paths like `../playback_observer/media_element_playback_observer`) as a way to push better modularization. Though here it may seem like a feature because its unusual-ness forces the developper to double check if this is the right file that is imported. --- .../adaptive_representation_selector.ts | 10 +- .../common/DecipherabilityFreezeDetector.ts | 2 +- .../content_time_boundaries_observer.ts | 2 +- ...create_content_time_boundaries_observer.ts | 2 +- src/core/main/index.ts | 1 - src/core/main/worker/worker_main.ts | 18 +- .../main/worker/worker_playback_observer.ts | 90 ---- src/core/segment_sinks/garbage_collector.ts | 2 +- .../get_representations_switch_strategy.ts | 7 +- src/core/stream/adaptation/types.ts | 2 +- .../orchestrator/stream_orchestrator.ts | 2 +- src/core/stream/period/period_stream.ts | 10 +- src/core/stream/period/types.ts | 10 +- .../utils/get_adaptation_switch_strategy.ts | 2 +- src/core/stream/representation/types.ts | 12 +- .../utils/append_segment_to_buffer.ts | 2 +- .../representation/utils/get_buffer_status.ts | 2 +- .../representation/utils/push_init_segment.ts | 2 +- .../utils/push_media_segment.ts | 2 +- src/core/types.ts | 4 - src/main_thread/api/index.ts | 10 - src/main_thread/api/public_api.ts | 9 +- src/main_thread/api/utils.ts | 11 +- .../init/directfile_content_initializer.ts | 6 +- .../init/media_source_content_initializer.ts | 10 +- .../init/multi_thread_content_initializer.ts | 26 +- src/main_thread/init/types.ts | 4 +- .../utils/create_core_playback_observer.ts | 48 +- .../init/utils/get_loaded_reference.ts | 8 +- .../init/utils/initial_seek_and_play.ts | 6 +- .../init/utils/rebuffering_controller.ts | 24 +- .../stream_events_emitter.ts | 7 +- src/main_thread/types.ts | 12 - src/multithread_types.ts | 14 +- src/playback_observer/index.ts | 13 + .../media_element_playback_observer.ts} | 442 +----------------- src/playback_observer/types.ts | 242 ++++++++++ .../utils/generate_read_only_observer.ts | 64 +++ .../utils/observation_position.ts | 105 +++++ .../worker_playback_observer.ts | 137 ++++++ 40 files changed, 725 insertions(+), 657 deletions(-) delete mode 100644 src/core/main/index.ts delete mode 100644 src/core/main/worker/worker_playback_observer.ts create mode 100644 src/playback_observer/index.ts rename src/{main_thread/api/playback_observer.ts => playback_observer/media_element_playback_observer.ts} (68%) create mode 100644 src/playback_observer/types.ts create mode 100644 src/playback_observer/utils/generate_read_only_observer.ts create mode 100644 src/playback_observer/utils/observation_position.ts create mode 100644 src/playback_observer/worker_playback_observer.ts diff --git a/src/core/adaptive/adaptive_representation_selector.ts b/src/core/adaptive/adaptive_representation_selector.ts index d1da676048..a33add8870 100644 --- a/src/core/adaptive/adaptive_representation_selector.ts +++ b/src/core/adaptive/adaptive_representation_selector.ts @@ -16,10 +16,6 @@ import config from "../../config"; import log from "../../log"; -import type { - IObservationPosition, - IReadOnlyPlaybackObserver, -} from "../../main_thread/types"; import type { IAdaptation, IManifest, @@ -27,6 +23,10 @@ import type { IRepresentation, ISegment, } from "../../manifest"; +import type { + ObservationPosition, + IReadOnlyPlaybackObserver, +} from "../../playback_observer"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; import noop from "../../utils/noop"; import type { IRange } from "../../utils/ranges"; @@ -604,7 +604,7 @@ export interface IRepresentationEstimatorPlaybackObservation { * Information on the current media position in seconds at the time of a * Playback Observation. */ - position : IObservationPosition; + position : ObservationPosition; /** * Last "playback rate" set by the user. This is the ideal "playback rate" at * which the media should play. diff --git a/src/core/main/common/DecipherabilityFreezeDetector.ts b/src/core/main/common/DecipherabilityFreezeDetector.ts index eb0734d3dd..8ac496e549 100644 --- a/src/core/main/common/DecipherabilityFreezeDetector.ts +++ b/src/core/main/common/DecipherabilityFreezeDetector.ts @@ -15,7 +15,7 @@ */ import log from "../../../log"; -import type { IFreezingStatus, IRebufferingStatus } from "../../../main_thread/types"; +import type { IFreezingStatus, IRebufferingStatus } from "../../../playback_observer"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp"; import type SegmentSinksStore from "../../segment_sinks"; diff --git a/src/core/main/common/content_time_boundaries_observer.ts b/src/core/main/common/content_time_boundaries_observer.ts index 460837c2d3..ab3353c71f 100644 --- a/src/core/main/common/content_time_boundaries_observer.ts +++ b/src/core/main/common/content_time_boundaries_observer.ts @@ -19,13 +19,13 @@ import type { IBufferType, } from "../../../core/types"; import { MediaError } from "../../../errors"; -import type { IReadOnlyPlaybackObserver } from "../../../main_thread/types"; import type { IManifest, IAdaptation, IRepresentationIndex, IPeriod, } from "../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../playback_observer"; import type { IPlayerError } from "../../../public_types"; import EventEmitter from "../../../utils/event_emitter"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; diff --git a/src/core/main/common/create_content_time_boundaries_observer.ts b/src/core/main/common/create_content_time_boundaries_observer.ts index 2f3142e69c..00a80e500a 100644 --- a/src/core/main/common/create_content_time_boundaries_observer.ts +++ b/src/core/main/common/create_content_time_boundaries_observer.ts @@ -3,12 +3,12 @@ import type { IStreamOrchestratorPlaybackObservation, } from "../../../core/types"; import log from "../../../log"; -import type { IReadOnlyPlaybackObserver } from "../../../main_thread/types"; import type { IManifest, IPeriod, } from "../../../manifest"; import type { IMediaSourceInterface } from "../../../mse"; +import type { IReadOnlyPlaybackObserver } from "../../../playback_observer"; import type { IPlayerError } from "../../../public_types"; import type { CancellationSignal } from "../../../utils/task_canceller"; import ContentTimeBoundariesObserver from "./content_time_boundaries_observer"; diff --git a/src/core/main/index.ts b/src/core/main/index.ts deleted file mode 100644 index dc1999b407..0000000000 --- a/src/core/main/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { ICorePlaybackObservation } from "./worker/worker_playback_observer"; diff --git a/src/core/main/worker/worker_main.ts b/src/core/main/worker/worker_main.ts index 35a71ebb83..2d16707af6 100644 --- a/src/core/main/worker/worker_main.ts +++ b/src/core/main/worker/worker_main.ts @@ -5,8 +5,6 @@ import { } from "../../../errors"; import features from "../../../features"; import log from "../../../log"; -// XXX TODO -import { ObservationPosition } from "../../../main_thread/api/playback_observer"; import Manifest, { Adaptation, Period, @@ -24,6 +22,11 @@ import { WorkerMessageType, } from "../../../multithread_types"; import DashWasmParser from "../../../parsers/manifest/dash/wasm-parser"; +import { ObservationPosition } from "../../../playback_observer"; +import type { + IWorkerPlaybackObservation, +} from "../../../playback_observer/worker_playback_observer"; +import WorkerPlaybackObserver from "../../../playback_observer/worker_playback_observer"; import type { IPlayerError, ITrackType } from "../../../public_types"; import createDashPipelines from "../../../transports/dash"; import arrayFind from "../../../utils/array_find"; @@ -60,10 +63,6 @@ import { import sendMessage, { formatErrorForSender, } from "./send_message"; -import type { - ICorePlaybackObservation, -} from "./worker_playback_observer"; -import WorkerPlaybackObserver from "./worker_playback_observer"; export default function initializeWorkerMain() { /** @@ -97,7 +96,7 @@ export default function initializeWorkerMain() { /** * When set, emit playback observation made on the main thread. */ - let playbackObservationRef : SharedReference | null = null; + let playbackObservationRef : SharedReference | null = null; onmessage = function (e: MessageEvent) { log.debug("Worker: received message", e.data.type); @@ -153,7 +152,7 @@ export default function initializeWorkerMain() { const currentCanceller = new TaskCanceller(); const currentContentObservationRef = new SharedReference< - ICorePlaybackObservation + IWorkerPlaybackObservation >(objectAssign(msg.value.initialObservation, { position: new ObservationPosition(...msg.value.initialObservation.position), })); @@ -467,7 +466,7 @@ interface IBufferingInitializationInformation { function loadOrReloadPreparedContent( val : IBufferingInitializationInformation, contentPreparer : ContentPreparer, - playbackObservationRef : IReadOnlySharedReference, + playbackObservationRef : IReadOnlySharedReference, parentCancelSignal : CancellationSignal ) { const currentLoadCanceller = new TaskCanceller(); @@ -536,6 +535,7 @@ function loadOrReloadPreparedContent( const playbackObserver = new WorkerPlaybackObserver(playbackObservationRef, contentId, + sendMessage, currentLoadCanceller.signal); const contentTimeBoundariesObserver = createContentTimeBoundariesObserver( diff --git a/src/core/main/worker/worker_playback_observer.ts b/src/core/main/worker/worker_playback_observer.ts deleted file mode 100644 index 6692acbae3..0000000000 --- a/src/core/main/worker/worker_playback_observer.ts +++ /dev/null @@ -1,90 +0,0 @@ -// XXX TODO -import { generateReadOnlyObserver } from "../../../main_thread/api/playback_observer"; -import type { - IFreezingStatus, - IReadOnlyPlaybackObserver, - IRebufferingStatus, -} from "../../../main_thread/types"; -import { WorkerMessageType } from "../../../multithread_types"; -import type { IReadOnlySharedReference } from "../../../utils/reference"; -import type { CancellationSignal } from "../../../utils/task_canceller"; -import type { IStreamOrchestratorPlaybackObservation } from "../../stream"; -import sendMessage from "./send_message"; - -export type ICorePlaybackObservation = IStreamOrchestratorPlaybackObservation & { - rebuffering: IRebufferingStatus | null; - freezing: IFreezingStatus | null; - bufferGap: number | undefined; -}; - -export default class WorkerPlaybackObserver implements IReadOnlyPlaybackObserver< - ICorePlaybackObservation -> { - private _src : IReadOnlySharedReference; - private _cancelSignal : CancellationSignal; - private _contentId : string; - - constructor( - src : IReadOnlySharedReference, - contentId : string, - cancellationSignal : CancellationSignal - ) { - this._src = src; - this._contentId = contentId; - this._cancelSignal = cancellationSignal; - } - - public getCurrentTime(): number | undefined { - return undefined; - } - - public getReadyState(): number | undefined { - return undefined; - } - - public getIsPaused(): boolean | undefined { - return undefined; - } - - public getReference() : IReadOnlySharedReference { - return this._src; - } - - public setPlaybackRate(playbackRate : number) : void { - sendMessage({ type: WorkerMessageType.UpdatePlaybackRate, - contentId: this._contentId, - value: playbackRate }); - } - - public getPlaybackRate() : number | undefined { - return undefined; - } - - public listen( - cb : ( - observation : ICorePlaybackObservation, - stopListening : () => void - ) => void, - options? : { includeLastObservation? : boolean | undefined; - clearSignal? : CancellationSignal | undefined; } - ) : void { - if (this._cancelSignal.isCancelled() || - options?.clearSignal?.isCancelled() === true) { - return ; - } - - this._src.onUpdate(cb, { - clearSignal: options?.clearSignal, - emitCurrentValue: options?.includeLastObservation, - }); - } - - public deriveReadOnlyObserver( - transform : ( - observationRef : IReadOnlySharedReference, - cancellationSignal : CancellationSignal - ) => IReadOnlySharedReference - ) : IReadOnlyPlaybackObserver { - return generateReadOnlyObserver(this, transform, this._cancelSignal); - } -} diff --git a/src/core/segment_sinks/garbage_collector.ts b/src/core/segment_sinks/garbage_collector.ts index ed080f85e6..a779a69a59 100644 --- a/src/core/segment_sinks/garbage_collector.ts +++ b/src/core/segment_sinks/garbage_collector.ts @@ -15,7 +15,7 @@ */ import log from "../../log"; -import type { IReadOnlyPlaybackObserver } from "../../main_thread/types"; +import type { IReadOnlyPlaybackObserver } from "../../playback_observer"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; import type { IRange } from "../../utils/ranges"; import { getInnerAndOuterRanges } from "../../utils/ranges"; diff --git a/src/core/stream/adaptation/get_representations_switch_strategy.ts b/src/core/stream/adaptation/get_representations_switch_strategy.ts index 7e6fff9fa7..3009747401 100644 --- a/src/core/stream/adaptation/get_representations_switch_strategy.ts +++ b/src/core/stream/adaptation/get_representations_switch_strategy.ts @@ -15,11 +15,8 @@ */ import config from "../../../config"; -import type { IReadOnlyPlaybackObserver } from "../../../main_thread/types"; -import type { - IAdaptation, - IPeriod, -} from "../../../manifest"; +import type { IAdaptation, IPeriod } from "../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../playback_observer"; import arrayIncludes from "../../../utils/array_includes"; import type { IRange } from "../../../utils/ranges"; diff --git a/src/core/stream/adaptation/types.ts b/src/core/stream/adaptation/types.ts index 5eb869569c..1a9bcc5c24 100644 --- a/src/core/stream/adaptation/types.ts +++ b/src/core/stream/adaptation/types.ts @@ -1,10 +1,10 @@ -import type { IReadOnlyPlaybackObserver } from "../../../main_thread/types"; import type { IManifest, IAdaptation, IPeriod, IRepresentation, } from "../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../playback_observer"; import type { IAudioTrackSwitchingMode, IVideoTrackSwitchingMode, diff --git a/src/core/stream/orchestrator/stream_orchestrator.ts b/src/core/stream/orchestrator/stream_orchestrator.ts index b241cb38f3..93b95daa20 100644 --- a/src/core/stream/orchestrator/stream_orchestrator.ts +++ b/src/core/stream/orchestrator/stream_orchestrator.ts @@ -17,12 +17,12 @@ import config from "../../../config"; import { MediaError } from "../../../errors"; import log from "../../../log"; -import type { IReadOnlyPlaybackObserver } from "../../../main_thread/types"; import type { IManifest, IDecipherabilityUpdateElement, IPeriod, } from "../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../playback_observer"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; import queueMicrotask from "../../../utils/queue_microtask"; import type { diff --git a/src/core/stream/period/period_stream.ts b/src/core/stream/period/period_stream.ts index fc5da05bc5..4dc5809e78 100644 --- a/src/core/stream/period/period_stream.ts +++ b/src/core/stream/period/period_stream.ts @@ -20,13 +20,9 @@ import { MediaError, } from "../../../errors"; import log from "../../../log"; -import type { IReadOnlyPlaybackObserver } from "../../../main_thread/types"; -import type { - IAdaptation, - IPeriod } from "../../../manifest"; -import { - toTaggedTrack, -} from "../../../manifest"; +import type { IAdaptation, IPeriod } from "../../../manifest"; +import { toTaggedTrack } from "../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../playback_observer"; import type { ITrackType } from "../../../public_types"; import arrayFind from "../../../utils/array_find"; import objectAssign from "../../../utils/object_assign"; diff --git a/src/core/stream/period/types.ts b/src/core/stream/period/types.ts index b89130d085..cc43ae3c65 100644 --- a/src/core/stream/period/types.ts +++ b/src/core/stream/period/types.ts @@ -1,12 +1,12 @@ -import type { - IObservationPosition, - IReadOnlyPlaybackObserver, -} from "../../../main_thread/types"; import type { IManifest, IAdaptation, IPeriod, } from "../../../manifest"; +import type { + ObservationPosition, + IReadOnlyPlaybackObserver, +} from "../../../playback_observer"; import type { ITrackType } from "../../../public_types"; import type { IRange } from "../../../utils/ranges"; import type { @@ -94,7 +94,7 @@ export interface IPeriodStreamPlaybackObservation { * Information on the current media position in seconds at the time of the * Observation. */ - position : IObservationPosition; + position : ObservationPosition; /** `duration` property of the HTMLMediaElement. */ duration : number; /** `readyState` property of the HTMLMediaElement. */ diff --git a/src/core/stream/period/utils/get_adaptation_switch_strategy.ts b/src/core/stream/period/utils/get_adaptation_switch_strategy.ts index 35e3e9783c..1cf350609e 100644 --- a/src/core/stream/period/utils/get_adaptation_switch_strategy.ts +++ b/src/core/stream/period/utils/get_adaptation_switch_strategy.ts @@ -15,8 +15,8 @@ */ import config from "../../../../config"; -import type { IReadOnlyPlaybackObserver } from "../../../../main_thread/types"; import type { IAdaptation, IPeriod } from "../../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../../playback_observer"; import areCodecsCompatible from "../../../../utils/are_codecs_compatible"; import type { IRange } from "../../../../utils/ranges"; import { diff --git a/src/core/stream/representation/types.ts b/src/core/stream/representation/types.ts index ee47a49836..c55cccee97 100644 --- a/src/core/stream/representation/types.ts +++ b/src/core/stream/representation/types.ts @@ -1,8 +1,4 @@ -import type { - IContentProtection, - IObservationPosition, - IReadOnlyPlaybackObserver, -} from "../../../main_thread/types"; +import type { IContentProtection } from "../../../main_thread/types"; import type { IManifest, IAdaptation, @@ -11,6 +7,10 @@ import type { IRepresentation, } from "../../../manifest"; import type { IEMSG } from "../../../parsers/containers/isobmff"; +import type { + ObservationPosition, + IReadOnlyPlaybackObserver, +} from "../../../playback_observer"; import type { IAudioRepresentationsSwitchingMode, IPlayerError, @@ -185,7 +185,7 @@ export interface IRepresentationStreamPlaybackObservation { * Information on the current media position in seconds at the time of a * Playback Observation. */ - position : IObservationPosition; + position : ObservationPosition; /** * Information on whether the media element was paused at the time of the * Observation. diff --git a/src/core/stream/representation/utils/append_segment_to_buffer.ts b/src/core/stream/representation/utils/append_segment_to_buffer.ts index 6a75b772de..f7bd52f3b5 100644 --- a/src/core/stream/representation/utils/append_segment_to_buffer.ts +++ b/src/core/stream/representation/utils/append_segment_to_buffer.ts @@ -23,8 +23,8 @@ import { SourceBufferError, } from "../../../../errors"; import log from "../../../../log"; -import type { IReadOnlyPlaybackObserver } from "../../../../main_thread/types"; import { toTaggedTrack } from "../../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../../playback_observer"; import type { IRange } from "../../../../utils/ranges"; import type { IReadOnlySharedReference } from "../../../../utils/reference"; import sleep from "../../../../utils/sleep"; diff --git a/src/core/stream/representation/utils/get_buffer_status.ts b/src/core/stream/representation/utils/get_buffer_status.ts index 7543447f75..68c7eb84ba 100644 --- a/src/core/stream/representation/utils/get_buffer_status.ts +++ b/src/core/stream/representation/utils/get_buffer_status.ts @@ -15,13 +15,13 @@ */ import config from "../../../../config"; -import type { IReadOnlyPlaybackObserver } from "../../../../main_thread/types"; import type { IManifest, IAdaptation, IPeriod, IRepresentation, } from "../../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../../playback_observer"; import isNullOrUndefined from "../../../../utils/is_null_or_undefined"; import type { IBufferedChunk, diff --git a/src/core/stream/representation/utils/push_init_segment.ts b/src/core/stream/representation/utils/push_init_segment.ts index 41c4ac8496..0d80b6b854 100644 --- a/src/core/stream/representation/utils/push_init_segment.ts +++ b/src/core/stream/representation/utils/push_init_segment.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import type { IReadOnlyPlaybackObserver } from "../../../../main_thread/types"; import type { IManifest, IAdaptation, @@ -22,6 +21,7 @@ import type { IPeriod, IRepresentation, } from "../../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../../playback_observer"; import objectAssign from "../../../../utils/object_assign"; import type { IReadOnlySharedReference } from "../../../../utils/reference"; import type { CancellationSignal } from "../../../../utils/task_canceller"; diff --git a/src/core/stream/representation/utils/push_media_segment.ts b/src/core/stream/representation/utils/push_media_segment.ts index eefdb0a2a1..33d734f143 100644 --- a/src/core/stream/representation/utils/push_media_segment.ts +++ b/src/core/stream/representation/utils/push_media_segment.ts @@ -15,7 +15,6 @@ */ import config from "../../../../config"; -import type { IReadOnlyPlaybackObserver } from "../../../../main_thread/types"; import type { IManifest, IAdaptation, @@ -23,6 +22,7 @@ import type { IPeriod, IRepresentation, } from "../../../../manifest"; +import type { IReadOnlyPlaybackObserver } from "../../../../playback_observer"; import type { ISegmentParserParsedMediaChunk } from "../../../../transports"; import objectAssign from "../../../../utils/object_assign"; import type { IReadOnlySharedReference } from "../../../../utils/reference"; diff --git a/src/core/types.ts b/src/core/types.ts index 684b1ac418..7f9e1ba9e1 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -7,7 +7,6 @@ import type { IManifestFetcherSettings, ISegmentFetcherCreatorBackoffOptions, } from "./fetchers"; -import type { ICorePlaybackObservation } from "./main"; import type { IBufferedChunk, IBufferType, @@ -35,9 +34,6 @@ export type { IABRThrottlers, IResolutionInfo, - // Main - ICorePlaybackObservation, - // Fetchers Metadata IManifestFetcherSettings, ISegmentFetcherCreatorBackoffOptions, diff --git a/src/main_thread/api/index.ts b/src/main_thread/api/index.ts index f7c8f36a8e..9a99af32aa 100644 --- a/src/main_thread/api/index.ts +++ b/src/main_thread/api/index.ts @@ -14,15 +14,5 @@ * limitations under the License. */ -import PlaybackObserver from "./playback_observer"; import Player from "./public_api"; -export { PlaybackObserver }; -export { - IObservationPosition, - IPlaybackObservation, - IPlaybackObserverEventType, - IReadOnlyPlaybackObserver, - IFreezingStatus, - IRebufferingStatus, -} from "./playback_observer"; export default Player; diff --git a/src/main_thread/api/public_api.ts b/src/main_thread/api/public_api.ts index 062e80800c..fb636d3cb0 100644 --- a/src/main_thread/api/public_api.ts +++ b/src/main_thread/api/public_api.ts @@ -67,6 +67,9 @@ import { createRepresentationFilterFromFnString, } from "../../manifest"; import { MainThreadMessageType } from "../../multithread_types"; +import type { IPlaybackObservation } from "../../playback_observer"; +/* eslint-disable-next-line max-len */ +import MediaElementPlaybackObserver from "../../playback_observer/media_element_playback_observer"; import type { IAudioRepresentation, IAudioRepresentationsSwitchingMode, @@ -146,10 +149,6 @@ import { parseConstructorOptions, parseLoadVideoOptions, } from "./option_utils"; -import type { - IPlaybackObservation, -} from "./playback_observer"; -import PlaybackObserver from "./playback_observer"; import { constructPlayerStateReference, emitPlayPauseEvents, @@ -951,7 +950,7 @@ class Player extends EventEmitter { this.stop(); /** Global "playback observer" which will emit playback conditions */ - const playbackObserver = new PlaybackObserver(videoElement, { + const playbackObserver = new MediaElementPlaybackObserver(videoElement, { withMediaSource: !isDirectFile, lowLatencyMode, }); diff --git a/src/main_thread/api/utils.ts b/src/main_thread/api/utils.ts index 7e178ba7ad..db6a01b2df 100644 --- a/src/main_thread/api/utils.ts +++ b/src/main_thread/api/utils.ts @@ -15,6 +15,11 @@ */ import config from "../../config"; +import type { + IPlaybackObservation, + IReadOnlyPlaybackObserver, +} from "../../playback_observer"; +import { SeekingState } from "../../playback_observer"; import type { IPlayerState } from "../../public_types"; import arrayIncludes from "../../utils/array_includes"; import isNullOrUndefined from "../../utils/is_null_or_undefined"; @@ -27,12 +32,6 @@ import type { ContentInitializer, IStallingSituation, } from "../init"; -import type { - IPlaybackObservation, - IReadOnlyPlaybackObserver } from "./playback_observer"; -import { - SeekingState, -} from "./playback_observer"; /** * @param {HTMLMediaElement} mediaElement diff --git a/src/main_thread/init/directfile_content_initializer.ts b/src/main_thread/init/directfile_content_initializer.ts index af1b8cb8ee..4dcffa6b6d 100644 --- a/src/main_thread/init/directfile_content_initializer.ts +++ b/src/main_thread/init/directfile_content_initializer.ts @@ -22,6 +22,7 @@ import { clearElementSrc } from "../../compat"; import type { MediaError } from "../../errors"; import log from "../../log"; +import type { IMediaElementPlaybackObserver } from "../../playback_observer"; import type { IKeySystemOption, IPlayerError, @@ -34,7 +35,6 @@ import type { } from "../../utils/reference"; import SharedReference from "../../utils/reference"; import TaskCanceller from "../../utils/task_canceller"; -import type { PlaybackObserver } from "../api"; import { ContentInitializer } from "./types"; import type { IInitialTimeOptions } from "./utils/get_initial_time"; import getLoadedReference from "./utils/get_loaded_reference"; @@ -91,7 +91,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { */ public start( mediaElement : HTMLMediaElement, - playbackObserver : PlaybackObserver + playbackObserver : IMediaElementPlaybackObserver ) : void { const cancelSignal = this._initCanceller.signal; const { keySystems, speed, url } = this._settings; @@ -198,7 +198,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { */ private _seekAndPlay( mediaElement : HTMLMediaElement, - playbackObserver : PlaybackObserver + playbackObserver : IMediaElementPlaybackObserver ) : void { const cancelSignal = this._initCanceller.signal; const { autoPlay, startAt } = this._settings; diff --git a/src/main_thread/init/media_source_content_initializer.ts b/src/main_thread/init/media_source_content_initializer.ts index 06d937f656..6037d33ee1 100644 --- a/src/main_thread/init/media_source_content_initializer.ts +++ b/src/main_thread/init/media_source_content_initializer.ts @@ -43,6 +43,7 @@ import type { IPeriodMetadata, } from "../../manifest"; import type MainMediaSourceInterface from "../../mse/main_media_source_interface"; +import type { IMediaElementPlaybackObserver } from "../../playback_observer"; import type { IKeySystemOption, IPlayerError, @@ -62,7 +63,6 @@ import type { ISyncOrAsyncValue } from "../../utils/sync_or_async"; import SyncOrAsync from "../../utils/sync_or_async"; import type { CancellationSignal } from "../../utils/task_canceller"; import TaskCanceller from "../../utils/task_canceller"; -import type { PlaybackObserver } from "../api"; import type { IContentProtection, IProcessedProtectionData } from "../decrypt"; import { getKeySystemConfiguration } from "../decrypt"; import type { ITextDisplayer } from "../text_displayer"; @@ -163,7 +163,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { */ public start( mediaElement : HTMLMediaElement, - playbackObserver : PlaybackObserver + playbackObserver : IMediaElementPlaybackObserver ): void { this.prepare(); // Load Manifest if not already done @@ -298,7 +298,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { private async _onInitialMediaSourceReady( mediaElement : HTMLMediaElement, initialMediaSource : MainMediaSourceInterface, - playbackObserver : PlaybackObserver, + playbackObserver : IMediaElementPlaybackObserver, drmSystemId : string | undefined, protectionRef : SharedReference, initialMediaSourceCanceller : TaskCanceller @@ -816,7 +816,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { * @returns {Object} */ private _createRebufferingController( - playbackObserver : PlaybackObserver, + playbackObserver : IMediaElementPlaybackObserver, manifest : IManifest, speed : IReadOnlySharedReference, cancelSignal : CancellationSignal @@ -906,7 +906,7 @@ interface IBufferingMediaSettings { /** Media Element on which the content will be played. */ mediaElement : HTMLMediaElement; /** Emit playback conditions regularly. */ - playbackObserver : PlaybackObserver; + playbackObserver : IMediaElementPlaybackObserver; /** Estimate the right Representation. */ representationEstimator : IRepresentationEstimator; /** Module to facilitate segment fetching. */ diff --git a/src/main_thread/init/multi_thread_content_initializer.ts b/src/main_thread/init/multi_thread_content_initializer.ts index bb13536e65..43441dd2d1 100644 --- a/src/main_thread/init/multi_thread_content_initializer.ts +++ b/src/main_thread/init/multi_thread_content_initializer.ts @@ -7,7 +7,6 @@ import type { IAdaptationChoice, IManifestFetcherSettings, IResolutionInfo, - ICorePlaybackObservation, } from "../../core/types"; import { EncryptedMediaError, @@ -18,9 +17,7 @@ import { } from "../../errors"; import features from "../../features"; import log from "../../log"; -import type { - ICodecSupportList, - IManifestMetadata } from "../../manifest"; +import type { ICodecSupportList, IManifestMetadata } from "../../manifest"; import { replicateUpdatesOnManifestMetadata, updateDecipherabilityFromKeyIds, @@ -30,11 +27,19 @@ import MainMediaSourceInterface from "../../mse/main_media_source_interface"; import type { ICreateMediaSourceWorkerMessage, ISentError, - IWorkerMessage } from "../../multithread_types"; + IWorkerMessage, +} from "../../multithread_types"; import { MainThreadMessageType, WorkerMessageType, } from "../../multithread_types"; +import type { + IReadOnlyPlaybackObserver, + IMediaElementPlaybackObserver, +} from "../../playback_observer"; +import type { + IWorkerPlaybackObservation, +} from "../../playback_observer/worker_playback_observer"; import type { IKeySystemOption, IPlayerError, @@ -58,7 +63,6 @@ import type { import TaskCanceller, { CancellationError, } from "../../utils/task_canceller"; -import type { IReadOnlyPlaybackObserver, PlaybackObserver } from "../api"; import type { IContentProtection } from "../decrypt"; import { @@ -272,7 +276,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { */ public start( mediaElement : HTMLMediaElement, - playbackObserver : PlaybackObserver + playbackObserver : IMediaElementPlaybackObserver ): void { this.prepare(); // Load Manifest if not already done if (this._initCanceller.isUsed()) { @@ -1204,7 +1208,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { private _reload( mediaElement : HTMLMediaElement, textDisplayer : ITextDisplayer | null, - playbackObserver : PlaybackObserver, + playbackObserver : IMediaElementPlaybackObserver, mediaSourceStatus: SharedReference, position : number, autoPlay : boolean @@ -1265,9 +1269,9 @@ export default class MultiThreadContentInitializer extends ContentInitializer { autoPlay : boolean; mediaElement : HTMLMediaElement; textDisplayer : ITextDisplayer | null; - playbackObserver : PlaybackObserver; }, + playbackObserver : IMediaElementPlaybackObserver; }, cancelSignal : CancellationSignal - ): IReadOnlyPlaybackObserver | null { + ): IReadOnlyPlaybackObserver | null { if (cancelSignal.isCancelled()) { return null; } @@ -1402,7 +1406,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer { private _startPlaybackIfReady(parameters: { mediaElement: HTMLMediaElement; textDisplayer: ITextDisplayer | null; - playbackObserver: PlaybackObserver; + playbackObserver: IMediaElementPlaybackObserver; drmInitializationStatus: IReadOnlySharedReference; mediaSourceStatus: IReadOnlySharedReference; }): boolean { diff --git a/src/main_thread/init/types.ts b/src/main_thread/init/types.ts index 2938f0b1a0..db55e927cd 100644 --- a/src/main_thread/init/types.ts +++ b/src/main_thread/init/types.ts @@ -28,10 +28,10 @@ import type { IRepresentationMetadata, IDecipherabilityStatusChangedElement, } from "../../manifest"; +import type { IMediaElementPlaybackObserver } from "../../playback_observer"; import type { IPlayerError } from "../../public_types"; import EventEmitter from "../../utils/event_emitter"; import type SharedReference from "../../utils/reference"; -import type { PlaybackObserver } from "../api"; import type { IPublicNonFiniteStreamEvent, IPublicStreamEvent, @@ -84,7 +84,7 @@ export abstract class ContentInitializer extends EventEmitter; + rebuffering: IRebufferingStatus | null; + freezing: IFreezingStatus | null; + bufferGap: number | undefined; +} + /** * Create PlaybackObserver for the core part of the code. * @param {Object} srcPlaybackObserver - Base `PlaybackObserver` from which we @@ -65,7 +99,7 @@ export interface ICorePlaybackObserverArguments { * @returns {Object} */ export default function createCorePlaybackObserver( - srcPlaybackObserver : PlaybackObserver, + srcPlaybackObserver : IMediaElementPlaybackObserver, { autoPlay, initialPlayPerformed, manifest, diff --git a/src/main_thread/init/utils/get_loaded_reference.ts b/src/main_thread/init/utils/get_loaded_reference.ts index e360c77878..922046c705 100644 --- a/src/main_thread/init/utils/get_loaded_reference.ts +++ b/src/main_thread/init/utils/get_loaded_reference.ts @@ -20,16 +20,16 @@ import { shouldWaitForDataBeforeLoaded, shouldWaitForHaveEnoughData, } from "../../../compat"; +import type { + IPlaybackObservation, + IReadOnlyPlaybackObserver, +} from "../../../playback_observer"; import type { IReadOnlySharedReference, } from "../../../utils/reference"; import SharedReference from "../../../utils/reference"; import type { CancellationSignal } from "../../../utils/task_canceller"; import TaskCanceller from "../../../utils/task_canceller"; -import type { - IPlaybackObservation, - IReadOnlyPlaybackObserver, -} from "../../api"; /** * Returns an `IReadOnlySharedReference` that switches to `true` once the diff --git a/src/main_thread/init/utils/initial_seek_and_play.ts b/src/main_thread/init/utils/initial_seek_and_play.ts index 6a46f59988..a3a4c8bc27 100644 --- a/src/main_thread/init/utils/initial_seek_and_play.ts +++ b/src/main_thread/init/utils/initial_seek_and_play.ts @@ -18,6 +18,8 @@ import { shouldValidateMetadata } from "../../../compat"; import { isSafariMobile } from "../../../compat/browser_detection"; import { MediaError } from "../../../errors"; import log from "../../../log"; +import type { IMediaElementPlaybackObserver } from "../../../playback_observer"; +import { SeekingState } from "../../../playback_observer"; import type { IPlayerError } from "../../../public_types"; import type { IReadOnlySharedReference } from "../../../utils/reference"; import SharedReference from "../../../utils/reference"; @@ -25,8 +27,6 @@ import type { CancellationError, CancellationSignal, } from "../../../utils/task_canceller"; -import type { PlaybackObserver } from "../../api"; -import { SeekingState } from "../../api/playback_observer"; /** Event emitted when trying to perform the initial `play`. */ export type IInitialPlayEvent = @@ -66,7 +66,7 @@ export default function performInitialSeekAndPlay( mustAutoPlay, isDirectfile, onWarning }: { mediaElement : HTMLMediaElement; - playbackObserver : PlaybackObserver; + playbackObserver : IMediaElementPlaybackObserver; startTime : number|(() => number); mustAutoPlay : boolean; isDirectfile: boolean; diff --git a/src/main_thread/init/utils/rebuffering_controller.ts b/src/main_thread/init/utils/rebuffering_controller.ts index f60aebe4b7..c5c9133531 100644 --- a/src/main_thread/init/utils/rebuffering_controller.ts +++ b/src/main_thread/init/utils/rebuffering_controller.ts @@ -18,23 +18,19 @@ import config from "../../../config"; import type { IBufferType } from "../../../core/types"; import { MediaError } from "../../../errors"; import log from "../../../log"; +import type { IManifestMetadata, IPeriodMetadata } from "../../../manifest"; +import { getPeriodAfter } from "../../../manifest"; +import { SeekingState } from "../../../playback_observer"; import type { - IManifestMetadata, - IPeriodMetadata } from "../../../manifest"; -import { - getPeriodAfter, -} from "../../../manifest"; + IMediaElementPlaybackObserver, + IPlaybackObservation, +} from "../../../playback_observer"; import type { IPlayerError } from "../../../public_types"; import EventEmitter from "../../../utils/event_emitter"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp"; import { getNextBufferedTimeRangeGap } from "../../../utils/ranges"; import type { IReadOnlySharedReference } from "../../../utils/reference"; import TaskCanceller from "../../../utils/task_canceller"; -import type { - IPlaybackObservation, - PlaybackObserver, -} from "../../api"; -import { SeekingState } from "../../api/playback_observer"; import type { IStallingSituation } from "../types"; @@ -56,7 +52,7 @@ export default class RebufferingController extends EventEmitter { /** Emit the current playback conditions */ - private _playbackObserver : PlaybackObserver; + private _playbackObserver : IMediaElementPlaybackObserver; private _manifest : IManifestMetadata | null; private _speed : IReadOnlySharedReference; private _isStarted : boolean; @@ -75,7 +71,7 @@ export default class RebufferingController * @param {Object} speed - The last speed set by the user */ constructor( - playbackObserver : PlaybackObserver, + playbackObserver : IMediaElementPlaybackObserver, manifest : IManifestMetadata | null, speed : IReadOnlySharedReference ) { @@ -466,7 +462,7 @@ function generateDiscontinuityError( * @class PlaybackRateUpdater */ class PlaybackRateUpdater { - private _playbackObserver : PlaybackObserver; + private _playbackObserver : IMediaElementPlaybackObserver; private _speed : IReadOnlySharedReference; private _speedUpdateCanceller : TaskCanceller; private _isRebuffering : boolean; @@ -478,7 +474,7 @@ class PlaybackRateUpdater { * @param {Object} speed */ constructor( - playbackObserver : PlaybackObserver, + playbackObserver : IMediaElementPlaybackObserver, speed : IReadOnlySharedReference ) { this._speedUpdateCanceller = new TaskCanceller(); diff --git a/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts b/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts index 39be0e65bb..992eca9dc1 100644 --- a/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts +++ b/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts @@ -16,12 +16,15 @@ import config from "../../../../config"; import type { IManifestMetadata } from "../../../../manifest"; +import { SeekingState } from "../../../../playback_observer"; +import type { + IPlaybackObservation, + IReadOnlyPlaybackObserver, +} from "../../../../playback_observer"; import EventEmitter from "../../../../utils/event_emitter"; import SharedReference from "../../../../utils/reference"; import type { CancellationSignal } from "../../../../utils/task_canceller"; import TaskCanceller from "../../../../utils/task_canceller"; -import type { IPlaybackObservation, IReadOnlyPlaybackObserver } from "../../../api"; -import { SeekingState } from "../../../api/playback_observer"; import refreshScheduledEventsList from "./refresh_scheduled_events_list"; import type { INonFiniteStreamEventPayload, diff --git a/src/main_thread/types.ts b/src/main_thread/types.ts index a73593b038..81876b0432 100644 --- a/src/main_thread/types.ts +++ b/src/main_thread/types.ts @@ -1,10 +1,4 @@ import type RxPlayer from "./api"; -import type { - IFreezingStatus, - IRebufferingStatus, - IObservationPosition, - IReadOnlyPlaybackObserver, -} from "./api"; import type { IContentProtection, IProcessedProtectionData, @@ -17,12 +11,6 @@ import type { export type IRxPlayer = RxPlayer; export type { - // Playback Observation Metadata - IObservationPosition, - IReadOnlyPlaybackObserver, - IFreezingStatus, - IRebufferingStatus, - // Decrypt Metadata IContentProtection, IProcessedProtectionData, diff --git a/src/multithread_types.ts b/src/multithread_types.ts index 1c01e251fa..a074bf1b45 100644 --- a/src/multithread_types.ts +++ b/src/multithread_types.ts @@ -20,17 +20,13 @@ import type { ISerializedOtherError, } from "./errors"; import type { ISerializedSourceBufferError } from "./errors/source_buffer_error"; -import type { - IContentProtection, - IFreezingStatus, - IRebufferingStatus, - ITextDisplayerData, -} from "./main_thread/types"; +import type { IContentProtection, ITextDisplayerData } from "./main_thread/types"; import type { IManifestMetadata, IPeriodsUpdateResult } from "./manifest"; import type { ISourceBufferInterfaceAppendBufferParameters, SourceBufferType, } from "./mse"; +import type { IFreezingStatus, IRebufferingStatus } from "./playback_observer"; import type { ITrackType } from "./public_types"; import type { ITransportOptions } from "./transports"; import type { ILoggerLevel } from "./utils/logger"; @@ -198,7 +194,7 @@ export interface IStartPreparedContentMessageValue { /** The start time at which we should play, in seconds. */ initialTime : number; /** The current playback observation. */ - initialObservation : IWorkerPlaybackObservation; + initialObservation : ISerializedPlaybackObservation; /** * Hex-encoded string identifying the key system used. * May be cross-referenced with the content's metadata when performing @@ -254,7 +250,7 @@ export interface IPlaybackObservationMessage { */ contentId: string; /** The media-related metadata that has just been observed now. */ - value : IWorkerPlaybackObservation; + value : ISerializedPlaybackObservation; } /** @@ -357,7 +353,7 @@ export interface IRepresentationUpdateMessage { } /** Media-related metadata. */ -export interface IWorkerPlaybackObservation { +export interface ISerializedPlaybackObservation { /** * Information on whether the media element was paused at the time of the * Observation. diff --git a/src/playback_observer/index.ts b/src/playback_observer/index.ts new file mode 100644 index 0000000000..31937a0119 --- /dev/null +++ b/src/playback_observer/index.ts @@ -0,0 +1,13 @@ +import type MediaElementPlaybackObserver from "./media_element_playback_observer"; +import ObservationPosition from "./utils/observation_position"; + +export { SeekingState } from "./types"; +export type { + IFreezingStatus, + IRebufferingStatus, + IPlaybackObservation, + IReadOnlyPlaybackObserver, +} from "./types"; +export type IMediaElementPlaybackObserver = MediaElementPlaybackObserver; +export { ObservationPosition }; + diff --git a/src/main_thread/api/playback_observer.ts b/src/playback_observer/media_element_playback_observer.ts similarity index 68% rename from src/main_thread/api/playback_observer.ts rename to src/playback_observer/media_element_playback_observer.ts index c538383c9e..c0623992cb 100644 --- a/src/main_thread/api/playback_observer.ts +++ b/src/playback_observer/media_element_playback_observer.ts @@ -14,21 +14,28 @@ * limitations under the License. */ -import isSeekingApproximate from "../../compat/is_seeking_approximate"; -import config from "../../config"; -import log from "../../log"; -import getMonotonicTimeStamp from "../../utils/monotonic_timestamp"; -import noop from "../../utils/noop"; -import objectAssign from "../../utils/object_assign"; -import { getBufferedTimeRange } from "../../utils/ranges"; +import isSeekingApproximate from "../compat/is_seeking_approximate"; +import config from "../config"; +import log from "../log"; +import getMonotonicTimeStamp from "../utils/monotonic_timestamp"; +import noop from "../utils/noop"; +import objectAssign from "../utils/object_assign"; +import { getBufferedTimeRange } from "../utils/ranges"; +import type { IReadOnlySharedReference } from "../utils/reference"; +import SharedReference from "../utils/reference"; +import type { CancellationSignal } from "../utils/task_canceller"; +import TaskCanceller from "../utils/task_canceller"; import type { - IReadOnlySharedReference, -} from "../../utils/reference"; -import SharedReference from "../../utils/reference"; -import type { - CancellationSignal, -} from "../../utils/task_canceller"; -import TaskCanceller from "../../utils/task_canceller"; + IMediaInfos, + IPlaybackObservation, + IPlaybackObserverEventType, + IReadOnlyPlaybackObserver, + IRebufferingStatus, + IFreezingStatus, +} from "./types"; +import { SeekingState } from "./types"; +import generateReadOnlyObserver from "./utils/generate_read_only_observer"; +import ObservationPosition from "./utils/observation_position"; /** * HTMLMediaElement Events for which playback observations are calculated and @@ -503,352 +510,6 @@ export default class PlaybackObserver { } } -/** "Event" that triggered the playback observation. */ -export type IPlaybackObserverEventType = - /** First playback observation automatically emitted. */ - "init" | - /** Observation manually forced by the PlaybackObserver. */ - "manual" | - /** Regularly emitted playback observation when no event happened in a long time. */ - "timeupdate" | - /** On the HTML5 event with the same name */ - "canplay" | - /** On the HTML5 event with the same name */ - "ended" | - /** On the HTML5 event with the same name */ - "canplaythrough" | // HTML5 Event - /** On the HTML5 event with the same name */ - "play" | - /** On the HTML5 event with the same name */ - "pause" | - /** On the HTML5 event with the same name */ - "seeking" | - /** On the HTML5 event with the same name */ - "seeked" | - /** On the HTML5 event with the same name */ - "stalled" | - /** On the HTML5 event with the same name */ - "loadedmetadata" | - /** On the HTML5 event with the same name */ - "ratechange" | - /** An internal seek happens */ - "internal-seeking"; - -/** Information recuperated on the media element on each playback observation. */ -interface IMediaInfos { - /** Value of `buffered` (buffered ranges) for the media element. */ - buffered : TimeRanges; - /** - * `currentTime` (position) set on the media element at the time of the - * PlaybackObserver's measure. - */ - position : number; - /** Current `duration` set on the media element. */ - duration : number; - /** Current `ended` set on the media element. */ - ended: boolean; - /** Current `paused` set on the media element. */ - paused : boolean; - /** Current `playbackRate` set on the media element. */ - playbackRate : number; - /** Current `readyState` value on the media element. */ - readyState : number; - /** Current `seeking` value on the mediaElement. */ - seeking : boolean; -} - -/** Categorize a pending seek operation. */ -export const enum SeekingState { - /** We're not currently seeking. */ - None, - /** - * We're currently seeking due to an internal logic of the RxPlayer (e.g. - * discontinuity skipping). - */ - Internal, - /** We're currently seeking due to a regular seek wanted by the application. */ - External, -} - -/** - * Describes when the player is "rebuffering" and what event started that - * status. - * "Rebuffering" is a status where the player has not enough buffer ahead to - * play reliably. - * The RxPlayer should pause playback when a playback observation indicates the - * rebuffering status. - */ -export interface IRebufferingStatus { - /** What started the player to rebuffer. */ - reason : "seeking" | // Building buffer after seeking - "not-ready" | // Building buffer after low readyState - "buffering"; // Other cases - /** - * Monotonically-raising timestamp at the time the rebuffering happened on the - * main thread. - */ - timestamp : number; - /** - * Position, in seconds, at which data is awaited. - * If `null` the player is rebuffering but not because it is awaiting future data. - * If `undefined`, that position is unknown. - */ - position : number | null | undefined; -} - -/** - * Describes when the player is "frozen". - * This status is reserved for when the player is stuck at the same position for - * an unknown reason. - */ -export interface IFreezingStatus { - /** - * Monotonically-raising timestamp at the time the freezing started to be - * detected. - */ - timestamp : number; -} - -/** Information emitted on each playback observation. */ -export interface IPlaybackObservation extends Omit { - /** Event that triggered this playback observation. */ - event : IPlaybackObserverEventType; - /** Current seeking state. */ - seeking : SeekingState; - /** - * Information on the current position being played, the position for which - * media is wanted etc. - */ - position : IObservationPosition; - /** - * Set if the player is short on audio and/or video media data and is a such, - * rebuffering. - * `null` if not. - */ - rebuffering : IRebufferingStatus | null; - /** - * Set if the player is frozen, that is, stuck in place for unknown reason. - * Note that this reason can be a valid one, such as a necessary license not - * being obtained yet. - * - * `null` if the player is not frozen. - */ - freezing : IFreezingStatus | null; - /** - * Gap between `currentTime` and the next position with un-buffered data. - * `Infinity` if we don't have buffered data right now. - * `undefined` if we cannot determine the buffer gap. - */ - bufferGap : number | undefined; - /** - * The buffered range we are currently playing. - * `null` if no range is currently available. - * `undefined` if we cannot tell which range is currently available. - */ - currentRange : { start : number; - end : number; } | - null | - undefined; -} - -export type IObservationPosition = ObservationPosition; - -export class ObservationPosition { - /** - * Known position at the time the Observation was emitted, in seconds. - * - * Note that it might have changed since. If you want truly precize - * information, you should recuperate it from the HTMLMediaElement directly - * through another mean. - */ - private _last: number; - /** - * Actually wanted position in seconds that is not yet reached. - * - * This might for example be set to the initial position when the content is - * loading (and thus potentially at a `0` position) but which will be seeked - * to a given position once possible. It may also be the position of a seek - * that has not been properly accounted for by the current device. - */ - private _wanted: number | null; - constructor(last: number, wanted: number | null) { - this._last = last; - this._wanted = wanted; - } - - /** - * Obtain arguments allowing to instanciate the same ObservationPosition. - * - * This can be used to create a new `ObservationPosition` across JS realms, - * generally to communicate its data between the main thread and a WebWorker. - * @returns {Array.} - */ - public serialize(): [number, number | null] { - return [this._last, this._wanted]; - } - - /** - * Returns the playback position actually observed on the media element at - * the time the playback observation was made. - * - * Note that it may be different than the position for which media data is - * wanted in rare scenarios where the goal position is not yet set on the - * media element. - * - * You should use this value when you want to obtain the actual position set - * on the media element for browser compatibility purposes. Note that this - * position was calculated at observation time, it might thus not be - * up-to-date if what you want is milliseconds-accuracy. - * - * If what you want is the actual position which the player is intended to - * play, you should rely on `getWanted` instead`. - * @returns {number} - */ - public getPolled(): number { - return this._last; - } - - /** - * Returns the position which the player should consider to load media data - * at the time the observation was made. - * - * It can be different than the value returned by `getPolled` in rare - * scenarios: - * - * - When the initial position has not been set yet. - * - * - When the current device do not let the RxPlayer peform precize seeks, - * usually for perfomance reasons by seeking to a previous IDR frame - * instead (for now only Tizen may be like this), in which case we - * prefer to generally rely on the position wanted by the player (this - * e.g. prevents issues where the RxPlayer logic and the device are - * seeking back and forth in a loop). - * - * - When a wanted position has been "forced" (@see forceWantedPosition). - * @returns {number} - */ - public getWanted(): number { - return this._wanted ?? this._last; - } - - /** - * Method to call if you want to overwrite the currently wanted position. - * @param {number} pos - */ - public forceWantedPosition(pos: number): void { - this._wanted = pos; - } - - /** - * Returns `true` when the position wanted returned by `getWanted` and the - * actual position returned by `getPolled` may be different, meaning that - * we're currently not at the position we want to reach. - * - * This is a relatively rare situation which only happens when either the - * initial seek has not yet been performed. on specific targets where the - * seeking behavior is a little broken (@see getWanted) or when the wanted - * position has been forced (@see forceWantedPosition). - * - * In those situations, you might temporarily refrain from acting upon the - * actual current media position, as it may change soon. - * - * @returns {boolean} - */ - public isAwaitingFuturePosition(): boolean { - return this._wanted !== null; - } -} - -/** - * Interface providing a generic and read-only version of a `PlaybackObserver`. - * - * This interface allows to provide regular and specific playback information - * without allowing any effect on playback like seeking. - * - * This can be very useful to give specific playback information to modules you - * don't want to be able to update playback. - * - * Note that a `PlaybackObserver` is compatible and can thus be upcasted to a - * `IReadOnlyPlaybackObserver` to "remove" its right to update playback. - */ -export interface IReadOnlyPlaybackObserver { - /** - * Get the current playing position, in seconds. - * Returns `undefined` when this cannot be known, such as when the playback - * observer is running in a WebWorker. - * @returns {number|undefined} - */ - getCurrentTime() : number | undefined; - /** - * Returns the current playback rate advertised by the `HTMLMediaElement`. - * Returns `undefined` when this cannot be known, such as when the playback - * observer is running in a WebWorker. - * @returns {number|undefined} - */ - getPlaybackRate() : number | undefined; - /** - * Get the HTMLMediaElement's current `readyState`. - * Returns `undefined` when this cannot be known, such as when the playback - * observer is running in a WebWorker. - * @returns {number|undefined} - */ - getReadyState() : number | undefined; - /** - * Returns the current `paused` status advertised by the `HTMLMediaElement`. - * - * Use this instead of the same status emitted on an observation when you want - * to be sure you're using the current value. - * - * Returns `undefined` when this cannot be known, such as when the playback - * observer is running in a WebWorker. - * @returns {boolean|undefined} - */ - getIsPaused() : boolean | undefined; - /** - * Returns an `IReadOnlySharedReference` storing the last playback observation - * produced by the `IReadOnlyPlaybackObserver` and updated each time a new one - * is produced. - * - * This value can then be for example listened to to be notified of future - * playback observations. - * - * @returns {Object} - */ - getReference() : IReadOnlySharedReference; - /** - * Register a callback so it regularly receives playback observations. - * @param {Function} cb - * @param {Object} options - Configuration options: - * - `includeLastObservation`: If set to `true` the last observation will - * be first emitted synchronously. - * - `clearSignal`: If set, the callback will be unregistered when this - * CancellationSignal emits. - * @returns {Function} - Allows to easily unregister the callback - */ - listen( - cb : ( - observation : TObservationType, - stopListening : () => void - ) => void, - options? : { includeLastObservation? : boolean | undefined; - clearSignal? : CancellationSignal | undefined; } - ) : void; - /** - * Generate a new `IReadOnlyPlaybackObserver` from this one. - * - * As argument, this method takes a function which will allow to produce - * the new set of properties to be present on each observation. - * @param {Function} transform - * @returns {Object} - */ - deriveReadOnlyObserver( - transform : ( - observationRef : IReadOnlySharedReference, - cancellationSignal : CancellationSignal - ) => IReadOnlySharedReference - ) : IReadOnlyPlaybackObserver; -} - /** * Returns the amount of time in seconds the buffer should have ahead of the * current position before resuming playback. Based on the infos of the @@ -1219,67 +880,6 @@ function prettyPrintBuffered( return str + "\n" + currentTimeStr; } -/** - * Create `IReadOnlyPlaybackObserver` from a source `IReadOnlyPlaybackObserver` - * and a mapping function. - * @param {Object} src - * @param {Function} transform - * @returns {Object} - */ -export function generateReadOnlyObserver( - src : IReadOnlyPlaybackObserver, - transform : ( - observationRef : IReadOnlySharedReference, - cancellationSignal : CancellationSignal - ) => IReadOnlySharedReference, - cancellationSignal : CancellationSignal -) : IReadOnlyPlaybackObserver { - const mappedRef = transform(src.getReference(), cancellationSignal); - return { - getCurrentTime() { - return src.getCurrentTime(); - }, - getReadyState() { - return src.getReadyState(); - }, - getPlaybackRate() { - return src.getPlaybackRate(); - }, - getIsPaused() { - return src.getIsPaused(); - }, - getReference() : IReadOnlySharedReference { - return mappedRef; - }, - listen( - cb : ( - observation : TDest, - stopListening : () => void - ) => void, - options? : { includeLastObservation? : boolean | undefined; - clearSignal? : CancellationSignal | undefined; } - ) : void { - if (cancellationSignal.isCancelled() || - options?.clearSignal?.isCancelled() === true) - { - return ; - } - mappedRef.onUpdate(cb, { - clearSignal: options?.clearSignal, - emitCurrentValue: options?.includeLastObservation, - }); - }, - deriveReadOnlyObserver( - newTransformFn : ( - observationRef : IReadOnlySharedReference, - signal : CancellationSignal - ) => IReadOnlySharedReference - ) : IReadOnlyPlaybackObserver { - return generateReadOnlyObserver(this, newTransformFn, cancellationSignal); - }, - }; -} - /** * Generate the initial playback observation for when no event has yet been * emitted to lead to one. diff --git a/src/playback_observer/types.ts b/src/playback_observer/types.ts new file mode 100644 index 0000000000..5c7f698c08 --- /dev/null +++ b/src/playback_observer/types.ts @@ -0,0 +1,242 @@ +import type { IReadOnlySharedReference } from "../utils/reference"; +import type { CancellationSignal } from "../utils/task_canceller"; +import type ObservationPosition from "./utils/observation_position"; + +/** "Event" that triggered the playback observation. */ +export type IPlaybackObserverEventType = + /** First playback observation automatically emitted. */ + "init" | + /** Observation manually forced by the PlaybackObserver. */ + "manual" | + /** Regularly emitted playback observation when no event happened in a long time. */ + "timeupdate" | + /** On the HTML5 event with the same name */ + "canplay" | + /** On the HTML5 event with the same name */ + "ended" | + /** On the HTML5 event with the same name */ + "canplaythrough" | // HTML5 Event + /** On the HTML5 event with the same name */ + "play" | + /** On the HTML5 event with the same name */ + "pause" | + /** On the HTML5 event with the same name */ + "seeking" | + /** On the HTML5 event with the same name */ + "seeked" | + /** On the HTML5 event with the same name */ + "stalled" | + /** On the HTML5 event with the same name */ + "loadedmetadata" | + /** On the HTML5 event with the same name */ + "ratechange" | + /** An internal seek happens */ + "internal-seeking"; + +/** Information recuperated on the media element on each playback observation. */ +export interface IMediaInfos { + /** Value of `buffered` (buffered ranges) for the media element. */ + buffered : TimeRanges; + /** + * `currentTime` (position) set on the media element at the time of the + * PlaybackObserver's measure. + */ + position : number; + /** Current `duration` set on the media element. */ + duration : number; + /** Current `ended` set on the media element. */ + ended: boolean; + /** Current `paused` set on the media element. */ + paused : boolean; + /** Current `playbackRate` set on the media element. */ + playbackRate : number; + /** Current `readyState` value on the media element. */ + readyState : number; + /** Current `seeking` value on the mediaElement. */ + seeking : boolean; +} + +/** Categorize a pending seek operation. */ +export const enum SeekingState { + /** We're not currently seeking. */ + None, + /** + * We're currently seeking due to an internal logic of the RxPlayer (e.g. + * discontinuity skipping). + */ + Internal, + /** We're currently seeking due to a regular seek wanted by the application. */ + External, +} + +/** + * Describes when the player is "rebuffering" and what event started that + * status. + * "Rebuffering" is a status where the player has not enough buffer ahead to + * play reliably. + * The RxPlayer should pause playback when a playback observation indicates the + * rebuffering status. + */ +export interface IRebufferingStatus { + /** What started the player to rebuffer. */ + reason : "seeking" | // Building buffer after seeking + "not-ready" | // Building buffer after low readyState + "buffering"; // Other cases + /** + * Monotonically-raising timestamp at the time the rebuffering happened on the + * main thread. + */ + timestamp : number; + /** + * Position, in seconds, at which data is awaited. + * If `null` the player is rebuffering but not because it is awaiting future data. + * If `undefined`, that position is unknown. + */ + position : number | null | undefined; +} + +/** + * Describes when the player is "frozen". + * This status is reserved for when the player is stuck at the same position for + * an unknown reason. + */ +export interface IFreezingStatus { + /** + * Monotonically-raising timestamp at the time the freezing started to be + * detected. + */ + timestamp : number; +} + +/** Information emitted on each playback observation. */ +export interface IPlaybackObservation extends Omit { + /** Event that triggered this playback observation. */ + event : IPlaybackObserverEventType; + /** Current seeking state. */ + seeking : SeekingState; + /** + * Information on the current position being played, the position for which + * media is wanted etc. + */ + position : ObservationPosition; + /** + * Set if the player is short on audio and/or video media data and is a such, + * rebuffering. + * `null` if not. + */ + rebuffering : IRebufferingStatus | null; + /** + * Set if the player is frozen, that is, stuck in place for unknown reason. + * Note that this reason can be a valid one, such as a necessary license not + * being obtained yet. + * + * `null` if the player is not frozen. + */ + freezing : IFreezingStatus | null; + /** + * Gap between `currentTime` and the next position with un-buffered data. + * `Infinity` if we don't have buffered data right now. + * `undefined` if we cannot determine the buffer gap. + */ + bufferGap : number | undefined; + /** + * The buffered range we are currently playing. + * `null` if no range is currently available. + * `undefined` if we cannot tell which range is currently available. + */ + currentRange : { start : number; + end : number; } | + null | + undefined; +} + +/** + * Interface providing a generic and read-only version of a `PlaybackObserver`. + * + * This interface allows to provide regular and specific playback information + * without allowing any effect on playback like seeking. + * + * This can be very useful to give specific playback information to modules you + * don't want to be able to update playback. + * + * Note that a `PlaybackObserver` is compatible and can thus be upcasted to a + * `IReadOnlyPlaybackObserver` to "remove" its right to update playback. + */ +export interface IReadOnlyPlaybackObserver { + /** + * Get the current playing position, in seconds. + * Returns `undefined` when this cannot be known, such as when the playback + * observer is running in a WebWorker. + * @returns {number|undefined} + */ + getCurrentTime() : number | undefined; + /** + * Returns the current playback rate advertised by the `HTMLMediaElement`. + * Returns `undefined` when this cannot be known, such as when the playback + * observer is running in a WebWorker. + * @returns {number|undefined} + */ + getPlaybackRate() : number | undefined; + /** + * Get the HTMLMediaElement's current `readyState`. + * Returns `undefined` when this cannot be known, such as when the playback + * observer is running in a WebWorker. + * @returns {number|undefined} + */ + getReadyState() : number | undefined; + /** + * Returns the current `paused` status advertised by the `HTMLMediaElement`. + * + * Use this instead of the same status emitted on an observation when you want + * to be sure you're using the current value. + * + * Returns `undefined` when this cannot be known, such as when the playback + * observer is running in a WebWorker. + * @returns {boolean|undefined} + */ + getIsPaused() : boolean | undefined; + /** + * Returns an `IReadOnlySharedReference` storing the last playback observation + * produced by the `IReadOnlyPlaybackObserver` and updated each time a new one + * is produced. + * + * This value can then be for example listened to to be notified of future + * playback observations. + * + * @returns {Object} + */ + getReference() : IReadOnlySharedReference; + /** + * Register a callback so it regularly receives playback observations. + * @param {Function} cb + * @param {Object} options - Configuration options: + * - `includeLastObservation`: If set to `true` the last observation will + * be first emitted synchronously. + * - `clearSignal`: If set, the callback will be unregistered when this + * CancellationSignal emits. + * @returns {Function} - Allows to easily unregister the callback + */ + listen( + cb : ( + observation : TObservationType, + stopListening : () => void + ) => void, + options? : { includeLastObservation? : boolean | undefined; + clearSignal? : CancellationSignal | undefined; } + ) : void; + /** + * Generate a new `IReadOnlyPlaybackObserver` from this one. + * + * As argument, this method takes a function which will allow to produce + * the new set of properties to be present on each observation. + * @param {Function} transform + * @returns {Object} + */ + deriveReadOnlyObserver( + transform : ( + observationRef : IReadOnlySharedReference, + cancellationSignal : CancellationSignal + ) => IReadOnlySharedReference + ) : IReadOnlyPlaybackObserver; +} + diff --git a/src/playback_observer/utils/generate_read_only_observer.ts b/src/playback_observer/utils/generate_read_only_observer.ts new file mode 100644 index 0000000000..55c3192b69 --- /dev/null +++ b/src/playback_observer/utils/generate_read_only_observer.ts @@ -0,0 +1,64 @@ +import type { IReadOnlySharedReference } from "../../utils/reference"; +import type { CancellationSignal } from "../../utils/task_canceller"; +import type { IReadOnlyPlaybackObserver } from "../types"; + +/** + * Create `IReadOnlyPlaybackObserver` from a source `IReadOnlyPlaybackObserver` + * and a mapping function. + * @param {Object} src + * @param {Function} transform + * @returns {Object} + */ +export default function generateReadOnlyObserver( + src : IReadOnlyPlaybackObserver, + transform : ( + observationRef : IReadOnlySharedReference, + cancellationSignal : CancellationSignal + ) => IReadOnlySharedReference, + cancellationSignal : CancellationSignal +) : IReadOnlyPlaybackObserver { + const mappedRef = transform(src.getReference(), cancellationSignal); + return { + getCurrentTime() { + return src.getCurrentTime(); + }, + getReadyState() { + return src.getReadyState(); + }, + getPlaybackRate() { + return src.getPlaybackRate(); + }, + getIsPaused() { + return src.getIsPaused(); + }, + getReference() : IReadOnlySharedReference { + return mappedRef; + }, + listen( + cb : ( + observation : TDest, + stopListening : () => void + ) => void, + options? : { includeLastObservation? : boolean | undefined; + clearSignal? : CancellationSignal | undefined; } + ) : void { + if (cancellationSignal.isCancelled() || + options?.clearSignal?.isCancelled() === true) + { + return ; + } + mappedRef.onUpdate(cb, { + clearSignal: options?.clearSignal, + emitCurrentValue: options?.includeLastObservation, + }); + }, + deriveReadOnlyObserver( + newTransformFn : ( + observationRef : IReadOnlySharedReference, + signal : CancellationSignal + ) => IReadOnlySharedReference + ) : IReadOnlyPlaybackObserver { + return generateReadOnlyObserver(this, newTransformFn, cancellationSignal); + }, + }; +} diff --git a/src/playback_observer/utils/observation_position.ts b/src/playback_observer/utils/observation_position.ts new file mode 100644 index 0000000000..be0f84c72a --- /dev/null +++ b/src/playback_observer/utils/observation_position.ts @@ -0,0 +1,105 @@ +export default class ObservationPosition { + /** + * Known position at the time the Observation was emitted, in seconds. + * + * Note that it might have changed since. If you want truly precize + * information, you should recuperate it from the HTMLMediaElement directly + * through another mean. + */ + private _last: number; + /** + * Actually wanted position in seconds that is not yet reached. + * + * This might for example be set to the initial position when the content is + * loading (and thus potentially at a `0` position) but which will be seeked + * to a given position once possible. It may also be the position of a seek + * that has not been properly accounted for by the current device. + */ + private _wanted: number | null; + constructor(last: number, wanted: number | null) { + this._last = last; + this._wanted = wanted; + } + + /** + * Obtain arguments allowing to instanciate the same ObservationPosition. + * + * This can be used to create a new `ObservationPosition` across JS realms, + * generally to communicate its data between the main thread and a WebWorker. + * @returns {Array.} + */ + public serialize(): [number, number | null] { + return [this._last, this._wanted]; + } + + /** + * Returns the playback position actually observed on the media element at + * the time the playback observation was made. + * + * Note that it may be different than the position for which media data is + * wanted in rare scenarios where the goal position is not yet set on the + * media element. + * + * You should use this value when you want to obtain the actual position set + * on the media element for browser compatibility purposes. Note that this + * position was calculated at observation time, it might thus not be + * up-to-date if what you want is milliseconds-accuracy. + * + * If what you want is the actual position which the player is intended to + * play, you should rely on `getWanted` instead`. + * @returns {number} + */ + public getPolled(): number { + return this._last; + } + + /** + * Returns the position which the player should consider to load media data + * at the time the observation was made. + * + * It can be different than the value returned by `getPolled` in rare + * scenarios: + * + * - When the initial position has not been set yet. + * + * - When the current device do not let the RxPlayer peform precize seeks, + * usually for perfomance reasons by seeking to a previous IDR frame + * instead (for now only Tizen may be like this), in which case we + * prefer to generally rely on the position wanted by the player (this + * e.g. prevents issues where the RxPlayer logic and the device are + * seeking back and forth in a loop). + * + * - When a wanted position has been "forced" (@see forceWantedPosition). + * @returns {number} + */ + public getWanted(): number { + return this._wanted ?? this._last; + } + + /** + * Method to call if you want to overwrite the currently wanted position. + * @param {number} pos + */ + public forceWantedPosition(pos: number): void { + this._wanted = pos; + } + + /** + * Returns `true` when the position wanted returned by `getWanted` and the + * actual position returned by `getPolled` may be different, meaning that + * we're currently not at the position we want to reach. + * + * This is a relatively rare situation which only happens when either the + * initial seek has not yet been performed. on specific targets where the + * seeking behavior is a little broken (@see getWanted) or when the wanted + * position has been forced (@see forceWantedPosition). + * + * In those situations, you might temporarily refrain from acting upon the + * actual current media position, as it may change soon. + * + * @returns {boolean} + */ + public isAwaitingFuturePosition(): boolean { + return this._wanted !== null; + } +} diff --git a/src/playback_observer/worker_playback_observer.ts b/src/playback_observer/worker_playback_observer.ts new file mode 100644 index 0000000000..799b8058f1 --- /dev/null +++ b/src/playback_observer/worker_playback_observer.ts @@ -0,0 +1,137 @@ +import type { IUpdatePlaybackRateWorkerMessage } from "../multithread_types"; +import { WorkerMessageType } from "../multithread_types"; +import type { ITrackType } from "../public_types"; +import type { IRange } from "../utils/ranges"; +import type { IReadOnlySharedReference } from "../utils/reference"; +import type { CancellationSignal } from "../utils/task_canceller"; +import type { + IFreezingStatus, + IReadOnlyPlaybackObserver, + IRebufferingStatus, +} from "./types"; +import generateReadOnlyObserver from "./utils/generate_read_only_observer"; +import type ObservationPosition from "./utils/observation_position"; + +export interface IWorkerPlaybackObservation { + /** + * Information on whether the media element was paused at the time of the + * Observation. + */ + paused : IPausedPlaybackObservation; + /** + * Information on the current media position in seconds at the time of the + * Observation. + */ + position : ObservationPosition; + /** `duration` property of the HTMLMediaElement. */ + duration : number; + /** `readyState` property of the HTMLMediaElement. */ + readyState : number; + /** Target playback rate at which we want to play the content. */ + speed : number; + /** Theoretical maximum position on the content that can currently be played. */ + maximumPosition : number; + /** + * Ranges of buffered data per type of media. + * `null` if no buffer exists for that type of media. + */ + buffered : Record; + rebuffering: IRebufferingStatus | null; + freezing: IFreezingStatus | null; + bufferGap: number | undefined; +}; + +/** Pause-related information linked to an emitted Playback observation. */ +export interface IPausedPlaybackObservation { + /** + * Known paused state at the time the Observation was emitted. + * + * `true` indicating that the HTMLMediaElement was in a paused state. + * + * Note that it might have changed since. If you want truly precize + * information, you should recuperate it from the HTMLMediaElement directly + * through another mean. + */ + last : boolean; + /** + * Actually wanted paused state not yet reached. + * This might for example be set to `false` when the content is currently + * loading (and thus paused) but with autoPlay enabled. + */ + pending : boolean | undefined; +} + +export default class WorkerPlaybackObserver implements IReadOnlyPlaybackObserver< + IWorkerPlaybackObservation +> { + private _src : IReadOnlySharedReference; + private _cancelSignal : CancellationSignal; + private _messageSender : (msg: IUpdatePlaybackRateWorkerMessage) => void; + private _contentId : string; + + constructor( + src : IReadOnlySharedReference, + contentId : string, + sendMessage : (msg: IUpdatePlaybackRateWorkerMessage) => void, + cancellationSignal : CancellationSignal + ) { + this._src = src; + this._contentId = contentId; + this._messageSender = sendMessage; + this._cancelSignal = cancellationSignal; + } + + public getCurrentTime(): number | undefined { + return undefined; + } + + public getReadyState(): number | undefined { + return undefined; + } + + public getIsPaused(): boolean | undefined { + return undefined; + } + + public getReference() : IReadOnlySharedReference { + return this._src; + } + + public setPlaybackRate(playbackRate : number) : void { + this._messageSender({ type: WorkerMessageType.UpdatePlaybackRate, + contentId: this._contentId, + value: playbackRate }); + } + + public getPlaybackRate() : number | undefined { + return undefined; + } + + public listen( + cb : ( + observation : IWorkerPlaybackObservation, + stopListening : () => void + ) => void, + options? : { includeLastObservation? : boolean | undefined; + clearSignal? : CancellationSignal | undefined; } + ) : void { + if (this._cancelSignal.isCancelled() || + options?.clearSignal?.isCancelled() === true) { + return ; + } + + this._src.onUpdate(cb, { + clearSignal: options?.clearSignal, + emitCurrentValue: options?.includeLastObservation, + }); + } + + public deriveReadOnlyObserver( + transform : ( + observationRef : IReadOnlySharedReference, + cancellationSignal : CancellationSignal + ) => IReadOnlySharedReference + ) : IReadOnlyPlaybackObserver { + return generateReadOnlyObserver(this, transform, this._cancelSignal); + } +}