mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
205 lines
8.3 KiB
JavaScript
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
|