mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
build(spf): build26 from 45504a2b
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import { peek } from "../../core/signals/primitives.js";
|
||||
import { SerialRunner, Task } from "../../core/tasks/task.js";
|
||||
import { createMachineActor } from "../../core/actors/create-machine-actor.js";
|
||||
import { DEFAULT_FORWARD_BUFFER_CONFIG, getSegmentsToLoad } from "../../media/buffer/forward-buffer.js";
|
||||
import { DEFAULT_TEXT_MESSAGE_PIPELINES } from "../primitives/text-segment-load-pipeline.js";
|
||||
//#region src/playback/actors/text-track-segment-loader.ts
|
||||
/**
|
||||
* Loads text-track segments for a track and delegates cue management
|
||||
* to a TextTracksActor. Mirrors the v/a `SegmentLoaderActor` shape (FSM
|
||||
* with `idle` / `loading` and `inFlight*` context for continue-vs-preempt),
|
||||
* adapted to text:
|
||||
*
|
||||
* - No init segment, no flush ops (text cues don't need eviction — they're
|
||||
* small and the playhead-relative window is enforced by the runtime).
|
||||
* - Single in-flight identity (`inFlightSegmentId`) — text has only the
|
||||
* media-segment path, no init-segment path.
|
||||
*
|
||||
* Planning is done in the load handler on every incoming message:
|
||||
* `getSegmentsToLoad` filters to the forward window, then the segments
|
||||
* not already in `TextTracksActor`'s context are scheduled. When a new
|
||||
* `load` arrives mid-run, the handler replans and either:
|
||||
*
|
||||
* - **Continues**: the in-flight segment is still in the new plan →
|
||||
* `abortPending` only, schedule the rest of the plan (minus the
|
||||
* in-flight item, which covers its slot).
|
||||
* - **Preempts**: in-flight segment is no longer wanted (track switch,
|
||||
* large seek out of window) → `abortAll`, schedule the new plan
|
||||
* from scratch.
|
||||
*
|
||||
* The cue parser is injected so this factory is host-agnostic. A DOM
|
||||
* host supplies a VTT parser backed by `<track>`/`TextTrack` APIs; a
|
||||
* non-DOM host (worker, test fake, alternate runtime) supplies its own.
|
||||
*/
|
||||
function createTextTrackSegmentLoaderActor(textTracksActor, resolveSegment, config = {}, compositionDeps = {
|
||||
state: {},
|
||||
context: {},
|
||||
config: {}
|
||||
}) {
|
||||
const forwardBufferConfig = {
|
||||
...DEFAULT_FORWARD_BUFFER_CONFIG,
|
||||
...config.forwardBuffer
|
||||
};
|
||||
const deps = {
|
||||
state: compositionDeps.state,
|
||||
context: compositionDeps.context,
|
||||
config: {
|
||||
...compositionDeps.config,
|
||||
textTracksActor,
|
||||
resolveSegment
|
||||
}
|
||||
};
|
||||
const pipeline = (config.messagePipelines ?? DEFAULT_TEXT_MESSAGE_PIPELINES)();
|
||||
/**
|
||||
* Translate a load message into an ordered TextLoadTask list based on
|
||||
* committed actor state. In-flight awareness is handled separately in
|
||||
* the `loading` state's load handler.
|
||||
*
|
||||
* Metadata mode (no `range`) is a no-op for text — text tracks have
|
||||
* no init-segment concept, so there's nothing to load until a range
|
||||
* arrives via `'full-range'` dispatch.
|
||||
*/
|
||||
const planTasks = (message) => {
|
||||
const { track, range } = message;
|
||||
if (!range) return [];
|
||||
const trackId = track.id;
|
||||
const bufferedSegments = peek(textTracksActor.snapshot).context.segments[trackId] ?? [];
|
||||
return getSegmentsToLoad(track.segments, bufferedSegments, range.start, forwardBufferConfig).map((segment) => ({
|
||||
segment,
|
||||
trackId
|
||||
}));
|
||||
};
|
||||
/**
|
||||
* Wraps a TextLoadTask into a Task that runs the op's step pipeline
|
||||
* (resolve/relocate/dispatch, per the composition's `messagePipelines`).
|
||||
* Updates `inFlightSegmentId` around the async region so the load handler can
|
||||
* make accurate continue/preempt decisions, and checks the abort signal before
|
||||
* each step.
|
||||
*
|
||||
* Text degrades gracefully: a step throwing (e.g. a failed segment fetch) is
|
||||
* logged and swallowed so the runner continues to the next segment — unlike the
|
||||
* v/a loader, where a failed init must abort the remaining tasks.
|
||||
*/
|
||||
const makeLoadTask = (op, { getContext, setContext }) => {
|
||||
return new Task(async (signal) => {
|
||||
if (signal.aborted) return;
|
||||
const frame = { op };
|
||||
setContext({
|
||||
...getContext(),
|
||||
inFlightTrackId: op.trackId,
|
||||
inFlightSegmentId: op.segment.id
|
||||
});
|
||||
try {
|
||||
for (const step of pipeline) {
|
||||
if (signal.aborted) return;
|
||||
await step(frame, signal, deps);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load text-track segment:", error);
|
||||
} finally {
|
||||
setContext({
|
||||
...getContext(),
|
||||
inFlightTrackId: null,
|
||||
inFlightSegmentId: null
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const scheduleAll = (tasks, ctx) => {
|
||||
for (const op of tasks) ctx.runner.schedule(makeLoadTask(op, ctx)).then(void 0, (e) => {
|
||||
if (e instanceof Error && e.name === "AbortError") return;
|
||||
console.error("Unexpected error in text-track segment loader:", e);
|
||||
ctx.runner.abortPending();
|
||||
});
|
||||
};
|
||||
return createMachineActor({
|
||||
runner: () => new SerialRunner(),
|
||||
initial: "idle",
|
||||
context: {
|
||||
inFlightTrackId: null,
|
||||
inFlightSegmentId: null
|
||||
},
|
||||
states: {
|
||||
idle: { on: { load: (msg, ctx) => {
|
||||
const tasks = planTasks(msg);
|
||||
if (tasks.length === 0) return;
|
||||
ctx.transition("loading");
|
||||
scheduleAll(tasks, ctx);
|
||||
} } },
|
||||
loading: {
|
||||
onSettled: "idle",
|
||||
on: { load: (msg, ctx) => {
|
||||
const { context, runner } = ctx;
|
||||
const tasks = planTasks(msg);
|
||||
if (context.inFlightTrackId !== null && context.inFlightSegmentId !== null && tasks.some((t) => t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)) {
|
||||
runner.abortPending();
|
||||
scheduleAll(tasks.filter((t) => !(t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)), ctx);
|
||||
} else {
|
||||
runner.abortAll();
|
||||
scheduleAll(tasks, ctx);
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
export { createTextTrackSegmentLoaderActor };
|
||||
|
||||
//# sourceMappingURL=text-track-segment-loader.js.map
|
||||
Reference in New Issue
Block a user