@videojs/spf 10.0.0-beta.25 → 10.0.0-beta.26
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/README.md +5 -0
- package/dist/default/core/actors/create-machine-actor.js.map +1 -1
- package/dist/default/core/actors/create-transition-actor.js.map +1 -1
- package/dist/default/core/composition/create-composition.js +1 -1
- package/dist/default/core/composition/create-composition.js.map +1 -1
- package/dist/default/core/composition/share-signals.js.map +1 -1
- package/dist/default/core/machine.js.map +1 -1
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
- package/dist/default/core/signals/effect.js.map +1 -1
- package/dist/default/core/signals/primitives.js.map +1 -1
- package/dist/default/core/tasks/task.js.map +1 -1
- package/dist/default/hls.js +2 -1
- package/dist/default/media/abr/quality-selection.js.map +1 -1
- package/dist/default/media/buffer/back-buffer.js.map +1 -1
- package/dist/default/media/buffer/forward-buffer.js +37 -3
- package/dist/default/media/buffer/forward-buffer.js.map +1 -1
- package/dist/default/media/dom/capabilities.js.map +1 -1
- package/dist/default/media/dom/mse/append-segment.js.map +1 -1
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
- package/dist/default/media/dom/mse/duration.js +27 -10
- package/dist/default/media/dom/mse/duration.js.map +1 -1
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
- package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
- package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
- package/dist/default/media/hls/parse-attributes.js +1 -1
- package/dist/default/media/hls/parse-attributes.js.map +1 -1
- package/dist/default/media/hls/parse-media-playlist.js +8 -3
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
- package/dist/default/media/hls/parse-multivariant.js.map +1 -1
- package/dist/default/media/hls/resolve-url.js.map +1 -1
- package/dist/default/media/media-tracks/media-tracks.js +101 -0
- package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/default/media/mp4/box.js +58 -0
- package/dist/default/media/mp4/box.js.map +1 -0
- package/dist/default/media/mp4/timestamp-origin.js +107 -0
- package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -1
- package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
- package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/default/media/types/index.js.map +1 -1
- package/dist/default/media/utils/cdn.js.map +1 -1
- package/dist/default/media/utils/preload.js.map +1 -1
- package/dist/default/media/utils/track-selection.js +1 -1
- package/dist/default/media/utils/track-selection.js.map +1 -1
- package/dist/default/media/utils/tracks.js +3 -3
- package/dist/default/media/utils/tracks.js.map +1 -1
- package/dist/default/media-tracks.js +2 -0
- package/dist/default/network/bandwidth-estimator.js.map +1 -1
- package/dist/default/network/chunked-stream-iterable.js.map +1 -1
- package/dist/default/network/ewma.js +1 -1
- package/dist/default/network/ewma.js.map +1 -1
- package/dist/default/network/fetch.js.map +1 -1
- package/dist/default/playback/actors/dom/segment-loader.js +61 -95
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
- package/dist/default/playback/actors/dom/source-buffer.js +1 -0
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
- package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
- package/dist/default/playback/behaviors/dom/airplay.js +211 -0
- package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js +12 -1
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
- package/dist/default/playback/behaviors/dom/load-segments.js +5 -1
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
- package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
- package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +17 -4
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +2 -1
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
- package/dist/default/playback/behaviors/establish-start-media-time.js +123 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
- package/dist/default/playback/behaviors/setup-failover-monitor.js +1 -1
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
- package/dist/default/playback/behaviors/track-switching.js +63 -1
- package/dist/default/playback/behaviors/track-switching.js.map +1 -1
- package/dist/default/playback/engines/background-video/adapter.js.map +1 -1
- package/dist/default/playback/engines/background-video/engine.js.map +1 -1
- package/dist/default/playback/engines/hls/adapter-audio-only.js +14 -9
- package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -1
- package/dist/default/playback/engines/hls/adapter.js +13 -11
- package/dist/default/playback/engines/hls/adapter.js.map +1 -1
- package/dist/default/playback/engines/hls/engine-audio-only.js +16 -2
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
- package/dist/default/playback/engines/hls/engine.js +19 -2
- package/dist/default/playback/engines/hls/engine.js.map +1 -1
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
- package/dist/default/playback/primitives/head-peek.js +44 -0
- package/dist/default/playback/primitives/head-peek.js.map +1 -0
- package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
- package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
- package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/track-types.js.map +1 -1
- package/dist/dev/core/actors/actor.d.ts +0 -1
- package/dist/dev/core/actors/actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
- package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
- package/dist/dev/core/composition/create-composition.d.ts +10 -11
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
- package/dist/dev/core/composition/create-composition.js +1 -1
- package/dist/dev/core/composition/create-composition.js.map +1 -1
- package/dist/dev/core/composition/share-signals.d.ts +0 -1
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
- package/dist/dev/core/composition/share-signals.js.map +1 -1
- package/dist/dev/core/machine.d.ts +1 -0
- package/dist/dev/core/machine.d.ts.map +1 -1
- package/dist/dev/core/machine.js.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
- package/dist/dev/core/signals/effect.d.ts.map +1 -1
- package/dist/dev/core/signals/effect.js.map +1 -1
- package/dist/dev/core/signals/primitives.d.ts +1 -2
- package/dist/dev/core/signals/primitives.d.ts.map +1 -1
- package/dist/dev/core/signals/primitives.js.map +1 -1
- package/dist/dev/core/tasks/task.d.ts +2 -2
- package/dist/dev/core/tasks/task.d.ts.map +1 -1
- package/dist/dev/core/tasks/task.js.map +1 -1
- package/dist/dev/hls.d.ts +3 -1
- package/dist/dev/hls.js +2 -1
- package/dist/dev/index.d.ts +0 -1
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
- package/dist/dev/media/abr/quality-selection.js.map +1 -1
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
- package/dist/dev/media/buffer/back-buffer.js.map +1 -1
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
- package/dist/dev/media/buffer/forward-buffer.js +37 -3
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
- package/dist/dev/media/dom/capabilities.js.map +1 -1
- package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
- package/dist/dev/media/dom/mse/duration.js +27 -10
- package/dist/dev/media/dom/mse/duration.js.map +1 -1
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
- package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
- package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
- package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
- package/dist/dev/media/hls/parse-attributes.js +1 -1
- package/dist/dev/media/hls/parse-attributes.js.map +1 -1
- package/dist/dev/media/hls/parse-media-playlist.js +8 -3
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
- package/dist/dev/media/hls/resolve-url.js.map +1 -1
- package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.js +101 -0
- package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/dev/media/mp4/box.js +58 -0
- package/dist/dev/media/mp4/box.js.map +1 -0
- package/dist/dev/media/mp4/timestamp-origin.js +107 -0
- package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.d.ts +0 -1
- package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -1
- package/dist/dev/media/primitives/select-tracks.js.map +1 -1
- package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/dev/media/types/index.d.ts +44 -1
- package/dist/dev/media/types/index.d.ts.map +1 -1
- package/dist/dev/media/types/index.js.map +1 -1
- package/dist/dev/media/utils/cdn.d.ts.map +1 -1
- package/dist/dev/media/utils/cdn.js.map +1 -1
- package/dist/dev/media/utils/preload.js.map +1 -1
- package/dist/dev/media/utils/track-selection.js +1 -1
- package/dist/dev/media/utils/track-selection.js.map +1 -1
- package/dist/dev/media/utils/tracks.js +3 -3
- package/dist/dev/media/utils/tracks.js.map +1 -1
- package/dist/dev/media-tracks.d.ts +3 -0
- package/dist/dev/media-tracks.js +2 -0
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
- package/dist/dev/network/bandwidth-estimator.js.map +1 -1
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
- package/dist/dev/network/ewma.js +1 -1
- package/dist/dev/network/ewma.js.map +1 -1
- package/dist/dev/network/fetch.js.map +1 -1
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
- package/dist/dev/playback/actors/dom/segment-loader.js +61 -95
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +3 -33
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
- package/dist/dev/playback/actors/dom/source-buffer.js +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
- package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
- package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +3 -2
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
- package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +12 -1
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/load-segments.js +5 -1
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +17 -4
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +2 -1
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +31 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js +123 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
- package/dist/dev/playback/behaviors/select-tracks.d.ts +2 -1
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +2 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +1 -1
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
- package/dist/dev/playback/behaviors/track-switching.js +63 -1
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
- package/dist/dev/playback/engines/background-video/adapter.d.ts +0 -1
- package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -1
- package/dist/dev/playback/engines/background-video/adapter.js.map +1 -1
- package/dist/dev/playback/engines/background-video/engine.d.ts +1 -2
- package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -1
- package/dist/dev/playback/engines/background-video/engine.js.map +1 -1
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +4 -3
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/adapter-audio-only.js +14 -9
- package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -1
- package/dist/dev/playback/engines/hls/adapter.d.ts +3 -5
- package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/adapter.js +13 -11
- package/dist/dev/playback/engines/hls/adapter.js.map +1 -1
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +31 -2
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/engine-audio-only.js +16 -2
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
- package/dist/dev/playback/engines/hls/engine.d.ts +49 -3
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/engine.js +19 -2
- package/dist/dev/playback/engines/hls/engine.js.map +1 -1
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
- package/dist/dev/playback/primitives/head-peek.js +44 -0
- package/dist/dev/playback/primitives/head-peek.js.map +1 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
- package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -1
- package/package.json +12 -7
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "../../core/signals/primitives.js";
|
|
2
|
+
import "../../core/composition/create-composition.js";
|
|
3
|
+
import { DeriveStartMediaTime, DeriveStartMediaTimeContext } from "../primitives/derive-start-media-time.js";
|
|
4
|
+
//#region src/playback/behaviors/establish-start-media-time.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The **default** — relocate the whole presentation by one shared origin: the `min`
|
|
7
|
+
* across the *selected* A/V tracks' own origins, denormalized onto every type. This
|
|
8
|
+
* single reduce subsumes the "per-type" and "shared" tiers:
|
|
9
|
+
* - **aligned A/V** — `min` equals each origin (they're equal), so it matches per-type;
|
|
10
|
+
* - **skewed A/V** (e.g. Apple's 44ms audio-lead) — `min` keeps every track's earliest
|
|
11
|
+
* DTS ≥ 0 (relocating by ≤ each own origin never drives one negative) *and* preserves
|
|
12
|
+
* the real skew (per-type would flatten it, desyncing A/V);
|
|
13
|
+
* - **single type / muxed** — `min` of the one origin is that origin.
|
|
14
|
+
*
|
|
15
|
+
* Returns `undefined` for every type until all *selected* types have a complete origin
|
|
16
|
+
* (the shared-`min` barrier). Which types must contribute is read from `ctx` (the
|
|
17
|
+
* selected v/a ids); with no selection context it coordinates across whatever types
|
|
18
|
+
* have data. A shared origin below {@link NEAR_ZERO_ORIGIN_THRESHOLD} is returned as `0`
|
|
19
|
+
* (native — ordinary ~0-PTS VOD isn't relocated).
|
|
20
|
+
*/
|
|
21
|
+
declare const deriveSharedMinStartMediaTime: DeriveStartMediaTime;
|
|
22
|
+
/**
|
|
23
|
+
* Coordination-axis *off* — each type relocates by its own origin, independently. Not
|
|
24
|
+
* the default: it flattens real A/V skew (see {@link deriveSharedMinStartMediaTime}).
|
|
25
|
+
* Kept as an opt-in for compositions that know their A/V is aligned and want to skip
|
|
26
|
+
* the shared-`min` barrier (each type stamps as soon as its own origin is discovered).
|
|
27
|
+
*/
|
|
28
|
+
declare const derivePerTypeStartMediaTime: DeriveStartMediaTime;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { type DeriveStartMediaTime, derivePerTypeStartMediaTime, deriveSharedMinStartMediaTime };
|
|
31
|
+
//# sourceMappingURL=establish-start-media-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"establish-start-media-time.d.ts","names":[],"sources":["../../../../src/playback/behaviors/establish-start-media-time.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAiGa,+BAA+B;;;;;;;cAsB/B,6BAA6B"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
3
|
+
import { isResolvedPresentation } from "../../media/types/index.js";
|
|
4
|
+
import { findTrackById } from "../../media/utils/tracks.js";
|
|
5
|
+
//#region src/playback/behaviors/establish-start-media-time.ts
|
|
6
|
+
/**
|
|
7
|
+
* A single type's own media-timeline origin:
|
|
8
|
+
* `baseMediaDecodeTime/timescale − segmentStartTime` (the `segmentStartTime` term
|
|
9
|
+
* makes it the stream origin even when the first loaded segment isn't the 0th).
|
|
10
|
+
* `undefined` until timescale + baseMediaDecodeTime + segmentStartTime are all present.
|
|
11
|
+
*/
|
|
12
|
+
function ownOrigin(data) {
|
|
13
|
+
const { timescale, baseMediaDecodeTime, segmentStartTime } = data ?? {};
|
|
14
|
+
return timescale != null && baseMediaDecodeTime != null && segmentStartTime != null ? baseMediaDecodeTime / timescale - segmentStartTime : void 0;
|
|
15
|
+
}
|
|
16
|
+
/** Snap a below-threshold (incl. negative) origin to `0` so it isn't relocated. */
|
|
17
|
+
function thresholdOrigin(origin) {
|
|
18
|
+
return origin < 1 ? 0 : origin;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The **default** — relocate the whole presentation by one shared origin: the `min`
|
|
22
|
+
* across the *selected* A/V tracks' own origins, denormalized onto every type. This
|
|
23
|
+
* single reduce subsumes the "per-type" and "shared" tiers:
|
|
24
|
+
* - **aligned A/V** — `min` equals each origin (they're equal), so it matches per-type;
|
|
25
|
+
* - **skewed A/V** (e.g. Apple's 44ms audio-lead) — `min` keeps every track's earliest
|
|
26
|
+
* DTS ≥ 0 (relocating by ≤ each own origin never drives one negative) *and* preserves
|
|
27
|
+
* the real skew (per-type would flatten it, desyncing A/V);
|
|
28
|
+
* - **single type / muxed** — `min` of the one origin is that origin.
|
|
29
|
+
*
|
|
30
|
+
* Returns `undefined` for every type until all *selected* types have a complete origin
|
|
31
|
+
* (the shared-`min` barrier). Which types must contribute is read from `ctx` (the
|
|
32
|
+
* selected v/a ids); with no selection context it coordinates across whatever types
|
|
33
|
+
* have data. A shared origin below {@link NEAR_ZERO_ORIGIN_THRESHOLD} is returned as `0`
|
|
34
|
+
* (native — ordinary ~0-PTS VOD isn't relocated).
|
|
35
|
+
*/
|
|
36
|
+
const deriveSharedMinStartMediaTime = (containerData, ctx) => {
|
|
37
|
+
const contributingTypes = [];
|
|
38
|
+
if (ctx.selectedVideoTrackId != null) contributingTypes.push("video");
|
|
39
|
+
if (ctx.selectedAudioTrackId != null) contributingTypes.push("audio");
|
|
40
|
+
const origins = (contributingTypes.length > 0 ? contributingTypes : Object.keys(containerData)).map((type) => ownOrigin(containerData[type]));
|
|
41
|
+
if (origins.length === 0 || origins.some((origin) => origin === void 0)) return {};
|
|
42
|
+
const shared = thresholdOrigin(Math.min(...origins));
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const type of Object.keys(containerData)) out[type] = shared;
|
|
45
|
+
return out;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Coordination-axis *off* — each type relocates by its own origin, independently. Not
|
|
49
|
+
* the default: it flattens real A/V skew (see {@link deriveSharedMinStartMediaTime}).
|
|
50
|
+
* Kept as an opt-in for compositions that know their A/V is aligned and want to skip
|
|
51
|
+
* the shared-`min` barrier (each type stamps as soon as its own origin is discovered).
|
|
52
|
+
*/
|
|
53
|
+
const derivePerTypeStartMediaTime = (containerData) => {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [type, data] of Object.entries(containerData)) {
|
|
56
|
+
const origin = ownOrigin(data);
|
|
57
|
+
out[type] = origin === void 0 ? void 0 : thresholdOrigin(origin);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
61
|
+
/** Stamp the derived per-track `startMediaTime` onto the model (idempotent — same reference when nothing moved). */
|
|
62
|
+
function stampTracks(presentation, startMediaTimes) {
|
|
63
|
+
let changed = false;
|
|
64
|
+
const selectionSets = presentation.selectionSets.map((selectionSet) => ({
|
|
65
|
+
...selectionSet,
|
|
66
|
+
switchingSets: selectionSet.switchingSets.map((switchingSet) => ({
|
|
67
|
+
...switchingSet,
|
|
68
|
+
tracks: switchingSet.tracks.map((track) => {
|
|
69
|
+
const startMediaTime = startMediaTimes[track.type];
|
|
70
|
+
if (startMediaTime === void 0 || track.startMediaTime === startMediaTime) return track;
|
|
71
|
+
changed = true;
|
|
72
|
+
return {
|
|
73
|
+
...track,
|
|
74
|
+
startMediaTime
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
}))
|
|
78
|
+
}));
|
|
79
|
+
return changed ? {
|
|
80
|
+
...presentation,
|
|
81
|
+
selectionSets
|
|
82
|
+
} : presentation;
|
|
83
|
+
}
|
|
84
|
+
function establishStartMediaTimeSetup({ state, config = {} }) {
|
|
85
|
+
const derive = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;
|
|
86
|
+
const selectionContext = () => ({
|
|
87
|
+
selectedVideoTrackId: state.selectedVideoTrackId?.get(),
|
|
88
|
+
selectedAudioTrackId: state.selectedAudioTrackId?.get()
|
|
89
|
+
});
|
|
90
|
+
/** Established once the selected A/V tracks (whichever exist) carry `startMediaTime`. */
|
|
91
|
+
const established = () => {
|
|
92
|
+
const presentation = state.presentation.get();
|
|
93
|
+
if (!isResolvedPresentation(presentation)) return false;
|
|
94
|
+
const ids = [state.selectedVideoTrackId?.get(), state.selectedAudioTrackId?.get()].filter((id) => id !== void 0);
|
|
95
|
+
return ids.length > 0 && ids.every((id) => findTrackById(presentation, id)?.startMediaTime !== void 0);
|
|
96
|
+
};
|
|
97
|
+
return createMachineReactor({
|
|
98
|
+
initial: "inactive",
|
|
99
|
+
monitor: () => {
|
|
100
|
+
if (!isResolvedPresentation(state.presentation.get())) return "inactive";
|
|
101
|
+
return established() ? "established" : "monitoring";
|
|
102
|
+
},
|
|
103
|
+
states: {
|
|
104
|
+
inactive: { entry: () => state.mediaContainerData.set(void 0) },
|
|
105
|
+
monitoring: { effects: () => {
|
|
106
|
+
const containerData = state.mediaContainerData.get();
|
|
107
|
+
if (!containerData) return;
|
|
108
|
+
const startMediaTimes = derive(containerData, selectionContext());
|
|
109
|
+
update(state.presentation, (current) => stampTracks(current, startMediaTimes));
|
|
110
|
+
} },
|
|
111
|
+
established: {}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const establishStartMediaTime = {
|
|
116
|
+
stateKeys: ["presentation", "mediaContainerData"],
|
|
117
|
+
contextKeys: [],
|
|
118
|
+
setup: establishStartMediaTimeSetup
|
|
119
|
+
};
|
|
120
|
+
//#endregion
|
|
121
|
+
export { derivePerTypeStartMediaTime, deriveSharedMinStartMediaTime, establishStartMediaTime };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=establish-start-media-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"establish-start-media-time.js","names":[],"sources":["../../../../src/playback/behaviors/establish-start-media-time.ts"],"sourcesContent":["/**\n * Establish each track's `startMediaTime` (its media-timeline origin) once per\n * source, so a non-zero-PTS source relocates onto a 0-based presentation timeline.\n * The VOD sibling of `anchor-presentation-timeline`: both are per-source\n * establishment units that write a coordinate base value onto every track (the\n * anchor writes `startDate`/`startTime` from wall clock; this writes\n * `startMediaTime` from the container's decode-time origin).\n *\n * This is the **DOM-free reactor half**: it owns the transient `mediaContainerData`\n * slot lifecycle (`inactive` clears it per source), runs the injected\n * {@link DeriveStartMediaTime} seam over it in `monitoring`, and stamps the settled\n * `startMediaTime` onto the model — the coordinate *consume* — until `established`.\n * The byte-level discover/stamp steps that fill the slot are a separate,\n * config `messagePipelines` array (`primitives/relocation-pipelines`);\n * the two coordinate only through the shared `state.mediaContainerData` slot, never\n * by import. See `internal/design/spf/presentation-timeline-model.md`.\n */\nimport type { Behavior } from '../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../core/reactors/create-machine-reactor';\nimport { type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport {\n isResolvedPresentation,\n type MaybeResolvedPresentation,\n type MediaContainerData,\n type Presentation,\n} from '../../media/types';\nimport { findTrackById } from '../../media/utils/tracks';\nimport type { DeriveStartMediaTime, DeriveStartMediaTimeContext } from '../primitives/derive-start-media-time';\n\nexport type { DeriveStartMediaTime, DeriveStartMediaTimeContext };\n\n// ============================================================================\n// STATE / CONFIG\n// ============================================================================\n\nexport interface EstablishStartMediaTimeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * Transient origin-establishment data, keyed by **track type** (`'video'` /\n * `'audio'`) — per spec one init+media pair per type suffices, and ABR rungs of\n * a type share the origin. Filled by the discover steps across appends, reset\n * per source. Never the model — the churn stays here; only the settled\n * `startMediaTime` reaches `Track`.\n */\n mediaContainerData?: Record<string, MediaContainerData>;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * A single type's own media-timeline origin:\n * `baseMediaDecodeTime/timescale − segmentStartTime` (the `segmentStartTime` term\n * makes it the stream origin even when the first loaded segment isn't the 0th).\n * `undefined` until timescale + baseMediaDecodeTime + segmentStartTime are all present.\n */\nfunction ownOrigin(data: MediaContainerData | undefined): number | undefined {\n const { timescale, baseMediaDecodeTime, segmentStartTime } = data ?? {};\n return timescale != null && baseMediaDecodeTime != null && segmentStartTime != null\n ? baseMediaDecodeTime / timescale - segmentStartTime\n : undefined;\n}\n\n/**\n * Origins below this magnitude (seconds) are treated as `0` — the derive returns `0`, so\n * the presentation is left on its native (~0-based) timeline and no `timestampOffset` is\n * set (the loader stamp no-ops on a derived `0`). Ordinary VOD carries a small nonzero\n * encode origin (audio priming, first-frame CTS, edit lists); relocating by a sub-second\n * amount is pointless, and setting `timestampOffset` at all can ripple edge segments.\n * Relocation targets streams with an intentional large origin (instant clips, bipbop @10s).\n *\n * Absolute basis, not proportional: the DTS-below-zero / ripple risk scales with the\n * origin's absolute size, not with the presentation's duration. Also snaps negatives to\n * `0` — a negative origin would relocate *forward*, which is never the intent.\n */\nexport const NEAR_ZERO_ORIGIN_THRESHOLD = 1;\n\n/** Snap a below-threshold (incl. negative) origin to `0` so it isn't relocated. */\nfunction thresholdOrigin(origin: number): number {\n return origin < NEAR_ZERO_ORIGIN_THRESHOLD ? 0 : origin;\n}\n\n/**\n * The **default** — relocate the whole presentation by one shared origin: the `min`\n * across the *selected* A/V tracks' own origins, denormalized onto every type. This\n * single reduce subsumes the \"per-type\" and \"shared\" tiers:\n * - **aligned A/V** — `min` equals each origin (they're equal), so it matches per-type;\n * - **skewed A/V** (e.g. Apple's 44ms audio-lead) — `min` keeps every track's earliest\n * DTS ≥ 0 (relocating by ≤ each own origin never drives one negative) *and* preserves\n * the real skew (per-type would flatten it, desyncing A/V);\n * - **single type / muxed** — `min` of the one origin is that origin.\n *\n * Returns `undefined` for every type until all *selected* types have a complete origin\n * (the shared-`min` barrier). Which types must contribute is read from `ctx` (the\n * selected v/a ids); with no selection context it coordinates across whatever types\n * have data. A shared origin below {@link NEAR_ZERO_ORIGIN_THRESHOLD} is returned as `0`\n * (native — ordinary ~0-PTS VOD isn't relocated).\n */\nexport const deriveSharedMinStartMediaTime: DeriveStartMediaTime = (containerData, ctx) => {\n const contributingTypes: string[] = [];\n if (ctx.selectedVideoTrackId != null) contributingTypes.push('video');\n if (ctx.selectedAudioTrackId != null) contributingTypes.push('audio');\n const types = contributingTypes.length > 0 ? contributingTypes : Object.keys(containerData);\n\n const origins = types.map((type) => ownOrigin(containerData[type]));\n // Barrier: not ready until every contributing type has a complete origin.\n if (origins.length === 0 || origins.some((origin) => origin === undefined)) return {};\n\n const shared = thresholdOrigin(Math.min(...(origins as number[])));\n const out: Record<string, number | undefined> = {};\n for (const type of Object.keys(containerData)) out[type] = shared;\n return out;\n};\n\n/**\n * Coordination-axis *off* — each type relocates by its own origin, independently. Not\n * the default: it flattens real A/V skew (see {@link deriveSharedMinStartMediaTime}).\n * Kept as an opt-in for compositions that know their A/V is aligned and want to skip\n * the shared-`min` barrier (each type stamps as soon as its own origin is discovered).\n */\nexport const derivePerTypeStartMediaTime: DeriveStartMediaTime = (containerData) => {\n const out: Record<string, number | undefined> = {};\n for (const [type, data] of Object.entries(containerData)) {\n const origin = ownOrigin(data);\n out[type] = origin === undefined ? undefined : thresholdOrigin(origin);\n }\n return out;\n};\n\nexport interface EstablishStartMediaTimeConfig {\n /** The reduce seam (coordination knob). Defaults to {@link deriveSharedMinStartMediaTime}. */\n deriveStartMediaTime?: DeriveStartMediaTime;\n}\n\n// ============================================================================\n// REACTOR (owns the transient slot; derives + consumes onto the model)\n// ============================================================================\n\n/** Stamp the derived per-track `startMediaTime` onto the model (idempotent — same reference when nothing moved). */\nfunction stampTracks(presentation: Presentation, startMediaTimes: Record<string, number | undefined>): Presentation {\n let changed = false;\n const selectionSets = presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => {\n const startMediaTime = startMediaTimes[track.type];\n if (startMediaTime === undefined || track.startMediaTime === startMediaTime) return track;\n changed = true;\n return { ...track, startMediaTime };\n }),\n })),\n }));\n return changed ? ({ ...presentation, selectionSets } as Presentation) : presentation;\n}\n\ntype EstablishFsmState = 'inactive' | 'monitoring' | 'established';\n\ninterface EstablishStartMediaTimeDeps {\n state: {\n presentation: Signal<EstablishStartMediaTimeState['presentation']>;\n mediaContainerData: Signal<EstablishStartMediaTimeState['mediaContainerData']>;\n // Optional so the one behavior composes across video-only / audio-only / both,\n // like other cross-track-type behaviors (present at runtime iff a sibling owns it).\n selectedVideoTrackId?: ReadonlySignal<EstablishStartMediaTimeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<EstablishStartMediaTimeState['selectedAudioTrackId']>;\n };\n config?: EstablishStartMediaTimeConfig;\n}\n\nfunction establishStartMediaTimeSetup({\n state,\n config = {},\n}: EstablishStartMediaTimeDeps): Reactor<EstablishFsmState | 'destroying' | 'destroyed'> {\n const derive = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;\n\n const selectionContext = (): DeriveStartMediaTimeContext => ({\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n });\n\n /** Established once the selected A/V tracks (whichever exist) carry `startMediaTime`. */\n const established = (): boolean => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return false;\n const ids = [state.selectedVideoTrackId?.get(), state.selectedAudioTrackId?.get()].filter(\n (id): id is string => id !== undefined\n );\n return ids.length > 0 && ids.every((id) => findTrackById(presentation, id)?.startMediaTime !== undefined);\n };\n\n return createMachineReactor<EstablishFsmState>({\n initial: 'inactive',\n monitor: () => {\n if (!isResolvedPresentation(state.presentation.get())) return 'inactive';\n return established() ? 'established' : 'monitoring';\n },\n states: {\n // Fresh per source: clear the transient slot so a new source re-discovers.\n inactive: { entry: () => state.mediaContainerData.set(undefined) },\n // Derive: reduce the accumulating container data into per-track startMediaTime\n // and stamp it onto the model. Re-runs as discover fills the slot; the update\n // reads presentation untracked, so no self-loop. Disabled on entry to\n // `established` (establish-once, sticky per source).\n monitoring: {\n effects: () => {\n const containerData = state.mediaContainerData.get();\n if (!containerData) return;\n const startMediaTimes = derive(containerData, selectionContext());\n // `current` is always resolved here — the monitor gates `monitoring` on a\n // resolved presentation and transitions before effects re-run — so we cast\n // to `Presentation` rather than re-narrowing.\n update(state.presentation as Signal<MaybeResolvedPresentation>, (current) =>\n stampTracks(current as Presentation, startMediaTimes)\n );\n },\n },\n established: {},\n },\n });\n}\n\nexport const establishStartMediaTime: Behavior<\n {\n presentation: Signal<EstablishStartMediaTimeState['presentation']>;\n mediaContainerData: Signal<EstablishStartMediaTimeState['mediaContainerData']>;\n },\n Record<never, never>,\n EstablishStartMediaTimeConfig\n> = {\n stateKeys: ['presentation', 'mediaContainerData'],\n contextKeys: [],\n setup: establishStartMediaTimeSetup,\n};\n"],"mappings":";;;;;;;;;;;AAuDA,SAAS,UAAU,MAA0D;CAC3E,MAAM,EAAE,WAAW,qBAAqB,qBAAqB,QAAQ,CAAC;CACtE,OAAO,aAAa,QAAQ,uBAAuB,QAAQ,oBAAoB,OAC3E,sBAAsB,YAAY,mBAClC,KAAA;AACN;;AAiBA,SAAS,gBAAgB,QAAwB;CAC/C,OAAO,SAAA,IAAsC,IAAI;AACnD;;;;;;;;;;;;;;;;;AAkBA,MAAa,iCAAuD,eAAe,QAAQ;CACzF,MAAM,oBAA8B,CAAC;CACrC,IAAI,IAAI,wBAAwB,MAAM,kBAAkB,KAAK,OAAO;CACpE,IAAI,IAAI,wBAAwB,MAAM,kBAAkB,KAAK,OAAO;CAGpE,MAAM,WAFQ,kBAAkB,SAAS,IAAI,oBAAoB,OAAO,KAAK,aAAa,EAAA,CAEpE,KAAK,SAAS,UAAU,cAAc,KAAK,CAAC;CAElE,IAAI,QAAQ,WAAW,KAAK,QAAQ,MAAM,WAAW,WAAW,KAAA,CAAS,GAAG,OAAO,CAAC;CAEpF,MAAM,SAAS,gBAAgB,KAAK,IAAI,GAAI,OAAoB,CAAC;CACjE,MAAM,MAA0C,CAAC;CACjD,KAAK,MAAM,QAAQ,OAAO,KAAK,aAAa,GAAG,IAAI,QAAQ;CAC3D,OAAO;AACT;;;;;;;AAQA,MAAa,+BAAqD,kBAAkB;CAClF,MAAM,MAA0C,CAAC;CACjD,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,aAAa,GAAG;EACxD,MAAM,SAAS,UAAU,IAAI;EAC7B,IAAI,QAAQ,WAAW,KAAA,IAAY,KAAA,IAAY,gBAAgB,MAAM;CACvE;CACA,OAAO;AACT;;AAYA,SAAS,YAAY,cAA4B,iBAAmE;CAClH,IAAI,UAAU;CACd,MAAM,gBAAgB,aAAa,cAAc,KAAK,kBAAkB;EACtE,GAAG;EACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;GAC/D,GAAG;GACH,QAAQ,aAAa,OAAO,KAAK,UAAU;IACzC,MAAM,iBAAiB,gBAAgB,MAAM;IAC7C,IAAI,mBAAmB,KAAA,KAAa,MAAM,mBAAmB,gBAAgB,OAAO;IACpF,UAAU;IACV,OAAO;KAAE,GAAG;KAAO;IAAe;GACpC,CAAC;EACH,EAAE;CACJ,EAAE;CACF,OAAO,UAAW;EAAE,GAAG;EAAc;CAAc,IAAqB;AAC1E;AAgBA,SAAS,6BAA6B,EACpC,OACA,SAAS,CAAC,KAC6E;CACvF,MAAM,SAAS,OAAO,wBAAwB;CAE9C,MAAM,0BAAuD;EAC3D,sBAAsB,MAAM,sBAAsB,IAAI;EACtD,sBAAsB,MAAM,sBAAsB,IAAI;CACxD;;CAGA,MAAM,oBAA6B;EACjC,MAAM,eAAe,MAAM,aAAa,IAAI;EAC5C,IAAI,CAAC,uBAAuB,YAAY,GAAG,OAAO;EAClD,MAAM,MAAM,CAAC,MAAM,sBAAsB,IAAI,GAAG,MAAM,sBAAsB,IAAI,CAAC,CAAC,CAAC,QAChF,OAAqB,OAAO,KAAA,CAC/B;EACA,OAAO,IAAI,SAAS,KAAK,IAAI,OAAO,OAAO,cAAc,cAAc,EAAE,CAAC,EAAE,mBAAmB,KAAA,CAAS;CAC1G;CAEA,OAAO,qBAAwC;EAC7C,SAAS;EACT,eAAe;GACb,IAAI,CAAC,uBAAuB,MAAM,aAAa,IAAI,CAAC,GAAG,OAAO;GAC9D,OAAO,YAAY,IAAI,gBAAgB;EACzC;EACA,QAAQ;GAEN,UAAU,EAAE,aAAa,MAAM,mBAAmB,IAAI,KAAA,CAAS,EAAE;GAKjE,YAAY,EACV,eAAe;IACb,MAAM,gBAAgB,MAAM,mBAAmB,IAAI;IACnD,IAAI,CAAC,eAAe;IACpB,MAAM,kBAAkB,OAAO,eAAe,iBAAiB,CAAC;IAIhE,OAAO,MAAM,eAAoD,YAC/D,YAAY,SAAyB,eAAe,CACtD;GACF,EACF;GACA,aAAa,CAAC;EAChB;CACF,CAAC;AACH;AAEA,MAAa,0BAOT;CACF,WAAW,CAAC,gBAAgB,oBAAoB;CAChD,aAAa,CAAC;CACd,OAAO;AACT"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MaybeResolvedPresentation, Presentation } from "../../media/types/index.js";
|
|
2
|
-
|
|
2
|
+
import "../../core/signals/primitives.js";
|
|
3
|
+
import "../../core/reactors/create-machine-reactor.js";
|
|
3
4
|
//#region src/playback/behaviors/resolve-presentation.d.ts
|
|
4
5
|
type ParsePresentation = (text: string, presentation: MaybeResolvedPresentation) => Presentation;
|
|
5
6
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-presentation.d.ts","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"resolve-presentation.d.ts","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"mappings":";;;;KAiDY,qBAAqB,cAAc,cAAc,8BAA8B"}
|
|
@@ -1 +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;
|
|
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;CAC1B,IAAI,CAAC,cAAc,KAAK,OAAO;CAC/B,IAAI,uBAAuB,YAAY,GAAG,OAAO;CAEjD,OADiB,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,SAAS,cAAc,IAC5D,cAAc;AAClC;AAEA,SAAS,yBAAyB,EAChC,OACA,UAQiE;CACjE,MAAM,EAAE,sBAAsB;CAC9B,MAAM,iBAAkC,OAAO,kBAAA;CAE/C,MAAM,qBAAqB,eACzB,YAAY,MAAM,aAAa,IAAI,GAAG,MAAM,QAAQ,IAAI,GAAG,MAAM,cAAc,IAAI,GAAG,cAAc,CACtG;CAEA,OAAO,qBAA+C;EACpD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GACxB,MAAM,CAAC;GACP,WAAW,EACT,aAAa;IACX,MAAM,eAAe,MAAM,aAAa,IAAI;IAC5C,MAAM,KAAK,IAAI,gBAAgB;IAE/B,gBAAgB,cAAc,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CACjD,MAAM,aAAa,gBAAgB,QAAQ,CAAC,CAAC,CAC7C,MAAM,SAAS;KACd,MAAM,SAAS,kBAAkB,MAAM,YAAY;KACnD,MAAM,aAAa,IAAI,MAAM;IAC/B,CAAC,CAAC,CACD,OAAO,UAAU;KAChB,IAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc;KAE3D,QAAQ,MAAM,sDAAsD,KAAK;IAC3E,CAAC;IAEH,OAAO;GACT,EACF;GACA,UAAU,CAAC;EACb;CACF,CAAC;AACH;AAEA,MAAa,sBAAsB,eAAe;CAChD,WAAW;EAAC;EAAgB;EAAW;CAAe;CACtD,aAAa,CAAC;CACd,OAAO;AACT,CAAC"}
|
|
@@ -1 +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"}
|
|
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,iBAAiB;CAWpC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;CAEA,OAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,2BAA2B,CAAC;GAC5B,yBAAyB;IAOvB,mBAAmB,OAAO,SAAS;IACnC,SAAS,OACD;KAMJ,MAAM,eAAe,KAAK,MAAM,YAAY;KAC5C,MAAM,UAAU,MAAM,YAAY,CAAC,IAAI;KACvC,IAAI,CAAC,gBAAgB,CAAC,SAAS;KAE/B,MAAM,QAAQ,mBAAmB,cAAc,OAAO;KACtD,IAAI,CAAC,SAAS,gBAAgB,KAAK,GAAG;KAEtC,OAAO,SAGL,IAAI,KACF,OAAO,WAAW;MAMhB,MAAM,aAAa,mBAAmB,MADnBC,sBAAoB,OAAO,EAAE,OAAO,CAAC,GACZ,KAAK;MASjD,OAAO,MAAM,eAAe,YAAY;OACtC,IAAI,CAAC,uBAAuB,OAAO,GAAG,OAAO;OAC7C,MAAM,UAAU,0BAA0B,SAAS,UAAU;OAS7D,OAAO,yBAAyB,IAAI,WAAW,QAAQ,IACnD,uBAAuB,SAAS,WAAW,MAAM,WAAW,QAAQ,IACpE;MACN,CAAC;KACH,GACA,EAAE,IAAI,MAAM,GAAG,CACjB,CACF;IACF,CACF;GACF;EACF;CACF,CAAC;AACH;AAMA,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,OAAO;AAC5C;AAEA,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,OAAO;AAC5C;AAEA,MAAM,+BAA+B;CACnC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,QAAQ,OAAO;AAC3C;;;;;;AAWA,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAIN;EAMJ,MAAM,cAAc;GAAE,GAAG;GAA+B,GAAG;EAAO;EAClE,OAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcD,qBAA4B,OAAO,WAAW;GAAE;EAC/G,CAAC;CACH;AACF,CAAC;;;;;AAMD,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAIN;EAEJ,MAAM,cAAc;GAAE,GAAG;GAA+B,GAAG;EAAO;EAClE,OAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcA,qBAA4B,OAAO,WAAW;GAAE;EAC/G,CAAC;CACH;AACF,CAAC;;;;;AAMD,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,qBAAqB;CACjD,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAIN;EAEJ,MAAM,cAAc;GAAE,GAAG;GAA8B,GAAG;EAAO;EACjE,OAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcA,qBAA4B,OAAO,WAAW;GAAE;EAC/G,CAAC;CACH;AACF,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import "../../core/signals/primitives.js";
|
|
2
|
+
import "../../index.js";
|
|
1
3
|
import { TrackPicker, VideoSelectionConfig } from "../../media/primitives/select-tracks.js";
|
|
2
|
-
|
|
3
4
|
//#region src/playback/behaviors/select-tracks.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* Config for `selectVideoTrack`. Pass `picker` to fully override selection
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-tracks.d.ts","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"select-tracks.d.ts","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"mappings":";;;;;;;;UA0IiB,+BAA+B;EAC9C,SAAS,YAAY"}
|
|
@@ -1 +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"}
|
|
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,IAAI,CAAC,IAC1C,0BACA,yBACP;CAEA,OAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,2BAA2B,CAAC;GAC5B,yBAAyB,EAYvB,aAAa;IACX,IAAI,CAAC,MAAM,YAAY,CAAC,IAAI,GAAG;KAK7B,MAAM,KAAK,OAAO,MAAM,aAAa,IAAI,GAAI,YAAY;KACzD,IAAI,IAAI,MAAM,YAAY,CAAC,IAAI,EAAE;IACnC;IACA,aAAa,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;GAC/C,EACF;EACF;CACF,CAAC;AACH;;AAaA,MAAM,sBAAmC,iBAAiB,iBAAiB,cAAc,OAAO;;;;;;;;;;;;;;;AA4BhG,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,QAAQ,QAAQ,UAAU;GAC1B,cAAc;EAChB;CACF,CAAC;AACL,CAAC;AAkC+B,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,QAAQ,QAAQ,UAAU;GAC1B,cAAc;EAChB;CACF,CAAC;AACL,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-failover-monitor.d.ts","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup-failover-monitor.d.ts","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"mappings":";;;;;;;UA0BiB;;EAEf"}
|
|
@@ -1 +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,
|
|
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,IACd;;;;;;;;AAmBA,MAAa,uBAAuB,eAAe;CACjD,WAAW,CAAC,gBAAgB,YAAY;CACxC,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAON;EACJ,MAAM,aAAa,OAAO,UAAU,cAAc,gCAAgC;EAIlF,MAAM,yBAAS,IAAI,IAA2C;EAC9D,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;EAEA,OAAO,qBAAqB;GAC1B,SAAS;GACT,eAAe,mBAAmB,IAAI;GACtC,QAAQ;IACN,2BAA2B,CAAC;IAC5B,yBAAyB;KAGvB,mBAAmB;MACjB,OAAO,SAAS,UAAU,aAAa,KAAK,CAAC;MAC7C,OAAO,MAAM;MACb,MAAM,WAAW,IAAI,KAAA,CAAS;KAChC;KACA,SAAS,OACD;MAEJ,CADe,MAAM,WAAW,IAAI,KAAK,CAAC,EAAA,CACnC,SAAS,QAAQ;OAGtB,IAAI,OAAO,IAAI,GAAG,GAAG;OACrB,MAAM,QAAQ,iBAAiB;QAC7B,OAAO,OAAO,GAAG;QACjB,OAAO,MAAM,aAAa,YAAY,SAAS,QAAQ,MAAM,MAAM,GAAG,CAAC;OACzE,GAAG,UAAU;OACb,OAAO,IAAI,KAAK,KAAK;MACvB,CAAC;KACH,CACF;IACF;GACF;EACF,CAAC;CACH;AACF,CAAC"}
|
|
@@ -1 +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,
|
|
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,IAAI,CAAC,EAAE,GAAG;CAUpE,MAAM,cAAc,aAAa;EAC/B,gBAAgB,IAAI;EACpB,MAAM,eAAe,QAAQ,aAAa,IAAI;EAG9C,MAAM,UAAU,KAAK,MAAM,OAAO;EAElC,IAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,OAAO,GAAG;EAE1D,MAAM,SACJ,gBAAgB,kBAAkB,aAAa,OAAO,IAClD,aAAa,UACb,YAAY,KAAA,IACV,iBACA,KAAA;EACR,IAAI,WAAW,KAAA,KAAa,WAAW,SAAS;EAChD,MAAM,QAAQ,IAAI,MAAM;CAC1B,CAAC;CAED,MAAM,eAAe,aAAa;EAChC,MAAM,OAAO,MAAM,QAAQ,IAAI;EAC/B,MAAM,eAAe,QAAQ,aAAa,IAAI;EAC9C,IAAI,CAAC,cAAc;EACnB,IAAI,CAAC,kBAAkB,IAAI,GAAG;EAC9B,IAAI,aAAa,YAAY,MAAM;EACnC,aAAa,UAAU;CACzB,CAAC;CAED,aAAa;EACX,YAAY;EACZ,aAAa;CACf;AACF;AAEA,MAAa,cAAc,eAAe;CACxC,WAAW,CAAC,WAAW,cAAc;CACrC,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT,CAAC"}
|
|
@@ -7,6 +7,68 @@ import { getCdnId } from "../../media/utils/cdn.js";
|
|
|
7
7
|
import { DEFAULT_BANDWIDTH_CONFIG, getBandwidthEstimate } from "../../network/bandwidth-estimator.js";
|
|
8
8
|
import { DEFAULT_QUALITY_CONFIG, resolutionArea } from "../../media/abr/quality-selection.js";
|
|
9
9
|
import { matchesPartialTrack, pickTextTrackFromTracks } from "../../media/primitives/select-tracks.js";
|
|
10
|
+
//#region src/playback/behaviors/track-switching.ts
|
|
11
|
+
/**
|
|
12
|
+
* **Per-type track selection as a rule chain.** While a presentation is
|
|
13
|
+
* resolved, owns that type's `selected{Video,Audio,Text}TrackId` signal: pick a
|
|
14
|
+
* default, react to user intent and algorithmic ranking, and clear it on src
|
|
15
|
+
* unload.
|
|
16
|
+
*
|
|
17
|
+
* Selection runs in two stages. First a **hard-constraints pre-pass**
|
|
18
|
+
* (`applyConstraints`) prunes the unplayable from the candidate set — the
|
|
19
|
+
* failed-CDN constraint (`excludeFailedCdns`, failover cooldown) and the
|
|
20
|
+
* capability constraint (`excludeUnplayableTracks`, codec support). Then a small
|
|
21
|
+
* ordered chain of rules (`applyRules`) picks among the survivors. Each constraint/rule reads the signals it needs at apply
|
|
22
|
+
* time, so the effect subscribes to exactly what was consulted. The chain is
|
|
23
|
+
* three rules, most authoritative first:
|
|
24
|
+
*
|
|
25
|
+
* 1. **user intent** — a soft filter on `user*TrackSelection`: narrow to the
|
|
26
|
+
* partial-track match; an empty match falls through to the full set.
|
|
27
|
+
* 2. **active CDN** — a soft filter on `cdnPriority` (`preferActiveCdn`):
|
|
28
|
+
* narrow to the highest-priority CDN that still has tracks; an empty match
|
|
29
|
+
* falls through. Shared by video and audio, so every type stays on one CDN
|
|
30
|
+
* (`deriveCdnPriority` owns the list). No-op for non-redundant sources.
|
|
31
|
+
* 3. **ranking** — the terminal sort: `rankByBandwidth`, shared by video and
|
|
32
|
+
* audio. Fitting tracks (within the throughput threshold) first, highest
|
|
33
|
+
* bitrate first; over-throughput tracks after, least-over first. Hysteresis
|
|
34
|
+
* via boosting the current track's sort weight by `upgradeMargin`.
|
|
35
|
+
*
|
|
36
|
+
* The composer's early-bail (one survivor → stop) is load-bearing: a user
|
|
37
|
+
* selection that narrows to a single track is the pick without the ranker
|
|
38
|
+
* running, so the bandwidth estimate is never read and the effect doesn't
|
|
39
|
+
* re-fire on bandwidth while that choice holds.
|
|
40
|
+
*
|
|
41
|
+
* Lifecycle: `'presentation-unresolved'` ↔ `'presentation-resolved'`. The
|
|
42
|
+
* resolved state owns the signal; its entry-returned cleanup clears it on exit
|
|
43
|
+
* (canonical cleanup-binds-to-setup per `reactors.md`).
|
|
44
|
+
*
|
|
45
|
+
* The pick is the chain's result mapped to a slot value by `resolveSelection`
|
|
46
|
+
* (default: the head, `applyRules(...)[0]`). Each variant supplies its
|
|
47
|
+
* **constraints + rule chain (+ optional resolveSelection)** via config;
|
|
48
|
+
* `setupTrackSwitching` owns only the lifecycle and runs what it's given. Video
|
|
49
|
+
* and audio run constraints `[excludeFailedCdns, excludeUnplayableTracks]` then
|
|
50
|
+
* rules `[filterByUserSelection, preferActiveCdn, rankByBandwidth]` and take the
|
|
51
|
+
* head; `switchVideoTrack` also accepts ABR tuning config, `switchAudioTrack`
|
|
52
|
+
* takes none. `switchTextTrack` differs — selection is *optional* (captions are
|
|
53
|
+
* opt-in / off-able), so it runs `[excludeFailedCdns]` + `[preferActiveCdn]` and
|
|
54
|
+
* supplies a text terminal (`pickResolvedTextTrack`) that resolves standing user
|
|
55
|
+
* intent (`userTextTrackSelection`, incl. `'off'`) and may yield no selection.
|
|
56
|
+
* (The active-CDN *scope* is the sticky-pick half of multi-CDN; the failed-CDN
|
|
57
|
+
* *constraint* is the failover half — prune the cooled-down CDN, the scope falls
|
|
58
|
+
* to the next.)
|
|
59
|
+
*
|
|
60
|
+
* When the pre-pass prunes a type's candidates to empty, the behavior leaves any
|
|
61
|
+
* prior pick in place and makes no new pick; the late `createSourceBuffer` check
|
|
62
|
+
* stays as the structural backstop for an unplayable rendition reaching the
|
|
63
|
+
* pipeline. Surfacing "nothing playable" as observable state is deferred until a
|
|
64
|
+
* consumer (error mapping) needs it.
|
|
65
|
+
*
|
|
66
|
+
* Deferred: audio's preferred-language / default-track selection as standing
|
|
67
|
+
* soft-filter rules (previously the empty-slot picker, dropped in the move to
|
|
68
|
+
* the rule chain).
|
|
69
|
+
*/
|
|
70
|
+
/** Default initial-bandwidth value before bandwidth measurements arrive. */
|
|
71
|
+
const DEFAULT_INITIAL_BANDWIDTH = 5e6;
|
|
10
72
|
/**
|
|
11
73
|
* Apply rules to a candidate list in order; the pick is the first survivor.
|
|
12
74
|
* Two responsibilities the rules don't carry: a rule that returns nothing is
|
|
@@ -336,6 +398,6 @@ const switchTextTrack = defineBehavior({
|
|
|
336
398
|
})
|
|
337
399
|
});
|
|
338
400
|
//#endregion
|
|
339
|
-
export { switchAudioTrack, switchTextTrack, switchVideoTrack };
|
|
401
|
+
export { DEFAULT_INITIAL_BANDWIDTH, applyConstraints, applyRules, setupTrackSwitching, switchAudioTrack, switchTextTrack, switchVideoTrack };
|
|
340
402
|
|
|
341
403
|
//# sourceMappingURL=track-switching.js.map
|