@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"establish-start-media-time.js","names":[],"sources":["../../../../src/playback/behaviors/establish-start-media-time.ts"],"sourcesContent":["/**\n * Establish the presentation's coordinate origin — the `startTime` /\n * `startMediaTime` / `startDate` triple — once per source, so every track lands\n * on one 0-based presentation timeline. One establishment unit with an internal\n * order: the wall-clock **anchor** settles (live PDT sources), gated parses\n * align to it, then `startMediaTime` latches — see\n * `internal/design/spf/live-presentation-timeline-model.md`.\n *\n * The two coordinate stamps it owns:\n * - **`startDate` (wall-clock anchor).** The designated *reference track*\n * (selected video, else audio) resolves first; its parser-computed\n * `startDate` — PDT at presentation-0 — is frozen as the anchor and stamped\n * onto every other track that lacks one, so their first parses place via\n * `placeOnAnchor` (the pre-applied-anchor path in `parse-media-playlist`)\n * and later-selected renditions resolve already aligned. Ordering is\n * enforced by {@link gateFirstParseOnAnchor}, which `resolve-track` awaits\n * through its injected gate seam. Absent PDT (ordinary VOD), no anchor is\n * stamped and local-from-0 placement is already correct.\n * - **`startMediaTime` (decode origin).** Runs the injected\n * {@link DeriveStartMediaTime} seam over the transient `mediaContainerData`\n * slot (owned here; `inactive` clears it per source) and stamps the settled\n * value onto the model until `established`. The byte-level discover/stamp\n * steps that fill the slot are a separate, config `messagePipelines` array\n * (`primitives/relocation-pipelines`); the two coordinate only through the\n * shared `state.mediaContainerData` slot, never by import. See\n * `internal/design/spf/presentation-timeline-model.md`.\n *\n * DOM-free reactor; sticky per source (`inactive → monitoring → established`).\n */\nimport type { Behavior } from '../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../core/reactors/create-machine-reactor';\nimport { type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport {\n isResolvedPresentation,\n isResolvedTrack,\n type MaybeResolvedPresentation,\n type MediaContainerData,\n type Presentation,\n} from '../../media/types';\nimport { findTrackById } from '../../media/utils/tracks';\nimport type { DeriveStartMediaTime, DeriveStartMediaTimeContext } from '../primitives/derive-start-media-time';\nimport type { GateFirstParse, GateFirstParseContext } from '../primitives/gate-first-parse';\n\nexport type { DeriveStartMediaTime, DeriveStartMediaTimeContext, GateFirstParse, GateFirstParseContext };\n\n// ============================================================================\n// STATE / CONFIG\n// ============================================================================\n\nexport interface EstablishStartMediaTimeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * Transient origin-establishment data, keyed by **track type** (`'video'` /\n * `'audio'`) — per spec one init+media pair per type suffices, and ABR rungs of\n * a type share the origin. Filled by the discover steps across appends, reset\n * per source. Never the model — the churn stays here; only the settled\n * `startMediaTime` reaches `Track`.\n */\n mediaContainerData?: Record<string, MediaContainerData>;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * A single type's own media-timeline origin:\n * `baseMediaDecodeTime/timescale − segmentStartTime` (the `segmentStartTime` term\n * makes it the stream origin even when the first loaded segment isn't the 0th).\n * `undefined` until timescale + baseMediaDecodeTime + segmentStartTime are all present.\n */\nfunction ownOrigin(data: MediaContainerData | undefined): number | undefined {\n const { timescale, baseMediaDecodeTime, segmentStartTime } = data ?? {};\n return timescale != null && baseMediaDecodeTime != null && segmentStartTime != null\n ? baseMediaDecodeTime / timescale - segmentStartTime\n : undefined;\n}\n\n/**\n * Origins below this magnitude (seconds) are treated as `0` — the derive returns `0`, so\n * the presentation is left on its native (~0-based) timeline and no `timestampOffset` is\n * set (the loader stamp no-ops on a derived `0`). Ordinary VOD carries a small nonzero\n * encode origin (audio priming, first-frame CTS, edit lists); relocating by a sub-second\n * amount is pointless, and setting `timestampOffset` at all can ripple edge segments.\n * Relocation targets streams with an intentional large origin (instant clips, bipbop @10s).\n *\n * Absolute basis, not proportional: the DTS-below-zero / ripple risk scales with the\n * origin's absolute size, not with the presentation's duration. Also snaps negatives to\n * `0` — a negative origin would relocate *forward*, which is never the intent.\n */\nexport const NEAR_ZERO_ORIGIN_THRESHOLD = 1;\n\n/** Snap a below-threshold (incl. negative) origin to `0` so it isn't relocated. */\nfunction thresholdOrigin(origin: number): number {\n return origin < NEAR_ZERO_ORIGIN_THRESHOLD ? 0 : origin;\n}\n\n/**\n * The **default** — relocate the whole presentation by one shared origin: the `min`\n * across the *selected* A/V tracks' own origins, denormalized onto every type. This\n * single reduce subsumes the \"per-type\" and \"shared\" tiers:\n * - **aligned A/V** — `min` equals each origin (they're equal), so it matches per-type;\n * - **skewed A/V** (e.g. Apple's 44ms audio-lead) — `min` keeps every track's earliest\n * DTS ≥ 0 (relocating by ≤ each own origin never drives one negative) *and* preserves\n * the real skew (per-type would flatten it, desyncing A/V);\n * - **single type / muxed** — `min` of the one origin is that origin.\n *\n * Returns `undefined` for every type until all *selected* types have a complete origin\n * (the shared-`min` barrier). Which types must contribute is read from `ctx` (the\n * selected v/a ids); with no selection context it coordinates across whatever types\n * have data. A shared origin below {@link NEAR_ZERO_ORIGIN_THRESHOLD} is returned as `0`\n * (native — ordinary ~0-PTS VOD isn't relocated).\n */\nexport const deriveSharedMinStartMediaTime: DeriveStartMediaTime = (containerData, ctx) => {\n const contributingTypes: string[] = [];\n if (ctx.selectedVideoTrackId != null) contributingTypes.push('video');\n if (ctx.selectedAudioTrackId != null) contributingTypes.push('audio');\n const types = contributingTypes.length > 0 ? contributingTypes : Object.keys(containerData);\n\n const origins = types.map((type) => ownOrigin(containerData[type]));\n // Barrier: not ready until every contributing type has a complete origin.\n if (origins.length === 0 || origins.some((origin) => origin === undefined)) return {};\n\n const shared = thresholdOrigin(Math.min(...(origins as number[])));\n const out: Record<string, number | undefined> = {};\n for (const type of Object.keys(containerData)) out[type] = shared;\n return out;\n};\n\n/**\n * Coordination-axis *off* — each type relocates by its own origin, independently. Not\n * the default: it flattens real A/V skew (see {@link deriveSharedMinStartMediaTime}).\n * Kept as an opt-in for compositions that know their A/V is aligned and want to skip\n * the shared-`min` barrier (each type stamps as soon as its own origin is discovered).\n *\n * Caution: {@link NEAR_ZERO_ORIGIN_THRESHOLD} applies independently per type here — if\n * two types' origins straddle it, one snaps to `0` and the other doesn't, injecting up\n * to a threshold's worth of A/V offset. Safe for the intended aligned-A/V opt-in; see\n * `internal/design/spf/live-presentation-timeline-model.md` (collision 5).\n */\nexport const derivePerTypeStartMediaTime: DeriveStartMediaTime = (containerData) => {\n const out: Record<string, number | undefined> = {};\n for (const [type, data] of Object.entries(containerData)) {\n const origin = ownOrigin(data);\n out[type] = origin === undefined ? undefined : thresholdOrigin(origin);\n }\n return out;\n};\n\n/**\n * The anchor-source track: the selected video track, falling back to audio\n * (audio-only). Deterministic — never \"first resolved\", which is race-dependent\n * under concurrent resolves.\n */\nfunction referenceTrackId(ctx: GateFirstParseContext): string | undefined {\n return ctx.selectedVideoTrackId ?? ctx.selectedAudioTrackId;\n}\n\n/**\n * The live-anchor first-parse gate (the \"parses align\" step of the establishment\n * order). Open for the reference track itself — its local-from-0 placement *is*\n * the presentation timeline (presentation-0 = the join point). Every other track\n * holds until the reference's first parse settles the anchor question: no PDT →\n * local placement is already correct, proceed; PDT anchor → hold until the\n * establishment reactor stamps it onto this track, so the parse takes the\n * `placeOnAnchor` path. Establishment is sticky-once, so an unanchored first\n * parse would be permanently misaligned — that's the race this gate closes.\n *\n * Pairs with the {@link establishStartMediaTime} reactor (the stamp that opens\n * the anchored branch); wire both or neither.\n */\nexport const gateFirstParseOnAnchor: GateFirstParse = (presentation, ctx, trackId) => {\n const referenceId = referenceTrackId(ctx);\n if (referenceId === undefined || referenceId === trackId) return true;\n if (!isResolvedPresentation(presentation)) return false;\n const reference = findTrackById(presentation, referenceId);\n // A dangling reference id can never anchor — don't deadlock on it.\n if (!reference) return true;\n if (!isResolvedTrack(reference)) return false;\n if (reference.startDate === undefined) return true;\n return findTrackById(presentation, trackId)?.startDate !== undefined;\n};\n\nexport interface EstablishStartMediaTimeConfig {\n /** The reduce seam (coordination knob). Defaults to {@link deriveSharedMinStartMediaTime}. */\n deriveStartMediaTime?: DeriveStartMediaTime;\n}\n\n// ============================================================================\n// REACTOR (owns the transient slot; derives + consumes onto the model)\n// ============================================================================\n\n/**\n * Stamp the frozen wall-clock anchor as `startDate` onto every track that lacks\n * one (idempotent — same reference when nothing moved). Unresolved shells pick\n * it up as the `placeOnAnchor` preset; covering *all* tracks in one pass means\n * any track selected later — an ABR rung, another audio language, late captions\n * — resolves already anchored. The reference track's own parser-computed\n * `startDate` reads back as the anchor, so it's naturally left untouched.\n */\nfunction stampStartDates(presentation: Presentation, anchor: number): Presentation {\n let changed = false;\n const selectionSets = presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => {\n if (track.startDate !== undefined) return track;\n changed = true;\n return { ...track, startDate: anchor };\n }),\n })),\n }));\n return changed ? ({ ...presentation, selectionSets } as Presentation) : presentation;\n}\n\n/** Stamp the derived per-track `startMediaTime` onto the model (idempotent — same reference when nothing moved). */\nfunction stampTracks(presentation: Presentation, startMediaTimes: Record<string, number | undefined>): Presentation {\n let changed = false;\n const selectionSets = presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => {\n const startMediaTime = startMediaTimes[track.type];\n if (startMediaTime === undefined || track.startMediaTime === startMediaTime) return track;\n changed = true;\n return { ...track, startMediaTime };\n }),\n })),\n }));\n return changed ? ({ ...presentation, selectionSets } as Presentation) : presentation;\n}\n\ntype EstablishFsmState = 'inactive' | 'monitoring' | 'established';\n\ninterface EstablishStartMediaTimeDeps {\n state: {\n presentation: Signal<EstablishStartMediaTimeState['presentation']>;\n mediaContainerData: Signal<EstablishStartMediaTimeState['mediaContainerData']>;\n // Optional so the one behavior composes across video-only / audio-only / both,\n // like other cross-track-type behaviors (present at runtime iff a sibling owns it).\n selectedVideoTrackId?: ReadonlySignal<EstablishStartMediaTimeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<EstablishStartMediaTimeState['selectedAudioTrackId']>;\n };\n config?: EstablishStartMediaTimeConfig;\n}\n\nfunction establishStartMediaTimeSetup({\n state,\n config = {},\n}: EstablishStartMediaTimeDeps): Reactor<EstablishFsmState | 'destroying' | 'destroyed'> {\n const derive = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;\n\n const selectionContext = (): DeriveStartMediaTimeContext => ({\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n });\n\n /** Established once the selected A/V tracks (whichever exist) carry `startMediaTime`. */\n const established = (): boolean => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return false;\n const ids = [state.selectedVideoTrackId?.get(), state.selectedAudioTrackId?.get()].filter(\n (id): id is string => id !== undefined\n );\n return ids.length > 0 && ids.every((id) => findTrackById(presentation, id)?.startMediaTime !== undefined);\n };\n\n return createMachineReactor<EstablishFsmState>({\n initial: 'inactive',\n monitor: () => {\n if (!isResolvedPresentation(state.presentation.get())) return 'inactive';\n return established() ? 'established' : 'monitoring';\n },\n states: {\n // Fresh per source: clear the transient slot so a new source re-discovers.\n inactive: { entry: () => state.mediaContainerData.set(undefined) },\n // The establishment order, as two effects: anchor first, derive second.\n // Both stamps are sticky per source — disabled on entry to `established`,\n // and a new source re-enters through `inactive`.\n monitoring: {\n effects: [\n // Anchor: freeze the wall-clock origin from the reference track's\n // first parse and stamp it onto every other track (the gate's open\n // condition + the `placeOnAnchor` preset for shells). Reads the\n // presentation tracked — it must see the reference resolve — and its\n // own write converges immediately (the stamp is idempotent, so the\n // re-run sets an identical value and stops).\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return;\n const referenceId = referenceTrackId(selectionContext());\n const reference = referenceId === undefined ? undefined : findTrackById(presentation, referenceId);\n if (!reference || !isResolvedTrack(reference)) return;\n const anchor = reference.startDate;\n if (anchor === undefined) return;\n update(state.presentation as Signal<MaybeResolvedPresentation>, (current) =>\n stampStartDates(current as Presentation, anchor)\n );\n },\n // Derive: reduce the accumulating container data into per-track startMediaTime\n // and stamp it onto the model. Re-runs as discover fills the slot; the update\n // reads presentation untracked, so no self-loop.\n () => {\n const containerData = state.mediaContainerData.get();\n if (!containerData) return;\n const startMediaTimes = derive(containerData, selectionContext());\n // `current` is always resolved here — the monitor gates `monitoring` on a\n // resolved presentation and transitions before effects re-run — so we cast\n // to `Presentation` rather than re-narrowing.\n update(state.presentation as Signal<MaybeResolvedPresentation>, (current) =>\n stampTracks(current as Presentation, startMediaTimes)\n );\n },\n ],\n },\n established: {},\n },\n });\n}\n\nexport const establishStartMediaTime: Behavior<\n {\n presentation: Signal<EstablishStartMediaTimeState['presentation']>;\n mediaContainerData: Signal<EstablishStartMediaTimeState['mediaContainerData']>;\n },\n Record<never, never>,\n EstablishStartMediaTimeConfig\n> = {\n stateKeys: ['presentation', 'mediaContainerData'],\n contextKeys: [],\n setup: establishStartMediaTimeSetup,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"establish-start-media-time.js","names":[],"sources":["../../../../src/playback/behaviors/establish-start-media-time.ts"],"sourcesContent":["/**\n * Establish the presentation's coordinate origin — the `startTime` / `startMediaTime` / `startDate` triple — once per\n * source, so every track lands on one 0-based presentation timeline. One establishment unit with an internal order: the\n * wall-clock **anchor** settles (live PDT sources), gated parses align to it, then `startMediaTime` latches — see\n * `internal/design/spf/live-presentation-timeline-model.md`.\n *\n * The two coordinate stamps it owns:\n *\n * - **`startDate` (wall-clock anchor).** The designated _reference track_ (selected video, else audio) resolves first;\n * its parser-computed `startDate` — PDT at presentation-0 — is frozen as the anchor and stamped onto every other\n * track that lacks one, so their first parses place via `placeOnAnchor` (the pre-applied-anchor path in\n * `parse-media-playlist`) and later-selected renditions resolve already aligned. Ordering is enforced by\n * {@link gateFirstParseOnAnchor}, which `resolve-track` awaits through its injected gate seam. Absent PDT (ordinary\n * VOD), no anchor is stamped and local-from-0 placement is already correct.\n * - **`startMediaTime` (decode origin).** Runs the injected {@link DeriveStartMediaTime} seam over the transient\n * `mediaContainerData` slot (owned here; `inactive` clears it per source) and stamps the settled value onto the model\n * until `established`. The byte-level discover/stamp steps that fill the slot are a separate, config\n * `messagePipelines` array (`primitives/relocation-pipelines`); the two coordinate only through the shared\n * `state.mediaContainerData` slot, never by import. See `internal/design/spf/presentation-timeline-model.md`.\n *\n * DOM-free reactor; sticky per source (`inactive → monitoring → established`).\n */\nimport type { Behavior } from '../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../core/reactors/create-machine-reactor';\nimport { type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport {\n isResolvedPresentation,\n isResolvedTrack,\n type MaybeResolvedPresentation,\n type MediaContainerData,\n type Presentation,\n} from '../../media/types';\nimport { findTrackById } from '../../media/utils/tracks';\nimport type { DeriveStartMediaTime, DeriveStartMediaTimeContext } from '../primitives/derive-start-media-time';\nimport type { GateFirstParse, GateFirstParseContext } from '../primitives/gate-first-parse';\n\nexport type { DeriveStartMediaTime, DeriveStartMediaTimeContext, GateFirstParse, GateFirstParseContext };\n\n// ============================================================================\n// STATE / CONFIG\n// ============================================================================\n\nexport interface EstablishStartMediaTimeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * Transient origin-establishment data, keyed by **track type** (`'video'` / `'audio'`) — per spec one init+media pair\n * per type suffices, and ABR rungs of a type share the origin. Filled by the discover steps across appends, reset per\n * source. Never the model — the churn stays here; only the settled `startMediaTime` reaches `Track`.\n */\n mediaContainerData?: Record<string, MediaContainerData>;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * A single type's own media-timeline origin: `baseMediaDecodeTime/timescale − segmentStartTime` (the `segmentStartTime`\n * term makes it the stream origin even when the first loaded segment isn't the 0th). `undefined` until timescale +\n * baseMediaDecodeTime + segmentStartTime are all present.\n */\nfunction ownOrigin(data: MediaContainerData | undefined): number | undefined {\n const { timescale, baseMediaDecodeTime, segmentStartTime } = data ?? {};\n\n return timescale != null && baseMediaDecodeTime != null && segmentStartTime != null\n ? baseMediaDecodeTime / timescale - segmentStartTime\n : undefined;\n}\n\n/**\n * Origins below this magnitude (seconds) are treated as `0` — the derive returns `0`, so the presentation is left on\n * its native (~0-based) timeline and no `timestampOffset` is set (the loader stamp no-ops on a derived `0`). Ordinary\n * VOD carries a small nonzero encode origin (audio priming, first-frame CTS, edit lists); relocating by a sub-second\n * amount is pointless, and setting `timestampOffset` at all can ripple edge segments. Relocation targets streams with\n * an intentional large origin (instant clips, bipbop @10s).\n *\n * Absolute basis, not proportional: the DTS-below-zero / ripple risk scales with the origin's absolute size, not with\n * the presentation's duration. Also snaps negatives to `0` — a negative origin would relocate _forward_, which is never\n * the intent.\n */\nexport const NEAR_ZERO_ORIGIN_THRESHOLD = 1;\n\n/** Snap a below-threshold (incl. negative) origin to `0` so it isn't relocated. */\nfunction thresholdOrigin(origin: number): number {\n return origin < NEAR_ZERO_ORIGIN_THRESHOLD ? 0 : origin;\n}\n\n/**\n * The **default** — relocate the whole presentation by one shared origin: the `min` across the _selected_ A/V tracks'\n * own origins, denormalized onto every type. This single reduce subsumes the \"per-type\" and \"shared\" tiers:\n *\n * - **aligned A/V** — `min` equals each origin (they're equal), so it matches per-type;\n * - **skewed A/V** (e.g. Apple's 44ms audio-lead) — `min` keeps every track's earliest DTS ≥ 0 (relocating by ≤ each own\n * origin never drives one negative) _and_ preserves the real skew (per-type would flatten it, desyncing A/V);\n * - **single type / muxed** — `min` of the one origin is that origin.\n *\n * Returns `undefined` for every type until all _selected_ types have a complete origin (the shared-`min` barrier).\n * Which types must contribute is read from `ctx` (the selected v/a ids); with no selection context it coordinates\n * across whatever types have data. A shared origin below {@link NEAR_ZERO_ORIGIN_THRESHOLD} is returned as `0` (native\n * — ordinary ~0-PTS VOD isn't relocated).\n */\nexport const deriveSharedMinStartMediaTime: DeriveStartMediaTime = (containerData, ctx) => {\n const contributingTypes: string[] = [];\n\n if (ctx.selectedVideoTrackId != null) contributingTypes.push('video');\n\n if (ctx.selectedAudioTrackId != null) contributingTypes.push('audio');\n\n const types = contributingTypes.length > 0 ? contributingTypes : Object.keys(containerData);\n\n const origins = types.map((type) => ownOrigin(containerData[type]));\n // Barrier: not ready until every contributing type has a complete origin.\n if (origins.length === 0 || origins.some((origin) => origin === undefined)) return {};\n\n const shared = thresholdOrigin(Math.min(...(origins as number[])));\n const out: Record<string, number | undefined> = {};\n\n for (const type of Object.keys(containerData)) out[type] = shared;\n\n return out;\n};\n\n/**\n * Coordination-axis _off_ — each type relocates by its own origin, independently. Not the default: it flattens real A/V\n * skew (see {@link deriveSharedMinStartMediaTime}). Kept as an opt-in for compositions that know their A/V is aligned\n * and want to skip the shared-`min` barrier (each type stamps as soon as its own origin is discovered).\n *\n * Caution: {@link NEAR_ZERO_ORIGIN_THRESHOLD} applies independently per type here — if two types' origins straddle it,\n * one snaps to `0` and the other doesn't, injecting up to a threshold's worth of A/V offset. Safe for the intended\n * aligned-A/V opt-in; see `internal/design/spf/live-presentation-timeline-model.md` (collision 5).\n */\nexport const derivePerTypeStartMediaTime: DeriveStartMediaTime = (containerData) => {\n const out: Record<string, number | undefined> = {};\n\n for (const [type, data] of Object.entries(containerData)) {\n const origin = ownOrigin(data);\n\n out[type] = origin === undefined ? undefined : thresholdOrigin(origin);\n }\n\n return out;\n};\n\n/**\n * The anchor-source track: the selected video track, falling back to audio (audio-only). Deterministic — never \"first\n * resolved\", which is race-dependent under concurrent resolves.\n */\nfunction referenceTrackId(ctx: GateFirstParseContext): string | undefined {\n return ctx.selectedVideoTrackId ?? ctx.selectedAudioTrackId;\n}\n\n/**\n * The live-anchor first-parse gate (the \"parses align\" step of the establishment order). Open for the reference track\n * itself — its local-from-0 placement _is_ the presentation timeline (presentation-0 = the join point). Every other\n * track holds until the reference's first parse settles the anchor question: no PDT → local placement is already\n * correct, proceed; PDT anchor → hold until the establishment reactor stamps it onto this track, so the parse takes the\n * `placeOnAnchor` path. Establishment is sticky-once, so an unanchored first parse would be permanently misaligned —\n * that's the race this gate closes.\n *\n * Pairs with the {@link establishStartMediaTime} reactor (the stamp that opens the anchored branch); wire both or\n * neither.\n */\nexport const gateFirstParseOnAnchor: GateFirstParse = (presentation, ctx, trackId) => {\n const referenceId = referenceTrackId(ctx);\n if (referenceId === undefined || referenceId === trackId) return true;\n\n if (!isResolvedPresentation(presentation)) return false;\n\n const reference = findTrackById(presentation, referenceId);\n // A dangling reference id can never anchor — don't deadlock on it.\n if (!reference) return true;\n\n if (!isResolvedTrack(reference)) return false;\n\n if (reference.startDate === undefined) return true;\n\n return findTrackById(presentation, trackId)?.startDate !== undefined;\n};\n\nexport interface EstablishStartMediaTimeConfig {\n /** The reduce seam (coordination knob). Defaults to {@link deriveSharedMinStartMediaTime}. */\n deriveStartMediaTime?: DeriveStartMediaTime;\n}\n\n// ============================================================================\n// REACTOR (owns the transient slot; derives + consumes onto the model)\n// ============================================================================\n\n/**\n * Stamp the frozen wall-clock anchor as `startDate` onto every track that lacks one (idempotent — same reference when\n * nothing moved). Unresolved shells pick it up as the `placeOnAnchor` preset; covering _all_ tracks in one pass means\n * any track selected later — an ABR rung, another audio language, late captions — resolves already anchored. The\n * reference track's own parser-computed `startDate` reads back as the anchor, so it's naturally left untouched.\n */\nfunction stampStartDates(presentation: Presentation, anchor: number): Presentation {\n let changed = false;\n const selectionSets = presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => {\n if (track.startDate !== undefined) return track;\n\n changed = true;\n return { ...track, startDate: anchor };\n }),\n })),\n }));\n\n return changed ? ({ ...presentation, selectionSets } as Presentation) : presentation;\n}\n\n/** Stamp the derived per-track `startMediaTime` onto the model (idempotent — same reference when nothing moved). */\nfunction stampTracks(presentation: Presentation, startMediaTimes: Record<string, number | undefined>): Presentation {\n let changed = false;\n const selectionSets = presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => {\n const startMediaTime = startMediaTimes[track.type];\n if (startMediaTime === undefined || track.startMediaTime === startMediaTime) return track;\n\n changed = true;\n return { ...track, startMediaTime };\n }),\n })),\n }));\n\n return changed ? ({ ...presentation, selectionSets } as Presentation) : presentation;\n}\n\ntype EstablishFsmState = 'inactive' | 'monitoring' | 'established';\n\ninterface EstablishStartMediaTimeDeps {\n state: {\n presentation: Signal<EstablishStartMediaTimeState['presentation']>;\n mediaContainerData: Signal<EstablishStartMediaTimeState['mediaContainerData']>;\n // Optional so the one behavior composes across video-only / audio-only / both,\n // like other cross-track-type behaviors (present at runtime iff a sibling owns it).\n selectedVideoTrackId?: ReadonlySignal<EstablishStartMediaTimeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<EstablishStartMediaTimeState['selectedAudioTrackId']>;\n };\n config?: EstablishStartMediaTimeConfig;\n}\n\nfunction establishStartMediaTimeSetup({\n state,\n config = {},\n}: EstablishStartMediaTimeDeps): Reactor<EstablishFsmState | 'destroying' | 'destroyed'> {\n const derive = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;\n\n const selectionContext = (): DeriveStartMediaTimeContext => ({\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n });\n\n /** Established once the selected A/V tracks (whichever exist) carry `startMediaTime`. */\n const established = (): boolean => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return false;\n\n const ids = [state.selectedVideoTrackId?.get(), state.selectedAudioTrackId?.get()].filter(\n (id): id is string => id !== undefined\n );\n\n return ids.length > 0 && ids.every((id) => findTrackById(presentation, id)?.startMediaTime !== undefined);\n };\n\n return createMachineReactor<EstablishFsmState>({\n initial: 'inactive',\n monitor: () => {\n if (!isResolvedPresentation(state.presentation.get())) return 'inactive';\n\n return established() ? 'established' : 'monitoring';\n },\n states: {\n // Fresh per source: clear the transient slot so a new source re-discovers.\n inactive: { entry: () => state.mediaContainerData.set(undefined) },\n // The establishment order, as two effects: anchor first, derive second.\n // Both stamps are sticky per source — disabled on entry to `established`,\n // and a new source re-enters through `inactive`.\n monitoring: {\n effects: [\n // Anchor: freeze the wall-clock origin from the reference track's\n // first parse and stamp it onto every other track (the gate's open\n // condition + the `placeOnAnchor` preset for shells). Reads the\n // presentation tracked — it must see the reference resolve — and its\n // own write converges immediately (the stamp is idempotent, so the\n // re-run sets an identical value and stops).\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return;\n\n const referenceId = referenceTrackId(selectionContext());\n const reference = referenceId === undefined ? undefined : findTrackById(presentation, referenceId);\n if (!reference || !isResolvedTrack(reference)) return;\n\n const anchor = reference.startDate;\n if (anchor === undefined) return;\n\n update(state.presentation as Signal<MaybeResolvedPresentation>, (current) =>\n stampStartDates(current as Presentation, anchor)\n );\n },\n // Derive: reduce the accumulating container data into per-track startMediaTime\n // and stamp it onto the model. Re-runs as discover fills the slot; the update\n // reads presentation untracked, so no self-loop.\n () => {\n const containerData = state.mediaContainerData.get();\n if (!containerData) return;\n\n const startMediaTimes = derive(containerData, selectionContext());\n\n // `current` is always resolved here — the monitor gates `monitoring` on a\n // resolved presentation and transitions before effects re-run — so we cast\n // to `Presentation` rather than re-narrowing.\n update(state.presentation as Signal<MaybeResolvedPresentation>, (current) =>\n stampTracks(current as Presentation, startMediaTimes)\n );\n },\n ],\n },\n established: {},\n },\n });\n}\n\nexport const establishStartMediaTime: Behavior<\n {\n presentation: Signal<EstablishStartMediaTimeState['presentation']>;\n mediaContainerData: Signal<EstablishStartMediaTimeState['mediaContainerData']>;\n },\n Record<never, never>,\n EstablishStartMediaTimeConfig\n> = {\n stateKeys: ['presentation', 'mediaContainerData'],\n contextKeys: [],\n setup: establishStartMediaTimeSetup,\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-presentation.d.ts","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"resolve-presentation.d.ts","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"mappings":";;;;KAwCY,qBAAqB,cAAc,cAAc,8BAA8B"}
|
|
@@ -8,36 +8,27 @@ import { isBlockingPreload } from "../../media/utils/preload.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* **Resolve an unresolved presentation by fetching and parsing its manifest.**
|
|
10
10
|
*
|
|
11
|
-
* Reads `state.presentation`; when it holds `{ url }` (unresolved) and the
|
|
12
|
-
*
|
|
13
|
-
* the
|
|
14
|
-
*
|
|
15
|
-
* composing engine wires in its parser (e.g. the HLS engine supplies the
|
|
16
|
-
* multivariant-playlist parser).
|
|
11
|
+
* Reads `state.presentation`; when it holds `{ url }` (unresolved) and the preload / load-activation gate is met,
|
|
12
|
+
* fetches the manifest, parses it via the **required** `config.parsePresentation`, and writes the resolved
|
|
13
|
+
* `Presentation` back to the same slot. The behavior is format-neutral: the composing engine wires in its parser (e.g.
|
|
14
|
+
* the HLS engine supplies the multivariant-playlist parser).
|
|
17
15
|
*
|
|
18
16
|
* Source-identity-driven, expressed as a 4-state machine:
|
|
19
17
|
*
|
|
20
|
-
*
|
|
21
|
-
* 'preconditions-unmet' → 'idle' → 'resolving' → 'resolved'
|
|
22
|
-
* ```
|
|
18
|
+
* 'preconditions-unmet' → 'idle' → 'resolving' → 'resolved'
|
|
23
19
|
*
|
|
24
20
|
* - `'preconditions-unmet'`: no presentation, or presentation has no URL.
|
|
25
|
-
* - `'idle'`: URL present, unresolved, gate unmet (blocking preload + no
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* and returns the AbortController — the reactor calls `.abort()` on state
|
|
29
|
-
* exit, so source change / gate-close / destroy all cancel cleanly.
|
|
21
|
+
* - `'idle'`: URL present, unresolved, gate unmet (blocking preload + no load-activation). Waits for the gate to open.
|
|
22
|
+
* - `'resolving'`: URL present, unresolved, gate met. Entry starts the fetch and returns the AbortController — the
|
|
23
|
+
* reactor calls `.abort()` on state exit, so source change / gate-close / destroy all cancel cleanly.
|
|
30
24
|
* - `'resolved'`: `state.presentation` holds a resolved `Presentation`.
|
|
31
25
|
*
|
|
32
|
-
* Gate semantics: `state.preload` (or `config.defaultPreload`, default
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `state.loadActivated` is an override — true bypasses the preload gate
|
|
36
|
-
* entirely.
|
|
26
|
+
* Gate semantics: `state.preload` (or `config.defaultPreload`, default `'metadata'`, when state.preload is unset)
|
|
27
|
+
* blocks resolution when its value is `'none'` (see `isBlockingPreload` in `media/utils/preload`).
|
|
28
|
+
* `state.loadActivated` is an override — true bypasses the preload gate entirely.
|
|
37
29
|
*
|
|
38
|
-
* Multi-writer with the engine adapter, which writes the initial unresolved
|
|
39
|
-
*
|
|
40
|
-
* (config-input vs. derived state via fetch) — legitimate multi-writer.
|
|
30
|
+
* Multi-writer with the engine adapter, which writes the initial unresolved `{ url }` to `state.presentation` from src
|
|
31
|
+
* input. Different domains (config-input vs. derived state via fetch) — legitimate multi-writer.
|
|
41
32
|
*/
|
|
42
33
|
function deriveState(presentation, preload, loadActivated, defaultPreload) {
|
|
43
34
|
if (!presentation?.url) return "preconditions-unmet";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-presentation.js","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"sourcesContent":["/**\n * **Resolve an unresolved presentation by fetching and parsing its manifest.**\n *\n * Reads `state.presentation`; when it holds `{ url }` (unresolved) and the
|
|
1
|
+
{"version":3,"file":"resolve-presentation.js","names":[],"sources":["../../../../src/playback/behaviors/resolve-presentation.ts"],"sourcesContent":["/**\n * **Resolve an unresolved presentation by fetching and parsing its manifest.**\n *\n * Reads `state.presentation`; when it holds `{ url }` (unresolved) and the preload / load-activation gate is met,\n * fetches the manifest, parses it via the **required** `config.parsePresentation`, and writes the resolved\n * `Presentation` back to the same slot. The behavior is format-neutral: the composing engine wires in its parser (e.g.\n * the HLS engine supplies the multivariant-playlist parser).\n *\n * Source-identity-driven, expressed as a 4-state machine:\n *\n * 'preconditions-unmet' → 'idle' → 'resolving' → 'resolved'\n *\n * - `'preconditions-unmet'`: no presentation, or presentation has no URL.\n * - `'idle'`: URL present, unresolved, gate unmet (blocking preload + no load-activation). Waits for the gate to open.\n * - `'resolving'`: URL present, unresolved, gate met. Entry starts the fetch and returns the AbortController — the\n * reactor calls `.abort()` on state exit, so source change / gate-close / destroy all cancel cleanly.\n * - `'resolved'`: `state.presentation` holds a resolved `Presentation`.\n *\n * Gate semantics: `state.preload` (or `config.defaultPreload`, default `'metadata'`, when state.preload is unset)\n * blocks resolution when its value is `'none'` (see `isBlockingPreload` in `media/utils/preload`).\n * `state.loadActivated` is an override — true bypasses the preload gate entirely.\n *\n * Multi-writer with the engine adapter, which writes the initial unresolved `{ url }` to `state.presentation` from src\n * input. Different domains (config-input vs. derived state via fetch) — legitimate multi-writer.\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport type { Reactor } from '../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation, type Presentation } from '../../media/types';\nimport { DEFAULT_PRELOAD, isBlockingPreload, type StandardPreload } from '../../media/utils/preload';\nimport { fetchResolvable, getResponseText } from '../../network/fetch';\n\nexport interface PresentationState {\n presentation?: MaybeResolvedPresentation;\n preload?: 'auto' | 'metadata' | 'none' | undefined;\n /** True once a preload-overriding event has fired for the current source — enables resolution regardless of preload. */\n loadActivated?: boolean;\n}\n\nexport type ParsePresentation = (text: string, presentation: MaybeResolvedPresentation) => Presentation;\n\nexport interface ResolvePresentationConfig {\n /**\n * Parses a manifest body into a resolved `Presentation`. **Required** — the behavior is format-neutral and the\n * composing engine supplies its own parser (HLS, DASH, etc.).\n */\n parsePresentation: ParsePresentation;\n /**\n * Fallback used when `state.preload` is unset (undefined / empty) for the resolution-gate decision. Defaults to\n * `'metadata'`, matching `syncPreload`'s own `defaultPreload`.\n */\n defaultPreload?: StandardPreload;\n}\n\nexport type ResolvePresentationState = 'preconditions-unmet' | 'idle' | 'resolving' | 'resolved';\n\nfunction deriveState(\n presentation: MaybeResolvedPresentation | undefined,\n preload: PresentationState['preload'],\n loadActivated: boolean | undefined,\n defaultPreload: StandardPreload\n): ResolvePresentationState {\n if (!presentation?.url) return 'preconditions-unmet';\n\n if (isResolvedPresentation(presentation)) return 'resolved';\n\n const gateOpen = !!loadActivated || !isBlockingPreload(preload, defaultPreload);\n\n return gateOpen ? 'resolving' : 'idle';\n}\n\nfunction resolvePresentationSetup({\n state,\n config,\n}: {\n state: {\n presentation: Signal<PresentationState['presentation']>;\n preload: ReadonlySignal<PresentationState['preload']>;\n loadActivated: ReadonlySignal<PresentationState['loadActivated']>;\n };\n config: ResolvePresentationConfig;\n}): Reactor<ResolvePresentationState | 'destroying' | 'destroyed'> {\n const { parsePresentation } = config;\n const defaultPreload: StandardPreload = config.defaultPreload ?? DEFAULT_PRELOAD;\n\n const derivedStateSignal = computed(() =>\n deriveState(state.presentation.get(), state.preload.get(), state.loadActivated.get(), defaultPreload)\n );\n\n return createMachineReactor<ResolvePresentationState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n idle: {},\n resolving: {\n entry: () => {\n const presentation = state.presentation.get()!;\n const ac = new AbortController();\n\n fetchResolvable(presentation, { signal: ac.signal })\n .then((response) => getResponseText(response))\n .then((text) => {\n const parsed = parsePresentation(text, presentation);\n\n state.presentation.set(parsed);\n })\n .catch((error) => {\n if (error instanceof Error && error.name === 'AbortError') return;\n\n // TODO(error-management): route to a state-error slot once one exists.\n console.error('[resolvePresentation] manifest fetch/parse failed:', error);\n });\n\n return ac;\n },\n },\n resolved: {},\n },\n });\n}\n\nexport const resolvePresentation = defineBehavior({\n stateKeys: ['presentation', 'preload', 'loadActivated'],\n contextKeys: [],\n setup: resolvePresentationSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAS,YACP,cACA,SACA,eACA,gBAC0B;CAC1B,IAAI,CAAC,cAAc,KAAK,OAAO;CAE/B,IAAI,uBAAuB,YAAY,GAAG,OAAO;CAIjD,OAFiB,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,SAAS,cAAc,IAE5D,cAAc;AAClC;AAEA,SAAS,yBAAyB,EAChC,OACA,UAQiE;CACjE,MAAM,EAAE,sBAAsB;CAC9B,MAAM,iBAAkC,OAAO,kBAAA;CAE/C,MAAM,qBAAqB,eACzB,YAAY,MAAM,aAAa,IAAI,GAAG,MAAM,QAAQ,IAAI,GAAG,MAAM,cAAc,IAAI,GAAG,cAAc,CACtG;CAEA,OAAO,qBAA+C;EACpD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GACxB,MAAM,CAAC;GACP,WAAW,EACT,aAAa;IACX,MAAM,eAAe,MAAM,aAAa,IAAI;IAC5C,MAAM,KAAK,IAAI,gBAAgB;IAE/B,gBAAgB,cAAc,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CACjD,MAAM,aAAa,gBAAgB,QAAQ,CAAC,CAAC,CAC7C,MAAM,SAAS;KACd,MAAM,SAAS,kBAAkB,MAAM,YAAY;KAEnD,MAAM,aAAa,IAAI,MAAM;IAC/B,CAAC,CAAC,CACD,OAAO,UAAU;KAChB,IAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc;KAG3D,QAAQ,MAAM,sDAAsD,KAAK;IAC3E,CAAC;IAEH,OAAO;GACT,EACF;GACA,UAAU,CAAC;EACb;CACF,CAAC;AACH;AAEA,MAAa,sBAAsB,eAAe;CAChD,WAAW;EAAC;EAAgB;EAAW;CAAe;CACtD,aAAa,CAAC;CACd,OAAO;AACT,CAAC"}
|
|
@@ -5,8 +5,8 @@ import { createMachineReactor } from "../../core/reactors/create-machine-reactor
|
|
|
5
5
|
import { deriveStreamType, getMediaPlaylistMetadata, isResolvedPresentation, isResolvedTrack } from "../../media/types/index.js";
|
|
6
6
|
import { applyContainerMimeType, findTrack, updateTrackInPresentation } from "../../media/utils/tracks.js";
|
|
7
7
|
import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
|
|
8
|
-
import { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from "../../media/hls/parse-media-playlist.js";
|
|
9
8
|
import { emitError } from "./collect-errors.js";
|
|
9
|
+
import { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from "../../media/hls/parse-media-playlist.js";
|
|
10
10
|
import { fetchResolvableText } from "../../network/fetch.js";
|
|
11
11
|
import { failoverFetch } from "../primitives/failover-fetch.js";
|
|
12
12
|
import { when } from "../../core/signals/when.js";
|
|
@@ -72,9 +72,8 @@ const TEXT_TRACK_RESOLUTION_CONFIG = {
|
|
|
72
72
|
findTrackToResolve: (presentation, trackId) => findTrack(presentation, "text", trackId)
|
|
73
73
|
};
|
|
74
74
|
/**
|
|
75
|
-
* Resolve unresolved video tracks. Schedules a fetch task whenever the
|
|
76
|
-
*
|
|
77
|
-
* writes the resolved track back into `state.presentation`.
|
|
75
|
+
* Resolve unresolved video tracks. Schedules a fetch task whenever the selected video track is partially resolved,
|
|
76
|
+
* parses the manifest, and writes the resolved track back into `state.presentation`.
|
|
78
77
|
*/
|
|
79
78
|
const resolveVideoTrack = defineBehavior({
|
|
80
79
|
stateKeys: ["presentation", "selectedVideoTrackId"],
|
|
@@ -93,10 +92,7 @@ const resolveVideoTrack = defineBehavior({
|
|
|
93
92
|
});
|
|
94
93
|
}
|
|
95
94
|
});
|
|
96
|
-
/**
|
|
97
|
-
* Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,
|
|
98
|
-
* narrowed to audio.
|
|
99
|
-
*/
|
|
95
|
+
/** Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`, narrowed to audio. */
|
|
100
96
|
const resolveAudioTrack = defineBehavior({
|
|
101
97
|
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
102
98
|
contextKeys: [],
|
|
@@ -114,10 +110,7 @@ const resolveAudioTrack = defineBehavior({
|
|
|
114
110
|
});
|
|
115
111
|
}
|
|
116
112
|
});
|
|
117
|
-
/**
|
|
118
|
-
* Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,
|
|
119
|
-
* narrowed to text.
|
|
120
|
-
*/
|
|
113
|
+
/** Resolve unresolved text tracks. Same shape as `resolveVideoTrack`, narrowed to text. */
|
|
121
114
|
const resolveTextTrack = defineBehavior({
|
|
122
115
|
stateKeys: ["presentation", "selectedTextTrackId"],
|
|
123
116
|
contextKeys: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-track.js","names":["defaultFetchResolvableText","fetchResolvableText"],"sources":["../../../../src/playback/behaviors/resolve-track.ts"],"sourcesContent":["import { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { when } from '../../core/signals/when';\nimport { RecurringRunner, type Reschedule, runOnce, Task } from '../../core/tasks/task';\nimport { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from '../../media/hls/parse-media-playlist';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack, ResolvedTrack } from '../../media/types';\nimport { deriveStreamType, getMediaPlaylistMetadata, isResolvedPresentation, isResolvedTrack } from '../../media/types';\nimport type { GetCdnId } from '../../media/utils/cdn';\nimport { applyContainerMimeType, findTrack, updateTrackInPresentation } from '../../media/utils/tracks';\nimport { fetchResolvableText as defaultFetchResolvableText, type FetchText } from '../../network/fetch';\nimport { failoverFetch } from '../primitives/failover-fetch';\nimport type { GateFirstParse } from '../primitives/gate-first-parse';\nimport type { ReportUnsupportedTrackConditions } from '../primitives/report-track-conditions';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\nimport { type ErrorEmitterState, emitError } from './collect-errors';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackResolution` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => cleanup`. Each `resolveXTrack` export below calls it\n// from inside its own `defineBehavior` setup, supplying its per-type config\n// inline. The orchestration — gate on a selection, short-circuit when the\n// track is already resolved or missing, schedule the fetch+parse, and patch\n// the resolved track back into `state.presentation` — is shared.\n// ============================================================================\n\n/**\n * State shape for track resolution. Uses `MaybeResolvedPresentation` so it\n * matches the engine's slot type; resolution narrows internally.\n */\nexport interface ResolveTrackState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n failedCdns?: string[];\n}\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\ntype ResolveTrackStateMap<K extends SelectedTrackKey> = {\n presentation: Signal<ResolveTrackState['presentation']>;\n} & { [P in K]: ReadonlySignal<ResolveTrackState[P]> };\n\n/**\n * Sibling-owned A/V selection signals, present at runtime iff a sibling\n * behavior owns them. Deliberately not in the typed slice / `stateKeys`\n * (declaring them would force every composition to carry both selections);\n * read only to build the injected first-parse gate's selection context.\n */\ntype SiblingSelectionSignals = {\n selectedVideoTrackId?: ReadonlySignal<ResolveTrackState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<ResolveTrackState['selectedAudioTrackId']>;\n};\n\ninterface TrackResolutionConfig<K extends SelectedTrackKey> {\n selectedKey: K;\n findTrackToResolve: (\n presentation: MaybeResolvedPresentation,\n trackId: string\n ) => PartiallyResolvedTrack | ResolvedTrack | undefined;\n /** Fetch a track's media-playlist text — already failover-decorated by the behavior. */\n fetchResolvableText?: FetchText;\n /** Holds a track's first parse until its placement inputs settle (live anchor); absent → parse immediately. */\n gateFirstParse?: GateFirstParse;\n /** Live re-run policy for the `RecurringRunner`; absent → resolve once (VOD). */\n reschedule?: Reschedule<ResolvedTrack>;\n /**\n * Report conditions found in the parsed playlist (see\n * `primitives/report-track-conditions`); absent → report nothing.\n */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n}\n\n/**\n * Engine-config slice each `resolve*` behavior reads to build its failover-\n * decorated playlist fetch.\n */\ninterface ResolveTrackConfig {\n /** CDN-id derivation for the failover trip; defaults to origin-based `getCdnId`. */\n getCdnId?: GetCdnId;\n /** First-parse placement gate (see `primitives/gate-first-parse`); absent → parse immediately. */\n gateFirstParse?: GateFirstParse;\n /** Live media-playlist re-run policy; absent → resolve once. */\n reschedule?: Reschedule<ResolvedTrack>;\n /** Playlist-derived condition reporting (see `primitives/report-track-conditions`). */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n}\n\nfunction setupTrackResolution<K extends SelectedTrackKey>({\n state,\n config: {\n selectedKey,\n findTrackToResolve,\n fetchResolvableText = defaultFetchResolvableText,\n gateFirstParse,\n reschedule,\n reportUnsupportedTrackConditions,\n },\n}: {\n // Widened with the optional `errors` slot: reporting writes through it without\n // the behavior declaring ownership, and no-ops when `collectErrors` isn't\n // composed. Same contract as `failedCdns` / `failoverFetch`.\n state: ResolveTrackStateMap<K> & ErrorEmitterState;\n config: TrackResolutionConfig<K>;\n}) {\n // Recurrence lives in the runner: with a `reschedule` (live) it re-runs the\n // task until the policy stops; `runOnce` (VOD) runs it exactly once. Single-\n // slot — a selection change re-schedules (abort-and-replace).\n const runner = new RecurringRunner<ResolvedTrack>(reschedule ?? runOnce);\n\n // Reactor states model the FSM the previous effect-based body was\n // hand-rolling. 'presentation-resolved' is entered when the\n // presentation is fully parsed (has a Ham id + selectionSets); leaving\n // it (presentation cleared or reset to an unresolved value) aborts all\n // in-flight tasks via the entry-cleanup. Most URL changes go through\n // 'presentation-unresolved' naturally (set undefined → set new partial\n // → re-parse), so the common case is covered by state-exit alone; the\n // task body's commit-time id check covers the pathological\n // resolved→resolved-without-unresolved transition.\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // `entry` runs on state entry; the function it returns is the\n // state-exit cleanup. Returning `() => runner.abortAll()` binds\n // abort-of-in-flight-resolutions to leaving 'presentation-resolved'\n // (presentation cleared/reset, or behavior destroyed) —\n // source-change cancellation expressed structurally through the\n // state machine.\n entry: () => () => runner.abortAll(),\n effects: [\n () => {\n // The reactor's state transitions handle relevant presentation\n // changes (presentation-resolved ↔ presentation-unresolved);\n // within 'presentation-resolved' we peek (untracked read) so\n // internal updates (segments added by sibling tasks, or by this\n // behavior's own reload cycles) don't re-fire the effect.\n const presentation = peek(state.presentation);\n const trackId = state[selectedKey].get();\n if (!presentation || !trackId) return;\n\n const track = findTrackToResolve(presentation, trackId);\n // Skip a complete or missing track; a resolved-but-incomplete (live)\n // window still reloads — its window may have slid past the playhead.\n if (!track || (isResolvedTrack(track) && Number.isFinite(track.duration))) return;\n\n // Abort (selection/source change) settles quietly; a genuine resolve\n // failure rejects — swallowed for now (TODO: surface to state).\n const scheduled = runner.schedule(\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createResolveTrackTask(track, context, config)),\n // likely eventually passed down via config or a new \"definitions\" argument (CJP).\n //\n // Re-runs (clones) on each reload, so the body re-reads the live\n // snapshot (via `trackId`) rather than capturing the gate-time\n // `track` — a reload carries the prior window's timeline forward.\n new Task(\n async (signal) => {\n const snapshot = peek(state.presentation);\n const current = snapshot ? findTrackToResolve(snapshot, trackId) : undefined;\n if (!current) throw new Error('resolve-track: selected track not found');\n\n // `fetchResolvableText` is the behavior's failover-decorated\n // fetch: it trips the CDN on a failed fetch (network error or\n // non-OK status). A parse failure is a content issue, not a\n // CDN-availability one, so it doesn't trip. The run-time\n // `current` supplies only the playlist URL (stable across the\n // fetch).\n const text = await fetchResolvableText(current, { signal });\n\n // Hold placement — never the fetch — until this track's\n // first-parse placement inputs settle (for live, the\n // wall-clock anchor; see `primitives/gate-first-parse`).\n // First parses only — a reload's window is already on the\n // established timeline. Aborting rejects the wait, so a\n // source change can't strand a gated task.\n if (gateFirstParse && !isResolvedTrack(current)) {\n const { selectedVideoTrackId, selectedAudioTrackId } = state as SiblingSelectionSignals;\n await when(\n () =>\n gateFirstParse(\n state.presentation.get(),\n {\n selectedVideoTrackId: selectedVideoTrackId?.get(),\n selectedAudioTrackId: selectedAudioTrackId?.get(),\n },\n trackId\n ),\n { signal }\n );\n }\n\n // Re-read `previous` after the awaits: a concurrent write\n // during them — notably the establishment reactor stamping\n // the anchor `startDate` onto this track's shell — must feed\n // the parse, not be clobbered (anchoring is establish-once;\n // parsing the pre-fetch snapshot would strand the track off\n // the anchor for good). Correctness rests on a\n // run-to-completion invariant: NOTHING may yield (await)\n // between this re-read and the write below, so no writer can\n // interleave. `parseMediaPlaylist` is synchronous — keep it\n // that way, or move the read into the updater.\n const live = peek(state.presentation);\n const previous = live ? findTrackToResolve(live, trackId) : undefined;\n if (!previous) throw new Error('resolve-track: selected track not found');\n const mediaTrack = parseMediaPlaylist(text, previous);\n\n // Report what the parse revealed about this rendition, before\n // committing it. Causes only — one unplayable rendition doesn't\n // make the source unplayable, so the verdict stays with\n // track-switching's empty-candidate branch.\n if (reportUnsupportedTrackConditions) {\n for (const condition of reportUnsupportedTrackConditions(mediaTrack as ResolvedTrack)) {\n emitError(state, condition);\n }\n }\n\n // Updater handles undefined inputs by returning current\n // unchanged; isResolvedPresentation narrows for the patch.\n // State-exit on resolving→unresolved fires runner.abortAll\n // before any URL change settles, and per the Fetch spec the\n // signal abort cancels in-flight body reads — so by the\n // time we reach this point the presentation we resolved\n // against is the live one.\n update(state.presentation, (current) => {\n if (!isResolvedPresentation(current)) return current;\n const patched = updateTrackInPresentation(current, mediaTrack);\n // Container is uniform within a type (an ABR ladder shares\n // its container), so a detected non-fMP4 rendition (TS,\n // raw AAC) implies every rendition of *this* type matches —\n // relabel them all from one resolved playlist instead of\n // fetching each. Scoped to this track's own type: never cross\n // audio↔video (mixed-container sources exist, e.g. muxed-TS\n // video + raw-.aac audio), which also keeps per-type\n // resolutions' writes disjoint (no race).\n const relabeled = NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType)\n ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType)\n : patched;\n // Stream nature (live vs on-demand), rewritten from whichever\n // track just parsed — every type's resolve and every live\n // reload takes this path. Idempotent in practice rather than\n // by construction: `PLAYLIST-TYPE` is a per-source constant\n // that a source's renditions agree on, so each write lands\n // the same value. Renditions that disagreed would make the\n // winner resolve-order-dependent.\n return { ...relabeled, streamType: deriveStreamType(getMediaPlaylistMetadata(mediaTrack)) };\n });\n return mediaTrack;\n },\n { id: track.id }\n )\n );\n scheduled.catch(() => {});\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_TRACK_RESOLUTION_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'video', trackId),\n} as const;\n\nconst AUDIO_TRACK_RESOLUTION_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'audio', trackId),\n} as const;\n\nconst TEXT_TRACK_RESOLUTION_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'text', trackId),\n} as const;\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Resolve unresolved video tracks. Schedules a fetch task whenever the\n * selected video track is partially resolved, parses the manifest, and\n * writes the resolved track back into `state.presentation`.\n */\nexport const resolveVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedVideoTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Engine `config` layers over the per-type defaults (mirrors the other\n // per-type variants, see track-types.ts); `failoverFetch` reads its\n // `selectedKey` + `getCdnId` from the merged result. `fetchResolvableText`\n // is then placed AFTER the spread so the failover-decorated fetch wins —\n // unlike segments, playlists expose no overridable per-type fetch.\n const trackConfig = { ...VIDEO_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/**\n * Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,\n * narrowed to audio.\n */\nexport const resolveAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedAudioTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...AUDIO_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/**\n * Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,\n * narrowed to text.\n */\nexport const resolveTextTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedTextTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedTextTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...TEXT_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resolve-track.js","names":["defaultFetchResolvableText","fetchResolvableText"],"sources":["../../../../src/playback/behaviors/resolve-track.ts"],"sourcesContent":["import { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { when } from '../../core/signals/when';\nimport { RecurringRunner, type Reschedule, runOnce, Task } from '../../core/tasks/task';\nimport { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from '../../media/hls/parse-media-playlist';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack, ResolvedTrack } from '../../media/types';\nimport { deriveStreamType, getMediaPlaylistMetadata, isResolvedPresentation, isResolvedTrack } from '../../media/types';\nimport type { GetCdnId } from '../../media/utils/cdn';\nimport { applyContainerMimeType, findTrack, updateTrackInPresentation } from '../../media/utils/tracks';\nimport { fetchResolvableText as defaultFetchResolvableText, type FetchText } from '../../network/fetch';\nimport { failoverFetch } from '../primitives/failover-fetch';\nimport type { GateFirstParse } from '../primitives/gate-first-parse';\nimport type { ReportUnsupportedTrackConditions } from '../primitives/report-track-conditions';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\nimport { type ErrorEmitterState, emitError } from './collect-errors';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackResolution` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => cleanup`. Each `resolveXTrack` export below calls it\n// from inside its own `defineBehavior` setup, supplying its per-type config\n// inline. The orchestration — gate on a selection, short-circuit when the\n// track is already resolved or missing, schedule the fetch+parse, and patch\n// the resolved track back into `state.presentation` — is shared.\n// ============================================================================\n\n/**\n * State shape for track resolution. Uses `MaybeResolvedPresentation` so it matches the engine's slot type; resolution\n * narrows internally.\n */\nexport interface ResolveTrackState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n failedCdns?: string[];\n}\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\ntype ResolveTrackStateMap<K extends SelectedTrackKey> = {\n presentation: Signal<ResolveTrackState['presentation']>;\n} & { [P in K]: ReadonlySignal<ResolveTrackState[P]> };\n\n/**\n * Sibling-owned A/V selection signals, present at runtime iff a sibling behavior owns them. Deliberately not in the\n * typed slice / `stateKeys` (declaring them would force every composition to carry both selections); read only to build\n * the injected first-parse gate's selection context.\n */\ntype SiblingSelectionSignals = {\n selectedVideoTrackId?: ReadonlySignal<ResolveTrackState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<ResolveTrackState['selectedAudioTrackId']>;\n};\n\ninterface TrackResolutionConfig<K extends SelectedTrackKey> {\n selectedKey: K;\n findTrackToResolve: (\n presentation: MaybeResolvedPresentation,\n trackId: string\n ) => PartiallyResolvedTrack | ResolvedTrack | undefined;\n /** Fetch a track's media-playlist text — already failover-decorated by the behavior. */\n fetchResolvableText?: FetchText;\n /** Holds a track's first parse until its placement inputs settle (live anchor); absent → parse immediately. */\n gateFirstParse?: GateFirstParse;\n /** Live re-run policy for the `RecurringRunner`; absent → resolve once (VOD). */\n reschedule?: Reschedule<ResolvedTrack>;\n /** Report conditions found in the parsed playlist (see `primitives/report-track-conditions`); absent → report nothing. */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n}\n\n/** Engine-config slice each `resolve*` behavior reads to build its failover- decorated playlist fetch. */\ninterface ResolveTrackConfig {\n /** CDN-id derivation for the failover trip; defaults to origin-based `getCdnId`. */\n getCdnId?: GetCdnId;\n /** First-parse placement gate (see `primitives/gate-first-parse`); absent → parse immediately. */\n gateFirstParse?: GateFirstParse;\n /** Live media-playlist re-run policy; absent → resolve once. */\n reschedule?: Reschedule<ResolvedTrack>;\n /** Playlist-derived condition reporting (see `primitives/report-track-conditions`). */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n}\n\nfunction setupTrackResolution<K extends SelectedTrackKey>({\n state,\n config: {\n selectedKey,\n findTrackToResolve,\n fetchResolvableText = defaultFetchResolvableText,\n gateFirstParse,\n reschedule,\n reportUnsupportedTrackConditions,\n },\n}: {\n // Widened with the optional `errors` slot: reporting writes through it without\n // the behavior declaring ownership, and no-ops when `collectErrors` isn't\n // composed. Same contract as `failedCdns` / `failoverFetch`.\n state: ResolveTrackStateMap<K> & ErrorEmitterState;\n config: TrackResolutionConfig<K>;\n}) {\n // Recurrence lives in the runner: with a `reschedule` (live) it re-runs the\n // task until the policy stops; `runOnce` (VOD) runs it exactly once. Single-\n // slot — a selection change re-schedules (abort-and-replace).\n const runner = new RecurringRunner<ResolvedTrack>(reschedule ?? runOnce);\n\n // Reactor states model the FSM the previous effect-based body was\n // hand-rolling. 'presentation-resolved' is entered when the\n // presentation is fully parsed (has a Ham id + selectionSets); leaving\n // it (presentation cleared or reset to an unresolved value) aborts all\n // in-flight tasks via the entry-cleanup. Most URL changes go through\n // 'presentation-unresolved' naturally (set undefined → set new partial\n // → re-parse), so the common case is covered by state-exit alone; the\n // task body's commit-time id check covers the pathological\n // resolved→resolved-without-unresolved transition.\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // `entry` runs on state entry; the function it returns is the\n // state-exit cleanup. Returning `() => runner.abortAll()` binds\n // abort-of-in-flight-resolutions to leaving 'presentation-resolved'\n // (presentation cleared/reset, or behavior destroyed) —\n // source-change cancellation expressed structurally through the\n // state machine.\n entry: () => () => runner.abortAll(),\n effects: [\n () => {\n // The reactor's state transitions handle relevant presentation\n // changes (presentation-resolved ↔ presentation-unresolved);\n // within 'presentation-resolved' we peek (untracked read) so\n // internal updates (segments added by sibling tasks, or by this\n // behavior's own reload cycles) don't re-fire the effect.\n const presentation = peek(state.presentation);\n const trackId = state[selectedKey].get();\n if (!presentation || !trackId) return;\n\n const track = findTrackToResolve(presentation, trackId);\n // Skip a complete or missing track; a resolved-but-incomplete (live)\n // window still reloads — its window may have slid past the playhead.\n if (!track || (isResolvedTrack(track) && Number.isFinite(track.duration))) return;\n\n // Abort (selection/source change) settles quietly; a genuine resolve\n // failure rejects — swallowed for now (TODO: surface to state).\n const scheduled = runner.schedule(\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createResolveTrackTask(track, context, config)),\n // likely eventually passed down via config or a new \"definitions\" argument (CJP).\n //\n // Re-runs (clones) on each reload, so the body re-reads the live\n // snapshot (via `trackId`) rather than capturing the gate-time\n // `track` — a reload carries the prior window's timeline forward.\n new Task(\n async (signal) => {\n const snapshot = peek(state.presentation);\n const current = snapshot ? findTrackToResolve(snapshot, trackId) : undefined;\n if (!current) throw new Error('resolve-track: selected track not found');\n\n // `fetchResolvableText` is the behavior's failover-decorated\n // fetch: it trips the CDN on a failed fetch (network error or\n // non-OK status). A parse failure is a content issue, not a\n // CDN-availability one, so it doesn't trip. The run-time\n // `current` supplies only the playlist URL (stable across the\n // fetch).\n const text = await fetchResolvableText(current, { signal });\n\n // Hold placement — never the fetch — until this track's\n // first-parse placement inputs settle (for live, the\n // wall-clock anchor; see `primitives/gate-first-parse`).\n // First parses only — a reload's window is already on the\n // established timeline. Aborting rejects the wait, so a\n // source change can't strand a gated task.\n if (gateFirstParse && !isResolvedTrack(current)) {\n const { selectedVideoTrackId, selectedAudioTrackId } = state as SiblingSelectionSignals;\n\n await when(\n () =>\n gateFirstParse(\n state.presentation.get(),\n {\n selectedVideoTrackId: selectedVideoTrackId?.get(),\n selectedAudioTrackId: selectedAudioTrackId?.get(),\n },\n trackId\n ),\n { signal }\n );\n }\n\n // Re-read `previous` after the awaits: a concurrent write\n // during them — notably the establishment reactor stamping\n // the anchor `startDate` onto this track's shell — must feed\n // the parse, not be clobbered (anchoring is establish-once;\n // parsing the pre-fetch snapshot would strand the track off\n // the anchor for good). Correctness rests on a\n // run-to-completion invariant: NOTHING may yield (await)\n // between this re-read and the write below, so no writer can\n // interleave. `parseMediaPlaylist` is synchronous — keep it\n // that way, or move the read into the updater.\n const live = peek(state.presentation);\n const previous = live ? findTrackToResolve(live, trackId) : undefined;\n if (!previous) throw new Error('resolve-track: selected track not found');\n\n const mediaTrack = parseMediaPlaylist(text, previous);\n\n // Report what the parse revealed about this rendition, before\n // committing it. Causes only — one unplayable rendition doesn't\n // make the source unplayable, so the verdict stays with\n // track-switching's empty-candidate branch.\n if (reportUnsupportedTrackConditions) {\n for (const condition of reportUnsupportedTrackConditions(mediaTrack as ResolvedTrack)) {\n emitError(state, condition);\n }\n }\n\n // Updater handles undefined inputs by returning current\n // unchanged; isResolvedPresentation narrows for the patch.\n // State-exit on resolving→unresolved fires runner.abortAll\n // before any URL change settles, and per the Fetch spec the\n // signal abort cancels in-flight body reads — so by the\n // time we reach this point the presentation we resolved\n // against is the live one.\n update(state.presentation, (current) => {\n if (!isResolvedPresentation(current)) return current;\n\n const patched = updateTrackInPresentation(current, mediaTrack);\n // Container is uniform within a type (an ABR ladder shares\n // its container), so a detected non-fMP4 rendition (TS,\n // raw AAC) implies every rendition of *this* type matches —\n // relabel them all from one resolved playlist instead of\n // fetching each. Scoped to this track's own type: never cross\n // audio↔video (mixed-container sources exist, e.g. muxed-TS\n // video + raw-.aac audio), which also keeps per-type\n // resolutions' writes disjoint (no race).\n const relabeled = NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType)\n ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType)\n : patched;\n\n // Stream nature (live vs on-demand), rewritten from whichever\n // track just parsed — every type's resolve and every live\n // reload takes this path. Idempotent in practice rather than\n // by construction: `PLAYLIST-TYPE` is a per-source constant\n // that a source's renditions agree on, so each write lands\n // the same value. Renditions that disagreed would make the\n // winner resolve-order-dependent.\n return { ...relabeled, streamType: deriveStreamType(getMediaPlaylistMetadata(mediaTrack)) };\n });\n return mediaTrack;\n },\n { id: track.id }\n )\n );\n\n scheduled.catch(() => {});\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_TRACK_RESOLUTION_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'video', trackId),\n} as const;\n\nconst AUDIO_TRACK_RESOLUTION_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'audio', trackId),\n} as const;\n\nconst TEXT_TRACK_RESOLUTION_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'text', trackId),\n} as const;\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Resolve unresolved video tracks. Schedules a fetch task whenever the selected video track is partially resolved,\n * parses the manifest, and writes the resolved track back into `state.presentation`.\n */\nexport const resolveVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedVideoTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Engine `config` layers over the per-type defaults (mirrors the other\n // per-type variants, see track-types.ts); `failoverFetch` reads its\n // `selectedKey` + `getCdnId` from the merged result. `fetchResolvableText`\n // is then placed AFTER the spread so the failover-decorated fetch wins —\n // unlike segments, playlists expose no overridable per-type fetch.\n const trackConfig = { ...VIDEO_TRACK_RESOLUTION_CONFIG, ...config };\n\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/** Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`, narrowed to audio. */\nexport const resolveAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedAudioTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...AUDIO_TRACK_RESOLUTION_CONFIG, ...config };\n\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/** Resolve unresolved text tracks. Same shape as `resolveVideoTrack`, narrowed to text. */\nexport const resolveTextTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedTextTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedTextTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...TEXT_TRACK_RESOLUTION_CONFIG, ...config };\n\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n"],"mappings":"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"}
|
|
@@ -9,59 +9,48 @@ type SelectableTrack = {
|
|
|
9
9
|
id: string;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
|
-
* Order the candidates by resolution, largest first, with bandwidth breaking ties
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* head.
|
|
12
|
+
* Order the candidates by resolution, largest first, with bandwidth breaking ties between renditions of identical
|
|
13
|
+
* dimensions. The background-video default — that variant pins one rendition for the session, and absent a cap the
|
|
14
|
+
* largest is the head.
|
|
16
15
|
*
|
|
17
|
-
* A ranker, so it reorders rather than narrowing: the chain's pick is the head of
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* leaving it last is what lets `applyRules` early-bail before it runs.
|
|
16
|
+
* A ranker, so it reorders rather than narrowing: the chain's pick is the head of what it returns, which means ranking
|
|
17
|
+
* never has to collapse to one track. Belongs last in a chain — a sort only reorders what survived the filters ahead of
|
|
18
|
+
* it, and leaving it last is what lets `applyRules` early-bail before it runs.
|
|
21
19
|
*
|
|
22
|
-
* Exported because it is a
|
|
23
|
-
*
|
|
20
|
+
* Exported because it is a _rule_, not a variant's private policy: the same one composes into `switchVideoTrack`'s
|
|
21
|
+
* chain when a ranker is wanted there.
|
|
24
22
|
*/
|
|
25
23
|
declare const preferHighestResolution: SelectTrackRule<unknown>;
|
|
26
24
|
/**
|
|
27
|
-
* Narrow to the renditions that fit the screen, by pixel area — the screen-size
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* unplayable, so nothing here may make a source unplayable.
|
|
25
|
+
* Narrow to the renditions that fit the screen, by pixel area — the screen-size cap from
|
|
26
|
+
* `internal/design/spf/features/rendition-selection-caps.md`, as a scope (soft filter) rather than a constraint: an
|
|
27
|
+
* over-cap rendition is wasteful, not unplayable, so nothing here may make a source unplayable.
|
|
31
28
|
*
|
|
32
|
-
* Narrows only — it neither orders the survivors nor resolves the case where none
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* fitting rendition the manifest happened to list first.
|
|
29
|
+
* Narrows only — it neither orders the survivors nor resolves the case where none survive, because `applyRules` owns
|
|
30
|
+
* both. So it needs a ranker behind it to pick within the cap: `[screenResolutionCap, preferHighestResolution]` yields
|
|
31
|
+
* the largest rendition that fits. Composed _last_, the pick would instead be whichever fitting rendition the manifest
|
|
32
|
+
* happened to list first.
|
|
37
33
|
*
|
|
38
|
-
* Reading `state.screenResolution` through its signal is what subscribes a
|
|
39
|
-
*
|
|
40
|
-
* pins the first answer instead, by design.
|
|
34
|
+
* Reading `state.screenResolution` through its signal is what subscribes a re-evaluating chain (`switchVideoTrack`) to
|
|
35
|
+
* screen changes; `selectVideoTrack` pins the first answer instead, by design.
|
|
41
36
|
*
|
|
42
|
-
* Compares areas rather than matching a `"1080p"`-style tier because a tier only
|
|
43
|
-
*
|
|
44
|
-
* mis-measures an anamorphic ladder. See `media/dom/screen.ts`.
|
|
37
|
+
* Compares areas rather than matching a `"1080p"`-style tier because a tier only describes a rendition once you assume
|
|
38
|
+
* its aspect ratio — the assumption that mis-measures an anamorphic ladder. See `media/dom/screen.ts`.
|
|
45
39
|
*
|
|
46
40
|
* Three ways the cap ends up not applying, all of them fall-through:
|
|
47
41
|
*
|
|
48
|
-
* - **No `screenResolution` signal at all**, because the composition omits
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* skips the empty result and the chain proceeds unnarrowed, so the ranker behind
|
|
56
|
-
* the cap decides — for `preferHighestResolution`, the largest rendition. A floor
|
|
57
|
-
* is the fix if that ever matters (`rendition-selection-caps.md` carries one), not
|
|
58
|
-
* a special case here.
|
|
42
|
+
* - **No `screenResolution` signal at all**, because the composition omits `trackScreenResolution`. So composing the cap
|
|
43
|
+
* without its signal source is inert rather than broken.
|
|
44
|
+
* - **A `screenResolution` of `undefined`**, meaning no screen to read. "Unknown" has to mean "don't cap": treating it as
|
|
45
|
+
* an area of zero would pin every source to its smallest rendition on exactly the environments we know least about.
|
|
46
|
+
* - **No rendition fits**, on a screen smaller than the whole ladder. `applyRules` skips the empty result and the chain
|
|
47
|
+
* proceeds unnarrowed, so the ranker behind the cap decides — for `preferHighestResolution`, the largest rendition. A
|
|
48
|
+
* floor is the fix if that ever matters (`rendition-selection-caps.md` carries one), not a special case here.
|
|
59
49
|
*/
|
|
60
50
|
declare const screenResolutionCap: SelectTrackRule<unknown>;
|
|
61
51
|
/**
|
|
62
|
-
* Config for `selectVideoTrack`. Pass `rules` to replace the selection chain, or
|
|
63
|
-
*
|
|
64
|
-
* and the first playable candidate is the pick.
|
|
52
|
+
* Config for `selectVideoTrack`. Pass `rules` to replace the selection chain, or `constraints` to replace the
|
|
53
|
+
* capability pre-pass; otherwise the chain is empty and the first playable candidate is the pick.
|
|
65
54
|
*/
|
|
66
55
|
interface SelectVideoTrackConfig extends CapabilityConstraintConfig {
|
|
67
56
|
constraints?: readonly SelectTrackRule<SelectVideoTrackConfig>[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-tracks.d.ts","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"select-tracks.d.ts","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"mappings":";;;;;KAkFY,gBAAgB,UAAU,cAAc,mCAAmC;;KAGlF;EAAoB;;;;;;;;;;;;;;cAkKZ,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiCzB,qBAAqB;;;;;UAejB,+BAA+B;EAC9C,uBAAuB,gBAAgB;EACvC,iBAAiB,gBAAgB"}
|