@videojs/spf 10.0.0-beta.11 → 10.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/default/core/abr/bandwidth-estimator.js +2 -3
- package/dist/default/core/abr/bandwidth-estimator.js.map +1 -1
- package/dist/default/core/abr/ewma.js +1 -1
- package/dist/default/core/abr/quality-selection.js +2 -2
- package/dist/default/core/abr/quality-selection.js.map +1 -1
- package/dist/default/core/buffer/back-buffer.js +2 -2
- package/dist/default/core/buffer/forward-buffer.js +1 -1
- package/dist/default/core/features/calculate-presentation-duration.js +11 -9
- package/dist/default/core/features/calculate-presentation-duration.js.map +1 -1
- package/dist/default/core/features/quality-switching.js +7 -6
- package/dist/default/core/features/quality-switching.js.map +1 -1
- package/dist/default/core/features/resolve-presentation.js +30 -52
- package/dist/default/core/features/resolve-presentation.js.map +1 -1
- package/dist/default/core/features/resolve-track.js +17 -28
- package/dist/default/core/features/resolve-track.js.map +1 -1
- package/dist/default/core/features/select-tracks.js +18 -35
- package/dist/default/core/features/select-tracks.js.map +1 -1
- package/dist/default/core/features/sync-preload-attribute.js +26 -0
- package/dist/default/core/features/sync-preload-attribute.js.map +1 -0
- package/dist/default/core/hls/parse-attributes.js +1 -1
- package/dist/default/core/hls/parse-attributes.js.map +1 -1
- package/dist/default/core/hls/parse-media-playlist.js +1 -2
- package/dist/default/core/hls/parse-media-playlist.js.map +1 -1
- package/dist/default/core/hls/parse-multivariant.js +2 -3
- package/dist/default/core/hls/parse-multivariant.js.map +1 -1
- package/dist/default/core/hls/resolve-url.js +1 -1
- package/dist/default/core/signals/effect.js +41 -0
- package/dist/default/core/signals/effect.js.map +1 -0
- package/dist/default/core/signals/primitives.js +32 -0
- package/dist/default/core/signals/primitives.js.map +1 -0
- package/dist/default/core/task.js +3 -3
- package/dist/default/core/task.js.map +1 -1
- package/dist/default/core/types/index.js +1 -1
- package/dist/default/core/types/index.js.map +1 -1
- package/dist/default/core/utils/generate-id.js +1 -1
- package/dist/default/core/utils/track-selection.js +10 -2
- package/dist/default/core/utils/track-selection.js.map +1 -1
- package/dist/default/dom/features/end-of-stream.js +24 -48
- package/dist/default/dom/features/end-of-stream.js.map +1 -1
- package/dist/default/dom/features/load-segments.js +40 -36
- package/dist/default/dom/features/load-segments.js.map +1 -1
- package/dist/default/dom/features/load-text-track-cues.js +36 -26
- package/dist/default/dom/features/load-text-track-cues.js.map +1 -1
- package/dist/default/dom/features/segment-loader-actor.js +4 -5
- package/dist/default/dom/features/segment-loader-actor.js.map +1 -1
- package/dist/default/dom/features/setup-mediasource.js +29 -36
- package/dist/default/dom/features/setup-mediasource.js.map +1 -1
- package/dist/default/dom/features/setup-sourcebuffer.js +43 -35
- package/dist/default/dom/features/setup-sourcebuffer.js.map +1 -1
- package/dist/default/dom/features/setup-text-tracks.js +25 -46
- package/dist/default/dom/features/setup-text-tracks.js.map +1 -1
- package/dist/default/dom/features/sync-selected-text-track-from-dom.js +14 -23
- package/dist/default/dom/features/sync-selected-text-track-from-dom.js.map +1 -1
- package/dist/default/dom/features/sync-text-track-modes.js +12 -17
- package/dist/default/dom/features/sync-text-track-modes.js.map +1 -1
- package/dist/default/dom/features/track-current-time.js +10 -7
- package/dist/default/dom/features/track-current-time.js.map +1 -1
- package/dist/default/dom/features/track-playback-initiated.js +34 -36
- package/dist/default/dom/features/track-playback-initiated.js.map +1 -1
- package/dist/default/dom/features/track-playback-rate.js +12 -16
- package/dist/default/dom/features/track-playback-rate.js.map +1 -1
- package/dist/default/dom/features/update-duration.js +19 -13
- package/dist/default/dom/features/update-duration.js.map +1 -1
- package/dist/default/dom/media/append-segment.js +1 -1
- package/dist/default/dom/media/buffer-flusher.js +1 -1
- package/dist/default/dom/media/mediasource-setup.js +32 -40
- package/dist/default/dom/media/mediasource-setup.js.map +1 -1
- package/dist/default/dom/media/source-buffer-actor.js +16 -26
- package/dist/default/dom/media/source-buffer-actor.js.map +1 -1
- package/dist/default/dom/network/chunked-stream-iterable.js +1 -1
- package/dist/default/dom/network/chunked-stream-iterable.js.map +1 -1
- package/dist/default/dom/network/fetch.js +1 -2
- package/dist/default/dom/network/fetch.js.map +1 -1
- package/dist/default/dom/playback-engine/adapter.js +13 -13
- package/dist/default/dom/playback-engine/adapter.js.map +1 -1
- package/dist/default/dom/playback-engine/engine.js +24 -49
- package/dist/default/dom/playback-engine/engine.js.map +1 -1
- package/dist/default/dom/playback-engine.js +2 -2
- package/dist/default/dom/text/parse-vtt-segment.js +1 -1
- package/dist/default/dom.js +1 -2
- package/dist/default/index.js +1 -1
- package/dist/dev/core/abr/bandwidth-estimator.js +2 -3
- package/dist/dev/core/abr/bandwidth-estimator.js.map +1 -1
- package/dist/dev/core/abr/ewma.js +1 -1
- package/dist/dev/core/abr/quality-selection.js +2 -2
- package/dist/dev/core/abr/quality-selection.js.map +1 -1
- package/dist/dev/core/actor.d.ts +6 -19
- package/dist/dev/core/actor.d.ts.map +1 -1
- package/dist/dev/core/buffer/back-buffer.js +2 -2
- package/dist/dev/core/buffer/forward-buffer.js +1 -1
- package/dist/dev/core/features/calculate-presentation-duration.js +11 -9
- package/dist/dev/core/features/calculate-presentation-duration.js.map +1 -1
- package/dist/dev/core/features/quality-switching.js +7 -6
- package/dist/dev/core/features/quality-switching.js.map +1 -1
- package/dist/dev/core/features/resolve-presentation.js +30 -52
- package/dist/dev/core/features/resolve-presentation.js.map +1 -1
- package/dist/dev/core/features/resolve-track.js +17 -28
- package/dist/dev/core/features/resolve-track.js.map +1 -1
- package/dist/dev/core/features/select-tracks.js +18 -35
- package/dist/dev/core/features/select-tracks.js.map +1 -1
- package/dist/dev/core/features/sync-preload-attribute.js +26 -0
- package/dist/dev/core/features/sync-preload-attribute.js.map +1 -0
- package/dist/dev/core/hls/parse-attributes.js +1 -1
- package/dist/dev/core/hls/parse-attributes.js.map +1 -1
- package/dist/dev/core/hls/parse-media-playlist.js +1 -2
- package/dist/dev/core/hls/parse-media-playlist.js.map +1 -1
- package/dist/dev/core/hls/parse-multivariant.js +2 -3
- package/dist/dev/core/hls/parse-multivariant.js.map +1 -1
- package/dist/dev/core/hls/resolve-url.js +1 -1
- package/dist/dev/core/signals/effect.d.ts +15 -0
- package/dist/dev/core/signals/effect.d.ts.map +1 -0
- package/dist/dev/core/signals/effect.js +41 -0
- package/dist/dev/core/signals/effect.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +10 -0
- package/dist/dev/core/signals/primitives.d.ts.map +1 -0
- package/dist/dev/core/signals/primitives.js +32 -0
- package/dist/dev/core/signals/primitives.js.map +1 -0
- package/dist/dev/core/task.js +3 -3
- package/dist/dev/core/task.js.map +1 -1
- package/dist/dev/core/types/index.js +1 -1
- package/dist/dev/core/types/index.js.map +1 -1
- package/dist/dev/core/utils/generate-id.js +1 -1
- package/dist/dev/core/utils/track-selection.js +10 -2
- package/dist/dev/core/utils/track-selection.js.map +1 -1
- package/dist/dev/dom/features/end-of-stream.js +24 -48
- package/dist/dev/dom/features/end-of-stream.js.map +1 -1
- package/dist/dev/dom/features/load-segments.d.ts +4 -4
- package/dist/dev/dom/features/load-segments.d.ts.map +1 -1
- package/dist/dev/dom/features/load-segments.js +40 -36
- package/dist/dev/dom/features/load-segments.js.map +1 -1
- package/dist/dev/dom/features/load-text-track-cues.d.ts +4 -4
- package/dist/dev/dom/features/load-text-track-cues.d.ts.map +1 -1
- package/dist/dev/dom/features/load-text-track-cues.js +36 -26
- package/dist/dev/dom/features/load-text-track-cues.js.map +1 -1
- package/dist/dev/dom/features/segment-loader-actor.d.ts +1 -0
- package/dist/dev/dom/features/segment-loader-actor.js +4 -5
- package/dist/dev/dom/features/segment-loader-actor.js.map +1 -1
- package/dist/dev/dom/features/setup-mediasource.js +29 -36
- package/dist/dev/dom/features/setup-mediasource.js.map +1 -1
- package/dist/dev/dom/features/setup-sourcebuffer.d.ts.map +1 -1
- package/dist/dev/dom/features/setup-sourcebuffer.js +43 -35
- package/dist/dev/dom/features/setup-sourcebuffer.js.map +1 -1
- package/dist/dev/dom/features/setup-text-tracks.js +25 -46
- package/dist/dev/dom/features/setup-text-tracks.js.map +1 -1
- package/dist/dev/dom/features/sync-selected-text-track-from-dom.js +14 -23
- package/dist/dev/dom/features/sync-selected-text-track-from-dom.js.map +1 -1
- package/dist/dev/dom/features/sync-text-track-modes.js +12 -17
- package/dist/dev/dom/features/sync-text-track-modes.js.map +1 -1
- package/dist/dev/dom/features/track-current-time.d.ts +4 -4
- package/dist/dev/dom/features/track-current-time.d.ts.map +1 -1
- package/dist/dev/dom/features/track-current-time.js +10 -7
- package/dist/dev/dom/features/track-current-time.js.map +1 -1
- package/dist/dev/dom/features/track-playback-initiated.d.ts +14 -23
- package/dist/dev/dom/features/track-playback-initiated.d.ts.map +1 -1
- package/dist/dev/dom/features/track-playback-initiated.js +34 -36
- package/dist/dev/dom/features/track-playback-initiated.js.map +1 -1
- package/dist/dev/dom/features/track-playback-rate.d.ts +4 -4
- package/dist/dev/dom/features/track-playback-rate.d.ts.map +1 -1
- package/dist/dev/dom/features/track-playback-rate.js +12 -16
- package/dist/dev/dom/features/track-playback-rate.js.map +1 -1
- package/dist/dev/dom/features/update-duration.js +19 -13
- package/dist/dev/dom/features/update-duration.js.map +1 -1
- package/dist/dev/dom/media/append-segment.js +1 -1
- package/dist/dev/dom/media/buffer-flusher.js +1 -1
- package/dist/dev/dom/media/mediasource-setup.js +32 -40
- package/dist/dev/dom/media/mediasource-setup.js.map +1 -1
- package/dist/dev/dom/media/source-buffer-actor.d.ts +2 -2
- package/dist/dev/dom/media/source-buffer-actor.d.ts.map +1 -1
- package/dist/dev/dom/media/source-buffer-actor.js +16 -26
- package/dist/dev/dom/media/source-buffer-actor.js.map +1 -1
- package/dist/dev/dom/network/chunked-stream-iterable.js +1 -1
- package/dist/dev/dom/network/chunked-stream-iterable.js.map +1 -1
- package/dist/dev/dom/network/fetch.js +1 -2
- package/dist/dev/dom/network/fetch.js.map +1 -1
- package/dist/dev/dom/playback-engine/adapter.d.ts.map +1 -1
- package/dist/dev/dom/playback-engine/adapter.js +13 -13
- package/dist/dev/dom/playback-engine/adapter.js.map +1 -1
- package/dist/dev/dom/playback-engine/engine.d.ts +14 -26
- package/dist/dev/dom/playback-engine/engine.d.ts.map +1 -1
- package/dist/dev/dom/playback-engine/engine.js +24 -49
- package/dist/dev/dom/playback-engine/engine.js.map +1 -1
- package/dist/dev/dom/playback-engine.d.ts +3 -2
- package/dist/dev/dom/playback-engine.js +2 -2
- package/dist/dev/dom/text/parse-vtt-segment.js +1 -1
- package/dist/dev/dom.js +1 -2
- package/dist/dev/index.js +1 -1
- package/package.json +5 -4
- package/dist/default/all.js +0 -16
- package/dist/default/core/events/create-event-stream.js +0 -48
- package/dist/default/core/events/create-event-stream.js.map +0 -1
- package/dist/default/core/reactive/combine-latest.js +0 -72
- package/dist/default/core/reactive/combine-latest.js.map +0 -1
- package/dist/default/core/state/create-state.js +0 -143
- package/dist/default/core/state/create-state.js.map +0 -1
- package/dist/dev/all.js +0 -16
- package/dist/dev/core/events/create-event-stream.d.ts +0 -48
- package/dist/dev/core/events/create-event-stream.d.ts.map +0 -1
- package/dist/dev/core/events/create-event-stream.js +0 -48
- package/dist/dev/core/events/create-event-stream.js.map +0 -1
- package/dist/dev/core/features/resolve-presentation.d.ts +0 -16
- package/dist/dev/core/features/resolve-presentation.d.ts.map +0 -1
- package/dist/dev/core/features/resolve-track.d.ts +0 -13
- package/dist/dev/core/features/resolve-track.d.ts.map +0 -1
- package/dist/dev/core/features/select-tracks.d.ts +0 -11
- package/dist/dev/core/features/select-tracks.d.ts.map +0 -1
- package/dist/dev/core/reactive/combine-latest.js +0 -72
- package/dist/dev/core/reactive/combine-latest.js.map +0 -1
- package/dist/dev/core/state/create-state.d.ts +0 -104
- package/dist/dev/core/state/create-state.d.ts.map +0 -1
- package/dist/dev/core/state/create-state.js +0 -143
- package/dist/dev/core/state/create-state.js.map +0 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
2
|
+
import { computed, update } from "../../core/signals/primitives.js";
|
|
1
3
|
import { isResolvedTrack } from "../../core/types/index.js";
|
|
4
|
+
import { BufferKeyByType, getSelectedTrack } from "../../core/utils/track-selection.js";
|
|
2
5
|
import { createSourceBuffer } from "../media/mediasource-setup.js";
|
|
3
|
-
import { combineLatest } from "../../core/reactive/combine-latest.js";
|
|
4
|
-
import { getSelectedTrack } from "../../core/utils/track-selection.js";
|
|
5
6
|
import { createSourceBufferActor } from "../media/source-buffer-actor.js";
|
|
6
|
-
import "@videojs/utils/predicate";
|
|
7
|
-
|
|
8
7
|
//#region src/dom/features/setup-sourcebuffer.ts
|
|
8
|
+
const ActorKeyByType = {
|
|
9
|
+
video: "videoBufferActor",
|
|
10
|
+
audio: "audioBufferActor"
|
|
11
|
+
};
|
|
9
12
|
/**
|
|
10
13
|
* Build MIME codec string from track metadata.
|
|
11
14
|
*
|
|
@@ -23,48 +26,53 @@ function buildMimeCodec(track) {
|
|
|
23
26
|
/**
|
|
24
27
|
* Setup all needed SourceBuffers as a single coordinated operation.
|
|
25
28
|
*
|
|
26
|
-
* Waits until ALL
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Waits until ALL media tracks in the presentation are resolved with codecs,
|
|
30
|
+
* then creates every SourceBuffer in one synchronous block before setting
|
|
31
|
+
* owners. This guarantees that downstream consumers (e.g. loadSegments) never
|
|
32
|
+
* see a partial set of SourceBuffers — preventing the Firefox bug where
|
|
33
|
+
* appending to a video SourceBuffer before the audio SourceBuffer exists
|
|
34
|
+
* causes mozHasAudio to be permanently false.
|
|
32
35
|
*
|
|
33
36
|
* Handles video-only, audio-only, and combined presentations correctly:
|
|
34
|
-
*
|
|
37
|
+
* track types are derived from the presentation rather than hardcoded.
|
|
35
38
|
*
|
|
36
39
|
* @example
|
|
37
40
|
* const cleanup = setupSourceBuffers({ state, owners });
|
|
38
41
|
*/
|
|
39
42
|
function setupSourceBuffers({ state, owners }) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
const presentationTypesSignal = computed(() => {
|
|
44
|
+
const { presentation } = state.get();
|
|
45
|
+
if (!presentation || !("selectionSets" in presentation)) return [];
|
|
46
|
+
return presentation.selectionSets.map(({ type }) => type).filter((type) => type === "video" || type === "audio");
|
|
47
|
+
});
|
|
48
|
+
const canSetupSignal = computed(() => {
|
|
49
|
+
const types = presentationTypesSignal.get();
|
|
50
|
+
if (!owners.get().mediaSource || types.length === 0) return false;
|
|
51
|
+
const s = state.get();
|
|
52
|
+
return types.every((type) => {
|
|
53
|
+
const track = getSelectedTrack(s, type);
|
|
54
|
+
return track && isResolvedTrack(track) && !!track.codecs?.length;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
const shouldSetupSignal = computed(() => {
|
|
58
|
+
const o = owners.get();
|
|
59
|
+
return presentationTypesSignal.get().every((type) => !o[BufferKeyByType[type]]);
|
|
60
|
+
});
|
|
61
|
+
return effect(() => {
|
|
62
|
+
if (!canSetupSignal.get() || !shouldSetupSignal.get()) return;
|
|
63
|
+
const s = state.get();
|
|
64
|
+
const o = owners.get();
|
|
52
65
|
const patch = {};
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
patch
|
|
66
|
+
for (const type of presentationTypesSignal.get()) {
|
|
67
|
+
const track = getSelectedTrack(s, type);
|
|
68
|
+
const buffer = createSourceBuffer(o.mediaSource, buildMimeCodec(track));
|
|
69
|
+
patch[BufferKeyByType[type]] = buffer;
|
|
70
|
+
patch[ActorKeyByType[type]] = createSourceBufferActor(buffer);
|
|
57
71
|
}
|
|
58
|
-
|
|
59
|
-
const buffer = createSourceBuffer(currentOwners.mediaSource, buildMimeCodec(audioTrack));
|
|
60
|
-
patch.audioBuffer = buffer;
|
|
61
|
-
patch.audioBufferActor = createSourceBufferActor(buffer);
|
|
62
|
-
}
|
|
63
|
-
owners.patch(patch);
|
|
64
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
72
|
+
update(owners, patch);
|
|
65
73
|
});
|
|
66
74
|
}
|
|
67
|
-
|
|
68
75
|
//#endregion
|
|
69
76
|
export { setupSourceBuffers };
|
|
77
|
+
|
|
70
78
|
//# sourceMappingURL=setup-sourcebuffer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-sourcebuffer.js","names":[],"sources":["../../../../src/dom/features/setup-sourcebuffer.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"setup-sourcebuffer.js","names":[],"sources":["../../../../src/dom/features/setup-sourcebuffer.ts"],"sourcesContent":["import { effect } from '../../core/signals/effect';\nimport { computed, type Signal, update } from '../../core/signals/primitives';\nimport type { Presentation, ResolvedTrack } from '../../core/types';\nimport { isResolvedTrack } from '../../core/types';\nimport { BufferKeyByType, getSelectedTrack, type TrackSelectionState } from '../../core/utils/track-selection';\nimport { createSourceBuffer } from '../media/mediasource-setup';\nimport { createSourceBufferActor, type SourceBufferActor } from '../media/source-buffer-actor';\n\n/**\n * Media track type for SourceBuffer setup.\n * Text tracks are excluded as they don't use MSE SourceBuffers.\n */\nexport type MediaTrackType = 'video' | 'audio';\n\nconst ActorKeyByType = {\n video: 'videoBufferActor',\n audio: 'audioBufferActor',\n} as const satisfies Record<MediaTrackType, keyof SourceBufferOwners>;\n\n/**\n * State shape for SourceBuffer setup.\n */\nexport interface SourceBufferState extends TrackSelectionState {\n presentation?: Presentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * Owners shape for SourceBuffer setup.\n */\nexport interface SourceBufferOwners {\n mediaSource?: MediaSource;\n videoBuffer?: SourceBuffer;\n audioBuffer?: SourceBuffer;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n}\n\n/**\n * Build MIME codec string from track metadata.\n *\n * @param track - Resolved track with mimeType and codecs\n * @returns MIME codec string (e.g., 'video/mp4; codecs=\"avc1.42E01E,mp4a.40.2\"')\n *\n * @example\n * buildMimeCodec({ mimeType: 'video/mp4', codecs: ['avc1.42E01E'] })\n * // => 'video/mp4; codecs=\"avc1.42E01E\"'\n */\nexport function buildMimeCodec(track: ResolvedTrack): string {\n const codecString = track.codecs?.join(',') ?? '';\n return `${track.mimeType}; codecs=\"${codecString}\"`;\n}\n\n/**\n * Setup all needed SourceBuffers as a single coordinated operation.\n *\n * Waits until ALL media tracks in the presentation are resolved with codecs,\n * then creates every SourceBuffer in one synchronous block before setting\n * owners. This guarantees that downstream consumers (e.g. loadSegments) never\n * see a partial set of SourceBuffers — preventing the Firefox bug where\n * appending to a video SourceBuffer before the audio SourceBuffer exists\n * causes mozHasAudio to be permanently false.\n *\n * Handles video-only, audio-only, and combined presentations correctly:\n * track types are derived from the presentation rather than hardcoded.\n *\n * @example\n * const cleanup = setupSourceBuffers({ state, owners });\n */\nexport function setupSourceBuffers<S extends SourceBufferState, O extends SourceBufferOwners>({\n state,\n owners,\n}: {\n state: Signal<S>;\n owners: Signal<O>;\n}): () => void {\n // Derive which media track types this presentation actually contains\n const presentationTypesSignal = computed((): MediaTrackType[] => {\n const { presentation } = state.get();\n if (!presentation || !('selectionSets' in presentation)) return [];\n return presentation.selectionSets\n .map(({ type }) => type)\n .filter((type): type is MediaTrackType => type === 'video' || type === 'audio');\n });\n\n // All presentation track types must have a selected, resolved track with codecs\n const canSetupSignal = computed(() => {\n const types = presentationTypesSignal.get();\n if (!owners.get().mediaSource || types.length === 0) return false;\n const s = state.get();\n return types.every((type) => {\n const track = getSelectedTrack(s, type);\n return track && isResolvedTrack(track) && !!track.codecs?.length;\n });\n });\n\n // DOM-observable \"already done\" guard — once all presentation buffers exist in owners, setup has run\n const shouldSetupSignal = computed(() => {\n const o = owners.get();\n return presentationTypesSignal.get().every((type) => !o[BufferKeyByType[type]]);\n });\n\n const cleanupEffect = effect(() => {\n if (!canSetupSignal.get() || !shouldSetupSignal.get()) return;\n\n const s = state.get();\n const o = owners.get();\n\n // Create all SourceBuffers synchronously — no await between addSourceBuffer\n // calls — then set owners once so subscribers see all buffers simultaneously.\n const patch: Partial<SourceBufferOwners> = {};\n\n for (const type of presentationTypesSignal.get()) {\n const track = getSelectedTrack(s, type) as ResolvedTrack;\n const buffer = createSourceBuffer(o.mediaSource!, buildMimeCodec(track));\n patch[BufferKeyByType[type]] = buffer;\n patch[ActorKeyByType[type]] = createSourceBufferActor(buffer);\n }\n\n update(owners, patch);\n });\n\n return cleanupEffect;\n}\n"],"mappings":";;;;;;;AAcA,MAAM,iBAAiB;CACrB,OAAO;CACP,OAAO;CACR;;;;;;;;;;;AAgCD,SAAgB,eAAe,OAA8B;CAC3D,MAAM,cAAc,MAAM,QAAQ,KAAK,IAAI,IAAI;AAC/C,QAAO,GAAG,MAAM,SAAS,YAAY,YAAY;;;;;;;;;;;;;;;;;;AAmBnD,SAAgB,mBAA8E,EAC5F,OACA,UAIa;CAEb,MAAM,0BAA0B,eAAiC;EAC/D,MAAM,EAAE,iBAAiB,MAAM,KAAK;AACpC,MAAI,CAAC,gBAAgB,EAAE,mBAAmB,cAAe,QAAO,EAAE;AAClE,SAAO,aAAa,cACjB,KAAK,EAAE,WAAW,KAAK,CACvB,QAAQ,SAAiC,SAAS,WAAW,SAAS,QAAQ;GACjF;CAGF,MAAM,iBAAiB,eAAe;EACpC,MAAM,QAAQ,wBAAwB,KAAK;AAC3C,MAAI,CAAC,OAAO,KAAK,CAAC,eAAe,MAAM,WAAW,EAAG,QAAO;EAC5D,MAAM,IAAI,MAAM,KAAK;AACrB,SAAO,MAAM,OAAO,SAAS;GAC3B,MAAM,QAAQ,iBAAiB,GAAG,KAAK;AACvC,UAAO,SAAS,gBAAgB,MAAM,IAAI,CAAC,CAAC,MAAM,QAAQ;IAC1D;GACF;CAGF,MAAM,oBAAoB,eAAe;EACvC,MAAM,IAAI,OAAO,KAAK;AACtB,SAAO,wBAAwB,KAAK,CAAC,OAAO,SAAS,CAAC,EAAE,gBAAgB,OAAO;GAC/E;AAsBF,QApBsB,aAAa;AACjC,MAAI,CAAC,eAAe,KAAK,IAAI,CAAC,kBAAkB,KAAK,CAAE;EAEvD,MAAM,IAAI,MAAM,KAAK;EACrB,MAAM,IAAI,OAAO,KAAK;EAItB,MAAM,QAAqC,EAAE;AAE7C,OAAK,MAAM,QAAQ,wBAAwB,KAAK,EAAE;GAChD,MAAM,QAAQ,iBAAiB,GAAG,KAAK;GACvC,MAAM,SAAS,mBAAmB,EAAE,aAAc,eAAe,MAAM,CAAC;AACxE,SAAM,gBAAgB,SAAS;AAC/B,SAAM,eAAe,SAAS,wBAAwB,OAAO;;AAG/D,SAAO,QAAQ,MAAM;GACrB"}
|
|
@@ -1,32 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
2
|
+
import { computed, update } from "../../core/signals/primitives.js";
|
|
3
3
|
//#region src/dom/features/setup-text-tracks.ts
|
|
4
4
|
/**
|
|
5
|
-
* Get all text tracks from presentation.
|
|
6
|
-
*/
|
|
7
|
-
function getTextTracks(presentation) {
|
|
8
|
-
if (!presentation?.selectionSets) return [];
|
|
9
|
-
const textSet = presentation.selectionSets.find((set) => set.type === "text");
|
|
10
|
-
if (!textSet?.switchingSets?.[0]?.tracks) return [];
|
|
11
|
-
return textSet.switchingSets[0].tracks;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Check if we can setup text tracks.
|
|
15
|
-
*
|
|
16
|
-
* Requires:
|
|
17
|
-
* - mediaElement exists
|
|
18
|
-
* - presentation has text tracks to setup
|
|
19
|
-
*/
|
|
20
|
-
function canSetupTextTracks(state, owners) {
|
|
21
|
-
return !!owners.mediaElement && !!getTextTracks(state.presentation).length;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Check if we should setup text tracks (not already set up).
|
|
25
|
-
*/
|
|
26
|
-
function shouldSetupTextTracks(owners) {
|
|
27
|
-
return !owners.textTracks;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
5
|
* Create a track element for a text track.
|
|
31
6
|
*
|
|
32
7
|
* Note: We use DOM <track> elements instead of the TextTrack JS API
|
|
@@ -59,30 +34,34 @@ function createTrackElement(track) {
|
|
|
59
34
|
* const cleanup = setupTextTracks({ state, owners });
|
|
60
35
|
*/
|
|
61
36
|
function setupTextTracks({ state, owners }) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
37
|
+
const modelTextTracksSignal = computed(() => state.get().presentation?.selectionSets?.find((selectionSet) => selectionSet.type === "text")?.switchingSets[0]?.tracks, { equals(prevTextTracks, nextTextTracks) {
|
|
38
|
+
if (prevTextTracks === nextTextTracks) return true;
|
|
39
|
+
if (typeof prevTextTracks !== typeof nextTextTracks) return false;
|
|
40
|
+
if (prevTextTracks?.length !== nextTextTracks?.length) return false;
|
|
41
|
+
return !!nextTextTracks && nextTextTracks.every((nextTextTrack) => prevTextTracks?.some((prevTextTrack) => prevTextTrack.id === nextTextTrack.id));
|
|
42
|
+
} });
|
|
43
|
+
const ownerTextTracksSignal = computed(() => owners.get().textTracks);
|
|
44
|
+
const mediaElementSignal = computed(() => owners.get().mediaElement);
|
|
45
|
+
const canSetupTextTracksSignal = computed(() => !!mediaElementSignal.get() && modelTextTracksSignal.get()?.length);
|
|
46
|
+
const shouldSetupTextTracksSignal = computed(() => !ownerTextTracksSignal.get());
|
|
47
|
+
const cleanupEffect = effect(() => {
|
|
48
|
+
if (!canSetupTextTracksSignal.get() || !shouldSetupTextTracksSignal.get()) return;
|
|
49
|
+
const mediaElement = mediaElementSignal.get();
|
|
50
|
+
const modelTextTracks = modelTextTracksSignal.get();
|
|
70
51
|
const trackMap = /* @__PURE__ */ new Map();
|
|
71
|
-
|
|
72
|
-
const trackElement = createTrackElement(
|
|
73
|
-
|
|
74
|
-
trackMap.set(
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
owners.patch({ textTracks: trackMap });
|
|
52
|
+
modelTextTracks.forEach((modelTextTrack) => {
|
|
53
|
+
const trackElement = createTrackElement(modelTextTrack);
|
|
54
|
+
mediaElement.appendChild(trackElement);
|
|
55
|
+
trackMap.set(modelTextTrack.id, trackElement);
|
|
56
|
+
});
|
|
57
|
+
if (trackMap.size) update(owners, { textTracks: trackMap });
|
|
78
58
|
});
|
|
79
59
|
return () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
unsubscribe();
|
|
60
|
+
owners.get().textTracks?.forEach((trackElement) => trackElement.remove());
|
|
61
|
+
cleanupEffect();
|
|
83
62
|
};
|
|
84
63
|
}
|
|
85
|
-
|
|
86
64
|
//#endregion
|
|
87
65
|
export { setupTextTracks };
|
|
66
|
+
|
|
88
67
|
//# sourceMappingURL=setup-text-tracks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-text-tracks.js","names":[],"sources":["../../../../src/dom/features/setup-text-tracks.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"setup-text-tracks.js","names":[],"sources":["../../../../src/dom/features/setup-text-tracks.ts"],"sourcesContent":["import { effect } from '../../core/signals/effect';\nimport { computed, type Signal, update } from '../../core/signals/primitives';\nimport type { PartiallyResolvedTextTrack, Presentation, TextTrack } from '../../core/types';\n\n/**\n * State shape for text track setup.\n */\nexport interface TextTrackState {\n presentation?: Presentation | undefined;\n selectedTextTrackId?: string | undefined;\n}\n\n/**\n * Owners shape for text track setup.\n */\nexport interface TextTrackOwners {\n mediaElement?: HTMLMediaElement | undefined;\n textTracks?: Map<string, HTMLTrackElement>;\n}\n\n/**\n * Create a track element for a text track.\n *\n * Note: We use DOM <track> elements instead of the TextTrack JS API\n * because there's no way to remove TextTracks added via addTextTrack().\n */\nfunction createTrackElement(track: PartiallyResolvedTextTrack | TextTrack): HTMLTrackElement {\n const trackElement = document.createElement('track');\n\n trackElement.id = track.id;\n trackElement.kind = track.kind;\n trackElement.label = track.label;\n\n if (track.language) {\n trackElement.srclang = track.language;\n }\n\n if (track.default) {\n trackElement.default = true;\n }\n\n // Set src to track URL\n trackElement.src = track.url;\n\n return trackElement;\n}\n\n/**\n * Setup text tracks orchestration.\n *\n * Triggers when:\n * - mediaElement exists\n * - presentation is resolved (has text tracks)\n *\n * Creates <track> elements for all text tracks and adds them as children\n * to the media element. This allows the browser's native text track rendering.\n *\n * Note: Uses DOM track elements instead of TextTrack API because tracks\n * added via addTextTrack() cannot be removed.\n *\n * @example\n * const cleanup = setupTextTracks({ state, owners });\n */\nexport function setupTextTracks<S extends TextTrackState, O extends TextTrackOwners>({\n state,\n owners,\n}: {\n state: Signal<S>;\n owners: Signal<O>;\n}): () => void {\n const modelTextTracksSignal = computed(\n // NOTE: This assumes exactly one selection set and switching set for TextTracks (CJP)\n () =>\n state.get().presentation?.selectionSets?.find((selectionSet) => selectionSet.type === 'text')?.switchingSets[0]\n ?.tracks,\n {\n /** @TODO Make generic and abstract away for Array<T> | undefined (CJP) */\n equals(prevTextTracks, nextTextTracks) {\n if (prevTextTracks === nextTextTracks) return true;\n if (typeof prevTextTracks !== typeof nextTextTracks) return false;\n if (prevTextTracks?.length !== nextTextTracks?.length) return false;\n // NOTE: This could probably be optimized, but the set should generally be small (CJP)\n return (\n !!nextTextTracks &&\n nextTextTracks.every((nextTextTrack) =>\n prevTextTracks?.some((prevTextTrack) => prevTextTrack.id === nextTextTrack.id)\n )\n );\n },\n }\n );\n const ownerTextTracksSignal = computed(() => owners.get().textTracks);\n const mediaElementSignal = computed(() => owners.get().mediaElement);\n\n const canSetupTextTracksSignal = computed(() => !!mediaElementSignal.get() && modelTextTracksSignal.get()?.length);\n const shouldSetupTextTracksSignal = computed(() => !ownerTextTracksSignal.get());\n\n const cleanupEffect = effect(() => {\n if (!canSetupTextTracksSignal.get() || !shouldSetupTextTracksSignal.get()) return;\n const mediaElement = mediaElementSignal.get();\n const modelTextTracks = modelTextTracksSignal.get() as TextTrack[];\n\n const trackMap = new Map<string, HTMLTrackElement>();\n modelTextTracks.forEach((modelTextTrack) => {\n const trackElement = createTrackElement(modelTextTrack);\n mediaElement!.appendChild(trackElement);\n trackMap.set(modelTextTrack.id, trackElement);\n });\n\n if (trackMap.size) {\n const patch: Partial<TextTrackOwners> = { textTracks: trackMap };\n update(owners, patch);\n }\n });\n\n return () => {\n owners.get().textTracks?.forEach((trackElement) => trackElement.remove());\n cleanupEffect();\n };\n}\n"],"mappings":";;;;;;;;;AA0BA,SAAS,mBAAmB,OAAiE;CAC3F,MAAM,eAAe,SAAS,cAAc,QAAQ;AAEpD,cAAa,KAAK,MAAM;AACxB,cAAa,OAAO,MAAM;AAC1B,cAAa,QAAQ,MAAM;AAE3B,KAAI,MAAM,SACR,cAAa,UAAU,MAAM;AAG/B,KAAI,MAAM,QACR,cAAa,UAAU;AAIzB,cAAa,MAAM,MAAM;AAEzB,QAAO;;;;;;;;;;;;;;;;;;AAmBT,SAAgB,gBAAqE,EACnF,OACA,UAIa;CACb,MAAM,wBAAwB,eAG1B,MAAM,KAAK,CAAC,cAAc,eAAe,MAAM,iBAAiB,aAAa,SAAS,OAAO,EAAE,cAAc,IACzG,QACN,EAEE,OAAO,gBAAgB,gBAAgB;AACrC,MAAI,mBAAmB,eAAgB,QAAO;AAC9C,MAAI,OAAO,mBAAmB,OAAO,eAAgB,QAAO;AAC5D,MAAI,gBAAgB,WAAW,gBAAgB,OAAQ,QAAO;AAE9D,SACE,CAAC,CAAC,kBACF,eAAe,OAAO,kBACpB,gBAAgB,MAAM,kBAAkB,cAAc,OAAO,cAAc,GAAG,CAC/E;IAGN,CACF;CACD,MAAM,wBAAwB,eAAe,OAAO,KAAK,CAAC,WAAW;CACrE,MAAM,qBAAqB,eAAe,OAAO,KAAK,CAAC,aAAa;CAEpE,MAAM,2BAA2B,eAAe,CAAC,CAAC,mBAAmB,KAAK,IAAI,sBAAsB,KAAK,EAAE,OAAO;CAClH,MAAM,8BAA8B,eAAe,CAAC,sBAAsB,KAAK,CAAC;CAEhF,MAAM,gBAAgB,aAAa;AACjC,MAAI,CAAC,yBAAyB,KAAK,IAAI,CAAC,4BAA4B,KAAK,CAAE;EAC3E,MAAM,eAAe,mBAAmB,KAAK;EAC7C,MAAM,kBAAkB,sBAAsB,KAAK;EAEnD,MAAM,2BAAW,IAAI,KAA+B;AACpD,kBAAgB,SAAS,mBAAmB;GAC1C,MAAM,eAAe,mBAAmB,eAAe;AACvD,gBAAc,YAAY,aAAa;AACvC,YAAS,IAAI,eAAe,IAAI,aAAa;IAC7C;AAEF,MAAI,SAAS,KAEX,QAAO,QADiC,EAAE,YAAY,UAAU,CAC3C;GAEvB;AAEF,cAAa;AACX,SAAO,KAAK,CAAC,YAAY,SAAS,iBAAiB,aAAa,QAAQ,CAAC;AACzE,iBAAe"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
2
|
+
import { computed, update } from "../../core/signals/primitives.js";
|
|
1
3
|
import { listen } from "@videojs/utils/dom";
|
|
2
|
-
|
|
3
4
|
//#region src/dom/features/sync-selected-text-track-from-dom.ts
|
|
4
5
|
/**
|
|
5
6
|
* Sync selectedTextTrackId from DOM text track mode changes.
|
|
@@ -24,40 +25,30 @@ import { listen } from "@videojs/utils/dom";
|
|
|
24
25
|
* const cleanup = syncSelectedTextTrackFromDom({ state, owners });
|
|
25
26
|
*/
|
|
26
27
|
function syncSelectedTextTrackFromDom({ state, owners }) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
lastMediaElement = mediaElement;
|
|
35
|
-
if (!mediaElement) return;
|
|
36
|
-
const sync = () => {
|
|
37
|
-
const newId = Array.from(mediaElement.textTracks).find((t) => t.mode === "showing" && (t.kind === "subtitles" || t.kind === "captions"))?.id || void 0;
|
|
38
|
-
const current = state.current;
|
|
28
|
+
const mediaElement = computed(() => owners.get().mediaElement);
|
|
29
|
+
return effect(() => {
|
|
30
|
+
const el = mediaElement.get();
|
|
31
|
+
if (!el) return;
|
|
32
|
+
return listen(el.textTracks, "change", () => {
|
|
33
|
+
const newId = Array.from(el.textTracks).find((t) => t.mode === "showing" && (t.kind === "subtitles" || t.kind === "captions"))?.id || void 0;
|
|
34
|
+
const current = state.get();
|
|
39
35
|
if (current.selectedTextTrackId === newId) return;
|
|
40
|
-
if (newId) state
|
|
36
|
+
if (newId) update(state, { selectedTextTrackId: newId });
|
|
41
37
|
else {
|
|
42
38
|
const prevId = current.selectedTextTrackId;
|
|
43
39
|
if (prevId && current.textBufferState?.[prevId]) {
|
|
44
40
|
const next = { ...current.textBufferState };
|
|
45
41
|
delete next[prevId];
|
|
46
|
-
state
|
|
42
|
+
update(state, {
|
|
47
43
|
selectedTextTrackId: void 0,
|
|
48
44
|
textBufferState: next
|
|
49
45
|
});
|
|
50
|
-
} else state
|
|
46
|
+
} else update(state, { selectedTextTrackId: void 0 });
|
|
51
47
|
}
|
|
52
|
-
};
|
|
53
|
-
removeListener = listen(mediaElement.textTracks, "change", sync);
|
|
48
|
+
});
|
|
54
49
|
});
|
|
55
|
-
return () => {
|
|
56
|
-
removeListener?.();
|
|
57
|
-
unsubscribe();
|
|
58
|
-
};
|
|
59
50
|
}
|
|
60
|
-
|
|
61
51
|
//#endregion
|
|
62
52
|
export { syncSelectedTextTrackFromDom };
|
|
53
|
+
|
|
63
54
|
//# sourceMappingURL=sync-selected-text-track-from-dom.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-selected-text-track-from-dom.js","names":[],"sources":["../../../../src/dom/features/sync-selected-text-track-from-dom.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport
|
|
1
|
+
{"version":3,"file":"sync-selected-text-track-from-dom.js","names":[],"sources":["../../../../src/dom/features/sync-selected-text-track-from-dom.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport { effect } from '../../core/signals/effect';\nimport { computed, type Signal, update } from '../../core/signals/primitives';\nimport type { TextTrackBufferState } from './load-text-track-cues';\n\n/**\n * State shape for DOM-driven text track selection.\n */\nexport interface SelectedTextTrackFromDomState {\n selectedTextTrackId?: string | undefined;\n textBufferState?: TextTrackBufferState | undefined;\n}\n\n/**\n * Owners shape for DOM-driven text track selection.\n */\nexport interface SelectedTextTrackFromDomOwners {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\n/**\n * Sync selectedTextTrackId from DOM text track mode changes.\n *\n * Listens to the `change` event on `media.textTracks` and updates\n * `selectedTextTrackId` when external code (e.g. the captions button via\n * `toggleSubtitles()`) changes a subtitle/caption track mode to 'showing'.\n *\n * This bridges the core store's `toggleSubtitles()` with SPF's reactive text\n * track pipeline (`syncTextTrackModes`, `loadTextTrackCues`). Without this\n * bridge, direct DOM mode changes would be immediately overridden by\n * `syncTextTrackModes` on the next SPF state update.\n *\n * When a subtitle/caption track's mode is 'showing', its DOM `id` — which\n * matches the SPF track ID set by `setupTextTracks` — is written to\n * `selectedTextTrackId`. When no subtitle/caption track is 'showing',\n * `selectedTextTrackId` is cleared along with the deselected track's\n * `textBufferState` entry — setting mode to 'disabled' clears native cues from\n * the track element, so the buffer must be reset to re-fetch cues on re-enable.\n *\n * @example\n * const cleanup = syncSelectedTextTrackFromDom({ state, owners });\n */\nexport function syncSelectedTextTrackFromDom<\n S extends SelectedTextTrackFromDomState,\n O extends SelectedTextTrackFromDomOwners,\n>({ state, owners }: { state: Signal<S>; owners: Signal<O> }): () => void {\n const mediaElement = computed(() => owners.get().mediaElement);\n\n return effect(() => {\n const el = mediaElement.get();\n if (!el) return;\n\n return listen(el.textTracks, 'change', () => {\n const showingTrack = Array.from(el.textTracks).find(\n (t) => t.mode === 'showing' && (t.kind === 'subtitles' || t.kind === 'captions')\n );\n\n // showingTrack.id is set from the SPF presentation track ID by setupTextTracks.\n // Fall back to undefined for empty-string IDs (non-SPF-managed tracks).\n const newId = showingTrack?.id || undefined;\n const current = state.get();\n\n // Guard against redundant writes — e.g. syncTextTrackModes confirming the\n // current selection, which would otherwise create a feedback loop.\n if (current.selectedTextTrackId === newId) return;\n\n if (newId) {\n const patch: Partial<SelectedTextTrackFromDomState> = { selectedTextTrackId: newId };\n update(state, patch);\n } else {\n // When deselecting, clear the textBufferState entry for the previous track.\n // Setting mode to 'disabled' (as toggleSubtitles() does) clears native cues\n // from the track element, so the buffer must be reset to allow re-fetching\n // on re-enable.\n const prevId = current.selectedTextTrackId;\n if (prevId && current.textBufferState?.[prevId]) {\n const next = { ...current.textBufferState };\n delete next[prevId];\n const patch: Partial<SelectedTextTrackFromDomState> = {\n selectedTextTrackId: undefined,\n textBufferState: next,\n };\n update(state, patch);\n } else {\n const patch: Partial<SelectedTextTrackFromDomState> = { selectedTextTrackId: undefined };\n update(state, patch);\n }\n }\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,6BAGd,EAAE,OAAO,UAA+D;CACxE,MAAM,eAAe,eAAe,OAAO,KAAK,CAAC,aAAa;AAE9D,QAAO,aAAa;EAClB,MAAM,KAAK,aAAa,KAAK;AAC7B,MAAI,CAAC,GAAI;AAET,SAAO,OAAO,GAAG,YAAY,gBAAgB;GAO3C,MAAM,QANe,MAAM,KAAK,GAAG,WAAW,CAAC,MAC5C,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,eAAe,EAAE,SAAS,YACtE,EAI2B,MAAM,KAAA;GAClC,MAAM,UAAU,MAAM,KAAK;AAI3B,OAAI,QAAQ,wBAAwB,MAAO;AAE3C,OAAI,MAEF,QAAO,OAD+C,EAAE,qBAAqB,OAAO,CAChE;QACf;IAKL,MAAM,SAAS,QAAQ;AACvB,QAAI,UAAU,QAAQ,kBAAkB,SAAS;KAC/C,MAAM,OAAO,EAAE,GAAG,QAAQ,iBAAiB;AAC3C,YAAO,KAAK;AAKZ,YAAO,OAJ+C;MACpD,qBAAqB,KAAA;MACrB,iBAAiB;MAClB,CACmB;UAGpB,QAAO,OAD+C,EAAE,qBAAqB,KAAA,GAAW,CACpE;;IAGxB;GACF"}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
2
|
+
import { computed } from "../../core/signals/primitives.js";
|
|
3
3
|
//#region src/dom/features/sync-text-track-modes.ts
|
|
4
4
|
/**
|
|
5
|
-
* Check if we can sync text track modes.
|
|
6
|
-
*
|
|
7
|
-
* Requires:
|
|
8
|
-
* - textTracks map exists (track elements created)
|
|
9
|
-
*/
|
|
10
|
-
function canSyncTextTrackModes(owners) {
|
|
11
|
-
return !!owners.textTracks && owners.textTracks.size > 0;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
5
|
* Sync text track modes orchestration.
|
|
15
6
|
*
|
|
16
7
|
* Manages track element modes based on selectedTextTrackId:
|
|
@@ -25,14 +16,18 @@ function canSyncTextTrackModes(owners) {
|
|
|
25
16
|
* const cleanup = syncTextTrackModes({ state, owners });
|
|
26
17
|
*/
|
|
27
18
|
function syncTextTrackModes({ state, owners }) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
const textTracksSignal = computed(() => owners.get().textTracks);
|
|
20
|
+
const selectedTextTrackIdSignal = computed(() => state.get().selectedTextTrackId);
|
|
21
|
+
const canSyncTextTrackModes = computed(() => !!textTracksSignal.get()?.size);
|
|
22
|
+
return effect(() => {
|
|
23
|
+
if (!canSyncTextTrackModes.get()) return;
|
|
24
|
+
/** @TODO refactor TextTracks owners model. Should simply use id. Also should use corresponding TextTrack (JS) element if possible (CJP) */
|
|
25
|
+
const textTracks = textTracksSignal.get();
|
|
26
|
+
const selectedTextTrackId = selectedTextTrackIdSignal.get();
|
|
27
|
+
for (const [trackId, trackElement] of textTracks) trackElement.track.mode = trackId === selectedTextTrackId ? "showing" : "hidden";
|
|
33
28
|
});
|
|
34
29
|
}
|
|
35
|
-
|
|
36
30
|
//#endregion
|
|
37
31
|
export { syncTextTrackModes };
|
|
32
|
+
|
|
38
33
|
//# sourceMappingURL=sync-text-track-modes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-text-track-modes.js","names":[],"sources":["../../../../src/dom/features/sync-text-track-modes.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"sync-text-track-modes.js","names":[],"sources":["../../../../src/dom/features/sync-text-track-modes.ts"],"sourcesContent":["import { effect } from '../../core/signals/effect';\nimport { computed, type Signal } from '../../core/signals/primitives';\n\n/**\n * State shape for text track mode synchronization.\n */\nexport interface TextTrackModeState {\n selectedTextTrackId?: string | undefined;\n}\n\n/**\n * Owners shape for text track mode synchronization.\n */\nexport interface TextTrackModeOwners {\n textTracks?: Map<string, HTMLTrackElement>;\n}\n\n/**\n * Sync text track modes orchestration.\n *\n * Manages track element modes based on selectedTextTrackId:\n * - Selected track: mode = \"showing\"\n * - Other tracks: mode = \"hidden\"\n * - No selection: all tracks mode = \"hidden\"\n *\n * Note: Uses \"hidden\" instead of \"disabled\" for non-selected tracks\n * so they remain available in the browser's track menu.\n *\n * @example\n * const cleanup = syncTextTrackModes({ state, owners });\n */\nexport function syncTextTrackModes<S extends TextTrackModeState, O extends TextTrackModeOwners>({\n state,\n owners,\n}: {\n state: Signal<S>;\n owners: Signal<O>;\n}): () => void {\n const textTracksSignal = computed(() => owners.get().textTracks);\n const selectedTextTrackIdSignal = computed(() => state.get().selectedTextTrackId);\n\n const canSyncTextTrackModes = computed(() => !!textTracksSignal.get()?.size);\n\n return effect(() => {\n if (!canSyncTextTrackModes.get()) return;\n /** @TODO refactor TextTracks owners model. Should simply use id. Also should use corresponding TextTrack (JS) element if possible (CJP) */\n const textTracks = textTracksSignal.get() as Map<string, HTMLTrackElement>;\n const selectedTextTrackId = selectedTextTrackIdSignal.get() as string;\n\n for (const [trackId, trackElement] of textTracks) {\n trackElement.track.mode = trackId === selectedTextTrackId ? 'showing' : 'hidden';\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA+BA,SAAgB,mBAAgF,EAC9F,OACA,UAIa;CACb,MAAM,mBAAmB,eAAe,OAAO,KAAK,CAAC,WAAW;CAChE,MAAM,4BAA4B,eAAe,MAAM,KAAK,CAAC,oBAAoB;CAEjF,MAAM,wBAAwB,eAAe,CAAC,CAAC,iBAAiB,KAAK,EAAE,KAAK;AAE5E,QAAO,aAAa;AAClB,MAAI,CAAC,sBAAsB,KAAK,CAAE;;EAElC,MAAM,aAAa,iBAAiB,KAAK;EACzC,MAAM,sBAAsB,0BAA0B,KAAK;AAE3D,OAAK,MAAM,CAAC,SAAS,iBAAiB,WACpC,cAAa,MAAM,OAAO,YAAY,sBAAsB,YAAY;GAE1E"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Signal } from "../../core/signals/primitives.js";
|
|
2
2
|
|
|
3
3
|
//#region src/dom/features/track-current-time.d.ts
|
|
4
4
|
/**
|
|
@@ -28,12 +28,12 @@ interface CurrentTimeOwners {
|
|
|
28
28
|
* @example
|
|
29
29
|
* const cleanup = trackCurrentTime({ state, owners });
|
|
30
30
|
*/
|
|
31
|
-
declare function trackCurrentTime({
|
|
31
|
+
declare function trackCurrentTime<S extends CurrentTimeState, O extends CurrentTimeOwners>({
|
|
32
32
|
state,
|
|
33
33
|
owners
|
|
34
34
|
}: {
|
|
35
|
-
state:
|
|
36
|
-
owners:
|
|
35
|
+
state: Signal<S>;
|
|
36
|
+
owners: Signal<O>;
|
|
37
37
|
}): () => void;
|
|
38
38
|
//#endregion
|
|
39
39
|
export { CurrentTimeOwners, CurrentTimeState, trackCurrentTime };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-current-time.d.ts","names":[],"sources":["../../../../src/dom/features/track-current-time.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"track-current-time.d.ts","names":[],"sources":["../../../../src/dom/features/track-current-time.ts"],"mappings":";;;;;AAOA;UAAiB,gBAAA;EACf,WAAA;AAAA;;AAMF;;UAAiB,iBAAA;EACf,YAAA,GAAe,gBAAA;AAAA;;;;;;;;;;;;;;;;iBA4BD,gBAAA,WAA2B,gBAAA,YAA4B,iBAAA,CAAA,CAAA;EACrE,KAAA;EACA;AAAA;EAEA,KAAA,EAAO,MAAA,CAAO,CAAA;EACd,MAAA,EAAQ,MAAA,CAAO,CAAA;AAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
2
|
+
import { update } from "../../core/signals/primitives.js";
|
|
1
3
|
import { listen } from "@videojs/utils/dom";
|
|
2
|
-
|
|
3
4
|
//#region src/dom/features/track-current-time.ts
|
|
4
5
|
/**
|
|
5
6
|
* Track current playback position from the media element.
|
|
@@ -19,15 +20,17 @@ import { listen } from "@videojs/utils/dom";
|
|
|
19
20
|
function trackCurrentTime({ state, owners }) {
|
|
20
21
|
let lastMediaElement;
|
|
21
22
|
let removeListeners = null;
|
|
22
|
-
const
|
|
23
|
-
const { mediaElement } =
|
|
23
|
+
const cleanupEffect = effect(() => {
|
|
24
|
+
const { mediaElement } = owners.get();
|
|
24
25
|
if (mediaElement === lastMediaElement) return;
|
|
25
26
|
removeListeners?.();
|
|
26
27
|
removeListeners = null;
|
|
27
28
|
lastMediaElement = mediaElement;
|
|
28
29
|
if (!mediaElement) return;
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const sync = () => {
|
|
31
|
+
update(state, { currentTime: mediaElement.currentTime });
|
|
32
|
+
};
|
|
33
|
+
sync();
|
|
31
34
|
const removeTimeupdate = listen(mediaElement, "timeupdate", sync);
|
|
32
35
|
const removeSeeking = listen(mediaElement, "seeking", sync);
|
|
33
36
|
removeListeners = () => {
|
|
@@ -37,10 +40,10 @@ function trackCurrentTime({ state, owners }) {
|
|
|
37
40
|
});
|
|
38
41
|
return () => {
|
|
39
42
|
removeListeners?.();
|
|
40
|
-
|
|
43
|
+
cleanupEffect();
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
|
-
|
|
44
46
|
//#endregion
|
|
45
47
|
export { trackCurrentTime };
|
|
48
|
+
|
|
46
49
|
//# sourceMappingURL=track-current-time.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-current-time.js","names":[],"sources":["../../../../src/dom/features/track-current-time.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport
|
|
1
|
+
{"version":3,"file":"track-current-time.js","names":[],"sources":["../../../../src/dom/features/track-current-time.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport { effect } from '../../core/signals/effect';\nimport { type Signal, update } from '../../core/signals/primitives';\n\n/**\n * State shape for current time tracking.\n */\nexport interface CurrentTimeState {\n currentTime?: number;\n}\n\n/**\n * Owners shape for current time tracking.\n */\nexport interface CurrentTimeOwners {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\n/**\n * Check if we can track current time.\n *\n * Requires:\n * - mediaElement exists in owners\n */\nexport function canTrackCurrentTime(owners: CurrentTimeOwners): boolean {\n return !!owners.mediaElement;\n}\n\n/**\n * Track current playback position from the media element.\n *\n * Mirrors `mediaElement.currentTime` into reactive state on:\n * - `timeupdate` — fires during playback (~4 Hz)\n * - `seeking` — fires when a seek begins; per spec, `currentTime` is\n * already at the new position when this event dispatches, so buffer\n * management can react immediately rather than waiting for `timeupdate`,\n * which does not fire while paused.\n *\n * Also syncs immediately when a media element becomes available.\n *\n * @example\n * const cleanup = trackCurrentTime({ state, owners });\n */\nexport function trackCurrentTime<S extends CurrentTimeState, O extends CurrentTimeOwners>({\n state,\n owners,\n}: {\n state: Signal<S>;\n owners: Signal<O>;\n}): () => void {\n let lastMediaElement: HTMLMediaElement | undefined;\n let removeListeners: (() => void) | null = null;\n\n const cleanupEffect = effect(() => {\n const { mediaElement } = owners.get();\n\n if (mediaElement === lastMediaElement) return;\n\n removeListeners?.();\n removeListeners = null;\n lastMediaElement = mediaElement;\n\n if (!mediaElement) return;\n\n const sync = () => {\n const patch: Partial<CurrentTimeState> = { currentTime: mediaElement.currentTime };\n update(state, patch);\n };\n\n // Sync immediately so consumers don't wait for the first event\n sync();\n const removeTimeupdate = listen(mediaElement, 'timeupdate', sync);\n const removeSeeking = listen(mediaElement, 'seeking', sync);\n removeListeners = () => {\n removeTimeupdate();\n removeSeeking();\n };\n });\n\n return () => {\n removeListeners?.();\n cleanupEffect();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,iBAA0E,EACxF,OACA,UAIa;CACb,IAAI;CACJ,IAAI,kBAAuC;CAE3C,MAAM,gBAAgB,aAAa;EACjC,MAAM,EAAE,iBAAiB,OAAO,KAAK;AAErC,MAAI,iBAAiB,iBAAkB;AAEvC,qBAAmB;AACnB,oBAAkB;AAClB,qBAAmB;AAEnB,MAAI,CAAC,aAAc;EAEnB,MAAM,aAAa;AAEjB,UAAO,OADkC,EAAE,aAAa,aAAa,aAAa,CAC9D;;AAItB,QAAM;EACN,MAAM,mBAAmB,OAAO,cAAc,cAAc,KAAK;EACjE,MAAM,gBAAgB,OAAO,cAAc,WAAW,KAAK;AAC3D,0BAAwB;AACtB,qBAAkB;AAClB,kBAAe;;GAEjB;AAEF,cAAa;AACX,qBAAmB;AACnB,iBAAe"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventStream } from "../../core/events/create-event-stream.js";
|
|
3
|
-
import { PresentationAction } from "../../core/features/resolve-presentation.js";
|
|
1
|
+
import { Signal } from "../../core/signals/primitives.js";
|
|
4
2
|
|
|
5
3
|
//#region src/dom/features/track-playback-initiated.d.ts
|
|
6
4
|
/**
|
|
7
5
|
* State shape for playback initiation tracking.
|
|
8
6
|
*/
|
|
9
7
|
interface PlaybackInitiatedState {
|
|
10
|
-
/** True once
|
|
8
|
+
/** True once play has been requested for the current presentation URL. */
|
|
11
9
|
playbackInitiated?: boolean;
|
|
12
|
-
/** Current presentation —
|
|
10
|
+
/** Current presentation — URL is used to detect source changes. */
|
|
13
11
|
presentation?: {
|
|
14
12
|
url?: string;
|
|
15
13
|
};
|
|
@@ -21,32 +19,25 @@ interface PlaybackInitiatedOwners {
|
|
|
21
19
|
mediaElement?: HTMLMediaElement | undefined;
|
|
22
20
|
}
|
|
23
21
|
/**
|
|
24
|
-
* Track whether playback has been initiated
|
|
22
|
+
* Track whether playback has been initiated for the current presentation URL.
|
|
25
23
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* can react.
|
|
24
|
+
* Uses a local intermediate signal written by two effect streams:
|
|
25
|
+
* - false stream: resets on URL change
|
|
26
|
+
* - true stream: sets on play event
|
|
30
27
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* This flag is used by `shouldLoadSegments` to allow segment loading after
|
|
36
|
-
* play is initiated regardless of the initial `preload` setting — `preload`
|
|
37
|
-
* is a startup hint, not a runtime gate.
|
|
28
|
+
* A third merge effect reads the local signal and writes to state, reading
|
|
29
|
+
* `state.get()` at merge time so the spread uses the up-to-date value after
|
|
30
|
+
* the async forward bridge has run.
|
|
38
31
|
*
|
|
39
32
|
* @example
|
|
40
33
|
* const cleanup = trackPlaybackInitiated({ state, owners, events });
|
|
41
34
|
*/
|
|
42
|
-
declare function trackPlaybackInitiated({
|
|
35
|
+
declare function trackPlaybackInitiated<S extends PlaybackInitiatedState, O extends PlaybackInitiatedOwners>({
|
|
43
36
|
state,
|
|
44
|
-
owners
|
|
45
|
-
events
|
|
37
|
+
owners
|
|
46
38
|
}: {
|
|
47
|
-
state:
|
|
48
|
-
owners:
|
|
49
|
-
events: EventStream<PresentationAction>;
|
|
39
|
+
state: Signal<S>;
|
|
40
|
+
owners: Signal<O>;
|
|
50
41
|
}): () => void;
|
|
51
42
|
//#endregion
|
|
52
43
|
export { PlaybackInitiatedOwners, PlaybackInitiatedState, trackPlaybackInitiated };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-playback-initiated.d.ts","names":[],"sources":["../../../../src/dom/features/track-playback-initiated.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"track-playback-initiated.d.ts","names":[],"sources":["../../../../src/dom/features/track-playback-initiated.ts"],"mappings":";;;;;AAOA;UAAiB,sBAAA;;EAEf,iBAAA;EAAA;EAEA,YAAA;IAAiB,GAAA;EAAA;AAAA;AAMnB;;;AAAA,UAAiB,uBAAA;EACf,YAAA,GAAe,gBAAA;AAAA;;;;;;;;;;;;;;;iBAiBD,sBAAA,WAAiC,sBAAA,YAAkC,uBAAA,CAAA,CAAA;EACjF,KAAA;EACA;AAAA;EAEA,KAAA,EAAO,MAAA,CAAO,CAAA;EACd,MAAA,EAAQ,MAAA,CAAO,CAAA;AAAA"}
|