mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
181 lines
6.4 KiB
JavaScript
181 lines
6.4 KiB
JavaScript
import { computed, peek } from "../../../core/signals/primitives.js";
|
|
import { defineBehavior } from "../../../core/composition/create-composition.js";
|
|
import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
|
|
import { DEFAULT_FORWARD_BUFFER_CONFIG, segmentStartForTime } from "../../../media/buffer/forward-buffer.js";
|
|
import { findResolvedAudioTrack, findResolvedTextTrack, findResolvedVideoTrack } from "../../../media/utils/tracks.js";
|
|
import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../../primitives/track-types.js";
|
|
//#region src/playback/behaviors/dom/load-segments.ts
|
|
/**
|
|
* **Per-type segment loading dispatch.** Per available track type (video /
|
|
* audio / text), reads the per-type segment-loader actor from context and
|
|
* sends typed `'load'` messages whenever a meaningful loading condition
|
|
* changes (selected track, current time crossing a segment boundary).
|
|
*
|
|
* Loader-actor lifecycle is owned upstream:
|
|
* - Video: `setupVideoBufferActors`
|
|
* - Audio: `setupAudioBufferActors`
|
|
* - Text: `setupTextTrackActors`
|
|
*
|
|
* This behavior is pure-consumer: it reads `context[loaderKey]` and
|
|
* dispatches typed messages via the variant's per-type `findResolvedTrack`
|
|
* resolver.
|
|
*
|
|
* # Load modes as reactor states
|
|
*
|
|
* Four states encode the load-gating policy directly:
|
|
*
|
|
* - `'preconditions-unmet'` — no loader actor in context, or the selected
|
|
* track hasn't resolved.
|
|
* - `'dormant'` — loading disabled by policy: an observed `loadingSuspended`
|
|
* (highest precedence) or `preload === 'none' && !loadActivated`. Nothing
|
|
* fires; already-queued loader work drains. Auto-resumes into the derived
|
|
* state when the policy lifts.
|
|
* - `'metadata-only'` — `!loadActivated && preload !== 'auto' && preload !== 'none'`.
|
|
* Fires an init-segment-only `load` message **once on entry**. The
|
|
* variant's loader actor decides what to do — v/a's actor fetches the
|
|
* init segment; text's actor no-ops (no init concept).
|
|
* - `'full-range'` — `loadActivated || preload === 'auto'`. Effect re-fires
|
|
* on selected-track change and on segment-boundary crossing.
|
|
*
|
|
* # Per-type parameterization (inference-driven)
|
|
*
|
|
* The helper is generic over `Track` — the resolved-track type. Each
|
|
* variant supplies its own `findResolvedTrack` resolver via config; TS
|
|
* infers `Track` from the resolver's return type. The loader signal's
|
|
* value type is constrained to `SegmentLoaderLike<Track>` — anything with
|
|
* a `send` method accepting the `Track`-parameterized message. Concrete
|
|
* actor types (`SegmentLoaderActor`, `TextTrackSegmentLoaderActor`)
|
|
* satisfy this via function-parameter contravariance: an actor whose
|
|
* `.send` accepts a wider track type is assignable to a slot expecting a
|
|
* narrower track type.
|
|
*
|
|
* No widening of actor message types is needed; no casts inside the
|
|
* helper. Per-variant wiring (right loader paired with right resolver)
|
|
* is enforced at the variant call site.
|
|
*/
|
|
/**
|
|
* Specialization helper. Generic over `Track` (inferred from
|
|
* `findResolvedTrack`'s return type). The loader value type is
|
|
* constrained to `SegmentLoaderLike<Track>` — concrete actor types
|
|
* (whose `.send` accepts a wider track union) satisfy this via
|
|
* function-parameter contravariance.
|
|
*
|
|
* Tracks that the helper handles must have a `segments` field — used by
|
|
* `segmentBoundarySignal` to compute the load-anchor boundary. Each
|
|
* variant's resolver narrows to the right resolved-track shape.
|
|
*/
|
|
function setupSegmentLoading({ state, context, config }) {
|
|
const { selectedKey, loaderKey, findResolvedTrack } = config;
|
|
const bufferDuration = config.forwardBuffer?.bufferDuration ?? DEFAULT_FORWARD_BUFFER_CONFIG.bufferDuration;
|
|
const selectedTrack = computed(() => findResolvedTrack(state.presentation.get(), state[selectedKey].get()));
|
|
const segmentBoundarySignal = computed(() => {
|
|
const track = selectedTrack.get();
|
|
if (!track) return void 0;
|
|
return segmentStartForTime(state.currentTime.get() ?? 0, track.segments);
|
|
});
|
|
const derivedStateSignal = computed(() => {
|
|
if (state.loadingSuspended?.get()) return "dormant";
|
|
if (!context[loaderKey].get() || !selectedTrack.get()) return "preconditions-unmet";
|
|
if (state.loadActivated.get() || state.preload.get() === "auto") return "full-range";
|
|
if (state.preload.get() === "none") return "dormant";
|
|
return "metadata-only";
|
|
});
|
|
return createMachineReactor({
|
|
initial: "preconditions-unmet",
|
|
monitor: () => derivedStateSignal.get(),
|
|
states: {
|
|
"preconditions-unmet": {},
|
|
dormant: {},
|
|
"metadata-only": { entry: () => {
|
|
const track = selectedTrack.get();
|
|
context[loaderKey].get().send({
|
|
type: "load",
|
|
track
|
|
});
|
|
} },
|
|
"full-range": { effects: () => {
|
|
const track = selectedTrack.get();
|
|
segmentBoundarySignal.get();
|
|
const currentTime = peek(state.currentTime) ?? 0;
|
|
peek(context[loaderKey]).send({
|
|
type: "load",
|
|
track,
|
|
range: {
|
|
start: currentTime,
|
|
end: currentTime + bufferDuration
|
|
}
|
|
});
|
|
} }
|
|
}
|
|
});
|
|
}
|
|
const VIDEO_SEGMENT_LOADING_CONFIG = {
|
|
...VIDEO_TYPE_CONFIG,
|
|
findResolvedTrack: findResolvedVideoTrack
|
|
};
|
|
const AUDIO_SEGMENT_LOADING_CONFIG = {
|
|
...AUDIO_TYPE_CONFIG,
|
|
findResolvedTrack: findResolvedAudioTrack
|
|
};
|
|
const TEXT_SEGMENT_LOADING_CONFIG = {
|
|
...TEXT_TYPE_CONFIG,
|
|
findResolvedTrack: findResolvedTextTrack
|
|
};
|
|
const loadVideoSegments = defineBehavior({
|
|
stateKeys: [
|
|
"presentation",
|
|
"preload",
|
|
"currentTime",
|
|
"loadActivated",
|
|
"selectedVideoTrackId"
|
|
],
|
|
contextKeys: ["videoSegmentLoaderActor"],
|
|
setup: ({ state, context, config = {} }) => setupSegmentLoading({
|
|
state,
|
|
context,
|
|
config: {
|
|
...VIDEO_SEGMENT_LOADING_CONFIG,
|
|
...config
|
|
}
|
|
})
|
|
});
|
|
const loadAudioSegments = defineBehavior({
|
|
stateKeys: [
|
|
"presentation",
|
|
"preload",
|
|
"currentTime",
|
|
"loadActivated",
|
|
"selectedAudioTrackId"
|
|
],
|
|
contextKeys: ["audioSegmentLoaderActor"],
|
|
setup: ({ state, context, config = {} }) => setupSegmentLoading({
|
|
state,
|
|
context,
|
|
config: {
|
|
...AUDIO_SEGMENT_LOADING_CONFIG,
|
|
...config
|
|
}
|
|
})
|
|
});
|
|
const loadTextTrackSegments = defineBehavior({
|
|
stateKeys: [
|
|
"presentation",
|
|
"preload",
|
|
"currentTime",
|
|
"loadActivated",
|
|
"selectedTextTrackId"
|
|
],
|
|
contextKeys: ["textTrackSegmentLoaderActor"],
|
|
setup: ({ state, context, config = {} }) => setupSegmentLoading({
|
|
state,
|
|
context,
|
|
config: {
|
|
...TEXT_SEGMENT_LOADING_CONFIG,
|
|
...config
|
|
}
|
|
})
|
|
});
|
|
//#endregion
|
|
export { loadAudioSegments, loadTextTrackSegments, loadVideoSegments };
|
|
|
|
//# sourceMappingURL=load-segments.js.map
|