@videojs/spf 10.0.0-beta.3 → 10.0.0-beta.31
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 +19 -38
- package/dist/default/core/actors/create-machine-actor.js +91 -0
- package/dist/default/core/actors/create-machine-actor.js.map +1 -0
- package/dist/default/core/actors/create-transition-actor.js +46 -0
- package/dist/default/core/actors/create-transition-actor.js.map +1 -0
- package/dist/default/core/composition/create-composition.js +85 -0
- package/dist/default/core/composition/create-composition.js.map +1 -0
- package/dist/default/core/composition/share-signals.js +39 -0
- package/dist/default/core/composition/share-signals.js.map +1 -0
- package/dist/default/core/machine.js +26 -0
- package/dist/default/core/machine.js.map +1 -0
- package/dist/default/core/reactors/create-machine-reactor.js +83 -0
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/default/core/signals/effect.js +41 -0
- package/dist/default/core/signals/effect.js.map +1 -0
- package/dist/default/core/signals/primitives.js +54 -0
- package/dist/default/core/signals/primitives.js.map +1 -0
- package/dist/default/core/signals/when.js +38 -0
- package/dist/default/core/signals/when.js.map +1 -0
- package/dist/default/core/tasks/delayed-reschedule.js +31 -0
- package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/default/core/tasks/task.js +321 -0
- package/dist/default/core/tasks/task.js.map +1 -0
- package/dist/default/dom.js +9 -0
- package/dist/default/hls-audio.js +3 -0
- package/dist/default/hls-background-video.js +3 -0
- package/dist/default/hls-video.js +4 -0
- package/dist/default/hls.js +8 -0
- package/dist/default/index.js +23 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/media/abr/quality-selection.js +21 -0
- package/dist/default/media/abr/quality-selection.js.map +1 -0
- package/dist/default/media/buffer/back-buffer.js +46 -0
- package/dist/default/media/buffer/back-buffer.js.map +1 -0
- package/dist/default/media/buffer/forward-buffer.js +119 -0
- package/dist/default/media/buffer/forward-buffer.js.map +1 -0
- package/dist/default/media/dom/capabilities.js +72 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/default/media/dom/mse/append-segment.js +60 -0
- package/dist/default/media/dom/mse/append-segment.js.map +1 -0
- package/dist/default/media/dom/mse/buffer-flusher.js +55 -0
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/default/media/dom/mse/duration.js +95 -0
- package/dist/default/media/dom/mse/duration.js.map +1 -0
- package/dist/default/media/dom/mse/end-of-stream.js +20 -0
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/default/media/dom/mse/mediasource-setup.js +242 -0
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/default/media/dom/screen.js +112 -0
- package/dist/default/media/dom/screen.js.map +1 -0
- package/dist/default/media/dom/text/resolve-vtt-segment.js +50 -0
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/default/media/dom/text/text-track-slots.js +69 -0
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/default/media/errors.js +69 -0
- package/dist/default/media/errors.js.map +1 -0
- package/dist/default/media/hls/parse-attributes.js +148 -0
- package/dist/default/media/hls/parse-attributes.js.map +1 -0
- package/dist/default/media/hls/parse-media-playlist.js +261 -0
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/default/media/hls/parse-multivariant.js +234 -0
- package/dist/default/media/hls/parse-multivariant.js.map +1 -0
- package/dist/default/media/hls/reload-policy.js +71 -0
- package/dist/default/media/hls/reload-policy.js.map +1 -0
- package/dist/default/media/hls/resolve-url.js +11 -0
- package/dist/default/media/hls/resolve-url.js.map +1 -0
- package/dist/default/media/live-window.js +36 -0
- package/dist/default/media/live-window.js.map +1 -0
- 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/resolution.js +29 -0
- package/dist/default/media/primitives/resolution.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js +107 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -0
- 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 +50 -0
- package/dist/default/media/types/index.js.map +1 -0
- package/dist/default/media/utils/cdn.js +56 -0
- package/dist/default/media/utils/cdn.js.map +1 -0
- package/dist/default/media/utils/preload.js +22 -0
- package/dist/default/media/utils/preload.js.map +1 -0
- package/dist/default/media/utils/track-selection.js +47 -0
- package/dist/default/media/utils/track-selection.js.map +1 -0
- package/dist/default/media/utils/tracks.js +144 -0
- package/dist/default/media/utils/tracks.js.map +1 -0
- package/dist/default/media-tracks.js +2 -0
- package/dist/default/mux-audio.js +3 -0
- package/dist/default/mux-background-video.js +3 -0
- package/dist/default/mux-video.js +3 -0
- package/dist/default/network/bandwidth-estimator.js +96 -0
- package/dist/default/network/bandwidth-estimator.js.map +1 -0
- package/dist/default/network/chunked-stream-iterable.js +49 -0
- package/dist/default/network/chunked-stream-iterable.js.map +1 -0
- package/dist/default/network/ewma.js +71 -0
- package/dist/default/network/ewma.js.map +1 -0
- package/dist/default/network/fetch.js +115 -0
- package/dist/default/network/fetch.js.map +1 -0
- package/dist/default/playback/actors/dom/segment-loader.js +249 -0
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/default/playback/actors/dom/source-buffer.js +139 -0
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/default/playback/actors/dom/text-tracks.js +46 -0
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/default/playback/actors/text-track-segment-loader.js +149 -0
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js +186 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/media.js +9 -0
- package/dist/default/playback/adapters/hls-audio/media.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js +211 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/host.js +60 -0
- package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/media.js +21 -0
- package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/adapter.js +316 -0
- package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js +51 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js +126 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/media.js +11 -0
- package/dist/default/playback/adapters/hls-video/media.js.map +1 -0
- package/dist/default/playback/adapters/mux-audio/media.js +26 -0
- package/dist/default/playback/adapters/mux-audio/media.js.map +1 -0
- package/dist/default/playback/adapters/mux-video/adapter.js +105 -0
- package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/mux-video/media.js +23 -0
- package/dist/default/playback/adapters/mux-video/media.js.map +1 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/default/playback/behaviors/collect-errors.js +111 -0
- package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/default/playback/behaviors/dom/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 +102 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/default/playback/behaviors/dom/load-segments.js +181 -0
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
- 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/seek-to-live-edge.js +132 -0
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +205 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +114 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +40 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js +57 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js +29 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +113 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js +197 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-track.js +141 -0
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/default/playback/behaviors/select-tracks.js +216 -0
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/default/playback/behaviors/sync-preload.js +59 -0
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/default/playback/behaviors/track-switching.js +391 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js +98 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-background-video.js +92 -0
- package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +147 -0
- package/dist/default/playback/engines/hls/engine.js.map +1 -0
- package/dist/default/playback/primitives/error-messages.js +51 -0
- package/dist/default/playback/primitives/error-messages.js.map +1 -0
- package/dist/default/playback/primitives/failover-fetch.js +42 -0
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/default/playback/primitives/head-peek.js +44 -0
- package/dist/default/playback/primitives/head-peek.js.map +1 -0
- package/dist/default/playback/primitives/live-window.js +66 -0
- package/dist/default/playback/primitives/live-window.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/report-track-conditions.js +86 -0
- package/dist/default/playback/primitives/report-track-conditions.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/selection-rules.js +86 -0
- package/dist/default/playback/primitives/selection-rules.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 +61 -0
- package/dist/default/playback/primitives/track-types.js.map +1 -0
- package/dist/dev/core/actors/actor.d.ts +26 -0
- package/dist/dev/core/actors/actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts +128 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.js +91 -0
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts +41 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.js +46 -0
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
- package/dist/dev/core/composition/create-composition.d.ts +273 -0
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
- package/dist/dev/core/composition/create-composition.js +85 -0
- package/dist/dev/core/composition/create-composition.js.map +1 -0
- package/dist/dev/core/composition/share-signals.d.ts +46 -0
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
- package/dist/dev/core/composition/share-signals.js +39 -0
- package/dist/dev/core/composition/share-signals.js.map +1 -0
- package/dist/dev/core/machine.d.ts +21 -0
- package/dist/dev/core/machine.d.ts.map +1 -0
- package/dist/dev/core/machine.js +26 -0
- package/dist/dev/core/machine.js.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +93 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.js +83 -0
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/dev/core/signals/effect.d.ts +15 -0
- package/dist/dev/core/signals/effect.d.ts.map +1 -0
- package/dist/dev/core/signals/effect.js +41 -0
- package/dist/dev/core/signals/effect.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +46 -0
- package/dist/dev/core/signals/primitives.d.ts.map +1 -0
- package/dist/dev/core/signals/primitives.js +54 -0
- package/dist/dev/core/signals/primitives.js.map +1 -0
- package/dist/dev/core/signals/when.js +38 -0
- package/dist/dev/core/signals/when.js.map +1 -0
- package/dist/dev/core/tasks/delayed-reschedule.js +31 -0
- package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/dev/core/tasks/task.d.ts +166 -0
- package/dist/dev/core/tasks/task.d.ts.map +1 -0
- package/dist/dev/core/tasks/task.js +321 -0
- package/dist/dev/core/tasks/task.js.map +1 -0
- package/dist/dev/dom.d.ts +9 -0
- package/dist/dev/dom.js +9 -0
- package/dist/dev/hls-audio.d.ts +3 -0
- package/dist/dev/hls-audio.js +3 -0
- package/dist/dev/hls-background-video.d.ts +4 -0
- package/dist/dev/hls-background-video.js +3 -0
- package/dist/dev/hls-video.d.ts +5 -0
- package/dist/dev/hls-video.js +4 -0
- package/dist/dev/hls.d.ts +9 -0
- package/dist/dev/hls.js +8 -0
- package/dist/dev/index.d.ts +24 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +23 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/media/abr/quality-selection.d.ts +22 -0
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
- package/dist/dev/media/abr/quality-selection.js +21 -0
- package/dist/dev/media/abr/quality-selection.js.map +1 -0
- package/dist/dev/media/buffer/back-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/back-buffer.js +46 -0
- package/dist/dev/media/buffer/back-buffer.js.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.js +119 -0
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
- package/dist/dev/media/dom/capabilities.js +72 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts +18 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.js +60 -0
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts +24 -0
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/buffer-flusher.js +55 -0
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/dev/media/dom/mse/duration.js +95 -0
- package/dist/dev/media/dom/mse/duration.js.map +1 -0
- package/dist/dev/media/dom/mse/end-of-stream.js +20 -0
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js +242 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/dev/media/dom/screen.d.ts +7 -0
- package/dist/dev/media/dom/screen.d.ts.map +1 -0
- package/dist/dev/media/dom/screen.js +112 -0
- package/dist/dev/media/dom/screen.js.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +6 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.js +50 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts +30 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.js +69 -0
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/dev/media/errors.d.ts +82 -0
- package/dist/dev/media/errors.d.ts.map +1 -0
- package/dist/dev/media/errors.js +69 -0
- package/dist/dev/media/errors.js.map +1 -0
- package/dist/dev/media/hls/parse-attributes.js +148 -0
- package/dist/dev/media/hls/parse-attributes.js.map +1 -0
- package/dist/dev/media/hls/parse-media-playlist.js +261 -0
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/dev/media/hls/parse-multivariant.js +234 -0
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
- package/dist/dev/media/hls/reload-policy.js +71 -0
- package/dist/dev/media/hls/reload-policy.js.map +1 -0
- package/dist/dev/media/hls/resolve-url.js +11 -0
- package/dist/dev/media/hls/resolve-url.js.map +1 -0
- package/dist/dev/media/live-window.js +36 -0
- package/dist/dev/media/live-window.js.map +1 -0
- 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/resolution.d.ts +24 -0
- package/dist/dev/media/primitives/resolution.d.ts.map +1 -0
- package/dist/dev/media/primitives/resolution.js +29 -0
- package/dist/dev/media/primitives/resolution.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +107 -0
- package/dist/dev/media/primitives/select-tracks.js.map +1 -0
- 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 +377 -0
- package/dist/dev/media/types/index.d.ts.map +1 -0
- package/dist/dev/media/types/index.js +50 -0
- package/dist/dev/media/types/index.js.map +1 -0
- package/dist/dev/media/utils/cdn.d.ts +13 -0
- package/dist/dev/media/utils/cdn.d.ts.map +1 -0
- package/dist/dev/media/utils/cdn.js +56 -0
- package/dist/dev/media/utils/cdn.js.map +1 -0
- package/dist/dev/media/utils/preload.js +22 -0
- package/dist/dev/media/utils/preload.js.map +1 -0
- package/dist/dev/media/utils/track-selection.js +47 -0
- package/dist/dev/media/utils/track-selection.js.map +1 -0
- package/dist/dev/media/utils/tracks.js +144 -0
- package/dist/dev/media/utils/tracks.js.map +1 -0
- package/dist/dev/media-tracks.d.ts +3 -0
- package/dist/dev/media-tracks.js +2 -0
- package/dist/dev/mux-audio.d.ts +4 -0
- package/dist/dev/mux-audio.js +3 -0
- package/dist/dev/mux-background-video.d.ts +4 -0
- package/dist/dev/mux-background-video.js +3 -0
- package/dist/dev/mux-video.d.ts +4 -0
- package/dist/dev/mux-video.js +3 -0
- package/dist/dev/network/bandwidth-estimator.d.ts +50 -0
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
- package/dist/dev/network/bandwidth-estimator.js +96 -0
- package/dist/dev/network/bandwidth-estimator.js.map +1 -0
- package/dist/dev/network/chunked-stream-iterable.js +49 -0
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
- package/dist/dev/network/ewma.js +71 -0
- package/dist/dev/network/ewma.js.map +1 -0
- package/dist/dev/network/fetch.js +115 -0
- package/dist/dev/network/fetch.js.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +48 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.js +249 -0
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +47 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.js +139 -0
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -0
- package/dist/dev/playback/actors/dom/text-tracks.js +46 -0
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +54 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js +149 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/text-tracks.d.ts +33 -0
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +54 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js +186 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/media.d.ts +10 -0
- package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/media.js +9 -0
- package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +77 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.js +211 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts +38 -0
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js +60 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/media.d.ts +20 -0
- package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/media.js +21 -0
- package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts +63 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js +316 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +18 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.js +51 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +22 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js +126 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media.d.ts +10 -0
- package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media.js +11 -0
- package/dist/dev/playback/adapters/hls-video/media.js.map +1 -0
- package/dist/dev/playback/adapters/mux-audio/media.d.ts +28 -0
- package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/mux-audio/media.js +26 -0
- package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -0
- package/dist/dev/playback/adapters/mux-video/adapter.d.ts +34 -0
- package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/mux-video/adapter.js +105 -0
- package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/mux-video/media.d.ts +24 -0
- package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/mux-video/media.js +23 -0
- package/dist/dev/playback/adapters/mux-video/media.js.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +26 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/collect-errors.js +111 -0
- package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/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 +102 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +72 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js +181 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
- 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/seek-to-live-edge.js +132 -0
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +205 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +114 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +28 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +40 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +33 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +47 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +33 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts +8 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js +57 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +29 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +113 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +36 -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 +197 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +8 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-track.js +141 -0
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts +72 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +216 -0
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +14 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/dev/playback/behaviors/sync-preload.js +59 -0
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.js +391 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +173 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js +98 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts +158 -0
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js +92 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +329 -0
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine.js +147 -0
- package/dist/dev/playback/engines/hls/engine.js.map +1 -0
- 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/error-messages.js +51 -0
- package/dist/dev/playback/primitives/error-messages.js.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js +42 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/dev/playback/primitives/head-peek.js +44 -0
- package/dist/dev/playback/primitives/head-peek.js.map +1 -0
- package/dist/dev/playback/primitives/live-window.js +66 -0
- package/dist/dev/playback/primitives/live-window.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/report-track-conditions.d.ts +11 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.js +86 -0
- package/dist/dev/playback/primitives/report-track-conditions.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/selection-rules.d.ts +38 -0
- package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -0
- package/dist/dev/playback/primitives/selection-rules.js +86 -0
- package/dist/dev/playback/primitives/selection-rules.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 +61 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +63 -26
- package/dist/adapter-CMw-rvbk.js +0 -3512
- package/dist/adapter-CMw-rvbk.js.map +0 -1
- package/dist/adapter-l8iJN_GI.d.ts +0 -689
- package/dist/adapter-l8iJN_GI.d.ts.map +0 -1
- package/dist/dom/index.d.ts +0 -243
- package/dist/dom/index.d.ts.map +0 -1
- package/dist/dom/index.js +0 -38
- package/dist/dom/index.js.map +0 -1
- package/dist/dom/playback-engine/index.d.ts +0 -2
- package/dist/dom/playback-engine/index.js +0 -4
- package/dist/index.d.ts +0 -13
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/src-DRsS_H80.js +0 -14
- package/dist/src-DRsS_H80.js.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { computed, peek, update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { RecurringRunner, Task, runOnce } from "../../core/tasks/task.js";
|
|
4
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
5
|
+
import { deriveStreamType, getMediaPlaylistMetadata, isResolvedPresentation, isResolvedTrack } from "../../media/types/index.js";
|
|
6
|
+
import { applyContainerMimeType, findTrack, updateTrackInPresentation } from "../../media/utils/tracks.js";
|
|
7
|
+
import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
|
|
8
|
+
import { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from "../../media/hls/parse-media-playlist.js";
|
|
9
|
+
import { emitError } from "./collect-errors.js";
|
|
10
|
+
import { fetchResolvableText } from "../../network/fetch.js";
|
|
11
|
+
import { failoverFetch } from "../primitives/failover-fetch.js";
|
|
12
|
+
import { when } from "../../core/signals/when.js";
|
|
13
|
+
//#region src/playback/behaviors/resolve-track.ts
|
|
14
|
+
function setupTrackResolution({ state, config: { selectedKey, findTrackToResolve, fetchResolvableText: fetchResolvableText$1 = fetchResolvableText, gateFirstParse, reschedule, reportUnsupportedTrackConditions } }) {
|
|
15
|
+
const runner = new RecurringRunner(reschedule ?? runOnce);
|
|
16
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
17
|
+
return createMachineReactor({
|
|
18
|
+
initial: "presentation-unresolved",
|
|
19
|
+
monitor: () => derivedStateSignal.get(),
|
|
20
|
+
states: {
|
|
21
|
+
"presentation-unresolved": {},
|
|
22
|
+
"presentation-resolved": {
|
|
23
|
+
entry: () => () => runner.abortAll(),
|
|
24
|
+
effects: [() => {
|
|
25
|
+
const presentation = peek(state.presentation);
|
|
26
|
+
const trackId = state[selectedKey].get();
|
|
27
|
+
if (!presentation || !trackId) return;
|
|
28
|
+
const track = findTrackToResolve(presentation, trackId);
|
|
29
|
+
if (!track || isResolvedTrack(track) && Number.isFinite(track.duration)) return;
|
|
30
|
+
runner.schedule(new Task(async (signal) => {
|
|
31
|
+
const snapshot = peek(state.presentation);
|
|
32
|
+
const current = snapshot ? findTrackToResolve(snapshot, trackId) : void 0;
|
|
33
|
+
if (!current) throw new Error("resolve-track: selected track not found");
|
|
34
|
+
const text = await fetchResolvableText$1(current, { signal });
|
|
35
|
+
if (gateFirstParse && !isResolvedTrack(current)) {
|
|
36
|
+
const { selectedVideoTrackId, selectedAudioTrackId } = state;
|
|
37
|
+
await when(() => gateFirstParse(state.presentation.get(), {
|
|
38
|
+
selectedVideoTrackId: selectedVideoTrackId?.get(),
|
|
39
|
+
selectedAudioTrackId: selectedAudioTrackId?.get()
|
|
40
|
+
}, trackId), { signal });
|
|
41
|
+
}
|
|
42
|
+
const live = peek(state.presentation);
|
|
43
|
+
const previous = live ? findTrackToResolve(live, trackId) : void 0;
|
|
44
|
+
if (!previous) throw new Error("resolve-track: selected track not found");
|
|
45
|
+
const mediaTrack = parseMediaPlaylist(text, previous);
|
|
46
|
+
if (reportUnsupportedTrackConditions) for (const condition of reportUnsupportedTrackConditions(mediaTrack)) emitError(state, condition);
|
|
47
|
+
update(state.presentation, (current) => {
|
|
48
|
+
if (!isResolvedPresentation(current)) return current;
|
|
49
|
+
const patched = updateTrackInPresentation(current, mediaTrack);
|
|
50
|
+
return {
|
|
51
|
+
...NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType) ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType) : patched,
|
|
52
|
+
streamType: deriveStreamType(getMediaPlaylistMetadata(mediaTrack))
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
return mediaTrack;
|
|
56
|
+
}, { id: track.id })).catch(() => {});
|
|
57
|
+
}]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const VIDEO_TRACK_RESOLUTION_CONFIG = {
|
|
63
|
+
...VIDEO_TYPE_CONFIG,
|
|
64
|
+
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "video", trackId)
|
|
65
|
+
};
|
|
66
|
+
const AUDIO_TRACK_RESOLUTION_CONFIG = {
|
|
67
|
+
...AUDIO_TYPE_CONFIG,
|
|
68
|
+
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "audio", trackId)
|
|
69
|
+
};
|
|
70
|
+
const TEXT_TRACK_RESOLUTION_CONFIG = {
|
|
71
|
+
...TEXT_TYPE_CONFIG,
|
|
72
|
+
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "text", trackId)
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Resolve unresolved video tracks. Schedules a fetch task whenever the
|
|
76
|
+
* selected video track is partially resolved, parses the manifest, and
|
|
77
|
+
* writes the resolved track back into `state.presentation`.
|
|
78
|
+
*/
|
|
79
|
+
const resolveVideoTrack = defineBehavior({
|
|
80
|
+
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
81
|
+
contextKeys: [],
|
|
82
|
+
setup: ({ state, config = {} }) => {
|
|
83
|
+
const trackConfig = {
|
|
84
|
+
...VIDEO_TRACK_RESOLUTION_CONFIG,
|
|
85
|
+
...config
|
|
86
|
+
};
|
|
87
|
+
return setupTrackResolution({
|
|
88
|
+
state,
|
|
89
|
+
config: {
|
|
90
|
+
...trackConfig,
|
|
91
|
+
fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,
|
|
98
|
+
* narrowed to audio.
|
|
99
|
+
*/
|
|
100
|
+
const resolveAudioTrack = defineBehavior({
|
|
101
|
+
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
102
|
+
contextKeys: [],
|
|
103
|
+
setup: ({ state, config = {} }) => {
|
|
104
|
+
const trackConfig = {
|
|
105
|
+
...AUDIO_TRACK_RESOLUTION_CONFIG,
|
|
106
|
+
...config
|
|
107
|
+
};
|
|
108
|
+
return setupTrackResolution({
|
|
109
|
+
state,
|
|
110
|
+
config: {
|
|
111
|
+
...trackConfig,
|
|
112
|
+
fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
/**
|
|
118
|
+
* Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,
|
|
119
|
+
* narrowed to text.
|
|
120
|
+
*/
|
|
121
|
+
const resolveTextTrack = defineBehavior({
|
|
122
|
+
stateKeys: ["presentation", "selectedTextTrackId"],
|
|
123
|
+
contextKeys: [],
|
|
124
|
+
setup: ({ state, config = {} }) => {
|
|
125
|
+
const trackConfig = {
|
|
126
|
+
...TEXT_TRACK_RESOLUTION_CONFIG,
|
|
127
|
+
...config
|
|
128
|
+
};
|
|
129
|
+
return setupTrackResolution({
|
|
130
|
+
state,
|
|
131
|
+
config: {
|
|
132
|
+
...trackConfig,
|
|
133
|
+
fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
//#endregion
|
|
139
|
+
export { resolveAudioTrack, resolveTextTrack, resolveVideoTrack };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=resolve-track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-track.js","names":["defaultFetchResolvableText","fetchResolvableText"],"sources":["../../../../src/playback/behaviors/resolve-track.ts"],"sourcesContent":["import { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { when } from '../../core/signals/when';\nimport { RecurringRunner, type Reschedule, runOnce, 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 { deriveStreamType, getMediaPlaylistMetadata, 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 type { GateFirstParse } from '../primitives/gate-first-parse';\nimport type { ReportUnsupportedTrackConditions } from '../primitives/report-track-conditions';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\nimport { type ErrorEmitterState, emitError } from './collect-errors';\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\n/**\n * Sibling-owned A/V selection signals, present at runtime iff a sibling\n * behavior owns them. Deliberately not in the typed slice / `stateKeys`\n * (declaring them would force every composition to carry both selections);\n * read only to build the injected first-parse gate's selection context.\n */\ntype SiblingSelectionSignals = {\n selectedVideoTrackId?: ReadonlySignal<ResolveTrackState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<ResolveTrackState['selectedAudioTrackId']>;\n};\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 /** Holds a track's first parse until its placement inputs settle (live anchor); absent → parse immediately. */\n gateFirstParse?: GateFirstParse;\n /** Live re-run policy for the `RecurringRunner`; absent → resolve once (VOD). */\n reschedule?: Reschedule<ResolvedTrack>;\n /**\n * Report conditions found in the parsed playlist (see\n * `primitives/report-track-conditions`); absent → report nothing.\n */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\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 /** First-parse placement gate (see `primitives/gate-first-parse`); absent → parse immediately. */\n gateFirstParse?: GateFirstParse;\n /** Live media-playlist re-run policy; absent → resolve once. */\n reschedule?: Reschedule<ResolvedTrack>;\n /** Playlist-derived condition reporting (see `primitives/report-track-conditions`). */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n}\n\nfunction setupTrackResolution<K extends SelectedTrackKey>({\n state,\n config: {\n selectedKey,\n findTrackToResolve,\n fetchResolvableText = defaultFetchResolvableText,\n gateFirstParse,\n reschedule,\n reportUnsupportedTrackConditions,\n },\n}: {\n // Widened with the optional `errors` slot: reporting writes through it without\n // the behavior declaring ownership, and no-ops when `collectErrors` isn't\n // composed. Same contract as `failedCdns` / `failoverFetch`.\n state: ResolveTrackStateMap<K> & ErrorEmitterState;\n config: TrackResolutionConfig<K>;\n}) {\n // Recurrence lives in the runner: with a `reschedule` (live) it re-runs the\n // task until the policy stops; `runOnce` (VOD) runs it exactly once. Single-\n // slot — a selection change re-schedules (abort-and-replace).\n const runner = new RecurringRunner<ResolvedTrack>(reschedule ?? runOnce);\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, or by this\n // behavior's own reload cycles) don't 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 // Skip a complete or missing track; a resolved-but-incomplete (live)\n // window still reloads — its window may have slid past the playhead.\n if (!track || (isResolvedTrack(track) && Number.isFinite(track.duration))) return;\n\n // Abort (selection/source change) settles quietly; a genuine resolve\n // failure rejects — swallowed for now (TODO: surface to state).\n const scheduled = 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 //\n // Re-runs (clones) on each reload, so the body re-reads the live\n // snapshot (via `trackId`) rather than capturing the gate-time\n // `track` — a reload carries the prior window's timeline forward.\n new Task(\n async (signal) => {\n const snapshot = peek(state.presentation);\n const current = snapshot ? findTrackToResolve(snapshot, trackId) : undefined;\n if (!current) throw new Error('resolve-track: selected track not found');\n\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. The run-time\n // `current` supplies only the playlist URL (stable across the\n // fetch).\n const text = await fetchResolvableText(current, { signal });\n\n // Hold placement — never the fetch — until this track's\n // first-parse placement inputs settle (for live, the\n // wall-clock anchor; see `primitives/gate-first-parse`).\n // First parses only — a reload's window is already on the\n // established timeline. Aborting rejects the wait, so a\n // source change can't strand a gated task.\n if (gateFirstParse && !isResolvedTrack(current)) {\n const { selectedVideoTrackId, selectedAudioTrackId } = state as SiblingSelectionSignals;\n await when(\n () =>\n gateFirstParse(\n state.presentation.get(),\n {\n selectedVideoTrackId: selectedVideoTrackId?.get(),\n selectedAudioTrackId: selectedAudioTrackId?.get(),\n },\n trackId\n ),\n { signal }\n );\n }\n\n // Re-read `previous` after the awaits: a concurrent write\n // during them — notably the establishment reactor stamping\n // the anchor `startDate` onto this track's shell — must feed\n // the parse, not be clobbered (anchoring is establish-once;\n // parsing the pre-fetch snapshot would strand the track off\n // the anchor for good). Correctness rests on a\n // run-to-completion invariant: NOTHING may yield (await)\n // between this re-read and the write below, so no writer can\n // interleave. `parseMediaPlaylist` is synchronous — keep it\n // that way, or move the read into the updater.\n const live = peek(state.presentation);\n const previous = live ? findTrackToResolve(live, trackId) : undefined;\n if (!previous) throw new Error('resolve-track: selected track not found');\n const mediaTrack = parseMediaPlaylist(text, previous);\n\n // Report what the parse revealed about this rendition, before\n // committing it. Causes only — one unplayable rendition doesn't\n // make the source unplayable, so the verdict stays with\n // track-switching's empty-candidate branch.\n if (reportUnsupportedTrackConditions) {\n for (const condition of reportUnsupportedTrackConditions(mediaTrack as ResolvedTrack)) {\n emitError(state, condition);\n }\n }\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 const relabeled = NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType)\n ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType)\n : patched;\n // Stream nature (live vs on-demand), rewritten from whichever\n // track just parsed — every type's resolve and every live\n // reload takes this path. Idempotent in practice rather than\n // by construction: `PLAYLIST-TYPE` is a per-source constant\n // that a source's renditions agree on, so each write lands\n // the same value. Renditions that disagreed would make the\n // winner resolve-order-dependent.\n return { ...relabeled, streamType: deriveStreamType(getMediaPlaylistMetadata(mediaTrack)) };\n });\n return mediaTrack;\n },\n { id: track.id }\n )\n );\n scheduled.catch(() => {});\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":";;;;;;;;;;;;;AA2FA,SAAS,qBAAiD,EACxD,OACA,QAAQ,EACN,aACA,oBACA,qBAAA,wBAAsBA,qBACtB,gBACA,YACA,sCAQD;CAID,MAAM,SAAS,IAAI,gBAA+B,cAAc,OAAO;CAWvE,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;KAGtD,IAAI,CAAC,SAAU,gBAAgB,KAAK,KAAK,OAAO,SAAS,MAAM,QAAQ,GAAI;KA2G3E,OAvGyB,SAOvB,IAAI,KACF,OAAO,WAAW;MAChB,MAAM,WAAW,KAAK,MAAM,YAAY;MACxC,MAAM,UAAU,WAAW,mBAAmB,UAAU,OAAO,IAAI,KAAA;MACnE,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,yCAAyC;MAQvE,MAAM,OAAO,MAAMC,sBAAoB,SAAS,EAAE,OAAO,CAAC;MAQ1D,IAAI,kBAAkB,CAAC,gBAAgB,OAAO,GAAG;OAC/C,MAAM,EAAE,sBAAsB,yBAAyB;OACvD,MAAM,WAEF,eACE,MAAM,aAAa,IAAI,GACvB;QACE,sBAAsB,sBAAsB,IAAI;QAChD,sBAAsB,sBAAsB,IAAI;OAClD,GACA,OACF,GACF,EAAE,OAAO,CACX;MACF;MAYA,MAAM,OAAO,KAAK,MAAM,YAAY;MACpC,MAAM,WAAW,OAAO,mBAAmB,MAAM,OAAO,IAAI,KAAA;MAC5D,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,yCAAyC;MACxE,MAAM,aAAa,mBAAmB,MAAM,QAAQ;MAMpD,IAAI,kCACF,KAAK,MAAM,aAAa,iCAAiC,UAA2B,GAClF,UAAU,OAAO,SAAS;MAW9B,OAAO,MAAM,eAAe,YAAY;OACtC,IAAI,CAAC,uBAAuB,OAAO,GAAG,OAAO;OAC7C,MAAM,UAAU,0BAA0B,SAAS,UAAU;OAmB7D,OAAO;QAAE,GAVS,yBAAyB,IAAI,WAAW,QAAQ,IAC9D,uBAAuB,SAAS,WAAW,MAAM,WAAW,QAAQ,IACpE;QAQmB,YAAY,iBAAiB,yBAAyB,UAAU,CAAC;OAAE;MAC5F,CAAC;MACD,OAAO;KACT,GACA,EAAE,IAAI,MAAM,GAAG,CACjB,CAEM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1B,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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import "../../core/signals/primitives.js";
|
|
2
|
+
import "../../index.js";
|
|
3
|
+
import { CapabilityConstraintConfig, SelectionRule } from "../primitives/selection-rules.js";
|
|
4
|
+
//#region src/playback/behaviors/select-tracks.d.ts
|
|
5
|
+
/** A selection rule over this behavior's candidate tracks. */
|
|
6
|
+
type SelectTrackRule<Config> = SelectionRule<SelectableTrack, unknown, unknown, Config | undefined>;
|
|
7
|
+
/** What a rule here needs off a candidate: the id it may become the pick by. */
|
|
8
|
+
type SelectableTrack = {
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Order the candidates by resolution, largest first, with bandwidth breaking ties
|
|
13
|
+
* between renditions of identical dimensions. The background-video default — that
|
|
14
|
+
* variant pins one rendition for the session, and absent a cap the largest is the
|
|
15
|
+
* head.
|
|
16
|
+
*
|
|
17
|
+
* A ranker, so it reorders rather than narrowing: the chain's pick is the head of
|
|
18
|
+
* what it returns, which means ranking never has to collapse to one track. Belongs
|
|
19
|
+
* last in a chain — a sort only reorders what survived the filters ahead of it, and
|
|
20
|
+
* leaving it last is what lets `applyRules` early-bail before it runs.
|
|
21
|
+
*
|
|
22
|
+
* Exported because it is a *rule*, not a variant's private policy: the same one
|
|
23
|
+
* composes into `switchVideoTrack`'s chain when a ranker is wanted there.
|
|
24
|
+
*/
|
|
25
|
+
declare const preferHighestResolution: SelectTrackRule<unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Narrow to the renditions that fit the screen, by pixel area — the screen-size
|
|
28
|
+
* cap from `internal/design/spf/features/rendition-selection-caps.md`, as a scope
|
|
29
|
+
* (soft filter) rather than a constraint: an over-cap rendition is wasteful, not
|
|
30
|
+
* unplayable, so nothing here may make a source unplayable.
|
|
31
|
+
*
|
|
32
|
+
* Narrows only — it neither orders the survivors nor resolves the case where none
|
|
33
|
+
* survive, because `applyRules` owns both. So it needs a ranker behind it to pick
|
|
34
|
+
* within the cap: `[screenResolutionCap, preferHighestResolution]` yields the
|
|
35
|
+
* largest rendition that fits. Composed *last*, the pick would instead be whichever
|
|
36
|
+
* fitting rendition the manifest happened to list first.
|
|
37
|
+
*
|
|
38
|
+
* Reading `state.screenResolution` through its signal is what subscribes a
|
|
39
|
+
* re-evaluating chain (`switchVideoTrack`) to screen changes; `selectVideoTrack`
|
|
40
|
+
* pins the first answer instead, by design.
|
|
41
|
+
*
|
|
42
|
+
* Compares areas rather than matching a `"1080p"`-style tier because a tier only
|
|
43
|
+
* describes a rendition once you assume its aspect ratio — the assumption that
|
|
44
|
+
* mis-measures an anamorphic ladder. See `media/dom/screen.ts`.
|
|
45
|
+
*
|
|
46
|
+
* Three ways the cap ends up not applying, all of them fall-through:
|
|
47
|
+
*
|
|
48
|
+
* - **No `screenResolution` signal at all**, because the composition omits
|
|
49
|
+
* `trackScreenResolution`. So composing the cap without its signal source is
|
|
50
|
+
* inert rather than broken.
|
|
51
|
+
* - **A `screenResolution` of `undefined`**, meaning no screen to read. "Unknown"
|
|
52
|
+
* has to mean "don't cap": treating it as an area of zero would pin every source
|
|
53
|
+
* to its smallest rendition on exactly the environments we know least about.
|
|
54
|
+
* - **No rendition fits**, on a screen smaller than the whole ladder. `applyRules`
|
|
55
|
+
* skips the empty result and the chain proceeds unnarrowed, so the ranker behind
|
|
56
|
+
* the cap decides — for `preferHighestResolution`, the largest rendition. A floor
|
|
57
|
+
* is the fix if that ever matters (`rendition-selection-caps.md` carries one), not
|
|
58
|
+
* a special case here.
|
|
59
|
+
*/
|
|
60
|
+
declare const screenResolutionCap: SelectTrackRule<unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Config for `selectVideoTrack`. Pass `rules` to replace the selection chain, or
|
|
63
|
+
* `constraints` to replace the capability pre-pass; otherwise the chain is empty
|
|
64
|
+
* and the first playable candidate is the pick.
|
|
65
|
+
*/
|
|
66
|
+
interface SelectVideoTrackConfig extends CapabilityConstraintConfig {
|
|
67
|
+
constraints?: readonly SelectTrackRule<SelectVideoTrackConfig>[];
|
|
68
|
+
rules?: readonly SelectTrackRule<SelectVideoTrackConfig>[];
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { SelectTrackRule, SelectVideoTrackConfig, preferHighestResolution, screenResolutionCap };
|
|
72
|
+
//# sourceMappingURL=select-tracks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-tracks.d.ts","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"mappings":";;;;;KA8FY,gBAAgB,UAAU,cAAc,mCAAmC;;KAGlF;EAAoB;;;;;;;;;;;;;;;;cA+JZ,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAyCzB,qBAAqB;;;;;;UAgBjB,+BAA+B;EAC9C,uBAAuB,gBAAgB;EACvC,iBAAiB,gBAAgB"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { computed, peek } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedPresentation } from "../../media/types/index.js";
|
|
5
|
+
import { getTracksByType } from "../../media/utils/tracks.js";
|
|
6
|
+
import { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
|
|
7
|
+
import { byDescendingResolution, pickAudioTrackFromTracks, tracksUnderPixelArea } from "../../media/primitives/select-tracks.js";
|
|
8
|
+
import { applyConstraints, applyRules, excludeUnplayableTracks, sameCandidateSet } from "../primitives/selection-rules.js";
|
|
9
|
+
//#region src/playback/behaviors/select-tracks.ts
|
|
10
|
+
/**
|
|
11
|
+
* **Default audio/video track selection on src load / unselect on src unload.**
|
|
12
|
+
* When a presentation is resolved, sets `selectedVideoTrackId` /
|
|
13
|
+
* `selectedAudioTrackId` from a per-type default rule chain if no selection
|
|
14
|
+
* already exists. When the presentation is unset/reset (transitions back to unresolved),
|
|
15
|
+
* clears the selection so a stale id from the previous source doesn't persist.
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle-driven: the pick fires once per transition, and nothing re-picks —
|
|
18
|
+
* that is what separates these from the `switch*` variants. External writes (user
|
|
19
|
+
* picks, ABR, programmatic filter-driven re-picks) are left alone, including a
|
|
20
|
+
* write naming a track the manifest never offered.
|
|
21
|
+
*
|
|
22
|
+
* The one thing policed between transitions is a pick the *constraints* turn
|
|
23
|
+
* against: a rendition's container and encryption are only known once its media
|
|
24
|
+
* playlist resolves, which is after the pick was made, so a selection that becomes
|
|
25
|
+
* unplayable is dropped. Dropped, never moved — re-picking is exactly the behavior
|
|
26
|
+
* `switchVideoTrack` exists to provide. Dropping reports nothing on its own, since
|
|
27
|
+
* whatever made the pick unplayable already reported its own, more specific cause.
|
|
28
|
+
*
|
|
29
|
+
* Selection runs the same rule model `switchVideoTrack` does — a hard
|
|
30
|
+
* `constraints` pre-pass, then an ordered `rules` chain, with the pick as the
|
|
31
|
+
* head (see `internal/design/spf/track-switching-model.md`). What differs is
|
|
32
|
+
* reactivity, not the rules: this evaluates the chain once on resolve and pins
|
|
33
|
+
* the result, where `switchVideoTrack` re-evaluates inside an effect so its rules
|
|
34
|
+
* subscribe to bandwidth and user selection. A rule written for one therefore
|
|
35
|
+
* composes into the other unchanged.
|
|
36
|
+
*
|
|
37
|
+
* Both are config-driven, each per-type export wiring a sensible default: audio's
|
|
38
|
+
* three-tier language policy, and for video the *empty* chain — with nothing
|
|
39
|
+
* narrowing or reordering, the head is the first candidate. The behavior's
|
|
40
|
+
* `config` is forwarded to the rules, so options like `preferredAudioLanguage`
|
|
41
|
+
* reach them without an intermediate layer.
|
|
42
|
+
*
|
|
43
|
+
* Note a rule can only pick among real candidates, where the picker it replaced
|
|
44
|
+
* could return any id at all. An id absent from the manifest was never
|
|
45
|
+
* selectable, so that narrowing is the point rather than a limitation.
|
|
46
|
+
*
|
|
47
|
+
* Compose `selectVideoTrack` for the simple "pick a default video track"
|
|
48
|
+
* behavior, or `switchVideoTrack` (`./track-switching.ts`) for the
|
|
49
|
+
* ABR-driven variant. Compose `selectAudioTrack` for the simple default
|
|
50
|
+
* pick, or `switchAudioTrack` (`./track-switching.ts`) for the
|
|
51
|
+
* filter-reactive + mid-stream-flush slot-owner variant — when audio-abr
|
|
52
|
+
* lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose
|
|
53
|
+
* only one per type — they're alternatives, not stackable (each writes
|
|
54
|
+
* the same `selected*TrackId` slot). The simple variants tree-shake out
|
|
55
|
+
* the heavier machinery (bandwidth estimator, quality selection, flush
|
|
56
|
+
* orchestration).
|
|
57
|
+
*
|
|
58
|
+
* Text selection has no simple variant here — it's owned by `switchTextTrack`
|
|
59
|
+
* (`./track-switching.ts`), which resolves standing `userTextTrackSelection`
|
|
60
|
+
* intent against the constrained, CDN-scoped renditions.
|
|
61
|
+
*/
|
|
62
|
+
function setupTrackSelection({ state, config: { selectedKey, trackType, constraints, rules, ruleConfig } }) {
|
|
63
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
64
|
+
const deps = {
|
|
65
|
+
state,
|
|
66
|
+
config: ruleConfig
|
|
67
|
+
};
|
|
68
|
+
const candidateSet = computed(() => {
|
|
69
|
+
const presentation = state.presentation.get();
|
|
70
|
+
if (!isResolvedPresentation(presentation)) return [];
|
|
71
|
+
return applyConstraints(constraints, getTracksByType(presentation, trackType), deps);
|
|
72
|
+
}, { equals: sameCandidateSet });
|
|
73
|
+
return createMachineReactor({
|
|
74
|
+
initial: "presentation-unresolved",
|
|
75
|
+
monitor: () => derivedStateSignal.get(),
|
|
76
|
+
states: {
|
|
77
|
+
"presentation-unresolved": {},
|
|
78
|
+
"presentation-resolved": {
|
|
79
|
+
entry: () => {
|
|
80
|
+
if (!state[selectedKey].get()) {
|
|
81
|
+
const id = applyRules(rules, peek(candidateSet), deps)[0]?.id;
|
|
82
|
+
if (id) state[selectedKey].set(id);
|
|
83
|
+
}
|
|
84
|
+
return () => state[selectedKey].set(void 0);
|
|
85
|
+
},
|
|
86
|
+
effects: [() => {
|
|
87
|
+
const selectedId = peek(state[selectedKey]);
|
|
88
|
+
if (!selectedId) return;
|
|
89
|
+
if (candidateSet.get().some((track) => track.id === selectedId)) return;
|
|
90
|
+
const presentation = peek(state.presentation);
|
|
91
|
+
if (isResolvedPresentation(presentation) && getTracksByType(presentation, trackType).some((track) => track.id === selectedId)) state[selectedKey].set(void 0);
|
|
92
|
+
}]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** Default video chain: none. The first candidate is the pick. */
|
|
98
|
+
const DEFAULT_VIDEO_RULES = [];
|
|
99
|
+
/**
|
|
100
|
+
* Default audio chain: the three-tier policy (`preferredAudioLanguage` →
|
|
101
|
+
* `DEFAULT=YES` → first) as a single narrowing rule. Returning `[]` when nothing
|
|
102
|
+
* is picked lets `applyRules` fall through to the unnarrowed candidates, so the
|
|
103
|
+
* head stays the first track — the same last tier the policy itself ends on.
|
|
104
|
+
*/
|
|
105
|
+
const preferAudioPolicy = (tracks, { config }) => {
|
|
106
|
+
const id = pickAudioTrackFromTracks(tracks, config);
|
|
107
|
+
const pick = tracks.find((track) => track.id === id);
|
|
108
|
+
return pick ? [pick] : [];
|
|
109
|
+
};
|
|
110
|
+
const DEFAULT_AUDIO_RULES = [preferAudioPolicy];
|
|
111
|
+
/**
|
|
112
|
+
* Order the candidates by resolution, largest first, with bandwidth breaking ties
|
|
113
|
+
* between renditions of identical dimensions. The background-video default — that
|
|
114
|
+
* variant pins one rendition for the session, and absent a cap the largest is the
|
|
115
|
+
* head.
|
|
116
|
+
*
|
|
117
|
+
* A ranker, so it reorders rather than narrowing: the chain's pick is the head of
|
|
118
|
+
* what it returns, which means ranking never has to collapse to one track. Belongs
|
|
119
|
+
* last in a chain — a sort only reorders what survived the filters ahead of it, and
|
|
120
|
+
* leaving it last is what lets `applyRules` early-bail before it runs.
|
|
121
|
+
*
|
|
122
|
+
* Exported because it is a *rule*, not a variant's private policy: the same one
|
|
123
|
+
* composes into `switchVideoTrack`'s chain when a ranker is wanted there.
|
|
124
|
+
*/
|
|
125
|
+
const preferHighestResolution = (tracks) => [...tracks].sort(byDescendingResolution);
|
|
126
|
+
/**
|
|
127
|
+
* Narrow to the renditions that fit the screen, by pixel area — the screen-size
|
|
128
|
+
* cap from `internal/design/spf/features/rendition-selection-caps.md`, as a scope
|
|
129
|
+
* (soft filter) rather than a constraint: an over-cap rendition is wasteful, not
|
|
130
|
+
* unplayable, so nothing here may make a source unplayable.
|
|
131
|
+
*
|
|
132
|
+
* Narrows only — it neither orders the survivors nor resolves the case where none
|
|
133
|
+
* survive, because `applyRules` owns both. So it needs a ranker behind it to pick
|
|
134
|
+
* within the cap: `[screenResolutionCap, preferHighestResolution]` yields the
|
|
135
|
+
* largest rendition that fits. Composed *last*, the pick would instead be whichever
|
|
136
|
+
* fitting rendition the manifest happened to list first.
|
|
137
|
+
*
|
|
138
|
+
* Reading `state.screenResolution` through its signal is what subscribes a
|
|
139
|
+
* re-evaluating chain (`switchVideoTrack`) to screen changes; `selectVideoTrack`
|
|
140
|
+
* pins the first answer instead, by design.
|
|
141
|
+
*
|
|
142
|
+
* Compares areas rather than matching a `"1080p"`-style tier because a tier only
|
|
143
|
+
* describes a rendition once you assume its aspect ratio — the assumption that
|
|
144
|
+
* mis-measures an anamorphic ladder. See `media/dom/screen.ts`.
|
|
145
|
+
*
|
|
146
|
+
* Three ways the cap ends up not applying, all of them fall-through:
|
|
147
|
+
*
|
|
148
|
+
* - **No `screenResolution` signal at all**, because the composition omits
|
|
149
|
+
* `trackScreenResolution`. So composing the cap without its signal source is
|
|
150
|
+
* inert rather than broken.
|
|
151
|
+
* - **A `screenResolution` of `undefined`**, meaning no screen to read. "Unknown"
|
|
152
|
+
* has to mean "don't cap": treating it as an area of zero would pin every source
|
|
153
|
+
* to its smallest rendition on exactly the environments we know least about.
|
|
154
|
+
* - **No rendition fits**, on a screen smaller than the whole ladder. `applyRules`
|
|
155
|
+
* skips the empty result and the chain proceeds unnarrowed, so the ranker behind
|
|
156
|
+
* the cap decides — for `preferHighestResolution`, the largest rendition. A floor
|
|
157
|
+
* is the fix if that ever matters (`rendition-selection-caps.md` carries one), not
|
|
158
|
+
* a special case here.
|
|
159
|
+
*/
|
|
160
|
+
const screenResolutionCap = (tracks, { state }) => {
|
|
161
|
+
const screenResolution = state?.screenResolution?.get();
|
|
162
|
+
if (!screenResolution) return [];
|
|
163
|
+
return tracksUnderPixelArea(tracks, screenResolution.width * screenResolution.height);
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Default video constraints: the capability pre-pass alone. No
|
|
167
|
+
* `excludeFailedCdns` — this variant's compositions run no failover monitor, so
|
|
168
|
+
* `failedCdns` has no writer and the constraint would always pass through.
|
|
169
|
+
*/
|
|
170
|
+
const DEFAULT_VIDEO_CONSTRAINTS = [excludeUnplayableTracks];
|
|
171
|
+
/**
|
|
172
|
+
* Select a video track when a presentation loads. Clears the selection on
|
|
173
|
+
* src unload.
|
|
174
|
+
*
|
|
175
|
+
* This is the simple, non-ABR counterpart to `switchVideoTrack` — compose
|
|
176
|
+
* one or the other, not both (both write `selectedVideoTrackId`). Composing
|
|
177
|
+
* `selectVideoTrack` alone tree-shakes out the ABR code path
|
|
178
|
+
* (bandwidth-estimator, quality-selection); use it for sources without
|
|
179
|
+
* meaningful quality variants, test setups, or players that intentionally
|
|
180
|
+
* pin a quality.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* const reactor = selectVideoTrack.setup({ state });
|
|
184
|
+
*/
|
|
185
|
+
const selectVideoTrack = defineBehavior({
|
|
186
|
+
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
187
|
+
contextKeys: [],
|
|
188
|
+
setup: ({ state, config }) => setupTrackSelection({
|
|
189
|
+
state,
|
|
190
|
+
config: {
|
|
191
|
+
selectedKey: VIDEO_TYPE_CONFIG.selectedKey,
|
|
192
|
+
trackType: "video",
|
|
193
|
+
constraints: config?.constraints ?? DEFAULT_VIDEO_CONSTRAINTS,
|
|
194
|
+
rules: config?.rules ?? DEFAULT_VIDEO_RULES,
|
|
195
|
+
ruleConfig: config
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
defineBehavior({
|
|
200
|
+
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
201
|
+
contextKeys: [],
|
|
202
|
+
setup: ({ state, config }) => setupTrackSelection({
|
|
203
|
+
state,
|
|
204
|
+
config: {
|
|
205
|
+
selectedKey: AUDIO_TYPE_CONFIG.selectedKey,
|
|
206
|
+
trackType: "audio",
|
|
207
|
+
constraints: config?.constraints ?? [],
|
|
208
|
+
rules: config?.rules ?? DEFAULT_AUDIO_RULES,
|
|
209
|
+
ruleConfig: config
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
});
|
|
213
|
+
//#endregion
|
|
214
|
+
export { preferHighestResolution, screenResolutionCap, selectVideoTrack };
|
|
215
|
+
|
|
216
|
+
//# sourceMappingURL=select-tracks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-tracks.js","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"sourcesContent":["/**\n * **Default audio/video track selection on src load / unselect on src unload.**\n * When a presentation is resolved, sets `selectedVideoTrackId` /\n * `selectedAudioTrackId` from a per-type default rule chain if no selection\n * already 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: the pick fires once per transition, and nothing re-picks —\n * that is what separates these from the `switch*` variants. External writes (user\n * picks, ABR, programmatic filter-driven re-picks) are left alone, including a\n * write naming a track the manifest never offered.\n *\n * The one thing policed between transitions is a pick the *constraints* turn\n * against: a rendition's container and encryption are only known once its media\n * playlist resolves, which is after the pick was made, so a selection that becomes\n * unplayable is dropped. Dropped, never moved — re-picking is exactly the behavior\n * `switchVideoTrack` exists to provide. Dropping reports nothing on its own, since\n * whatever made the pick unplayable already reported its own, more specific cause.\n *\n * Selection runs the same rule model `switchVideoTrack` does — a hard\n * `constraints` pre-pass, then an ordered `rules` chain, with the pick as the\n * head (see `internal/design/spf/track-switching-model.md`). What differs is\n * reactivity, not the rules: this evaluates the chain once on resolve and pins\n * the result, where `switchVideoTrack` re-evaluates inside an effect so its rules\n * subscribe to bandwidth and user selection. A rule written for one therefore\n * composes into the other unchanged.\n *\n * Both are config-driven, each per-type export wiring a sensible default: audio's\n * three-tier language policy, and for video the *empty* chain — with nothing\n * narrowing or reordering, the head is the first candidate. The behavior's\n * `config` is forwarded to the rules, so options like `preferredAudioLanguage`\n * reach them without an intermediate layer.\n *\n * Note a rule can only pick among real candidates, where the picker it replaced\n * could return any id at all. An id absent from the manifest was never\n * selectable, so that narrowing is the point rather than a limitation.\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, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport type { Resolution } from '../../media/primitives/resolution';\nimport {\n type AudioSelectionConfig,\n byDescendingResolution,\n pickAudioTrackFromTracks,\n type TrackSelectionState,\n tracksUnderPixelArea,\n} from '../../media/primitives/select-tracks';\nimport { isResolvedPresentation, type TrackType } from '../../media/types';\nimport { getTracksByType } from '../../media/utils/tracks';\nimport {\n applyConstraints,\n applyRules,\n type CapabilityConstraintConfig,\n excludeUnplayableTracks,\n type SelectionRule,\n sameCandidateSet,\n} from '../primitives/selection-rules';\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`, track type, default rule chain, and forwarded 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\n/** A selection rule over this behavior's candidate tracks. */\nexport type SelectTrackRule<Config> = SelectionRule<SelectableTrack, unknown, unknown, Config | undefined>;\n\n/** What a rule here needs off a candidate: the id it may become the pick by. */\ntype SelectableTrack = { id: string };\n\ninterface TrackSelectionSetupConfig<K extends SelectedTrackKey, RuleConfig> {\n selectedKey: K;\n trackType: TrackType;\n constraints: readonly SelectTrackRule<RuleConfig>[];\n rules: readonly SelectTrackRule<RuleConfig>[];\n ruleConfig?: RuleConfig;\n}\n\nfunction setupTrackSelection<K extends SelectedTrackKey, RuleConfig>({\n state,\n config: { selectedKey, trackType, constraints, rules, ruleConfig },\n}: {\n state: SelectStateMap<K>;\n config: TrackSelectionSetupConfig<K, RuleConfig>;\n}) {\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n const deps = { state, config: ruleConfig };\n\n // The playable candidate set — the type's tracks after the hard-constraints\n // pre-pass. A `computed` so it re-evaluates when its inputs change, which is what\n // lets a *pinned* selection still notice it has gone unplayable: `resolve-track`\n // relabels the whole type's container from the first resolved media playlist,\n // long after `entry` made its pick under the fMP4 default.\n //\n // The `equals` gates notification on the set of track ids rather than array\n // identity, matching `setupTrackSwitching`'s. Segment appends and live reloads\n // both swap in a new presentation object carrying the same variants; without\n // this the effect below would re-run on every one of them.\n const candidateSet = computed(\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return [];\n return applyConstraints(constraints, getTracksByType(presentation, trackType), deps);\n },\n { equals: sameCandidateSet }\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 //\n // Constraints prune the unplayable, then the chain narrows and ranks;\n // the pick is the head. An empty `rules` chain therefore selects the\n // first candidate, which falls out of the model rather than needing a\n // first-track code path of its own.\n const survivors = applyRules(rules, peek(candidateSet), deps);\n const id = survivors[0]?.id;\n if (id) state[selectedKey].set(id);\n }\n return () => state[selectedKey].set(undefined);\n },\n effects: [\n // Selection is `entry`'s alone — this only ever *de*selects. Keeping the\n // pick out of a reaction is what makes this the pinned variant rather\n // than a worse-spelled `switchVideoTrack`: nothing here re-ranks or moves\n // the pin. It has to be a reaction all the same, because what it watches\n // for is learned late — container and encryption come from a rendition's\n // media playlist, which resolves after the pick was made.\n //\n // Deliberately does *not* report why. Whatever made the pick unplayable\n // reported its own cause as the playlist resolved (1004 container, 4008\n // encryption, via `reportUnsupportedTrackConditions`), which is both more\n // specific than a verdict and already logged. The one condition no cause\n // covers is nothing being playable at all — no rendition resolved, so\n // none reported — which is why that alone emits here. See\n // `internal/design/spf/features/errors.md`.\n () => {\n // Untracked: writing the slot below must not re-enter this reaction.\n const selectedId = peek(state[selectedKey]);\n if (!selectedId) return;\n if (candidateSet.get().some((track) => track.id === selectedId)) return;\n\n // Only a pick the source actually offers is this behavior's to drop. An\n // id absent from the manifest was never selectable, and external writes\n // are left alone — see this module's header.\n const presentation = peek(state.presentation);\n if (\n isResolvedPresentation(presentation) &&\n getTracksByType(presentation, trackType).some((track) => track.id === selectedId)\n ) {\n state[selectedKey].set(undefined);\n }\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Default rules\n//\n// Each variant resolves its chain as `config?.rules ?? <default>`. The whole\n// behavior config is forwarded as the rules' `config`, so a policy rule reads\n// its own options (`preferredAudioLanguage`) directly off it.\n//\n// Video's default is the *empty* chain: with no rule narrowing or reordering the\n// candidates, the head is the first track — a consequence of the model rather than\n// a first-track code path of its own.\n// ============================================================================\n\n/** Default video chain: none. The first candidate is the pick. */\nconst DEFAULT_VIDEO_RULES: readonly SelectTrackRule<SelectVideoTrackConfig>[] = [];\n\n/**\n * Default audio chain: the three-tier policy (`preferredAudioLanguage` →\n * `DEFAULT=YES` → first) as a single narrowing rule. Returning `[]` when nothing\n * is picked lets `applyRules` fall through to the unnarrowed candidates, so the\n * head stays the first track — the same last tier the policy itself ends on.\n */\nconst preferAudioPolicy: SelectTrackRule<SelectAudioTrackConfig> = (tracks, { config }) => {\n const id = pickAudioTrackFromTracks(tracks as readonly { id: string }[], config);\n const pick = tracks.find((track) => track.id === id);\n return pick ? [pick] : [];\n};\n\nconst DEFAULT_AUDIO_RULES: readonly SelectTrackRule<SelectAudioTrackConfig>[] = [preferAudioPolicy];\n\n/**\n * Order the candidates by resolution, largest first, with bandwidth breaking ties\n * between renditions of identical dimensions. The background-video default — that\n * variant pins one rendition for the session, and absent a cap the largest is the\n * head.\n *\n * A ranker, so it reorders rather than narrowing: the chain's pick is the head of\n * what it returns, which means ranking never has to collapse to one track. Belongs\n * last in a chain — a sort only reorders what survived the filters ahead of it, and\n * leaving it last is what lets `applyRules` early-bail before it runs.\n *\n * Exported because it is a *rule*, not a variant's private policy: the same one\n * composes into `switchVideoTrack`'s chain when a ranker is wanted there.\n */\nexport const preferHighestResolution: SelectTrackRule<unknown> = (tracks) => [...tracks].sort(byDescendingResolution);\n\n/** What {@link screenResolutionCap} reads off the composition state. */\ntype ScreenResolutionRuleState = {\n screenResolution?: ReadonlySignal<Resolution | undefined>;\n};\n\n/**\n * Narrow to the renditions that fit the screen, by pixel area — the screen-size\n * cap from `internal/design/spf/features/rendition-selection-caps.md`, as a scope\n * (soft filter) rather than a constraint: an over-cap rendition is wasteful, not\n * unplayable, so nothing here may make a source unplayable.\n *\n * Narrows only — it neither orders the survivors nor resolves the case where none\n * survive, because `applyRules` owns both. So it needs a ranker behind it to pick\n * within the cap: `[screenResolutionCap, preferHighestResolution]` yields the\n * largest rendition that fits. Composed *last*, the pick would instead be whichever\n * fitting rendition the manifest happened to list first.\n *\n * Reading `state.screenResolution` through its signal is what subscribes a\n * re-evaluating chain (`switchVideoTrack`) to screen changes; `selectVideoTrack`\n * pins the first answer instead, by design.\n *\n * Compares areas rather than matching a `\"1080p\"`-style tier because a tier only\n * describes a rendition once you assume its aspect ratio — the assumption that\n * mis-measures an anamorphic ladder. See `media/dom/screen.ts`.\n *\n * Three ways the cap ends up not applying, all of them fall-through:\n *\n * - **No `screenResolution` signal at all**, because the composition omits\n * `trackScreenResolution`. So composing the cap without its signal source is\n * inert rather than broken.\n * - **A `screenResolution` of `undefined`**, meaning no screen to read. \"Unknown\"\n * has to mean \"don't cap\": treating it as an area of zero would pin every source\n * to its smallest rendition on exactly the environments we know least about.\n * - **No rendition fits**, on a screen smaller than the whole ladder. `applyRules`\n * skips the empty result and the chain proceeds unnarrowed, so the ranker behind\n * the cap decides — for `preferHighestResolution`, the largest rendition. A floor\n * is the fix if that ever matters (`rendition-selection-caps.md` carries one), not\n * a special case here.\n */\nexport const screenResolutionCap: SelectTrackRule<unknown> = (tracks, { state }) => {\n const screenResolution = (state as ScreenResolutionRuleState | undefined)?.screenResolution?.get();\n if (!screenResolution) return [];\n\n return tracksUnderPixelArea(tracks, screenResolution.width * screenResolution.height);\n};\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Config for `selectVideoTrack`. Pass `rules` to replace the selection chain, or\n * `constraints` to replace the capability pre-pass; otherwise the chain is empty\n * and the first playable candidate is the pick.\n */\nexport interface SelectVideoTrackConfig extends CapabilityConstraintConfig {\n constraints?: readonly SelectTrackRule<SelectVideoTrackConfig>[];\n rules?: readonly SelectTrackRule<SelectVideoTrackConfig>[];\n}\n\n/**\n * Default video constraints: the capability pre-pass alone. No\n * `excludeFailedCdns` — this variant's compositions run no failover monitor, so\n * `failedCdns` has no writer and the constraint would always pass through.\n */\nconst DEFAULT_VIDEO_CONSTRAINTS: readonly SelectTrackRule<SelectVideoTrackConfig>[] = [excludeUnplayableTracks];\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 trackType: 'video',\n constraints: config?.constraints ?? DEFAULT_VIDEO_CONSTRAINTS,\n rules: config?.rules ?? DEFAULT_VIDEO_RULES,\n ruleConfig: config,\n },\n }),\n});\n\n/**\n * Config for `selectAudioTrack`. Pass `rules` to replace the selection chain, or\n * `constraints` to prune candidates before it runs; otherwise the default\n * three-tier policy applies (`preferredAudioLanguage` → `DEFAULT=YES` → first).\n */\nexport interface SelectAudioTrackConfig extends AudioSelectionConfig {\n constraints?: readonly SelectTrackRule<SelectAudioTrackConfig>[];\n rules?: readonly SelectTrackRule<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 * // Language preference, honored by the default audio policy rule\n * const reactor = selectAudioTrack.setup({\n * state,\n * config: { preferredAudioLanguage: 'en' },\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 trackType: 'audio',\n constraints: config?.constraints ?? [],\n rules: config?.rules ?? DEFAULT_AUDIO_RULES,\n ruleConfig: config,\n },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GA,SAAS,oBAA4D,EACnE,OACA,QAAQ,EAAE,aAAa,WAAW,aAAa,OAAO,gBAIrD;CACD,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;CAEA,MAAM,OAAO;EAAE;EAAO,QAAQ;CAAW;CAYzC,MAAM,eAAe,eACb;EACJ,MAAM,eAAe,MAAM,aAAa,IAAI;EAC5C,IAAI,CAAC,uBAAuB,YAAY,GAAG,OAAO,CAAC;EACnD,OAAO,iBAAiB,aAAa,gBAAgB,cAAc,SAAS,GAAG,IAAI;CACrF,GACA,EAAE,QAAQ,iBAAiB,CAC7B;CAEA,OAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,2BAA2B,CAAC;GAC5B,yBAAyB;IAYvB,aAAa;KACX,IAAI,CAAC,MAAM,YAAY,CAAC,IAAI,GAAG;MAW7B,MAAM,KADY,WAAW,OAAO,KAAK,YAAY,GAAG,IACrC,CAAC,CAAC,EAAE,EAAE;MACzB,IAAI,IAAI,MAAM,YAAY,CAAC,IAAI,EAAE;KACnC;KACA,aAAa,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;IAC/C;IACA,SAAS,OAeD;KAEJ,MAAM,aAAa,KAAK,MAAM,YAAY;KAC1C,IAAI,CAAC,YAAY;KACjB,IAAI,aAAa,IAAI,CAAC,CAAC,MAAM,UAAU,MAAM,OAAO,UAAU,GAAG;KAKjE,MAAM,eAAe,KAAK,MAAM,YAAY;KAC5C,IACE,uBAAuB,YAAY,KACnC,gBAAgB,cAAc,SAAS,CAAC,CAAC,MAAM,UAAU,MAAM,OAAO,UAAU,GAEhF,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;IAEpC,CACF;GACF;EACF;CACF,CAAC;AACH;;AAeA,MAAM,sBAA0E,CAAC;;;;;;;AAQjF,MAAM,qBAA8D,QAAQ,EAAE,aAAa;CACzF,MAAM,KAAK,yBAAyB,QAAqC,MAAM;CAC/E,MAAM,OAAO,OAAO,MAAM,UAAU,MAAM,OAAO,EAAE;CACnD,OAAO,OAAO,CAAC,IAAI,IAAI,CAAC;AAC1B;AAEA,MAAM,sBAA0E,CAAC,iBAAiB;;;;;;;;;;;;;;;AAgBlG,MAAa,2BAAqD,WAAW,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCpH,MAAa,uBAAiD,QAAQ,EAAE,YAAY;CAClF,MAAM,mBAAoB,OAAiD,kBAAkB,IAAI;CACjG,IAAI,CAAC,kBAAkB,OAAO,CAAC;CAE/B,OAAO,qBAAqB,QAAQ,iBAAiB,QAAQ,iBAAiB,MAAM;AACtF;;;;;;AAqBA,MAAM,4BAAgF,CAAC,uBAAuB;;;;;;;;;;;;;;;AAgB9G,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,WAAW;GACX,aAAa,QAAQ,eAAe;GACpC,OAAO,QAAQ,SAAS;GACxB,YAAY;EACd;CACF,CAAC;AACL,CAAC;AAmC+B,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,WAAW;GACX,aAAa,QAAQ,eAAe,CAAC;GACrC,OAAO,QAAQ,SAAS;GACxB,YAAY;EACd;CACF,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "../../core/signals/primitives.js";
|
|
2
|
+
import "../../index.js";
|
|
3
|
+
//#region src/playback/behaviors/setup-failover-monitor.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Failover policy: how long a CDN stays excluded after a failed fetch trips it.
|
|
6
|
+
* Supplied via engine config.
|
|
7
|
+
*/
|
|
8
|
+
interface FailoverMonitorConfig {
|
|
9
|
+
/** How long a tripped CDN stays excluded, in milliseconds. */
|
|
10
|
+
cooldownMs: number;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { FailoverMonitorConfig };
|
|
14
|
+
//# sourceMappingURL=setup-failover-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-failover-monitor.d.ts","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"mappings":";;;;;;;UA0BiB;;EAEf"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { computed, update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedPresentation } from "../../media/types/index.js";
|
|
5
|
+
//#region src/playback/behaviors/setup-failover-monitor.ts
|
|
6
|
+
/**
|
|
7
|
+
* **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites
|
|
8
|
+
* own the *trip*: on a failed fetch they add the failing CDN (origin) to the
|
|
9
|
+
* `failedCdns` state signal directly. This behavior owns the *expiry*: while a
|
|
10
|
+
* presentation is resolved, it watches `failedCdns` and, for each CDN that
|
|
11
|
+
* appears, schedules a timer to remove it once its cooldown lapses.
|
|
12
|
+
* `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's
|
|
13
|
+
* tracks and the active-CDN scope falls to the next one — and back, once the
|
|
14
|
+
* cooldown removes it here.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new
|
|
17
|
+
* source starts with a clean slate). Policy (cooldown) is engine config. This is
|
|
18
|
+
* the minimal `network-resilience` slice — a single failure trips a CDN, since
|
|
19
|
+
* transient blips are the retry layer's job (it sits below the fetch sites, so
|
|
20
|
+
* anything that reaches `failedCdns` is already terminal).
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_FAILOVER_MONITOR_CONFIG = { cooldownMs: 3e5 };
|
|
23
|
+
/**
|
|
24
|
+
* Expire failed CDNs from `failedCdns` once their cooldown lapses, for the
|
|
25
|
+
* resolved source.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const reactor = setupFailoverMonitor.setup({ state });
|
|
29
|
+
*/
|
|
30
|
+
const setupFailoverMonitor = defineBehavior({
|
|
31
|
+
stateKeys: ["presentation", "failedCdns"],
|
|
32
|
+
contextKeys: [],
|
|
33
|
+
setup: ({ state, config = {} }) => {
|
|
34
|
+
const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;
|
|
35
|
+
const timers = /* @__PURE__ */ new Map();
|
|
36
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
37
|
+
return createMachineReactor({
|
|
38
|
+
initial: "presentation-unresolved",
|
|
39
|
+
monitor: () => derivedStateSignal.get(),
|
|
40
|
+
states: {
|
|
41
|
+
"presentation-unresolved": {},
|
|
42
|
+
"presentation-resolved": {
|
|
43
|
+
entry: () => () => {
|
|
44
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
45
|
+
timers.clear();
|
|
46
|
+
state.failedCdns.set(void 0);
|
|
47
|
+
},
|
|
48
|
+
effects: [() => {
|
|
49
|
+
(state.failedCdns.get() ?? []).forEach((cdn) => {
|
|
50
|
+
if (timers.has(cdn)) return;
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
timers.delete(cdn);
|
|
53
|
+
update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));
|
|
54
|
+
}, cooldownMs);
|
|
55
|
+
timers.set(cdn, timer);
|
|
56
|
+
});
|
|
57
|
+
}]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
//#endregion
|
|
64
|
+
export { DEFAULT_FAILOVER_MONITOR_CONFIG, setupFailoverMonitor };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=setup-failover-monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-failover-monitor.js","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"sourcesContent":["/**\n * **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites\n * own the *trip*: on a failed fetch they add the failing CDN (origin) to the\n * `failedCdns` state signal directly. This behavior owns the *expiry*: while a\n * presentation is resolved, it watches `failedCdns` and, for each CDN that\n * appears, schedules a timer to remove it once its cooldown lapses.\n * `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's\n * tracks and the active-CDN scope falls to the next one — and back, once the\n * cooldown removes it here.\n *\n * Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new\n * source starts with a clean slate). Policy (cooldown) is engine config. This is\n * the minimal `network-resilience` slice — a single failure trips a CDN, since\n * transient blips are the retry layer's job (it sits below the fetch sites, so\n * anything that reaches `failedCdns` is already terminal).\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\n\n/**\n * Failover policy: how long a CDN stays excluded after a failed fetch trips it.\n * Supplied via engine config.\n */\nexport interface FailoverMonitorConfig {\n /** How long a tripped CDN stays excluded, in milliseconds. */\n cooldownMs: number;\n}\n\nexport const DEFAULT_FAILOVER_MONITOR_CONFIG: FailoverMonitorConfig = {\n // 5 minutes — a CDN outage is an infrastructure problem that outlasts a\n // transient blip, so re-probing it sooner mostly re-trips. Matches the\n // prevailing prior-art default (ExoPlayer's location exclusion, hls.js's\n // content-steering penalty box).\n cooldownMs: 300_000,\n};\n\nexport interface SetupFailoverMonitorState {\n presentation?: MaybeResolvedPresentation;\n failedCdns?: string[];\n}\n\nexport interface SetupFailoverMonitorConfig {\n /** Failover policy (cooldown); defaults to `DEFAULT_FAILOVER_MONITOR_CONFIG`. */\n failover?: Partial<FailoverMonitorConfig>;\n}\n\n/**\n * Expire failed CDNs from `failedCdns` once their cooldown lapses, for the\n * resolved source.\n *\n * @example\n * const reactor = setupFailoverMonitor.setup({ state });\n */\nexport const setupFailoverMonitor = defineBehavior({\n stateKeys: ['presentation', 'failedCdns'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: {\n presentation: ReadonlySignal<SetupFailoverMonitorState['presentation']>;\n failedCdns: Signal<SetupFailoverMonitorState['failedCdns']>;\n };\n config?: SetupFailoverMonitorConfig;\n }) => {\n const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;\n // CDN id → its pending cooldown-removal timer. Shared by the `effects`\n // scheduler (adds a timer per newly-failed CDN) and the exit cleanup\n // (clears them). Per-source: emptied on exit, so it re-enters clean.\n const timers = new Map<string, ReturnType<typeof setTimeout>>();\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Cleanup-binds-to-setup: on exit (src unload + destroy) clear the\n // pending timers and reset `failedCdns` for the next source.\n entry: () => () => {\n timers.forEach((timer) => clearTimeout(timer));\n timers.clear();\n state.failedCdns.set(undefined);\n },\n effects: [\n () => {\n const failed = state.failedCdns.get() ?? [];\n failed.forEach((cdn) => {\n // Idempotent: a CDN already counting down keeps its original\n // deadline (re-failing it mid-cooldown doesn't extend it).\n if (timers.has(cdn)) return;\n const timer = setTimeout(() => {\n timers.delete(cdn);\n update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));\n }, cooldownMs);\n timers.set(cdn, timer);\n });\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,kCAAyD,EAKpE,YAAY,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"}
|