@videojs/spf 10.0.0-beta.9 → 10.0.0-rc.2
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 +89 -0
- package/dist/default/core/actors/create-machine-actor.js.map +1 -0
- package/dist/default/core/actors/create-transition-actor.js +44 -0
- package/dist/default/core/actors/create-transition-actor.js.map +1 -0
- package/dist/default/core/composition/create-composition.js +82 -0
- package/dist/default/core/composition/create-composition.js.map +1 -0
- package/dist/default/core/composition/share-signals.js +34 -0
- package/dist/default/core/composition/share-signals.js.map +1 -0
- package/dist/default/core/machine.js +25 -0
- package/dist/default/core/machine.js.map +1 -0
- package/dist/default/core/reactors/create-machine-reactor.js +84 -0
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/default/core/signals/effect.js +61 -0
- package/dist/default/core/signals/effect.js.map +1 -0
- package/dist/default/core/signals/primitives.js +50 -0
- package/dist/default/core/signals/primitives.js.map +1 -0
- package/dist/default/core/signals/when.js +36 -0
- package/dist/default/core/signals/when.js.map +1 -0
- package/dist/default/core/tasks/delayed-reschedule.js +27 -0
- package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/default/core/tasks/task.js +298 -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 +10 -0
- package/dist/default/index.js +22 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/media/abr/quality-selection.js +17 -0
- package/dist/default/media/abr/quality-selection.js.map +1 -0
- package/dist/default/media/buffer/back-buffer.js +42 -0
- package/dist/default/media/buffer/back-buffer.js.map +1 -0
- package/dist/default/media/buffer/forward-buffer.js +110 -0
- package/dist/default/media/buffer/forward-buffer.js.map +1 -0
- package/dist/default/media/dom/capabilities.js +61 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/default/media/dom/mse/append-segment.js +58 -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 +87 -0
- package/dist/default/media/dom/mse/duration.js.map +1 -0
- package/dist/default/media/dom/mse/end-of-stream.js +17 -0
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/default/media/dom/mse/mediasource-setup.js +227 -0
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/default/media/dom/screen.js +95 -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 +61 -0
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/default/media/errors.js +60 -0
- package/dist/default/media/errors.js.map +1 -0
- package/dist/default/media/hls/parse-attributes.js +131 -0
- package/dist/default/media/hls/parse-attributes.js.map +1 -0
- package/dist/default/media/hls/parse-media-playlist.js +253 -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 +63 -0
- package/dist/default/media/hls/reload-policy.js.map +1 -0
- package/dist/default/media/hls/resolve-url.js +9 -0
- package/dist/default/media/hls/resolve-url.js.map +1 -0
- package/dist/default/media/live-window.js +32 -0
- package/dist/default/media/live-window.js.map +1 -0
- package/dist/default/media/media-tracks/media-tracks.js +96 -0
- package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/default/media/mp4/box.js +57 -0
- package/dist/default/media/mp4/box.js.map +1 -0
- package/dist/default/media/mp4/timestamp-origin.js +95 -0
- package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/default/media/primitives/resolution.js +26 -0
- package/dist/default/media/primitives/resolution.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js +97 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js +40 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/default/media/text/resolve-vtt-metadata.js +22 -0
- package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/default/media/types/index.js +42 -0
- package/dist/default/media/types/index.js.map +1 -0
- package/dist/default/media/utils/cdn.js +50 -0
- package/dist/default/media/utils/cdn.js.map +1 -0
- package/dist/default/media/utils/preload.js +21 -0
- package/dist/default/media/utils/preload.js.map +1 -0
- package/dist/default/media/utils/track-selection.js +43 -0
- package/dist/default/media/utils/track-selection.js.map +1 -0
- package/dist/default/media/utils/tracks.js +146 -0
- package/dist/default/media/utils/tracks.js.map +1 -0
- package/dist/default/media-tracks.js +2 -0
- package/dist/default/network/bandwidth-estimator.js +95 -0
- package/dist/default/network/bandwidth-estimator.js.map +1 -0
- package/dist/default/network/chunked-stream-iterable.js +47 -0
- package/dist/default/network/chunked-stream-iterable.js.map +1 -0
- package/dist/default/network/ewma.js +68 -0
- package/dist/default/network/ewma.js.map +1 -0
- package/dist/default/network/fetch.js +108 -0
- package/dist/default/network/fetch.js.map +1 -0
- package/dist/default/playback/actors/dom/segment-loader.js +233 -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 +138 -0
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js +8 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/mixin.js +178 -0
- package/dist/default/playback/adapters/hls-audio/mixin.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js +17 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/host.js +56 -0
- package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/mixin.js +191 -0
- package/dist/default/playback/adapters/hls-background-video/mixin.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/adapter.js +11 -0
- package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js +46 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js +125 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/mixin.js +299 -0
- package/dist/default/playback/adapters/hls-video/mixin.js.map +1 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +33 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/default/playback/behaviors/collect-errors.js +98 -0
- package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js +61 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/default/playback/behaviors/dom/airplay.js +188 -0
- package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js +81 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js +97 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/default/playback/behaviors/dom/load-segments.js +169 -0
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/default/playback/behaviors/dom/recover-end-stall.js +72 -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 +113 -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 +181 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +100 -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 +25 -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 +109 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js +50 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +86 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +35 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js +53 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js +28 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +98 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js +187 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js +76 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-track.js +134 -0
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/default/playback/behaviors/select-tracks.js +189 -0
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js +61 -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 +407 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js +94 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-background-video.js +87 -0
- package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +144 -0
- package/dist/default/playback/engines/hls/engine.js.map +1 -0
- package/dist/default/playback/primitives/error-messages.js +42 -0
- package/dist/default/playback/primitives/error-messages.js.map +1 -0
- package/dist/default/playback/primitives/failover-fetch.js +37 -0
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/default/playback/primitives/head-peek.js +41 -0
- package/dist/default/playback/primitives/head-peek.js.map +1 -0
- package/dist/default/playback/primitives/live-window.js +63 -0
- package/dist/default/playback/primitives/live-window.js.map +1 -0
- package/dist/default/playback/primitives/relocation-pipelines.js +191 -0
- package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/default/playback/primitives/report-track-conditions.js +74 -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 +115 -0
- package/dist/default/playback/primitives/selection-rules.js.map +1 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js +36 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/track-types.js +53 -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 +119 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.js +89 -0
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts +37 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.js +44 -0
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
- package/dist/dev/core/composition/create-composition.d.ts +241 -0
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
- package/dist/dev/core/composition/create-composition.js +82 -0
- package/dist/dev/core/composition/create-composition.js.map +1 -0
- package/dist/dev/core/composition/share-signals.d.ts +39 -0
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
- package/dist/dev/core/composition/share-signals.js +34 -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 +25 -0
- package/dist/dev/core/machine.js.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +87 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.js +84 -0
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/dev/core/signals/effect.d.ts +14 -0
- package/dist/dev/core/signals/effect.d.ts.map +1 -0
- package/dist/dev/core/signals/effect.js +61 -0
- package/dist/dev/core/signals/effect.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +44 -0
- package/dist/dev/core/signals/primitives.d.ts.map +1 -0
- package/dist/dev/core/signals/primitives.js +50 -0
- package/dist/dev/core/signals/primitives.js.map +1 -0
- package/dist/dev/core/signals/when.js +36 -0
- package/dist/dev/core/signals/when.js.map +1 -0
- package/dist/dev/core/tasks/delayed-reschedule.js +27 -0
- package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/dev/core/tasks/task.d.ts +144 -0
- package/dist/dev/core/tasks/task.d.ts.map +1 -0
- package/dist/dev/core/tasks/task.js +298 -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 +11 -0
- package/dist/dev/hls.js +10 -0
- package/dist/dev/index.d.ts +23 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +22 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/media/abr/quality-selection.d.ts +19 -0
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
- package/dist/dev/media/abr/quality-selection.js +17 -0
- package/dist/dev/media/abr/quality-selection.js.map +1 -0
- package/dist/dev/media/buffer/back-buffer.d.ts +9 -0
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/back-buffer.js +42 -0
- package/dist/dev/media/buffer/back-buffer.js.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts +9 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.js +110 -0
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
- package/dist/dev/media/dom/capabilities.js +61 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts +16 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.js +58 -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 +87 -0
- package/dist/dev/media/dom/mse/duration.js.map +1 -0
- package/dist/dev/media/dom/mse/end-of-stream.js +17 -0
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js +227 -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 +95 -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 +24 -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 +61 -0
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/dev/media/errors.d.ts +68 -0
- package/dist/dev/media/errors.d.ts.map +1 -0
- package/dist/dev/media/errors.js +60 -0
- package/dist/dev/media/errors.js.map +1 -0
- package/dist/dev/media/hls/parse-attributes.js +131 -0
- package/dist/dev/media/hls/parse-attributes.js.map +1 -0
- package/dist/dev/media/hls/parse-media-playlist.js +253 -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 +63 -0
- package/dist/dev/media/hls/reload-policy.js.map +1 -0
- package/dist/dev/media/hls/resolve-url.js +9 -0
- package/dist/dev/media/hls/resolve-url.js.map +1 -0
- package/dist/dev/media/live-window.js +32 -0
- package/dist/dev/media/live-window.js.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts +46 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.js +96 -0
- package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/dev/media/mp4/box.js +57 -0
- package/dist/dev/media/mp4/box.js.map +1 -0
- package/dist/dev/media/mp4/timestamp-origin.js +95 -0
- package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/dev/media/primitives/resolution.d.ts +22 -0
- package/dist/dev/media/primitives/resolution.d.ts.map +1 -0
- package/dist/dev/media/primitives/resolution.js +26 -0
- package/dist/dev/media/primitives/resolution.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +97 -0
- package/dist/dev/media/primitives/select-tracks.js.map +1 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js +40 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js +22 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/dev/media/types/index.d.ts +306 -0
- package/dist/dev/media/types/index.d.ts.map +1 -0
- package/dist/dev/media/types/index.js +42 -0
- package/dist/dev/media/types/index.js.map +1 -0
- package/dist/dev/media/utils/cdn.d.ts +11 -0
- package/dist/dev/media/utils/cdn.d.ts.map +1 -0
- package/dist/dev/media/utils/cdn.js +50 -0
- package/dist/dev/media/utils/cdn.js.map +1 -0
- package/dist/dev/media/utils/preload.js +21 -0
- package/dist/dev/media/utils/preload.js.map +1 -0
- package/dist/dev/media/utils/track-selection.js +43 -0
- package/dist/dev/media/utils/track-selection.js.map +1 -0
- package/dist/dev/media/utils/tracks.js +146 -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/network/bandwidth-estimator.d.ts +48 -0
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
- package/dist/dev/network/bandwidth-estimator.js +95 -0
- package/dist/dev/network/bandwidth-estimator.js.map +1 -0
- package/dist/dev/network/chunked-stream-iterable.js +47 -0
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
- package/dist/dev/network/ewma.js +68 -0
- package/dist/dev/network/ewma.js.map +1 -0
- package/dist/dev/network/fetch.js +108 -0
- package/dist/dev/network/fetch.js.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +44 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.js +233 -0
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +45 -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 +48 -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 +138 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/text-tracks.d.ts +30 -0
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +11 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js +8 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.d.ts +52 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.js +178 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +19 -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 +17 -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 +34 -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 +56 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.d.ts +69 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.js +191 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts +11 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js +11 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +17 -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 +46 -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 +21 -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 +125 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/mixin.d.ts +62 -0
- package/dist/dev/playback/adapters/hls-video/mixin.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/mixin.js +299 -0
- package/dist/dev/playback/adapters/hls-video/mixin.js.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +23 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +33 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/collect-errors.d.ts +2 -0
- package/dist/dev/playback/behaviors/collect-errors.js +98 -0
- package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +61 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/airplay.js +188 -0
- package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js +81 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +97 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +69 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js +169 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js +72 -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 +113 -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 +181 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +100 -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 +25 -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 +109 -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 +50 -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 +41 -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 +86 -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 +35 -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 +53 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +28 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +98 -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 +32 -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 +187 -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 +76 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-track.js +134 -0
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts +61 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +189 -0
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +11 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +61 -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.d.ts +110 -0
- package/dist/dev/playback/behaviors/track-switching.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.js +407 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +160 -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 +94 -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 +131 -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 +87 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +289 -0
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine.js +144 -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 +42 -0
- package/dist/dev/playback/primitives/error-messages.js.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js +37 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/dev/playback/primitives/head-peek.js +41 -0
- package/dist/dev/playback/primitives/head-peek.js.map +1 -0
- package/dist/dev/playback/primitives/live-window.js +63 -0
- package/dist/dev/playback/primitives/live-window.js.map +1 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js +191 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts +8 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.js +74 -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 +66 -0
- package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -0
- package/dist/dev/playback/primitives/selection-rules.js +115 -0
- package/dist/dev/playback/primitives/selection-rules.js.map +1 -0
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts +40 -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 +56 -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 +36 -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 +53 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +60 -41
- package/dist/adapter-25AtMN8s.d.ts +0 -719
- package/dist/adapter-25AtMN8s.d.ts.map +0 -1
- package/dist/adapter-CflgYzjF.js +0 -3556
- package/dist/adapter-CflgYzjF.js.map +0 -1
- package/dist/dom/index.d.ts +0 -221
- 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,189 @@
|
|
|
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.** When a presentation is resolved, sets
|
|
12
|
+
* `selectedVideoTrackId` / `selectedAudioTrackId` from a per-type default rule chain if no selection already exists.
|
|
13
|
+
* When the presentation is unset/reset (transitions back to unresolved), clears the selection so a stale id from the
|
|
14
|
+
* previous source doesn't persist.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle-driven: the pick fires once per transition, and nothing re-picks — that is what separates these from the
|
|
17
|
+
* `switch*` variants. External writes (user picks, ABR, programmatic filter-driven re-picks) are left alone, including
|
|
18
|
+
* a write naming a track the manifest never offered.
|
|
19
|
+
*
|
|
20
|
+
* The one thing policed between transitions is a pick the _constraints_ turn against: a rendition's container and
|
|
21
|
+
* encryption are only known once its media playlist resolves, which is after the pick was made, so a selection that
|
|
22
|
+
* becomes unplayable is dropped. Dropped, never moved — re-picking is exactly the behavior `switchVideoTrack` exists to
|
|
23
|
+
* provide. Dropping reports nothing on its own, since whatever made the pick unplayable already reported its own, more
|
|
24
|
+
* specific cause.
|
|
25
|
+
*
|
|
26
|
+
* Selection runs the same rule model `switchVideoTrack` does — a hard `constraints` pre-pass, then an ordered `rules`
|
|
27
|
+
* chain, with the pick as the head (see `internal/design/spf/track-switching-model.md`). What differs is reactivity,
|
|
28
|
+
* not the rules: this evaluates the chain once on resolve and pins the result, where `switchVideoTrack` re-evaluates
|
|
29
|
+
* inside an effect so its rules subscribe to bandwidth and user selection. A rule written for one therefore composes
|
|
30
|
+
* into the other unchanged.
|
|
31
|
+
*
|
|
32
|
+
* Both are config-driven, each per-type export wiring a sensible default: audio's three-tier language policy, and for
|
|
33
|
+
* video the _empty_ chain — with nothing narrowing or reordering, the head is the first candidate. The behavior's
|
|
34
|
+
* `config` is forwarded to the rules, so options like `preferredAudioLanguage` reach them without an intermediate
|
|
35
|
+
* layer.
|
|
36
|
+
*
|
|
37
|
+
* Note a rule can only pick among real candidates, where the picker it replaced could return any id at all. An id
|
|
38
|
+
* absent from the manifest was never selectable, so that narrowing is the point rather than a limitation.
|
|
39
|
+
*
|
|
40
|
+
* Compose `selectVideoTrack` for the simple "pick a default video track" behavior, or `switchVideoTrack`
|
|
41
|
+
* (`./track-switching.ts`) for the ABR-driven variant. Compose `selectAudioTrack` for the simple default pick, or
|
|
42
|
+
* `switchAudioTrack` (`./track-switching.ts`) for the filter-reactive + mid-stream-flush slot-owner variant — when
|
|
43
|
+
* audio-abr lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose only one per type — they're
|
|
44
|
+
* alternatives, not stackable (each writes the same `selected*TrackId` slot). The simple variants tree-shake out the
|
|
45
|
+
* heavier machinery (bandwidth estimator, quality selection, flush orchestration).
|
|
46
|
+
*
|
|
47
|
+
* Text selection has no simple variant here — it's owned by `switchTextTrack` (`./track-switching.ts`), which resolves
|
|
48
|
+
* standing `userTextTrackSelection` intent against the constrained, CDN-scoped renditions.
|
|
49
|
+
*/
|
|
50
|
+
function setupTrackSelection({ state, config: { selectedKey, trackType, constraints, rules, ruleConfig } }) {
|
|
51
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
52
|
+
const deps = {
|
|
53
|
+
state,
|
|
54
|
+
config: ruleConfig
|
|
55
|
+
};
|
|
56
|
+
const candidateSet = computed(() => {
|
|
57
|
+
const presentation = state.presentation.get();
|
|
58
|
+
if (!isResolvedPresentation(presentation)) return [];
|
|
59
|
+
return applyConstraints(constraints, getTracksByType(presentation, trackType), deps);
|
|
60
|
+
}, { equals: sameCandidateSet });
|
|
61
|
+
return createMachineReactor({
|
|
62
|
+
initial: "presentation-unresolved",
|
|
63
|
+
monitor: () => derivedStateSignal.get(),
|
|
64
|
+
states: {
|
|
65
|
+
"presentation-unresolved": {},
|
|
66
|
+
"presentation-resolved": {
|
|
67
|
+
entry: () => {
|
|
68
|
+
if (!state[selectedKey].get()) {
|
|
69
|
+
const id = applyRules(rules, peek(candidateSet), deps)[0]?.id;
|
|
70
|
+
if (id) state[selectedKey].set(id);
|
|
71
|
+
}
|
|
72
|
+
return () => state[selectedKey].set(void 0);
|
|
73
|
+
},
|
|
74
|
+
effects: [() => {
|
|
75
|
+
const selectedId = peek(state[selectedKey]);
|
|
76
|
+
if (!selectedId) return;
|
|
77
|
+
if (candidateSet.get().some((track) => track.id === selectedId)) return;
|
|
78
|
+
const presentation = peek(state.presentation);
|
|
79
|
+
if (isResolvedPresentation(presentation) && getTracksByType(presentation, trackType).some((track) => track.id === selectedId)) state[selectedKey].set(void 0);
|
|
80
|
+
}]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Default video chain: none. The first candidate is the pick. */
|
|
86
|
+
const DEFAULT_VIDEO_RULES = [];
|
|
87
|
+
/**
|
|
88
|
+
* Default audio chain: the three-tier policy (`preferredAudioLanguage` → `DEFAULT=YES` → first) as a single narrowing
|
|
89
|
+
* rule. Returning `[]` when nothing is picked lets `applyRules` fall through to the unnarrowed candidates, so the head
|
|
90
|
+
* stays the first track — the same last tier the policy itself ends on.
|
|
91
|
+
*/
|
|
92
|
+
const preferAudioPolicy = (tracks, { config }) => {
|
|
93
|
+
const id = pickAudioTrackFromTracks(tracks, config);
|
|
94
|
+
const pick = tracks.find((track) => track.id === id);
|
|
95
|
+
return pick ? [pick] : [];
|
|
96
|
+
};
|
|
97
|
+
const DEFAULT_AUDIO_RULES = [preferAudioPolicy];
|
|
98
|
+
/**
|
|
99
|
+
* Order the candidates by resolution, largest first, with bandwidth breaking ties between renditions of identical
|
|
100
|
+
* dimensions. The background-video default — that variant pins one rendition for the session, and absent a cap the
|
|
101
|
+
* largest is the head.
|
|
102
|
+
*
|
|
103
|
+
* A ranker, so it reorders rather than narrowing: the chain's pick is the head of what it returns, which means ranking
|
|
104
|
+
* never has to collapse to one track. Belongs last in a chain — a sort only reorders what survived the filters ahead of
|
|
105
|
+
* it, and leaving it last is what lets `applyRules` early-bail before it runs.
|
|
106
|
+
*
|
|
107
|
+
* Exported because it is a _rule_, not a variant's private policy: the same one composes into `switchVideoTrack`'s
|
|
108
|
+
* chain when a ranker is wanted there.
|
|
109
|
+
*/
|
|
110
|
+
const preferHighestResolution = (tracks) => [...tracks].sort(byDescendingResolution);
|
|
111
|
+
/**
|
|
112
|
+
* Narrow to the renditions that fit the screen, by pixel area — the screen-size cap from
|
|
113
|
+
* `internal/design/spf/features/rendition-selection-caps.md`, as a scope (soft filter) rather than a constraint: an
|
|
114
|
+
* over-cap rendition is wasteful, not unplayable, so nothing here may make a source unplayable.
|
|
115
|
+
*
|
|
116
|
+
* Narrows only — it neither orders the survivors nor resolves the case where none survive, because `applyRules` owns
|
|
117
|
+
* both. So it needs a ranker behind it to pick within the cap: `[screenResolutionCap, preferHighestResolution]` yields
|
|
118
|
+
* the largest rendition that fits. Composed _last_, the pick would instead be whichever fitting rendition the manifest
|
|
119
|
+
* happened to list first.
|
|
120
|
+
*
|
|
121
|
+
* Reading `state.screenResolution` through its signal is what subscribes a re-evaluating chain (`switchVideoTrack`) to
|
|
122
|
+
* screen changes; `selectVideoTrack` pins the first answer instead, by design.
|
|
123
|
+
*
|
|
124
|
+
* Compares areas rather than matching a `"1080p"`-style tier because a tier only describes a rendition once you assume
|
|
125
|
+
* its aspect ratio — the assumption that mis-measures an anamorphic ladder. See `media/dom/screen.ts`.
|
|
126
|
+
*
|
|
127
|
+
* Three ways the cap ends up not applying, all of them fall-through:
|
|
128
|
+
*
|
|
129
|
+
* - **No `screenResolution` signal at all**, because the composition omits `trackScreenResolution`. So composing the cap
|
|
130
|
+
* without its signal source is inert rather than broken.
|
|
131
|
+
* - **A `screenResolution` of `undefined`**, meaning no screen to read. "Unknown" has to mean "don't cap": treating it as
|
|
132
|
+
* an area of zero would pin every source to its smallest rendition on exactly the environments we know least about.
|
|
133
|
+
* - **No rendition fits**, on a screen smaller than the whole ladder. `applyRules` skips the empty result and the chain
|
|
134
|
+
* proceeds unnarrowed, so the ranker behind the cap decides — for `preferHighestResolution`, the largest rendition. A
|
|
135
|
+
* floor is the fix if that ever matters (`rendition-selection-caps.md` carries one), not a special case here.
|
|
136
|
+
*/
|
|
137
|
+
const screenResolutionCap = (tracks, { state }) => {
|
|
138
|
+
const screenResolution = state?.screenResolution?.get();
|
|
139
|
+
if (!screenResolution) return [];
|
|
140
|
+
return tracksUnderPixelArea(tracks, screenResolution.width * screenResolution.height);
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Default video constraints: the capability pre-pass alone. No `excludeFailedCdns` — this variant's compositions run no
|
|
144
|
+
* failover monitor, so `failedCdns` has no writer and the constraint would always pass through.
|
|
145
|
+
*/
|
|
146
|
+
const DEFAULT_VIDEO_CONSTRAINTS = [excludeUnplayableTracks];
|
|
147
|
+
/**
|
|
148
|
+
* Select a video track when a presentation loads. Clears the selection on src unload.
|
|
149
|
+
*
|
|
150
|
+
* This is the simple, non-ABR counterpart to `switchVideoTrack` — compose one or the other, not both (both write
|
|
151
|
+
* `selectedVideoTrackId`). Composing `selectVideoTrack` alone tree-shakes out the ABR code path (bandwidth-estimator,
|
|
152
|
+
* quality-selection); use it for sources without meaningful quality variants, test setups, or players that
|
|
153
|
+
* intentionally pin a quality.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const reactor = selectVideoTrack.setup({ state });
|
|
157
|
+
*/
|
|
158
|
+
const selectVideoTrack = defineBehavior({
|
|
159
|
+
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
160
|
+
contextKeys: [],
|
|
161
|
+
setup: ({ state, config }) => setupTrackSelection({
|
|
162
|
+
state,
|
|
163
|
+
config: {
|
|
164
|
+
selectedKey: VIDEO_TYPE_CONFIG.selectedKey,
|
|
165
|
+
trackType: "video",
|
|
166
|
+
constraints: config?.constraints ?? DEFAULT_VIDEO_CONSTRAINTS,
|
|
167
|
+
rules: config?.rules ?? DEFAULT_VIDEO_RULES,
|
|
168
|
+
ruleConfig: config
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
});
|
|
172
|
+
defineBehavior({
|
|
173
|
+
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
174
|
+
contextKeys: [],
|
|
175
|
+
setup: ({ state, config }) => setupTrackSelection({
|
|
176
|
+
state,
|
|
177
|
+
config: {
|
|
178
|
+
selectedKey: AUDIO_TYPE_CONFIG.selectedKey,
|
|
179
|
+
trackType: "audio",
|
|
180
|
+
constraints: config?.constraints ?? [],
|
|
181
|
+
rules: config?.rules ?? DEFAULT_AUDIO_RULES,
|
|
182
|
+
ruleConfig: config
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
//#endregion
|
|
187
|
+
export { preferHighestResolution, screenResolutionCap, selectVideoTrack };
|
|
188
|
+
|
|
189
|
+
//# 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.** When a presentation is resolved, sets\n * `selectedVideoTrackId` / `selectedAudioTrackId` from a per-type default rule chain if no selection already exists.\n * When the presentation is unset/reset (transitions back to unresolved), clears the selection so a stale id from the\n * previous source doesn't persist.\n *\n * Lifecycle-driven: the pick fires once per transition, and nothing re-picks — that is what separates these from the\n * `switch*` variants. External writes (user picks, ABR, programmatic filter-driven re-picks) are left alone, including\n * a write naming a track the manifest never offered.\n *\n * The one thing policed between transitions is a pick the _constraints_ turn against: a rendition's container and\n * encryption are only known once its media playlist resolves, which is after the pick was made, so a selection that\n * becomes unplayable is dropped. Dropped, never moved — re-picking is exactly the behavior `switchVideoTrack` exists to\n * provide. Dropping reports nothing on its own, since whatever made the pick unplayable already reported its own, more\n * specific cause.\n *\n * Selection runs the same rule model `switchVideoTrack` does — a hard `constraints` pre-pass, then an ordered `rules`\n * chain, with the pick as the head (see `internal/design/spf/track-switching-model.md`). What differs is reactivity,\n * not the rules: this evaluates the chain once on resolve and pins the result, where `switchVideoTrack` re-evaluates\n * inside an effect so its rules subscribe to bandwidth and user selection. A rule written for one therefore composes\n * into the other unchanged.\n *\n * Both are config-driven, each per-type export wiring a sensible default: audio's three-tier language policy, and for\n * video the _empty_ chain — with nothing narrowing or reordering, the head is the first candidate. The behavior's\n * `config` is forwarded to the rules, so options like `preferredAudioLanguage` reach them without an intermediate\n * layer.\n *\n * Note a rule can only pick among real candidates, where the picker it replaced could return any id at all. An id\n * absent from the manifest was never selectable, so that narrowing is the point rather than a limitation.\n *\n * Compose `selectVideoTrack` for the simple \"pick a default video track\" behavior, or `switchVideoTrack`\n * (`./track-switching.ts`) for the ABR-driven variant. Compose `selectAudioTrack` for the simple default pick, or\n * `switchAudioTrack` (`./track-switching.ts`) for the filter-reactive + mid-stream-flush slot-owner variant — when\n * audio-abr lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose only one per type — they're\n * alternatives, not stackable (each writes the same `selected*TrackId` slot). The simple variants tree-shake out the\n * heavier machinery (bandwidth estimator, quality selection, flush orchestration).\n *\n * Text selection has no simple variant here — it's owned by `switchTextTrack` (`./track-switching.ts`), which resolves\n * standing `userTextTrackSelection` 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\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\n if (id) state[selectedKey].set(id);\n }\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\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\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` → `DEFAULT=YES` → first) as a single narrowing\n * rule. Returning `[]` when nothing is picked lets `applyRules` fall through to the unnarrowed candidates, so the head\n * 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\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 between renditions of identical\n * dimensions. The background-video default — that variant pins one rendition for the session, and absent a cap the\n * largest is the head.\n *\n * A ranker, so it reorders rather than narrowing: the chain's pick is the head of what it returns, which means ranking\n * never has to collapse to one track. Belongs last in a chain — a sort only reorders what survived the filters ahead of\n * it, and 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 composes into `switchVideoTrack`'s\n * 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 cap from\n * `internal/design/spf/features/rendition-selection-caps.md`, as a scope (soft filter) rather than a constraint: an\n * over-cap rendition is wasteful, not unplayable, so nothing here may make a source unplayable.\n *\n * Narrows only — it neither orders the survivors nor resolves the case where none survive, because `applyRules` owns\n * both. So it needs a ranker behind it to pick within the cap: `[screenResolutionCap, preferHighestResolution]` yields\n * the largest rendition that fits. Composed _last_, the pick would instead be whichever fitting rendition the manifest\n * happened to list first.\n *\n * Reading `state.screenResolution` through its signal is what subscribes a re-evaluating chain (`switchVideoTrack`) to\n * screen changes; `selectVideoTrack` pins the first answer instead, by design.\n *\n * Compares areas rather than matching a `\"1080p\"`-style tier because a tier only describes a rendition once you assume\n * its aspect ratio — the assumption that 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 `trackScreenResolution`. So composing the cap\n * without its signal source is inert rather than broken.\n * - **A `screenResolution` of `undefined`**, meaning no screen to read. \"Unknown\" has to mean \"don't cap\": treating it as\n * an area of zero would pin every source 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` skips the empty result and the chain\n * proceeds unnarrowed, so the ranker behind the cap decides — for `preferHighestResolution`, the largest rendition. A\n * floor is the fix if that ever matters (`rendition-selection-caps.md` carries one), not 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 `constraints` to replace the\n * capability pre-pass; otherwise the chain is empty 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 `excludeFailedCdns` — this variant's compositions run no\n * failover monitor, so `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 src unload.\n *\n * This is the simple, non-ABR counterpart to `switchVideoTrack` — compose one or the other, not both (both write\n * `selectedVideoTrackId`). Composing `selectVideoTrack` alone tree-shakes out the ABR code path (bandwidth-estimator,\n * quality-selection); use it for sources without meaningful quality variants, test setups, or players that\n * intentionally 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 `constraints` to prune candidates\n * before it runs; otherwise the default 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 on src unload.\n *\n * This is the simple, lifecycle-only counterpart to `switchAudioTrack` (in `./track-switching.ts`) — compose one or the\n * other, not both (both write `selectedAudioTrackId`). `switchAudioTrack` adds filter-reactivity\n * (`userAudioTrackSelection`) and mid-stream-flush orchestration; `selectAudioTrack` covers the default-on-load case\n * without those. Use this variant for test setups, audio-only flows that don't expose language switching, or\n * composition variants that 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA,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;EAEnD,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;MAEzB,IAAI,IAAI,MAAM,YAAY,CAAC,IAAI,EAAE;KACnC;KAEA,aAAa,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;IAC/C;IACA,SAAS,OAeD;KAEJ,MAAM,aAAa,KAAK,MAAM,YAAY;KAC1C,IAAI,CAAC,YAAY;KAEjB,IAAI,aAAa,IAAI,CAAC,CAAC,MAAM,UAAU,MAAM,OAAO,UAAU,GAAG;KAKjE,MAAM,eAAe,KAAK,MAAM,YAAY;KAE5C,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;;;;;;AAOjF,MAAM,qBAA8D,QAAQ,EAAE,aAAa;CACzF,MAAM,KAAK,yBAAyB,QAAqC,MAAM;CAC/E,MAAM,OAAO,OAAO,MAAM,UAAU,MAAM,OAAO,EAAE;CAEnD,OAAO,OAAO,CAAC,IAAI,IAAI,CAAC;AAC1B;AAEA,MAAM,sBAA0E,CAAC,iBAAiB;;;;;;;;;;;;;AAclG,MAAa,2BAAqD,WAAW,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCpH,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;;;;;AAmBA,MAAM,4BAAgF,CAAC,uBAAuB;;;;;;;;;;;;AAa9G,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;AA8B+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,11 @@
|
|
|
1
|
+
import "../../core/signals/primitives.js";
|
|
2
|
+
import "../../index.js";
|
|
3
|
+
//#region src/playback/behaviors/setup-failover-monitor.d.ts
|
|
4
|
+
/** Failover policy: how long a CDN stays excluded after a failed fetch trips it. Supplied via engine config. */
|
|
5
|
+
interface FailoverMonitorConfig {
|
|
6
|
+
/** How long a tripped CDN stays excluded, in milliseconds. */
|
|
7
|
+
cooldownMs: number;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { FailoverMonitorConfig };
|
|
11
|
+
//# 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":";;;;UAmBiB;;EAEf"}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 own the _trip_: on a failed fetch they
|
|
8
|
+
* add the failing CDN (origin) to the `failedCdns` state signal directly. This behavior owns the _expiry_: while a
|
|
9
|
+
* presentation is resolved, it watches `failedCdns` and, for each CDN that appears, schedules a timer to remove it once
|
|
10
|
+
* its cooldown lapses. `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's tracks and the
|
|
11
|
+
* active-CDN scope falls to the next one — and back, once the cooldown removes it here.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new source starts with a clean slate). Policy
|
|
14
|
+
* (cooldown) is engine config. This is the minimal `network-resilience` slice — a single failure trips a CDN, since
|
|
15
|
+
* transient blips are the retry layer's job (it sits below the fetch sites, so anything that reaches `failedCdns` is
|
|
16
|
+
* already terminal).
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_FAILOVER_MONITOR_CONFIG = { cooldownMs: 3e5 };
|
|
19
|
+
/**
|
|
20
|
+
* Expire failed CDNs from `failedCdns` once their cooldown lapses, for the resolved source.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const reactor = setupFailoverMonitor.setup({ state });
|
|
24
|
+
*/
|
|
25
|
+
const setupFailoverMonitor = defineBehavior({
|
|
26
|
+
stateKeys: ["presentation", "failedCdns"],
|
|
27
|
+
contextKeys: [],
|
|
28
|
+
setup: ({ state, config = {} }) => {
|
|
29
|
+
const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;
|
|
30
|
+
const timers = /* @__PURE__ */ new Map();
|
|
31
|
+
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
32
|
+
return createMachineReactor({
|
|
33
|
+
initial: "presentation-unresolved",
|
|
34
|
+
monitor: () => derivedStateSignal.get(),
|
|
35
|
+
states: {
|
|
36
|
+
"presentation-unresolved": {},
|
|
37
|
+
"presentation-resolved": {
|
|
38
|
+
entry: () => () => {
|
|
39
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
40
|
+
timers.clear();
|
|
41
|
+
state.failedCdns.set(void 0);
|
|
42
|
+
},
|
|
43
|
+
effects: [() => {
|
|
44
|
+
(state.failedCdns.get() ?? []).forEach((cdn) => {
|
|
45
|
+
if (timers.has(cdn)) return;
|
|
46
|
+
const timer = setTimeout(() => {
|
|
47
|
+
timers.delete(cdn);
|
|
48
|
+
update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));
|
|
49
|
+
}, cooldownMs);
|
|
50
|
+
timers.set(cdn, timer);
|
|
51
|
+
});
|
|
52
|
+
}]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
//#endregion
|
|
59
|
+
export { DEFAULT_FAILOVER_MONITOR_CONFIG, setupFailoverMonitor };
|
|
60
|
+
|
|
61
|
+
//# 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 own the _trip_: on a failed fetch they\n * add the failing CDN (origin) to the `failedCdns` state signal directly. This behavior owns the _expiry_: while a\n * presentation is resolved, it watches `failedCdns` and, for each CDN that appears, schedules a timer to remove it once\n * its cooldown lapses. `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's tracks and the\n * active-CDN scope falls to the next one — and back, once the cooldown removes it here.\n *\n * Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new source starts with a clean slate). Policy\n * (cooldown) is engine config. This is 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 anything that reaches `failedCdns` is\n * 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/** Failover policy: how long a CDN stays excluded after a failed fetch trips it. Supplied via engine config. */\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 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\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\n const timer = setTimeout(() => {\n timers.delete(cdn);\n update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));\n }, cooldownMs);\n\n timers.set(cdn, timer);\n });\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAa,kCAAyD,EAKpE,YAAY,IACd;;;;;;;AAkBA,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;MAGJ,CAFe,MAAM,WAAW,IAAI,KAAK,CAAC,EAAA,CAEnC,SAAS,QAAQ;OAGtB,IAAI,OAAO,IAAI,GAAG,GAAG;OAErB,MAAM,QAAQ,iBAAiB;QAC7B,OAAO,OAAO,GAAG;QACjB,OAAO,MAAM,aAAa,YAAY,SAAS,QAAQ,MAAM,MAAM,GAAG,CAAC;OACzE,GAAG,UAAU;OAEb,OAAO,IAAI,KAAK,KAAK;MACvB,CAAC;KACH,CACF;IACF;GACF;EACF,CAAC;CACH;AACF,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { computed, peek } from "../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../core/composition/create-composition.js";
|
|
3
|
+
import { effect } from "../../core/signals/effect.js";
|
|
4
|
+
import { isStandardPreload } from "../../media/utils/preload.js";
|
|
5
|
+
//#region src/playback/behaviors/sync-preload.ts
|
|
6
|
+
/**
|
|
7
|
+
* **Bidirectional sync between `state.preload` and `mediaElement.preload`.**
|
|
8
|
+
*
|
|
9
|
+
* Two effects:
|
|
10
|
+
*
|
|
11
|
+
* - **Read (DOM → state)** — on `context.mediaElement` swap or `state.presentation.url` change, copies the media
|
|
12
|
+
* element's _authored_ preload into `state.preload` if it's a W3C value and `state.preload` isn't holding an extended
|
|
13
|
+
* (non-W3C) value. Author intent only: on a DOM element the `preload` IDL property reports a browser-dependent UA
|
|
14
|
+
* default even when no attribute was set (`'metadata'` Chromium, `'auto'` WebKit), so the property is consulted only
|
|
15
|
+
* when the element carries a `preload` content attribute — a UA default is never a source. When the element has no
|
|
16
|
+
* authored W3C opinion and `state.preload` is undefined, backfills from `config.defaultPreload` (default-default
|
|
17
|
+
* `'metadata'`) so `state.preload` is never undefined in steady state.
|
|
18
|
+
* - **Write (state → DOM)** — on `state.preload` change or `context.mediaElement` swap, writes `state.preload` back to
|
|
19
|
+
* `mediaElement.preload` if the value is W3C.
|
|
20
|
+
*
|
|
21
|
+
* Extended values (e.g. `'canplay'`) written externally to `state.preload` are sticky: read won't overwrite them, write
|
|
22
|
+
* won't push them to the DOM. All writes are deduped to break echo loops and avoid spurious re-triggers downstream
|
|
23
|
+
* (notably `resolvePresentation`, which reads `state.preload`).
|
|
24
|
+
*/
|
|
25
|
+
function syncPreloadSetup({ state, context, config }) {
|
|
26
|
+
const defaultPreload = config?.defaultPreload ?? "metadata";
|
|
27
|
+
const presentationUrl = computed(() => state.presentation.get()?.url);
|
|
28
|
+
const cleanupRead = effect(() => {
|
|
29
|
+
presentationUrl.get();
|
|
30
|
+
const mediaElement = context.mediaElement.get();
|
|
31
|
+
const current = peek(state.preload);
|
|
32
|
+
if (current !== void 0 && !isStandardPreload(current)) return;
|
|
33
|
+
const authoredPreload = mediaElement && (!mediaElement.hasAttribute || mediaElement.hasAttribute("preload")) ? mediaElement.preload : void 0;
|
|
34
|
+
const target = isStandardPreload(authoredPreload) ? authoredPreload : current === void 0 ? defaultPreload : void 0;
|
|
35
|
+
if (target === void 0 || target === current) return;
|
|
36
|
+
state.preload.set(target);
|
|
37
|
+
});
|
|
38
|
+
const cleanupWrite = effect(() => {
|
|
39
|
+
const next = state.preload.get();
|
|
40
|
+
const mediaElement = context.mediaElement.get();
|
|
41
|
+
if (!mediaElement) return;
|
|
42
|
+
if (!isStandardPreload(next)) return;
|
|
43
|
+
if (mediaElement.preload === next) return;
|
|
44
|
+
mediaElement.preload = next;
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
cleanupRead();
|
|
48
|
+
cleanupWrite();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const syncPreload = defineBehavior({
|
|
52
|
+
stateKeys: ["preload", "presentation"],
|
|
53
|
+
contextKeys: ["mediaElement"],
|
|
54
|
+
setup: syncPreloadSetup
|
|
55
|
+
});
|
|
56
|
+
//#endregion
|
|
57
|
+
export { syncPreload };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=sync-preload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-preload.js","names":[],"sources":["../../../../src/playback/behaviors/sync-preload.ts"],"sourcesContent":["/**\n * **Bidirectional sync between `state.preload` and `mediaElement.preload`.**\n *\n * Two effects:\n *\n * - **Read (DOM → state)** — on `context.mediaElement` swap or `state.presentation.url` change, copies the media\n * element's _authored_ preload into `state.preload` if it's a W3C value and `state.preload` isn't holding an extended\n * (non-W3C) value. Author intent only: on a DOM element the `preload` IDL property reports a browser-dependent UA\n * default even when no attribute was set (`'metadata'` Chromium, `'auto'` WebKit), so the property is consulted only\n * when the element carries a `preload` content attribute — a UA default is never a source. When the element has no\n * authored W3C opinion and `state.preload` is undefined, backfills from `config.defaultPreload` (default-default\n * `'metadata'`) so `state.preload` is never undefined in steady state.\n * - **Write (state → DOM)** — on `state.preload` change or `context.mediaElement` swap, writes `state.preload` back to\n * `mediaElement.preload` if the value is W3C.\n *\n * Extended values (e.g. `'canplay'`) written externally to `state.preload` are sticky: read won't overwrite them, write\n * won't push them to the DOM. All writes are deduped to break echo loops and avoid spurious re-triggers downstream\n * (notably `resolvePresentation`, which reads `state.preload`).\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport type { MediaElementLike } from '../../media/types';\nimport { DEFAULT_PRELOAD, isStandardPreload, type StandardPreload } from '../../media/utils/preload';\nimport type { PresentationState } from './resolve-presentation';\n\nexport interface SyncPreloadConfig {\n /**\n * Backfill applied to `state.preload` when neither the host element nor external code has supplied a W3C value.\n * Defaults to `'metadata'`.\n */\n defaultPreload?: StandardPreload;\n}\n\ntype State = Pick<PresentationState, 'preload' | 'presentation'>;\n\nfunction syncPreloadSetup({\n state,\n context,\n config,\n}: {\n state: {\n preload: Signal<State['preload']>;\n presentation: ReadonlySignal<State['presentation']>;\n };\n context: { mediaElement: ReadonlySignal<MediaElementLike | undefined> };\n config?: SyncPreloadConfig;\n}): () => void {\n const defaultPreload: StandardPreload = config?.defaultPreload ?? DEFAULT_PRELOAD;\n const presentationUrl = computed(() => state.presentation.get()?.url);\n\n // Read must be registered before write: effects re-run in registration\n // order (see core/signals/effect.ts → runPending). On a shared-signal\n // change (e.g. `context.mediaElement` swap), read fires first and gets to\n // claim `state.preload`, giving \"most-recent-wins on attach\" — a freshly\n // mounted <video> with a W3C `preload` attribute overrides whatever\n // `state.preload` previously held. Swap the order or split these into\n // separate behaviors and the \"most-recent-wins on attach\" test pins the\n // regression.\n const cleanupRead = effect(() => {\n presentationUrl.get();\n const mediaElement = context.mediaElement.get();\n // peek (not get): external writes to state.preload shouldn't re-trigger\n // this effect, only mediaElement / presentation-url changes should.\n const current = peek(state.preload);\n // Extended sticky: leave non-W3C values (e.g. 'canplay') alone.\n if (current !== undefined && !isStandardPreload(current)) return;\n\n // Author intent only: a DOM element's `preload` property reports a UA\n // default when no attribute was authored (the shadow <video> under an SPF\n // Media never carries one), so gate the property read on attribute\n // presence. An element without attribute reflection has no UA defaults —\n // its property is authored by construction.\n const authoredPreload =\n mediaElement && (!mediaElement.hasAttribute || mediaElement.hasAttribute('preload'))\n ? mediaElement.preload\n : undefined;\n\n const target: StandardPreload | undefined = isStandardPreload(authoredPreload)\n ? authoredPreload\n : current === undefined\n ? defaultPreload\n : undefined;\n if (target === undefined || target === current) return;\n\n state.preload.set(target);\n });\n\n const cleanupWrite = effect(() => {\n const next = state.preload.get();\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) return;\n\n if (!isStandardPreload(next)) return;\n\n if (mediaElement.preload === next) return;\n\n mediaElement.preload = next;\n });\n\n return () => {\n cleanupRead();\n cleanupWrite();\n };\n}\n\nexport const syncPreload = defineBehavior({\n stateKeys: ['preload', 'presentation'],\n contextKeys: ['mediaElement'],\n setup: syncPreloadSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAS,iBAAiB,EACxB,OACA,SACA,UAQa;CACb,MAAM,iBAAkC,QAAQ,kBAAA;CAChD,MAAM,kBAAkB,eAAe,MAAM,aAAa,IAAI,CAAC,EAAE,GAAG;CAUpE,MAAM,cAAc,aAAa;EAC/B,gBAAgB,IAAI;EACpB,MAAM,eAAe,QAAQ,aAAa,IAAI;EAG9C,MAAM,UAAU,KAAK,MAAM,OAAO;EAElC,IAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,OAAO,GAAG;EAO1D,MAAM,kBACJ,iBAAiB,CAAC,aAAa,gBAAgB,aAAa,aAAa,SAAS,KAC9E,aAAa,UACb,KAAA;EAEN,MAAM,SAAsC,kBAAkB,eAAe,IACzE,kBACA,YAAY,KAAA,IACV,iBACA,KAAA;EACN,IAAI,WAAW,KAAA,KAAa,WAAW,SAAS;EAEhD,MAAM,QAAQ,IAAI,MAAM;CAC1B,CAAC;CAED,MAAM,eAAe,aAAa;EAChC,MAAM,OAAO,MAAM,QAAQ,IAAI;EAC/B,MAAM,eAAe,QAAQ,aAAa,IAAI;EAC9C,IAAI,CAAC,cAAc;EAEnB,IAAI,CAAC,kBAAkB,IAAI,GAAG;EAE9B,IAAI,aAAa,YAAY,MAAM;EAEnC,aAAa,UAAU;CACzB,CAAC;CAED,aAAa;EACX,YAAY;EACZ,aAAa;CACf;AACF;AAEA,MAAa,cAAc,eAAe;CACxC,WAAW,CAAC,WAAW,cAAc;CACrC,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { MaybeResolvedPresentation } from "../../media/types/index.js";
|
|
2
|
+
import { ReadonlySignal, Signal } from "../../core/signals/primitives.js";
|
|
3
|
+
import { AnySlotMap } from "../../core/composition/create-composition.js";
|
|
4
|
+
import "../../index.js";
|
|
5
|
+
import { CodecPreferenceConfig, DEFAULT_PREFERRED_CODECS, SelectionRule, SelectionRuleDeps, preferCodecFamilies } from "../primitives/selection-rules.js";
|
|
6
|
+
import "./collect-errors.js";
|
|
7
|
+
//#region src/playback/behaviors/track-switching.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* The slots `setupTrackSwitching` itself owns: the `presentation` gate it reads and the per-type `selected*TrackId` it
|
|
10
|
+
* writes. Rule-only inputs are deliberately absent — `user*TrackSelection` and `bandwidthState` belong to whoever
|
|
11
|
+
* materializes them (the embedder via `shareSignals`, the buffer-actor sampler), and each rule declares the signal it
|
|
12
|
+
* consults as an optional slot on its own deps map, so the behavior never assumes a rule's signal exists.
|
|
13
|
+
*/
|
|
14
|
+
interface TrackSwitchingState {
|
|
15
|
+
presentation?: MaybeResolvedPresentation;
|
|
16
|
+
selectedVideoTrackId?: string;
|
|
17
|
+
selectedAudioTrackId?: string;
|
|
18
|
+
selectedTextTrackId?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Minimum candidate-track shape consumed by the helper and its rules: an `id` (the pick), a `url` (the active-CDN scope
|
|
22
|
+
* derives the CDN from it), an optional `bandwidth` (the ranker's throughput sort), and optional `width`/`height` (the
|
|
23
|
+
* ranker's equal-bitrate tie-break — absent on audio, so audio candidates area-compare equal). Every
|
|
24
|
+
* resolved/partially-resolved video track carries them all; audio tracks omit the dimensions.
|
|
25
|
+
*/
|
|
26
|
+
type SwitchableTrack = {
|
|
27
|
+
id: string;
|
|
28
|
+
url: string;
|
|
29
|
+
bandwidth?: number;
|
|
30
|
+
width?: number;
|
|
31
|
+
height?: number;
|
|
32
|
+
mimeType?: string;
|
|
33
|
+
codecs?: string[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Map the rule chain's surviving candidates to the final selection id, or `undefined` for a deliberate no-selection.
|
|
37
|
+
* Defaults to the chain head (`selectChainHead`) — the always-pick contract video and audio rely on (`applyRules`
|
|
38
|
+
* guarantees a non-empty result, so the head is always there). A variant whose selection is legitimately optional
|
|
39
|
+
* (text: opt-in captions, explicit off) supplies its own picker that may return `undefined`; the helper writes that
|
|
40
|
+
* straight through to the slot, clearing it.
|
|
41
|
+
*/
|
|
42
|
+
type ResolveSelection<T extends SwitchableTrack, State = unknown, Context = unknown, Config = unknown> = (candidates: readonly T[], deps: SelectionRuleDeps<State, Context, Config>) => string | undefined;
|
|
43
|
+
type SelectionKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';
|
|
44
|
+
type TrackSwitchingStateMap<S extends SelectionKey> = {
|
|
45
|
+
presentation: ReadonlySignal<TrackSwitchingState['presentation']>;
|
|
46
|
+
} & { [P in S]: Signal<TrackSwitchingState[P]>; };
|
|
47
|
+
/**
|
|
48
|
+
* Config `setupTrackSwitching` itself reads — its own wiring: which selection slot to write and clear (`selectionKey`),
|
|
49
|
+
* how to enumerate candidate tracks (`getTracks`), the optional **hard-constraints pre-pass** (`constraints`, applied
|
|
50
|
+
* before the chain to prune the unplayable), and the **rule chain** to run (`rules`), and how to map the chain's
|
|
51
|
+
* survivors to the final pick (`resolveSelection`, defaulting to the chain head). Rule-/constraint-specific config is
|
|
52
|
+
* deliberately absent — each declares the fields it reads as _optional_ on its own config view (`UserSelectionConfig`,
|
|
53
|
+
* `BandwidthRankerConfig`), so the behavior never enumerates them. The variant builds the concrete config as this base
|
|
54
|
+
* plus whatever its chain consults; it flows through untouched as the `C` type param on `setupTrackSwitching`.
|
|
55
|
+
*/
|
|
56
|
+
interface TrackSwitchingConfig<S extends SelectionKey, T extends SwitchableTrack> {
|
|
57
|
+
selectionKey: S;
|
|
58
|
+
getTracks: (presentation: MaybeResolvedPresentation) => readonly T[];
|
|
59
|
+
constraints?: readonly SelectionRule<T, TrackSwitchingStateMap<S>, AnySlotMap, TrackSwitchingConfig<S, T>>[];
|
|
60
|
+
rules: readonly SelectionRule<T, TrackSwitchingStateMap<S>, AnySlotMap, TrackSwitchingConfig<S, T>>[];
|
|
61
|
+
/**
|
|
62
|
+
* Map the chain's surviving candidates to the final selection id. Optional — absent means the chain head
|
|
63
|
+
* (`selectChainHead`), the always-pick path video and audio use. A variant with optional selection (text) supplies
|
|
64
|
+
* one that may return `undefined`.
|
|
65
|
+
*/
|
|
66
|
+
resolveSelection?: ResolveSelection<T, TrackSwitchingStateMap<S>, AnySlotMap, TrackSwitchingConfig<S, T>>;
|
|
67
|
+
/**
|
|
68
|
+
* SVTA code to report when this type _has_ tracks but the constraints pruned every one. Per-variant because the
|
|
69
|
+
* condition isn't universally an error: video and audio supply {@link SVTA_NO_SUPPORTED_VIDEO_TRACK} /
|
|
70
|
+
* {@link SVTA_NO_SUPPORTED_AUDIO_TRACK}, while text supplies none — an unavailable subtitle track is a legitimate
|
|
71
|
+
* outcome, not a playback failure. Absent → the selection still clears, nothing is reported.
|
|
72
|
+
*/
|
|
73
|
+
noSupportedTrackCode?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Codec-family sticky constraint — a _hard_ filter for the constraints pre-pass, shared by video and audio, and the
|
|
77
|
+
* reason the re-evaluating variants can run ABR over a mixed-codec source at all: SPF implements no
|
|
78
|
+
* `SourceBuffer.changeType()`, so once segments of the selected track's codec families are what a buffer was created
|
|
79
|
+
* for, a pick outside them could never play — constraint semantics ("can't play here"), not preference. Removes the
|
|
80
|
+
* candidates whose codec-family set doesn't _equal_ the selected track's (set-equality so a muxed `hvc1,mp4a` rendition
|
|
81
|
+
* can't pass as a match for `avc1,mp4a` on its audio half). Purely relational: the lock _is_ the current selection's
|
|
82
|
+
* families, no state of its own. Pruned pre-pass, a cross-family user selection mid-stream never reaches the user
|
|
83
|
+
* filter, which falls through unhonored instead of killing playback. Before any selection exists (the initial pick) it
|
|
84
|
+
* removes nothing, which is what leaves the initial family choice to the rule chain (user intent, then
|
|
85
|
+
* `preferCodecFamilies`).
|
|
86
|
+
*
|
|
87
|
+
* The selected track's families come from the _presentation's_ track list, not the already-pruned candidates: a CDN
|
|
88
|
+
* entering cooldown may prune the current track itself while same-family renditions survive on another host, and the
|
|
89
|
+
* lock must carry over to them. Removes nothing when: no selection yet, a selection the presentation no longer carries,
|
|
90
|
+
* or a selected track without codecs (then no candidate's compatibility is decidable — same inertness as
|
|
91
|
+
* `preferCodecFamilies` on a codec-less ladder).
|
|
92
|
+
*
|
|
93
|
+
* When the selected family genuinely vanishes from the playable set, the pre-pass empties: the behavior reports the
|
|
94
|
+
* type's no-supported-track code and clears the selection — an explicable stop instead of a cross-family append
|
|
95
|
+
* surfacing as an opaque decode error. Clearing also releases the lock (it keys on the live selection), so a following
|
|
96
|
+
* pick may land in the surviving family on freshly-created buffer actors; whether the append layer survives that
|
|
97
|
+
* rebuild is the changeType gap, out of this constraint's hands. If `changeType` support ever lands, this constraint is
|
|
98
|
+
* what relaxes.
|
|
99
|
+
*
|
|
100
|
+
* Reading the selection here — a slot the picking effect itself writes, from inside the candidate-set computed — is
|
|
101
|
+
* safe only because the effect scheduler revalidates an effect's sources after each run; see `core/signals/effect.ts`
|
|
102
|
+
* (`revalidateSources`) for the lost-wakeup it prevents.
|
|
103
|
+
*
|
|
104
|
+
* Composed only into the re-evaluating `switch*` variants: the pinned `selectVideoTrack` (background compositions)
|
|
105
|
+
* evaluates once and never re-picks, so it's immune by construction.
|
|
106
|
+
*/
|
|
107
|
+
declare function stickToSelectedCodecs<S extends SelectionKey, T extends SwitchableTrack>(tracks: readonly T[], { state, config }: SelectionRuleDeps<TrackSwitchingStateMap<S>, AnySlotMap, TrackSwitchingConfig<S, T>>): readonly T[];
|
|
108
|
+
//#endregion
|
|
109
|
+
export { ResolveSelection, TrackSwitchingState, TrackSwitchingStateMap, stickToSelectedCodecs };
|
|
110
|
+
//# sourceMappingURL=track-switching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-switching.d.ts","names":[],"sources":["../../../../src/playback/behaviors/track-switching.ts"],"mappings":";;;;;;;;;;;;;UAyGiB;EACf,eAAe;EACf;EACA;EACA;;;;;;;;KAyFG;EACH;EACA;EACA;EACA;EACA;EAIA;EACA;;;;;;;;;KAUU,iBAAiB,UAAU,iBAAiB,iBAAiB,mBAAmB,qBAC1F,qBAAqB,KACrB,MAAM,kBAAkB,OAAO,SAAS;KAGrC;KAeO,uBAAuB,UAAU;EAC3C,cAAc,eAAe;OACxB,KAAK,IAAI,OAAO,oBAAoB;;;;;;;;;;UAkBjC,qBAAqB,UAAU,cAAc,UAAU;EAC/D,cAAc;EACd,YAAY,cAAc,uCAAuC;EACjE,uBAAuB,cAAc,GAAG,uBAAuB,IAAI,YAAY,qBAAqB,GAAG;EACvG,gBAAgB,cAAc,GAAG,uBAAuB,IAAI,YAAY,qBAAqB,GAAG;;;;;;EAMhG,mBAAmB,iBAAiB,GAAG,uBAAuB,IAAI,YAAY,qBAAqB,GAAG;;;;;;;EAOtG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwOc,sBAAsB,UAAU,cAAc,UAAU,iBACtE,iBAAiB,OACf,OAAO,UAAU,kBAAkB,uBAAuB,IAAI,YAAY,qBAAqB,GAAG,eAC1F"}
|