@videojs/spf 10.0.0-beta.23 → 10.0.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/default/background-video.js +3 -0
- package/dist/{dev/core → default/core/actors}/create-machine-actor.js +3 -3
- package/dist/default/core/actors/create-machine-actor.js.map +1 -0
- package/dist/default/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
- package/dist/default/core/actors/create-transition-actor.js.map +1 -0
- package/dist/default/core/composition/create-composition.js +85 -0
- package/dist/default/core/composition/create-composition.js.map +1 -0
- package/dist/default/core/composition/share-signals.js +39 -0
- package/dist/default/core/composition/share-signals.js.map +1 -0
- package/dist/{dev/core → default/core/reactors}/create-machine-reactor.js +4 -4
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/default/core/signals/primitives.js +33 -11
- package/dist/default/core/signals/primitives.js.map +1 -1
- package/dist/default/core/{task.js → tasks/task.js} +2 -2
- package/dist/default/core/tasks/task.js.map +1 -0
- package/dist/default/dom.js +9 -10
- package/dist/default/hls.js +5 -0
- package/dist/default/index.js +12 -3
- package/dist/default/index.js.map +1 -1
- package/dist/default/media/abr/quality-selection.js +21 -0
- package/dist/default/media/abr/quality-selection.js.map +1 -0
- package/dist/default/{core → media}/buffer/back-buffer.js +2 -2
- package/dist/default/media/buffer/back-buffer.js.map +1 -0
- package/dist/{dev/core → default/media}/buffer/forward-buffer.js +14 -2
- package/dist/default/media/buffer/forward-buffer.js.map +1 -0
- package/dist/default/media/dom/capabilities.js +70 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/{dev/dom/media → default/media/dom/mse}/append-segment.js +1 -1
- package/dist/default/media/dom/mse/append-segment.js.map +1 -0
- package/dist/default/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/default/media/dom/mse/duration.js +78 -0
- package/dist/default/media/dom/mse/duration.js.map +1 -0
- package/dist/default/media/dom/mse/end-of-stream.js +20 -0
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/default/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/default/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/default/media/dom/text/text-track-slots.js +69 -0
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/{dev/core → default/media}/hls/parse-attributes.js +17 -3
- package/dist/default/media/hls/parse-attributes.js.map +1 -0
- package/dist/{dev/core → default/media}/hls/parse-media-playlist.js +25 -2
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/{dev/core → default/media}/hls/parse-multivariant.js +17 -8
- package/dist/default/media/hls/parse-multivariant.js.map +1 -0
- package/dist/default/{core → media}/hls/resolve-url.js +1 -1
- package/dist/default/media/hls/resolve-url.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js +130 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -0
- package/dist/default/media/types/index.js +36 -0
- package/dist/default/media/types/index.js.map +1 -0
- package/dist/default/media/utils/cdn.js +56 -0
- package/dist/default/media/utils/cdn.js.map +1 -0
- package/dist/default/media/utils/preload.js +22 -0
- package/dist/default/media/utils/preload.js.map +1 -0
- package/dist/default/media/utils/track-selection.js +47 -0
- package/dist/default/media/utils/track-selection.js.map +1 -0
- package/dist/default/media/utils/tracks.js +130 -0
- package/dist/default/media/utils/tracks.js.map +1 -0
- package/dist/default/{core/abr → network}/bandwidth-estimator.js +6 -5
- package/dist/default/network/bandwidth-estimator.js.map +1 -0
- package/dist/{dev/dom → default}/network/chunked-stream-iterable.js +1 -1
- package/dist/default/network/chunked-stream-iterable.js.map +1 -0
- package/dist/{dev/core/abr → default/network}/ewma.js +1 -1
- package/dist/default/network/ewma.js.map +1 -0
- package/dist/default/network/fetch.js +115 -0
- package/dist/default/network/fetch.js.map +1 -0
- package/dist/default/{dom/features/segment-loader-actor.js → playback/actors/dom/segment-loader.js} +63 -23
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/default/{dom/media/source-buffer-actor.js → playback/actors/dom/source-buffer.js} +10 -9
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/{dev/dom/features/text-tracks-actor.js → default/playback/actors/dom/text-tracks.js} +7 -3
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/default/playback/actors/text-track-segment-loader.js +135 -0
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js +90 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/default/playback/behaviors/dom/load-segments.js +177 -0
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +192 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +89 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +54 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +90 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-track.js +120 -0
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/default/playback/behaviors/select-tracks.js +101 -0
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/default/playback/behaviors/sync-preload.js +59 -0
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/default/playback/behaviors/track-switching.js +341 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- package/dist/default/playback/engines/background-video/adapter.js +158 -0
- package/dist/default/playback/engines/background-video/adapter.js.map +1 -0
- package/dist/default/playback/engines/background-video/engine.js +75 -0
- package/dist/default/playback/engines/background-video/engine.js.map +1 -0
- package/dist/default/playback/engines/hls/adapter-audio-only.js +119 -0
- package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -0
- package/dist/default/{dom/playback-engine → playback/engines/hls}/adapter.js +40 -26
- package/dist/default/playback/engines/hls/adapter.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js +80 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +115 -0
- package/dist/default/playback/engines/hls/engine.js.map +1 -0
- package/dist/default/playback/primitives/failover-fetch.js +42 -0
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/default/playback/primitives/track-types.js +61 -0
- package/dist/default/playback/primitives/track-types.js.map +1 -0
- package/dist/dev/background-video.d.ts +3 -0
- package/dist/dev/background-video.js +3 -0
- package/dist/dev/core/{actor.d.ts → actors/actor.d.ts} +2 -2
- package/dist/dev/core/actors/actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts +125 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
- package/dist/{default/core → dev/core/actors}/create-machine-actor.js +3 -3
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts +42 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
- package/dist/dev/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
- package/dist/dev/core/composition/create-composition.d.ts +274 -0
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
- package/dist/dev/core/composition/create-composition.js +85 -0
- package/dist/dev/core/composition/create-composition.js.map +1 -0
- package/dist/dev/core/composition/share-signals.d.ts +47 -0
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
- package/dist/dev/core/composition/share-signals.js +39 -0
- package/dist/dev/core/composition/share-signals.js.map +1 -0
- package/dist/dev/core/machine.d.ts +0 -1
- package/dist/dev/core/machine.d.ts.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +93 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
- package/dist/{default/core → dev/core/reactors}/create-machine-reactor.js +4 -4
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +38 -1
- package/dist/dev/core/signals/primitives.d.ts.map +1 -1
- package/dist/dev/core/signals/primitives.js +33 -11
- package/dist/dev/core/signals/primitives.js.map +1 -1
- package/dist/dev/core/tasks/task.d.ts +115 -0
- package/dist/dev/core/tasks/task.d.ts.map +1 -0
- package/dist/dev/core/{task.js → tasks/task.js} +2 -2
- package/dist/dev/core/tasks/task.js.map +1 -0
- package/dist/dev/dom.d.ts +9 -10
- package/dist/dev/dom.js +9 -10
- package/dist/dev/hls.d.ts +5 -0
- package/dist/dev/hls.js +5 -0
- package/dist/dev/index.d.ts +15 -3
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +12 -3
- package/dist/dev/index.js.map +1 -1
- package/dist/dev/media/abr/quality-selection.d.ts +22 -0
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
- package/dist/dev/media/abr/quality-selection.js +21 -0
- package/dist/dev/media/abr/quality-selection.js.map +1 -0
- package/dist/dev/media/buffer/back-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
- package/dist/dev/{core → media}/buffer/back-buffer.js +2 -2
- package/dist/dev/media/buffer/back-buffer.js.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
- package/dist/{default/core → dev/media}/buffer/forward-buffer.js +14 -2
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
- package/dist/dev/media/dom/capabilities.js +70 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/append-segment.d.ts +1 -1
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
- package/dist/{default/dom/media → dev/media/dom/mse}/append-segment.js +1 -1
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.d.ts +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/dev/media/dom/mse/duration.js +78 -0
- package/dist/dev/media/dom/mse/duration.js.map +1 -0
- package/dist/dev/media/dom/mse/end-of-stream.js +20 -0
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/dev/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +12 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
- package/dist/dev/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts +31 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.js +69 -0
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/{default/core → dev/media}/hls/parse-attributes.js +17 -3
- package/dist/dev/media/hls/parse-attributes.js.map +1 -0
- package/dist/{default/core → dev/media}/hls/parse-media-playlist.js +25 -2
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/{default/core → dev/media}/hls/parse-multivariant.js +17 -8
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
- package/dist/dev/{core → media}/hls/resolve-url.js +1 -1
- package/dist/dev/media/hls/resolve-url.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.d.ts +36 -0
- package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +130 -0
- package/dist/dev/media/primitives/select-tracks.js.map +1 -0
- package/dist/dev/{core → media}/types/index.d.ts +55 -3
- package/dist/dev/media/types/index.d.ts.map +1 -0
- package/dist/dev/media/types/index.js +36 -0
- package/dist/dev/media/types/index.js.map +1 -0
- package/dist/dev/media/utils/cdn.d.ts +13 -0
- package/dist/dev/media/utils/cdn.d.ts.map +1 -0
- package/dist/dev/media/utils/cdn.js +56 -0
- package/dist/dev/media/utils/cdn.js.map +1 -0
- package/dist/dev/media/utils/preload.js +22 -0
- package/dist/dev/media/utils/preload.js.map +1 -0
- package/dist/dev/media/utils/track-selection.js +47 -0
- package/dist/dev/media/utils/track-selection.js.map +1 -0
- package/dist/dev/media/utils/tracks.js +130 -0
- package/dist/dev/media/utils/tracks.js.map +1 -0
- package/dist/dev/{core/abr → network}/bandwidth-estimator.d.ts +17 -2
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
- package/dist/dev/{core/abr → network}/bandwidth-estimator.js +6 -5
- package/dist/dev/network/bandwidth-estimator.js.map +1 -0
- package/dist/{default/dom → dev}/network/chunked-stream-iterable.js +1 -1
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
- package/dist/{default/core/abr → dev/network}/ewma.js +1 -1
- package/dist/dev/network/ewma.js.map +1 -0
- package/dist/dev/network/fetch.js +115 -0
- package/dist/dev/network/fetch.js.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +36 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
- package/dist/dev/{dom/features/segment-loader-actor.js → playback/actors/dom/segment-loader.js} +63 -23
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/dev/{dom/media/source-buffer-actor.d.ts → playback/actors/dom/source-buffer.d.ts} +20 -5
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
- package/dist/dev/{dom/media/source-buffer-actor.js → playback/actors/dom/source-buffer.js} +10 -9
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +1 -0
- package/dist/{default/dom/features/text-tracks-actor.js → dev/playback/actors/dom/text-tracks.js} +7 -3
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +59 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js +135 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/text-tracks.d.ts +42 -0
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +25 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +90 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +62 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js +177 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +192 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +89 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +31 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +54 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +34 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +48 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +34 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +90 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +7 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-track.js +120 -0
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts +13 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +101 -0
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +12 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/dev/playback/behaviors/sync-preload.js +59 -0
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.js +341 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/background-video/adapter.d.ts +60 -0
- package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/engines/background-video/adapter.js +158 -0
- package/dist/dev/playback/engines/background-video/adapter.js.map +1 -0
- package/dist/dev/playback/engines/background-video/engine.d.ts +107 -0
- package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/background-video/engine.js +75 -0
- package/dist/dev/playback/engines/background-video/engine.js.map +1 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +45 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.js +119 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -0
- package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.d.ts +13 -12
- package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -0
- package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.js +40 -26
- package/dist/dev/playback/engines/hls/adapter.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +127 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js +80 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +236 -0
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine.js +115 -0
- package/dist/dev/playback/engines/hls/engine.js.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js +42 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/dev/playback/primitives/track-types.js +61 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +12 -7
- package/dist/default/core/abr/bandwidth-estimator.js.map +0 -1
- package/dist/default/core/abr/ewma.js.map +0 -1
- package/dist/default/core/abr/quality-selection.js +0 -53
- package/dist/default/core/abr/quality-selection.js.map +0 -1
- package/dist/default/core/buffer/back-buffer.js.map +0 -1
- package/dist/default/core/buffer/forward-buffer.js.map +0 -1
- package/dist/default/core/create-machine-actor.js.map +0 -1
- package/dist/default/core/create-machine-reactor.js.map +0 -1
- package/dist/default/core/create-transition-actor.js.map +0 -1
- package/dist/default/core/features/calculate-presentation-duration.js +0 -53
- package/dist/default/core/features/calculate-presentation-duration.js.map +0 -1
- package/dist/default/core/features/quality-switching.js +0 -69
- package/dist/default/core/features/quality-switching.js.map +0 -1
- package/dist/default/core/features/resolve-presentation.js +0 -73
- package/dist/default/core/features/resolve-presentation.js.map +0 -1
- package/dist/default/core/features/resolve-track.js +0 -66
- package/dist/default/core/features/resolve-track.js.map +0 -1
- package/dist/default/core/features/select-tracks.js +0 -130
- package/dist/default/core/features/select-tracks.js.map +0 -1
- package/dist/default/core/features/sync-preload-attribute.js +0 -26
- package/dist/default/core/features/sync-preload-attribute.js.map +0 -1
- package/dist/default/core/hls/parse-attributes.js.map +0 -1
- package/dist/default/core/hls/parse-media-playlist.js.map +0 -1
- package/dist/default/core/hls/parse-multivariant.js.map +0 -1
- package/dist/default/core/hls/resolve-url.js.map +0 -1
- package/dist/default/core/task.js.map +0 -1
- package/dist/default/core/types/index.js +0 -15
- package/dist/default/core/types/index.js.map +0 -1
- package/dist/default/core/utils/generate-id.js +0 -21
- package/dist/default/core/utils/generate-id.js.map +0 -1
- package/dist/default/core/utils/track-selection.js +0 -39
- package/dist/default/core/utils/track-selection.js.map +0 -1
- package/dist/default/dom/features/end-of-stream.js +0 -145
- package/dist/default/dom/features/end-of-stream.js.map +0 -1
- package/dist/default/dom/features/load-segments.js +0 -192
- package/dist/default/dom/features/load-segments.js.map +0 -1
- package/dist/default/dom/features/load-text-track-cues.js +0 -104
- package/dist/default/dom/features/load-text-track-cues.js.map +0 -1
- package/dist/default/dom/features/segment-loader-actor.js.map +0 -1
- package/dist/default/dom/features/setup-mediasource.js +0 -48
- package/dist/default/dom/features/setup-mediasource.js.map +0 -1
- package/dist/default/dom/features/setup-sourcebuffer.js +0 -78
- package/dist/default/dom/features/setup-sourcebuffer.js.map +0 -1
- package/dist/default/dom/features/sync-text-tracks.js +0 -95
- package/dist/default/dom/features/sync-text-tracks.js.map +0 -1
- package/dist/default/dom/features/text-track-segment-loader-actor.js +0 -55
- package/dist/default/dom/features/text-track-segment-loader-actor.js.map +0 -1
- package/dist/default/dom/features/text-tracks-actor.js.map +0 -1
- package/dist/default/dom/features/track-current-time.js +0 -49
- package/dist/default/dom/features/track-current-time.js.map +0 -1
- package/dist/default/dom/features/track-playback-initiated.js +0 -66
- package/dist/default/dom/features/track-playback-initiated.js.map +0 -1
- package/dist/default/dom/features/track-playback-rate.js +0 -32
- package/dist/default/dom/features/track-playback-rate.js.map +0 -1
- package/dist/default/dom/features/update-duration.js +0 -78
- package/dist/default/dom/features/update-duration.js.map +0 -1
- package/dist/default/dom/media/append-segment.js.map +0 -1
- package/dist/default/dom/media/buffer-flusher.js.map +0 -1
- package/dist/default/dom/media/mediasource-setup.js.map +0 -1
- package/dist/default/dom/media/source-buffer-actor.js.map +0 -1
- package/dist/default/dom/network/chunked-stream-iterable.js.map +0 -1
- package/dist/default/dom/network/fetch.js +0 -54
- package/dist/default/dom/network/fetch.js.map +0 -1
- package/dist/default/dom/playback-engine/adapter.js.map +0 -1
- package/dist/default/dom/playback-engine/engine.js +0 -142
- package/dist/default/dom/playback-engine/engine.js.map +0 -1
- package/dist/default/dom/playback-engine.js +0 -4
- package/dist/default/dom/text/parse-vtt-segment.js.map +0 -1
- package/dist/dev/core/abr/bandwidth-estimator.d.ts.map +0 -1
- package/dist/dev/core/abr/bandwidth-estimator.js.map +0 -1
- package/dist/dev/core/abr/ewma.js.map +0 -1
- package/dist/dev/core/abr/quality-selection.js +0 -53
- package/dist/dev/core/abr/quality-selection.js.map +0 -1
- package/dist/dev/core/actor.d.ts.map +0 -1
- package/dist/dev/core/buffer/back-buffer.js.map +0 -1
- package/dist/dev/core/buffer/forward-buffer.js.map +0 -1
- package/dist/dev/core/create-machine-actor.d.ts +0 -12
- package/dist/dev/core/create-machine-actor.d.ts.map +0 -1
- package/dist/dev/core/create-machine-actor.js.map +0 -1
- package/dist/dev/core/create-machine-reactor.d.ts +0 -8
- package/dist/dev/core/create-machine-reactor.d.ts.map +0 -1
- package/dist/dev/core/create-machine-reactor.js.map +0 -1
- package/dist/dev/core/create-transition-actor.d.ts +0 -23
- package/dist/dev/core/create-transition-actor.d.ts.map +0 -1
- package/dist/dev/core/create-transition-actor.js.map +0 -1
- package/dist/dev/core/features/calculate-presentation-duration.js +0 -53
- package/dist/dev/core/features/calculate-presentation-duration.js.map +0 -1
- package/dist/dev/core/features/quality-switching.js +0 -69
- package/dist/dev/core/features/quality-switching.js.map +0 -1
- package/dist/dev/core/features/resolve-presentation.js +0 -73
- package/dist/dev/core/features/resolve-presentation.js.map +0 -1
- package/dist/dev/core/features/resolve-track.js +0 -66
- package/dist/dev/core/features/resolve-track.js.map +0 -1
- package/dist/dev/core/features/select-tracks.js +0 -130
- package/dist/dev/core/features/select-tracks.js.map +0 -1
- package/dist/dev/core/features/sync-preload-attribute.js +0 -26
- package/dist/dev/core/features/sync-preload-attribute.js.map +0 -1
- package/dist/dev/core/hls/parse-attributes.js.map +0 -1
- package/dist/dev/core/hls/parse-media-playlist.js.map +0 -1
- package/dist/dev/core/hls/parse-multivariant.js.map +0 -1
- package/dist/dev/core/hls/resolve-url.js.map +0 -1
- package/dist/dev/core/task.js.map +0 -1
- package/dist/dev/core/types/index.d.ts.map +0 -1
- package/dist/dev/core/types/index.js +0 -15
- package/dist/dev/core/types/index.js.map +0 -1
- package/dist/dev/core/utils/generate-id.js +0 -21
- package/dist/dev/core/utils/generate-id.js.map +0 -1
- package/dist/dev/core/utils/track-selection.d.ts +0 -16
- package/dist/dev/core/utils/track-selection.d.ts.map +0 -1
- package/dist/dev/core/utils/track-selection.js +0 -39
- package/dist/dev/core/utils/track-selection.js.map +0 -1
- package/dist/dev/dom/features/end-of-stream.js +0 -145
- package/dist/dev/dom/features/end-of-stream.js.map +0 -1
- package/dist/dev/dom/features/load-segments.d.ts +0 -72
- package/dist/dev/dom/features/load-segments.d.ts.map +0 -1
- package/dist/dev/dom/features/load-segments.js +0 -192
- package/dist/dev/dom/features/load-segments.js.map +0 -1
- package/dist/dev/dom/features/load-text-track-cues.d.ts +0 -85
- package/dist/dev/dom/features/load-text-track-cues.d.ts.map +0 -1
- package/dist/dev/dom/features/load-text-track-cues.js +0 -104
- package/dist/dev/dom/features/load-text-track-cues.js.map +0 -1
- package/dist/dev/dom/features/segment-loader-actor.d.ts +0 -1
- package/dist/dev/dom/features/segment-loader-actor.js.map +0 -1
- package/dist/dev/dom/features/setup-mediasource.js +0 -48
- package/dist/dev/dom/features/setup-mediasource.js.map +0 -1
- package/dist/dev/dom/features/setup-sourcebuffer.d.ts +0 -9
- package/dist/dev/dom/features/setup-sourcebuffer.d.ts.map +0 -1
- package/dist/dev/dom/features/setup-sourcebuffer.js +0 -78
- package/dist/dev/dom/features/setup-sourcebuffer.js.map +0 -1
- package/dist/dev/dom/features/sync-text-tracks.js +0 -95
- package/dist/dev/dom/features/sync-text-tracks.js.map +0 -1
- package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts +0 -12
- package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts.map +0 -1
- package/dist/dev/dom/features/text-track-segment-loader-actor.js +0 -55
- package/dist/dev/dom/features/text-track-segment-loader-actor.js.map +0 -1
- package/dist/dev/dom/features/text-tracks-actor.d.ts +0 -31
- package/dist/dev/dom/features/text-tracks-actor.d.ts.map +0 -1
- package/dist/dev/dom/features/text-tracks-actor.js.map +0 -1
- package/dist/dev/dom/features/track-current-time.d.ts +0 -40
- package/dist/dev/dom/features/track-current-time.d.ts.map +0 -1
- package/dist/dev/dom/features/track-current-time.js +0 -49
- package/dist/dev/dom/features/track-current-time.js.map +0 -1
- package/dist/dev/dom/features/track-playback-initiated.d.ts +0 -46
- package/dist/dev/dom/features/track-playback-initiated.d.ts.map +0 -1
- package/dist/dev/dom/features/track-playback-initiated.js +0 -66
- package/dist/dev/dom/features/track-playback-initiated.js.map +0 -1
- package/dist/dev/dom/features/track-playback-rate.d.ts +0 -34
- package/dist/dev/dom/features/track-playback-rate.d.ts.map +0 -1
- package/dist/dev/dom/features/track-playback-rate.js +0 -32
- package/dist/dev/dom/features/track-playback-rate.js.map +0 -1
- package/dist/dev/dom/features/update-duration.js +0 -78
- package/dist/dev/dom/features/update-duration.js.map +0 -1
- package/dist/dev/dom/media/append-segment.d.ts.map +0 -1
- package/dist/dev/dom/media/append-segment.js.map +0 -1
- package/dist/dev/dom/media/buffer-flusher.d.ts.map +0 -1
- package/dist/dev/dom/media/buffer-flusher.js.map +0 -1
- package/dist/dev/dom/media/mediasource-setup.js.map +0 -1
- package/dist/dev/dom/media/source-buffer-actor.d.ts.map +0 -1
- package/dist/dev/dom/media/source-buffer-actor.js.map +0 -1
- package/dist/dev/dom/network/chunked-stream-iterable.js.map +0 -1
- package/dist/dev/dom/network/fetch.js +0 -54
- package/dist/dev/dom/network/fetch.js.map +0 -1
- package/dist/dev/dom/playback-engine/adapter.d.ts.map +0 -1
- package/dist/dev/dom/playback-engine/adapter.js.map +0 -1
- package/dist/dev/dom/playback-engine/engine.d.ts +0 -124
- package/dist/dev/dom/playback-engine/engine.d.ts.map +0 -1
- package/dist/dev/dom/playback-engine/engine.js +0 -142
- package/dist/dev/dom/playback-engine/engine.js.map +0 -1
- package/dist/dev/dom/playback-engine.d.ts +0 -4
- package/dist/dev/dom/playback-engine.js +0 -4
- package/dist/dev/dom/text/parse-vtt-segment.d.ts +0 -12
- package/dist/dev/dom/text/parse-vtt-segment.d.ts.map +0 -1
- package/dist/dev/dom/text/parse-vtt-segment.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-tracks.js","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"sourcesContent":["/**\n * **Default audio/video track selection on src load / unselect on src unload.**\n * When a presentation is resolved, sets `selectedVideoTrackId` /\n * `selectedAudioTrackId` to a per-type-picker default if no selection already\n * exists. When the presentation is unset/reset (transitions back to unresolved),\n * clears the selection so a stale id from the previous source doesn't persist.\n *\n * Lifecycle-driven: each transition fires its work once. Does not police the\n * selection between transitions; external writes (user picks, ABR, programmatic\n * filter-driven re-picks) are left alone.\n *\n * Picker is config-driven: each per-type export wires a sensible default\n * (`pickAudioTrack` for audio — three-tier language-aware; `pickFirstTrackId`\n * for video) and the caller can supply their own via `config.picker` for custom\n * selection logic. The behavior's `config` is forwarded to the picker as its\n * second argument, so options like `preferredAudioLanguage` reach the picker\n * without an intermediate wrapping layer.\n *\n * Compose `selectVideoTrack` for the simple \"pick a default video track\"\n * behavior, or `switchVideoTrack` (`./track-switching.ts`) for the\n * ABR-driven variant. Compose `selectAudioTrack` for the simple default\n * pick, or `switchAudioTrack` (`./track-switching.ts`) for the\n * filter-reactive + mid-stream-flush slot-owner variant — when audio-abr\n * lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose\n * only one per type — they're alternatives, not stackable (each writes\n * the same `selected*TrackId` slot). The simple variants tree-shake out\n * the heavier machinery (bandwidth estimator, quality selection, flush\n * orchestration).\n *\n * Text selection has no simple variant here — it's owned by `switchTextTrack`\n * (`./track-switching.ts`), which resolves standing `userTextTrackSelection`\n * intent against the constrained, CDN-scoped renditions.\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport {\n type AudioSelectionConfig,\n pickAudioTrack,\n pickFirstTrackId,\n type TrackPicker,\n type TrackSelectionState,\n type VideoSelectionConfig,\n} from '../../media/primitives/select-tracks';\nimport { isResolvedPresentation } from '../../media/types';\nimport { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackSelection` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => Reactor`. Each `selectXTrack` export below calls\n// it from inside its own `defineBehavior` setup, supplying its per-type\n// `selectedKey`, default picker, and forwarded picker config. The lifecycle\n// — pick on entering 'presentation-resolved' if not already selected; clear\n// on entering 'presentation-unresolved' — is shared.\n// ============================================================================\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId';\n\ntype SelectStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<TrackSelectionState['presentation']>;\n} & { [P in K]: Signal<TrackSelectionState[P]> };\n\ninterface TrackSelectionSetupConfig<K extends SelectedTrackKey, PickerConfig> {\n selectedKey: K;\n picker: TrackPicker<PickerConfig>;\n pickerConfig?: PickerConfig;\n}\n\nfunction setupTrackSelection<K extends SelectedTrackKey, PickerConfig>({\n state,\n config: { selectedKey, picker, pickerConfig },\n}: {\n state: SelectStateMap<K>;\n config: TrackSelectionSetupConfig<K, PickerConfig>;\n}) {\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: pick a default on entering presentation-resolved if none\n // is set. External writes (user picks, ABR) that already populated\n // the slot are left alone.\n //\n // The returned cleanup runs on state exit — which fires on src\n // unload (presentation-resolved → presentation-unresolved) AND on\n // behavior destroy (presentation-resolved → destroying →\n // destroyed). Putting the clear here rather than as\n // presentation-unresolved.entry is more cohesive (operation +\n // cleanup co-located) and correctly covers destroy (destroy\n // doesn't pass through presentation-unresolved).\n entry: () => {\n if (!state[selectedKey].get()) {\n // `state.presentation.get()` is non-null inside this entry —\n // the reactor's `'presentation-resolved'` gate is exactly\n // `isResolvedPresentation(state.presentation.get())`, which\n // requires a truthy Presentation.\n const id = picker(state.presentation.get()!, pickerConfig);\n if (id) state[selectedKey].set(id);\n }\n return () => state[selectedKey].set(undefined);\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Default pickers\n//\n// Each variant resolves its picker as `config?.picker ?? <default>` and\n// forwards the whole engine config as `pickerConfig`, so a rich picker\n// (`pickAudioTrack`) reads its options directly. Audio uses its primitive\n// picker as-is; video adapts `pickFirstTrackId` (positional `type` arg) into\n// the `TrackPicker` shape.\n// ============================================================================\n\n/** Default video picker: first track in the video selection set. */\nconst defaultVideoPicker: TrackPicker = (presentation) => pickFirstTrackId(presentation, 'video');\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Config for `selectVideoTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickFirstTrackId` is used.\n */\nexport interface SelectVideoTrackConfig extends VideoSelectionConfig {\n picker?: TrackPicker<SelectVideoTrackConfig>;\n}\n\n/**\n * Select a video track when a presentation loads. Clears the selection on\n * src unload.\n *\n * This is the simple, non-ABR counterpart to `switchVideoTrack` — compose\n * one or the other, not both (both write `selectedVideoTrackId`). Composing\n * `selectVideoTrack` alone tree-shakes out the ABR code path\n * (bandwidth-estimator, quality-selection); use it for sources without\n * meaningful quality variants, test setups, or players that intentionally\n * pin a quality.\n *\n * @example\n * const reactor = selectVideoTrack.setup({ state });\n */\nexport const selectVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedVideoTrackId'>; config?: SelectVideoTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n selectedKey: VIDEO_TYPE_CONFIG.selectedKey,\n picker: config?.picker ?? defaultVideoPicker,\n pickerConfig: config,\n },\n }),\n});\n\n/**\n * Config for `selectAudioTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickAudioTrack` is used (three-tier:\n * `preferredAudioLanguage` → `DEFAULT=YES` → first track).\n */\nexport interface SelectAudioTrackConfig extends AudioSelectionConfig {\n picker?: TrackPicker<SelectAudioTrackConfig>;\n}\n\n/**\n * Select an audio track when a presentation loads. Clears the selection\n * on src unload.\n *\n * This is the simple, lifecycle-only counterpart to `switchAudioTrack`\n * (in `./track-switching.ts`) — compose one or the other, not both\n * (both write `selectedAudioTrackId`). `switchAudioTrack` adds\n * filter-reactivity (`userAudioTrackSelection`) and mid-stream-flush\n * orchestration; `selectAudioTrack` covers the default-on-load case\n * without those. Use this variant for test setups, audio-only flows\n * that don't expose language switching, or composition variants that\n * intentionally pin a track.\n *\n * @example\n * const reactor = selectAudioTrack.setup({ state });\n *\n * @example\n * // Custom picker with language preference\n * const reactor = selectAudioTrack.setup({\n * state,\n * config: { preferredAudioLanguage: 'en', picker: myLanguageAwarePicker },\n * });\n */\nexport const selectAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedAudioTrackId'>; config?: SelectAudioTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n selectedKey: AUDIO_TYPE_CONFIG.selectedKey,\n picker: config?.picker ?? pickAudioTrack,\n pickerConfig: config,\n },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAS,oBAA8D,EACrE,OACA,QAAQ,EAAE,aAAa,QAAQ,kBAI9B;CACD,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,EAYvB,aAAa;AACX,QAAI,CAAC,MAAM,aAAa,KAAK,EAAE;KAK7B,MAAM,KAAK,OAAO,MAAM,aAAa,KAAK,EAAG,aAAa;AAC1D,SAAI,GAAI,OAAM,aAAa,IAAI,GAAG;;AAEpC,iBAAa,MAAM,aAAa,IAAI,KAAA,EAAU;MAEjD;GACF;EACF,CAAC;;;AAcJ,MAAM,sBAAmC,iBAAiB,iBAAiB,cAAc,QAAQ;;;;;;;;;;;;;;;AA4BjG,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,QAAQ,QAAQ,UAAU;GAC1B,cAAc;GACf;EACF,CAAC;CACL,CAAC;AAkC8B,eAAe;CAC7C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,QAAQ,QAAQ,UAAU;GAC1B,cAAc;GACf;EACF,CAAC;CACL,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { computed, update } 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
|
+
//#region src/playback/behaviors/setup-failover-monitor.ts
|
|
6
|
+
/**
|
|
7
|
+
* **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites
|
|
8
|
+
* own the *trip*: on a failed fetch they add the failing CDN (origin) to the
|
|
9
|
+
* `failedCdns` state signal directly. This behavior owns the *expiry*: while a
|
|
10
|
+
* presentation is resolved, it watches `failedCdns` and, for each CDN that
|
|
11
|
+
* appears, schedules a timer to remove it once its cooldown lapses.
|
|
12
|
+
* `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's
|
|
13
|
+
* tracks and the active-CDN scope falls to the next one — and back, once the
|
|
14
|
+
* cooldown removes it here.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new
|
|
17
|
+
* source starts with a clean slate). Policy (cooldown) is engine config. This is
|
|
18
|
+
* the minimal `network-resilience` slice — a single failure trips a CDN, since
|
|
19
|
+
* transient blips are the retry layer's job (it sits below the fetch sites, so
|
|
20
|
+
* anything that reaches `failedCdns` is already terminal).
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_FAILOVER_MONITOR_CONFIG = { cooldownMs: 3e5 };
|
|
23
|
+
/**
|
|
24
|
+
* Expire failed CDNs from `failedCdns` once their cooldown lapses, for the
|
|
25
|
+
* resolved source.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const reactor = setupFailoverMonitor.setup({ state });
|
|
29
|
+
*/
|
|
30
|
+
const setupFailoverMonitor = defineBehavior({
|
|
31
|
+
stateKeys: ["presentation", "failedCdns"],
|
|
32
|
+
contextKeys: [],
|
|
33
|
+
setup: ({ state, config = {} }) => {
|
|
34
|
+
const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;
|
|
35
|
+
const timers = /* @__PURE__ */ new Map();
|
|
36
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
37
|
+
return createMachineReactor({
|
|
38
|
+
initial: "presentation-unresolved",
|
|
39
|
+
monitor: () => derivedStateSignal.get(),
|
|
40
|
+
states: {
|
|
41
|
+
"presentation-unresolved": {},
|
|
42
|
+
"presentation-resolved": {
|
|
43
|
+
entry: () => () => {
|
|
44
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
45
|
+
timers.clear();
|
|
46
|
+
state.failedCdns.set(void 0);
|
|
47
|
+
},
|
|
48
|
+
effects: [() => {
|
|
49
|
+
(state.failedCdns.get() ?? []).forEach((cdn) => {
|
|
50
|
+
if (timers.has(cdn)) return;
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
timers.delete(cdn);
|
|
53
|
+
update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));
|
|
54
|
+
}, cooldownMs);
|
|
55
|
+
timers.set(cdn, timer);
|
|
56
|
+
});
|
|
57
|
+
}]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
//#endregion
|
|
64
|
+
export { setupFailoverMonitor };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=setup-failover-monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-failover-monitor.js","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"sourcesContent":["/**\n * **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites\n * own the *trip*: on a failed fetch they add the failing CDN (origin) to the\n * `failedCdns` state signal directly. This behavior owns the *expiry*: while a\n * presentation is resolved, it watches `failedCdns` and, for each CDN that\n * appears, schedules a timer to remove it once its cooldown lapses.\n * `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's\n * tracks and the active-CDN scope falls to the next one — and back, once the\n * cooldown removes it here.\n *\n * Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new\n * source starts with a clean slate). Policy (cooldown) is engine config. This is\n * the minimal `network-resilience` slice — a single failure trips a CDN, since\n * transient blips are the retry layer's job (it sits below the fetch sites, so\n * anything that reaches `failedCdns` is already terminal).\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\n\n/**\n * Failover policy: how long a CDN stays excluded after a failed fetch trips it.\n * Supplied via engine config.\n */\nexport interface FailoverMonitorConfig {\n /** How long a tripped CDN stays excluded, in milliseconds. */\n cooldownMs: number;\n}\n\nexport const DEFAULT_FAILOVER_MONITOR_CONFIG: FailoverMonitorConfig = {\n // 5 minutes — a CDN outage is an infrastructure problem that outlasts a\n // transient blip, so re-probing it sooner mostly re-trips. Matches the\n // prevailing prior-art default (ExoPlayer's location exclusion, hls.js's\n // content-steering penalty box).\n cooldownMs: 300_000,\n};\n\nexport interface SetupFailoverMonitorState {\n presentation?: MaybeResolvedPresentation;\n failedCdns?: string[];\n}\n\nexport interface SetupFailoverMonitorConfig {\n /** Failover policy (cooldown); defaults to `DEFAULT_FAILOVER_MONITOR_CONFIG`. */\n failover?: Partial<FailoverMonitorConfig>;\n}\n\n/**\n * Expire failed CDNs from `failedCdns` once their cooldown lapses, for the\n * resolved source.\n *\n * @example\n * const reactor = setupFailoverMonitor.setup({ state });\n */\nexport const setupFailoverMonitor = defineBehavior({\n stateKeys: ['presentation', 'failedCdns'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: {\n presentation: ReadonlySignal<SetupFailoverMonitorState['presentation']>;\n failedCdns: Signal<SetupFailoverMonitorState['failedCdns']>;\n };\n config?: SetupFailoverMonitorConfig;\n }) => {\n const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;\n // CDN id → its pending cooldown-removal timer. Shared by the `effects`\n // scheduler (adds a timer per newly-failed CDN) and the exit cleanup\n // (clears them). Per-source: emptied on exit, so it re-enters clean.\n const timers = new Map<string, ReturnType<typeof setTimeout>>();\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Cleanup-binds-to-setup: on exit (src unload + destroy) clear the\n // pending timers and reset `failedCdns` for the next source.\n entry: () => () => {\n timers.forEach((timer) => clearTimeout(timer));\n timers.clear();\n state.failedCdns.set(undefined);\n },\n effects: [\n () => {\n const failed = state.failedCdns.get() ?? [];\n failed.forEach((cdn) => {\n // Idempotent: a CDN already counting down keeps its original\n // deadline (re-failing it mid-cooldown doesn't extend it).\n if (timers.has(cdn)) return;\n const timer = setTimeout(() => {\n timers.delete(cdn);\n update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));\n }, cooldownMs);\n timers.set(cdn, timer);\n });\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,kCAAyD,EAKpE,YAAY,KACb;;;;;;;;AAmBD,MAAa,uBAAuB,eAAe;CACjD,WAAW,CAAC,gBAAgB,aAAa;CACzC,aAAa,EAAE;CACf,QAAQ,EACN,OACA,SAAS,EAAE,OAOP;EACJ,MAAM,aAAa,OAAO,UAAU,cAAc,gCAAgC;EAIlF,MAAM,yBAAS,IAAI,KAA4C;EAC/D,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,KAAK,CAAC,GAC3C,0BACA,0BACN;AAED,SAAO,qBAAqB;GAC1B,SAAS;GACT,eAAe,mBAAmB,KAAK;GACvC,QAAQ;IACN,2BAA2B,EAAE;IAC7B,yBAAyB;KAGvB,mBAAmB;AACjB,aAAO,SAAS,UAAU,aAAa,MAAM,CAAC;AAC9C,aAAO,OAAO;AACd,YAAM,WAAW,IAAI,KAAA,EAAU;;KAEjC,SAAS,OACD;AAEJ,OADe,MAAM,WAAW,KAAK,IAAI,EAAE,EACpC,SAAS,QAAQ;AAGtB,WAAI,OAAO,IAAI,IAAI,CAAE;OACrB,MAAM,QAAQ,iBAAiB;AAC7B,eAAO,OAAO,IAAI;AAClB,eAAO,MAAM,aAAa,YAAY,SAAS,QAAQ,MAAM,MAAM,IAAI,CAAC;UACvE,WAAW;AACd,cAAO,IAAI,KAAK,MAAM;QACtB;OAEL;KACF;IACF;GACF,CAAC;;CAEL,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { computed, peek } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { effect } from "../../core/signals/effect.js";
|
|
4
|
+
import { isStandardPreload } from "../../media/utils/preload.js";
|
|
5
|
+
//#region src/playback/behaviors/sync-preload.ts
|
|
6
|
+
/**
|
|
7
|
+
* **Bidirectional sync between `state.preload` and `mediaElement.preload`.**
|
|
8
|
+
*
|
|
9
|
+
* Two effects:
|
|
10
|
+
* - **Read (DOM → state)** — on `context.mediaElement` swap or
|
|
11
|
+
* `state.presentation.url` change, copies `mediaElement.preload` into
|
|
12
|
+
* `state.preload` if it's a W3C value and `state.preload` isn't holding
|
|
13
|
+
* an extended (non-W3C) value. When the DOM has no W3C opinion and
|
|
14
|
+
* `state.preload` is undefined, backfills from `config.defaultPreload`
|
|
15
|
+
* (default-default `'metadata'`) so `state.preload` is never undefined
|
|
16
|
+
* in steady state.
|
|
17
|
+
* - **Write (state → DOM)** — on `state.preload` change or
|
|
18
|
+
* `context.mediaElement` swap, writes `state.preload` back to
|
|
19
|
+
* `mediaElement.preload` if the value is W3C.
|
|
20
|
+
*
|
|
21
|
+
* Extended values (e.g. `'canplay'`) written externally to `state.preload`
|
|
22
|
+
* are sticky: read won't overwrite them, write won't push them to the DOM.
|
|
23
|
+
* All writes are deduped to break echo loops and avoid spurious re-triggers
|
|
24
|
+
* downstream (notably `resolvePresentation`, which reads `state.preload`).
|
|
25
|
+
*/
|
|
26
|
+
function syncPreloadSetup({ state, context, config }) {
|
|
27
|
+
const defaultPreload = config?.defaultPreload ?? "metadata";
|
|
28
|
+
const presentationUrl = computed(() => state.presentation.get()?.url);
|
|
29
|
+
const cleanupRead = effect(() => {
|
|
30
|
+
presentationUrl.get();
|
|
31
|
+
const mediaElement = context.mediaElement.get();
|
|
32
|
+
const current = peek(state.preload);
|
|
33
|
+
if (current !== void 0 && !isStandardPreload(current)) return;
|
|
34
|
+
const target = mediaElement && isStandardPreload(mediaElement.preload) ? mediaElement.preload : current === void 0 ? defaultPreload : void 0;
|
|
35
|
+
if (target === void 0 || target === current) return;
|
|
36
|
+
state.preload.set(target);
|
|
37
|
+
});
|
|
38
|
+
const cleanupWrite = effect(() => {
|
|
39
|
+
const next = state.preload.get();
|
|
40
|
+
const mediaElement = context.mediaElement.get();
|
|
41
|
+
if (!mediaElement) return;
|
|
42
|
+
if (!isStandardPreload(next)) return;
|
|
43
|
+
if (mediaElement.preload === next) return;
|
|
44
|
+
mediaElement.preload = next;
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
cleanupRead();
|
|
48
|
+
cleanupWrite();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const syncPreload = defineBehavior({
|
|
52
|
+
stateKeys: ["preload", "presentation"],
|
|
53
|
+
contextKeys: ["mediaElement"],
|
|
54
|
+
setup: syncPreloadSetup
|
|
55
|
+
});
|
|
56
|
+
//#endregion
|
|
57
|
+
export { syncPreload };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=sync-preload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-preload.js","names":[],"sources":["../../../../src/playback/behaviors/sync-preload.ts"],"sourcesContent":["/**\n * **Bidirectional sync between `state.preload` and `mediaElement.preload`.**\n *\n * Two effects:\n * - **Read (DOM → state)** — on `context.mediaElement` swap or\n * `state.presentation.url` change, copies `mediaElement.preload` into\n * `state.preload` if it's a W3C value and `state.preload` isn't holding\n * an extended (non-W3C) value. When the DOM has no W3C opinion and\n * `state.preload` is undefined, backfills from `config.defaultPreload`\n * (default-default `'metadata'`) so `state.preload` is never undefined\n * in steady state.\n * - **Write (state → DOM)** — on `state.preload` change or\n * `context.mediaElement` swap, writes `state.preload` back to\n * `mediaElement.preload` if the value is W3C.\n *\n * Extended values (e.g. `'canplay'`) written externally to `state.preload`\n * are sticky: read won't overwrite them, write won't push them to the DOM.\n * All writes are deduped to break echo loops and avoid spurious re-triggers\n * downstream (notably `resolvePresentation`, which reads `state.preload`).\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport type { MediaElementLike } from '../../media/types';\nimport { DEFAULT_PRELOAD, isStandardPreload, type StandardPreload } from '../../media/utils/preload';\nimport type { PresentationState } from './resolve-presentation';\n\nexport interface SyncPreloadConfig {\n /**\n * Backfill applied to `state.preload` when neither the host element nor\n * external code has supplied a W3C value. Defaults to `'metadata'`.\n */\n defaultPreload?: StandardPreload;\n}\n\ntype State = Pick<PresentationState, 'preload' | 'presentation'>;\n\nfunction syncPreloadSetup({\n state,\n context,\n config,\n}: {\n state: {\n preload: Signal<State['preload']>;\n presentation: ReadonlySignal<State['presentation']>;\n };\n context: { mediaElement: ReadonlySignal<MediaElementLike | undefined> };\n config?: SyncPreloadConfig;\n}): () => void {\n const defaultPreload: StandardPreload = config?.defaultPreload ?? DEFAULT_PRELOAD;\n const presentationUrl = computed(() => state.presentation.get()?.url);\n\n // Read must be registered before write: effects re-run in registration\n // order (see core/signals/effect.ts → runPending). On a shared-signal\n // change (e.g. `context.mediaElement` swap), read fires first and gets to\n // claim `state.preload`, giving \"most-recent-wins on attach\" — a freshly\n // mounted <video> with a W3C `preload` attribute overrides whatever\n // `state.preload` previously held. Swap the order or split these into\n // separate behaviors and the \"most-recent-wins on attach\" test pins the\n // regression.\n const cleanupRead = effect(() => {\n presentationUrl.get();\n const mediaElement = context.mediaElement.get();\n // peek (not get): external writes to state.preload shouldn't re-trigger\n // this effect, only mediaElement / presentation-url changes should.\n const current = peek(state.preload);\n // Extended sticky: leave non-W3C values (e.g. 'canplay') alone.\n if (current !== undefined && !isStandardPreload(current)) return;\n\n const target: StandardPreload | undefined =\n mediaElement && isStandardPreload(mediaElement.preload)\n ? mediaElement.preload\n : current === undefined\n ? defaultPreload\n : undefined;\n if (target === undefined || target === current) return;\n state.preload.set(target);\n });\n\n const cleanupWrite = effect(() => {\n const next = state.preload.get();\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) return;\n if (!isStandardPreload(next)) return;\n if (mediaElement.preload === next) return;\n mediaElement.preload = next;\n });\n\n return () => {\n cleanupRead();\n cleanupWrite();\n };\n}\n\nexport const syncPreload = defineBehavior({\n stateKeys: ['preload', 'presentation'],\n contextKeys: ['mediaElement'],\n setup: syncPreloadSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,iBAAiB,EACxB,OACA,SACA,UAQa;CACb,MAAM,iBAAkC,QAAQ,kBAAA;CAChD,MAAM,kBAAkB,eAAe,MAAM,aAAa,KAAK,EAAE,IAAI;CAUrE,MAAM,cAAc,aAAa;AAC/B,kBAAgB,KAAK;EACrB,MAAM,eAAe,QAAQ,aAAa,KAAK;EAG/C,MAAM,UAAU,KAAK,MAAM,QAAQ;AAEnC,MAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,QAAQ,CAAE;EAE1D,MAAM,SACJ,gBAAgB,kBAAkB,aAAa,QAAQ,GACnD,aAAa,UACb,YAAY,KAAA,IACV,iBACA,KAAA;AACR,MAAI,WAAW,KAAA,KAAa,WAAW,QAAS;AAChD,QAAM,QAAQ,IAAI,OAAO;GACzB;CAEF,MAAM,eAAe,aAAa;EAChC,MAAM,OAAO,MAAM,QAAQ,KAAK;EAChC,MAAM,eAAe,QAAQ,aAAa,KAAK;AAC/C,MAAI,CAAC,aAAc;AACnB,MAAI,CAAC,kBAAkB,KAAK,CAAE;AAC9B,MAAI,aAAa,YAAY,KAAM;AACnC,eAAa,UAAU;GACvB;AAEF,cAAa;AACX,eAAa;AACb,gBAAc;;;AAIlB,MAAa,cAAc,eAAe;CACxC,WAAW,CAAC,WAAW,eAAe;CACtC,aAAa,CAAC,eAAe;CAC7B,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { computed, peek } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedPresentation } from "../../media/types/index.js";
|
|
5
|
+
import { getTracksByType } from "../../media/utils/tracks.js";
|
|
6
|
+
import { getCdnId } from "../../media/utils/cdn.js";
|
|
7
|
+
import { DEFAULT_BANDWIDTH_CONFIG, getBandwidthEstimate } from "../../network/bandwidth-estimator.js";
|
|
8
|
+
import { DEFAULT_QUALITY_CONFIG, resolutionArea } from "../../media/abr/quality-selection.js";
|
|
9
|
+
import { matchesPartialTrack, pickTextTrackFromTracks } from "../../media/primitives/select-tracks.js";
|
|
10
|
+
/**
|
|
11
|
+
* Apply rules to a candidate list in order; the pick is the first survivor.
|
|
12
|
+
* Two responsibilities the rules don't carry: a rule that returns nothing is
|
|
13
|
+
* skipped (fall-through — a preference never empties the set), and once one
|
|
14
|
+
* survivor remains the chain stops (early-bail — later rules, including the
|
|
15
|
+
* bandwidth ranker, never run, so the effect doesn't subscribe to their
|
|
16
|
+
* signals while the choice is fixed).
|
|
17
|
+
*
|
|
18
|
+
* @param rules - Rules to apply, most authoritative first
|
|
19
|
+
* @param tracks - Candidate tracks
|
|
20
|
+
* @param deps - The behavior's `{ state, context, config }`, passed through to each rule
|
|
21
|
+
* @returns The surviving candidates, pick first
|
|
22
|
+
*/
|
|
23
|
+
function applyRules(rules, tracks, deps) {
|
|
24
|
+
let current = tracks;
|
|
25
|
+
for (const rule of rules) {
|
|
26
|
+
const remaining = rule(current, deps);
|
|
27
|
+
if (remaining.length === 0) continue;
|
|
28
|
+
current = remaining;
|
|
29
|
+
if (current.length === 1) break;
|
|
30
|
+
}
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Apply hard constraints to a candidate list — the pre-pass that runs before the
|
|
35
|
+
* rule chain. A constraint shares a rule's signature but its exclusion is
|
|
36
|
+
* *hard*: it removes the unplayable (a codec the environment can't decode, a CDN
|
|
37
|
+
* in failover cooldown) and a removed track is never attempted. Unlike
|
|
38
|
+
* `applyRules`, this never skips an empty result and never early-bails — every
|
|
39
|
+
* constraint always applies, and an empty survivor set is a real outcome
|
|
40
|
+
* ("nothing playable here"), not a fall-through. Because each constraint only
|
|
41
|
+
* removes, the order they run in can't change the result.
|
|
42
|
+
*
|
|
43
|
+
* @param constraints - Constraints to apply (pooled, order-independent)
|
|
44
|
+
* @param tracks - Candidate tracks
|
|
45
|
+
* @param deps - The behavior's `{ state, context, config }`, passed to each constraint
|
|
46
|
+
* @returns The playable survivors (possibly empty)
|
|
47
|
+
*/
|
|
48
|
+
function applyConstraints(constraints, tracks, deps) {
|
|
49
|
+
let current = tracks;
|
|
50
|
+
for (const constraint of constraints) current = constraint(current, deps);
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* User intent — a soft filter. Narrows to tracks matching the partial-track
|
|
55
|
+
* selection in `user*TrackSelection`; an empty match falls through (the
|
|
56
|
+
* composer skips it) to the unfiltered set — e.g. a stale id from a previous
|
|
57
|
+
* source.
|
|
58
|
+
*/
|
|
59
|
+
function filterByUserSelection(tracks, { state, config }) {
|
|
60
|
+
const key = config.userSelectionKey;
|
|
61
|
+
if (!key) return tracks;
|
|
62
|
+
const filter = state[key]?.get();
|
|
63
|
+
return filter ? tracks.filter((track) => matchesPartialTrack(track, filter)) : tracks;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Failed-CDN constraint — a *hard* filter (constraints pre-pass), shared by
|
|
67
|
+
* video and audio. Removes tracks served from a CDN currently in failover
|
|
68
|
+
* cooldown (`failedCdns`, written by the failover monitor). Removed tracks are never
|
|
69
|
+
* attempted; the scope then narrows to the next surviving CDN in `cdnPriority`,
|
|
70
|
+
* and snaps back to the primary once it leaves cooldown.
|
|
71
|
+
*
|
|
72
|
+
* Passes everything through when there's no `failedCdns` signal/value. When it
|
|
73
|
+
* prunes *every* track (all CDNs cooled down), the empty result is preserved
|
|
74
|
+
* (per `applyConstraints`) — "nothing playable," which clears the selection (no
|
|
75
|
+
* pick); a later CDN recovery refills the candidate set and re-picks.
|
|
76
|
+
*/
|
|
77
|
+
function excludeFailedCdns(tracks, { state, config }) {
|
|
78
|
+
const failed = state.failedCdns?.get();
|
|
79
|
+
if (!failed?.length) return tracks;
|
|
80
|
+
const getCdnId$1 = config.getCdnId ?? getCdnId;
|
|
81
|
+
const failedSet = new Set(failed);
|
|
82
|
+
return tracks.filter((track) => !failedSet.has(getCdnId$1(track.url)));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Capability constraint — a *hard* filter (constraints pre-pass), shared by
|
|
86
|
+
* video and audio. Removes renditions this environment can't decode, probed via
|
|
87
|
+
* the injected `canPlayTrack` (codec → `MediaSource.isTypeSupported`). Moving
|
|
88
|
+
* the check here — before selection — means an unplayable variant (e.g. HEVC on
|
|
89
|
+
* a browser without HEVC) is pruned upstream and never picked, instead of
|
|
90
|
+
* surviving into the pipeline to fail late at `createSourceBuffer`. That late
|
|
91
|
+
* throw stays as a defensive structural guarantee; with this constraint it
|
|
92
|
+
* should rarely fire.
|
|
93
|
+
*
|
|
94
|
+
* Passes everything through when there's no `canPlayTrack` probe (a composition
|
|
95
|
+
* that didn't wire it, or DOM-free tests). When it prunes *every* track (no
|
|
96
|
+
* decodable rendition), the empty result is preserved (per `applyConstraints`)
|
|
97
|
+
* — "nothing playable," so the behavior clears the selection (no pick) and the
|
|
98
|
+
* late `createSourceBuffer` check stays as the backstop.
|
|
99
|
+
*/
|
|
100
|
+
function excludeUnplayableTracks(tracks, { config }) {
|
|
101
|
+
const canPlay = config.canPlayTrack;
|
|
102
|
+
if (!canPlay) return tracks;
|
|
103
|
+
return tracks.filter((track) => canPlay(track));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Active-CDN scope — a soft filter, shared by video and audio. Narrows to the
|
|
107
|
+
* highest-priority CDN in `cdnPriority` (owned by `deriveCdnPriority`) that
|
|
108
|
+
* still has tracks, so every track type stays on one CDN. A redundant-streams
|
|
109
|
+
* source lists the same renditions on multiple hosts; this keeps the pick on one
|
|
110
|
+
* host rather than letting the ranker drift across them.
|
|
111
|
+
*
|
|
112
|
+
* "Active" is derived, not stored: constraints run before the rule chain, so a
|
|
113
|
+
* failed CDN's tracks are already pruned by the time this runs — "first CDN with
|
|
114
|
+
* survivors" *is* the active CDN, and it falls through to the next on failover
|
|
115
|
+
* (and snaps back to the primary when it recovers). Content steering reorders
|
|
116
|
+
* `cdnPriority`; this rule just honors the order.
|
|
117
|
+
*
|
|
118
|
+
* Soft-filter semantics: passes through when there's no `cdnPriority` signal/value
|
|
119
|
+
* (no preference) or when nothing matches (`applyRules` skips an empty result).
|
|
120
|
+
* Non-redundant sources have one CDN, so the narrow is a no-op.
|
|
121
|
+
*
|
|
122
|
+
* The CDN-id derivation defaults to origin-based `getCdnId`, overridable via the
|
|
123
|
+
* `getCdnId` config — it must match the one `deriveCdnPriority` used to build
|
|
124
|
+
* `cdnPriority`, or no track's CDN would ever equal an entry.
|
|
125
|
+
*/
|
|
126
|
+
function preferActiveCdn(tracks, { state, config }) {
|
|
127
|
+
const cdnPriority = state.cdnPriority?.get();
|
|
128
|
+
if (!cdnPriority?.length) return tracks;
|
|
129
|
+
const getCdnId$2 = config.getCdnId ?? getCdnId;
|
|
130
|
+
for (const cdn of cdnPriority) {
|
|
131
|
+
const tracksUsingCdn = tracks.filter((track) => getCdnId$2(track.url) === cdn);
|
|
132
|
+
if (tracksUsingCdn.length) return tracksUsingCdn;
|
|
133
|
+
}
|
|
134
|
+
return tracks;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Bandwidth ranking — the terminal sort, shared by video and audio. Orders by
|
|
138
|
+
* the throughput estimate: tracks within the bandwidth threshold first
|
|
139
|
+
* (fitting), highest bitrate first; then over-threshold tracks, least-over
|
|
140
|
+
* first. The head is the best-quality track that fits, falling back to the
|
|
141
|
+
* smallest over-throughput track when nothing fits.
|
|
142
|
+
*
|
|
143
|
+
* Hysteresis without temporal state: the current track's effective bitrate is
|
|
144
|
+
* boosted by `upgradeMargin` in the fitting sort, so a higher track only
|
|
145
|
+
* outranks it once it clears `current.bitrate * upgradeMargin` (no flapping on
|
|
146
|
+
* marginal bandwidth gains). Downgrades fall out for free — a current track
|
|
147
|
+
* over the threshold isn't in the fitting set to be boosted, so the best fit (a
|
|
148
|
+
* downgrade) wins immediately. Equal-bitrate tracks break by resolution (higher
|
|
149
|
+
* `width × height` first), so an equal-bitrate ladder never picks a lower-
|
|
150
|
+
* quality rendition by manifest order; audio tracks carry no dimensions, so
|
|
151
|
+
* they area-compare equal and a stable sort keeps their candidate order (e.g.
|
|
152
|
+
* same-bitrate language variants). Early-bail skips this rule when a prior one
|
|
153
|
+
* narrowed to a single track, so the estimate is neither read nor subscribed
|
|
154
|
+
* while that holds.
|
|
155
|
+
*/
|
|
156
|
+
function rankByBandwidth(tracks, { state, config }) {
|
|
157
|
+
const safetyMargin = config.quality?.safetyMargin ?? DEFAULT_QUALITY_CONFIG.safetyMargin;
|
|
158
|
+
const upgradeMargin = config.quality?.upgradeMargin ?? DEFAULT_QUALITY_CONFIG.upgradeMargin;
|
|
159
|
+
const initialBandwidth = config.initialBandwidth ?? 5e6;
|
|
160
|
+
const bandwidthConfig = {
|
|
161
|
+
...DEFAULT_BANDWIDTH_CONFIG,
|
|
162
|
+
...config.bandwidth
|
|
163
|
+
};
|
|
164
|
+
if (!state.bandwidthState) console.debug("[track-switching] rankByBandwidth: no bandwidthState signal in composition; ranking on initialBandwidth");
|
|
165
|
+
const threshold = getBandwidthEstimate(state.bandwidthState?.get(), initialBandwidth, bandwidthConfig) * safetyMargin;
|
|
166
|
+
const currentId = state[config.selectionKey].get();
|
|
167
|
+
const bitrate = (track) => track.bandwidth ?? 0;
|
|
168
|
+
const rank = (track) => track.id === currentId ? bitrate(track) * upgradeMargin : bitrate(track);
|
|
169
|
+
const fitting = tracks.filter((track) => bitrate(track) <= threshold).sort((a, b) => rank(b) - rank(a) || resolutionArea(b) - resolutionArea(a));
|
|
170
|
+
const over = tracks.filter((track) => bitrate(track) > threshold).sort((a, b) => bitrate(a) - bitrate(b) || resolutionArea(b) - resolutionArea(a));
|
|
171
|
+
return [...fitting, ...over];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Default final pick: the chain head. `applyRules` never narrows to nothing and
|
|
175
|
+
* early-bails to a single survivor, so video and audio always converge to a
|
|
176
|
+
* track and the head is the pick.
|
|
177
|
+
*/
|
|
178
|
+
function selectChainHead(candidates) {
|
|
179
|
+
return candidates[0].id;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Terminal pick for text — the `resolveSelection` the text variant supplies.
|
|
183
|
+
* Resolves the standing `userTextTrackSelection` intent against the chain's
|
|
184
|
+
* survivors (already CDN-failover-pruned and active-CDN-scoped):
|
|
185
|
+
*
|
|
186
|
+
* - `'off'` → no selection (clear the slot). Sticky through re-evaluation, so a
|
|
187
|
+
* live refresh or failover re-run can't re-assert a default.
|
|
188
|
+
* - explicit `Partial<TextTrack>` → narrow to the match (language-based). A
|
|
189
|
+
* stale pick whose match is gone (e.g. the language dropped on a source
|
|
190
|
+
* change) falls through to the default policy.
|
|
191
|
+
* - auto (`undefined`) → the opt-in default policy (`preferredSubtitleLanguage`
|
|
192
|
+
* → `DEFAULT=YES + AUTOSELECT=YES` → none), shared with `pickTextTrack`.
|
|
193
|
+
*
|
|
194
|
+
* Returning `undefined` is a real outcome (captions are opt-in), which is why the
|
|
195
|
+
* text variant relies on `setupTrackSwitching`'s no-selection seam.
|
|
196
|
+
*/
|
|
197
|
+
function pickResolvedTextTrack(candidates, { state, config }) {
|
|
198
|
+
const intent = state.userTextTrackSelection?.get();
|
|
199
|
+
if (intent === "off") return void 0;
|
|
200
|
+
if (intent) {
|
|
201
|
+
const matched = candidates.filter((track) => matchesPartialTrack(track, intent));
|
|
202
|
+
if (matched.length) return matched[0].id;
|
|
203
|
+
}
|
|
204
|
+
return pickTextTrackFromTracks(candidates, config);
|
|
205
|
+
}
|
|
206
|
+
function setupTrackSwitching(deps) {
|
|
207
|
+
const { state, config } = deps;
|
|
208
|
+
const { selectionKey, getTracks, rules, resolveSelection = selectChainHead } = config;
|
|
209
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
210
|
+
const candidateSet = computed(() => {
|
|
211
|
+
const presentation = state.presentation.get();
|
|
212
|
+
if (!isResolvedPresentation(presentation)) return [];
|
|
213
|
+
return applyConstraints(config.constraints ?? [], getTracks(presentation), deps);
|
|
214
|
+
}, { equals: (a, b) => a.length === b.length && a.every((track) => b.some((other) => other.id === track.id)) });
|
|
215
|
+
return createMachineReactor({
|
|
216
|
+
initial: "presentation-unresolved",
|
|
217
|
+
monitor: () => derivedStateSignal.get(),
|
|
218
|
+
states: {
|
|
219
|
+
"presentation-unresolved": {},
|
|
220
|
+
"presentation-resolved": {
|
|
221
|
+
entry: () => () => state[selectionKey].set(void 0),
|
|
222
|
+
effects: [() => {
|
|
223
|
+
const tracks = candidateSet.get();
|
|
224
|
+
if (!tracks.length) {
|
|
225
|
+
const presentation = peek(state.presentation);
|
|
226
|
+
if (isResolvedPresentation(presentation) && getTracks(presentation).length > 0) {
|
|
227
|
+
console.error(`[track-switching] every ${selectionKey} candidate was filtered out by constraints; clearing selection`);
|
|
228
|
+
state[selectionKey].set(void 0);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const candidates = applyRules(rules, tracks, deps);
|
|
233
|
+
if (!candidates.length) {
|
|
234
|
+
console.error("[track-switching] applyRules returned no candidates");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
state[selectionKey].set(resolveSelection(candidates, deps));
|
|
238
|
+
}]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Manage `selectedVideoTrackId`: pick a default on src load, dynamically
|
|
245
|
+
* adjust based on bandwidth, clear on src unload. Honors
|
|
246
|
+
* `userVideoTrackSelection` as a partial-track constraint on candidates;
|
|
247
|
+
* short-circuits ABR when the constraint narrows to a single track.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* const reactor = switchVideoTrack.setup({ state });
|
|
251
|
+
*/
|
|
252
|
+
const switchVideoTrack = defineBehavior({
|
|
253
|
+
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
254
|
+
contextKeys: [],
|
|
255
|
+
setup: ({ state, config, ...otherProps }) => setupTrackSwitching({
|
|
256
|
+
...otherProps,
|
|
257
|
+
state,
|
|
258
|
+
config: {
|
|
259
|
+
...config,
|
|
260
|
+
selectionKey: "selectedVideoTrackId",
|
|
261
|
+
userSelectionKey: "userVideoTrackSelection",
|
|
262
|
+
getTracks: (presentation) => getTracksByType(presentation, "video"),
|
|
263
|
+
constraints: [excludeFailedCdns, excludeUnplayableTracks],
|
|
264
|
+
rules: [
|
|
265
|
+
filterByUserSelection,
|
|
266
|
+
preferActiveCdn,
|
|
267
|
+
rankByBandwidth
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
});
|
|
272
|
+
/**
|
|
273
|
+
* Manage `selectedAudioTrackId`: pick a default on src load, narrow by
|
|
274
|
+
* `userAudioTrackSelection` filter, re-pick on filter change, clear on
|
|
275
|
+
* src unload.
|
|
276
|
+
*
|
|
277
|
+
* Mid-stream flush on language switch is handled by the segment-loader's
|
|
278
|
+
* `planTasks` (see `playback/actors/dom/segment-loader.ts`) — not this
|
|
279
|
+
* behavior. Same split as the video pipeline: slot owner writes; loader
|
|
280
|
+
* orchestrates segment + flush plans.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* const reactor = switchAudioTrack.setup({ state });
|
|
284
|
+
*/
|
|
285
|
+
const switchAudioTrack = defineBehavior({
|
|
286
|
+
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
287
|
+
contextKeys: [],
|
|
288
|
+
setup: ({ state, config, ...otherProps }) => setupTrackSwitching({
|
|
289
|
+
...otherProps,
|
|
290
|
+
state,
|
|
291
|
+
config: {
|
|
292
|
+
...config,
|
|
293
|
+
selectionKey: "selectedAudioTrackId",
|
|
294
|
+
userSelectionKey: "userAudioTrackSelection",
|
|
295
|
+
getTracks: (presentation) => getTracksByType(presentation, "audio"),
|
|
296
|
+
constraints: [excludeFailedCdns, excludeUnplayableTracks],
|
|
297
|
+
rules: [
|
|
298
|
+
filterByUserSelection,
|
|
299
|
+
preferActiveCdn,
|
|
300
|
+
rankByBandwidth
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
});
|
|
305
|
+
/**
|
|
306
|
+
* Manage `selectedTextTrackId` as the single-writer **output** of standing user
|
|
307
|
+
* intent (`userTextTrackSelection`) resolved against the playable, CDN-scoped
|
|
308
|
+
* text renditions: clear on src unload; re-resolve when a CDN fails or recovers.
|
|
309
|
+
*
|
|
310
|
+
* Unlike video/audio, the selection is *optional* — captions are opt-in and the
|
|
311
|
+
* user can turn them off — so the chain skips the bandwidth ranker and the shared
|
|
312
|
+
* user-selection filter, and supplies a text-specific terminal
|
|
313
|
+
* (`pickResolvedTextTrack`) that may resolve to no-selection via
|
|
314
|
+
* `setupTrackSwitching`'s `resolveSelection` seam. Constraints are failed-CDN only
|
|
315
|
+
* (`excludeUnplayableTracks`/`canPlayTrack` is MSE-based — the wrong probe for
|
|
316
|
+
* text, whose playability is SPF-parser support); the active-CDN scope co-locates
|
|
317
|
+
* captions with the surviving CDN on failover.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* const reactor = switchTextTrack.setup({ state, config: { preferredSubtitleLanguage: 'en' } });
|
|
321
|
+
*/
|
|
322
|
+
const switchTextTrack = defineBehavior({
|
|
323
|
+
stateKeys: ["presentation", "selectedTextTrackId"],
|
|
324
|
+
contextKeys: [],
|
|
325
|
+
setup: ({ state, config, ...otherProps }) => setupTrackSwitching({
|
|
326
|
+
...otherProps,
|
|
327
|
+
state,
|
|
328
|
+
config: {
|
|
329
|
+
...config,
|
|
330
|
+
selectionKey: "selectedTextTrackId",
|
|
331
|
+
getTracks: (presentation) => getTracksByType(presentation, "text"),
|
|
332
|
+
constraints: [excludeFailedCdns],
|
|
333
|
+
rules: [preferActiveCdn],
|
|
334
|
+
resolveSelection: pickResolvedTextTrack
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
});
|
|
338
|
+
//#endregion
|
|
339
|
+
export { switchAudioTrack, switchTextTrack, switchVideoTrack };
|
|
340
|
+
|
|
341
|
+
//# sourceMappingURL=track-switching.js.map
|