Files
v10/dist/dev/playback/behaviors/dom/setup-buffer-actors.js
T

205 lines
8.3 KiB
JavaScript

import { computed } from "../../../core/signals/primitives.js";
import { defineBehavior } from "../../../core/composition/create-composition.js";
import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
import { hasCodecs } from "../../../media/utils/tracks.js";
import { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../../primitives/track-types.js";
import { buildMimeCodec, createSourceBuffer } from "../../../media/dom/mse/mediasource-setup.js";
import { getSelectedTrack } from "../../../media/utils/track-selection.js";
import { createTrackedFetch, fetchStream } from "../../../network/fetch.js";
import { createSegmentLoaderActor } from "../../actors/dom/segment-loader.js";
import { createSourceBufferActor } from "../../actors/dom/source-buffer.js";
import { failoverFetch } from "../../primitives/failover-fetch.js";
import { listen } from "@videojs/utils/dom";
//#region src/playback/behaviors/dom/setup-buffer-actors.ts
/**
* **Per-type buffer + segment-loader actor setup.** Per available track
* type (video / audio), when `mediaSource` is attached and the selected
* track of that type is present in the presentation with codecs (partial
* resolution from the multivariant playlist is enough — codecs live on
* the `EXT-X-STREAM-INF` line, not in the per-type media playlist),
* creates a `SourceBuffer`, a `SourceBufferActor`, and a
* `SegmentLoaderActor` bound to that buffer-actor; publishes the per-type
* actor slots. On `mediaSource` detach or behavior destroy, destroys both
* actors in reverse order and clears the per-type slots so the next
* source starts fresh.
*
* Each per-type variant (`setupVideoBufferActors` /
* `setupAudioBufferActors`) is a single-positive-state reactor
* (`'preconditions-unmet'` ↔ `'buffer-ready'`) gating only on its own
* type. No cross-type coupling in `stateKeys` —
* `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus
* `bandwidthState`, written by its trackedFetch), and audio mirrors.
*
* # Firefox `mozHasAudio` invariant
*
* Appending to a video `SourceBuffer` before the audio `SourceBuffer`
* exists causes `mozHasAudio` to be permanently false in Firefox. With
* the two per-type variants decoupled, the invariant is no longer
* structural to a single `entry` body (as it was when both buffers were
* created in one synchronous block inside a merged behavior). It's now
* preserved by a chain of assumptions about how this behavior composes
* with its upstream and downstream siblings:
*
* 1. **Upstream — default selections land in one `runPending`.**
* `selectAudioTrack` (default audio) and `switchVideoTrack`
* (default video) both subscribe to `state.presentation` flipping to
* resolved; their effects run in the same `runPending` iteration and
* write `selectedAudioTrackId` + `selectedVideoTrackId` within it.
* 2. **Self — both per-type monitors flip in one `runPending`.**
* After (1), both monitors re-evaluate and flip to `'buffer-ready'`
* in the next `runPending`. Both `entry` bodies run synchronously
* within that iteration — both `addSourceBuffer` calls land before
* the iteration ends.
* 3. **Downstream — `appendBuffer` is async.** `loadVideoSegments` /
* `loadAudioSegments` read the per-type `xSegmentLoaderActor` slots;
* their effects fire in the *next* `runPending` and the actual
* `appendBuffer` requires a network round-trip via the
* `SegmentLoaderActor` — many microtasks past both `addSourceBuffer`
* calls.
*
* The cross-tick failure mode — a user-initiated audio track switch
* *after* video segments have begun appending — is out of scope for
* this behavior and would be addressed in the buffer/segment-loading
* path via `changeType`-aware logic.
*
* # Sole writer
*
* `setupVideoBufferActors` is sole writer of `videoBufferActor` +
* `videoSegmentLoaderActor` (and `bandwidthState` via its
* trackedFetch); `setupAudioBufferActors` is sole writer of
* `audioBufferActor` + `audioSegmentLoaderActor`. Both read
* `mediaSource` from `setupMediaSource`. Downstream MSE behaviors
* (`loadVideoSegments`, `loadAudioSegments`, `endOfStream`,
* `updateMediaSourceDuration`) only read these slots.
*/
function setupBufferActors({ state, context, config }) {
const { type, selectedKey, actorKey, loaderKey, fetch, forwardBuffer, backBuffer, messagePipelines } = config;
const derivedStateSignal = computed(() => {
if (!context.mediaSource.get()) return "preconditions-unmet";
return hasCodecs(getSelectedTrack({
presentation: state.presentation.get(),
[selectedKey]: state[selectedKey].get()
}, type)) ? "buffer-ready" : "preconditions-unmet";
});
return createMachineReactor({
initial: "preconditions-unmet",
monitor: () => derivedStateSignal.get(),
states: {
"preconditions-unmet": {},
"buffer-ready": { entry: () => {
const mediaSource = context.mediaSource.get();
const bufferActor = createSourceBufferActor(createSourceBuffer(mediaSource, buildMimeCodec(getSelectedTrack({
presentation: state.presentation.get(),
[selectedKey]: state[selectedKey].get()
}, type))));
const segmentLoader = createSegmentLoaderActor(bufferActor, fetch, {
forwardBuffer,
backBuffer,
messagePipelines
}, {
state,
context,
config
});
context[actorKey].set(bufferActor);
context[loaderKey].set(segmentLoader);
const disconnect = new AbortController();
const teardownActors = () => {
segmentLoader.destroy();
bufferActor.destroy();
context[loaderKey].set(void 0);
context[actorKey].set(void 0);
disconnect.abort();
};
listen(mediaSource, "sourceclose", teardownActors, { signal: disconnect.signal });
return teardownActors;
} }
}
});
}
/**
* Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires
* when `mediaSource` is attached and the selected video track is
* present in the presentation with codecs. Gates only on video state —
* no cross-type coupling. Owns a bandwidth-sampling `trackedFetch` and
* is sole writer of `state.bandwidthState`.
*/
const setupVideoBufferActors = defineBehavior({
stateKeys: [
"presentation",
"selectedVideoTrackId",
"bandwidthState"
],
contextKeys: [
"mediaSource",
"videoBufferActor",
"videoSegmentLoaderActor"
],
setup: ({ state, context, config = {} }) => {
const trackedFetch = createTrackedFetch(state.bandwidthState.get() ?? {
fastEstimate: 0,
fastTotalWeight: 0,
slowEstimate: 0,
slowTotalWeight: 0,
bytesSampled: 0
}, (next) => state.bandwidthState.set(next));
const typeConfig = {
...VIDEO_TYPE_CONFIG,
...config
};
return setupBufferActors({
state,
context,
config: {
...typeConfig,
messagePipelines: config.videoMessagePipelines,
fetch: failoverFetch(trackedFetch, state, typeConfig)
}
});
}
});
/**
* Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same
* shape as `setupVideoBufferActors`, narrowed to audio. Today supplies
* a non-sampling `fetchStream` (no audio ABR); adding audio ABR is a
* localized change to this setup body (swap `fetchStream` for a
* `createTrackedFetch` call + declare `bandwidthState` writable here)
* without touching the shared helper. See
* `internal/design/spf/features/audio-abr.md` for the design surface
* (bandwidth-state sharing, multi-writer coordination, EWMA mixed-
* source sampling).
*
* **Mid-stream audio track switching is NOT this behavior's concern.**
* Slot writes to `selectedAudioTrackId` (default selection, programmatic
* filter-driven, future ABR) are owned by `switchAudioTrack` / future
* `switchAudioQuality` in `track-switching.ts`. Flush orchestration on
* track change is dispatched from there via `audioBufferActor.send(...)`
* — keeping this setup behavior focused on per-source actor lifecycle.
*/
const setupAudioBufferActors = defineBehavior({
stateKeys: ["presentation", "selectedAudioTrackId"],
contextKeys: [
"mediaSource",
"audioBufferActor",
"audioSegmentLoaderActor"
],
setup: ({ state, context, config = {} }) => {
const typeConfig = {
...AUDIO_TYPE_CONFIG,
...config
};
return setupBufferActors({
state,
context,
config: {
...typeConfig,
messagePipelines: config.audioMessagePipelines,
fetch: failoverFetch(fetchStream, state, typeConfig)
}
});
}
});
//#endregion
export { setupAudioBufferActors, setupVideoBufferActors };
//# sourceMappingURL=setup-buffer-actors.js.map