@videojs/spf 10.0.0-beta.23 → 10.0.0-beta.25
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/background-video.js +3 -0
- package/dist/{dev/core → default/core/actors}/create-machine-actor.js +3 -3
- package/dist/default/core/actors/create-machine-actor.js.map +1 -0
- package/dist/default/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
- package/dist/default/core/actors/create-transition-actor.js.map +1 -0
- package/dist/default/core/composition/create-composition.js +85 -0
- package/dist/default/core/composition/create-composition.js.map +1 -0
- package/dist/default/core/composition/share-signals.js +39 -0
- package/dist/default/core/composition/share-signals.js.map +1 -0
- package/dist/{dev/core → default/core/reactors}/create-machine-reactor.js +4 -4
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/default/core/signals/primitives.js +33 -11
- package/dist/default/core/signals/primitives.js.map +1 -1
- package/dist/default/core/{task.js → tasks/task.js} +2 -2
- package/dist/default/core/tasks/task.js.map +1 -0
- package/dist/default/dom.js +9 -10
- package/dist/default/hls.js +5 -0
- package/dist/default/index.js +12 -3
- package/dist/default/index.js.map +1 -1
- package/dist/default/media/abr/quality-selection.js +21 -0
- package/dist/default/media/abr/quality-selection.js.map +1 -0
- package/dist/default/{core → media}/buffer/back-buffer.js +2 -2
- package/dist/default/media/buffer/back-buffer.js.map +1 -0
- package/dist/{dev/core → default/media}/buffer/forward-buffer.js +14 -2
- package/dist/default/media/buffer/forward-buffer.js.map +1 -0
- package/dist/default/media/dom/capabilities.js +70 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/{dev/dom/media → default/media/dom/mse}/append-segment.js +1 -1
- package/dist/default/media/dom/mse/append-segment.js.map +1 -0
- package/dist/default/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/default/media/dom/mse/duration.js +78 -0
- package/dist/default/media/dom/mse/duration.js.map +1 -0
- package/dist/default/media/dom/mse/end-of-stream.js +20 -0
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/default/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/default/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/default/media/dom/text/text-track-slots.js +69 -0
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/{dev/core → default/media}/hls/parse-attributes.js +17 -3
- package/dist/default/media/hls/parse-attributes.js.map +1 -0
- package/dist/{dev/core → default/media}/hls/parse-media-playlist.js +25 -2
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/{dev/core → default/media}/hls/parse-multivariant.js +17 -8
- package/dist/default/media/hls/parse-multivariant.js.map +1 -0
- package/dist/default/{core → media}/hls/resolve-url.js +1 -1
- package/dist/default/media/hls/resolve-url.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js +130 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -0
- package/dist/default/media/types/index.js +36 -0
- package/dist/default/media/types/index.js.map +1 -0
- package/dist/default/media/utils/cdn.js +56 -0
- package/dist/default/media/utils/cdn.js.map +1 -0
- package/dist/default/media/utils/preload.js +22 -0
- package/dist/default/media/utils/preload.js.map +1 -0
- package/dist/default/media/utils/track-selection.js +47 -0
- package/dist/default/media/utils/track-selection.js.map +1 -0
- package/dist/default/media/utils/tracks.js +130 -0
- package/dist/default/media/utils/tracks.js.map +1 -0
- package/dist/default/{core/abr → network}/bandwidth-estimator.js +6 -5
- package/dist/default/network/bandwidth-estimator.js.map +1 -0
- package/dist/{dev/dom → default}/network/chunked-stream-iterable.js +1 -1
- package/dist/default/network/chunked-stream-iterable.js.map +1 -0
- package/dist/{dev/core/abr → default/network}/ewma.js +1 -1
- package/dist/default/network/ewma.js.map +1 -0
- package/dist/default/network/fetch.js +115 -0
- package/dist/default/network/fetch.js.map +1 -0
- package/dist/default/{dom/features/segment-loader-actor.js → playback/actors/dom/segment-loader.js} +63 -23
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/default/{dom/media/source-buffer-actor.js → playback/actors/dom/source-buffer.js} +10 -9
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/{dev/dom/features/text-tracks-actor.js → default/playback/actors/dom/text-tracks.js} +7 -3
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/default/playback/actors/text-track-segment-loader.js +135 -0
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js +90 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/default/playback/behaviors/dom/load-segments.js +177 -0
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +192 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +89 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +54 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +90 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-track.js +120 -0
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/default/playback/behaviors/select-tracks.js +101 -0
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/default/playback/behaviors/sync-preload.js +59 -0
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/default/playback/behaviors/track-switching.js +341 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- package/dist/default/playback/engines/background-video/adapter.js +158 -0
- package/dist/default/playback/engines/background-video/adapter.js.map +1 -0
- package/dist/default/playback/engines/background-video/engine.js +75 -0
- package/dist/default/playback/engines/background-video/engine.js.map +1 -0
- package/dist/default/playback/engines/hls/adapter-audio-only.js +119 -0
- package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -0
- package/dist/default/{dom/playback-engine → playback/engines/hls}/adapter.js +40 -26
- package/dist/default/playback/engines/hls/adapter.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js +80 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +115 -0
- package/dist/default/playback/engines/hls/engine.js.map +1 -0
- package/dist/default/playback/primitives/failover-fetch.js +42 -0
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/default/playback/primitives/track-types.js +61 -0
- package/dist/default/playback/primitives/track-types.js.map +1 -0
- package/dist/dev/background-video.d.ts +3 -0
- package/dist/dev/background-video.js +3 -0
- package/dist/dev/core/{actor.d.ts → actors/actor.d.ts} +2 -2
- package/dist/dev/core/actors/actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts +125 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
- package/dist/{default/core → dev/core/actors}/create-machine-actor.js +3 -3
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts +42 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
- package/dist/dev/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
- package/dist/dev/core/composition/create-composition.d.ts +274 -0
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
- package/dist/dev/core/composition/create-composition.js +85 -0
- package/dist/dev/core/composition/create-composition.js.map +1 -0
- package/dist/dev/core/composition/share-signals.d.ts +47 -0
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
- package/dist/dev/core/composition/share-signals.js +39 -0
- package/dist/dev/core/composition/share-signals.js.map +1 -0
- package/dist/dev/core/machine.d.ts +0 -1
- package/dist/dev/core/machine.d.ts.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +93 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
- package/dist/{default/core → dev/core/reactors}/create-machine-reactor.js +4 -4
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +38 -1
- package/dist/dev/core/signals/primitives.d.ts.map +1 -1
- package/dist/dev/core/signals/primitives.js +33 -11
- package/dist/dev/core/signals/primitives.js.map +1 -1
- package/dist/dev/core/tasks/task.d.ts +115 -0
- package/dist/dev/core/tasks/task.d.ts.map +1 -0
- package/dist/dev/core/{task.js → tasks/task.js} +2 -2
- package/dist/dev/core/tasks/task.js.map +1 -0
- package/dist/dev/dom.d.ts +9 -10
- package/dist/dev/dom.js +9 -10
- package/dist/dev/hls.d.ts +5 -0
- package/dist/dev/hls.js +5 -0
- package/dist/dev/index.d.ts +15 -3
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +12 -3
- package/dist/dev/index.js.map +1 -1
- package/dist/dev/media/abr/quality-selection.d.ts +22 -0
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
- package/dist/dev/media/abr/quality-selection.js +21 -0
- package/dist/dev/media/abr/quality-selection.js.map +1 -0
- package/dist/dev/media/buffer/back-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
- package/dist/dev/{core → media}/buffer/back-buffer.js +2 -2
- package/dist/dev/media/buffer/back-buffer.js.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
- package/dist/{default/core → dev/media}/buffer/forward-buffer.js +14 -2
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
- package/dist/dev/media/dom/capabilities.js +70 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/append-segment.d.ts +1 -1
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
- package/dist/{default/dom/media → dev/media/dom/mse}/append-segment.js +1 -1
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.d.ts +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/dev/media/dom/mse/duration.js +78 -0
- package/dist/dev/media/dom/mse/duration.js.map +1 -0
- package/dist/dev/media/dom/mse/end-of-stream.js +20 -0
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +12 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
- package/dist/dev/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts +31 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.js +69 -0
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/{default/core → dev/media}/hls/parse-attributes.js +17 -3
- package/dist/dev/media/hls/parse-attributes.js.map +1 -0
- package/dist/{default/core → dev/media}/hls/parse-media-playlist.js +25 -2
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/{default/core → dev/media}/hls/parse-multivariant.js +17 -8
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
- package/dist/dev/{core → media}/hls/resolve-url.js +1 -1
- package/dist/dev/media/hls/resolve-url.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.d.ts +36 -0
- package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +130 -0
- package/dist/dev/media/primitives/select-tracks.js.map +1 -0
- package/dist/dev/{core → media}/types/index.d.ts +55 -3
- package/dist/dev/media/types/index.d.ts.map +1 -0
- package/dist/dev/media/types/index.js +36 -0
- package/dist/dev/media/types/index.js.map +1 -0
- package/dist/dev/media/utils/cdn.d.ts +13 -0
- package/dist/dev/media/utils/cdn.d.ts.map +1 -0
- package/dist/dev/media/utils/cdn.js +56 -0
- package/dist/dev/media/utils/cdn.js.map +1 -0
- package/dist/dev/media/utils/preload.js +22 -0
- package/dist/dev/media/utils/preload.js.map +1 -0
- package/dist/dev/media/utils/track-selection.js +47 -0
- package/dist/dev/media/utils/track-selection.js.map +1 -0
- package/dist/dev/media/utils/tracks.js +130 -0
- package/dist/dev/media/utils/tracks.js.map +1 -0
- package/dist/dev/{core/abr → network}/bandwidth-estimator.d.ts +17 -2
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
- package/dist/dev/{core/abr → network}/bandwidth-estimator.js +6 -5
- package/dist/dev/network/bandwidth-estimator.js.map +1 -0
- package/dist/{default/dom → dev}/network/chunked-stream-iterable.js +1 -1
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
- package/dist/{default/core/abr → dev/network}/ewma.js +1 -1
- package/dist/dev/network/ewma.js.map +1 -0
- package/dist/dev/network/fetch.js +115 -0
- package/dist/dev/network/fetch.js.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +36 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
- package/dist/dev/{dom/features/segment-loader-actor.js → playback/actors/dom/segment-loader.js} +63 -23
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/dev/{dom/media/source-buffer-actor.d.ts → playback/actors/dom/source-buffer.d.ts} +20 -5
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
- package/dist/dev/{dom/media/source-buffer-actor.js → playback/actors/dom/source-buffer.js} +10 -9
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +1 -0
- package/dist/{default/dom/features/text-tracks-actor.js → dev/playback/actors/dom/text-tracks.js} +7 -3
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +59 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js +135 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/text-tracks.d.ts +42 -0
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +25 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +90 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +62 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js +177 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +192 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +89 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +31 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +54 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +34 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +48 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +34 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +90 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +7 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-track.js +120 -0
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts +13 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +101 -0
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +12 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/dev/playback/behaviors/sync-preload.js +59 -0
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.js +341 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/background-video/adapter.d.ts +60 -0
- package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/engines/background-video/adapter.js +158 -0
- package/dist/dev/playback/engines/background-video/adapter.js.map +1 -0
- package/dist/dev/playback/engines/background-video/engine.d.ts +107 -0
- package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/background-video/engine.js +75 -0
- package/dist/dev/playback/engines/background-video/engine.js.map +1 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +45 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.js +119 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -0
- package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.d.ts +13 -12
- package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -0
- package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.js +40 -26
- package/dist/dev/playback/engines/hls/adapter.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +127 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js +80 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +236 -0
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine.js +115 -0
- package/dist/dev/playback/engines/hls/engine.js.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js +42 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/dev/playback/primitives/track-types.js +61 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +12 -7
- package/dist/default/core/abr/bandwidth-estimator.js.map +0 -1
- package/dist/default/core/abr/ewma.js.map +0 -1
- package/dist/default/core/abr/quality-selection.js +0 -53
- package/dist/default/core/abr/quality-selection.js.map +0 -1
- package/dist/default/core/buffer/back-buffer.js.map +0 -1
- package/dist/default/core/buffer/forward-buffer.js.map +0 -1
- package/dist/default/core/create-machine-actor.js.map +0 -1
- package/dist/default/core/create-machine-reactor.js.map +0 -1
- package/dist/default/core/create-transition-actor.js.map +0 -1
- package/dist/default/core/features/calculate-presentation-duration.js +0 -53
- package/dist/default/core/features/calculate-presentation-duration.js.map +0 -1
- package/dist/default/core/features/quality-switching.js +0 -69
- package/dist/default/core/features/quality-switching.js.map +0 -1
- package/dist/default/core/features/resolve-presentation.js +0 -73
- package/dist/default/core/features/resolve-presentation.js.map +0 -1
- package/dist/default/core/features/resolve-track.js +0 -66
- package/dist/default/core/features/resolve-track.js.map +0 -1
- package/dist/default/core/features/select-tracks.js +0 -130
- package/dist/default/core/features/select-tracks.js.map +0 -1
- package/dist/default/core/features/sync-preload-attribute.js +0 -26
- package/dist/default/core/features/sync-preload-attribute.js.map +0 -1
- package/dist/default/core/hls/parse-attributes.js.map +0 -1
- package/dist/default/core/hls/parse-media-playlist.js.map +0 -1
- package/dist/default/core/hls/parse-multivariant.js.map +0 -1
- package/dist/default/core/hls/resolve-url.js.map +0 -1
- package/dist/default/core/task.js.map +0 -1
- package/dist/default/core/types/index.js +0 -15
- package/dist/default/core/types/index.js.map +0 -1
- package/dist/default/core/utils/generate-id.js +0 -21
- package/dist/default/core/utils/generate-id.js.map +0 -1
- package/dist/default/core/utils/track-selection.js +0 -39
- package/dist/default/core/utils/track-selection.js.map +0 -1
- package/dist/default/dom/features/end-of-stream.js +0 -145
- package/dist/default/dom/features/end-of-stream.js.map +0 -1
- package/dist/default/dom/features/load-segments.js +0 -192
- package/dist/default/dom/features/load-segments.js.map +0 -1
- package/dist/default/dom/features/load-text-track-cues.js +0 -104
- package/dist/default/dom/features/load-text-track-cues.js.map +0 -1
- package/dist/default/dom/features/segment-loader-actor.js.map +0 -1
- package/dist/default/dom/features/setup-mediasource.js +0 -48
- package/dist/default/dom/features/setup-mediasource.js.map +0 -1
- package/dist/default/dom/features/setup-sourcebuffer.js +0 -78
- package/dist/default/dom/features/setup-sourcebuffer.js.map +0 -1
- package/dist/default/dom/features/sync-text-tracks.js +0 -95
- package/dist/default/dom/features/sync-text-tracks.js.map +0 -1
- package/dist/default/dom/features/text-track-segment-loader-actor.js +0 -55
- package/dist/default/dom/features/text-track-segment-loader-actor.js.map +0 -1
- package/dist/default/dom/features/text-tracks-actor.js.map +0 -1
- package/dist/default/dom/features/track-current-time.js +0 -49
- package/dist/default/dom/features/track-current-time.js.map +0 -1
- package/dist/default/dom/features/track-playback-initiated.js +0 -66
- package/dist/default/dom/features/track-playback-initiated.js.map +0 -1
- package/dist/default/dom/features/track-playback-rate.js +0 -32
- package/dist/default/dom/features/track-playback-rate.js.map +0 -1
- package/dist/default/dom/features/update-duration.js +0 -78
- package/dist/default/dom/features/update-duration.js.map +0 -1
- package/dist/default/dom/media/append-segment.js.map +0 -1
- package/dist/default/dom/media/buffer-flusher.js.map +0 -1
- package/dist/default/dom/media/mediasource-setup.js.map +0 -1
- package/dist/default/dom/media/source-buffer-actor.js.map +0 -1
- package/dist/default/dom/network/chunked-stream-iterable.js.map +0 -1
- package/dist/default/dom/network/fetch.js +0 -54
- package/dist/default/dom/network/fetch.js.map +0 -1
- package/dist/default/dom/playback-engine/adapter.js.map +0 -1
- package/dist/default/dom/playback-engine/engine.js +0 -142
- package/dist/default/dom/playback-engine/engine.js.map +0 -1
- package/dist/default/dom/playback-engine.js +0 -4
- package/dist/default/dom/text/parse-vtt-segment.js.map +0 -1
- package/dist/dev/core/abr/bandwidth-estimator.d.ts.map +0 -1
- package/dist/dev/core/abr/bandwidth-estimator.js.map +0 -1
- package/dist/dev/core/abr/ewma.js.map +0 -1
- package/dist/dev/core/abr/quality-selection.js +0 -53
- package/dist/dev/core/abr/quality-selection.js.map +0 -1
- package/dist/dev/core/actor.d.ts.map +0 -1
- package/dist/dev/core/buffer/back-buffer.js.map +0 -1
- package/dist/dev/core/buffer/forward-buffer.js.map +0 -1
- package/dist/dev/core/create-machine-actor.d.ts +0 -12
- package/dist/dev/core/create-machine-actor.d.ts.map +0 -1
- package/dist/dev/core/create-machine-actor.js.map +0 -1
- package/dist/dev/core/create-machine-reactor.d.ts +0 -8
- package/dist/dev/core/create-machine-reactor.d.ts.map +0 -1
- package/dist/dev/core/create-machine-reactor.js.map +0 -1
- package/dist/dev/core/create-transition-actor.d.ts +0 -23
- package/dist/dev/core/create-transition-actor.d.ts.map +0 -1
- package/dist/dev/core/create-transition-actor.js.map +0 -1
- package/dist/dev/core/features/calculate-presentation-duration.js +0 -53
- package/dist/dev/core/features/calculate-presentation-duration.js.map +0 -1
- package/dist/dev/core/features/quality-switching.js +0 -69
- package/dist/dev/core/features/quality-switching.js.map +0 -1
- package/dist/dev/core/features/resolve-presentation.js +0 -73
- package/dist/dev/core/features/resolve-presentation.js.map +0 -1
- package/dist/dev/core/features/resolve-track.js +0 -66
- package/dist/dev/core/features/resolve-track.js.map +0 -1
- package/dist/dev/core/features/select-tracks.js +0 -130
- package/dist/dev/core/features/select-tracks.js.map +0 -1
- package/dist/dev/core/features/sync-preload-attribute.js +0 -26
- package/dist/dev/core/features/sync-preload-attribute.js.map +0 -1
- package/dist/dev/core/hls/parse-attributes.js.map +0 -1
- package/dist/dev/core/hls/parse-media-playlist.js.map +0 -1
- package/dist/dev/core/hls/parse-multivariant.js.map +0 -1
- package/dist/dev/core/hls/resolve-url.js.map +0 -1
- package/dist/dev/core/task.js.map +0 -1
- package/dist/dev/core/types/index.d.ts.map +0 -1
- package/dist/dev/core/types/index.js +0 -15
- package/dist/dev/core/types/index.js.map +0 -1
- package/dist/dev/core/utils/generate-id.js +0 -21
- package/dist/dev/core/utils/generate-id.js.map +0 -1
- package/dist/dev/core/utils/track-selection.d.ts +0 -16
- package/dist/dev/core/utils/track-selection.d.ts.map +0 -1
- package/dist/dev/core/utils/track-selection.js +0 -39
- package/dist/dev/core/utils/track-selection.js.map +0 -1
- package/dist/dev/dom/features/end-of-stream.js +0 -145
- package/dist/dev/dom/features/end-of-stream.js.map +0 -1
- package/dist/dev/dom/features/load-segments.d.ts +0 -72
- package/dist/dev/dom/features/load-segments.d.ts.map +0 -1
- package/dist/dev/dom/features/load-segments.js +0 -192
- package/dist/dev/dom/features/load-segments.js.map +0 -1
- package/dist/dev/dom/features/load-text-track-cues.d.ts +0 -85
- package/dist/dev/dom/features/load-text-track-cues.d.ts.map +0 -1
- package/dist/dev/dom/features/load-text-track-cues.js +0 -104
- package/dist/dev/dom/features/load-text-track-cues.js.map +0 -1
- package/dist/dev/dom/features/segment-loader-actor.d.ts +0 -1
- package/dist/dev/dom/features/segment-loader-actor.js.map +0 -1
- package/dist/dev/dom/features/setup-mediasource.js +0 -48
- package/dist/dev/dom/features/setup-mediasource.js.map +0 -1
- package/dist/dev/dom/features/setup-sourcebuffer.d.ts +0 -9
- package/dist/dev/dom/features/setup-sourcebuffer.d.ts.map +0 -1
- package/dist/dev/dom/features/setup-sourcebuffer.js +0 -78
- package/dist/dev/dom/features/setup-sourcebuffer.js.map +0 -1
- package/dist/dev/dom/features/sync-text-tracks.js +0 -95
- package/dist/dev/dom/features/sync-text-tracks.js.map +0 -1
- package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts +0 -12
- package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts.map +0 -1
- package/dist/dev/dom/features/text-track-segment-loader-actor.js +0 -55
- package/dist/dev/dom/features/text-track-segment-loader-actor.js.map +0 -1
- package/dist/dev/dom/features/text-tracks-actor.d.ts +0 -31
- package/dist/dev/dom/features/text-tracks-actor.d.ts.map +0 -1
- package/dist/dev/dom/features/text-tracks-actor.js.map +0 -1
- package/dist/dev/dom/features/track-current-time.d.ts +0 -40
- package/dist/dev/dom/features/track-current-time.d.ts.map +0 -1
- package/dist/dev/dom/features/track-current-time.js +0 -49
- package/dist/dev/dom/features/track-current-time.js.map +0 -1
- package/dist/dev/dom/features/track-playback-initiated.d.ts +0 -46
- package/dist/dev/dom/features/track-playback-initiated.d.ts.map +0 -1
- package/dist/dev/dom/features/track-playback-initiated.js +0 -66
- package/dist/dev/dom/features/track-playback-initiated.js.map +0 -1
- package/dist/dev/dom/features/track-playback-rate.d.ts +0 -34
- package/dist/dev/dom/features/track-playback-rate.d.ts.map +0 -1
- package/dist/dev/dom/features/track-playback-rate.js +0 -32
- package/dist/dev/dom/features/track-playback-rate.js.map +0 -1
- package/dist/dev/dom/features/update-duration.js +0 -78
- package/dist/dev/dom/features/update-duration.js.map +0 -1
- package/dist/dev/dom/media/append-segment.d.ts.map +0 -1
- package/dist/dev/dom/media/append-segment.js.map +0 -1
- package/dist/dev/dom/media/buffer-flusher.d.ts.map +0 -1
- package/dist/dev/dom/media/buffer-flusher.js.map +0 -1
- package/dist/dev/dom/media/mediasource-setup.js.map +0 -1
- package/dist/dev/dom/media/source-buffer-actor.d.ts.map +0 -1
- package/dist/dev/dom/media/source-buffer-actor.js.map +0 -1
- package/dist/dev/dom/network/chunked-stream-iterable.js.map +0 -1
- package/dist/dev/dom/network/fetch.js +0 -54
- package/dist/dev/dom/network/fetch.js.map +0 -1
- package/dist/dev/dom/playback-engine/adapter.d.ts.map +0 -1
- package/dist/dev/dom/playback-engine/adapter.js.map +0 -1
- package/dist/dev/dom/playback-engine/engine.d.ts +0 -124
- package/dist/dev/dom/playback-engine/engine.d.ts.map +0 -1
- package/dist/dev/dom/playback-engine/engine.js +0 -142
- package/dist/dev/dom/playback-engine/engine.js.map +0 -1
- package/dist/dev/dom/playback-engine.d.ts +0 -4
- package/dist/dev/dom/playback-engine.js +0 -4
- package/dist/dev/dom/text/parse-vtt-segment.d.ts +0 -12
- package/dist/dev/dom/text/parse-vtt-segment.d.ts.map +0 -1
- package/dist/dev/dom/text/parse-vtt-segment.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-buffer.js","names":[],"sources":["../../../../../src/playback/actors/dom/source-buffer.ts"],"sourcesContent":["import { createMachineActor, type HandlerContext, type MessageActor } from '../../../core/actors/create-machine-actor';\nimport { SerialRunner, Task } from '../../../core/tasks/task';\nimport { type AppendData, appendSegment } from '../../../media/dom/mse/append-segment';\nimport { flushBuffer } from '../../../media/dom/mse/buffer-flusher';\nimport { SEGMENT_TIME_EPSILON, type Segment, type Track } from '../../../media/types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface BufferedRange {\n start: number;\n end: number;\n}\n\nexport type AppendSegmentMeta = Pick<Segment, 'id' | 'startTime' | 'duration'> & {\n trackId: Track['id'];\n /** Declared track bandwidth in bps (from playlist BANDWIDTH attribute). */\n trackBandwidth?: number;\n};\n\nexport type { AppendData };\n\nexport type AppendInitMessage = {\n type: 'append-init';\n data: AppendData;\n /**\n * `language` is captured alongside `trackId` so downstream loaders can\n * compare the buffered track's language to the newly-selected track's\n * language and decide whether ahead-buffer flush is warranted on track\n * switch (see `segment-loader`'s `planTasks`). Undefined for video and\n * for audio without explicit `LANGUAGE` attribute.\n */\n meta: { trackId: Track['id']; language?: string };\n};\nexport type AppendSegmentMessage = { type: 'append-segment'; data: AppendData; meta: AppendSegmentMeta };\nexport type RemoveMessage = { type: 'remove'; start: number; end: number };\nexport type IndividualSourceBufferMessage = AppendInitMessage | AppendSegmentMessage | RemoveMessage;\nexport type BatchMessage = { type: 'batch'; messages: IndividualSourceBufferMessage[] };\nexport type CancelMessage = { type: 'cancel' };\n\n/** All messages accepted by a SourceBufferActor. */\nexport type SourceBufferMessage = IndividualSourceBufferMessage | BatchMessage | CancelMessage;\n\n/** Finite states of the actor. */\nexport type SourceBufferActorState = 'idle' | 'updating' | 'destroyed';\n\n/** Non-finite (extended) data managed by the actor — the XState \"context\". */\nexport interface SourceBufferActorContext {\n initTrackId?: string | undefined;\n /**\n * Language of the most recently appended init segment's track (when\n * present on the playlist). Used by the segment-loader's `planTasks`\n * to detect cross-language switches and schedule ahead-buffer flush.\n * Undefined for video and for language-less audio.\n */\n initTrackLanguage?: string | undefined;\n segments: Array<\n Pick<Segment, 'id' | 'startTime' | 'duration'> & {\n trackId: Track['id'];\n trackBandwidth?: number;\n /**\n * True while a streaming append is in progress for this segment.\n * The segment's data is partially present in the SourceBuffer.\n * Downstream code must not treat a partial segment as fully buffered.\n */\n partial?: boolean;\n }\n >;\n bufferedRanges: BufferedRange[];\n}\n\n/** SourceBuffer actor: queues operations, owns its snapshot. */\nexport type SourceBufferActor = MessageActor<SourceBufferActorState, SourceBufferActorContext, SourceBufferMessage>;\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction snapshotBuffered(buffered: TimeRanges): BufferedRange[] {\n const ranges: BufferedRange[] = [];\n for (let i = 0; i < buffered.length; i++) {\n ranges.push({ start: buffered.start(i), end: buffered.end(i) });\n }\n return ranges;\n}\n\n// =============================================================================\n// Message task factories\n// =============================================================================\n\n// Context is read lazily via getContext at task execution time — not at creation\n// time — so each task always operates on the most recent context regardless of\n// when it was scheduled.\n\ninterface MessageTaskOptions {\n getContext: () => SourceBufferActorContext;\n sourceBuffer: SourceBuffer;\n setContext: (ctx: SourceBufferActorContext) => void;\n}\n\nfunction appendInitTask(\n message: AppendInitMessage,\n { getContext, sourceBuffer }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n await appendSegment(sourceBuffer, message.data);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n return { ...ctx, initTrackId: message.meta.trackId, initTrackLanguage: message.meta.language };\n });\n}\n\nfunction appendSegmentTask(\n message: AppendSegmentMessage,\n { getContext, sourceBuffer, setContext }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n\n const { meta } = message;\n // Remove any existing entry at the same start time (same \"slot\" in the\n // timeline), then record the new segment. Assumes time-aligned segments\n // across playlists. `SEGMENT_TIME_EPSILON` guards against floating-point\n // drift in parsed timestamps (shared with the segment-loader quality\n // filter — single source of truth).\n const filtered = ctx.segments.filter((s) => Math.abs(s.startTime - meta.startTime) >= SEGMENT_TIME_EPSILON);\n\n // For streaming data: emit partial state before the first chunk so\n // downstream code can see the in-progress segment and treat it as\n // incomplete. ArrayBuffer appends are atomic so no partial state is\n // needed — context is updated once at task completion.\n if (!(message.data instanceof ArrayBuffer)) {\n setContext({\n ...ctx,\n segments: [\n ...filtered,\n {\n id: meta.id,\n startTime: meta.startTime,\n duration: meta.duration,\n trackId: meta.trackId,\n ...(meta.trackBandwidth !== undefined && { trackBandwidth: meta.trackBandwidth }),\n partial: true,\n },\n ],\n bufferedRanges: ctx.bufferedRanges,\n });\n }\n\n await appendSegment(sourceBuffer, message.data, taskSignal);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n return {\n ...ctx,\n segments: [\n ...filtered,\n {\n id: meta.id,\n startTime: meta.startTime,\n duration: meta.duration,\n trackId: meta.trackId,\n ...(meta.trackBandwidth !== undefined && { trackBandwidth: meta.trackBandwidth }),\n },\n ],\n bufferedRanges: snapshotBuffered(sourceBuffer.buffered),\n };\n });\n}\n\nfunction removeTask(\n message: RemoveMessage,\n { getContext, sourceBuffer }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n await flushBuffer(sourceBuffer, message.start, message.end);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n //\n // Use the post-flush buffered ranges as ground truth. A segment is kept\n // in the model only if its midpoint falls within a buffered range.\n // Midpoint-based membership handles flush boundaries that don't align\n // exactly with segment edges without over-removing adjacent segments.\n const bufferedRanges = snapshotBuffered(sourceBuffer.buffered);\n const filtered = ctx.segments.filter((s) => {\n const midpoint = s.startTime + s.duration / 2;\n return bufferedRanges.some((r) => midpoint >= r.start && midpoint < r.end);\n });\n return { ...ctx, segments: filtered, bufferedRanges };\n });\n}\n\ntype MessageTaskFactory<T extends IndividualSourceBufferMessage> = (\n message: T,\n options: MessageTaskOptions\n) => Task<SourceBufferActorContext>;\n\nconst messageTaskFactories = {\n 'append-init': appendInitTask,\n 'append-segment': appendSegmentTask,\n remove: removeTask,\n} satisfies {\n [K in IndividualSourceBufferMessage['type']]: MessageTaskFactory<Extract<IndividualSourceBufferMessage, { type: K }>>;\n};\n\nfunction messageToTask(\n message: IndividualSourceBufferMessage,\n options: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n const factory = messageTaskFactories[message.type] as MessageTaskFactory<typeof message>;\n return factory(message, options);\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport function createSourceBufferActor(\n sourceBuffer: SourceBuffer,\n initialContext?: Partial<SourceBufferActorContext>\n): SourceBufferActor {\n type UserState = Exclude<SourceBufferActorState, 'destroyed'>;\n\n const handleError = (e: unknown): void => {\n if (!(e instanceof Error && e.name === 'AbortError')) {\n console.error('SourceBuffer operation failed:', e);\n }\n };\n\n type Ctx = HandlerContext<UserState, SourceBufferActorContext, () => SerialRunner>;\n\n const onMessage = (msg: IndividualSourceBufferMessage, { transition, setContext, getContext, runner }: Ctx): void => {\n transition('updating');\n const task = messageToTask(msg, { getContext, sourceBuffer, setContext });\n runner.schedule(task).then(setContext, handleError);\n };\n\n return createMachineActor<UserState, SourceBufferActorContext, SourceBufferMessage, () => SerialRunner>({\n runner: () => new SerialRunner(),\n initial: 'idle',\n context: { segments: [], bufferedRanges: [], initTrackId: undefined, ...initialContext },\n states: {\n idle: {\n on: {\n 'append-init': onMessage,\n 'append-segment': onMessage,\n remove: onMessage,\n batch: (msg, { transition, setContext, getContext, runner }) => {\n const { messages } = msg;\n if (messages.length === 0) return;\n\n transition('updating');\n messages.forEach((msg) => {\n const task = messageToTask(msg, { getContext, sourceBuffer, setContext });\n runner.schedule(task).then(setContext, handleError);\n });\n },\n },\n },\n updating: {\n // Automatically return to idle once all scheduled tasks settle.\n onSettled: 'idle',\n on: {\n // Abort all in-progress and pending tasks. onSettled handles → 'idle'\n // once the aborted tasks drain.\n cancel: (_, { runner }) => {\n runner.abortAll();\n },\n },\n },\n },\n });\n}\n"],"mappings":";;;;;;AA+EA,SAAS,iBAAiB,UAAuC;CAC/D,MAAM,SAA0B,EAAE;AAClC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IACnC,QAAO,KAAK;EAAE,OAAO,SAAS,MAAM,EAAE;EAAE,KAAK,SAAS,IAAI,EAAE;EAAE,CAAC;AAEjE,QAAO;;AAiBT,SAAS,eACP,SACA,EAAE,YAAY,gBACkB;AAChC,QAAO,IAAI,KAAK,OAAO,eAAe;EACpC,MAAM,MAAM,YAAY;AACxB,MAAI,WAAW,QAAS,QAAO;AAC/B,QAAM,cAAc,cAAc,QAAQ,KAAK;AAG/C,SAAO;GAAE,GAAG;GAAK,aAAa,QAAQ,KAAK;GAAS,mBAAmB,QAAQ,KAAK;GAAU;GAC9F;;AAGJ,SAAS,kBACP,SACA,EAAE,YAAY,cAAc,cACI;AAChC,QAAO,IAAI,KAAK,OAAO,eAAe;EACpC,MAAM,MAAM,YAAY;AACxB,MAAI,WAAW,QAAS,QAAO;EAE/B,MAAM,EAAE,SAAS;EAMjB,MAAM,WAAW,IAAI,SAAS,QAAQ,MAAM,KAAK,IAAI,EAAE,YAAY,KAAK,UAAU,IAAI,qBAAqB;AAM3G,MAAI,EAAE,QAAQ,gBAAgB,aAC5B,YAAW;GACT,GAAG;GACH,UAAU,CACR,GAAG,UACH;IACE,IAAI,KAAK;IACT,WAAW,KAAK;IAChB,UAAU,KAAK;IACf,SAAS,KAAK;IACd,GAAI,KAAK,mBAAmB,KAAA,KAAa,EAAE,gBAAgB,KAAK,gBAAgB;IAChF,SAAS;IACV,CACF;GACD,gBAAgB,IAAI;GACrB,CAAC;AAGJ,QAAM,cAAc,cAAc,QAAQ,MAAM,WAAW;AAG3D,SAAO;GACL,GAAG;GACH,UAAU,CACR,GAAG,UACH;IACE,IAAI,KAAK;IACT,WAAW,KAAK;IAChB,UAAU,KAAK;IACf,SAAS,KAAK;IACd,GAAI,KAAK,mBAAmB,KAAA,KAAa,EAAE,gBAAgB,KAAK,gBAAgB;IACjF,CACF;GACD,gBAAgB,iBAAiB,aAAa,SAAS;GACxD;GACD;;AAGJ,SAAS,WACP,SACA,EAAE,YAAY,gBACkB;AAChC,QAAO,IAAI,KAAK,OAAO,eAAe;EACpC,MAAM,MAAM,YAAY;AACxB,MAAI,WAAW,QAAS,QAAO;AAC/B,QAAM,YAAY,cAAc,QAAQ,OAAO,QAAQ,IAAI;EAQ3D,MAAM,iBAAiB,iBAAiB,aAAa,SAAS;EAC9D,MAAM,WAAW,IAAI,SAAS,QAAQ,MAAM;GAC1C,MAAM,WAAW,EAAE,YAAY,EAAE,WAAW;AAC5C,UAAO,eAAe,MAAM,MAAM,YAAY,EAAE,SAAS,WAAW,EAAE,IAAI;IAC1E;AACF,SAAO;GAAE,GAAG;GAAK,UAAU;GAAU;GAAgB;GACrD;;AAQJ,MAAM,uBAAuB;CAC3B,eAAe;CACf,kBAAkB;CAClB,QAAQ;CACT;AAID,SAAS,cACP,SACA,SACgC;CAChC,MAAM,UAAU,qBAAqB,QAAQ;AAC7C,QAAO,QAAQ,SAAS,QAAQ;;AAOlC,SAAgB,wBACd,cACA,gBACmB;CAGnB,MAAM,eAAe,MAAqB;AACxC,MAAI,EAAE,aAAa,SAAS,EAAE,SAAS,cACrC,SAAQ,MAAM,kCAAkC,EAAE;;CAMtD,MAAM,aAAa,KAAoC,EAAE,YAAY,YAAY,YAAY,aAAwB;AACnH,aAAW,WAAW;EACtB,MAAM,OAAO,cAAc,KAAK;GAAE;GAAY;GAAc;GAAY,CAAC;AACzE,SAAO,SAAS,KAAK,CAAC,KAAK,YAAY,YAAY;;AAGrD,QAAO,mBAAiG;EACtG,cAAc,IAAI,cAAc;EAChC,SAAS;EACT,SAAS;GAAE,UAAU,EAAE;GAAE,gBAAgB,EAAE;GAAE,aAAa,KAAA;GAAW,GAAG;GAAgB;EACxF,QAAQ;GACN,MAAM,EACJ,IAAI;IACF,eAAe;IACf,kBAAkB;IAClB,QAAQ;IACR,QAAQ,KAAK,EAAE,YAAY,YAAY,YAAY,aAAa;KAC9D,MAAM,EAAE,aAAa;AACrB,SAAI,SAAS,WAAW,EAAG;AAE3B,gBAAW,WAAW;AACtB,cAAS,SAAS,QAAQ;MACxB,MAAM,OAAO,cAAc,KAAK;OAAE;OAAY;OAAc;OAAY,CAAC;AACzE,aAAO,SAAS,KAAK,CAAC,KAAK,YAAY,YAAY;OACnD;;IAEL,EACF;GACD,UAAU;IAER,WAAW;IACX,IAAI,EAGF,SAAS,GAAG,EAAE,aAAa;AACzB,YAAO,UAAU;OAEpB;IACF;GACF;EACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { AddCuesMessage, ClearMessage, CueSegmentMeta, TextTracksActor, TextTracksActorContext, TextTracksActorMessage } from "../text-tracks.js";
|
package/dist/{default/dom/features/text-tracks-actor.js → dev/playback/actors/dom/text-tracks.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createTransitionActor } from "
|
|
2
|
-
//#region src/dom/
|
|
1
|
+
import { createTransitionActor } from "../../../core/actors/create-transition-actor.js";
|
|
2
|
+
//#region src/playback/actors/dom/text-tracks.ts
|
|
3
3
|
function isDuplicateCue(cue, existing) {
|
|
4
4
|
return existing.some((r) => r.startTime === cue.startTime && r.endTime === cue.endTime && r.text === cue.text);
|
|
5
5
|
}
|
|
@@ -9,6 +9,10 @@ function createTextTracksActor(mediaElement) {
|
|
|
9
9
|
loaded: {},
|
|
10
10
|
segments: {}
|
|
11
11
|
}, (context, message) => {
|
|
12
|
+
if (message.type === "clear") return {
|
|
13
|
+
loaded: {},
|
|
14
|
+
segments: {}
|
|
15
|
+
};
|
|
12
16
|
const { meta, cues } = message;
|
|
13
17
|
const { trackId, id: segmentId, startTime, duration } = meta;
|
|
14
18
|
const textTrack = Array.from(mediaElement.textTracks).find((t) => t.id === trackId);
|
|
@@ -39,4 +43,4 @@ function createTextTracksActor(mediaElement) {
|
|
|
39
43
|
//#endregion
|
|
40
44
|
export { createTextTracksActor };
|
|
41
45
|
|
|
42
|
-
//# sourceMappingURL=text-tracks
|
|
46
|
+
//# sourceMappingURL=text-tracks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-tracks.js","names":[],"sources":["../../../../../src/playback/actors/dom/text-tracks.ts"],"sourcesContent":["import { createTransitionActor } from '../../../core/actors/create-transition-actor';\nimport type { Cue } from '../../../media/types';\nimport type {\n AddCuesMessage,\n ClearMessage,\n CueSegmentMeta,\n TextTracksActor,\n TextTracksActorContext,\n TextTracksActorMessage,\n} from '../text-tracks';\n\n// Re-export the host-agnostic types so existing dom-side consumers can keep\n// importing from this module.\nexport type {\n AddCuesMessage,\n ClearMessage,\n CueSegmentMeta,\n TextTracksActor,\n TextTracksActorContext,\n TextTracksActorMessage,\n};\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction isDuplicateCue(cue: VTTCue, existing: Cue[]): boolean {\n return existing.some((r) => r.startTime === cue.startTime && r.endTime === cue.endTime && r.text === cue.text);\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** TextTrack actor: wraps all text tracks on a media element, owns cue operations. */\nexport function createTextTracksActor(mediaElement: HTMLMediaElement): TextTracksActor<VTTCue> {\n return createTransitionActor({ loaded: {}, segments: {} } as TextTracksActorContext, (context, message) => {\n if (message.type === 'clear') {\n // Reset the cue + segment cache. DOM cleanup is the caller's job —\n // by the time we get here, `syncTextTracks` has already removed\n // the `<track>` children (and their cues) via\n // `removeAllSubtitlesTracksFromMedia`.\n return { loaded: {}, segments: {} };\n }\n\n // NOTE: Currently assumes cues are applied to a non-disabled TextTrack. Discuss different approaches here, including:\n // - Making the message responsible for auto-selection of the textTrack (changes logic in sync-text-tracks)\n // - Silent gating/console warning + early bail\n // - throwing a domain-specific error\n // - accepting as is (which would result in errors, but also \"shouldn't ever happen\" unless a bug is introduced)\n // (CJP)\n const { meta, cues } = message;\n const { trackId, id: segmentId, startTime, duration } = meta;\n const textTrack = Array.from(mediaElement.textTracks).find((t) => t.id === trackId);\n if (!textTrack) return context;\n\n const existingCues = context.loaded[trackId] ?? [];\n const existingSegments = context.segments[trackId] ?? [];\n const prunedCues = cues.filter((cue) => !isDuplicateCue(cue, existingCues));\n const segmentAlreadyLoaded = existingSegments.some((s) => s.id === segmentId);\n\n if (prunedCues.length === 0 && segmentAlreadyLoaded) return context;\n\n for (const cue of prunedCues) textTrack.addCue(cue);\n return {\n ...context,\n loaded: {\n ...context.loaded,\n [trackId]: [...existingCues, ...prunedCues],\n },\n segments: segmentAlreadyLoaded\n ? context.segments\n : {\n ...context.segments,\n [trackId]: [...existingSegments, { id: segmentId, startTime, duration }],\n },\n };\n });\n}\n"],"mappings":";;AA0BA,SAAS,eAAe,KAAa,UAA0B;AAC7D,QAAO,SAAS,MAAM,MAAM,EAAE,cAAc,IAAI,aAAa,EAAE,YAAY,IAAI,WAAW,EAAE,SAAS,IAAI,KAAK;;;AAQhH,SAAgB,sBAAsB,cAAyD;AAC7F,QAAO,sBAAsB;EAAE,QAAQ,EAAE;EAAE,UAAU,EAAE;EAAE,GAA6B,SAAS,YAAY;AACzG,MAAI,QAAQ,SAAS,QAKnB,QAAO;GAAE,QAAQ,EAAE;GAAE,UAAU,EAAE;GAAE;EASrC,MAAM,EAAE,MAAM,SAAS;EACvB,MAAM,EAAE,SAAS,IAAI,WAAW,WAAW,aAAa;EACxD,MAAM,YAAY,MAAM,KAAK,aAAa,WAAW,CAAC,MAAM,MAAM,EAAE,OAAO,QAAQ;AACnF,MAAI,CAAC,UAAW,QAAO;EAEvB,MAAM,eAAe,QAAQ,OAAO,YAAY,EAAE;EAClD,MAAM,mBAAmB,QAAQ,SAAS,YAAY,EAAE;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ,CAAC,eAAe,KAAK,aAAa,CAAC;EAC3E,MAAM,uBAAuB,iBAAiB,MAAM,MAAM,EAAE,OAAO,UAAU;AAE7E,MAAI,WAAW,WAAW,KAAK,qBAAsB,QAAO;AAE5D,OAAK,MAAM,OAAO,WAAY,WAAU,OAAO,IAAI;AACnD,SAAO;GACL,GAAG;GACH,QAAQ;IACN,GAAG,QAAQ;KACV,UAAU,CAAC,GAAG,cAAc,GAAG,WAAW;IAC5C;GACD,UAAU,uBACN,QAAQ,WACR;IACE,GAAG,QAAQ;KACV,UAAU,CAAC,GAAG,kBAAkB;KAAE,IAAI;KAAW;KAAW;KAAU,CAAC;IACzE;GACN;GACD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MessageActor } from "../../core/actors/create-machine-actor.js";
|
|
2
|
+
import { Cue, TextTrack } from "../../media/types/index.js";
|
|
3
|
+
import { ForwardBufferConfig } from "../../media/buffer/forward-buffer.js";
|
|
4
|
+
//#region src/playback/actors/text-track-segment-loader.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors the v/a `SegmentLoaderMessage` shape. `range` carries the
|
|
7
|
+
* forward-window anchor (`range.start` is treated as the load anchor;
|
|
8
|
+
* the actor computes its own forward window internally via
|
|
9
|
+
* `getSegmentsToLoad`). When `range` is omitted (metadata mode), this
|
|
10
|
+
* actor is a no-op — text tracks have no init-segment concept.
|
|
11
|
+
*/
|
|
12
|
+
type TextTrackSegmentLoaderMessage = {
|
|
13
|
+
type: 'load';
|
|
14
|
+
track: TextTrack;
|
|
15
|
+
range?: {
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** Finite states of the actor. */
|
|
21
|
+
type TextTrackSegmentLoaderActorState = 'idle' | 'loading' | 'destroyed';
|
|
22
|
+
/** Non-finite (extended) data managed by the actor. */
|
|
23
|
+
interface TextTrackSegmentLoaderActorContext {
|
|
24
|
+
/**
|
|
25
|
+
* Track ID of the segment currently being fetched, or null. Paired
|
|
26
|
+
* with `inFlightSegmentId` so the continue-vs-preempt check survives
|
|
27
|
+
* cross-track segment-id collisions (e.g. each track starts at
|
|
28
|
+
* `seg-0`).
|
|
29
|
+
*/
|
|
30
|
+
inFlightTrackId: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Segment ID currently being fetched, or null. Used together with
|
|
33
|
+
* `inFlightTrackId` by the `loading` state's `load` handler.
|
|
34
|
+
*/
|
|
35
|
+
inFlightSegmentId: string | null;
|
|
36
|
+
}
|
|
37
|
+
type TextTrackSegmentLoaderActor = MessageActor<TextTrackSegmentLoaderActorState, TextTrackSegmentLoaderActorContext, TextTrackSegmentLoaderMessage>;
|
|
38
|
+
/**
|
|
39
|
+
* Resolves a text-track segment URL into the array of cues it contains.
|
|
40
|
+
*
|
|
41
|
+
* "Resolve" because the fn covers both network fetch and parse into the
|
|
42
|
+
* domain model. Host-agnostic — the concrete resolver (e.g. the
|
|
43
|
+
* browser's native VTT resolver) is supplied at engine-assembly time,
|
|
44
|
+
* so this actor stays DOM-free.
|
|
45
|
+
*/
|
|
46
|
+
type TextTrackSegmentResolver<C extends Cue = Cue> = (url: string) => Promise<C[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Configuration for `createTextTrackSegmentLoaderActor`. Spread over
|
|
49
|
+
* `DEFAULT_FORWARD_BUFFER_CONFIG` to override individual forward-window
|
|
50
|
+
* fields (e.g. `bufferDuration`). Text tracks don't have a back-buffer
|
|
51
|
+
* concern — cues evict by their playhead-relative window at runtime —
|
|
52
|
+
* so no `backBuffer` config field.
|
|
53
|
+
*/
|
|
54
|
+
interface TextTrackSegmentLoaderActorConfig {
|
|
55
|
+
forwardBuffer?: Partial<ForwardBufferConfig>;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { TextTrackSegmentLoaderActor, TextTrackSegmentLoaderActorConfig, TextTrackSegmentResolver };
|
|
59
|
+
//# sourceMappingURL=text-track-segment-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-track-segment-loader.d.ts","names":[],"sources":["../../../../src/playback/actors/text-track-segment-loader.ts"],"mappings":";;;;;;AAsBA;;;;;KAAY,6BAAA;EACV,IAAA;EACA,KAAA,EAAO,SAAA;EACP,KAAA;IAAU,KAAA;IAAe,GAAA;EAAA;AAAA;;KAIf,gCAAA;;UAGK,kCAAA;EAAA;;;;;AAejB;EARE,eAAA;;;;;EAKA,iBAAA;AAAA;AAAA,KAGU,2BAAA,GAA8B,YAAA,CACxC,gCAAA,EACA,kCAAA,EACA,6BAAA;;;;;;;AAWF;;KAAY,wBAAA,WAAmC,GAAA,GAAM,GAAA,KAAQ,GAAA,aAAgB,OAAA,CAAQ,CAAA;;;;;;;;UASpE,iCAAA;EACf,aAAA,GAAgB,OAAA,CAAQ,mBAAA;AAAA"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { peek } from "../../core/signals/primitives.js";
|
|
2
|
+
import { SerialRunner, Task } from "../../core/tasks/task.js";
|
|
3
|
+
import { createMachineActor } from "../../core/actors/create-machine-actor.js";
|
|
4
|
+
import { DEFAULT_FORWARD_BUFFER_CONFIG, getSegmentsToLoad } from "../../media/buffer/forward-buffer.js";
|
|
5
|
+
//#region src/playback/actors/text-track-segment-loader.ts
|
|
6
|
+
/**
|
|
7
|
+
* Loads text-track segments for a track and delegates cue management
|
|
8
|
+
* to a TextTracksActor. Mirrors the v/a `SegmentLoaderActor` shape (FSM
|
|
9
|
+
* with `idle` / `loading` and `inFlight*` context for continue-vs-preempt),
|
|
10
|
+
* adapted to text:
|
|
11
|
+
*
|
|
12
|
+
* - No init segment, no flush ops (text cues don't need eviction — they're
|
|
13
|
+
* small and the playhead-relative window is enforced by the runtime).
|
|
14
|
+
* - Single in-flight identity (`inFlightSegmentId`) — text has only the
|
|
15
|
+
* media-segment path, no init-segment path.
|
|
16
|
+
*
|
|
17
|
+
* Planning is done in the load handler on every incoming message:
|
|
18
|
+
* `getSegmentsToLoad` filters to the forward window, then the segments
|
|
19
|
+
* not already in `TextTracksActor`'s context are scheduled. When a new
|
|
20
|
+
* `load` arrives mid-run, the handler replans and either:
|
|
21
|
+
*
|
|
22
|
+
* - **Continues**: the in-flight segment is still in the new plan →
|
|
23
|
+
* `abortPending` only, schedule the rest of the plan (minus the
|
|
24
|
+
* in-flight item, which covers its slot).
|
|
25
|
+
* - **Preempts**: in-flight segment is no longer wanted (track switch,
|
|
26
|
+
* large seek out of window) → `abortAll`, schedule the new plan
|
|
27
|
+
* from scratch.
|
|
28
|
+
*
|
|
29
|
+
* The cue parser is injected so this factory is host-agnostic. A DOM
|
|
30
|
+
* host supplies a VTT parser backed by `<track>`/`TextTrack` APIs; a
|
|
31
|
+
* non-DOM host (worker, test fake, alternate runtime) supplies its own.
|
|
32
|
+
*/
|
|
33
|
+
function createTextTrackSegmentLoaderActor(textTracksActor, resolveSegment, config = {}) {
|
|
34
|
+
const forwardBufferConfig = {
|
|
35
|
+
...DEFAULT_FORWARD_BUFFER_CONFIG,
|
|
36
|
+
...config.forwardBuffer
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Translate a load message into an ordered TextLoadTask list based on
|
|
40
|
+
* committed actor state. In-flight awareness is handled separately in
|
|
41
|
+
* the `loading` state's load handler.
|
|
42
|
+
*
|
|
43
|
+
* Metadata mode (no `range`) is a no-op for text — text tracks have
|
|
44
|
+
* no init-segment concept, so there's nothing to load until a range
|
|
45
|
+
* arrives via `'full-range'` dispatch.
|
|
46
|
+
*/
|
|
47
|
+
const planTasks = (message) => {
|
|
48
|
+
const { track, range } = message;
|
|
49
|
+
if (!range) return [];
|
|
50
|
+
const trackId = track.id;
|
|
51
|
+
const bufferedSegments = peek(textTracksActor.snapshot).context.segments[trackId] ?? [];
|
|
52
|
+
return getSegmentsToLoad(track.segments, bufferedSegments, range.start, forwardBufferConfig).map((segment) => ({
|
|
53
|
+
segment,
|
|
54
|
+
trackId
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Wraps a TextLoadTask into a Task that fetches + dispatches `add-cues`.
|
|
59
|
+
* Updates `inFlightSegmentId` around the fetch so the load handler can
|
|
60
|
+
* make accurate continue/preempt decisions.
|
|
61
|
+
*/
|
|
62
|
+
const makeLoadTask = (op, { getContext, setContext }) => {
|
|
63
|
+
return new Task(async (signal) => {
|
|
64
|
+
if (signal.aborted) return;
|
|
65
|
+
setContext({
|
|
66
|
+
...getContext(),
|
|
67
|
+
inFlightTrackId: op.trackId,
|
|
68
|
+
inFlightSegmentId: op.segment.id
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
const cues = await resolveSegment(op.segment.url);
|
|
72
|
+
if (signal.aborted) return;
|
|
73
|
+
textTracksActor.send({
|
|
74
|
+
type: "add-cues",
|
|
75
|
+
meta: {
|
|
76
|
+
trackId: op.trackId,
|
|
77
|
+
id: op.segment.id,
|
|
78
|
+
startTime: op.segment.startTime,
|
|
79
|
+
duration: op.segment.duration
|
|
80
|
+
},
|
|
81
|
+
cues
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("Failed to load text-track segment:", error);
|
|
85
|
+
} finally {
|
|
86
|
+
setContext({
|
|
87
|
+
...getContext(),
|
|
88
|
+
inFlightTrackId: null,
|
|
89
|
+
inFlightSegmentId: null
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const scheduleAll = (tasks, ctx) => {
|
|
95
|
+
for (const op of tasks) ctx.runner.schedule(makeLoadTask(op, ctx)).then(void 0, (e) => {
|
|
96
|
+
if (e instanceof Error && e.name === "AbortError") return;
|
|
97
|
+
console.error("Unexpected error in text-track segment loader:", e);
|
|
98
|
+
ctx.runner.abortPending();
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
return createMachineActor({
|
|
102
|
+
runner: () => new SerialRunner(),
|
|
103
|
+
initial: "idle",
|
|
104
|
+
context: {
|
|
105
|
+
inFlightTrackId: null,
|
|
106
|
+
inFlightSegmentId: null
|
|
107
|
+
},
|
|
108
|
+
states: {
|
|
109
|
+
idle: { on: { load: (msg, ctx) => {
|
|
110
|
+
const tasks = planTasks(msg);
|
|
111
|
+
if (tasks.length === 0) return;
|
|
112
|
+
ctx.transition("loading");
|
|
113
|
+
scheduleAll(tasks, ctx);
|
|
114
|
+
} } },
|
|
115
|
+
loading: {
|
|
116
|
+
onSettled: "idle",
|
|
117
|
+
on: { load: (msg, ctx) => {
|
|
118
|
+
const { context, runner } = ctx;
|
|
119
|
+
const tasks = planTasks(msg);
|
|
120
|
+
if (context.inFlightTrackId !== null && context.inFlightSegmentId !== null && tasks.some((t) => t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)) {
|
|
121
|
+
runner.abortPending();
|
|
122
|
+
scheduleAll(tasks.filter((t) => !(t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)), ctx);
|
|
123
|
+
} else {
|
|
124
|
+
runner.abortAll();
|
|
125
|
+
scheduleAll(tasks, ctx);
|
|
126
|
+
}
|
|
127
|
+
} }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { createTextTrackSegmentLoaderActor };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=text-track-segment-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-track-segment-loader.js","names":[],"sources":["../../../../src/playback/actors/text-track-segment-loader.ts"],"sourcesContent":["import { createMachineActor, type HandlerContext, type MessageActor } from '../../core/actors/create-machine-actor';\nimport { peek } from '../../core/signals/primitives';\nimport { SerialRunner, Task } from '../../core/tasks/task';\nimport {\n DEFAULT_FORWARD_BUFFER_CONFIG,\n type ForwardBufferConfig,\n getSegmentsToLoad,\n} from '../../media/buffer/forward-buffer';\nimport type { Cue, Segment, TextTrack } from '../../media/types';\nimport type { TextTracksActor } from './text-tracks';\n\n// =============================================================================\n// Message / actor types\n// =============================================================================\n\n/**\n * Mirrors the v/a `SegmentLoaderMessage` shape. `range` carries the\n * forward-window anchor (`range.start` is treated as the load anchor;\n * the actor computes its own forward window internally via\n * `getSegmentsToLoad`). When `range` is omitted (metadata mode), this\n * actor is a no-op — text tracks have no init-segment concept.\n */\nexport type TextTrackSegmentLoaderMessage = {\n type: 'load';\n track: TextTrack;\n range?: { start: number; end: number };\n};\n\n/** Finite states of the actor. */\nexport type TextTrackSegmentLoaderActorState = 'idle' | 'loading' | 'destroyed';\n\n/** Non-finite (extended) data managed by the actor. */\nexport interface TextTrackSegmentLoaderActorContext {\n /**\n * Track ID of the segment currently being fetched, or null. Paired\n * with `inFlightSegmentId` so the continue-vs-preempt check survives\n * cross-track segment-id collisions (e.g. each track starts at\n * `seg-0`).\n */\n inFlightTrackId: string | null;\n /**\n * Segment ID currently being fetched, or null. Used together with\n * `inFlightTrackId` by the `loading` state's `load` handler.\n */\n inFlightSegmentId: string | null;\n}\n\nexport type TextTrackSegmentLoaderActor = MessageActor<\n TextTrackSegmentLoaderActorState,\n TextTrackSegmentLoaderActorContext,\n TextTrackSegmentLoaderMessage\n>;\n\n/**\n * Resolves a text-track segment URL into the array of cues it contains.\n *\n * \"Resolve\" because the fn covers both network fetch and parse into the\n * domain model. Host-agnostic — the concrete resolver (e.g. the\n * browser's native VTT resolver) is supplied at engine-assembly time,\n * so this actor stays DOM-free.\n */\nexport type TextTrackSegmentResolver<C extends Cue = Cue> = (url: string) => Promise<C[]>;\n\n/**\n * Configuration for `createTextTrackSegmentLoaderActor`. Spread over\n * `DEFAULT_FORWARD_BUFFER_CONFIG` to override individual forward-window\n * fields (e.g. `bufferDuration`). Text tracks don't have a back-buffer\n * concern — cues evict by their playhead-relative window at runtime —\n * so no `backBuffer` config field.\n */\nexport interface TextTrackSegmentLoaderActorConfig {\n forwardBuffer?: Partial<ForwardBufferConfig>;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** Internal load-task descriptor — one segment fetch + dispatch unit. */\ninterface TextLoadTask {\n segment: Segment;\n trackId: string;\n}\n\n/**\n * Loads text-track segments for a track and delegates cue management\n * to a TextTracksActor. Mirrors the v/a `SegmentLoaderActor` shape (FSM\n * with `idle` / `loading` and `inFlight*` context for continue-vs-preempt),\n * adapted to text:\n *\n * - No init segment, no flush ops (text cues don't need eviction — they're\n * small and the playhead-relative window is enforced by the runtime).\n * - Single in-flight identity (`inFlightSegmentId`) — text has only the\n * media-segment path, no init-segment path.\n *\n * Planning is done in the load handler on every incoming message:\n * `getSegmentsToLoad` filters to the forward window, then the segments\n * not already in `TextTracksActor`'s context are scheduled. When a new\n * `load` arrives mid-run, the handler replans and either:\n *\n * - **Continues**: the in-flight segment is still in the new plan →\n * `abortPending` only, schedule the rest of the plan (minus the\n * in-flight item, which covers its slot).\n * - **Preempts**: in-flight segment is no longer wanted (track switch,\n * large seek out of window) → `abortAll`, schedule the new plan\n * from scratch.\n *\n * The cue parser is injected so this factory is host-agnostic. A DOM\n * host supplies a VTT parser backed by `<track>`/`TextTrack` APIs; a\n * non-DOM host (worker, test fake, alternate runtime) supplies its own.\n */\nexport function createTextTrackSegmentLoaderActor<C extends Cue>(\n textTracksActor: TextTracksActor<C>,\n resolveSegment: TextTrackSegmentResolver<C>,\n config: TextTrackSegmentLoaderActorConfig = {}\n): TextTrackSegmentLoaderActor {\n type UserState = Exclude<TextTrackSegmentLoaderActorState, 'destroyed'>;\n type Ctx = HandlerContext<UserState, TextTrackSegmentLoaderActorContext, () => SerialRunner>;\n\n const forwardBufferConfig: ForwardBufferConfig = { ...DEFAULT_FORWARD_BUFFER_CONFIG, ...config.forwardBuffer };\n\n /**\n * Translate a load message into an ordered TextLoadTask list based on\n * committed actor state. In-flight awareness is handled separately in\n * the `loading` state's load handler.\n *\n * Metadata mode (no `range`) is a no-op for text — text tracks have\n * no init-segment concept, so there's nothing to load until a range\n * arrives via `'full-range'` dispatch.\n */\n const planTasks = (message: TextTrackSegmentLoaderMessage): TextLoadTask[] => {\n const { track, range } = message;\n if (!range) return [];\n const trackId = track.id;\n // Peek (don't track) the snapshot: the loader's `send` is typically\n // invoked from inside the dispatcher's effect body, so a tracked\n // `.get()` here would leak the textTracksActor snapshot into the\n // dispatcher's dep set — every `add-cues` would re-fire the dispatcher\n // and schedule duplicate loads.\n const bufferedSegments = peek(textTracksActor.snapshot).context.segments[trackId] ?? [];\n // `getSegmentsToLoad` uses the actor's threaded `forwardBufferConfig`\n // to compute its forward-window; only `range.start` is consulted\n // here as the playhead anchor. `range.end` is informational — it\n // carries the dispatcher's intended window upper bound but doesn't\n // override the actor's planning.\n const segmentsToLoad = getSegmentsToLoad(track.segments, bufferedSegments, range.start, forwardBufferConfig);\n return segmentsToLoad.map((segment) => ({ segment, trackId }));\n };\n\n /**\n * Wraps a TextLoadTask into a Task that fetches + dispatches `add-cues`.\n * Updates `inFlightSegmentId` around the fetch so the load handler can\n * make accurate continue/preempt decisions.\n */\n const makeLoadTask = (op: TextLoadTask, { getContext, setContext }: Ctx): Task<void> => {\n return new Task(async (signal) => {\n if (signal.aborted) return;\n setContext({ ...getContext(), inFlightTrackId: op.trackId, inFlightSegmentId: op.segment.id });\n try {\n const cues = await resolveSegment(op.segment.url);\n if (signal.aborted) return;\n textTracksActor.send({\n type: 'add-cues',\n meta: {\n trackId: op.trackId,\n id: op.segment.id,\n startTime: op.segment.startTime,\n duration: op.segment.duration,\n },\n cues,\n });\n } catch (error) {\n // Graceful degradation: log and continue to the next segment.\n console.error('Failed to load text-track segment:', error);\n } finally {\n setContext({ ...getContext(), inFlightTrackId: null, inFlightSegmentId: null });\n }\n });\n };\n\n const scheduleAll = (tasks: TextLoadTask[], ctx: Ctx): void => {\n for (const op of tasks) {\n ctx.runner.schedule(makeLoadTask(op, ctx)).then(undefined, (e: unknown) => {\n if (e instanceof Error && e.name === 'AbortError') return;\n console.error('Unexpected error in text-track segment loader:', e);\n ctx.runner.abortPending();\n });\n }\n };\n\n return createMachineActor<\n UserState,\n TextTrackSegmentLoaderActorContext,\n TextTrackSegmentLoaderMessage,\n () => SerialRunner\n >({\n runner: () => new SerialRunner(),\n initial: 'idle',\n context: { inFlightTrackId: null, inFlightSegmentId: null },\n states: {\n idle: {\n on: {\n load: (msg, ctx) => {\n const tasks = planTasks(msg);\n if (tasks.length === 0) return;\n ctx.transition('loading');\n scheduleAll(tasks, ctx);\n },\n },\n },\n loading: {\n onSettled: 'idle',\n on: {\n load: (msg, ctx) => {\n const { context, runner } = ctx;\n const tasks = planTasks(msg);\n\n const inFlightStillNeeded =\n context.inFlightTrackId !== null &&\n context.inFlightSegmentId !== null &&\n tasks.some((t) => t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId);\n\n if (inFlightStillNeeded) {\n // Continue: keep the in-flight fetch, schedule the rest.\n runner.abortPending();\n scheduleAll(\n tasks.filter(\n (t) => !(t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)\n ),\n ctx\n );\n } else {\n // Preempt: abort everything (including in-flight) and replan.\n runner.abortAll();\n scheduleAll(tasks, ctx);\n }\n },\n },\n },\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+GA,SAAgB,kCACd,iBACA,gBACA,SAA4C,EAAE,EACjB;CAI7B,MAAM,sBAA2C;EAAE,GAAG;EAA+B,GAAG,OAAO;EAAe;;;;;;;;;;CAW9G,MAAM,aAAa,YAA2D;EAC5E,MAAM,EAAE,OAAO,UAAU;AACzB,MAAI,CAAC,MAAO,QAAO,EAAE;EACrB,MAAM,UAAU,MAAM;EAMtB,MAAM,mBAAmB,KAAK,gBAAgB,SAAS,CAAC,QAAQ,SAAS,YAAY,EAAE;AAOvF,SADuB,kBAAkB,MAAM,UAAU,kBAAkB,MAAM,OAAO,oBAAoB,CACtF,KAAK,aAAa;GAAE;GAAS;GAAS,EAAE;;;;;;;CAQhE,MAAM,gBAAgB,IAAkB,EAAE,YAAY,iBAAkC;AACtF,SAAO,IAAI,KAAK,OAAO,WAAW;AAChC,OAAI,OAAO,QAAS;AACpB,cAAW;IAAE,GAAG,YAAY;IAAE,iBAAiB,GAAG;IAAS,mBAAmB,GAAG,QAAQ;IAAI,CAAC;AAC9F,OAAI;IACF,MAAM,OAAO,MAAM,eAAe,GAAG,QAAQ,IAAI;AACjD,QAAI,OAAO,QAAS;AACpB,oBAAgB,KAAK;KACnB,MAAM;KACN,MAAM;MACJ,SAAS,GAAG;MACZ,IAAI,GAAG,QAAQ;MACf,WAAW,GAAG,QAAQ;MACtB,UAAU,GAAG,QAAQ;MACtB;KACD;KACD,CAAC;YACK,OAAO;AAEd,YAAQ,MAAM,sCAAsC,MAAM;aAClD;AACR,eAAW;KAAE,GAAG,YAAY;KAAE,iBAAiB;KAAM,mBAAmB;KAAM,CAAC;;IAEjF;;CAGJ,MAAM,eAAe,OAAuB,QAAmB;AAC7D,OAAK,MAAM,MAAM,MACf,KAAI,OAAO,SAAS,aAAa,IAAI,IAAI,CAAC,CAAC,KAAK,KAAA,IAAY,MAAe;AACzE,OAAI,aAAa,SAAS,EAAE,SAAS,aAAc;AACnD,WAAQ,MAAM,kDAAkD,EAAE;AAClE,OAAI,OAAO,cAAc;IACzB;;AAIN,QAAO,mBAKL;EACA,cAAc,IAAI,cAAc;EAChC,SAAS;EACT,SAAS;GAAE,iBAAiB;GAAM,mBAAmB;GAAM;EAC3D,QAAQ;GACN,MAAM,EACJ,IAAI,EACF,OAAO,KAAK,QAAQ;IAClB,MAAM,QAAQ,UAAU,IAAI;AAC5B,QAAI,MAAM,WAAW,EAAG;AACxB,QAAI,WAAW,UAAU;AACzB,gBAAY,OAAO,IAAI;MAE1B,EACF;GACD,SAAS;IACP,WAAW;IACX,IAAI,EACF,OAAO,KAAK,QAAQ;KAClB,MAAM,EAAE,SAAS,WAAW;KAC5B,MAAM,QAAQ,UAAU,IAAI;AAO5B,SAJE,QAAQ,oBAAoB,QAC5B,QAAQ,sBAAsB,QAC9B,MAAM,MAAM,MAAM,EAAE,YAAY,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,QAAQ,kBAAkB,EAE/E;AAEvB,aAAO,cAAc;AACrB,kBACE,MAAM,QACH,MAAM,EAAE,EAAE,YAAY,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,QAAQ,mBAC5E,EACD,IACD;YACI;AAEL,aAAO,UAAU;AACjB,kBAAY,OAAO,IAAI;;OAG5B;IACF;GACF;EACF,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TransitionActor } from "../../core/actors/create-transition-actor.js";
|
|
2
|
+
import { Cue, Segment } from "../../media/types/index.js";
|
|
3
|
+
|
|
4
|
+
//#region src/playback/actors/text-tracks.d.ts
|
|
5
|
+
/** Segment identity and timing — mirrors AppendSegmentMeta without trackId (keyed separately). */
|
|
6
|
+
type CueSegmentMeta = Pick<Segment, 'id' | 'startTime' | 'duration'> & {
|
|
7
|
+
trackId: string;
|
|
8
|
+
};
|
|
9
|
+
/** Non-finite (extended) data managed by the actor — the XState "context". */
|
|
10
|
+
interface TextTracksActorContext {
|
|
11
|
+
/** Cues added per track ID. Used for duplicate detection and snapshot observability. */
|
|
12
|
+
loaded: Record<string, Cue[]>;
|
|
13
|
+
/** Segments whose cues have been fully added, keyed by track ID. Used for load planning. */
|
|
14
|
+
segments: Record<string, Array<Pick<Segment, 'id' | 'startTime' | 'duration'>>>;
|
|
15
|
+
}
|
|
16
|
+
interface AddCuesMessage<C extends Cue = Cue> {
|
|
17
|
+
type: 'add-cues';
|
|
18
|
+
meta: CueSegmentMeta;
|
|
19
|
+
cues: C[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wipe the actor's `loaded` + `segments` context. Sent on source reset
|
|
23
|
+
* (typically by `syncTextTracks` on state exit) so a subsequent
|
|
24
|
+
* presentation starts with a fresh cue+segment cache. Without this, a
|
|
25
|
+
* new presentation reusing trackIds from the prior source would have
|
|
26
|
+
* `getSegmentsToLoad` treat its segments as already-buffered and skip
|
|
27
|
+
* loading them.
|
|
28
|
+
*/
|
|
29
|
+
interface ClearMessage {
|
|
30
|
+
type: 'clear';
|
|
31
|
+
}
|
|
32
|
+
type TextTracksActorMessage<C extends Cue = Cue> = AddCuesMessage<C> | ClearMessage;
|
|
33
|
+
/**
|
|
34
|
+
* Host-agnostic text-track actor type.
|
|
35
|
+
*
|
|
36
|
+
* Generic over the concrete cue shape `C` so DOM hosts (using `VTTCue`)
|
|
37
|
+
* and non-DOM hosts (custom cue representations) can both satisfy it.
|
|
38
|
+
*/
|
|
39
|
+
type TextTracksActor<C extends Cue = Cue> = TransitionActor<TextTracksActorContext, TextTracksActorMessage<C>>;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { AddCuesMessage, ClearMessage, CueSegmentMeta, TextTracksActor, TextTracksActorContext, TextTracksActorMessage };
|
|
42
|
+
//# sourceMappingURL=text-tracks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-tracks.d.ts","names":[],"sources":["../../../../src/playback/actors/text-tracks.ts"],"mappings":";;;;;KAQY,cAAA,GAAiB,IAAA,CAAK,OAAA;EAA8C,OAAA;AAAA;;UAG/D,sBAAA;EAHY;EAK3B,MAAA,EAAQ,MAAA,SAAe,GAAA;EALuD;EAO9E,QAAA,EAAU,MAAA,SAAe,KAAA,CAAM,IAAA,CAAK,OAAA;AAAA;AAAA,UAGrB,cAAA,WAAyB,GAAA,GAAM,GAAA;EAC9C,IAAA;EACA,IAAA,EAAM,cAAA;EACN,IAAA,EAAM,CAAA;AAAA;;;;;;;;;UAWS,YAAA;EACf,IAAA;AAAA;AAAA,KAGU,sBAAA,WAAiC,GAAA,GAAM,GAAA,IAAO,cAAA,CAAe,CAAA,IAAK,YAAA;;;;;AAlB9E;;KA0BY,eAAA,WAA0B,GAAA,GAAM,GAAA,IAAO,eAAA,CAAgB,sBAAA,EAAwB,sBAAA,CAAuB,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MaybeResolvedPresentation } from "../../media/types/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/playback/behaviors/calculate-presentation-duration.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Input shape passed to the duration resolver. Represents the union of
|
|
6
|
+
* track-selection slots the default `getResolvedSelectedTrackDuration`
|
|
7
|
+
* resolver inspects to pick a representative resolved track. Variants
|
|
8
|
+
* that compose neither audio nor video selection still satisfy this
|
|
9
|
+
* shape — the missing fields read as `undefined` and the resolver
|
|
10
|
+
* falls through.
|
|
11
|
+
*/
|
|
12
|
+
interface PresentationDurationState {
|
|
13
|
+
presentation?: MaybeResolvedPresentation;
|
|
14
|
+
selectedVideoTrackId?: string;
|
|
15
|
+
selectedAudioTrackId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolver supplied via `config.resolveDuration`. Returns the duration to
|
|
19
|
+
* write to `presentation.duration`, or `undefined` when it isn't yet
|
|
20
|
+
* derivable. Positive `Infinity` is the canonical live value.
|
|
21
|
+
*/
|
|
22
|
+
type PresentationDurationResolver = (state: PresentationDurationState) => number | undefined;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { PresentationDurationResolver };
|
|
25
|
+
//# sourceMappingURL=calculate-presentation-duration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculate-presentation-duration.d.ts","names":[],"sources":["../../../../src/playback/behaviors/calculate-presentation-duration.ts"],"mappings":";;;;;;;;;;;UA0CiB,yBAAA;EACf,YAAA,GAAe,yBAAA;EACf,oBAAA;EACA,oBAAA;AAAA;;;;;;KAQU,4BAAA,IAAgC,KAAA,EAAO,yBAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { untrack, update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { effect } from "../../core/signals/effect.js";
|
|
3
|
+
//#region src/playback/behaviors/calculate-presentation-duration.ts
|
|
4
|
+
function calculatePresentationDurationSetup({ state, config }) {
|
|
5
|
+
return effect(() => {
|
|
6
|
+
const presentation = state.presentation.get();
|
|
7
|
+
if (!presentation || presentation.duration !== void 0) return;
|
|
8
|
+
const resolverInput = untrack(() => ({
|
|
9
|
+
presentation,
|
|
10
|
+
selectedVideoTrackId: state.selectedVideoTrackId?.get(),
|
|
11
|
+
selectedAudioTrackId: state.selectedAudioTrackId?.get()
|
|
12
|
+
}));
|
|
13
|
+
const duration = config.resolveDuration(resolverInput);
|
|
14
|
+
if (duration === void 0 || Number.isNaN(duration) || duration <= 0) return;
|
|
15
|
+
update(state.presentation, { duration });
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `calculatePresentationDuration` uses a manual `Behavior<>` literal
|
|
20
|
+
* (rather than `defineBehavior`) so it can declare just `presentation`
|
|
21
|
+
* in its stateKeys while the typed setup-param shape includes the
|
|
22
|
+
* optional `selectedVideoTrackId` / `selectedAudioTrackId` reads used
|
|
23
|
+
* at runtime. Mirrors the pattern in `endOfStream` for the same
|
|
24
|
+
* reason: the behavior is uniform-across-tracks and reads slots
|
|
25
|
+
* contributed by other behaviors, so it shouldn't leak those slot
|
|
26
|
+
* declarations into variants that don't compose the contributors.
|
|
27
|
+
*/
|
|
28
|
+
const calculatePresentationDuration = {
|
|
29
|
+
stateKeys: ["presentation"],
|
|
30
|
+
contextKeys: [],
|
|
31
|
+
setup: calculatePresentationDurationSetup
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { calculatePresentationDuration };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=calculate-presentation-duration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculate-presentation-duration.js","names":[],"sources":["../../../../src/playback/behaviors/calculate-presentation-duration.ts"],"sourcesContent":["/**\n * **Populate presentation duration via a config-supplied resolver.**\n *\n * Once a presentation is in place and its `duration` is still undefined,\n * calls `config.resolveDuration(state)` whenever a tracked slot changes. The\n * resolver decides what counts as \"duration is now derivable\" for the variant\n * in play; the behavior itself stays variant-agnostic:\n *\n * - **VoD** — derive from the first resolved selected track's `duration`\n * (video preferred, audio fallback). The HLS engine wires\n * `getResolvedSelectedTrackDuration` from `media/utils/track-selection.ts`\n * as the default. Audio-only falls out of the same resolver naturally\n * (no video selected → audio is the first resolved track).\n * - **Live** — return `Number.POSITIVE_INFINITY` once the presentation is\n * established as live. This is the MSE-spec value for `mediaSource.duration`\n * under live playback; downstream `updateMediaSourceDuration` propagates it through.\n *\n * Validation: writes whatever the resolver returns as long as it's a positive\n * number, including `Infinity`. `undefined` / `NaN` / `<= 0` are skipped.\n *\n * Fires at most once per presentation — an already-set `duration` is never\n * overwritten, and the next reset arrives structurally when a new\n * (unresolved) presentation replaces the current one. The resolver may\n * return `undefined` while duration is still indeterminate; subsequent\n * tracked-slot changes re-run the effect until the resolver commits a value.\n *\n * Downstream of `resolveVideoTrack` / `resolveAudioTrack`; upstream of\n * `updateMediaSourceDuration` (which writes the value through to `mediaSource.duration`).\n */\nimport type { Behavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { type ReadonlySignal, type Signal, untrack, update } from '../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../media/types';\n\n/**\n * Input shape passed to the duration resolver. Represents the union of\n * track-selection slots the default `getResolvedSelectedTrackDuration`\n * resolver inspects to pick a representative resolved track. Variants\n * that compose neither audio nor video selection still satisfy this\n * shape — the missing fields read as `undefined` and the resolver\n * falls through.\n */\nexport interface PresentationDurationState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * Resolver supplied via `config.resolveDuration`. Returns the duration to\n * write to `presentation.duration`, or `undefined` when it isn't yet\n * derivable. Positive `Infinity` is the canonical live value.\n */\nexport type PresentationDurationResolver = (state: PresentationDurationState) => number | undefined;\n\nexport interface PresentationDurationConfig {\n resolveDuration: PresentationDurationResolver;\n}\n\nfunction calculatePresentationDurationSetup({\n state,\n config,\n}: {\n state: {\n presentation: Signal<PresentationDurationState['presentation']>;\n // selectedVideoTrackId / selectedAudioTrackId are read defensively —\n // they are *not* declared in this behavior's stateKeys. The slots are\n // contributed by other behaviors (`switchVideoTrack` writes the\n // video slot in the default engine; `selectAudioTrack` writes the\n // audio slot) which compose conditionally per engine variant. Treating\n // each signal as optional lets calculatePresentationDuration stay\n // variant-agnostic — it feeds whatever's present to the resolver and\n // lets the resolver decide.\n selectedVideoTrackId?: ReadonlySignal<PresentationDurationState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<PresentationDurationState['selectedAudioTrackId']>;\n };\n config: PresentationDurationConfig;\n}): () => void {\n return effect(() => {\n const presentation = state.presentation.get();\n if (!presentation || presentation.duration !== undefined) return;\n\n // presentation drives the effect; selection-only changes can't yield a\n // writable duration on their own (resolver returns undefined until a\n // track resolves, at which point resolve-track writes back through\n // state.presentation anyway), so we read the rest untracked.\n const resolverInput: PresentationDurationState = untrack(() => ({\n presentation,\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n }));\n const duration = config.resolveDuration(resolverInput);\n if (duration === undefined || Number.isNaN(duration) || duration <= 0) return;\n\n // Patch-object form requires `T extends object`; `state.presentation`'s\n // T is `MaybeResolvedPresentation | undefined`, which doesn't satisfy\n // the constraint. The guard at the top of the effect already\n // established that the slot holds a defined value, so cast to narrow\n // the signal type and use the cleaner merge form.\n update(state.presentation as Signal<MaybeResolvedPresentation>, { duration });\n });\n}\n\n/**\n * `calculatePresentationDuration` uses a manual `Behavior<>` literal\n * (rather than `defineBehavior`) so it can declare just `presentation`\n * in its stateKeys while the typed setup-param shape includes the\n * optional `selectedVideoTrackId` / `selectedAudioTrackId` reads used\n * at runtime. Mirrors the pattern in `endOfStream` for the same\n * reason: the behavior is uniform-across-tracks and reads slots\n * contributed by other behaviors, so it shouldn't leak those slot\n * declarations into variants that don't compose the contributors.\n */\nexport const calculatePresentationDuration: Behavior<\n { presentation: Signal<PresentationDurationState['presentation']> },\n Record<string, never>,\n PresentationDurationConfig\n> = {\n stateKeys: ['presentation'],\n contextKeys: [],\n setup: calculatePresentationDurationSetup,\n};\n"],"mappings":";;;AA2DA,SAAS,mCAAmC,EAC1C,OACA,UAgBa;AACb,QAAO,aAAa;EAClB,MAAM,eAAe,MAAM,aAAa,KAAK;AAC7C,MAAI,CAAC,gBAAgB,aAAa,aAAa,KAAA,EAAW;EAM1D,MAAM,gBAA2C,eAAe;GAC9D;GACA,sBAAsB,MAAM,sBAAsB,KAAK;GACvD,sBAAsB,MAAM,sBAAsB,KAAK;GACxD,EAAE;EACH,MAAM,WAAW,OAAO,gBAAgB,cAAc;AACtD,MAAI,aAAa,KAAA,KAAa,OAAO,MAAM,SAAS,IAAI,YAAY,EAAG;AAOvE,SAAO,MAAM,cAAmD,EAAE,UAAU,CAAC;GAC7E;;;;;;;;;;;;AAaJ,MAAa,gCAIT;CACF,WAAW,CAAC,eAAe;CAC3B,aAAa,EAAE;CACf,OAAO;CACR"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { computed, peek } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedPresentation } from "../../media/types/index.js";
|
|
5
|
+
import { getCdnId, getOrderedCdnIds } from "../../media/utils/cdn.js";
|
|
6
|
+
//#region src/playback/behaviors/derive-cdn-priority.ts
|
|
7
|
+
/**
|
|
8
|
+
* **Session-level CDN priority.** While a presentation is resolved, owns the
|
|
9
|
+
* `cdnPriority` signal: the distinct CDNs the source is served from (origin of
|
|
10
|
+
* each track's URL), in manifest priority order — most-preferred first. Cleared
|
|
11
|
+
* on src unload. The name mirrors HLS content steering's `PATHWAY-PRIORITY`.
|
|
12
|
+
*
|
|
13
|
+
* Redundant-stream sources (e.g. Mux's `?redundant_streams=true`) list the same
|
|
14
|
+
* content on multiple hosts, so the candidate tracks already include one variant
|
|
15
|
+
* per CDN. This behavior publishes *which* CDNs exist and their priority; the
|
|
16
|
+
* `preferActiveCdn` scope rule in `track-switching` reads `cdnPriority` and
|
|
17
|
+
* narrows each type's candidates to the first CDN with surviving tracks — so
|
|
18
|
+
* video / audio / text all resolve from one host (the shared list is the
|
|
19
|
+
* per-presentation coherence guarantee).
|
|
20
|
+
*
|
|
21
|
+
* The "active" CDN is not stored — it's derived by the scope as the
|
|
22
|
+
* highest-priority entry in `cdnPriority` that still has tracks after the
|
|
23
|
+
* constraints pre-pass. That makes failover a pure consequence of the (future)
|
|
24
|
+
* failed-CDN constraint: when the primary's tracks are pruned during cooldown,
|
|
25
|
+
* the scope falls to the next CDN; when the primary recovers, it snaps back.
|
|
26
|
+
* Content steering, when it lands, reorders `cdnPriority` (pathway priority as a
|
|
27
|
+
* sort key).
|
|
28
|
+
*
|
|
29
|
+
* Lifecycle: `'presentation-unresolved'` ↔ `'presentation-resolved'`, mirroring
|
|
30
|
+
* `setupTrackSwitching`. The resolved state owns the signal; its entry-returned
|
|
31
|
+
* cleanup clears it on exit (canonical cleanup-binds-to-setup per `reactors.md`).
|
|
32
|
+
*/
|
|
33
|
+
const samePriority = (a, b) => !!a && a.length === b.length && a.every((cdn, i) => cdn === b[i]);
|
|
34
|
+
/**
|
|
35
|
+
* Manage `cdnPriority`: publish the manifest-ordered CDN list on src load, clear
|
|
36
|
+
* on src unload.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const reactor = deriveCdnPriority.setup({ state });
|
|
40
|
+
*/
|
|
41
|
+
const deriveCdnPriority = defineBehavior({
|
|
42
|
+
stateKeys: ["presentation", "cdnPriority"],
|
|
43
|
+
contextKeys: [],
|
|
44
|
+
setup: ({ state, config = {} }) => {
|
|
45
|
+
const getCdnId$1 = config.getCdnId ?? getCdnId;
|
|
46
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
47
|
+
return createMachineReactor({
|
|
48
|
+
initial: "presentation-unresolved",
|
|
49
|
+
monitor: () => derivedStateSignal.get(),
|
|
50
|
+
states: {
|
|
51
|
+
"presentation-unresolved": {},
|
|
52
|
+
"presentation-resolved": {
|
|
53
|
+
entry: () => () => state.cdnPriority.set(void 0),
|
|
54
|
+
effects: [() => {
|
|
55
|
+
const presentation = state.presentation.get();
|
|
56
|
+
if (!isResolvedPresentation(presentation)) return;
|
|
57
|
+
const next = getOrderedCdnIds(presentation, getCdnId$1);
|
|
58
|
+
if (!samePriority(peek(state.cdnPriority), next)) state.cdnPriority.set(next);
|
|
59
|
+
}]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
//#endregion
|
|
66
|
+
export { deriveCdnPriority };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=derive-cdn-priority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive-cdn-priority.js","names":["getCdnId","defaultGetCdnId"],"sources":["../../../../src/playback/behaviors/derive-cdn-priority.ts"],"sourcesContent":["/**\n * **Session-level CDN priority.** While a presentation is resolved, owns the\n * `cdnPriority` signal: the distinct CDNs the source is served from (origin of\n * each track's URL), in manifest priority order — most-preferred first. Cleared\n * on src unload. The name mirrors HLS content steering's `PATHWAY-PRIORITY`.\n *\n * Redundant-stream sources (e.g. Mux's `?redundant_streams=true`) list the same\n * content on multiple hosts, so the candidate tracks already include one variant\n * per CDN. This behavior publishes *which* CDNs exist and their priority; the\n * `preferActiveCdn` scope rule in `track-switching` reads `cdnPriority` and\n * narrows each type's candidates to the first CDN with surviving tracks — so\n * video / audio / text all resolve from one host (the shared list is the\n * per-presentation coherence guarantee).\n *\n * The \"active\" CDN is not stored — it's derived by the scope as the\n * highest-priority entry in `cdnPriority` that still has tracks after the\n * constraints pre-pass. That makes failover a pure consequence of the (future)\n * failed-CDN constraint: when the primary's tracks are pruned during cooldown,\n * the scope falls to the next CDN; when the primary recovers, it snaps back.\n * Content steering, when it lands, reorders `cdnPriority` (pathway priority as a\n * sort key).\n *\n * Lifecycle: `'presentation-unresolved'` ↔ `'presentation-resolved'`, mirroring\n * `setupTrackSwitching`. The resolved state owns the signal; its entry-returned\n * cleanup clears it on exit (canonical cleanup-binds-to-setup per `reactors.md`).\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\nimport { getCdnId as defaultGetCdnId, type GetCdnId, getOrderedCdnIds } from '../../media/utils/cdn';\n\nexport interface DeriveCdnPriorityState {\n presentation?: MaybeResolvedPresentation;\n cdnPriority?: string[];\n}\n\nconst samePriority = (a: string[] | undefined, b: string[]): boolean =>\n !!a && a.length === b.length && a.every((cdn, i) => cdn === b[i]);\n\n/**\n * Manage `cdnPriority`: publish the manifest-ordered CDN list on src load, clear\n * on src unload.\n *\n * @example\n * const reactor = deriveCdnPriority.setup({ state });\n */\nexport const deriveCdnPriority = defineBehavior({\n stateKeys: ['presentation', 'cdnPriority'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: {\n presentation: ReadonlySignal<DeriveCdnPriorityState['presentation']>;\n cdnPriority: Signal<DeriveCdnPriorityState['cdnPriority']>;\n };\n config?: { getCdnId?: GetCdnId };\n }) => {\n const getCdnId = config.getCdnId ?? defaultGetCdnId;\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Cleanup-binds-to-setup: the CDN priority list is valid for exactly\n // 'presentation-resolved'. Clear fires on exit (src unload + destroy).\n entry: () => () => state.cdnPriority.set(undefined),\n effects: [\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return;\n const next = getOrderedCdnIds(presentation, getCdnId);\n // Skip the write when the CDN set is unchanged — a live reload swaps\n // in a new presentation object with the same hosts, and re-setting a\n // fresh array would re-fire the scope for an identical result.\n // `peek` reads without subscribing, so this never self-triggers.\n if (!samePriority(peek(state.cdnPriority), next)) state.cdnPriority.set(next);\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAM,gBAAgB,GAAyB,MAC7C,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,KAAK,MAAM,QAAQ,EAAE,GAAG;;;;;;;;AASnE,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,cAAc;CAC1C,aAAa,EAAE;CACf,QAAQ,EACN,OACA,SAAS,EAAE,OAOP;EACJ,MAAMA,aAAW,OAAO,YAAYC;EACpC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,KAAK,CAAC,GAC3C,0BACA,0BACN;AAED,SAAO,qBAAqB;GAC1B,SAAS;GACT,eAAe,mBAAmB,KAAK;GACvC,QAAQ;IACN,2BAA2B,EAAE;IAC7B,yBAAyB;KAGvB,mBAAmB,MAAM,YAAY,IAAI,KAAA,EAAU;KACnD,SAAS,OACD;MACJ,MAAM,eAAe,MAAM,aAAa,KAAK;AAC7C,UAAI,CAAC,uBAAuB,aAAa,CAAE;MAC3C,MAAM,OAAO,iBAAiB,cAAcD,WAAS;AAKrD,UAAI,CAAC,aAAa,KAAK,MAAM,YAAY,EAAE,KAAK,CAAE,OAAM,YAAY,IAAI,KAAK;OAEhF;KACF;IACF;GACF,CAAC;;CAEL,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { computed, signal } from "../../../core/signals/primitives.js";
|
|
2
|
+
import { effect } from "../../../core/signals/effect.js";
|
|
3
|
+
import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedTrack } from "../../../media/types/index.js";
|
|
5
|
+
import { findTrackById } from "../../../media/utils/tracks.js";
|
|
6
|
+
import { onMediaSourceReadyStateChange } from "../../../media/dom/mse/mediasource-setup.js";
|
|
7
|
+
import { getMaxBufferedEnd, waitForSourceBuffersReady } from "../../../media/dom/mse/duration.js";
|
|
8
|
+
import { isLastSegmentAppended } from "../../../media/dom/mse/end-of-stream.js";
|
|
9
|
+
//#region src/playback/behaviors/dom/end-of-stream.ts
|
|
10
|
+
function deriveState(presentation, mediaSource, msIsOpen, videoBufferActor, audioBufferActor, currentTime) {
|
|
11
|
+
if (!mediaSource || !presentation || !msIsOpen) return "preconditions-unmet";
|
|
12
|
+
const actors = [videoBufferActor, audioBufferActor].filter((a) => a !== void 0);
|
|
13
|
+
if (actors.length === 0) return "preconditions-unmet";
|
|
14
|
+
let lastSegStart;
|
|
15
|
+
for (const actor of actors) {
|
|
16
|
+
const snapshot = actor.snapshot.get();
|
|
17
|
+
if (snapshot.value !== "idle") return "preconditions-unmet";
|
|
18
|
+
const { initTrackId, segments: appended } = snapshot.context;
|
|
19
|
+
if (!initTrackId) return "preconditions-unmet";
|
|
20
|
+
const track = findTrackById(presentation, initTrackId);
|
|
21
|
+
if (!track || !isResolvedTrack(track)) return "preconditions-unmet";
|
|
22
|
+
if (!isLastSegmentAppended(track.segments, appended)) return "preconditions-unmet";
|
|
23
|
+
if (track.segments.length > 0) {
|
|
24
|
+
const start = track.segments[track.segments.length - 1].startTime;
|
|
25
|
+
if (lastSegStart === void 0 || start > lastSegStart) lastSegStart = start;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (lastSegStart !== void 0 && (currentTime ?? 0) < lastSegStart) return "preconditions-unmet";
|
|
29
|
+
return "eos-ready";
|
|
30
|
+
}
|
|
31
|
+
function endOfStreamSetup({ state, context }) {
|
|
32
|
+
const msIsOpen = signal(false);
|
|
33
|
+
const cleanupMsListener = effect(() => {
|
|
34
|
+
const mediaSource = context.mediaSource.get();
|
|
35
|
+
if (!mediaSource) {
|
|
36
|
+
msIsOpen.set(false);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
msIsOpen.set(mediaSource.readyState === "open");
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
onMediaSourceReadyStateChange(mediaSource, controller.signal, (rs) => {
|
|
42
|
+
msIsOpen.set(rs === "open");
|
|
43
|
+
});
|
|
44
|
+
return () => controller.abort();
|
|
45
|
+
});
|
|
46
|
+
const derivedStateSignal = computed(() => deriveState(state.presentation.get(), context.mediaSource.get(), msIsOpen.get(), context.videoBufferActor?.get(), context.audioBufferActor?.get(), state.currentTime.get()));
|
|
47
|
+
const reactor = createMachineReactor({
|
|
48
|
+
initial: "preconditions-unmet",
|
|
49
|
+
monitor: () => derivedStateSignal.get(),
|
|
50
|
+
states: {
|
|
51
|
+
"preconditions-unmet": {},
|
|
52
|
+
"eos-ready": { entry: () => {
|
|
53
|
+
const mediaSource = context.mediaSource.get();
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const endStreamWhenReady = async () => {
|
|
56
|
+
await waitForSourceBuffersReady(mediaSource.sourceBuffers, controller.signal);
|
|
57
|
+
if (controller.signal.aborted) return;
|
|
58
|
+
const bufferedEnd = getMaxBufferedEnd(mediaSource.sourceBuffers);
|
|
59
|
+
if (bufferedEnd > 0) mediaSource.duration = bufferedEnd;
|
|
60
|
+
mediaSource.endOfStream();
|
|
61
|
+
};
|
|
62
|
+
endStreamWhenReady().catch((err) => console.error("Failed to call endOfStream:", err));
|
|
63
|
+
return () => controller.abort();
|
|
64
|
+
} }
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
cleanupMsListener();
|
|
69
|
+
reactor.destroy();
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `endOfStream` uses a manual `Behavior<>` literal (rather than
|
|
74
|
+
* `defineBehavior`) because it reads `videoBufferActor` /
|
|
75
|
+
* `audioBufferActor` defensively without declaring them in its
|
|
76
|
+
* contextKeys — those slots are contributed by other behaviors and
|
|
77
|
+
* compose conditionally per engine variant. The `Behavior<>` literal
|
|
78
|
+
* opts out of the exhaustiveness check so the typed context shape can
|
|
79
|
+
* include the optional fields used at runtime. See the comment on
|
|
80
|
+
* `endOfStreamSetup`'s context param for the discipline.
|
|
81
|
+
*/
|
|
82
|
+
const endOfStream = {
|
|
83
|
+
stateKeys: ["presentation", "currentTime"],
|
|
84
|
+
contextKeys: ["mediaSource"],
|
|
85
|
+
setup: endOfStreamSetup
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
export { endOfStream };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=end-of-stream.js.map
|