@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,94 @@
|
|
|
1
|
+
import { computed } 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 { listen } from "@videojs/utils/dom";
|
|
5
|
+
//#region src/playback/behaviors/dom/track-load-triggers.ts
|
|
6
|
+
/**
|
|
7
|
+
* Slot-driven FSM. The slot `loadActivated` is the canonical externally-
|
|
8
|
+
* observable state; `deriveState` reads it (and the preconditions) to
|
|
9
|
+
* derive the local FSM state. The slot is *both* the state and the data —
|
|
10
|
+
* no separate internal bookkeeping.
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* 'preconditions-unmet' ⟷ 'monitoring' ⟷ 'load-active'
|
|
14
|
+
*
|
|
15
|
+
* preconditions-unmet → monitoring element + URL appear
|
|
16
|
+
* monitoring → load-active slot flips true (listener fires
|
|
17
|
+
* or external write)
|
|
18
|
+
* load-active → monitoring within-state cleanup resets slot
|
|
19
|
+
* (URL or element identity change)
|
|
20
|
+
* any → preconditions-unmet element or URL → undefined
|
|
21
|
+
*
|
|
22
|
+
* any state → destroying → destroyed on destroy()
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function deriveState(presentation, mediaElement, loadActivated) {
|
|
26
|
+
if (!mediaElement || !presentation?.url) return "preconditions-unmet";
|
|
27
|
+
if (loadActivated) return "load-active";
|
|
28
|
+
return "monitoring";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Track preload-overriding events per source.
|
|
32
|
+
*
|
|
33
|
+
* Writes `state.loadActivated = true` the first time a `play` or `seeking`
|
|
34
|
+
* event fires on the attached media element for the current source — or
|
|
35
|
+
* immediately on entry if the element is already in such a state
|
|
36
|
+
* (`!el.paused` or `el.seeking`), mirroring autoplay / native-controls /
|
|
37
|
+
* direct-DOM-`play()` scenarios.
|
|
38
|
+
*
|
|
39
|
+
* Sticky-true *within a source identity*: subsequent play/pause/seek
|
|
40
|
+
* cycles don't flip back. Source identity = (mediaElement, presentation
|
|
41
|
+
* URL). Either changing — including direct in-place swap with no
|
|
42
|
+
* `undefined` intermediate — resets the slot to `false`.
|
|
43
|
+
*
|
|
44
|
+
* Multi-writer with `hls/adapter.ts:play()` (which writes `true` directly
|
|
45
|
+
* on programmatic play) is intentional — different domains. The adapter
|
|
46
|
+
* records programmatic intent; this behavior is the DOM-side observer.
|
|
47
|
+
* Pre-existing `true` writes are honored because `deriveState` reads the
|
|
48
|
+
* slot — a `true` value routes directly to `'load-active'` without
|
|
49
|
+
* entering `'monitoring'`.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const reactor = trackLoadTriggers.setup({ state, context });
|
|
53
|
+
* // later:
|
|
54
|
+
* reactor.destroy();
|
|
55
|
+
*/
|
|
56
|
+
function trackLoadTriggersSetup({ state, context }) {
|
|
57
|
+
const derivedStateSignal = computed(() => deriveState(state.presentation.get(), context.mediaElement.get(), state.loadActivated.get()));
|
|
58
|
+
const urlSignal = computed(() => state.presentation.get()?.url);
|
|
59
|
+
return createMachineReactor({
|
|
60
|
+
initial: "preconditions-unmet",
|
|
61
|
+
monitor: () => derivedStateSignal.get(),
|
|
62
|
+
states: {
|
|
63
|
+
"preconditions-unmet": {},
|
|
64
|
+
monitoring: { effects: () => {
|
|
65
|
+
const el = context.mediaElement.get();
|
|
66
|
+
const setLoadActivated = () => state.loadActivated.set(true);
|
|
67
|
+
if (!el.paused || el.seeking) {
|
|
68
|
+
setLoadActivated();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const cleanupPlay = listen(el, "play", setLoadActivated);
|
|
72
|
+
const cleanupSeeking = listen(el, "seeking", setLoadActivated);
|
|
73
|
+
return () => {
|
|
74
|
+
cleanupPlay();
|
|
75
|
+
cleanupSeeking();
|
|
76
|
+
};
|
|
77
|
+
} },
|
|
78
|
+
"load-active": { effects: () => {
|
|
79
|
+
context.mediaElement.get();
|
|
80
|
+
urlSignal.get();
|
|
81
|
+
return () => state.loadActivated.set(false);
|
|
82
|
+
} }
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const trackLoadTriggers = defineBehavior({
|
|
87
|
+
stateKeys: ["loadActivated", "presentation"],
|
|
88
|
+
contextKeys: ["mediaElement"],
|
|
89
|
+
setup: trackLoadTriggersSetup
|
|
90
|
+
});
|
|
91
|
+
//#endregion
|
|
92
|
+
export { trackLoadTriggers };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=track-load-triggers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-load-triggers.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/track-load-triggers.ts"],"sourcesContent":["import { listen } from '@videojs/utils/dom';\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport type { Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\n\n/**\n * State shape for load-trigger tracking.\n */\nexport interface LoadTriggersState {\n /**\n * Sticky-true per source. Flips to `true` the first time an event occurs\n * that should switch loading behavior from preload-based to standard load\n * (currently DOM `play` / `seeking`). Reset to `false` on source change.\n *\n * Read by `resolvePresentation` and `loadVideoSegments` / `loadAudioSegments`\n * to mirror native `HTMLMediaElement` preload semantics — only meaningful\n * when `preload !== 'auto'`, which already loads fully regardless.\n */\n loadActivated?: boolean;\n /** Current presentation — URL is used to detect source changes. */\n presentation?: MaybeResolvedPresentation;\n}\n\n/**\n * Context shape for load-trigger tracking.\n */\nexport interface LoadTriggersContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\ntype LoadTriggersFsmState = 'preconditions-unmet' | 'monitoring' | 'load-active';\n\n/**\n * Slot-driven FSM. The slot `loadActivated` is the canonical externally-\n * observable state; `deriveState` reads it (and the preconditions) to\n * derive the local FSM state. The slot is *both* the state and the data —\n * no separate internal bookkeeping.\n *\n * ```\n * 'preconditions-unmet' ⟷ 'monitoring' ⟷ 'load-active'\n *\n * preconditions-unmet → monitoring element + URL appear\n * monitoring → load-active slot flips true (listener fires\n * or external write)\n * load-active → monitoring within-state cleanup resets slot\n * (URL or element identity change)\n * any → preconditions-unmet element or URL → undefined\n *\n * any state → destroying → destroyed on destroy()\n * ```\n */\nfunction deriveState(\n presentation: MaybeResolvedPresentation | undefined,\n mediaElement: HTMLMediaElement | undefined,\n loadActivated: boolean | undefined\n): LoadTriggersFsmState {\n if (!mediaElement || !presentation?.url) return 'preconditions-unmet';\n if (loadActivated) return 'load-active';\n return 'monitoring';\n}\n\n/**\n * Track preload-overriding events per source.\n *\n * Writes `state.loadActivated = true` the first time a `play` or `seeking`\n * event fires on the attached media element for the current source — or\n * immediately on entry if the element is already in such a state\n * (`!el.paused` or `el.seeking`), mirroring autoplay / native-controls /\n * direct-DOM-`play()` scenarios.\n *\n * Sticky-true *within a source identity*: subsequent play/pause/seek\n * cycles don't flip back. Source identity = (mediaElement, presentation\n * URL). Either changing — including direct in-place swap with no\n * `undefined` intermediate — resets the slot to `false`.\n *\n * Multi-writer with `hls/adapter.ts:play()` (which writes `true` directly\n * on programmatic play) is intentional — different domains. The adapter\n * records programmatic intent; this behavior is the DOM-side observer.\n * Pre-existing `true` writes are honored because `deriveState` reads the\n * slot — a `true` value routes directly to `'load-active'` without\n * entering `'monitoring'`.\n *\n * @example\n * const reactor = trackLoadTriggers.setup({ state, context });\n * // later:\n * reactor.destroy();\n */\nfunction trackLoadTriggersSetup({\n state,\n context,\n}: {\n state: {\n loadActivated: Signal<LoadTriggersState['loadActivated']>;\n presentation: ReadonlySignal<LoadTriggersState['presentation']>;\n };\n context: { mediaElement: ReadonlySignal<LoadTriggersContext['mediaElement']> };\n}): Reactor<LoadTriggersFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() =>\n deriveState(state.presentation.get(), context.mediaElement.get(), state.loadActivated.get())\n );\n // Sparse-firing source-URL signal. Emits whenever `presentation?.url`\n // resolves to a different value — covering both URL string changes\n // (url_a → url_b) and transitions where the URL becomes defined or\n // undefined (presentation set, cleared, or replaced without a URL\n // field). Does NOT emit when `state.presentation` is rewritten with\n // the same URL: manifest parsing, duration calc, track resolution,\n // and other enrichment paths re-write the presentation object\n // frequently — tracking raw `state.presentation` here would spuriously\n // reset the 'load-active' slot.\n const urlSignal = computed(() => state.presentation.get()?.url);\n\n return createMachineReactor<LoadTriggersFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n monitoring: {\n // effects (not entry) so element-identity change within this state\n // re-fires and re-attaches listeners to the new element. The monitor\n // guarantees mediaElement is truthy while we're in this state.\n effects: () => {\n const el = context.mediaElement.get()!;\n\n const setLoadActivated = () => state.loadActivated.set(true);\n if (!el.paused || el.seeking) {\n setLoadActivated();\n return;\n }\n const cleanupPlay = listen(el, 'play', setLoadActivated);\n const cleanupSeeking = listen(el, 'seeking', setLoadActivated);\n return () => {\n cleanupPlay();\n cleanupSeeking();\n };\n },\n },\n\n 'load-active': {\n // effects-based cleanup (not entry) because the cleanup must fire on\n // BOTH state exit AND within-state identity change (url_a → url_b,\n // element_a → element_b — both truthy, no state transition).\n // Re-firing tracked reads → cleanup writes loadActivated=false →\n // deriveState (which reads loadActivated) returns 'monitoring' →\n // state transitions → fresh listeners attached for the new source.\n effects: () => {\n context.mediaElement.get();\n urlSignal.get();\n return () => state.loadActivated.set(false);\n },\n },\n },\n });\n}\n\nexport const trackLoadTriggers = defineBehavior({\n stateKeys: ['loadActivated', 'presentation'],\n contextKeys: ['mediaElement'],\n setup: trackLoadTriggersSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAS,YACP,cACA,cACA,eACsB;AACtB,KAAI,CAAC,gBAAgB,CAAC,cAAc,IAAK,QAAO;AAChD,KAAI,cAAe,QAAO;AAC1B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAS,uBAAuB,EAC9B,OACA,WAO6D;CAC7D,MAAM,qBAAqB,eACzB,YAAY,MAAM,aAAa,KAAK,EAAE,QAAQ,aAAa,KAAK,EAAE,MAAM,cAAc,KAAK,CAAC,CAC7F;CAUD,MAAM,YAAY,eAAe,MAAM,aAAa,KAAK,EAAE,IAAI;AAE/D,QAAO,qBAA2C;EAChD,SAAS;EACT,eAAe,mBAAmB,KAAK;EACvC,QAAQ;GACN,uBAAuB,EAAE;GAEzB,YAAY,EAIV,eAAe;IACb,MAAM,KAAK,QAAQ,aAAa,KAAK;IAErC,MAAM,yBAAyB,MAAM,cAAc,IAAI,KAAK;AAC5D,QAAI,CAAC,GAAG,UAAU,GAAG,SAAS;AAC5B,uBAAkB;AAClB;;IAEF,MAAM,cAAc,OAAO,IAAI,QAAQ,iBAAiB;IACxD,MAAM,iBAAiB,OAAO,IAAI,WAAW,iBAAiB;AAC9D,iBAAa;AACX,kBAAa;AACb,qBAAgB;;MAGrB;GAED,eAAe,EAOb,eAAe;AACb,YAAQ,aAAa,KAAK;AAC1B,cAAU,KAAK;AACf,iBAAa,MAAM,cAAc,IAAI,MAAM;MAE9C;GACF;EACF,CAAC;;AAGJ,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,iBAAiB,eAAe;CAC5C,aAAa,CAAC,eAAe;CAC7B,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineBehavior } from "../../../core/composition/create-composition.js";
|
|
2
|
+
import { effect } from "../../../core/signals/effect.js";
|
|
3
|
+
import { listen } from "@videojs/utils/dom";
|
|
4
|
+
//#region src/playback/behaviors/dom/track-playback-rate.ts
|
|
5
|
+
/**
|
|
6
|
+
* Mirror `mediaElement.playbackRate` into reactive state. On each `ratechange`
|
|
7
|
+
* event, write the new value to `state.playbackRate`. Also syncs immediately
|
|
8
|
+
* when a media element becomes available so consumers don't wait for the first
|
|
9
|
+
* event.
|
|
10
|
+
*
|
|
11
|
+
* When no media element is attached, writes `config.defaultPlaybackRate`
|
|
12
|
+
* (default-default `1`, matching the HTMLMediaElement spec) so consumers
|
|
13
|
+
* always see the rate a freshly attached element would have. Read-only mirror;
|
|
14
|
+
* does not push `state.playbackRate` back to the element.
|
|
15
|
+
*/
|
|
16
|
+
function trackPlaybackRateSetup({ state, context, config }) {
|
|
17
|
+
const defaultPlaybackRate = config?.defaultPlaybackRate ?? 1;
|
|
18
|
+
return effect(() => {
|
|
19
|
+
const mediaElement = context.mediaElement.get();
|
|
20
|
+
if (!mediaElement) {
|
|
21
|
+
state.playbackRate.set(defaultPlaybackRate);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const sync = () => state.playbackRate.set(mediaElement.playbackRate);
|
|
25
|
+
sync();
|
|
26
|
+
return listen(mediaElement, "ratechange", sync);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const trackPlaybackRate = defineBehavior({
|
|
30
|
+
stateKeys: ["playbackRate"],
|
|
31
|
+
contextKeys: ["mediaElement"],
|
|
32
|
+
setup: trackPlaybackRateSetup
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
35
|
+
export { trackPlaybackRate };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=track-playback-rate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-playback-rate.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/track-playback-rate.ts"],"sourcesContent":["/**\n * Mirror `mediaElement.playbackRate` into reactive state. On each `ratechange`\n * event, write the new value to `state.playbackRate`. Also syncs immediately\n * when a media element becomes available so consumers don't wait for the first\n * event.\n *\n * When no media element is attached, writes `config.defaultPlaybackRate`\n * (default-default `1`, matching the HTMLMediaElement spec) so consumers\n * always see the rate a freshly attached element would have. Read-only mirror;\n * does not push `state.playbackRate` back to the element.\n */\nimport { listen } from '@videojs/utils/dom';\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport { effect } from '../../../core/signals/effect';\nimport type { ReadonlySignal, Signal } from '../../../core/signals/primitives';\n\nexport interface PlaybackRateState {\n playbackRate?: number;\n}\n\nexport interface PlaybackRateContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\nexport interface TrackPlaybackRateConfig {\n /**\n * Value written to `state.playbackRate` when no media element is attached.\n * Defaults to `1` — the HTMLMediaElement spec default.\n */\n defaultPlaybackRate?: number;\n}\n\nfunction trackPlaybackRateSetup({\n state,\n context,\n config,\n}: {\n state: { playbackRate: Signal<PlaybackRateState['playbackRate']> };\n context: { mediaElement: ReadonlySignal<PlaybackRateContext['mediaElement']> };\n config?: TrackPlaybackRateConfig;\n}): () => void {\n const defaultPlaybackRate = config?.defaultPlaybackRate ?? 1;\n\n return effect(() => {\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) {\n state.playbackRate.set(defaultPlaybackRate);\n return;\n }\n\n const sync = () => state.playbackRate.set(mediaElement.playbackRate);\n sync();\n return listen(mediaElement, 'ratechange', sync);\n });\n}\n\nexport const trackPlaybackRate = defineBehavior({\n stateKeys: ['playbackRate'],\n contextKeys: ['mediaElement'],\n setup: trackPlaybackRateSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;AAgCA,SAAS,uBAAuB,EAC9B,OACA,SACA,UAKa;CACb,MAAM,sBAAsB,QAAQ,uBAAuB;AAE3D,QAAO,aAAa;EAClB,MAAM,eAAe,QAAQ,aAAa,KAAK;AAC/C,MAAI,CAAC,cAAc;AACjB,SAAM,aAAa,IAAI,oBAAoB;AAC3C;;EAGF,MAAM,aAAa,MAAM,aAAa,IAAI,aAAa,aAAa;AACpE,QAAM;AACN,SAAO,OAAO,cAAc,cAAc,KAAK;GAC/C;;AAGJ,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,eAAe;CAC3B,aAAa,CAAC,eAAe;CAC7B,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { computed } 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 { waitForMediaSourceOpen } from "../../../media/dom/mse/mediasource-setup.js";
|
|
5
|
+
import { getMaxBufferedEnd, shouldUpdateDuration, waitForSourceBuffersReady } from "../../../media/dom/mse/duration.js";
|
|
6
|
+
//#region src/playback/behaviors/dom/update-mediasource-duration.ts
|
|
7
|
+
/**
|
|
8
|
+
* **Propagate `presentation.duration` to `mediaSource.duration` — exactly
|
|
9
|
+
* once per MediaSource.**
|
|
10
|
+
*
|
|
11
|
+
* When the presentation has a valid positive duration (including `Infinity`
|
|
12
|
+
* for live) and a MediaSource is in context, writes the value through to
|
|
13
|
+
* `mediaSource.duration` on initial setup — once, while `mediaSource.duration`
|
|
14
|
+
* is still `NaN`. Once any non-NaN value is present (set by us, or by
|
|
15
|
+
* `endOfStream` from the buffered end), the behavior leaves the property
|
|
16
|
+
* alone; re-syncing a drift against `presentation.duration` would race with
|
|
17
|
+
* concurrent `appendBuffer()` calls.
|
|
18
|
+
*
|
|
19
|
+
* The entry resolves three async preconditions in order before writing:
|
|
20
|
+
*
|
|
21
|
+
* 1. **MediaSource open** — `waitForMediaSourceOpen` defers until the first
|
|
22
|
+
* `sourceopen` event (or any readyState transition, since `'ended'` /
|
|
23
|
+
* `'closed'` mean we've missed the window and should bail).
|
|
24
|
+
* 2. **All SourceBuffers idle** — `waitForSourceBuffersReady` defers per
|
|
25
|
+
* the MSE-spec rule that `duration` cannot be set while any buffer has
|
|
26
|
+
* `updating === true`.
|
|
27
|
+
* 3. **Buffered-range clamp** — `getMaxBufferedEnd` ensures the written
|
|
28
|
+
* duration is at least the highest buffered end (MSE spec disallows
|
|
29
|
+
* a smaller `duration` than any buffered range).
|
|
30
|
+
*
|
|
31
|
+
* The buffer-set helpers operate across `mediaSource.sourceBuffers` (the
|
|
32
|
+
* canonical aggregate), so the behavior composes uniformly across
|
|
33
|
+
* audio-only, video-only, and mixed configurations.
|
|
34
|
+
*
|
|
35
|
+
* Single-positive-state reactor (`'preconditions-unmet'` ↔ `'duration-writable'`):
|
|
36
|
+
* state derivation is purely signal-driven (presentation validity +
|
|
37
|
+
* mediaSource existence). MediaSource lifecycle state (`readyState`) and
|
|
38
|
+
* the `duration` itself are non-signal DOM properties resolved inside the
|
|
39
|
+
* entry's async sequence. The state-exit cleanup aborts the in-flight
|
|
40
|
+
* wait, so source resets and behavior destroy structurally cancel the
|
|
41
|
+
* pending write. A post-await re-check of `mediaSource.readyState ===
|
|
42
|
+
* 'open'` covers the narrow race where `endOfStream()` synchronously
|
|
43
|
+
* transitions readyState to `'ended'` between our `waitForMediaSourceOpen`
|
|
44
|
+
* resolution and the `mediaSource.duration` write.
|
|
45
|
+
*
|
|
46
|
+
* Downstream of `calculatePresentationDuration` (which writes
|
|
47
|
+
* `presentation.duration`); concurrent with `endOfStream` (which may later
|
|
48
|
+
* write `mediaSource.duration` — the "exactly once" contract keeps us out
|
|
49
|
+
* of that path).
|
|
50
|
+
*/
|
|
51
|
+
function deriveState(presentation, mediaSource) {
|
|
52
|
+
return shouldUpdateDuration(presentation, mediaSource) ? "duration-writable" : "preconditions-unmet";
|
|
53
|
+
}
|
|
54
|
+
function updateMediaSourceDurationSetup({ state, context }) {
|
|
55
|
+
const derivedStateSignal = computed(() => deriveState(state.presentation.get(), context.mediaSource.get()));
|
|
56
|
+
return createMachineReactor({
|
|
57
|
+
initial: "preconditions-unmet",
|
|
58
|
+
monitor: () => derivedStateSignal.get(),
|
|
59
|
+
states: {
|
|
60
|
+
"preconditions-unmet": {},
|
|
61
|
+
"duration-writable": { entry: () => {
|
|
62
|
+
const presentation = state.presentation.get();
|
|
63
|
+
const mediaSource = context.mediaSource.get();
|
|
64
|
+
if (!Number.isNaN(mediaSource.duration)) return;
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const writeWhenReady = async () => {
|
|
67
|
+
await waitForMediaSourceOpen(mediaSource, controller.signal);
|
|
68
|
+
if (controller.signal.aborted) return;
|
|
69
|
+
if (mediaSource.readyState !== "open") return;
|
|
70
|
+
await waitForSourceBuffersReady(mediaSource.sourceBuffers, controller.signal);
|
|
71
|
+
if (controller.signal.aborted) return;
|
|
72
|
+
if (mediaSource.readyState !== "open") return;
|
|
73
|
+
const maxBufferedEnd = getMaxBufferedEnd(mediaSource.sourceBuffers);
|
|
74
|
+
mediaSource.duration = maxBufferedEnd > presentation.duration ? maxBufferedEnd : presentation.duration;
|
|
75
|
+
};
|
|
76
|
+
writeWhenReady();
|
|
77
|
+
return () => controller.abort();
|
|
78
|
+
} }
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const updateMediaSourceDuration = defineBehavior({
|
|
83
|
+
stateKeys: ["presentation"],
|
|
84
|
+
contextKeys: ["mediaSource"],
|
|
85
|
+
setup: updateMediaSourceDurationSetup
|
|
86
|
+
});
|
|
87
|
+
//#endregion
|
|
88
|
+
export { updateMediaSourceDuration };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=update-mediasource-duration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-mediasource-duration.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/update-mediasource-duration.ts"],"sourcesContent":["/**\n * **Propagate `presentation.duration` to `mediaSource.duration` — exactly\n * once per MediaSource.**\n *\n * When the presentation has a valid positive duration (including `Infinity`\n * for live) and a MediaSource is in context, writes the value through to\n * `mediaSource.duration` on initial setup — once, while `mediaSource.duration`\n * is still `NaN`. Once any non-NaN value is present (set by us, or by\n * `endOfStream` from the buffered end), the behavior leaves the property\n * alone; re-syncing a drift against `presentation.duration` would race with\n * concurrent `appendBuffer()` calls.\n *\n * The entry resolves three async preconditions in order before writing:\n *\n * 1. **MediaSource open** — `waitForMediaSourceOpen` defers until the first\n * `sourceopen` event (or any readyState transition, since `'ended'` /\n * `'closed'` mean we've missed the window and should bail).\n * 2. **All SourceBuffers idle** — `waitForSourceBuffersReady` defers per\n * the MSE-spec rule that `duration` cannot be set while any buffer has\n * `updating === true`.\n * 3. **Buffered-range clamp** — `getMaxBufferedEnd` ensures the written\n * duration is at least the highest buffered end (MSE spec disallows\n * a smaller `duration` than any buffered range).\n *\n * The buffer-set helpers operate across `mediaSource.sourceBuffers` (the\n * canonical aggregate), so the behavior composes uniformly across\n * audio-only, video-only, and mixed configurations.\n *\n * Single-positive-state reactor (`'preconditions-unmet'` ↔ `'duration-writable'`):\n * state derivation is purely signal-driven (presentation validity +\n * mediaSource existence). MediaSource lifecycle state (`readyState`) and\n * the `duration` itself are non-signal DOM properties resolved inside the\n * entry's async sequence. The state-exit cleanup aborts the in-flight\n * wait, so source resets and behavior destroy structurally cancel the\n * pending write. A post-await re-check of `mediaSource.readyState ===\n * 'open'` covers the narrow race where `endOfStream()` synchronously\n * transitions readyState to `'ended'` between our `waitForMediaSourceOpen`\n * resolution and the `mediaSource.duration` write.\n *\n * Downstream of `calculatePresentationDuration` (which writes\n * `presentation.duration`); concurrent with `endOfStream` (which may later\n * write `mediaSource.duration` — the \"exactly once\" contract keeps us out\n * of that path).\n */\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport type { Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal } from '../../../core/signals/primitives';\nimport { getMaxBufferedEnd, shouldUpdateDuration, waitForSourceBuffersReady } from '../../../media/dom/mse/duration';\nimport { waitForMediaSourceOpen } from '../../../media/dom/mse/mediasource-setup';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\n\ntype DurationFsmState = 'preconditions-unmet' | 'duration-writable';\n\nfunction deriveState(\n presentation: MaybeResolvedPresentation | undefined,\n mediaSource: MediaSource | undefined\n): DurationFsmState {\n return shouldUpdateDuration(presentation, mediaSource) ? 'duration-writable' : 'preconditions-unmet';\n}\n\nfunction updateMediaSourceDurationSetup({\n state,\n context,\n}: {\n state: { presentation: ReadonlySignal<MaybeResolvedPresentation | undefined> };\n context: { mediaSource: ReadonlySignal<MediaSource | undefined> };\n}): Reactor<DurationFsmState | 'destroying' | 'destroyed'> {\n // Purely signal-driven derivation: only `presentation` validity and\n // `mediaSource` existence. MediaSource lifecycle (`readyState`),\n // its attached `sourceBuffers`, and `mediaSource.duration` itself are\n // non-signal DOM properties consumed inside the entry — none of them\n // sneak into `computed(...)`.\n const derivedStateSignal = computed(() => deriveState(state.presentation.get(), context.mediaSource.get()));\n\n return createMachineReactor<DurationFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'duration-writable': {\n // entry body is auto-untracked. The state machine handles source\n // resets via deriveState; this entry resolves the MediaSource\n // lifecycle preconditions in sequence and binds its cleanup\n // (abort) to state exit + destroy.\n entry: () => {\n const presentation = state.presentation.get()!;\n const mediaSource = context.mediaSource.get()!;\n\n // Idempotency: someone (us on a prior entry, or concurrent\n // `endOfStream`) has already written. No-op and leave the state\n // alone — the next source-reset transition will produce a fresh\n // entry against the new MediaSource (whose `duration` starts at\n // `NaN` again).\n if (!Number.isNaN(mediaSource.duration)) return;\n\n const controller = new AbortController();\n\n const writeWhenReady = async () => {\n await waitForMediaSourceOpen(mediaSource, controller.signal);\n if (controller.signal.aborted) return;\n // `waitForMediaSourceOpen` resolves on any readyState transition\n // out of 'closed'; if we landed in 'ended' / 'closed' instead of\n // 'open', the write window is gone.\n if (mediaSource.readyState !== 'open') return;\n\n await waitForSourceBuffersReady(mediaSource.sourceBuffers, controller.signal);\n if (controller.signal.aborted) return;\n\n // Narrow race: `endOfStream()` synchronously transitions\n // readyState to 'ended' AND writes `mediaSource.duration`\n // between our `waitForMediaSourceOpen` resolution and here.\n // Re-read the DOM property to catch this window.\n if (mediaSource.readyState !== 'open') return;\n\n // MSE spec: duration cannot be less than any buffered range.\n // Re-read sourceBuffers after the await so any buffer added\n // during the wait is included in the clamp.\n const maxBufferedEnd = getMaxBufferedEnd(mediaSource.sourceBuffers);\n const duration = maxBufferedEnd > presentation.duration! ? maxBufferedEnd : presentation.duration!;\n mediaSource.duration = duration;\n };\n\n writeWhenReady();\n\n return () => controller.abort();\n },\n },\n },\n });\n}\n\nexport const updateMediaSourceDuration = defineBehavior({\n stateKeys: ['presentation'],\n contextKeys: ['mediaSource'],\n setup: updateMediaSourceDurationSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,SAAS,YACP,cACA,aACkB;AAClB,QAAO,qBAAqB,cAAc,YAAY,GAAG,sBAAsB;;AAGjF,SAAS,+BAA+B,EACtC,OACA,WAIyD;CAMzD,MAAM,qBAAqB,eAAe,YAAY,MAAM,aAAa,KAAK,EAAE,QAAQ,YAAY,KAAK,CAAC,CAAC;AAE3G,QAAO,qBAAuC;EAC5C,SAAS;EACT,eAAe,mBAAmB,KAAK;EACvC,QAAQ;GACN,uBAAuB,EAAE;GAEzB,qBAAqB,EAKnB,aAAa;IACX,MAAM,eAAe,MAAM,aAAa,KAAK;IAC7C,MAAM,cAAc,QAAQ,YAAY,KAAK;AAO7C,QAAI,CAAC,OAAO,MAAM,YAAY,SAAS,CAAE;IAEzC,MAAM,aAAa,IAAI,iBAAiB;IAExC,MAAM,iBAAiB,YAAY;AACjC,WAAM,uBAAuB,aAAa,WAAW,OAAO;AAC5D,SAAI,WAAW,OAAO,QAAS;AAI/B,SAAI,YAAY,eAAe,OAAQ;AAEvC,WAAM,0BAA0B,YAAY,eAAe,WAAW,OAAO;AAC7E,SAAI,WAAW,OAAO,QAAS;AAM/B,SAAI,YAAY,eAAe,OAAQ;KAKvC,MAAM,iBAAiB,kBAAkB,YAAY,cAAc;AAEnE,iBAAY,WADK,iBAAiB,aAAa,WAAY,iBAAiB,aAAa;;AAI3F,oBAAgB;AAEhB,iBAAa,WAAW,OAAO;MAElC;GACF;EACF,CAAC;;AAGJ,MAAa,4BAA4B,eAAe;CACtD,WAAW,CAAC,eAAe;CAC3B,aAAa,CAAC,cAAc;CAC5B,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { computed } 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 { fetchResolvable, getResponseText } from "../../network/fetch.js";
|
|
6
|
+
import { isBlockingPreload } from "../../media/utils/preload.js";
|
|
7
|
+
//#region src/playback/behaviors/resolve-presentation.ts
|
|
8
|
+
/**
|
|
9
|
+
* **Resolve an unresolved presentation by fetching and parsing its manifest.**
|
|
10
|
+
*
|
|
11
|
+
* Reads `state.presentation`; when it holds `{ url }` (unresolved) and the
|
|
12
|
+
* preload / load-activation gate is met, fetches the manifest, parses it via
|
|
13
|
+
* the **required** `config.parsePresentation`, and writes the resolved
|
|
14
|
+
* `Presentation` back to the same slot. The behavior is format-neutral: the
|
|
15
|
+
* composing engine wires in its parser (e.g. the HLS engine supplies the
|
|
16
|
+
* multivariant-playlist parser).
|
|
17
|
+
*
|
|
18
|
+
* Source-identity-driven, expressed as a 4-state machine:
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* 'preconditions-unmet' → 'idle' → 'resolving' → 'resolved'
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* - `'preconditions-unmet'`: no presentation, or presentation has no URL.
|
|
25
|
+
* - `'idle'`: URL present, unresolved, gate unmet (blocking preload + no
|
|
26
|
+
* load-activation). Waits for the gate to open.
|
|
27
|
+
* - `'resolving'`: URL present, unresolved, gate met. Entry starts the fetch
|
|
28
|
+
* and returns the AbortController — the reactor calls `.abort()` on state
|
|
29
|
+
* exit, so source change / gate-close / destroy all cancel cleanly.
|
|
30
|
+
* - `'resolved'`: `state.presentation` holds a resolved `Presentation`.
|
|
31
|
+
*
|
|
32
|
+
* Gate semantics: `state.preload` (or `config.defaultPreload`, default
|
|
33
|
+
* `'metadata'`, when state.preload is unset) blocks resolution when its
|
|
34
|
+
* value is `'none'` (see `isBlockingPreload` in `media/utils/preload`).
|
|
35
|
+
* `state.loadActivated` is an override — true bypasses the preload gate
|
|
36
|
+
* entirely.
|
|
37
|
+
*
|
|
38
|
+
* Multi-writer with the engine adapter, which writes the initial unresolved
|
|
39
|
+
* `{ url }` to `state.presentation` from src input. Different domains
|
|
40
|
+
* (config-input vs. derived state via fetch) — legitimate multi-writer.
|
|
41
|
+
*/
|
|
42
|
+
function deriveState(presentation, preload, loadActivated, defaultPreload) {
|
|
43
|
+
if (!presentation?.url) return "preconditions-unmet";
|
|
44
|
+
if (isResolvedPresentation(presentation)) return "resolved";
|
|
45
|
+
return !!loadActivated || !isBlockingPreload(preload, defaultPreload) ? "resolving" : "idle";
|
|
46
|
+
}
|
|
47
|
+
function resolvePresentationSetup({ state, config }) {
|
|
48
|
+
const { parsePresentation } = config;
|
|
49
|
+
const defaultPreload = config.defaultPreload ?? "metadata";
|
|
50
|
+
const derivedStateSignal = computed(() => deriveState(state.presentation.get(), state.preload.get(), state.loadActivated.get(), defaultPreload));
|
|
51
|
+
return createMachineReactor({
|
|
52
|
+
initial: "preconditions-unmet",
|
|
53
|
+
monitor: () => derivedStateSignal.get(),
|
|
54
|
+
states: {
|
|
55
|
+
"preconditions-unmet": {},
|
|
56
|
+
idle: {},
|
|
57
|
+
resolving: { entry: () => {
|
|
58
|
+
const presentation = state.presentation.get();
|
|
59
|
+
const ac = new AbortController();
|
|
60
|
+
fetchResolvable(presentation, { signal: ac.signal }).then((response) => getResponseText(response)).then((text) => {
|
|
61
|
+
const parsed = parsePresentation(text, presentation);
|
|
62
|
+
state.presentation.set(parsed);
|
|
63
|
+
}).catch((error) => {
|
|
64
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
65
|
+
console.error("[resolvePresentation] manifest fetch/parse failed:", error);
|
|
66
|
+
});
|
|
67
|
+
return ac;
|
|
68
|
+
} },
|
|
69
|
+
resolved: {}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const resolvePresentation = defineBehavior({
|
|
74
|
+
stateKeys: [
|
|
75
|
+
"presentation",
|
|
76
|
+
"preload",
|
|
77
|
+
"loadActivated"
|
|
78
|
+
],
|
|
79
|
+
contextKeys: [],
|
|
80
|
+
setup: resolvePresentationSetup
|
|
81
|
+
});
|
|
82
|
+
//#endregion
|
|
83
|
+
export { resolvePresentation };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=resolve-presentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-presentation.js","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"sourcesContent":["/**\n * **Resolve an unresolved presentation by fetching and parsing its manifest.**\n *\n * Reads `state.presentation`; when it holds `{ url }` (unresolved) and the\n * preload / load-activation gate is met, fetches the manifest, parses it via\n * the **required** `config.parsePresentation`, and writes the resolved\n * `Presentation` back to the same slot. The behavior is format-neutral: the\n * composing engine wires in its parser (e.g. the HLS engine supplies the\n * multivariant-playlist parser).\n *\n * Source-identity-driven, expressed as a 4-state machine:\n *\n * ```\n * 'preconditions-unmet' → 'idle' → 'resolving' → 'resolved'\n * ```\n *\n * - `'preconditions-unmet'`: no presentation, or presentation has no URL.\n * - `'idle'`: URL present, unresolved, gate unmet (blocking preload + no\n * load-activation). Waits for the gate to open.\n * - `'resolving'`: URL present, unresolved, gate met. Entry starts the fetch\n * and returns the AbortController — the reactor calls `.abort()` on state\n * exit, so source change / gate-close / destroy all cancel cleanly.\n * - `'resolved'`: `state.presentation` holds a resolved `Presentation`.\n *\n * Gate semantics: `state.preload` (or `config.defaultPreload`, default\n * `'metadata'`, when state.preload is unset) blocks resolution when its\n * value is `'none'` (see `isBlockingPreload` in `media/utils/preload`).\n * `state.loadActivated` is an override — true bypasses the preload gate\n * entirely.\n *\n * Multi-writer with the engine adapter, which writes the initial unresolved\n * `{ url }` to `state.presentation` from src input. Different domains\n * (config-input vs. derived state via fetch) — legitimate multi-writer.\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport type { Reactor } from '../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation, type Presentation } from '../../media/types';\nimport { DEFAULT_PRELOAD, isBlockingPreload, type StandardPreload } from '../../media/utils/preload';\nimport { fetchResolvable, getResponseText } from '../../network/fetch';\n\nexport interface PresentationState {\n presentation?: MaybeResolvedPresentation;\n preload?: 'auto' | 'metadata' | 'none' | undefined;\n /** True once a preload-overriding event has fired for the current source — enables resolution regardless of preload. */\n loadActivated?: boolean;\n}\n\nexport type ParsePresentation = (text: string, presentation: MaybeResolvedPresentation) => Presentation;\n\nexport interface ResolvePresentationConfig {\n /**\n * Parses a manifest body into a resolved `Presentation`. **Required** —\n * the behavior is format-neutral and the composing engine supplies its\n * own parser (HLS, DASH, etc.).\n */\n parsePresentation: ParsePresentation;\n /**\n * Fallback used when `state.preload` is unset (undefined / empty) for the\n * resolution-gate decision. Defaults to `'metadata'`, matching\n * `syncPreload`'s own `defaultPreload`.\n */\n defaultPreload?: StandardPreload;\n}\n\nexport type ResolvePresentationState = 'preconditions-unmet' | 'idle' | 'resolving' | 'resolved';\n\nfunction deriveState(\n presentation: MaybeResolvedPresentation | undefined,\n preload: PresentationState['preload'],\n loadActivated: boolean | undefined,\n defaultPreload: StandardPreload\n): ResolvePresentationState {\n if (!presentation?.url) return 'preconditions-unmet';\n if (isResolvedPresentation(presentation)) return 'resolved';\n const gateOpen = !!loadActivated || !isBlockingPreload(preload, defaultPreload);\n return gateOpen ? 'resolving' : 'idle';\n}\n\nfunction resolvePresentationSetup({\n state,\n config,\n}: {\n state: {\n presentation: Signal<PresentationState['presentation']>;\n preload: ReadonlySignal<PresentationState['preload']>;\n loadActivated: ReadonlySignal<PresentationState['loadActivated']>;\n };\n config: ResolvePresentationConfig;\n}): Reactor<ResolvePresentationState | 'destroying' | 'destroyed'> {\n const { parsePresentation } = config;\n const defaultPreload: StandardPreload = config.defaultPreload ?? DEFAULT_PRELOAD;\n\n const derivedStateSignal = computed(() =>\n deriveState(state.presentation.get(), state.preload.get(), state.loadActivated.get(), defaultPreload)\n );\n\n return createMachineReactor<ResolvePresentationState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n idle: {},\n resolving: {\n entry: () => {\n const presentation = state.presentation.get()!;\n const ac = new AbortController();\n\n fetchResolvable(presentation, { signal: ac.signal })\n .then((response) => getResponseText(response))\n .then((text) => {\n const parsed = parsePresentation(text, presentation);\n state.presentation.set(parsed);\n })\n .catch((error) => {\n if (error instanceof Error && error.name === 'AbortError') return;\n // TODO(error-management): route to a state-error slot once one exists.\n console.error('[resolvePresentation] manifest fetch/parse failed:', error);\n });\n\n return ac;\n },\n },\n resolved: {},\n },\n });\n}\n\nexport const resolvePresentation = defineBehavior({\n stateKeys: ['presentation', 'preload', 'loadActivated'],\n contextKeys: [],\n setup: resolvePresentationSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,SAAS,YACP,cACA,SACA,eACA,gBAC0B;AAC1B,KAAI,CAAC,cAAc,IAAK,QAAO;AAC/B,KAAI,uBAAuB,aAAa,CAAE,QAAO;AAEjD,QADiB,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,SAAS,eAAe,GAC7D,cAAc;;AAGlC,SAAS,yBAAyB,EAChC,OACA,UAQiE;CACjE,MAAM,EAAE,sBAAsB;CAC9B,MAAM,iBAAkC,OAAO,kBAAA;CAE/C,MAAM,qBAAqB,eACzB,YAAY,MAAM,aAAa,KAAK,EAAE,MAAM,QAAQ,KAAK,EAAE,MAAM,cAAc,KAAK,EAAE,eAAe,CACtG;AAED,QAAO,qBAA+C;EACpD,SAAS;EACT,eAAe,mBAAmB,KAAK;EACvC,QAAQ;GACN,uBAAuB,EAAE;GACzB,MAAM,EAAE;GACR,WAAW,EACT,aAAa;IACX,MAAM,eAAe,MAAM,aAAa,KAAK;IAC7C,MAAM,KAAK,IAAI,iBAAiB;AAEhC,oBAAgB,cAAc,EAAE,QAAQ,GAAG,QAAQ,CAAC,CACjD,MAAM,aAAa,gBAAgB,SAAS,CAAC,CAC7C,MAAM,SAAS;KACd,MAAM,SAAS,kBAAkB,MAAM,aAAa;AACpD,WAAM,aAAa,IAAI,OAAO;MAC9B,CACD,OAAO,UAAU;AAChB,SAAI,iBAAiB,SAAS,MAAM,SAAS,aAAc;AAE3D,aAAQ,MAAM,sDAAsD,MAAM;MAC1E;AAEJ,WAAO;MAEV;GACD,UAAU,EAAE;GACb;EACF,CAAC;;AAGJ,MAAa,sBAAsB,eAAe;CAChD,WAAW;EAAC;EAAgB;EAAW;EAAgB;CACvD,aAAa,EAAE;CACf,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { computed, peek, update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { ConcurrentRunner, Task } from "../../core/tasks/task.js";
|
|
4
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
5
|
+
import { isResolvedPresentation, isResolvedTrack } from "../../media/types/index.js";
|
|
6
|
+
import { applyContainerMimeType, findTrack, updateTrackInPresentation } from "../../media/utils/tracks.js";
|
|
7
|
+
import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
|
|
8
|
+
import { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from "../../media/hls/parse-media-playlist.js";
|
|
9
|
+
import { fetchResolvableText } from "../../network/fetch.js";
|
|
10
|
+
import { failoverFetch } from "../primitives/failover-fetch.js";
|
|
11
|
+
//#region src/playback/behaviors/resolve-track.ts
|
|
12
|
+
function setupTrackResolution({ state, config: { selectedKey, findTrackToResolve, fetchResolvableText: fetchResolvableText$1 = fetchResolvableText } }) {
|
|
13
|
+
const runner = new ConcurrentRunner();
|
|
14
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
15
|
+
return createMachineReactor({
|
|
16
|
+
initial: "presentation-unresolved",
|
|
17
|
+
monitor: () => derivedStateSignal.get(),
|
|
18
|
+
states: {
|
|
19
|
+
"presentation-unresolved": {},
|
|
20
|
+
"presentation-resolved": {
|
|
21
|
+
entry: () => () => runner.abortAll(),
|
|
22
|
+
effects: [() => {
|
|
23
|
+
const presentation = peek(state.presentation);
|
|
24
|
+
const trackId = state[selectedKey].get();
|
|
25
|
+
if (!presentation || !trackId) return;
|
|
26
|
+
const track = findTrackToResolve(presentation, trackId);
|
|
27
|
+
if (!track || isResolvedTrack(track)) return;
|
|
28
|
+
runner.schedule(new Task(async (signal) => {
|
|
29
|
+
const mediaTrack = parseMediaPlaylist(await fetchResolvableText$1(track, { signal }), track);
|
|
30
|
+
update(state.presentation, (current) => {
|
|
31
|
+
if (!isResolvedPresentation(current)) return current;
|
|
32
|
+
const patched = updateTrackInPresentation(current, mediaTrack);
|
|
33
|
+
return NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType) ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType) : patched;
|
|
34
|
+
});
|
|
35
|
+
}, { id: track.id }));
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const VIDEO_TRACK_RESOLUTION_CONFIG = {
|
|
42
|
+
...VIDEO_TYPE_CONFIG,
|
|
43
|
+
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "video", trackId)
|
|
44
|
+
};
|
|
45
|
+
const AUDIO_TRACK_RESOLUTION_CONFIG = {
|
|
46
|
+
...AUDIO_TYPE_CONFIG,
|
|
47
|
+
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "audio", trackId)
|
|
48
|
+
};
|
|
49
|
+
const TEXT_TRACK_RESOLUTION_CONFIG = {
|
|
50
|
+
...TEXT_TYPE_CONFIG,
|
|
51
|
+
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "text", trackId)
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Resolve unresolved video tracks. Schedules a fetch task whenever the
|
|
55
|
+
* selected video track is partially resolved, parses the manifest, and
|
|
56
|
+
* writes the resolved track back into `state.presentation`.
|
|
57
|
+
*/
|
|
58
|
+
const resolveVideoTrack = defineBehavior({
|
|
59
|
+
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
60
|
+
contextKeys: [],
|
|
61
|
+
setup: ({ state, config = {} }) => {
|
|
62
|
+
const trackConfig = {
|
|
63
|
+
...VIDEO_TRACK_RESOLUTION_CONFIG,
|
|
64
|
+
...config
|
|
65
|
+
};
|
|
66
|
+
return setupTrackResolution({
|
|
67
|
+
state,
|
|
68
|
+
config: {
|
|
69
|
+
...trackConfig,
|
|
70
|
+
fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,
|
|
77
|
+
* narrowed to audio.
|
|
78
|
+
*/
|
|
79
|
+
const resolveAudioTrack = defineBehavior({
|
|
80
|
+
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
81
|
+
contextKeys: [],
|
|
82
|
+
setup: ({ state, config = {} }) => {
|
|
83
|
+
const trackConfig = {
|
|
84
|
+
...AUDIO_TRACK_RESOLUTION_CONFIG,
|
|
85
|
+
...config
|
|
86
|
+
};
|
|
87
|
+
return setupTrackResolution({
|
|
88
|
+
state,
|
|
89
|
+
config: {
|
|
90
|
+
...trackConfig,
|
|
91
|
+
fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,
|
|
98
|
+
* narrowed to text.
|
|
99
|
+
*/
|
|
100
|
+
const resolveTextTrack = defineBehavior({
|
|
101
|
+
stateKeys: ["presentation", "selectedTextTrackId"],
|
|
102
|
+
contextKeys: [],
|
|
103
|
+
setup: ({ state, config = {} }) => {
|
|
104
|
+
const trackConfig = {
|
|
105
|
+
...TEXT_TRACK_RESOLUTION_CONFIG,
|
|
106
|
+
...config
|
|
107
|
+
};
|
|
108
|
+
return setupTrackResolution({
|
|
109
|
+
state,
|
|
110
|
+
config: {
|
|
111
|
+
...trackConfig,
|
|
112
|
+
fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
//#endregion
|
|
118
|
+
export { resolveAudioTrack, resolveTextTrack, resolveVideoTrack };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=resolve-track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-track.js","names":["defaultFetchResolvableText","fetchResolvableText"],"sources":["../../../../src/playback/behaviors/resolve-track.ts"],"sourcesContent":["import { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { ConcurrentRunner, Task } from '../../core/tasks/task';\nimport { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from '../../media/hls/parse-media-playlist';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack, ResolvedTrack } from '../../media/types';\nimport { isResolvedPresentation, isResolvedTrack } from '../../media/types';\nimport type { GetCdnId } from '../../media/utils/cdn';\nimport { applyContainerMimeType, findTrack, updateTrackInPresentation } from '../../media/utils/tracks';\nimport { fetchResolvableText as defaultFetchResolvableText, type FetchText } from '../../network/fetch';\nimport { failoverFetch } from '../primitives/failover-fetch';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackResolution` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => cleanup`. Each `resolveXTrack` export below calls it\n// from inside its own `defineBehavior` setup, supplying its per-type config\n// inline. The orchestration — gate on a selection, short-circuit when the\n// track is already resolved or missing, schedule the fetch+parse, and patch\n// the resolved track back into `state.presentation` — is shared.\n// ============================================================================\n\n/**\n * State shape for track resolution. Uses `MaybeResolvedPresentation` so it\n * matches the engine's slot type; resolution narrows internally.\n */\nexport interface ResolveTrackState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n failedCdns?: string[];\n}\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\ntype ResolveTrackStateMap<K extends SelectedTrackKey> = {\n presentation: Signal<ResolveTrackState['presentation']>;\n} & { [P in K]: ReadonlySignal<ResolveTrackState[P]> };\n\ninterface TrackResolutionConfig<K extends SelectedTrackKey> {\n selectedKey: K;\n findTrackToResolve: (\n presentation: MaybeResolvedPresentation,\n trackId: string\n ) => PartiallyResolvedTrack | ResolvedTrack | undefined;\n /** Fetch a track's media-playlist text — already failover-decorated by the behavior. */\n fetchResolvableText?: FetchText;\n}\n\n/**\n * Engine-config slice each `resolve*` behavior reads to build its failover-\n * decorated playlist fetch.\n */\ninterface ResolveTrackConfig {\n /** CDN-id derivation for the failover trip; defaults to origin-based `getCdnId`. */\n getCdnId?: GetCdnId;\n}\n\nfunction setupTrackResolution<K extends SelectedTrackKey>({\n state,\n config: { selectedKey, findTrackToResolve, fetchResolvableText = defaultFetchResolvableText },\n}: {\n state: ResolveTrackStateMap<K>;\n config: TrackResolutionConfig<K>;\n}) {\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createTaskRunner() with args TBD),\n // likely eventually passed down via config or a new \"definitions\" argument. This will allow us to decide if we want our task runner/scheduler\n // to e.g. run concurrently (like we currently are), serially with a queue, or abort the previous task and replace it with the newly scheduled one. (CJP).\n const runner = new ConcurrentRunner();\n\n // Reactor states model the FSM the previous effect-based body was\n // hand-rolling. 'presentation-resolved' is entered when the\n // presentation is fully parsed (has a Ham id + selectionSets); leaving\n // it (presentation cleared or reset to an unresolved value) aborts all\n // in-flight tasks via the entry-cleanup. Most URL changes go through\n // 'presentation-unresolved' naturally (set undefined → set new partial\n // → re-parse), so the common case is covered by state-exit alone; the\n // task body's commit-time id check covers the pathological\n // resolved→resolved-without-unresolved transition.\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 // `entry` runs on state entry; the function it returns is the\n // state-exit cleanup. Returning `() => runner.abortAll()` binds\n // abort-of-in-flight-resolutions to leaving 'presentation-resolved'\n // (presentation cleared/reset, or behavior destroyed) —\n // source-change cancellation expressed structurally through the\n // state machine.\n entry: () => () => runner.abortAll(),\n effects: [\n () => {\n // The reactor's state transitions handle relevant presentation\n // changes (presentation-resolved ↔ presentation-unresolved);\n // within 'presentation-resolved' we peek (untracked read) so\n // internal updates (segments added by sibling tasks) don't\n // re-fire the effect.\n const presentation = peek(state.presentation);\n const trackId = state[selectedKey].get();\n if (!presentation || !trackId) return;\n\n const track = findTrackToResolve(presentation, trackId);\n if (!track || isResolvedTrack(track)) return;\n\n runner.schedule(\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createResolveTrackTask(track, context, config)),\n // likely eventually passed down via config or a new \"definitions\" argument (CJP).\n new Task(\n async (signal) => {\n // `fetchResolvableText` is the behavior's failover-decorated\n // fetch: it trips the CDN on a failed fetch (network error or\n // non-OK status). A parse failure is a content issue, not a\n // CDN-availability one, so it doesn't trip.\n const text = await fetchResolvableText(track, { signal });\n const mediaTrack = parseMediaPlaylist(text, track);\n\n // Updater handles undefined inputs by returning current\n // unchanged; isResolvedPresentation narrows for the patch.\n // State-exit on resolving→unresolved fires runner.abortAll\n // before any URL change settles, and per the Fetch spec the\n // signal abort cancels in-flight body reads — so by the\n // time we reach this point the presentation we resolved\n // against is the live one.\n update(state.presentation, (current) => {\n if (!isResolvedPresentation(current)) return current;\n const patched = updateTrackInPresentation(current, mediaTrack);\n // Container is uniform within a type (an ABR ladder shares\n // its container), so a detected non-fMP4 rendition (TS,\n // raw AAC) implies every rendition of *this* type matches —\n // relabel them all from one resolved playlist instead of\n // fetching each. Scoped to this track's own type: never cross\n // audio↔video (mixed-container sources exist, e.g. muxed-TS\n // video + raw-.aac audio), which also keeps per-type\n // resolutions' writes disjoint (no race).\n return NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType)\n ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType)\n : patched;\n });\n },\n { id: track.id }\n )\n );\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_TRACK_RESOLUTION_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'video', trackId),\n} as const;\n\nconst AUDIO_TRACK_RESOLUTION_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'audio', trackId),\n} as const;\n\nconst TEXT_TRACK_RESOLUTION_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'text', trackId),\n} as const;\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Resolve unresolved video tracks. Schedules a fetch task whenever the\n * selected video track is partially resolved, parses the manifest, and\n * writes the resolved track back into `state.presentation`.\n */\nexport const resolveVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedVideoTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Engine `config` layers over the per-type defaults (mirrors the other\n // per-type variants, see track-types.ts); `failoverFetch` reads its\n // `selectedKey` + `getCdnId` from the merged result. `fetchResolvableText`\n // is then placed AFTER the spread so the failover-decorated fetch wins —\n // unlike segments, playlists expose no overridable per-type fetch.\n const trackConfig = { ...VIDEO_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/**\n * Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,\n * narrowed to audio.\n */\nexport const resolveAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedAudioTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...AUDIO_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/**\n * Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,\n * narrowed to text.\n */\nexport const resolveTextTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedTextTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedTextTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...TEXT_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;AA6DA,SAAS,qBAAiD,EACxD,OACA,QAAQ,EAAE,aAAa,oBAAoB,qBAAA,wBAAsBA,yBAIhE;CAID,MAAM,SAAS,IAAI,kBAAkB;CAWrC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,KAAK,CAAC,GAC3C,0BACA,0BACN;AAED,QAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,KAAK;EACvC,QAAQ;GACN,2BAA2B,EAAE;GAC7B,yBAAyB;IAOvB,mBAAmB,OAAO,UAAU;IACpC,SAAS,OACD;KAMJ,MAAM,eAAe,KAAK,MAAM,aAAa;KAC7C,MAAM,UAAU,MAAM,aAAa,KAAK;AACxC,SAAI,CAAC,gBAAgB,CAAC,QAAS;KAE/B,MAAM,QAAQ,mBAAmB,cAAc,QAAQ;AACvD,SAAI,CAAC,SAAS,gBAAgB,MAAM,CAAE;AAEtC,YAAO,SAGL,IAAI,KACF,OAAO,WAAW;MAMhB,MAAM,aAAa,mBADN,MAAMC,sBAAoB,OAAO,EAAE,QAAQ,CAAC,EACb,MAAM;AASlD,aAAO,MAAM,eAAe,YAAY;AACtC,WAAI,CAAC,uBAAuB,QAAQ,CAAE,QAAO;OAC7C,MAAM,UAAU,0BAA0B,SAAS,WAAW;AAS9D,cAAO,yBAAyB,IAAI,WAAW,SAAS,GACpD,uBAAuB,SAAS,WAAW,MAAM,WAAW,SAAS,GACrE;QACJ;QAEJ,EAAE,IAAI,MAAM,IAAI,CACjB,CACF;MAEJ;IACF;GACF;EACF,CAAC;;AAOJ,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,QAAQ;CAC5C;AAED,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,QAAQ;CAC5C;AAED,MAAM,+BAA+B;CACnC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,QAAQ,QAAQ;CAC3C;;;;;;AAWD,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EACN,OACA,SAAS,EAAE,OAIP;EAMJ,MAAM,cAAc;GAAE,GAAG;GAA+B,GAAG;GAAQ;AACnE,SAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcD,qBAA4B,OAAO,YAAY;IAAE;GAC/G,CAAC;;CAEL,CAAC;;;;;AAMF,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EACN,OACA,SAAS,EAAE,OAIP;EAEJ,MAAM,cAAc;GAAE,GAAG;GAA+B,GAAG;GAAQ;AACnE,SAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcA,qBAA4B,OAAO,YAAY;IAAE;GAC/G,CAAC;;CAEL,CAAC;;;;;AAMF,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,EAAE;CACf,QAAQ,EACN,OACA,SAAS,EAAE,OAIP;EAEJ,MAAM,cAAc;GAAE,GAAG;GAA8B,GAAG;GAAQ;AAClE,SAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcA,qBAA4B,OAAO,YAAY;IAAE;GAC/G,CAAC;;CAEL,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { computed } 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 { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
|
|
6
|
+
import { pickAudioTrack, pickFirstTrackId } from "../../media/primitives/select-tracks.js";
|
|
7
|
+
//#region src/playback/behaviors/select-tracks.ts
|
|
8
|
+
/**
|
|
9
|
+
* **Default audio/video track selection on src load / unselect on src unload.**
|
|
10
|
+
* When a presentation is resolved, sets `selectedVideoTrackId` /
|
|
11
|
+
* `selectedAudioTrackId` to a per-type-picker default if no selection already
|
|
12
|
+
* exists. When the presentation is unset/reset (transitions back to unresolved),
|
|
13
|
+
* clears the selection so a stale id from the previous source doesn't persist.
|
|
14
|
+
*
|
|
15
|
+
* Lifecycle-driven: each transition fires its work once. Does not police the
|
|
16
|
+
* selection between transitions; external writes (user picks, ABR, programmatic
|
|
17
|
+
* filter-driven re-picks) are left alone.
|
|
18
|
+
*
|
|
19
|
+
* Picker is config-driven: each per-type export wires a sensible default
|
|
20
|
+
* (`pickAudioTrack` for audio — three-tier language-aware; `pickFirstTrackId`
|
|
21
|
+
* for video) and the caller can supply their own via `config.picker` for custom
|
|
22
|
+
* selection logic. The behavior's `config` is forwarded to the picker as its
|
|
23
|
+
* second argument, so options like `preferredAudioLanguage` reach the picker
|
|
24
|
+
* without an intermediate wrapping layer.
|
|
25
|
+
*
|
|
26
|
+
* Compose `selectVideoTrack` for the simple "pick a default video track"
|
|
27
|
+
* behavior, or `switchVideoTrack` (`./track-switching.ts`) for the
|
|
28
|
+
* ABR-driven variant. Compose `selectAudioTrack` for the simple default
|
|
29
|
+
* pick, or `switchAudioTrack` (`./track-switching.ts`) for the
|
|
30
|
+
* filter-reactive + mid-stream-flush slot-owner variant — when audio-abr
|
|
31
|
+
* lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose
|
|
32
|
+
* only one per type — they're alternatives, not stackable (each writes
|
|
33
|
+
* the same `selected*TrackId` slot). The simple variants tree-shake out
|
|
34
|
+
* the heavier machinery (bandwidth estimator, quality selection, flush
|
|
35
|
+
* orchestration).
|
|
36
|
+
*
|
|
37
|
+
* Text selection has no simple variant here — it's owned by `switchTextTrack`
|
|
38
|
+
* (`./track-switching.ts`), which resolves standing `userTextTrackSelection`
|
|
39
|
+
* intent against the constrained, CDN-scoped renditions.
|
|
40
|
+
*/
|
|
41
|
+
function setupTrackSelection({ state, config: { selectedKey, picker, pickerConfig } }) {
|
|
42
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
43
|
+
return createMachineReactor({
|
|
44
|
+
initial: "presentation-unresolved",
|
|
45
|
+
monitor: () => derivedStateSignal.get(),
|
|
46
|
+
states: {
|
|
47
|
+
"presentation-unresolved": {},
|
|
48
|
+
"presentation-resolved": { entry: () => {
|
|
49
|
+
if (!state[selectedKey].get()) {
|
|
50
|
+
const id = picker(state.presentation.get(), pickerConfig);
|
|
51
|
+
if (id) state[selectedKey].set(id);
|
|
52
|
+
}
|
|
53
|
+
return () => state[selectedKey].set(void 0);
|
|
54
|
+
} }
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Default video picker: first track in the video selection set. */
|
|
59
|
+
const defaultVideoPicker = (presentation) => pickFirstTrackId(presentation, "video");
|
|
60
|
+
/**
|
|
61
|
+
* Select a video track when a presentation loads. Clears the selection on
|
|
62
|
+
* src unload.
|
|
63
|
+
*
|
|
64
|
+
* This is the simple, non-ABR counterpart to `switchVideoTrack` — compose
|
|
65
|
+
* one or the other, not both (both write `selectedVideoTrackId`). Composing
|
|
66
|
+
* `selectVideoTrack` alone tree-shakes out the ABR code path
|
|
67
|
+
* (bandwidth-estimator, quality-selection); use it for sources without
|
|
68
|
+
* meaningful quality variants, test setups, or players that intentionally
|
|
69
|
+
* pin a quality.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const reactor = selectVideoTrack.setup({ state });
|
|
73
|
+
*/
|
|
74
|
+
const selectVideoTrack = defineBehavior({
|
|
75
|
+
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
76
|
+
contextKeys: [],
|
|
77
|
+
setup: ({ state, config }) => setupTrackSelection({
|
|
78
|
+
state,
|
|
79
|
+
config: {
|
|
80
|
+
selectedKey: VIDEO_TYPE_CONFIG.selectedKey,
|
|
81
|
+
picker: config?.picker ?? defaultVideoPicker,
|
|
82
|
+
pickerConfig: config
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
defineBehavior({
|
|
87
|
+
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
88
|
+
contextKeys: [],
|
|
89
|
+
setup: ({ state, config }) => setupTrackSelection({
|
|
90
|
+
state,
|
|
91
|
+
config: {
|
|
92
|
+
selectedKey: AUDIO_TYPE_CONFIG.selectedKey,
|
|
93
|
+
picker: config?.picker ?? pickAudioTrack,
|
|
94
|
+
pickerConfig: config
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
//#endregion
|
|
99
|
+
export { selectVideoTrack };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=select-tracks.js.map
|