@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,55 +1,53 @@
|
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
2
|
+
import { computed, signal, update } from "../../core/signals/primitives.js";
|
|
1
3
|
import { listen } from "@videojs/utils/dom";
|
|
2
|
-
|
|
3
4
|
//#region src/dom/features/track-playback-initiated.ts
|
|
4
5
|
/**
|
|
5
|
-
* Track whether playback has been initiated
|
|
6
|
-
*
|
|
7
|
-
* Sets `state.playbackInitiated = true` when the media element fires a `play`
|
|
8
|
-
* event (via `element.play()`, native controls, or autoplay) and simultaneously
|
|
9
|
-
* dispatches `{ type: 'play' }` to the event stream so `resolvePresentation`
|
|
10
|
-
* can react.
|
|
6
|
+
* Track whether playback has been initiated for the current presentation URL.
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Uses a local intermediate signal written by two effect streams:
|
|
9
|
+
* - false stream: resets on URL change
|
|
10
|
+
* - true stream: sets on play event
|
|
15
11
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* A third merge effect reads the local signal and writes to state, reading
|
|
13
|
+
* `state.get()` at merge time so the spread uses the up-to-date value after
|
|
14
|
+
* the async forward bridge has run.
|
|
19
15
|
*
|
|
20
16
|
* @example
|
|
21
17
|
* const cleanup = trackPlaybackInitiated({ state, owners, events });
|
|
22
18
|
*/
|
|
23
|
-
function trackPlaybackInitiated({ state, owners
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
function trackPlaybackInitiated({ state, owners }) {
|
|
20
|
+
const presentationUrl = computed(() => state.get().presentation?.url);
|
|
21
|
+
const mediaElement = computed(() => owners.get().mediaElement);
|
|
22
|
+
const playbackInitiated = signal(void 0);
|
|
26
23
|
let lastPresentationUrl;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
let lastMediaElement;
|
|
25
|
+
const cleanupResetEffect = effect(() => {
|
|
26
|
+
const url = presentationUrl.get();
|
|
27
|
+
const el = mediaElement.get();
|
|
28
|
+
if (url !== lastPresentationUrl && lastPresentationUrl !== void 0 || el !== lastMediaElement && lastMediaElement !== void 0) playbackInitiated.set(false);
|
|
29
|
+
lastPresentationUrl = url;
|
|
30
|
+
lastMediaElement = el;
|
|
33
31
|
});
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
lastMediaElement = mediaElement;
|
|
40
|
-
if (!mediaElement) return;
|
|
41
|
-
removeListener = listen(mediaElement, "play", () => {
|
|
42
|
-
state.patch({ playbackInitiated: true });
|
|
43
|
-
events.dispatch({ type: "play" });
|
|
32
|
+
const cleanupPlayEffect = effect(() => {
|
|
33
|
+
const el = mediaElement.get();
|
|
34
|
+
if (!el) return;
|
|
35
|
+
return listen(el, "play", () => {
|
|
36
|
+
playbackInitiated.set(true);
|
|
44
37
|
});
|
|
45
38
|
});
|
|
39
|
+
const cleanupMergeEffect = effect(() => {
|
|
40
|
+
const pi = playbackInitiated.get();
|
|
41
|
+
if (pi === void 0) return;
|
|
42
|
+
if (state.get().playbackInitiated !== pi) update(state, { playbackInitiated: pi });
|
|
43
|
+
});
|
|
46
44
|
return () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
cleanupResetEffect();
|
|
46
|
+
cleanupPlayEffect();
|
|
47
|
+
cleanupMergeEffect();
|
|
50
48
|
};
|
|
51
49
|
}
|
|
52
|
-
|
|
53
50
|
//#endregion
|
|
54
51
|
export { trackPlaybackInitiated };
|
|
52
|
+
|
|
55
53
|
//# sourceMappingURL=track-playback-initiated.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-playback-initiated.js","names":[],"sources":["../../../../src/dom/features/track-playback-initiated.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport
|
|
1
|
+
{"version":3,"file":"track-playback-initiated.js","names":[],"sources":["../../../../src/dom/features/track-playback-initiated.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport { effect } from '../../core/signals/effect';\nimport { computed, type Signal, signal, update } from '../../core/signals/primitives';\n\n/**\n * State shape for playback initiation tracking.\n */\nexport interface PlaybackInitiatedState {\n /** True once play has been requested for the current presentation URL. */\n playbackInitiated?: boolean;\n /** Current presentation — URL is used to detect source changes. */\n presentation?: { url?: string };\n}\n\n/**\n * Owners shape for playback initiation tracking.\n */\nexport interface PlaybackInitiatedOwners {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\n/**\n * Track whether playback has been initiated for the current presentation URL.\n *\n * Uses a local intermediate signal written by two effect streams:\n * - false stream: resets on URL change\n * - true stream: sets on play event\n *\n * A third merge effect reads the local signal and writes to state, reading\n * `state.get()` at merge time so the spread uses the up-to-date value after\n * the async forward bridge has run.\n *\n * @example\n * const cleanup = trackPlaybackInitiated({ state, owners, events });\n */\nexport function trackPlaybackInitiated<S extends PlaybackInitiatedState, O extends PlaybackInitiatedOwners>({\n state,\n owners,\n}: {\n state: Signal<S>;\n owners: Signal<O>;\n}): () => void {\n const presentationUrl = computed(() => state.get().presentation?.url);\n const mediaElement = computed(() => owners.get().mediaElement);\n\n // Local signal: written by the URL effect (false) and the play listener (true).\n // undefined = not yet initialized (suppresses the merge effect on startup).\n const playbackInitiated = signal<boolean | undefined>(undefined);\n\n let lastPresentationUrl: string | undefined;\n let lastMediaElement: HTMLMediaElement | undefined;\n\n // False stream: reset on URL change or element swap.\n const cleanupResetEffect = effect(() => {\n const url = presentationUrl.get();\n const el = mediaElement.get();\n\n const urlChanged = url !== lastPresentationUrl;\n const elChanged = el !== lastMediaElement;\n\n if ((urlChanged && lastPresentationUrl !== undefined) || (elChanged && lastMediaElement !== undefined)) {\n playbackInitiated.set(false);\n }\n\n lastPresentationUrl = url;\n lastMediaElement = el;\n });\n\n // True stream: set on play event. Cleanup return removes the listener on element swap.\n const cleanupPlayEffect = effect(() => {\n const el = mediaElement.get();\n if (!el) return;\n return listen(el, 'play', () => {\n playbackInitiated.set(true);\n });\n });\n\n // Merge effect: bridge local signal → state.\n // Reads state.get() at merge time (after the async forward bridge has flushed)\n // so the spread captures the current value rather than a stale event-time snapshot.\n // The guard makes the write idempotent.\n const cleanupMergeEffect = effect(() => {\n const pi = playbackInitiated.get();\n if (pi === undefined) return;\n const current = state.get();\n if (current.playbackInitiated !== pi) {\n const patch: Partial<PlaybackInitiatedState> = { playbackInitiated: pi };\n update(state, patch);\n }\n });\n\n return () => {\n cleanupResetEffect();\n cleanupPlayEffect();\n cleanupMergeEffect();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmCA,SAAgB,uBAA4F,EAC1G,OACA,UAIa;CACb,MAAM,kBAAkB,eAAe,MAAM,KAAK,CAAC,cAAc,IAAI;CACrE,MAAM,eAAe,eAAe,OAAO,KAAK,CAAC,aAAa;CAI9D,MAAM,oBAAoB,OAA4B,KAAA,EAAU;CAEhE,IAAI;CACJ,IAAI;CAGJ,MAAM,qBAAqB,aAAa;EACtC,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,KAAK,aAAa,KAAK;AAK7B,MAHmB,QAAQ,uBAGR,wBAAwB,KAAA,KAFzB,OAAO,oBAE8C,qBAAqB,KAAA,EAC1F,mBAAkB,IAAI,MAAM;AAG9B,wBAAsB;AACtB,qBAAmB;GACnB;CAGF,MAAM,oBAAoB,aAAa;EACrC,MAAM,KAAK,aAAa,KAAK;AAC7B,MAAI,CAAC,GAAI;AACT,SAAO,OAAO,IAAI,cAAc;AAC9B,qBAAkB,IAAI,KAAK;IAC3B;GACF;CAMF,MAAM,qBAAqB,aAAa;EACtC,MAAM,KAAK,kBAAkB,KAAK;AAClC,MAAI,OAAO,KAAA,EAAW;AAEtB,MADgB,MAAM,KAAK,CACf,sBAAsB,GAEhC,QAAO,OADwC,EAAE,mBAAmB,IAAI,CACpD;GAEtB;AAEF,cAAa;AACX,sBAAoB;AACpB,qBAAmB;AACnB,sBAAoB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Signal } from "../../core/signals/primitives.js";
|
|
2
2
|
|
|
3
3
|
//#region src/dom/features/track-playback-rate.d.ts
|
|
4
4
|
/**
|
|
@@ -22,12 +22,12 @@ interface PlaybackRateOwners {
|
|
|
22
22
|
* @example
|
|
23
23
|
* const cleanup = trackPlaybackRate({ state, owners });
|
|
24
24
|
*/
|
|
25
|
-
declare function trackPlaybackRate({
|
|
25
|
+
declare function trackPlaybackRate<O extends PlaybackRateOwners>({
|
|
26
26
|
state,
|
|
27
27
|
owners
|
|
28
28
|
}: {
|
|
29
|
-
state:
|
|
30
|
-
owners:
|
|
29
|
+
state: Signal<PlaybackRateState>;
|
|
30
|
+
owners: Signal<O>;
|
|
31
31
|
}): () => void;
|
|
32
32
|
//#endregion
|
|
33
33
|
export { PlaybackRateOwners, PlaybackRateState, trackPlaybackRate };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-playback-rate.d.ts","names":[],"sources":["../../../../src/dom/features/track-playback-rate.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"track-playback-rate.d.ts","names":[],"sources":["../../../../src/dom/features/track-playback-rate.ts"],"mappings":";;;;;AAOA;UAAiB,iBAAA;EACf,YAAA;AAAA;;AAMF;;UAAiB,kBAAA;EACf,YAAA,GAAe,gBAAA;AAAA;AAYjB;;;;;;;;;AAAA,iBAAgB,iBAAA,WAA4B,kBAAA,CAAA,CAAA;EAC1C,KAAA;EACA;AAAA;EAEA,KAAA,EAAO,MAAA,CAAO,iBAAA;EACd,MAAA,EAAQ,MAAA,CAAO,CAAA;AAAA"}
|
|
@@ -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/track-playback-rate.ts
|
|
4
5
|
/**
|
|
5
6
|
* Track playback rate from the media element.
|
|
@@ -11,26 +12,21 @@ import { listen } from "@videojs/utils/dom";
|
|
|
11
12
|
* const cleanup = trackPlaybackRate({ state, owners });
|
|
12
13
|
*/
|
|
13
14
|
function trackPlaybackRate({ state, owners }) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!mediaElement) return;
|
|
23
|
-
state.patch({ playbackRate: mediaElement.playbackRate });
|
|
24
|
-
removeListener = listen(mediaElement, "ratechange", () => {
|
|
25
|
-
state.patch({ playbackRate: mediaElement.playbackRate });
|
|
15
|
+
const mediaElementSignal = computed(() => owners.get().mediaElement);
|
|
16
|
+
const canTrackPlaybackRate = computed(() => !!mediaElementSignal.get());
|
|
17
|
+
const cleanupEffect = effect(() => {
|
|
18
|
+
if (!canTrackPlaybackRate.get()) return;
|
|
19
|
+
const mediaElement = mediaElementSignal.get();
|
|
20
|
+
update(state, { playbackRate: mediaElement.playbackRate });
|
|
21
|
+
return listen(mediaElement, "ratechange", () => {
|
|
22
|
+
update(state, { playbackRate: mediaElement.playbackRate });
|
|
26
23
|
});
|
|
27
24
|
});
|
|
28
25
|
return () => {
|
|
29
|
-
|
|
30
|
-
unsubscribe();
|
|
26
|
+
cleanupEffect();
|
|
31
27
|
};
|
|
32
28
|
}
|
|
33
|
-
|
|
34
29
|
//#endregion
|
|
35
30
|
export { trackPlaybackRate };
|
|
31
|
+
|
|
36
32
|
//# sourceMappingURL=track-playback-rate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-playback-rate.js","names":[],"sources":["../../../../src/dom/features/track-playback-rate.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport
|
|
1
|
+
{"version":3,"file":"track-playback-rate.js","names":[],"sources":["../../../../src/dom/features/track-playback-rate.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport { effect } from '../../core/signals/effect';\nimport { computed, type Signal, update } from '../../core/signals/primitives';\n\n/**\n * State shape for playback rate tracking.\n */\nexport interface PlaybackRateState {\n playbackRate?: number;\n}\n\n/**\n * Owners shape for playback rate tracking.\n */\nexport interface PlaybackRateOwners {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\n/**\n * Track playback rate from the media element.\n *\n * Mirrors `mediaElement.playbackRate` into reactive state on each `ratechange`\n * event. Also syncs immediately when a media element becomes available.\n *\n * @example\n * const cleanup = trackPlaybackRate({ state, owners });\n */\nexport function trackPlaybackRate<O extends PlaybackRateOwners>({\n state,\n owners,\n}: {\n state: Signal<PlaybackRateState>;\n owners: Signal<O>;\n}): () => void {\n const mediaElementSignal = computed(() => owners.get().mediaElement);\n const canTrackPlaybackRate = computed(() => !!mediaElementSignal.get());\n\n const cleanupEffect = effect(() => {\n if (!canTrackPlaybackRate.get()) return;\n const mediaElement = mediaElementSignal.get() as HTMLMediaElement;\n\n update(state, { playbackRate: mediaElement.playbackRate });\n\n return listen(mediaElement, 'ratechange', () => {\n update(state, { playbackRate: mediaElement.playbackRate });\n });\n });\n\n return () => {\n cleanupEffect();\n };\n}\n"],"mappings":";;;;;;;;;;;;;AA2BA,SAAgB,kBAAgD,EAC9D,OACA,UAIa;CACb,MAAM,qBAAqB,eAAe,OAAO,KAAK,CAAC,aAAa;CACpE,MAAM,uBAAuB,eAAe,CAAC,CAAC,mBAAmB,KAAK,CAAC;CAEvE,MAAM,gBAAgB,aAAa;AACjC,MAAI,CAAC,qBAAqB,KAAK,CAAE;EACjC,MAAM,eAAe,mBAAmB,KAAK;AAE7C,SAAO,OAAO,EAAE,cAAc,aAAa,cAAc,CAAC;AAE1D,SAAO,OAAO,cAAc,oBAAoB;AAC9C,UAAO,OAAO,EAAE,cAAc,aAAa,cAAc,CAAC;IAC1D;GACF;AAEF,cAAa;AACX,iBAAe"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
+
import { effect } from "../../core/signals/effect.js";
|
|
1
2
|
import { hasPresentationDuration } from "../../core/types/index.js";
|
|
2
|
-
import { combineLatest } from "../../core/reactive/combine-latest.js";
|
|
3
|
-
|
|
4
3
|
//#region src/dom/features/update-duration.ts
|
|
5
4
|
/**
|
|
6
5
|
* Check if we can update MediaSource duration (have required data).
|
|
@@ -30,7 +29,7 @@ function shouldUpdateDuration(state, owners) {
|
|
|
30
29
|
if (!canUpdateDuration(state, owners)) return false;
|
|
31
30
|
const { mediaSource } = owners;
|
|
32
31
|
const { presentation } = state;
|
|
33
|
-
if (mediaSource
|
|
32
|
+
if ((owners.mediaSourceReadyState?.get() ?? owners.mediaSource?.readyState) !== "open") return false;
|
|
34
33
|
const duration = presentation.duration;
|
|
35
34
|
if (!Number.isFinite(duration) || Number.isNaN(duration) || duration <= 0) return false;
|
|
36
35
|
return Number.isNaN(mediaSource.duration);
|
|
@@ -51,22 +50,29 @@ function waitForSourceBuffersReady(owners) {
|
|
|
51
50
|
*/
|
|
52
51
|
function updateDuration({ state, owners }) {
|
|
53
52
|
let destroyed = false;
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
let running = false;
|
|
54
|
+
const cleanupEffect = effect(() => {
|
|
55
|
+
const currentState = state.get();
|
|
56
|
+
const currentOwners = owners.get();
|
|
57
|
+
if (!shouldUpdateDuration(currentState, currentOwners) || running) return;
|
|
56
58
|
const { mediaSource } = currentOwners;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
running = true;
|
|
60
|
+
waitForSourceBuffersReady(currentOwners).then(() => {
|
|
61
|
+
if (destroyed || mediaSource.readyState !== "open") return;
|
|
62
|
+
let duration = currentState.presentation.duration;
|
|
63
|
+
const maxBufferedEnd = getMaxBufferedEnd(currentOwners);
|
|
64
|
+
if (maxBufferedEnd > duration) duration = maxBufferedEnd;
|
|
65
|
+
mediaSource.duration = duration;
|
|
66
|
+
}).finally(() => {
|
|
67
|
+
running = false;
|
|
68
|
+
});
|
|
63
69
|
});
|
|
64
70
|
return () => {
|
|
65
71
|
destroyed = true;
|
|
66
|
-
|
|
72
|
+
cleanupEffect();
|
|
67
73
|
};
|
|
68
74
|
}
|
|
69
|
-
|
|
70
75
|
//#endregion
|
|
71
76
|
export { updateDuration };
|
|
77
|
+
|
|
72
78
|
//# sourceMappingURL=update-duration.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-duration.js","names":[],"sources":["../../../../src/dom/features/update-duration.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"update-duration.js","names":[],"sources":["../../../../src/dom/features/update-duration.ts"],"sourcesContent":["import { effect } from '../../core/signals/effect';\nimport type { ReadonlySignal, Signal } from '../../core/signals/primitives';\nimport type { Presentation } from '../../core/types';\nimport { hasPresentationDuration } from '../../core/types';\n\nexport interface DurationUpdateState {\n presentation?: Presentation;\n}\n\nexport interface DurationUpdateOwners {\n mediaSource?: MediaSource;\n /** Reactive mirror of `mediaSource.readyState` — updated via DOM events. */\n mediaSourceReadyState?: ReadonlySignal<MediaSource['readyState']>;\n videoSourceBuffer?: SourceBuffer;\n audioSourceBuffer?: SourceBuffer;\n}\n\n/**\n * Check if we can update MediaSource duration (have required data).\n */\nexport function canUpdateDuration(state: DurationUpdateState, owners: DurationUpdateOwners): boolean {\n return !!(owners.mediaSource && state.presentation && hasPresentationDuration(state.presentation));\n}\n\n/**\n * Get the maximum buffered end time across all SourceBuffers.\n */\nexport function getMaxBufferedEnd(owners: DurationUpdateOwners): number {\n let maxEnd = 0;\n\n const buffers = [owners.videoSourceBuffer, owners.audioSourceBuffer].filter(\n (buf): buf is SourceBuffer => buf !== undefined\n );\n\n for (const buffer of buffers) {\n const { buffered } = buffer;\n if (buffered.length > 0) {\n const end = buffered.end(buffered.length - 1);\n if (end > maxEnd) {\n maxEnd = end;\n }\n }\n }\n\n return maxEnd;\n}\n\n/**\n * Check if we should update MediaSource duration (conditions met).\n */\nexport function shouldUpdateDuration(state: DurationUpdateState, owners: DurationUpdateOwners): boolean {\n if (!canUpdateDuration(state, owners)) return false;\n\n const { mediaSource } = owners;\n const { presentation } = state;\n\n // MediaSource must be open — use reactive readyState signal when available\n // so the effect re-evaluates when readyState changes from 'closed' to 'open'.\n const readyState = owners.mediaSourceReadyState?.get() ?? owners.mediaSource?.readyState;\n if (readyState !== 'open') return false;\n\n const duration = presentation!.duration!;\n\n // Validate duration: finite, positive, not NaN\n if (!Number.isFinite(duration) || Number.isNaN(duration) || duration <= 0) return false;\n\n // Only set duration on initial MediaSource setup, when it hasn't been set yet.\n // A freshly opened MediaSource has duration === NaN. Once set (either by this\n // task or by endOfStreamTask from buffered.end()), we leave it alone — attempting\n // to re-sync a slight drift between mediaSource.duration and presentation.duration\n // races with concurrent appendBuffer() calls from loadSegmentsTask.\n return Number.isNaN(mediaSource!.duration);\n}\n\n/**\n * Wait for all currently-updating SourceBuffers to finish.\n *\n * The MSE spec forbids setting MediaSource.duration while any attached\n * SourceBuffer has updating === true. This defers until all are idle.\n */\nfunction waitForSourceBuffersReady(owners: DurationUpdateOwners): Promise<void> {\n const updating = [owners.videoSourceBuffer, owners.audioSourceBuffer].filter(\n (buf): buf is SourceBuffer => buf?.updating === true\n );\n\n if (updating.length === 0) return Promise.resolve();\n\n return Promise.all(\n updating.map(\n (buf) => new Promise<void>((resolve) => buf.addEventListener('updateend', () => resolve(), { once: true }))\n )\n ).then(() => undefined);\n}\n\n/**\n * Update MediaSource duration when presentation duration becomes available.\n */\nexport function updateDuration<S extends DurationUpdateState, O extends DurationUpdateOwners>({\n state,\n owners,\n}: {\n state: Signal<S>;\n owners: Signal<O>;\n}): () => void {\n let destroyed = false;\n let running = false;\n\n const cleanupEffect = effect(() => {\n const currentState = state.get();\n const currentOwners = owners.get();\n\n if (!shouldUpdateDuration(currentState, currentOwners) || running) return;\n\n const { mediaSource } = currentOwners;\n running = true;\n\n // MSE spec: duration cannot be set while any SourceBuffer is updating.\n // Capture the snapshot; the async helper does not read signals.\n waitForSourceBuffersReady(currentOwners)\n .then(() => {\n // Re-check after async wait: destroyed, or readyState changed (e.g. endOfStream\n // already called endOfStream(), transitioning 'open' → 'ended').\n if (destroyed || mediaSource!.readyState !== 'open') return;\n\n let duration = currentState.presentation!.duration!;\n\n // MSE spec: duration cannot be less than any buffered range.\n // If buffered ranges exceed calculated duration, extend to match.\n const maxBufferedEnd = getMaxBufferedEnd(currentOwners);\n if (maxBufferedEnd > duration) {\n duration = maxBufferedEnd;\n }\n\n mediaSource!.duration = duration;\n })\n .finally(() => {\n running = false;\n });\n });\n\n return () => {\n destroyed = true;\n cleanupEffect();\n };\n}\n"],"mappings":";;;;;;AAoBA,SAAgB,kBAAkB,OAA4B,QAAuC;AACnG,QAAO,CAAC,EAAE,OAAO,eAAe,MAAM,gBAAgB,wBAAwB,MAAM,aAAa;;;;;AAMnG,SAAgB,kBAAkB,QAAsC;CACtE,IAAI,SAAS;CAEb,MAAM,UAAU,CAAC,OAAO,mBAAmB,OAAO,kBAAkB,CAAC,QAClE,QAA6B,QAAQ,KAAA,EACvC;AAED,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,EAAE,aAAa;AACrB,MAAI,SAAS,SAAS,GAAG;GACvB,MAAM,MAAM,SAAS,IAAI,SAAS,SAAS,EAAE;AAC7C,OAAI,MAAM,OACR,UAAS;;;AAKf,QAAO;;;;;AAMT,SAAgB,qBAAqB,OAA4B,QAAuC;AACtG,KAAI,CAAC,kBAAkB,OAAO,OAAO,CAAE,QAAO;CAE9C,MAAM,EAAE,gBAAgB;CACxB,MAAM,EAAE,iBAAiB;AAKzB,MADmB,OAAO,uBAAuB,KAAK,IAAI,OAAO,aAAa,gBAC3D,OAAQ,QAAO;CAElC,MAAM,WAAW,aAAc;AAG/B,KAAI,CAAC,OAAO,SAAS,SAAS,IAAI,OAAO,MAAM,SAAS,IAAI,YAAY,EAAG,QAAO;AAOlF,QAAO,OAAO,MAAM,YAAa,SAAS;;;;;;;;AAS5C,SAAS,0BAA0B,QAA6C;CAC9E,MAAM,WAAW,CAAC,OAAO,mBAAmB,OAAO,kBAAkB,CAAC,QACnE,QAA6B,KAAK,aAAa,KACjD;AAED,KAAI,SAAS,WAAW,EAAG,QAAO,QAAQ,SAAS;AAEnD,QAAO,QAAQ,IACb,SAAS,KACN,QAAQ,IAAI,SAAe,YAAY,IAAI,iBAAiB,mBAAmB,SAAS,EAAE,EAAE,MAAM,MAAM,CAAC,CAAC,CAC5G,CACF,CAAC,WAAW,KAAA,EAAU;;;;;AAMzB,SAAgB,eAA8E,EAC5F,OACA,UAIa;CACb,IAAI,YAAY;CAChB,IAAI,UAAU;CAEd,MAAM,gBAAgB,aAAa;EACjC,MAAM,eAAe,MAAM,KAAK;EAChC,MAAM,gBAAgB,OAAO,KAAK;AAElC,MAAI,CAAC,qBAAqB,cAAc,cAAc,IAAI,QAAS;EAEnE,MAAM,EAAE,gBAAgB;AACxB,YAAU;AAIV,4BAA0B,cAAc,CACrC,WAAW;AAGV,OAAI,aAAa,YAAa,eAAe,OAAQ;GAErD,IAAI,WAAW,aAAa,aAAc;GAI1C,MAAM,iBAAiB,kBAAkB,cAAc;AACvD,OAAI,iBAAiB,SACnB,YAAW;AAGb,eAAa,WAAW;IACxB,CACD,cAAc;AACb,aAAU;IACV;GACJ;AAEF,cAAa;AACX,cAAY;AACZ,iBAAe"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { signal } from "../../core/signals/primitives.js";
|
|
1
2
|
//#region src/dom/media/mediasource-setup.ts
|
|
2
3
|
/**
|
|
3
4
|
* MediaSource Setup
|
|
@@ -50,7 +51,6 @@ function createMediaSource(options = {}) {
|
|
|
50
51
|
* @example
|
|
51
52
|
* const mediaSource = createMediaSource();
|
|
52
53
|
* const { detach } = attachMediaSource(mediaSource, videoElement);
|
|
53
|
-
* await waitForSourceOpen(mediaSource);
|
|
54
54
|
* // Use mediaSource...
|
|
55
55
|
* // Later, to clean up:
|
|
56
56
|
* detach();
|
|
@@ -81,42 +81,6 @@ function attachMediaSource(mediaSource, mediaElement) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
|
-
* Wait for a MediaSource to reach the 'open' state.
|
|
85
|
-
* Resolves immediately if already open.
|
|
86
|
-
*
|
|
87
|
-
* @param mediaSource - The MediaSource to wait for
|
|
88
|
-
* @param signal - Optional AbortSignal for cancellation
|
|
89
|
-
* @returns Promise that resolves when the MediaSource is open
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* const mediaSource = createMediaSource();
|
|
93
|
-
* attachMediaSource(mediaSource, videoElement);
|
|
94
|
-
* await waitForSourceOpen(mediaSource);
|
|
95
|
-
* // MediaSource is now ready for SourceBuffer creation
|
|
96
|
-
*/
|
|
97
|
-
function waitForSourceOpen(mediaSource, signal) {
|
|
98
|
-
return new Promise((resolve, reject) => {
|
|
99
|
-
if (mediaSource.readyState === "open") {
|
|
100
|
-
resolve();
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (signal?.aborted) {
|
|
104
|
-
reject(new DOMException("Aborted", "AbortError"));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const controller = new AbortController();
|
|
108
|
-
const options = { signal: controller.signal };
|
|
109
|
-
mediaSource.addEventListener("sourceopen", () => {
|
|
110
|
-
controller.abort();
|
|
111
|
-
resolve();
|
|
112
|
-
}, options);
|
|
113
|
-
signal?.addEventListener("abort", () => {
|
|
114
|
-
controller.abort();
|
|
115
|
-
reject(new DOMException("Aborted", "AbortError"));
|
|
116
|
-
}, options);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
84
|
* Create a SourceBuffer on a MediaSource.
|
|
121
85
|
*
|
|
122
86
|
* @param mediaSource - The MediaSource (must be in 'open' state)
|
|
@@ -125,7 +89,6 @@ function waitForSourceOpen(mediaSource, signal) {
|
|
|
125
89
|
* @throws Error if MediaSource is not open or codec is unsupported
|
|
126
90
|
*
|
|
127
91
|
* @example
|
|
128
|
-
* await waitForSourceOpen(mediaSource);
|
|
129
92
|
* const buffer = createSourceBuffer(mediaSource, 'video/mp4; codecs="avc1.42E01E"');
|
|
130
93
|
*/
|
|
131
94
|
function createSourceBuffer(mediaSource, mimeCodec) {
|
|
@@ -148,7 +111,36 @@ function isCodecSupported(mimeCodec) {
|
|
|
148
111
|
if (!supportsMediaSource()) return false;
|
|
149
112
|
return MediaSource.isTypeSupported(mimeCodec);
|
|
150
113
|
}
|
|
151
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Create a reactive signal that mirrors `mediaSource.readyState`.
|
|
116
|
+
*
|
|
117
|
+
* Listens to `sourceopen`, `sourceended`, and `sourceclose` events and updates
|
|
118
|
+
* the signal accordingly, making readyState visible to the TC39 signal graph.
|
|
119
|
+
* Listeners are automatically removed when `signal` is aborted.
|
|
120
|
+
*
|
|
121
|
+
* @param mediaSource - The MediaSource to observe
|
|
122
|
+
* @param signal - AbortSignal that controls listener lifetime
|
|
123
|
+
* @returns A `Signal.ReadonlyState` that reflects the current `readyState`
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const controller = new AbortController();
|
|
127
|
+
* const readyState = observeMediaSourceReadyState(mediaSource, controller.signal);
|
|
128
|
+
* effect(() => {
|
|
129
|
+
* if (readyState.get() === 'open') { ... }
|
|
130
|
+
* });
|
|
131
|
+
* // Later:
|
|
132
|
+
* controller.abort();
|
|
133
|
+
*/
|
|
134
|
+
function observeMediaSourceReadyState(mediaSource, abortSignal) {
|
|
135
|
+
const readyState = signal(mediaSource.readyState);
|
|
136
|
+
const update = () => readyState.set(mediaSource.readyState);
|
|
137
|
+
const options = { signal: abortSignal };
|
|
138
|
+
mediaSource.addEventListener("sourceopen", update, options);
|
|
139
|
+
mediaSource.addEventListener("sourceended", update, options);
|
|
140
|
+
mediaSource.addEventListener("sourceclose", update, options);
|
|
141
|
+
return readyState;
|
|
142
|
+
}
|
|
152
143
|
//#endregion
|
|
153
|
-
export { attachMediaSource, createMediaSource, createSourceBuffer,
|
|
144
|
+
export { attachMediaSource, createMediaSource, createSourceBuffer, observeMediaSourceReadyState };
|
|
145
|
+
|
|
154
146
|
//# sourceMappingURL=mediasource-setup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mediasource-setup.js","names":[],"sources":["../../../../src/dom/media/mediasource-setup.ts"],"sourcesContent":["/**\n * MediaSource Setup\n *\n * Utilities for creating and configuring MediaSource/ManagedMediaSource\n * for MSE (Media Source Extensions) playback.\n *\n * Global ManagedMediaSource types are defined in ./mediasource.d.ts\n */\n\n/**\n * Check if MediaSource API is supported.\n */\nexport function supportsMediaSource(): boolean {\n return typeof MediaSource !== 'undefined';\n}\n\n/**\n * Check if ManagedMediaSource API is supported.\n * ManagedMediaSource is a newer Safari API with better lifecycle management.\n */\nexport function supportsManagedMediaSource(): boolean {\n return typeof ManagedMediaSource !== 'undefined';\n}\n\n/**\n * Options for creating a MediaSource.\n */\nexport interface CreateMediaSourceOptions {\n /** Prefer ManagedMediaSource when available (default: false for broader compatibility). */\n preferManaged?: boolean;\n}\n\n/**\n * Create a MediaSource or ManagedMediaSource instance.\n *\n * @param options - Creation options\n * @returns A MediaSource or ManagedMediaSource instance\n * @throws Error if no MediaSource API is available\n *\n * @example\n * const mediaSource = createMediaSource();\n * const mediaElement = document.querySelector('video');\n * attachMediaSource(mediaSource, mediaElement);\n */\nexport function createMediaSource(options: CreateMediaSourceOptions = {}): MediaSource {\n const { preferManaged = false } = options;\n\n if (preferManaged && supportsManagedMediaSource()) {\n return new ManagedMediaSource!();\n }\n\n if (supportsMediaSource()) {\n return new MediaSource();\n }\n\n throw new Error('MediaSource API is not supported');\n}\n\n/**\n * Result of attaching a MediaSource to a media element.\n */\nexport interface AttachMediaSourceResult {\n /** The object URL created for the MediaSource (empty string for ManagedMediaSource). */\n url: string;\n /** Detach the MediaSource and clean up resources. */\n detach: () => void;\n}\n\n/**\n * Attach a MediaSource to an HTMLMediaElement.\n *\n * Uses srcObject for ManagedMediaSource (Safari), or createObjectURL for regular MediaSource.\n *\n * @param mediaSource - The MediaSource to attach\n * @param mediaElement - The media element to attach to\n * @returns Object with URL and detach function\n *\n * @example\n * const mediaSource = createMediaSource();\n * const { detach } = attachMediaSource(mediaSource, videoElement);\n *
|
|
1
|
+
{"version":3,"file":"mediasource-setup.js","names":[],"sources":["../../../../src/dom/media/mediasource-setup.ts"],"sourcesContent":["/**\n * MediaSource Setup\n *\n * Utilities for creating and configuring MediaSource/ManagedMediaSource\n * for MSE (Media Source Extensions) playback.\n *\n * Global ManagedMediaSource types are defined in ./mediasource.d.ts\n */\n\nimport { type ReadonlySignal, signal } from '../../core/signals/primitives';\n\n/**\n * Check if MediaSource API is supported.\n */\nexport function supportsMediaSource(): boolean {\n return typeof MediaSource !== 'undefined';\n}\n\n/**\n * Check if ManagedMediaSource API is supported.\n * ManagedMediaSource is a newer Safari API with better lifecycle management.\n */\nexport function supportsManagedMediaSource(): boolean {\n return typeof ManagedMediaSource !== 'undefined';\n}\n\n/**\n * Options for creating a MediaSource.\n */\nexport interface CreateMediaSourceOptions {\n /** Prefer ManagedMediaSource when available (default: false for broader compatibility). */\n preferManaged?: boolean;\n}\n\n/**\n * Create a MediaSource or ManagedMediaSource instance.\n *\n * @param options - Creation options\n * @returns A MediaSource or ManagedMediaSource instance\n * @throws Error if no MediaSource API is available\n *\n * @example\n * const mediaSource = createMediaSource();\n * const mediaElement = document.querySelector('video');\n * attachMediaSource(mediaSource, mediaElement);\n */\nexport function createMediaSource(options: CreateMediaSourceOptions = {}): MediaSource {\n const { preferManaged = false } = options;\n\n if (preferManaged && supportsManagedMediaSource()) {\n return new ManagedMediaSource!();\n }\n\n if (supportsMediaSource()) {\n return new MediaSource();\n }\n\n throw new Error('MediaSource API is not supported');\n}\n\n/**\n * Result of attaching a MediaSource to a media element.\n */\nexport interface AttachMediaSourceResult {\n /** The object URL created for the MediaSource (empty string for ManagedMediaSource). */\n url: string;\n /** Detach the MediaSource and clean up resources. */\n detach: () => void;\n}\n\n/**\n * Attach a MediaSource to an HTMLMediaElement.\n *\n * Uses srcObject for ManagedMediaSource (Safari), or createObjectURL for regular MediaSource.\n *\n * @param mediaSource - The MediaSource to attach\n * @param mediaElement - The media element to attach to\n * @returns Object with URL and detach function\n *\n * @example\n * const mediaSource = createMediaSource();\n * const { detach } = attachMediaSource(mediaSource, videoElement);\n * // Use mediaSource...\n * // Later, to clean up:\n * detach();\n */\nexport function attachMediaSource(mediaSource: MediaSource, mediaElement: HTMLMediaElement): AttachMediaSourceResult {\n // ManagedMediaSource requires srcObject instead of createObjectURL\n const isManagedMediaSource = supportsManagedMediaSource() && mediaSource instanceof ManagedMediaSource!;\n\n if (isManagedMediaSource) {\n // ManagedMediaSource requires disableRemotePlayback — without it Safari\n // will not fire sourceopen.\n (mediaElement as HTMLMediaElement & { disableRemotePlayback: boolean }).disableRemotePlayback = true;\n\n // Use srcObject for ManagedMediaSource\n (mediaElement as HTMLMediaElement & { srcObject: MediaSource | null }).srcObject = mediaSource;\n\n const detach = (): void => {\n (mediaElement as HTMLMediaElement & { srcObject: MediaSource | null }).srcObject = null;\n mediaElement.load(); // Reset the element\n };\n\n return { url: '', detach };\n }\n\n // Use createObjectURL for regular MediaSource\n const url = URL.createObjectURL(mediaSource);\n mediaElement.src = url;\n\n const detach = (): void => {\n mediaElement.removeAttribute('src');\n mediaElement.load(); // Reset the element\n URL.revokeObjectURL(url);\n };\n\n return { url, detach };\n}\n\n/**\n * Create a SourceBuffer on a MediaSource.\n *\n * @param mediaSource - The MediaSource (must be in 'open' state)\n * @param mimeCodec - MIME type with codecs (e.g., 'video/mp4; codecs=\"avc1.42E01E\"')\n * @returns The created SourceBuffer\n * @throws Error if MediaSource is not open or codec is unsupported\n *\n * @example\n * const buffer = createSourceBuffer(mediaSource, 'video/mp4; codecs=\"avc1.42E01E\"');\n */\nexport function createSourceBuffer(mediaSource: MediaSource, mimeCodec: string): SourceBuffer {\n if (mediaSource.readyState !== 'open') {\n throw new Error('MediaSource is not open');\n }\n\n if (!isCodecSupported(mimeCodec)) {\n throw new Error(`Codec not supported: ${mimeCodec}`);\n }\n\n return mediaSource.addSourceBuffer(mimeCodec);\n}\n\n/**\n * Check if a codec is supported.\n *\n * @param mimeCodec - MIME type with codecs string\n * @returns True if the codec is supported\n *\n * @example\n * if (isCodecSupported('video/mp4; codecs=\"avc1.42E01E\"')) {\n * // Create source buffer\n * }\n */\nexport function isCodecSupported(mimeCodec: string): boolean {\n if (!supportsMediaSource()) {\n return false;\n }\n\n return MediaSource.isTypeSupported(mimeCodec);\n}\n\n/**\n * Create a reactive signal that mirrors `mediaSource.readyState`.\n *\n * Listens to `sourceopen`, `sourceended`, and `sourceclose` events and updates\n * the signal accordingly, making readyState visible to the TC39 signal graph.\n * Listeners are automatically removed when `signal` is aborted.\n *\n * @param mediaSource - The MediaSource to observe\n * @param signal - AbortSignal that controls listener lifetime\n * @returns A `Signal.ReadonlyState` that reflects the current `readyState`\n *\n * @example\n * const controller = new AbortController();\n * const readyState = observeMediaSourceReadyState(mediaSource, controller.signal);\n * effect(() => {\n * if (readyState.get() === 'open') { ... }\n * });\n * // Later:\n * controller.abort();\n */\nexport function observeMediaSourceReadyState(\n mediaSource: MediaSource,\n abortSignal: AbortSignal\n): ReadonlySignal<MediaSource['readyState']> {\n const readyState = signal<MediaSource['readyState']>(mediaSource.readyState);\n const update = () => readyState.set(mediaSource.readyState);\n const options = { signal: abortSignal };\n mediaSource.addEventListener('sourceopen', update, options);\n mediaSource.addEventListener('sourceended', update, options);\n mediaSource.addEventListener('sourceclose', update, options);\n return readyState;\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAgB,sBAA+B;AAC7C,QAAO,OAAO,gBAAgB;;;;;;AAOhC,SAAgB,6BAAsC;AACpD,QAAO,OAAO,uBAAuB;;;;;;;;;;;;;;AAuBvC,SAAgB,kBAAkB,UAAoC,EAAE,EAAe;CACrF,MAAM,EAAE,gBAAgB,UAAU;AAElC,KAAI,iBAAiB,4BAA4B,CAC/C,QAAO,IAAI,oBAAqB;AAGlC,KAAI,qBAAqB,CACvB,QAAO,IAAI,aAAa;AAG1B,OAAM,IAAI,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;AA6BrD,SAAgB,kBAAkB,aAA0B,cAAyD;AAInH,KAF6B,4BAA4B,IAAI,uBAAuB,oBAE1D;AAGvB,eAAuE,wBAAwB;AAG/F,eAAsE,YAAY;EAEnF,MAAM,eAAqB;AACxB,gBAAsE,YAAY;AACnF,gBAAa,MAAM;;AAGrB,SAAO;GAAE,KAAK;GAAI;GAAQ;;CAI5B,MAAM,MAAM,IAAI,gBAAgB,YAAY;AAC5C,cAAa,MAAM;CAEnB,MAAM,eAAqB;AACzB,eAAa,gBAAgB,MAAM;AACnC,eAAa,MAAM;AACnB,MAAI,gBAAgB,IAAI;;AAG1B,QAAO;EAAE;EAAK;EAAQ;;;;;;;;;;;;;AAcxB,SAAgB,mBAAmB,aAA0B,WAAiC;AAC5F,KAAI,YAAY,eAAe,OAC7B,OAAM,IAAI,MAAM,0BAA0B;AAG5C,KAAI,CAAC,iBAAiB,UAAU,CAC9B,OAAM,IAAI,MAAM,wBAAwB,YAAY;AAGtD,QAAO,YAAY,gBAAgB,UAAU;;;;;;;;;;;;;AAc/C,SAAgB,iBAAiB,WAA4B;AAC3D,KAAI,CAAC,qBAAqB,CACxB,QAAO;AAGT,QAAO,YAAY,gBAAgB,UAAU;;;;;;;;;;;;;;;;;;;;;;AAuB/C,SAAgB,6BACd,aACA,aAC2C;CAC3C,MAAM,aAAa,OAAkC,YAAY,WAAW;CAC5E,MAAM,eAAe,WAAW,IAAI,YAAY,WAAW;CAC3D,MAAM,UAAU,EAAE,QAAQ,aAAa;AACvC,aAAY,iBAAiB,cAAc,QAAQ,QAAQ;AAC3D,aAAY,iBAAiB,eAAe,QAAQ,QAAQ;AAC5D,aAAY,iBAAiB,eAAe,QAAQ,QAAQ;AAC5D,QAAO"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Segment, Track } from "../../core/types/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { SignalActor } from "../../core/actor.js";
|
|
3
3
|
import { AppendData } from "./append-segment.js";
|
|
4
4
|
|
|
5
5
|
//#region src/dom/media/source-buffer-actor.d.ts
|
|
@@ -47,7 +47,7 @@ interface SourceBufferActorContext {
|
|
|
47
47
|
bufferedRanges: BufferedRange[];
|
|
48
48
|
}
|
|
49
49
|
/** SourceBuffer actor: queues operations, owns its snapshot. */
|
|
50
|
-
interface SourceBufferActor extends
|
|
50
|
+
interface SourceBufferActor extends SignalActor<SourceBufferActorStatus, SourceBufferActorContext> {
|
|
51
51
|
send(message: SourceBufferMessage, signal: AbortSignal): Promise<void>;
|
|
52
52
|
batch(messages: SourceBufferMessage[], signal: AbortSignal): Promise<void>;
|
|
53
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-buffer-actor.d.ts","names":[],"sources":["../../../../src/dom/media/source-buffer-actor.ts"],"mappings":";;;;;UAWiB,aAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,IAAA,CAAK,OAAA;EACnC,OAAA,EAAS,KAAA,QAJN;EAMH,cAAA;AAAA;AAAA,KAKU,iBAAA;EAAsB,IAAA;EAAqB,IAAA,EAAM,UAAA;EAAY,IAAA;IAAQ,OAAA,EAAS,KAAA;EAAA;AAAA;AAAA,KAC9E,oBAAA;EAAyB,IAAA;EAAwB,IAAA,EAAM,UAAA;EAAY,IAAA,EAAM,iBAAA;AAAA;AAAA,KACzE,aAAA;EAAkB,IAAA;EAAgB,KAAA;EAAe,GAAA;AAAA;AAAA,KACjD,mBAAA,GAAsB,iBAAA,GAAoB,oBAAA,GAAuB,aAAA;;KAGjE,uBAAA;;UAGK,wBAAA;EACf,WAAA;EACA,QAAA,EAAU,KAAA,CACR,IAAA,CAAK,OAAA;IACH,OAAA,EAAS,KAAA;IACT,cAAA;IAb6D;;;;;IAmB7D,OAAA;EAAA;EAGJ,cAAA,EAAgB,aAAA;AAAA;AApBlB;AAAA,UAsCiB,iBAAA,SAA0B,
|
|
1
|
+
{"version":3,"file":"source-buffer-actor.d.ts","names":[],"sources":["../../../../src/dom/media/source-buffer-actor.ts"],"mappings":";;;;;UAWiB,aAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,IAAA,CAAK,OAAA;EACnC,OAAA,EAAS,KAAA,QAJN;EAMH,cAAA;AAAA;AAAA,KAKU,iBAAA;EAAsB,IAAA;EAAqB,IAAA,EAAM,UAAA;EAAY,IAAA;IAAQ,OAAA,EAAS,KAAA;EAAA;AAAA;AAAA,KAC9E,oBAAA;EAAyB,IAAA;EAAwB,IAAA,EAAM,UAAA;EAAY,IAAA,EAAM,iBAAA;AAAA;AAAA,KACzE,aAAA;EAAkB,IAAA;EAAgB,KAAA;EAAe,GAAA;AAAA;AAAA,KACjD,mBAAA,GAAsB,iBAAA,GAAoB,oBAAA,GAAuB,aAAA;;KAGjE,uBAAA;;UAGK,wBAAA;EACf,WAAA;EACA,QAAA,EAAU,KAAA,CACR,IAAA,CAAK,OAAA;IACH,OAAA,EAAS,KAAA;IACT,cAAA;IAb6D;;;;;IAmB7D,OAAA;EAAA;EAGJ,cAAA,EAAgB,aAAA;AAAA;AApBlB;AAAA,UAsCiB,iBAAA,SAA0B,WAAA,CAAY,uBAAA,EAAyB,wBAAA;EAC9E,IAAA,CAAK,OAAA,EAAS,mBAAA,EAAqB,MAAA,EAAQ,WAAA,GAAc,OAAA;EACzD,KAAA,CAAM,QAAA,EAAU,mBAAA,IAAuB,MAAA,EAAQ,WAAA,GAAc,OAAA;AAAA"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { signal, update } from "../../core/signals/primitives.js";
|
|
2
2
|
import { appendSegment } from "./append-segment.js";
|
|
3
3
|
import { flushBuffer } from "./buffer-flusher.js";
|
|
4
4
|
import { SerialRunner, Task } from "../../core/task.js";
|
|
5
|
-
|
|
6
5
|
//#region src/dom/media/source-buffer-actor.ts
|
|
7
6
|
/**
|
|
8
7
|
* Thrown when a message is sent to the actor in a state that does not
|
|
@@ -93,7 +92,7 @@ function messageToTask(message, options) {
|
|
|
93
92
|
return factory(message, options);
|
|
94
93
|
}
|
|
95
94
|
function createSourceBufferActor(sourceBuffer, initialContext) {
|
|
96
|
-
const
|
|
95
|
+
const snapshotSignal = signal({
|
|
97
96
|
status: "idle",
|
|
98
97
|
context: {
|
|
99
98
|
segments: [],
|
|
@@ -104,55 +103,47 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
|
|
|
104
103
|
});
|
|
105
104
|
const runner = new SerialRunner();
|
|
106
105
|
function applyResult(newContext) {
|
|
107
|
-
const status =
|
|
108
|
-
|
|
106
|
+
const status = snapshotSignal.get().status === "destroyed" ? "destroyed" : "idle";
|
|
107
|
+
snapshotSignal.set({
|
|
109
108
|
status,
|
|
110
109
|
context: newContext
|
|
111
110
|
});
|
|
112
|
-
state.flush();
|
|
113
111
|
}
|
|
114
112
|
function handleError(e) {
|
|
115
|
-
|
|
116
|
-
state.patch({ status });
|
|
117
|
-
state.flush();
|
|
113
|
+
update(snapshotSignal, { status: snapshotSignal.get().status === "destroyed" ? "destroyed" : "idle" });
|
|
118
114
|
throw e;
|
|
119
115
|
}
|
|
120
116
|
return {
|
|
121
117
|
get snapshot() {
|
|
122
|
-
return
|
|
123
|
-
},
|
|
124
|
-
subscribe(listener) {
|
|
125
|
-
return state.subscribe(listener);
|
|
118
|
+
return snapshotSignal;
|
|
126
119
|
},
|
|
127
120
|
send(message, signal) {
|
|
128
|
-
if (
|
|
129
|
-
|
|
121
|
+
if (snapshotSignal.get().status !== "idle") return Promise.reject(new SourceBufferActorError(`send() called while actor is ${snapshotSignal.get().status}`));
|
|
122
|
+
update(snapshotSignal, { status: "updating" });
|
|
130
123
|
const onPartialContext = (ctx) => {
|
|
131
|
-
|
|
124
|
+
snapshotSignal.set({
|
|
132
125
|
status: "updating",
|
|
133
126
|
context: ctx
|
|
134
127
|
});
|
|
135
|
-
state.flush();
|
|
136
128
|
};
|
|
137
129
|
const task = messageToTask(message, {
|
|
138
130
|
signal,
|
|
139
|
-
getCtx: () =>
|
|
131
|
+
getCtx: () => snapshotSignal.get().context,
|
|
140
132
|
sourceBuffer,
|
|
141
133
|
onPartialContext
|
|
142
134
|
});
|
|
143
135
|
return runner.schedule(task).then(applyResult).catch(handleError);
|
|
144
136
|
},
|
|
145
137
|
batch(messages, signal) {
|
|
146
|
-
if (
|
|
138
|
+
if (snapshotSignal.get().status !== "idle") return Promise.reject(new SourceBufferActorError(`batch() called while actor is ${snapshotSignal.get().status}`));
|
|
147
139
|
if (messages.length === 0) return Promise.resolve();
|
|
148
|
-
|
|
149
|
-
let workingCtx =
|
|
140
|
+
update(snapshotSignal, { status: "updating" });
|
|
141
|
+
let workingCtx = snapshotSignal.get().context;
|
|
150
142
|
const onPartialContext = (ctx) => {
|
|
151
|
-
|
|
143
|
+
snapshotSignal.set({
|
|
152
144
|
status: "updating",
|
|
153
145
|
context: ctx
|
|
154
146
|
});
|
|
155
|
-
state.flush();
|
|
156
147
|
};
|
|
157
148
|
for (const message of messages.slice(0, -1)) {
|
|
158
149
|
const task = messageToTask(message, {
|
|
@@ -174,13 +165,12 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
|
|
|
174
165
|
return runner.schedule(lastTask).then(applyResult).catch(handleError);
|
|
175
166
|
},
|
|
176
167
|
destroy() {
|
|
177
|
-
|
|
178
|
-
state.flush();
|
|
168
|
+
update(snapshotSignal, { status: "destroyed" });
|
|
179
169
|
runner.destroy();
|
|
180
170
|
}
|
|
181
171
|
};
|
|
182
172
|
}
|
|
183
|
-
|
|
184
173
|
//#endregion
|
|
185
174
|
export { createSourceBufferActor };
|
|
175
|
+
|
|
186
176
|
//# sourceMappingURL=source-buffer-actor.js.map
|