@videojs/spf 10.0.0-beta.30 → 10.0.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/default/core/actors/create-machine-actor.js +23 -25
- package/dist/default/core/actors/create-machine-actor.js.map +1 -1
- package/dist/default/core/actors/create-transition-actor.js +7 -9
- package/dist/default/core/actors/create-transition-actor.js.map +1 -1
- package/dist/default/core/composition/create-composition.js +26 -29
- package/dist/default/core/composition/create-composition.js.map +1 -1
- package/dist/default/core/composition/share-signals.js +12 -17
- package/dist/default/core/composition/share-signals.js.map +1 -1
- package/dist/default/core/machine.js +3 -4
- package/dist/default/core/machine.js.map +1 -1
- package/dist/default/core/reactors/create-machine-reactor.js +20 -19
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
- package/dist/default/core/signals/effect.js +24 -4
- package/dist/default/core/signals/effect.js.map +1 -1
- package/dist/default/core/signals/primitives.js +10 -14
- package/dist/default/core/signals/primitives.js.map +1 -1
- package/dist/default/core/signals/when.js +6 -8
- package/dist/default/core/signals/when.js.map +1 -1
- package/dist/default/core/tasks/delayed-reschedule.js +7 -11
- package/dist/default/core/tasks/delayed-reschedule.js.map +1 -1
- package/dist/default/core/tasks/task.js +59 -82
- package/dist/default/core/tasks/task.js.map +1 -1
- package/dist/default/hls.js +3 -1
- package/dist/default/index.js +2 -3
- package/dist/default/index.js.map +1 -1
- package/dist/default/media/abr/quality-selection.js +3 -7
- package/dist/default/media/abr/quality-selection.js.map +1 -1
- package/dist/default/media/buffer/back-buffer.js +15 -19
- package/dist/default/media/buffer/back-buffer.js.map +1 -1
- package/dist/default/media/buffer/forward-buffer.js +32 -41
- package/dist/default/media/buffer/forward-buffer.js.map +1 -1
- package/dist/default/media/dom/capabilities.js +26 -37
- package/dist/default/media/dom/capabilities.js.map +1 -1
- package/dist/default/media/dom/mse/append-segment.js +3 -5
- package/dist/default/media/dom/mse/append-segment.js.map +1 -1
- package/dist/default/media/dom/mse/buffer-flusher.js +5 -5
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
- package/dist/default/media/dom/mse/duration.js +23 -31
- package/dist/default/media/dom/mse/duration.js.map +1 -1
- package/dist/default/media/dom/mse/end-of-stream.js +3 -6
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
- package/dist/default/media/dom/mse/mediasource-setup.js +65 -81
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
- package/dist/default/media/dom/screen.js +37 -54
- package/dist/default/media/dom/screen.js.map +1 -1
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
- package/dist/default/media/dom/text/text-track-slots.js +15 -23
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
- package/dist/default/media/errors.js +25 -34
- package/dist/default/media/errors.js.map +1 -1
- package/dist/default/media/hls/parse-attributes.js +9 -26
- package/dist/default/media/hls/parse-attributes.js.map +1 -1
- package/dist/default/media/hls/parse-media-playlist.js +25 -33
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
- package/dist/default/media/hls/parse-multivariant.js +3 -3
- package/dist/default/media/hls/parse-multivariant.js.map +1 -1
- package/dist/default/media/hls/reload-policy.js +19 -27
- package/dist/default/media/hls/reload-policy.js.map +1 -1
- package/dist/default/media/hls/resolve-url.js +1 -3
- package/dist/default/media/hls/resolve-url.js.map +1 -1
- package/dist/default/media/live-window.js +9 -13
- package/dist/default/media/live-window.js.map +1 -1
- package/dist/default/media/media-tracks/media-tracks.js +12 -17
- package/dist/default/media/media-tracks/media-tracks.js.map +1 -1
- package/dist/default/media/mp4/box.js +4 -5
- package/dist/default/media/mp4/box.js.map +1 -1
- package/dist/default/media/mp4/timestamp-origin.js +30 -42
- package/dist/default/media/mp4/timestamp-origin.js.map +1 -1
- package/dist/default/media/primitives/resolution.js +5 -8
- package/dist/default/media/primitives/resolution.js.map +1 -1
- package/dist/default/media/primitives/select-tracks.js +24 -34
- package/dist/default/media/primitives/select-tracks.js.map +1 -1
- package/dist/default/media/text/parse-vtt-timestamp-map.js +6 -8
- package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -1
- package/dist/default/media/text/resolve-vtt-metadata.js +8 -9
- package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -1
- package/dist/default/media/types/index.js +10 -18
- package/dist/default/media/types/index.js.map +1 -1
- package/dist/default/media/utils/cdn.js +10 -16
- package/dist/default/media/utils/cdn.js.map +1 -1
- package/dist/default/media/utils/preload.js +4 -6
- package/dist/default/media/utils/preload.js.map +1 -1
- package/dist/default/media/utils/track-selection.js +10 -14
- package/dist/default/media/utils/track-selection.js.map +1 -1
- package/dist/default/media/utils/tracks.js +56 -54
- package/dist/default/media/utils/tracks.js.map +1 -1
- package/dist/default/network/bandwidth-estimator.js +18 -19
- package/dist/default/network/bandwidth-estimator.js.map +1 -1
- package/dist/default/network/chunked-stream-iterable.js +4 -6
- package/dist/default/network/chunked-stream-iterable.js.map +1 -1
- package/dist/default/network/ewma.js +22 -25
- package/dist/default/network/ewma.js.map +1 -1
- package/dist/default/network/fetch.js +30 -37
- package/dist/default/network/fetch.js.map +1 -1
- package/dist/default/playback/actors/dom/segment-loader.js +28 -44
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
- package/dist/default/playback/actors/text-track-segment-loader.js +24 -35
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
- package/dist/default/playback/adapters/hls-audio/adapter.js +23 -31
- package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -1
- package/dist/default/playback/adapters/hls-audio/media.js.map +1 -1
- package/dist/default/playback/adapters/hls-background-video/adapter.js +50 -70
- package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -1
- package/dist/default/playback/adapters/hls-background-video/host.js +12 -16
- package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -1
- package/dist/default/playback/adapters/hls-background-video/media.js +5 -8
- package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -1
- package/dist/default/playback/adapters/hls-video/adapter.js +43 -60
- package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -1
- package/dist/default/playback/adapters/hls-video/error-surface.js +13 -18
- package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -1
- package/dist/default/playback/adapters/hls-video/media-tracks.js +4 -5
- package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -1
- package/dist/default/playback/adapters/hls-video/media.js.map +1 -1
- package/dist/default/playback/adapters/mux-audio/media.js +8 -12
- package/dist/default/playback/adapters/mux-audio/media.js.map +1 -1
- package/dist/default/playback/adapters/mux-video/adapter.js +25 -33
- package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -1
- package/dist/default/playback/adapters/mux-video/media.js +7 -9
- package/dist/default/playback/adapters/mux-video/media.js.map +1 -1
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +5 -8
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
- package/dist/default/playback/behaviors/collect-errors.js +39 -52
- package/dist/default/playback/behaviors/collect-errors.js.map +1 -1
- package/dist/default/playback/behaviors/derive-cdn-priority.js +16 -23
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
- package/dist/default/playback/behaviors/dom/airplay.js +38 -61
- package/dist/default/playback/behaviors/dom/airplay.js.map +1 -1
- package/dist/default/playback/behaviors/dom/apply-start-position.js +23 -35
- package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -1
- package/dist/default/playback/behaviors/dom/end-of-stream.js +11 -16
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
- package/dist/default/playback/behaviors/dom/load-segments.js +27 -39
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
- package/dist/default/playback/behaviors/dom/recover-end-stall.js +21 -26
- package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -1
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +41 -60
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +50 -74
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +29 -43
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +3 -4
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -1
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +30 -45
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-current-time.js +9 -11
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +24 -32
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +6 -8
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-player-resolution.js +10 -14
- package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js +8 -9
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -1
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +28 -43
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
- package/dist/default/playback/behaviors/establish-start-media-time.js +32 -42
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -1
- package/dist/default/playback/behaviors/resolve-presentation.js +13 -22
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
- package/dist/default/playback/behaviors/resolve-track.js +5 -12
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
- package/dist/default/playback/behaviors/select-tracks.js +77 -104
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
- package/dist/default/playback/behaviors/setup-failover-monitor.js +11 -16
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
- package/dist/default/playback/behaviors/sync-preload.js +9 -13
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
- package/dist/default/playback/behaviors/track-switching.js +189 -173
- package/dist/default/playback/behaviors/track-switching.js.map +1 -1
- package/dist/default/playback/engines/hls/engine-audio-only.js +20 -24
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
- package/dist/default/playback/engines/hls/engine-background-video.js +22 -27
- package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -1
- package/dist/default/playback/engines/hls/engine.js +22 -25
- package/dist/default/playback/engines/hls/engine.js.map +1 -1
- package/dist/default/playback/primitives/error-messages.js +16 -25
- package/dist/default/playback/primitives/error-messages.js.map +1 -1
- package/dist/default/playback/primitives/failover-fetch.js +9 -14
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
- package/dist/default/playback/primitives/head-peek.js +7 -10
- package/dist/default/playback/primitives/head-peek.js.map +1 -1
- package/dist/default/playback/primitives/live-window.js +12 -15
- package/dist/default/playback/primitives/live-window.js.map +1 -1
- package/dist/default/playback/primitives/relocation-pipelines.js +34 -46
- package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -1
- package/dist/default/playback/primitives/report-track-conditions.js +26 -38
- package/dist/default/playback/primitives/report-track-conditions.js.map +1 -1
- package/dist/default/playback/primitives/segment-load-pipeline.js +15 -15
- package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -1
- package/dist/default/playback/primitives/selection-rules.js +65 -36
- package/dist/default/playback/primitives/selection-rules.js.map +1 -1
- package/dist/default/playback/primitives/text-segment-load-pipeline.js +4 -5
- package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -1
- package/dist/default/playback/primitives/track-types.js +10 -18
- package/dist/default/playback/primitives/track-types.js.map +1 -1
- package/dist/dev/core/actors/actor.d.ts +4 -4
- package/dist/dev/core/actors/actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-machine-actor.d.ts +38 -47
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-machine-actor.js +23 -25
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
- package/dist/dev/core/actors/create-transition-actor.d.ts +11 -15
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-transition-actor.js +7 -9
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
- package/dist/dev/core/composition/create-composition.d.ts +70 -102
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
- package/dist/dev/core/composition/create-composition.js +26 -29
- package/dist/dev/core/composition/create-composition.js.map +1 -1
- package/dist/dev/core/composition/share-signals.d.ts +17 -24
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
- package/dist/dev/core/composition/share-signals.js +12 -17
- package/dist/dev/core/composition/share-signals.js.map +1 -1
- package/dist/dev/core/machine.d.ts +4 -4
- package/dist/dev/core/machine.js +3 -4
- package/dist/dev/core/machine.js.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +33 -39
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.js +20 -19
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
- package/dist/dev/core/signals/effect.d.ts +3 -4
- package/dist/dev/core/signals/effect.d.ts.map +1 -1
- package/dist/dev/core/signals/effect.js +24 -4
- package/dist/dev/core/signals/effect.js.map +1 -1
- package/dist/dev/core/signals/primitives.d.ts +10 -12
- package/dist/dev/core/signals/primitives.d.ts.map +1 -1
- package/dist/dev/core/signals/primitives.js +10 -14
- package/dist/dev/core/signals/primitives.js.map +1 -1
- package/dist/dev/core/signals/when.js +6 -8
- package/dist/dev/core/signals/when.js.map +1 -1
- package/dist/dev/core/tasks/delayed-reschedule.js +7 -11
- package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -1
- package/dist/dev/core/tasks/task.d.ts +51 -73
- package/dist/dev/core/tasks/task.d.ts.map +1 -1
- package/dist/dev/core/tasks/task.js +59 -82
- package/dist/dev/core/tasks/task.js.map +1 -1
- package/dist/dev/hls.d.ts +3 -1
- package/dist/dev/hls.js +3 -1
- package/dist/dev/index.d.ts +2 -3
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +2 -3
- package/dist/dev/index.js.map +1 -1
- package/dist/dev/media/abr/quality-selection.d.ts +7 -10
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
- package/dist/dev/media/abr/quality-selection.js +3 -7
- package/dist/dev/media/abr/quality-selection.js.map +1 -1
- package/dist/dev/media/buffer/back-buffer.d.ts +2 -7
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
- package/dist/dev/media/buffer/back-buffer.js +15 -19
- package/dist/dev/media/buffer/back-buffer.js.map +1 -1
- package/dist/dev/media/buffer/forward-buffer.d.ts +2 -7
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
- package/dist/dev/media/buffer/forward-buffer.js +32 -41
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
- package/dist/dev/media/dom/capabilities.js +26 -37
- package/dist/dev/media/dom/capabilities.js.map +1 -1
- package/dist/dev/media/dom/mse/append-segment.d.ts +3 -5
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
- package/dist/dev/media/dom/mse/append-segment.js +3 -5
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts +5 -5
- package/dist/dev/media/dom/mse/buffer-flusher.js +5 -5
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
- package/dist/dev/media/dom/mse/duration.js +23 -31
- package/dist/dev/media/dom/mse/duration.js.map +1 -1
- package/dist/dev/media/dom/mse/end-of-stream.js +3 -6
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
- package/dist/dev/media/dom/mse/mediasource-setup.js +65 -81
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
- package/dist/dev/media/dom/screen.d.ts.map +1 -1
- package/dist/dev/media/dom/screen.js +37 -54
- package/dist/dev/media/dom/screen.js.map +1 -1
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
- package/dist/dev/media/dom/text/text-track-slots.d.ts +10 -16
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
- package/dist/dev/media/dom/text/text-track-slots.js +15 -23
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
- package/dist/dev/media/errors.d.ts +32 -46
- package/dist/dev/media/errors.d.ts.map +1 -1
- package/dist/dev/media/errors.js +25 -34
- package/dist/dev/media/errors.js.map +1 -1
- package/dist/dev/media/hls/parse-attributes.js +9 -26
- package/dist/dev/media/hls/parse-attributes.js.map +1 -1
- package/dist/dev/media/hls/parse-media-playlist.js +25 -33
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
- package/dist/dev/media/hls/parse-multivariant.js +3 -3
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
- package/dist/dev/media/hls/reload-policy.js +19 -27
- package/dist/dev/media/hls/reload-policy.js.map +1 -1
- package/dist/dev/media/hls/resolve-url.js +1 -3
- package/dist/dev/media/hls/resolve-url.js.map +1 -1
- package/dist/dev/media/live-window.js +9 -13
- package/dist/dev/media/live-window.js.map +1 -1
- package/dist/dev/media/media-tracks/media-tracks.d.ts +8 -13
- package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -1
- package/dist/dev/media/media-tracks/media-tracks.js +12 -17
- package/dist/dev/media/media-tracks/media-tracks.js.map +1 -1
- package/dist/dev/media/mp4/box.js +4 -5
- package/dist/dev/media/mp4/box.js.map +1 -1
- package/dist/dev/media/mp4/timestamp-origin.js +30 -42
- package/dist/dev/media/mp4/timestamp-origin.js.map +1 -1
- package/dist/dev/media/primitives/resolution.d.ts +6 -8
- package/dist/dev/media/primitives/resolution.d.ts.map +1 -1
- package/dist/dev/media/primitives/resolution.js +5 -8
- package/dist/dev/media/primitives/resolution.js.map +1 -1
- package/dist/dev/media/primitives/select-tracks.js +24 -34
- package/dist/dev/media/primitives/select-tracks.js.map +1 -1
- package/dist/dev/media/text/parse-vtt-timestamp-map.js +6 -8
- package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -1
- package/dist/dev/media/text/resolve-vtt-metadata.js +8 -9
- package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -1
- package/dist/dev/media/types/index.d.ts +106 -177
- package/dist/dev/media/types/index.d.ts.map +1 -1
- package/dist/dev/media/types/index.js +10 -18
- package/dist/dev/media/types/index.js.map +1 -1
- package/dist/dev/media/utils/cdn.d.ts +4 -6
- package/dist/dev/media/utils/cdn.d.ts.map +1 -1
- package/dist/dev/media/utils/cdn.js +10 -16
- package/dist/dev/media/utils/cdn.js.map +1 -1
- package/dist/dev/media/utils/preload.js +4 -6
- package/dist/dev/media/utils/preload.js.map +1 -1
- package/dist/dev/media/utils/track-selection.js +10 -14
- package/dist/dev/media/utils/track-selection.js.map +1 -1
- package/dist/dev/media/utils/tracks.js +56 -54
- package/dist/dev/media/utils/tracks.js.map +1 -1
- package/dist/dev/network/bandwidth-estimator.d.ts +7 -9
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
- package/dist/dev/network/bandwidth-estimator.js +18 -19
- package/dist/dev/network/bandwidth-estimator.js.map +1 -1
- package/dist/dev/network/chunked-stream-iterable.js +4 -6
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
- package/dist/dev/network/ewma.js +22 -25
- package/dist/dev/network/ewma.js.map +1 -1
- package/dist/dev/network/fetch.js +30 -37
- package/dist/dev/network/fetch.js.map +1 -1
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +7 -11
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
- package/dist/dev/playback/actors/dom/segment-loader.js +28 -44
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +5 -7
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +10 -16
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
- package/dist/dev/playback/actors/text-track-segment-loader.js +24 -35
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
- package/dist/dev/playback/actors/text-tracks.d.ts +5 -8
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +11 -14
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-audio/adapter.js +23 -31
- package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -1
- package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -1
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +28 -39
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-background-video/adapter.js +50 -70
- package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -1
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts +12 -16
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-background-video/host.js +12 -16
- package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -1
- package/dist/dev/playback/adapters/hls-background-video/media.d.ts +5 -8
- package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-background-video/media.js +5 -8
- package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -1
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts +12 -14
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-video/adapter.js +43 -60
- package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -1
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +4 -5
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-video/error-surface.js +13 -18
- package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -1
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +4 -5
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-video/media-tracks.js +4 -5
- package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -1
- package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -1
- package/dist/dev/playback/adapters/hls-video/media.js.map +1 -1
- package/dist/dev/playback/adapters/mux-audio/media.d.ts +8 -12
- package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -1
- package/dist/dev/playback/adapters/mux-audio/media.js +8 -12
- package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -1
- package/dist/dev/playback/adapters/mux-video/adapter.d.ts +9 -11
- package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -1
- package/dist/dev/playback/adapters/mux-video/adapter.js +25 -33
- package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -1
- package/dist/dev/playback/adapters/mux-video/media.d.ts +7 -9
- package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -1
- package/dist/dev/playback/adapters/mux-video/media.js +7 -9
- package/dist/dev/playback/adapters/mux-video/media.js.map +1 -1
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +5 -8
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +5 -8
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
- package/dist/dev/playback/behaviors/collect-errors.d.ts +2 -0
- package/dist/dev/playback/behaviors/collect-errors.js +39 -52
- package/dist/dev/playback/behaviors/collect-errors.js.map +1 -1
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +16 -23
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/airplay.js +38 -61
- package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/apply-start-position.js +23 -35
- package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +11 -16
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +5 -8
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/load-segments.js +27 -39
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js +21 -26
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +41 -60
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +50 -74
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +29 -43
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +3 -3
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +3 -4
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +30 -45
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +2 -2
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-current-time.js +9 -11
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +6 -12
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +24 -32
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +2 -2
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js +6 -8
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js +10 -14
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +8 -9
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +28 -43
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +15 -19
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/establish-start-media-time.js +32 -42
- package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -1
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/resolve-presentation.js +13 -22
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
- package/dist/dev/playback/behaviors/resolve-track.js +5 -12
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
- package/dist/dev/playback/behaviors/select-tracks.d.ts +28 -39
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/select-tracks.js +77 -104
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +1 -4
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +11 -16
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
- package/dist/dev/playback/behaviors/sync-preload.js +9 -13
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
- 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 +189 -173
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +59 -72
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/engine-audio-only.js +20 -24
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts +51 -78
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/engine-background-video.js +22 -27
- package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -1
- package/dist/dev/playback/engines/hls/engine.d.ts +115 -155
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/engine.js +22 -25
- package/dist/dev/playback/engines/hls/engine.js.map +1 -1
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts +3 -3
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -1
- package/dist/dev/playback/primitives/error-messages.js +16 -25
- package/dist/dev/playback/primitives/error-messages.js.map +1 -1
- package/dist/dev/playback/primitives/failover-fetch.js +9 -14
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
- package/dist/dev/playback/primitives/head-peek.js +7 -10
- package/dist/dev/playback/primitives/head-peek.js.map +1 -1
- package/dist/dev/playback/primitives/live-window.js +12 -15
- package/dist/dev/playback/primitives/live-window.js.map +1 -1
- package/dist/dev/playback/primitives/relocation-pipelines.js +34 -46
- package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -1
- package/dist/dev/playback/primitives/report-track-conditions.d.ts +1 -4
- package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -1
- package/dist/dev/playback/primitives/report-track-conditions.js +26 -38
- package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -1
- package/dist/dev/playback/primitives/segment-load-pipeline.js +15 -15
- package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -1
- package/dist/dev/playback/primitives/selection-rules.d.ts +45 -17
- package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -1
- package/dist/dev/playback/primitives/selection-rules.js +65 -36
- package/dist/dev/playback/primitives/selection-rules.js.map +1 -1
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts +6 -9
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -1
- package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +20 -29
- package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -1
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js +4 -5
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -1
- package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -1
- package/dist/dev/playback/primitives/track-types.js +10 -18
- package/dist/dev/playback/primitives/track-types.js.map +1 -1
- package/package.json +25 -28
|
@@ -8,56 +8,44 @@ import { byDescendingResolution, pickAudioTrackFromTracks, tracksUnderPixelArea
|
|
|
8
8
|
import { applyConstraints, applyRules, excludeUnplayableTracks, sameCandidateSet } from "../primitives/selection-rules.js";
|
|
9
9
|
//#region src/playback/behaviors/select-tracks.ts
|
|
10
10
|
/**
|
|
11
|
-
* **Default audio/video track selection on src load / unselect on src unload.**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `switchVideoTrack`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* the
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* ABR-driven variant. Compose `selectAudioTrack` for the simple default
|
|
50
|
-
* pick, or `switchAudioTrack` (`./track-switching.ts`) for the
|
|
51
|
-
* filter-reactive + mid-stream-flush slot-owner variant — when audio-abr
|
|
52
|
-
* lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose
|
|
53
|
-
* only one per type — they're alternatives, not stackable (each writes
|
|
54
|
-
* the same `selected*TrackId` slot). The simple variants tree-shake out
|
|
55
|
-
* the heavier machinery (bandwidth estimator, quality selection, flush
|
|
56
|
-
* orchestration).
|
|
57
|
-
*
|
|
58
|
-
* Text selection has no simple variant here — it's owned by `switchTextTrack`
|
|
59
|
-
* (`./track-switching.ts`), which resolves standing `userTextTrackSelection`
|
|
60
|
-
* intent against the constrained, CDN-scoped renditions.
|
|
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.
|
|
61
49
|
*/
|
|
62
50
|
function setupTrackSelection({ state, config: { selectedKey, trackType, constraints, rules, ruleConfig } }) {
|
|
63
51
|
const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
|
|
@@ -97,10 +85,9 @@ function setupTrackSelection({ state, config: { selectedKey, trackType, constrai
|
|
|
97
85
|
/** Default video chain: none. The first candidate is the pick. */
|
|
98
86
|
const DEFAULT_VIDEO_RULES = [];
|
|
99
87
|
/**
|
|
100
|
-
* Default audio chain: the three-tier policy (`preferredAudioLanguage` →
|
|
101
|
-
* `
|
|
102
|
-
*
|
|
103
|
-
* head stays the first track — the same last tier the policy itself ends on.
|
|
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.
|
|
104
91
|
*/
|
|
105
92
|
const preferAudioPolicy = (tracks, { config }) => {
|
|
106
93
|
const id = pickAudioTrackFromTracks(tracks, config);
|
|
@@ -109,53 +96,43 @@ const preferAudioPolicy = (tracks, { config }) => {
|
|
|
109
96
|
};
|
|
110
97
|
const DEFAULT_AUDIO_RULES = [preferAudioPolicy];
|
|
111
98
|
/**
|
|
112
|
-
* Order the candidates by resolution, largest first, with bandwidth breaking ties
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* Exported because it is a *rule*, not a variant's private policy: the same one
|
|
123
|
-
* composes into `switchVideoTrack`'s chain when a ranker is wanted there.
|
|
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.
|
|
124
109
|
*/
|
|
125
110
|
const preferHighestResolution = (tracks) => [...tracks].sort(byDescendingResolution);
|
|
126
111
|
/**
|
|
127
|
-
* Narrow to the renditions that fit the screen, by pixel area — the screen-size
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* unplayable, so nothing here may make a source unplayable.
|
|
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.
|
|
131
115
|
*
|
|
132
|
-
* Narrows only — it neither orders the survivors nor resolves the case where none
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* fitting rendition the manifest happened to list first.
|
|
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.
|
|
137
120
|
*
|
|
138
|
-
* Reading `state.screenResolution` through its signal is what subscribes a
|
|
139
|
-
*
|
|
140
|
-
* pins the first answer instead, by design.
|
|
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.
|
|
141
123
|
*
|
|
142
|
-
* Compares areas rather than matching a `"1080p"`-style tier because a tier only
|
|
143
|
-
*
|
|
144
|
-
* mis-measures an anamorphic ladder. See `media/dom/screen.ts`.
|
|
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`.
|
|
145
126
|
*
|
|
146
127
|
* Three ways the cap ends up not applying, all of them fall-through:
|
|
147
128
|
*
|
|
148
|
-
* - **No `screenResolution` signal at all**, because the composition omits
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* skips the empty result and the chain proceeds unnarrowed, so the ranker behind
|
|
156
|
-
* the cap decides — for `preferHighestResolution`, the largest rendition. A floor
|
|
157
|
-
* is the fix if that ever matters (`rendition-selection-caps.md` carries one), not
|
|
158
|
-
* a special case here.
|
|
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.
|
|
159
136
|
*/
|
|
160
137
|
const screenResolutionCap = (tracks, { state }) => {
|
|
161
138
|
const screenResolution = state?.screenResolution?.get();
|
|
@@ -163,24 +140,20 @@ const screenResolutionCap = (tracks, { state }) => {
|
|
|
163
140
|
return tracksUnderPixelArea(tracks, screenResolution.width * screenResolution.height);
|
|
164
141
|
};
|
|
165
142
|
/**
|
|
166
|
-
* Default video constraints: the capability pre-pass alone. No
|
|
167
|
-
* `
|
|
168
|
-
* `failedCdns` has no writer and the constraint would always pass through.
|
|
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.
|
|
169
145
|
*/
|
|
170
146
|
const DEFAULT_VIDEO_CONSTRAINTS = [excludeUnplayableTracks];
|
|
171
147
|
/**
|
|
172
|
-
* Select a video track when a presentation loads. Clears the selection on
|
|
173
|
-
* src unload.
|
|
148
|
+
* Select a video track when a presentation loads. Clears the selection on src unload.
|
|
174
149
|
*
|
|
175
|
-
* This is the simple, non-ABR counterpart to `switchVideoTrack` — compose
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* meaningful quality variants, test setups, or players that intentionally
|
|
180
|
-
* pin a quality.
|
|
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.
|
|
181
154
|
*
|
|
182
155
|
* @example
|
|
183
|
-
*
|
|
156
|
+
* const reactor = selectVideoTrack.setup({ state });
|
|
184
157
|
*/
|
|
185
158
|
const selectVideoTrack = defineBehavior({
|
|
186
159
|
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-tracks.js","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"sourcesContent":["/**\n * **Default audio/video track selection on src load / unselect on src unload.**\n * When a presentation is resolved, sets `selectedVideoTrackId` /\n * `selectedAudioTrackId` from a per-type default rule chain if no selection\n * already exists. When the presentation is unset/reset (transitions back to unresolved),\n * clears the selection so a stale id from the previous source doesn't persist.\n *\n * Lifecycle-driven: the pick fires once per transition, and nothing re-picks —\n * that is what separates these from the `switch*` variants. External writes (user\n * picks, ABR, programmatic filter-driven re-picks) are left alone, including a\n * write naming a track the manifest never offered.\n *\n * The one thing policed between transitions is a pick the *constraints* turn\n * against: a rendition's container and encryption are only known once its media\n * playlist resolves, which is after the pick was made, so a selection that becomes\n * unplayable is dropped. Dropped, never moved — re-picking is exactly the behavior\n * `switchVideoTrack` exists to provide. Dropping reports nothing on its own, since\n * whatever made the pick unplayable already reported its own, more specific cause.\n *\n * Selection runs the same rule model `switchVideoTrack` does — a hard\n * `constraints` pre-pass, then an ordered `rules` chain, with the pick as the\n * head (see `internal/design/spf/track-switching-model.md`). What differs is\n * reactivity, not the rules: this evaluates the chain once on resolve and pins\n * the result, where `switchVideoTrack` re-evaluates inside an effect so its rules\n * subscribe to bandwidth and user selection. A rule written for one therefore\n * composes into the other unchanged.\n *\n * Both are config-driven, each per-type export wiring a sensible default: audio's\n * three-tier language policy, and for video the *empty* chain — with nothing\n * narrowing or reordering, the head is the first candidate. The behavior's\n * `config` is forwarded to the rules, so options like `preferredAudioLanguage`\n * reach them without an intermediate layer.\n *\n * Note a rule can only pick among real candidates, where the picker it replaced\n * could return any id at all. An id absent from the manifest was never\n * selectable, so that narrowing is the point rather than a limitation.\n *\n * Compose `selectVideoTrack` for the simple \"pick a default video track\"\n * behavior, or `switchVideoTrack` (`./track-switching.ts`) for the\n * ABR-driven variant. Compose `selectAudioTrack` for the simple default\n * pick, or `switchAudioTrack` (`./track-switching.ts`) for the\n * filter-reactive + mid-stream-flush slot-owner variant — when audio-abr\n * lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose\n * only one per type — they're alternatives, not stackable (each writes\n * the same `selected*TrackId` slot). The simple variants tree-shake out\n * the heavier machinery (bandwidth estimator, quality selection, flush\n * orchestration).\n *\n * Text selection has no simple variant here — it's owned by `switchTextTrack`\n * (`./track-switching.ts`), which resolves standing `userTextTrackSelection`\n * intent against the constrained, CDN-scoped renditions.\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport type { Resolution } from '../../media/primitives/resolution';\nimport {\n type AudioSelectionConfig,\n byDescendingResolution,\n pickAudioTrackFromTracks,\n type TrackSelectionState,\n tracksUnderPixelArea,\n} from '../../media/primitives/select-tracks';\nimport { isResolvedPresentation, type TrackType } from '../../media/types';\nimport { getTracksByType } from '../../media/utils/tracks';\nimport {\n applyConstraints,\n applyRules,\n type CapabilityConstraintConfig,\n excludeUnplayableTracks,\n type SelectionRule,\n sameCandidateSet,\n} from '../primitives/selection-rules';\nimport { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackSelection` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => Reactor`. Each `selectXTrack` export below calls\n// it from inside its own `defineBehavior` setup, supplying its per-type\n// `selectedKey`, track type, default rule chain, and forwarded config. The lifecycle\n// — pick on entering 'presentation-resolved' if not already selected; clear\n// on entering 'presentation-unresolved' — is shared.\n// ============================================================================\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId';\n\ntype SelectStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<TrackSelectionState['presentation']>;\n} & { [P in K]: Signal<TrackSelectionState[P]> };\n\n/** A selection rule over this behavior's candidate tracks. */\nexport type SelectTrackRule<Config> = SelectionRule<SelectableTrack, unknown, unknown, Config | undefined>;\n\n/** What a rule here needs off a candidate: the id it may become the pick by. */\ntype SelectableTrack = { id: string };\n\ninterface TrackSelectionSetupConfig<K extends SelectedTrackKey, RuleConfig> {\n selectedKey: K;\n trackType: TrackType;\n constraints: readonly SelectTrackRule<RuleConfig>[];\n rules: readonly SelectTrackRule<RuleConfig>[];\n ruleConfig?: RuleConfig;\n}\n\nfunction setupTrackSelection<K extends SelectedTrackKey, RuleConfig>({\n state,\n config: { selectedKey, trackType, constraints, rules, ruleConfig },\n}: {\n state: SelectStateMap<K>;\n config: TrackSelectionSetupConfig<K, RuleConfig>;\n}) {\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n const deps = { state, config: ruleConfig };\n\n // The playable candidate set — the type's tracks after the hard-constraints\n // pre-pass. A `computed` so it re-evaluates when its inputs change, which is what\n // lets a *pinned* selection still notice it has gone unplayable: `resolve-track`\n // relabels the whole type's container from the first resolved media playlist,\n // long after `entry` made its pick under the fMP4 default.\n //\n // The `equals` gates notification on the set of track ids rather than array\n // identity, matching `setupTrackSwitching`'s. Segment appends and live reloads\n // both swap in a new presentation object carrying the same variants; without\n // this the effect below would re-run on every one of them.\n const candidateSet = computed(\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return [];\n return applyConstraints(constraints, getTracksByType(presentation, trackType), deps);\n },\n { equals: sameCandidateSet }\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Entry: pick a default on entering presentation-resolved if none\n // is set. External writes (user picks, ABR) that already populated\n // the slot are left alone.\n //\n // The returned cleanup runs on state exit — which fires on src\n // unload (presentation-resolved → presentation-unresolved) AND on\n // behavior destroy (presentation-resolved → destroying →\n // destroyed). Putting the clear here rather than as\n // presentation-unresolved.entry is more cohesive (operation +\n // cleanup co-located) and correctly covers destroy (destroy\n // doesn't pass through presentation-unresolved).\n entry: () => {\n if (!state[selectedKey].get()) {\n // `state.presentation.get()` is non-null inside this entry —\n // the reactor's `'presentation-resolved'` gate is exactly\n // `isResolvedPresentation(state.presentation.get())`, which\n // requires a truthy Presentation.\n //\n // Constraints prune the unplayable, then the chain narrows and ranks;\n // the pick is the head. An empty `rules` chain therefore selects the\n // first candidate, which falls out of the model rather than needing a\n // first-track code path of its own.\n const survivors = applyRules(rules, peek(candidateSet), deps);\n const id = survivors[0]?.id;\n if (id) state[selectedKey].set(id);\n }\n return () => state[selectedKey].set(undefined);\n },\n effects: [\n // Selection is `entry`'s alone — this only ever *de*selects. Keeping the\n // pick out of a reaction is what makes this the pinned variant rather\n // than a worse-spelled `switchVideoTrack`: nothing here re-ranks or moves\n // the pin. It has to be a reaction all the same, because what it watches\n // for is learned late — container and encryption come from a rendition's\n // media playlist, which resolves after the pick was made.\n //\n // Deliberately does *not* report why. Whatever made the pick unplayable\n // reported its own cause as the playlist resolved (1004 container, 4008\n // encryption, via `reportUnsupportedTrackConditions`), which is both more\n // specific than a verdict and already logged. The one condition no cause\n // covers is nothing being playable at all — no rendition resolved, so\n // none reported — which is why that alone emits here. See\n // `internal/design/spf/features/errors.md`.\n () => {\n // Untracked: writing the slot below must not re-enter this reaction.\n const selectedId = peek(state[selectedKey]);\n if (!selectedId) return;\n if (candidateSet.get().some((track) => track.id === selectedId)) return;\n\n // Only a pick the source actually offers is this behavior's to drop. An\n // id absent from the manifest was never selectable, and external writes\n // are left alone — see this module's header.\n const presentation = peek(state.presentation);\n if (\n isResolvedPresentation(presentation) &&\n getTracksByType(presentation, trackType).some((track) => track.id === selectedId)\n ) {\n state[selectedKey].set(undefined);\n }\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Default rules\n//\n// Each variant resolves its chain as `config?.rules ?? <default>`. The whole\n// behavior config is forwarded as the rules' `config`, so a policy rule reads\n// its own options (`preferredAudioLanguage`) directly off it.\n//\n// Video's default is the *empty* chain: with no rule narrowing or reordering the\n// candidates, the head is the first track — a consequence of the model rather than\n// a first-track code path of its own.\n// ============================================================================\n\n/** Default video chain: none. The first candidate is the pick. */\nconst DEFAULT_VIDEO_RULES: readonly SelectTrackRule<SelectVideoTrackConfig>[] = [];\n\n/**\n * Default audio chain: the three-tier policy (`preferredAudioLanguage` →\n * `DEFAULT=YES` → first) as a single narrowing rule. Returning `[]` when nothing\n * is picked lets `applyRules` fall through to the unnarrowed candidates, so the\n * head stays the first track — the same last tier the policy itself ends on.\n */\nconst preferAudioPolicy: SelectTrackRule<SelectAudioTrackConfig> = (tracks, { config }) => {\n const id = pickAudioTrackFromTracks(tracks as readonly { id: string }[], config);\n const pick = tracks.find((track) => track.id === id);\n return pick ? [pick] : [];\n};\n\nconst DEFAULT_AUDIO_RULES: readonly SelectTrackRule<SelectAudioTrackConfig>[] = [preferAudioPolicy];\n\n/**\n * Order the candidates by resolution, largest first, with bandwidth breaking ties\n * between renditions of identical dimensions. The background-video default — that\n * variant pins one rendition for the session, and absent a cap the largest is the\n * head.\n *\n * A ranker, so it reorders rather than narrowing: the chain's pick is the head of\n * what it returns, which means ranking never has to collapse to one track. Belongs\n * last in a chain — a sort only reorders what survived the filters ahead of it, and\n * leaving it last is what lets `applyRules` early-bail before it runs.\n *\n * Exported because it is a *rule*, not a variant's private policy: the same one\n * composes into `switchVideoTrack`'s chain when a ranker is wanted there.\n */\nexport const preferHighestResolution: SelectTrackRule<unknown> = (tracks) => [...tracks].sort(byDescendingResolution);\n\n/** What {@link screenResolutionCap} reads off the composition state. */\ntype ScreenResolutionRuleState = {\n screenResolution?: ReadonlySignal<Resolution | undefined>;\n};\n\n/**\n * Narrow to the renditions that fit the screen, by pixel area — the screen-size\n * cap from `internal/design/spf/features/rendition-selection-caps.md`, as a scope\n * (soft filter) rather than a constraint: an over-cap rendition is wasteful, not\n * unplayable, so nothing here may make a source unplayable.\n *\n * Narrows only — it neither orders the survivors nor resolves the case where none\n * survive, because `applyRules` owns both. So it needs a ranker behind it to pick\n * within the cap: `[screenResolutionCap, preferHighestResolution]` yields the\n * largest rendition that fits. Composed *last*, the pick would instead be whichever\n * fitting rendition the manifest happened to list first.\n *\n * Reading `state.screenResolution` through its signal is what subscribes a\n * re-evaluating chain (`switchVideoTrack`) to screen changes; `selectVideoTrack`\n * pins the first answer instead, by design.\n *\n * Compares areas rather than matching a `\"1080p\"`-style tier because a tier only\n * describes a rendition once you assume its aspect ratio — the assumption that\n * mis-measures an anamorphic ladder. See `media/dom/screen.ts`.\n *\n * Three ways the cap ends up not applying, all of them fall-through:\n *\n * - **No `screenResolution` signal at all**, because the composition omits\n * `trackScreenResolution`. So composing the cap without its signal source is\n * inert rather than broken.\n * - **A `screenResolution` of `undefined`**, meaning no screen to read. \"Unknown\"\n * has to mean \"don't cap\": treating it as an area of zero would pin every source\n * to its smallest rendition on exactly the environments we know least about.\n * - **No rendition fits**, on a screen smaller than the whole ladder. `applyRules`\n * skips the empty result and the chain proceeds unnarrowed, so the ranker behind\n * the cap decides — for `preferHighestResolution`, the largest rendition. A floor\n * is the fix if that ever matters (`rendition-selection-caps.md` carries one), not\n * a special case here.\n */\nexport const screenResolutionCap: SelectTrackRule<unknown> = (tracks, { state }) => {\n const screenResolution = (state as ScreenResolutionRuleState | undefined)?.screenResolution?.get();\n if (!screenResolution) return [];\n\n return tracksUnderPixelArea(tracks, screenResolution.width * screenResolution.height);\n};\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Config for `selectVideoTrack`. Pass `rules` to replace the selection chain, or\n * `constraints` to replace the capability pre-pass; otherwise the chain is empty\n * and the first playable candidate is the pick.\n */\nexport interface SelectVideoTrackConfig extends CapabilityConstraintConfig {\n constraints?: readonly SelectTrackRule<SelectVideoTrackConfig>[];\n rules?: readonly SelectTrackRule<SelectVideoTrackConfig>[];\n}\n\n/**\n * Default video constraints: the capability pre-pass alone. No\n * `excludeFailedCdns` — this variant's compositions run no failover monitor, so\n * `failedCdns` has no writer and the constraint would always pass through.\n */\nconst DEFAULT_VIDEO_CONSTRAINTS: readonly SelectTrackRule<SelectVideoTrackConfig>[] = [excludeUnplayableTracks];\n\n/**\n * Select a video track when a presentation loads. Clears the selection on\n * src unload.\n *\n * This is the simple, non-ABR counterpart to `switchVideoTrack` — compose\n * one or the other, not both (both write `selectedVideoTrackId`). Composing\n * `selectVideoTrack` alone tree-shakes out the ABR code path\n * (bandwidth-estimator, quality-selection); use it for sources without\n * meaningful quality variants, test setups, or players that intentionally\n * pin a quality.\n *\n * @example\n * const reactor = selectVideoTrack.setup({ state });\n */\nexport const selectVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedVideoTrackId'>; config?: SelectVideoTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n selectedKey: VIDEO_TYPE_CONFIG.selectedKey,\n trackType: 'video',\n constraints: config?.constraints ?? DEFAULT_VIDEO_CONSTRAINTS,\n rules: config?.rules ?? DEFAULT_VIDEO_RULES,\n ruleConfig: config,\n },\n }),\n});\n\n/**\n * Config for `selectAudioTrack`. Pass `rules` to replace the selection chain, or\n * `constraints` to prune candidates before it runs; otherwise the default\n * three-tier policy applies (`preferredAudioLanguage` → `DEFAULT=YES` → first).\n */\nexport interface SelectAudioTrackConfig extends AudioSelectionConfig {\n constraints?: readonly SelectTrackRule<SelectAudioTrackConfig>[];\n rules?: readonly SelectTrackRule<SelectAudioTrackConfig>[];\n}\n\n/**\n * Select an audio track when a presentation loads. Clears the selection\n * on src unload.\n *\n * This is the simple, lifecycle-only counterpart to `switchAudioTrack`\n * (in `./track-switching.ts`) — compose one or the other, not both\n * (both write `selectedAudioTrackId`). `switchAudioTrack` adds\n * filter-reactivity (`userAudioTrackSelection`) and mid-stream-flush\n * orchestration; `selectAudioTrack` covers the default-on-load case\n * without those. Use this variant for test setups, audio-only flows\n * that don't expose language switching, or composition variants that\n * intentionally pin a track.\n *\n * @example\n * const reactor = selectAudioTrack.setup({ state });\n *\n * @example\n * // Language preference, honored by the default audio policy rule\n * const reactor = selectAudioTrack.setup({\n * state,\n * config: { preferredAudioLanguage: 'en' },\n * });\n */\nexport const selectAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedAudioTrackId'>; config?: SelectAudioTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n selectedKey: AUDIO_TYPE_CONFIG.selectedKey,\n trackType: 'audio',\n constraints: config?.constraints ?? [],\n rules: config?.rules ?? DEFAULT_AUDIO_RULES,\n ruleConfig: config,\n },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GA,SAAS,oBAA4D,EACnE,OACA,QAAQ,EAAE,aAAa,WAAW,aAAa,OAAO,gBAIrD;CACD,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;CAEA,MAAM,OAAO;EAAE;EAAO,QAAQ;CAAW;CAYzC,MAAM,eAAe,eACb;EACJ,MAAM,eAAe,MAAM,aAAa,IAAI;EAC5C,IAAI,CAAC,uBAAuB,YAAY,GAAG,OAAO,CAAC;EACnD,OAAO,iBAAiB,aAAa,gBAAgB,cAAc,SAAS,GAAG,IAAI;CACrF,GACA,EAAE,QAAQ,iBAAiB,CAC7B;CAEA,OAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,2BAA2B,CAAC;GAC5B,yBAAyB;IAYvB,aAAa;KACX,IAAI,CAAC,MAAM,YAAY,CAAC,IAAI,GAAG;MAW7B,MAAM,KADY,WAAW,OAAO,KAAK,YAAY,GAAG,IACrC,CAAC,CAAC,EAAE,EAAE;MACzB,IAAI,IAAI,MAAM,YAAY,CAAC,IAAI,EAAE;KACnC;KACA,aAAa,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;IAC/C;IACA,SAAS,OAeD;KAEJ,MAAM,aAAa,KAAK,MAAM,YAAY;KAC1C,IAAI,CAAC,YAAY;KACjB,IAAI,aAAa,IAAI,CAAC,CAAC,MAAM,UAAU,MAAM,OAAO,UAAU,GAAG;KAKjE,MAAM,eAAe,KAAK,MAAM,YAAY;KAC5C,IACE,uBAAuB,YAAY,KACnC,gBAAgB,cAAc,SAAS,CAAC,CAAC,MAAM,UAAU,MAAM,OAAO,UAAU,GAEhF,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;IAEpC,CACF;GACF;EACF;CACF,CAAC;AACH;;AAeA,MAAM,sBAA0E,CAAC;;;;;;;AAQjF,MAAM,qBAA8D,QAAQ,EAAE,aAAa;CACzF,MAAM,KAAK,yBAAyB,QAAqC,MAAM;CAC/E,MAAM,OAAO,OAAO,MAAM,UAAU,MAAM,OAAO,EAAE;CACnD,OAAO,OAAO,CAAC,IAAI,IAAI,CAAC;AAC1B;AAEA,MAAM,sBAA0E,CAAC,iBAAiB;;;;;;;;;;;;;;;AAgBlG,MAAa,2BAAqD,WAAW,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCpH,MAAa,uBAAiD,QAAQ,EAAE,YAAY;CAClF,MAAM,mBAAoB,OAAiD,kBAAkB,IAAI;CACjG,IAAI,CAAC,kBAAkB,OAAO,CAAC;CAE/B,OAAO,qBAAqB,QAAQ,iBAAiB,QAAQ,iBAAiB,MAAM;AACtF;;;;;;AAqBA,MAAM,4BAAgF,CAAC,uBAAuB;;;;;;;;;;;;;;;AAgB9G,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,WAAW;GACX,aAAa,QAAQ,eAAe;GACpC,OAAO,QAAQ,SAAS;GACxB,YAAY;EACd;CACF,CAAC;AACL,CAAC;AAmC+B,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,WAAW;GACX,aAAa,QAAQ,eAAe,CAAC;GACrC,OAAO,QAAQ,SAAS;GACxB,YAAY;EACd;CACF,CAAC;AACL,CAAC"}
|
|
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"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import "../../core/signals/primitives.js";
|
|
2
2
|
import "../../index.js";
|
|
3
3
|
//#region src/playback/behaviors/setup-failover-monitor.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Failover policy: how long a CDN stays excluded after a failed fetch trips it.
|
|
6
|
-
* Supplied via engine config.
|
|
7
|
-
*/
|
|
4
|
+
/** Failover policy: how long a CDN stays excluded after a failed fetch trips it. Supplied via engine config. */
|
|
8
5
|
interface FailoverMonitorConfig {
|
|
9
6
|
/** How long a tripped CDN stays excluded, in milliseconds. */
|
|
10
7
|
cooldownMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-failover-monitor.d.ts","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup-failover-monitor.d.ts","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"mappings":";;;;UAmBiB;;EAEf"}
|
|
@@ -4,28 +4,23 @@ import { createMachineReactor } from "../../core/reactors/create-machine-reactor
|
|
|
4
4
|
import { isResolvedPresentation } from "../../media/types/index.js";
|
|
5
5
|
//#region src/playback/behaviors/setup-failover-monitor.ts
|
|
6
6
|
/**
|
|
7
|
-
* **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites
|
|
8
|
-
*
|
|
9
|
-
* `failedCdns`
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's
|
|
13
|
-
* tracks and the active-CDN scope falls to the next one — and back, once the
|
|
14
|
-
* cooldown removes it here.
|
|
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.
|
|
15
12
|
*
|
|
16
|
-
* Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new
|
|
17
|
-
*
|
|
18
|
-
* the
|
|
19
|
-
*
|
|
20
|
-
* anything that reaches `failedCdns` is already terminal).
|
|
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).
|
|
21
17
|
*/
|
|
22
18
|
const DEFAULT_FAILOVER_MONITOR_CONFIG = { cooldownMs: 3e5 };
|
|
23
19
|
/**
|
|
24
|
-
* Expire failed CDNs from `failedCdns` once their cooldown lapses, for the
|
|
25
|
-
* resolved source.
|
|
20
|
+
* Expire failed CDNs from `failedCdns` once their cooldown lapses, for the resolved source.
|
|
26
21
|
*
|
|
27
22
|
* @example
|
|
28
|
-
*
|
|
23
|
+
* const reactor = setupFailoverMonitor.setup({ state });
|
|
29
24
|
*/
|
|
30
25
|
const setupFailoverMonitor = defineBehavior({
|
|
31
26
|
stateKeys: ["presentation", "failedCdns"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-failover-monitor.js","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"sourcesContent":["/**\n * **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites
|
|
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"}
|
|
@@ -7,21 +7,17 @@ import { isStandardPreload } from "../../media/utils/preload.js";
|
|
|
7
7
|
* **Bidirectional sync between `state.preload` and `mediaElement.preload`.**
|
|
8
8
|
*
|
|
9
9
|
* Two effects:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `state.preload` if it's a W3C value and `state.preload` isn't holding
|
|
13
|
-
*
|
|
14
|
-
* `state.preload` is undefined
|
|
15
|
-
*
|
|
16
|
-
* in steady state.
|
|
17
|
-
* - **Write (state → DOM)** — on `state.preload` change or
|
|
18
|
-
* `context.mediaElement` swap, writes `state.preload` back to
|
|
10
|
+
*
|
|
11
|
+
* - **Read (DOM → state)** — on `context.mediaElement` swap or `state.presentation.url` change, copies
|
|
12
|
+
* `mediaElement.preload` into `state.preload` if it's a W3C value and `state.preload` isn't holding an extended
|
|
13
|
+
* (non-W3C) value. When the DOM has no W3C opinion and `state.preload` is undefined, backfills from
|
|
14
|
+
* `config.defaultPreload` (default-default `'metadata'`) so `state.preload` is never undefined in steady state.
|
|
15
|
+
* - **Write (state → DOM)** — on `state.preload` change or `context.mediaElement` swap, writes `state.preload` back to
|
|
19
16
|
* `mediaElement.preload` if the value is W3C.
|
|
20
17
|
*
|
|
21
|
-
* Extended values (e.g. `'canplay'`) written externally to `state.preload`
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* downstream (notably `resolvePresentation`, which reads `state.preload`).
|
|
18
|
+
* Extended values (e.g. `'canplay'`) written externally to `state.preload` are sticky: read won't overwrite them, write
|
|
19
|
+
* won't push them to the DOM. All writes are deduped to break echo loops and avoid spurious re-triggers downstream
|
|
20
|
+
* (notably `resolvePresentation`, which reads `state.preload`).
|
|
25
21
|
*/
|
|
26
22
|
function syncPreloadSetup({ state, context, config }) {
|
|
27
23
|
const defaultPreload = config?.defaultPreload ?? "metadata";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-preload.js","names":[],"sources":["../../../../src/playback/behaviors/sync-preload.ts"],"sourcesContent":["/**\n * **Bidirectional sync between `state.preload` and `mediaElement.preload`.**\n *\n * Two effects:\n * - **Read (DOM → state)** — on `context.mediaElement` swap or
|
|
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\n * `mediaElement.preload` into `state.preload` if it's a W3C value and `state.preload` isn't holding an extended\n * (non-W3C) value. When the DOM has no W3C opinion and `state.preload` is undefined, backfills from\n * `config.defaultPreload` (default-default `'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 const target: StandardPreload | undefined =\n mediaElement && isStandardPreload(mediaElement.preload)\n ? mediaElement.preload\n : current === undefined\n ? defaultPreload\n : undefined;\n if (target === undefined || target === current) return;\n\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":";;;;;;;;;;;;;;;;;;;;;AAiCA,SAAS,iBAAiB,EACxB,OACA,SACA,UAQa;CACb,MAAM,iBAAkC,QAAQ,kBAAA;CAChD,MAAM,kBAAkB,eAAe,MAAM,aAAa,IAAI,CAAC,EAAE,GAAG;CAUpE,MAAM,cAAc,aAAa;EAC/B,gBAAgB,IAAI;EACpB,MAAM,eAAe,QAAQ,aAAa,IAAI;EAG9C,MAAM,UAAU,KAAK,MAAM,OAAO;EAElC,IAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,OAAO,GAAG;EAE1D,MAAM,SACJ,gBAAgB,kBAAkB,aAAa,OAAO,IAClD,aAAa,UACb,YAAY,KAAA,IACV,iBACA,KAAA;EACR,IAAI,WAAW,KAAA,KAAa,WAAW,SAAS;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"}
|