@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
|
@@ -4,76 +4,58 @@ import { getLiveEdge } from "../../primitives/live-window.js";
|
|
|
4
4
|
import { listen } from "@videojs/utils/dom";
|
|
5
5
|
//#region src/playback/behaviors/dom/seek-to-live-edge.ts
|
|
6
6
|
/**
|
|
7
|
-
* Keep the playhead in the live window, via a two-state reactor gated on the
|
|
8
|
-
* preconditions for "we know where live is":
|
|
7
|
+
* Keep the playhead in the live window, via a two-state reactor gated on the preconditions for "we know where live is":
|
|
9
8
|
*
|
|
10
|
-
* - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* to the target live latency behind the edge (clamped to the window start) so
|
|
14
|
-
* playback begins near the edge and the loader dispatches an in-window range;
|
|
9
|
+
* - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`: VOD, ended, or unresolved). Idle.
|
|
10
|
+
* - **`live`** — preconditions met. `entry` commands `state.startPosition` once to the target live latency behind the
|
|
11
|
+
* edge (clamped to the window start) so playback begins near the edge and the loader dispatches an in-window range;
|
|
15
12
|
* `effects` runs the window-exit guard.
|
|
16
13
|
*
|
|
17
|
-
* A derivable live edge is itself the establishment gate: segment placement is
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* any window derived from resolved segments is already final, with no separate
|
|
22
|
-
* anchor signal to wait on (see
|
|
14
|
+
* A derivable live edge is itself the establishment gate: segment placement is settled at parse time — the reference
|
|
15
|
+
* track's local placement _is_ the presentation timeline, and every other track's first parse is held until the anchor
|
|
16
|
+
* is stamped (`resolve-track`'s gate + `establishStartMediaTime`) — so any window derived from resolved segments is
|
|
17
|
+
* already final, with no separate anchor signal to wait on (see
|
|
23
18
|
* `internal/design/spf/live-presentation-timeline-model.md`).
|
|
24
19
|
*
|
|
25
|
-
* The two pieces split along the axis a future DVR / EVENT mode will care about:
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* `inactive`, so the next source re-commands (no closure latch to reset). The
|
|
32
|
-
* guard stays a direct seek — it is recurring, while `startPosition` is a
|
|
33
|
-
* self-clearing one-shot.
|
|
20
|
+
* The two pieces split along the axis a future DVR / EVENT mode will care about: the **one-time start position**
|
|
21
|
+
* (`entry`) is the _live-specific_ behavior — start near the edge on load; a DVR mode makes it conditional (start in
|
|
22
|
+
* place). The **window-exit guard** (`effects`) is the _general windowed-live_ behavior — applies to sliding-window
|
|
23
|
+
* live, DVR, and EVENT alike. Because the command is an `entry`, it fires once per entry into `live`; a source change
|
|
24
|
+
* exits to `inactive`, so the next source re-commands (no closure latch to reset). The guard stays a direct seek — it
|
|
25
|
+
* is recurring, while `startPosition` is a self-clearing one-shot.
|
|
34
26
|
*
|
|
35
|
-
* Window-exit guard: while playing (not paused), reposition to the live edge
|
|
36
|
-
* when
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* a long pause the playhead sits behind the window at an unseekable position,
|
|
44
|
-
* where the browser stalls and `playing` never fires; `play` fires on the
|
|
45
|
-
* paused→false transition regardless, so we snap before the stall. In-window
|
|
46
|
-
* pause / DVR scrub-back are left untouched.
|
|
27
|
+
* Window-exit guard: while playing (not paused), reposition to the live edge when the playhead has fallen behind the
|
|
28
|
+
* window start — including when a seek to a now-evicted position has stranded the playhead (such a seek can never
|
|
29
|
+
* settle, so we rescue rather than wait on it). Two triggers: the **window-update re-fire** (the guard reads the live
|
|
30
|
+
* edge, so each reload / slide re-runs it — this catches a stall, where `timeupdate` stops but the playlist keeps
|
|
31
|
+
* reloading) and a **`play` listener** for immediate reactivity on resume, since the reload interval can be seconds.
|
|
32
|
+
* `play`, not `playing`: after a long pause the playhead sits behind the window at an unseekable position, where the
|
|
33
|
+
* browser stalls and `playing` never fires; `play` fires on the paused→false transition regardless, so we snap before
|
|
34
|
+
* the stall. In-window pause / DVR scrub-back are left untouched.
|
|
47
35
|
*
|
|
48
|
-
* The latency comes from the injected `resolveLiveLatency` seam (HLS:
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* `seekable` is clamped) — so this behavior needs no MediaSource precondition of
|
|
53
|
-
* its own.
|
|
36
|
+
* The latency comes from the injected `resolveLiveLatency` seam (HLS: `HOLD-BACK`), so this behavior carries no
|
|
37
|
+
* delivery-format specifics. `applyStartPosition` performs the seek, gated on `loadedmetadata` — which implies an open
|
|
38
|
+
* MediaSource and hence a declared seekable range (a seek outside `seekable` is clamped) — so this behavior needs no
|
|
39
|
+
* MediaSource precondition of its own.
|
|
54
40
|
*/
|
|
55
41
|
/**
|
|
56
|
-
* Tolerance (seconds) around the window edges before the guard repositions, so
|
|
57
|
-
*
|
|
42
|
+
* Tolerance (seconds) around the window edges before the guard repositions, so boundary / floating-point noise doesn't
|
|
43
|
+
* trigger a spurious seek.
|
|
58
44
|
*/
|
|
59
45
|
const REPOSITION_TOLERANCE = .1;
|
|
60
46
|
/**
|
|
61
|
-
* `'live'` once the preconditions hold: a media element and a derivable live edge
|
|
62
|
-
*
|
|
63
|
-
* `'inactive'` otherwise.
|
|
47
|
+
* `'live'` once the preconditions hold: a media element and a derivable live edge (whose placement is final by
|
|
48
|
+
* construction — see the module docstring). `'inactive'` otherwise.
|
|
64
49
|
*
|
|
65
|
-
* Deliberately narrow: every signal here can flip the reactor out of and back into
|
|
66
|
-
*
|
|
67
|
-
* `
|
|
68
|
-
*
|
|
69
|
-
* window) correctly doesn't re-command.
|
|
50
|
+
* Deliberately narrow: every signal here can flip the reactor out of and back into `live`, re-firing `entry`. Neither
|
|
51
|
+
* blinks mid-source — the edge can't, because `liveWindowForType` falls back to any resolved track of the type — so
|
|
52
|
+
* `entry` fires once per source without a latch, and a live reload (same source, slid window) correctly doesn't
|
|
53
|
+
* re-command.
|
|
70
54
|
*
|
|
71
|
-
* The flip side: the presentation
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* transits `inactive`. Supporting a seeded pre-resolved presentation (via
|
|
76
|
-
* `initialState`) that later changes would mean folding the url in here.
|
|
55
|
+
* The flip side: the presentation _url_ is not part of this state, so replacing one already-resolved live presentation
|
|
56
|
+
* with another would stay in `live` and never command a start position for the new source. Unreachable today — every
|
|
57
|
+
* writer sets `{ url }` (unresolved) or `undefined` first, so a source change always transits `inactive`. Supporting a
|
|
58
|
+
* seeded pre-resolved presentation (via `initialState`) that later changes would mean folding the url in here.
|
|
77
59
|
*/
|
|
78
60
|
function deriveState(mediaElement, edge) {
|
|
79
61
|
return mediaElement && edge ? "live" : "inactive";
|
|
@@ -116,10 +98,9 @@ function seekToLiveEdgeSetup({ state, context, config }) {
|
|
|
116
98
|
});
|
|
117
99
|
}
|
|
118
100
|
/**
|
|
119
|
-
* Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* switch* behaviors), so it composes without a stateKeys/type conflict.
|
|
101
|
+
* Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares only `presentation` + `startPosition` in
|
|
102
|
+
* stateKeys while reading `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the switch*
|
|
103
|
+
* behaviors), so it composes without a stateKeys/type conflict.
|
|
123
104
|
*/
|
|
124
105
|
const seekToLiveEdge = {
|
|
125
106
|
stateKeys: ["presentation", "startPosition"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seek-to-live-edge.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/seek-to-live-edge.ts"],"sourcesContent":["/**\n * Keep the playhead in the live window, via a two-state reactor gated on the\n * preconditions for \"we know where live is\":\n *\n * - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`:\n * VOD, ended, or unresolved). Idle.\n * - **`live`** — preconditions met. `entry` commands `state.startPosition` once\n * to the target live latency behind the edge (clamped to the window start) so\n * playback begins near the edge and the loader dispatches an in-window range;\n * `effects` runs the window-exit guard.\n *\n * A derivable live edge is itself the establishment gate: segment placement is\n * settled at parse time — the reference track's local placement *is* the\n * presentation timeline, and every other track's first parse is held until the\n * anchor is stamped (`resolve-track`'s gate + `establishStartMediaTime`) — so\n * any window derived from resolved segments is already final, with no separate\n * anchor signal to wait on (see\n * `internal/design/spf/live-presentation-timeline-model.md`).\n *\n * The two pieces split along the axis a future DVR / EVENT mode will care about:\n * the **one-time start position** (`entry`) is the *live-specific* behavior — start\n * near the edge on load; a DVR mode makes it conditional (start in place). The\n * **window-exit guard** (`effects`) is the *general windowed-live* behavior —\n * applies to sliding-window live, DVR, and EVENT alike. Because the command is an\n * `entry`, it fires once per entry into `live`; a source change exits to\n * `inactive`, so the next source re-commands (no closure latch to reset). The\n * guard stays a direct seek — it is recurring, while `startPosition` is a\n * self-clearing one-shot.\n *\n * Window-exit guard: while playing (not paused), reposition to the live edge\n * when the playhead has fallen behind the window start — including when a seek\n * to a now-evicted position has stranded the playhead (such a seek can never\n * settle, so we rescue rather than wait on it). Two triggers:\n * the **window-update re-fire** (the guard reads the live edge, so each reload /\n * slide re-runs it — this catches a stall, where `timeupdate` stops but the\n * playlist keeps reloading) and a **`play` listener** for immediate reactivity on\n * resume, since the reload interval can be seconds. `play`, not `playing`: after\n * a long pause the playhead sits behind the window at an unseekable position,\n * where the browser stalls and `playing` never fires; `play` fires on the\n * paused→false transition regardless, so we snap before the stall. In-window\n * pause / DVR scrub-back are left untouched.\n *\n * The latency comes from the injected `resolveLiveLatency` seam (HLS:\n * `HOLD-BACK`), so this behavior carries no delivery-format specifics.\n * `applyStartPosition` performs the seek, gated on `loadedmetadata` — which\n * implies an open MediaSource and hence a declared seekable range (a seek outside\n * `seekable` is clamped) — so this behavior needs no MediaSource precondition of\n * its own.\n */\nimport { listen } from '@videojs/utils/dom';\nimport type { Behavior } from '../../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport { getLiveEdge, type LiveEdge, type ResolveLiveLatency } from '../../primitives/live-window';\n\n/**\n * Tolerance (seconds) around the window edges before the guard repositions, so\n * boundary / floating-point noise doesn't trigger a spurious seek.\n */\nconst REPOSITION_TOLERANCE = 0.1;\n\nexport interface SeekToLiveEdgeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * One-shot start-position command in presentation-timeline seconds. Written\n * here on entry into `live`; consumed (cleared) by `applyStartPosition`.\n */\n startPosition?: number;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\nexport interface SeekToLiveEdgeContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\nexport interface SeekToLiveEdgeConfig {\n /**\n * Resolve the target live latency (seconds the playhead should trail the live\n * edge) for the timeline-bearing track. Injected by the engine so the latency\n * rule stays format-specific (HLS: `HOLD-BACK`, default 3× target duration;\n * DASH would read `suggestedPresentationDelay`) while this behavior stays\n * neutral. Absent → `0` (seek straight to the edge).\n */\n resolveLiveLatency?: ResolveLiveLatency;\n}\n\ntype SeekToLiveEdgeFsmState = 'inactive' | 'live';\n\n/**\n * `'live'` once the preconditions hold: a media element and a derivable live edge\n * (whose placement is final by construction — see the module docstring).\n * `'inactive'` otherwise.\n *\n * Deliberately narrow: every signal here can flip the reactor out of and back into\n * `live`, re-firing `entry`. Neither blinks mid-source — the edge can't, because\n * `liveWindowForType` falls back to any resolved track of the type — so `entry`\n * fires once per source without a latch, and a live reload (same source, slid\n * window) correctly doesn't re-command.\n *\n * The flip side: the presentation *url* is not part of this state, so replacing one\n * already-resolved live presentation with another would stay in `live` and never\n * command a start position for the new source. Unreachable today — every writer\n * sets `{ url }` (unresolved) or `undefined` first, so a source change always\n * transits `inactive`. Supporting a seeded pre-resolved presentation (via\n * `initialState`) that later changes would mean folding the url in here.\n */\nfunction deriveState(mediaElement: HTMLMediaElement | undefined, edge: LiveEdge | null): SeekToLiveEdgeFsmState {\n return mediaElement && edge ? 'live' : 'inactive';\n}\n\nfunction seekToLiveEdgeSetup({\n state,\n context,\n config,\n}: {\n state: {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n selectedVideoTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedAudioTrackId']>;\n };\n context: {\n mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']>;\n };\n config?: SeekToLiveEdgeConfig;\n}): Reactor<SeekToLiveEdgeFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), getLiveEdge({ state, config })));\n\n return createMachineReactor<SeekToLiveEdgeFsmState>({\n initial: 'inactive',\n monitor: () => derivedStateSignal.get(),\n states: {\n inactive: {},\n\n live: {\n // One-time-per-source start position so the loader dispatches an in-window\n // range and preload shows the right frame. The window-exit guard below is\n // the separate, continuous rule. Live-specific — a future DVR mode skips\n // even this first command (starts in place).\n entry: () => {\n // The monitor guarantees a media element + live edge while in `live`.\n const mediaElement = context.mediaElement.get()!;\n const { liveEdgeStart } = getLiveEdge({ state, config })!;\n // Never command backwards: seeding `state.currentTime` behind a playhead\n // already at/past the edge would drag the loaders back.\n if (mediaElement.currentTime >= liveEdgeStart) return;\n state.startPosition.set(liveEdgeStart);\n },\n\n // Window-exit guard. Repositions to the live edge when the playhead has\n // fallen behind the window start, while playing. Two triggers: this\n // effect's window-update re-fire (it reads the live edge, so each reload\n // re-runs it — catches a stall, where `timeupdate` is silent) and a\n // `play` listener for immediate reactivity on resume.\n effects: () => {\n // Read the live edge first — the only tracked dependency — so window\n // slides keep re-firing this effect even while paused. Bailing before\n // it (on paused) would drop the dependency and the effect would go\n // inert, missing the next slide. The monitor guarantees the edge while\n // in `live`; the media element is read untracked (the window update,\n // not element identity, is the re-fire trigger).\n const { start: windowStart, liveEdgeStart } = getLiveEdge({ state, config })!;\n const mediaElement = peek(context.mediaElement)!;\n const reposition = () => {\n // Don't yank a paused viewer. We deliberately DON'T bail on\n // `mediaElement.seeking`: a seek whose target sits behind the window\n // start can never complete (the data has slid out of the window and\n // been evicted), so `seeking` would stay true forever and strand the\n // playhead — the very stall this guard exists to rescue. The\n // `currentTime < windowStart` test is itself the precise\n // discriminator: an in-window DVR scrub-back lands at\n // `currentTime >= windowStart` and never trips it, so only a stuck\n // out-of-window seek is repositioned.\n if (mediaElement.paused) return;\n if (mediaElement.currentTime < windowStart - REPOSITION_TOLERANCE) {\n mediaElement.currentTime = liveEdgeStart;\n }\n };\n reposition();\n // `play` (not `playing`): a slid-past playhead is at an unseekable\n // position where `playing` would stall and never fire; `play` fires on\n // unpause regardless, so we snap before the stall.\n return listen(mediaElement, 'play', reposition);\n },\n },\n },\n });\n}\n\n/**\n * Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares\n * only `presentation` + `startPosition` in stateKeys while reading\n * `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the\n * switch* behaviors), so it composes without a stateKeys/type conflict.\n */\nexport const seekToLiveEdge: Behavior<\n {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n },\n { mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']> },\n SeekToLiveEdgeConfig\n> = {\n stateKeys: ['presentation', 'startPosition'],\n contextKeys: ['mediaElement'],\n setup: seekToLiveEdgeSetup,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;AAgD7B,SAAS,YAAY,cAA4C,MAA+C;CAC9G,OAAO,gBAAgB,OAAO,SAAS;AACzC;AAEA,SAAS,oBAAoB,EAC3B,OACA,SACA,UAY+D;CAC/D,MAAM,qBAAqB,eAAe,YAAY,QAAQ,aAAa,IAAI,GAAG,YAAY;EAAE;EAAO;CAAO,CAAC,CAAC,CAAC;CAEjH,OAAO,qBAA6C;EAClD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,UAAU,CAAC;GAEX,MAAM;IAKJ,aAAa;KAEX,MAAM,eAAe,QAAQ,aAAa,IAAI;KAC9C,MAAM,EAAE,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAGvD,IAAI,aAAa,eAAe,eAAe;KAC/C,MAAM,cAAc,IAAI,aAAa;IACvC;IAOA,eAAe;KAOb,MAAM,EAAE,OAAO,aAAa,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAC3E,MAAM,eAAe,KAAK,QAAQ,YAAY;KAC9C,MAAM,mBAAmB;MAUvB,IAAI,aAAa,QAAQ;MACzB,IAAI,aAAa,cAAc,cAAc,sBAC3C,aAAa,cAAc;KAE/B;KACA,WAAW;KAIX,OAAO,OAAO,cAAc,QAAQ,UAAU;IAChD;GACF;EACF;CACF,CAAC;AACH;;;;;;;AAQA,MAAa,iBAOT;CACF,WAAW,CAAC,gBAAgB,eAAe;CAC3C,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"seek-to-live-edge.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/seek-to-live-edge.ts"],"sourcesContent":["/**\n * Keep the playhead in the live window, via a two-state reactor gated on the preconditions for \"we know where live is\":\n *\n * - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`: VOD, ended, or unresolved). Idle.\n * - **`live`** — preconditions met. `entry` commands `state.startPosition` once to the target live latency behind the\n * edge (clamped to the window start) so playback begins near the edge and the loader dispatches an in-window range;\n * `effects` runs the window-exit guard.\n *\n * A derivable live edge is itself the establishment gate: segment placement is settled at parse time — the reference\n * track's local placement _is_ the presentation timeline, and every other track's first parse is held until the anchor\n * is stamped (`resolve-track`'s gate + `establishStartMediaTime`) — so any window derived from resolved segments is\n * already final, with no separate anchor signal to wait on (see\n * `internal/design/spf/live-presentation-timeline-model.md`).\n *\n * The two pieces split along the axis a future DVR / EVENT mode will care about: the **one-time start position**\n * (`entry`) is the _live-specific_ behavior — start near the edge on load; a DVR mode makes it conditional (start in\n * place). The **window-exit guard** (`effects`) is the _general windowed-live_ behavior — applies to sliding-window\n * live, DVR, and EVENT alike. Because the command is an `entry`, it fires once per entry into `live`; a source change\n * exits to `inactive`, so the next source re-commands (no closure latch to reset). The guard stays a direct seek — it\n * is recurring, while `startPosition` is a self-clearing one-shot.\n *\n * Window-exit guard: while playing (not paused), reposition to the live edge when the playhead has fallen behind the\n * window start — including when a seek to a now-evicted position has stranded the playhead (such a seek can never\n * settle, so we rescue rather than wait on it). Two triggers: the **window-update re-fire** (the guard reads the live\n * edge, so each reload / slide re-runs it — this catches a stall, where `timeupdate` stops but the playlist keeps\n * reloading) and a **`play` listener** for immediate reactivity on resume, since the reload interval can be seconds.\n * `play`, not `playing`: after a long pause the playhead sits behind the window at an unseekable position, where the\n * browser stalls and `playing` never fires; `play` fires on the paused→false transition regardless, so we snap before\n * the stall. In-window pause / DVR scrub-back are left untouched.\n *\n * The latency comes from the injected `resolveLiveLatency` seam (HLS: `HOLD-BACK`), so this behavior carries no\n * delivery-format specifics. `applyStartPosition` performs the seek, gated on `loadedmetadata` — which implies an open\n * MediaSource and hence a declared seekable range (a seek outside `seekable` is clamped) — so this behavior needs no\n * MediaSource precondition of its own.\n */\nimport { listen } from '@videojs/utils/dom';\n\nimport type { Behavior } from '../../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport { getLiveEdge, type LiveEdge, type ResolveLiveLatency } from '../../primitives/live-window';\n\n/**\n * Tolerance (seconds) around the window edges before the guard repositions, so boundary / floating-point noise doesn't\n * trigger a spurious seek.\n */\nconst REPOSITION_TOLERANCE = 0.1;\n\nexport interface SeekToLiveEdgeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * One-shot start-position command in presentation-timeline seconds. Written here on entry into `live`; consumed\n * (cleared) by `applyStartPosition`.\n */\n startPosition?: number;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\nexport interface SeekToLiveEdgeContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\nexport interface SeekToLiveEdgeConfig {\n /**\n * Resolve the target live latency (seconds the playhead should trail the live edge) for the timeline-bearing track.\n * Injected by the engine so the latency rule stays format-specific (HLS: `HOLD-BACK`, default 3× target duration;\n * DASH would read `suggestedPresentationDelay`) while this behavior stays neutral. Absent → `0` (seek straight to the\n * edge).\n */\n resolveLiveLatency?: ResolveLiveLatency;\n}\n\ntype SeekToLiveEdgeFsmState = 'inactive' | 'live';\n\n/**\n * `'live'` once the preconditions hold: a media element and a derivable live edge (whose placement is final by\n * construction — see the module docstring). `'inactive'` otherwise.\n *\n * Deliberately narrow: every signal here can flip the reactor out of and back into `live`, re-firing `entry`. Neither\n * blinks mid-source — the edge can't, because `liveWindowForType` falls back to any resolved track of the type — so\n * `entry` fires once per source without a latch, and a live reload (same source, slid window) correctly doesn't\n * re-command.\n *\n * The flip side: the presentation _url_ is not part of this state, so replacing one already-resolved live presentation\n * with another would stay in `live` and never command a start position for the new source. Unreachable today — every\n * writer sets `{ url }` (unresolved) or `undefined` first, so a source change always transits `inactive`. Supporting a\n * seeded pre-resolved presentation (via `initialState`) that later changes would mean folding the url in here.\n */\nfunction deriveState(mediaElement: HTMLMediaElement | undefined, edge: LiveEdge | null): SeekToLiveEdgeFsmState {\n return mediaElement && edge ? 'live' : 'inactive';\n}\n\nfunction seekToLiveEdgeSetup({\n state,\n context,\n config,\n}: {\n state: {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n selectedVideoTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedAudioTrackId']>;\n };\n context: {\n mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']>;\n };\n config?: SeekToLiveEdgeConfig;\n}): Reactor<SeekToLiveEdgeFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), getLiveEdge({ state, config })));\n\n return createMachineReactor<SeekToLiveEdgeFsmState>({\n initial: 'inactive',\n monitor: () => derivedStateSignal.get(),\n states: {\n inactive: {},\n\n live: {\n // One-time-per-source start position so the loader dispatches an in-window\n // range and preload shows the right frame. The window-exit guard below is\n // the separate, continuous rule. Live-specific — a future DVR mode skips\n // even this first command (starts in place).\n entry: () => {\n // The monitor guarantees a media element + live edge while in `live`.\n const mediaElement = context.mediaElement.get()!;\n const { liveEdgeStart } = getLiveEdge({ state, config })!;\n // Never command backwards: seeding `state.currentTime` behind a playhead\n // already at/past the edge would drag the loaders back.\n if (mediaElement.currentTime >= liveEdgeStart) return;\n\n state.startPosition.set(liveEdgeStart);\n },\n\n // Window-exit guard. Repositions to the live edge when the playhead has\n // fallen behind the window start, while playing. Two triggers: this\n // effect's window-update re-fire (it reads the live edge, so each reload\n // re-runs it — catches a stall, where `timeupdate` is silent) and a\n // `play` listener for immediate reactivity on resume.\n effects: () => {\n // Read the live edge first — the only tracked dependency — so window\n // slides keep re-firing this effect even while paused. Bailing before\n // it (on paused) would drop the dependency and the effect would go\n // inert, missing the next slide. The monitor guarantees the edge while\n // in `live`; the media element is read untracked (the window update,\n // not element identity, is the re-fire trigger).\n const { start: windowStart, liveEdgeStart } = getLiveEdge({ state, config })!;\n const mediaElement = peek(context.mediaElement)!;\n const reposition = () => {\n // Don't yank a paused viewer. We deliberately DON'T bail on\n // `mediaElement.seeking`: a seek whose target sits behind the window\n // start can never complete (the data has slid out of the window and\n // been evicted), so `seeking` would stay true forever and strand the\n // playhead — the very stall this guard exists to rescue. The\n // `currentTime < windowStart` test is itself the precise\n // discriminator: an in-window DVR scrub-back lands at\n // `currentTime >= windowStart` and never trips it, so only a stuck\n // out-of-window seek is repositioned.\n if (mediaElement.paused) return;\n\n if (mediaElement.currentTime < windowStart - REPOSITION_TOLERANCE) {\n mediaElement.currentTime = liveEdgeStart;\n }\n };\n\n reposition();\n // `play` (not `playing`): a slid-past playhead is at an unseekable\n // position where `playing` would stall and never fire; `play` fires on\n // unpause regardless, so we snap before the stall.\n return listen(mediaElement, 'play', reposition);\n },\n },\n },\n });\n}\n\n/**\n * Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares only `presentation` + `startPosition` in\n * stateKeys while reading `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the switch*\n * behaviors), so it composes without a stateKeys/type conflict.\n */\nexport const seekToLiveEdge: Behavior<\n {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n },\n { mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']> },\n SeekToLiveEdgeConfig\n> = {\n stateKeys: ['presentation', 'startPosition'],\n contextKeys: ['mediaElement'],\n setup: seekToLiveEdgeSetup,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAM,uBAAuB;;;;;;;;;;;;;;;AA2C7B,SAAS,YAAY,cAA4C,MAA+C;CAC9G,OAAO,gBAAgB,OAAO,SAAS;AACzC;AAEA,SAAS,oBAAoB,EAC3B,OACA,SACA,UAY+D;CAC/D,MAAM,qBAAqB,eAAe,YAAY,QAAQ,aAAa,IAAI,GAAG,YAAY;EAAE;EAAO;CAAO,CAAC,CAAC,CAAC;CAEjH,OAAO,qBAA6C;EAClD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,UAAU,CAAC;GAEX,MAAM;IAKJ,aAAa;KAEX,MAAM,eAAe,QAAQ,aAAa,IAAI;KAC9C,MAAM,EAAE,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAGvD,IAAI,aAAa,eAAe,eAAe;KAE/C,MAAM,cAAc,IAAI,aAAa;IACvC;IAOA,eAAe;KAOb,MAAM,EAAE,OAAO,aAAa,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAC3E,MAAM,eAAe,KAAK,QAAQ,YAAY;KAC9C,MAAM,mBAAmB;MAUvB,IAAI,aAAa,QAAQ;MAEzB,IAAI,aAAa,cAAc,cAAc,sBAC3C,aAAa,cAAc;KAE/B;KAEA,WAAW;KAIX,OAAO,OAAO,cAAc,QAAQ,UAAU;IAChD;GACF;EACF;CACF,CAAC;AACH;;;;;;AAOA,MAAa,iBAOT;CACF,WAAW,CAAC,gBAAgB,eAAe;CAC3C,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT"}
|
|
@@ -12,74 +12,55 @@ import { failoverFetch } from "../../primitives/failover-fetch.js";
|
|
|
12
12
|
import { listen } from "@videojs/utils/dom";
|
|
13
13
|
//#region src/playback/behaviors/dom/setup-buffer-actors.ts
|
|
14
14
|
/**
|
|
15
|
-
* **Per-type buffer + segment-loader actor setup.** Per available track
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `SegmentLoaderActor` bound to that buffer-actor; publishes the per-type
|
|
22
|
-
* actor slots. On `mediaSource` detach or behavior destroy, destroys both
|
|
23
|
-
* actors in reverse order and clears the per-type slots so the next
|
|
24
|
-
* source starts fresh.
|
|
15
|
+
* **Per-type buffer + segment-loader actor setup.** Per available track type (video / audio), when `mediaSource` is
|
|
16
|
+
* attached and the selected track of that type is present in the presentation with codecs (partial resolution from the
|
|
17
|
+
* multivariant playlist is enough — codecs live on the `EXT-X-STREAM-INF` line, not in the per-type media playlist),
|
|
18
|
+
* creates a `SourceBuffer`, a `SourceBufferActor`, and a `SegmentLoaderActor` bound to that buffer-actor; publishes the
|
|
19
|
+
* per-type actor slots. On `mediaSource` detach or behavior destroy, destroys both actors in reverse order and clears
|
|
20
|
+
* the per-type slots so the next source starts fresh.
|
|
25
21
|
*
|
|
26
|
-
* Each per-type variant (`setupVideoBufferActors` /
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus
|
|
31
|
-
* `bandwidthState`, written by its trackedFetch), and audio mirrors.
|
|
22
|
+
* Each per-type variant (`setupVideoBufferActors` / `setupAudioBufferActors`) is a single-positive-state reactor
|
|
23
|
+
* (`'preconditions-unmet'` ↔ `'buffer-ready'`) gating only on its own type. No cross-type coupling in `stateKeys` —
|
|
24
|
+
* `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus `bandwidthState`, written by its trackedFetch),
|
|
25
|
+
* and audio mirrors.
|
|
32
26
|
*
|
|
33
27
|
* # Firefox `mozHasAudio` invariant
|
|
34
28
|
*
|
|
35
|
-
* Appending to a video `SourceBuffer` before the audio `SourceBuffer`
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* created in one synchronous block inside a merged behavior). It's now
|
|
40
|
-
* preserved by a chain of assumptions about how this behavior composes
|
|
41
|
-
* with its upstream and downstream siblings:
|
|
29
|
+
* Appending to a video `SourceBuffer` before the audio `SourceBuffer` exists causes `mozHasAudio` to be permanently
|
|
30
|
+
* false in Firefox. With the two per-type variants decoupled, the invariant is no longer structural to a single `entry`
|
|
31
|
+
* body (as it was when both buffers were created in one synchronous block inside a merged behavior). It's now preserved
|
|
32
|
+
* by a chain of assumptions about how this behavior composes with its upstream and downstream siblings:
|
|
42
33
|
*
|
|
43
|
-
* 1. **Upstream — default selections land in one `runPending`.**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* in the
|
|
51
|
-
*
|
|
52
|
-
* the iteration ends.
|
|
53
|
-
* 3. **Downstream — `appendBuffer` is async.** `loadVideoSegments` /
|
|
54
|
-
* `loadAudioSegments` read the per-type `xSegmentLoaderActor` slots;
|
|
55
|
-
* their effects fire in the *next* `runPending` and the actual
|
|
56
|
-
* `appendBuffer` requires a network round-trip via the
|
|
57
|
-
* `SegmentLoaderActor` — many microtasks past both `addSourceBuffer`
|
|
58
|
-
* calls.
|
|
34
|
+
* 1. **Upstream — default selections land in one `runPending`.** `selectAudioTrack` (default audio) and `switchVideoTrack`
|
|
35
|
+
* (default video) both subscribe to `state.presentation` flipping to resolved; their effects run in the same
|
|
36
|
+
* `runPending` iteration and write `selectedAudioTrackId` + `selectedVideoTrackId` within it.
|
|
37
|
+
* 2. **Self — both per-type monitors flip in one `runPending`.** After (1), both monitors re-evaluate and flip to
|
|
38
|
+
* `'buffer-ready'` in the next `runPending`. Both `entry` bodies run synchronously within that iteration — both
|
|
39
|
+
* `addSourceBuffer` calls land before the iteration ends.
|
|
40
|
+
* 3. **Downstream — `appendBuffer` is async.** `loadVideoSegments` / `loadAudioSegments` read the per-type
|
|
41
|
+
* `xSegmentLoaderActor` slots; their effects fire in the _next_ `runPending` and the actual `appendBuffer` requires
|
|
42
|
+
* a network round-trip via the `SegmentLoaderActor` — many microtasks past both `addSourceBuffer` calls.
|
|
59
43
|
*
|
|
60
|
-
* The cross-tick failure mode — a user-initiated audio track switch
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* path via `changeType`-aware logic.
|
|
44
|
+
* The cross-tick failure mode — a user-initiated audio track switch _after_ video segments have begun appending — is
|
|
45
|
+
* out of scope for this behavior and would be addressed in the buffer/segment-loading path via `changeType`-aware
|
|
46
|
+
* logic.
|
|
64
47
|
*
|
|
65
48
|
* # Sole writer
|
|
66
49
|
*
|
|
67
|
-
* `setupVideoBufferActors` is sole writer of `videoBufferActor` +
|
|
68
|
-
* `
|
|
69
|
-
*
|
|
70
|
-
* `
|
|
71
|
-
* `mediaSource` from `setupMediaSource`. Downstream MSE behaviors
|
|
72
|
-
* (`loadVideoSegments`, `loadAudioSegments`, `endOfStream`,
|
|
73
|
-
* `updateMediaSourceDuration`) only read these slots.
|
|
50
|
+
* `setupVideoBufferActors` is sole writer of `videoBufferActor` + `videoSegmentLoaderActor` (and `bandwidthState` via
|
|
51
|
+
* its trackedFetch); `setupAudioBufferActors` is sole writer of `audioBufferActor` + `audioSegmentLoaderActor`. Both
|
|
52
|
+
* read `mediaSource` from `setupMediaSource`. Downstream MSE behaviors (`loadVideoSegments`, `loadAudioSegments`,
|
|
53
|
+
* `endOfStream`, `updateMediaSourceDuration`) only read these slots.
|
|
74
54
|
*/
|
|
75
55
|
function setupBufferActors({ state, context, config }) {
|
|
76
56
|
const { type, selectedKey, actorKey, loaderKey, fetch, forwardBuffer, backBuffer, messagePipelines } = config;
|
|
77
57
|
const derivedStateSignal = computed(() => {
|
|
78
58
|
if (!context.mediaSource.get()) return "preconditions-unmet";
|
|
79
|
-
|
|
59
|
+
const selection = {
|
|
80
60
|
presentation: state.presentation.get(),
|
|
81
61
|
[selectedKey]: state[selectedKey].get()
|
|
82
|
-
}
|
|
62
|
+
};
|
|
63
|
+
return hasCodecs(getSelectedTrack(selection, type)) ? "buffer-ready" : "preconditions-unmet";
|
|
83
64
|
});
|
|
84
65
|
return createMachineReactor({
|
|
85
66
|
initial: "preconditions-unmet",
|
|
@@ -88,10 +69,13 @@ function setupBufferActors({ state, context, config }) {
|
|
|
88
69
|
"preconditions-unmet": {},
|
|
89
70
|
"buffer-ready": { entry: () => {
|
|
90
71
|
const mediaSource = context.mediaSource.get();
|
|
91
|
-
const
|
|
72
|
+
const selection = {
|
|
92
73
|
presentation: state.presentation.get(),
|
|
93
74
|
[selectedKey]: state[selectedKey].get()
|
|
94
|
-
}
|
|
75
|
+
};
|
|
76
|
+
const track = getSelectedTrack(selection, type);
|
|
77
|
+
const buffer = createSourceBuffer(mediaSource, buildMimeCodec(track));
|
|
78
|
+
const bufferActor = createSourceBufferActor(buffer);
|
|
95
79
|
const segmentLoader = createSegmentLoaderActor(bufferActor, fetch, {
|
|
96
80
|
forwardBuffer,
|
|
97
81
|
backBuffer,
|
|
@@ -118,11 +102,9 @@ function setupBufferActors({ state, context, config }) {
|
|
|
118
102
|
});
|
|
119
103
|
}
|
|
120
104
|
/**
|
|
121
|
-
* Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* no cross-type coupling. Owns a bandwidth-sampling `trackedFetch` and
|
|
125
|
-
* is sole writer of `state.bandwidthState`.
|
|
105
|
+
* Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires when `mediaSource` is attached and the selected
|
|
106
|
+
* video track is present in the presentation with codecs. Gates only on video state — no cross-type coupling. Owns a
|
|
107
|
+
* bandwidth-sampling `trackedFetch` and is sole writer of `state.bandwidthState`.
|
|
126
108
|
*/
|
|
127
109
|
const setupVideoBufferActors = defineBehavior({
|
|
128
110
|
stateKeys: [
|
|
@@ -159,22 +141,16 @@ const setupVideoBufferActors = defineBehavior({
|
|
|
159
141
|
}
|
|
160
142
|
});
|
|
161
143
|
/**
|
|
162
|
-
* Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* without touching the shared helper. See
|
|
168
|
-
* `internal/design/spf/features/audio-abr.md` for the design surface
|
|
169
|
-
* (bandwidth-state sharing, multi-writer coordination, EWMA mixed-
|
|
170
|
-
* source sampling).
|
|
144
|
+
* Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same shape as `setupVideoBufferActors`, narrowed to
|
|
145
|
+
* audio. Today supplies a non-sampling `fetchStream` (no audio ABR); adding audio ABR is a localized change to this
|
|
146
|
+
* setup body (swap `fetchStream` for a `createTrackedFetch` call + declare `bandwidthState` writable here) without
|
|
147
|
+
* touching the shared helper. See `internal/design/spf/features/audio-abr.md` for the design surface (bandwidth-state
|
|
148
|
+
* sharing, multi-writer coordination, EWMA mixed- source sampling).
|
|
171
149
|
*
|
|
172
|
-
* **Mid-stream audio track switching is NOT this behavior's concern.**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* track change is dispatched from there via `audioBufferActor.send(...)`
|
|
177
|
-
* — keeping this setup behavior focused on per-source actor lifecycle.
|
|
150
|
+
* **Mid-stream audio track switching is NOT this behavior's concern.** Slot writes to `selectedAudioTrackId` (default
|
|
151
|
+
* selection, programmatic filter-driven, future ABR) are owned by `switchAudioTrack` / future `switchAudioQuality` in
|
|
152
|
+
* `track-switching.ts`. Flush orchestration on track change is dispatched from there via `audioBufferActor.send(...)` —
|
|
153
|
+
* keeping this setup behavior focused on per-source actor lifecycle.
|
|
178
154
|
*/
|
|
179
155
|
const setupAudioBufferActors = defineBehavior({
|
|
180
156
|
stateKeys: ["presentation", "selectedAudioTrackId"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-buffer-actors.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/setup-buffer-actors.ts"],"sourcesContent":["/**\n * **Per-type buffer + segment-loader actor setup.** Per available track\n * type (video / audio), when `mediaSource` is attached and the selected\n * track of that type is present in the presentation with codecs (partial\n * resolution from the multivariant playlist is enough — codecs live on\n * the `EXT-X-STREAM-INF` line, not in the per-type media playlist),\n * creates a `SourceBuffer`, a `SourceBufferActor`, and a\n * `SegmentLoaderActor` bound to that buffer-actor; publishes the per-type\n * actor slots. On `mediaSource` detach or behavior destroy, destroys both\n * actors in reverse order and clears the per-type slots so the next\n * source starts fresh.\n *\n * Each per-type variant (`setupVideoBufferActors` /\n * `setupAudioBufferActors`) is a single-positive-state reactor\n * (`'preconditions-unmet'` ↔ `'buffer-ready'`) gating only on its own\n * type. No cross-type coupling in `stateKeys` —\n * `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus\n * `bandwidthState`, written by its trackedFetch), and audio mirrors.\n *\n * # Firefox `mozHasAudio` invariant\n *\n * Appending to a video `SourceBuffer` before the audio `SourceBuffer`\n * exists causes `mozHasAudio` to be permanently false in Firefox. With\n * the two per-type variants decoupled, the invariant is no longer\n * structural to a single `entry` body (as it was when both buffers were\n * created in one synchronous block inside a merged behavior). It's now\n * preserved by a chain of assumptions about how this behavior composes\n * with its upstream and downstream siblings:\n *\n * 1. **Upstream — default selections land in one `runPending`.**\n * `selectAudioTrack` (default audio) and `switchVideoTrack`\n * (default video) both subscribe to `state.presentation` flipping to\n * resolved; their effects run in the same `runPending` iteration and\n * write `selectedAudioTrackId` + `selectedVideoTrackId` within it.\n * 2. **Self — both per-type monitors flip in one `runPending`.**\n * After (1), both monitors re-evaluate and flip to `'buffer-ready'`\n * in the next `runPending`. Both `entry` bodies run synchronously\n * within that iteration — both `addSourceBuffer` calls land before\n * the iteration ends.\n * 3. **Downstream — `appendBuffer` is async.** `loadVideoSegments` /\n * `loadAudioSegments` read the per-type `xSegmentLoaderActor` slots;\n * their effects fire in the *next* `runPending` and the actual\n * `appendBuffer` requires a network round-trip via the\n * `SegmentLoaderActor` — many microtasks past both `addSourceBuffer`\n * calls.\n *\n * The cross-tick failure mode — a user-initiated audio track switch\n * *after* video segments have begun appending — is out of scope for\n * this behavior and would be addressed in the buffer/segment-loading\n * path via `changeType`-aware logic.\n *\n * # Sole writer\n *\n * `setupVideoBufferActors` is sole writer of `videoBufferActor` +\n * `videoSegmentLoaderActor` (and `bandwidthState` via its\n * trackedFetch); `setupAudioBufferActors` is sole writer of\n * `audioBufferActor` + `audioSegmentLoaderActor`. Both read\n * `mediaSource` from `setupMediaSource`. Downstream MSE behaviors\n * (`loadVideoSegments`, `loadAudioSegments`, `endOfStream`,\n * `updateMediaSourceDuration`) only read these slots.\n */\nimport { listen } from '@videojs/utils/dom';\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 { buildMimeCodec, createSourceBuffer } from '../../../media/dom/mse/mediasource-setup';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack } from '../../../media/types';\nimport type { GetCdnId } from '../../../media/utils/cdn';\nimport { getSelectedTrack, type TrackSelectionState } from '../../../media/utils/track-selection';\nimport { hasCodecs } from '../../../media/utils/tracks';\nimport type { BandwidthState } from '../../../network/bandwidth-estimator';\nimport { createTrackedFetch, type FetchBytes, fetchStream } from '../../../network/fetch';\nimport {\n createSegmentLoaderActor,\n type SegmentLoaderActor,\n type SegmentLoaderActorConfig,\n} from '../../actors/dom/segment-loader';\nimport { createSourceBufferActor, type SourceBufferActor } from '../../actors/dom/source-buffer';\nimport { failoverFetch } from '../../primitives/failover-fetch';\nimport type { MessagePipelines } from '../../primitives/segment-load-pipeline';\nimport { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../../primitives/track-types';\n\n/**\n * Media track type for MSE buffer setup.\n * Text tracks are excluded as they don't use MSE SourceBuffers.\n */\nexport type MediaTrackType = 'video' | 'audio';\n\nexport interface BufferActorsState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n bandwidthState?: BandwidthState;\n}\n\nexport interface BufferActorsContext {\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n}\n\ntype BufferActorsFsmState = 'preconditions-unmet' | 'buffer-ready';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId';\ntype BufferActorKey = 'videoBufferActor' | 'audioBufferActor';\ntype SegmentLoaderActorKey = 'videoSegmentLoaderActor' | 'audioSegmentLoaderActor';\n\ntype BufferActorsStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<BufferActorsState['presentation']>;\n} & { [P in K]: ReadonlySignal<BufferActorsState[P]> };\n\ntype BufferActorsContextMap<A extends BufferActorKey, L extends SegmentLoaderActorKey> = {\n mediaSource: ReadonlySignal<BufferActorsContext['mediaSource']>;\n} & { [P in A]: Signal<SourceBufferActor | undefined> } & {\n [P in L]: Signal<SegmentLoaderActor | undefined>;\n};\n\n// ============================================================================\n// Specialization helper\n//\n// `setupBufferActors` has the same shape as a Behavior `setup` function:\n// `({ state, context, config }) => reactor`. Each `setupXBufferActors`\n// export below calls it from inside its own `defineBehavior` setup,\n// passing its narrowed `state` / `context` through directly and supplying\n// the per-type `selectedKey`, `actorKey`, `loaderKey`, `type` discriminator,\n// and `fetch` inline.\n//\n// Gating is per-type only: no cross-type coupling. See the file-level\n// JSDoc for how the Firefox `mozHasAudio` invariant survives the split\n// via SPF's effect coalescing.\n// ============================================================================\n\nfunction setupBufferActors<K extends SelectedTrackKey, A extends BufferActorKey, L extends SegmentLoaderActorKey>({\n state,\n context,\n config,\n}: {\n state: BufferActorsStateMap<K>;\n context: BufferActorsContextMap<A, L>;\n config: {\n type: MediaTrackType;\n selectedKey: K;\n actorKey: A;\n loaderKey: L;\n fetch: FetchBytes;\n } & SegmentLoaderActorConfig;\n}): Reactor<BufferActorsFsmState | 'destroying' | 'destroyed'> {\n const { type, selectedKey, actorKey, loaderKey, fetch, forwardBuffer, backBuffer, messagePipelines } = config;\n const derivedStateSignal = computed<BufferActorsFsmState>(() => {\n if (!context.mediaSource.get()) return 'preconditions-unmet';\n const selection: TrackSelectionState = {\n presentation: state.presentation.get(),\n [selectedKey]: state[selectedKey].get(),\n };\n return hasCodecs(getSelectedTrack(selection, type)) ? 'buffer-ready' : 'preconditions-unmet';\n });\n\n return createMachineReactor<BufferActorsFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'buffer-ready': {\n entry: () => {\n const mediaSource = context.mediaSource.get()!;\n const selection: TrackSelectionState = {\n presentation: state.presentation.get(),\n [selectedKey]: state[selectedKey].get(),\n };\n const track = getSelectedTrack(selection, type) as PartiallyResolvedTrack;\n const buffer = createSourceBuffer(mediaSource, buildMimeCodec(track));\n const bufferActor = createSourceBufferActor(buffer);\n const segmentLoader = createSegmentLoaderActor(\n bufferActor,\n fetch,\n { forwardBuffer, backBuffer, messagePipelines },\n // Thread the composition deps opaquely to steps (relocation reads\n // `state.mediaContainerData`); the loader/this behavior never read them.\n { state, context, config }\n );\n\n // Synchronous slot writes — load-bearing for the Firefox\n // `mozHasAudio` invariant (see file-level JSDoc). Both per-type\n // entries' `addSourceBuffer` calls must land in the same\n // `runPending` iteration; synchronous writes keep the contiguous\n // JS frame. Downstream `loadXSegments` effects fire in the\n // *next* `runPending`.\n context[actorKey].set(bufferActor);\n context[loaderKey].set(segmentLoader);\n\n // State-exit cleanup — fires when `mediaSource` detaches, the\n // selection unsets, or the behavior is destroyed. Destroy the\n // loader before its upstream buffer-actor so any in-flight\n // `appendBuffer` is aborted before the buffer-actor's own\n // teardown. Idempotent, so the sourceclose listener below shares it.\n const disconnect = new AbortController();\n const teardownActors = () => {\n segmentLoader.destroy();\n bufferActor.destroy();\n context[loaderKey].set(undefined);\n context[actorKey].set(undefined);\n disconnect.abort();\n };\n\n // Also tear down when the UA closes the MediaSource out from under\n // us (notably an AirPlay handoff — see `setupAirPlay`). Must be a\n // DOM listener, not reactive cleanup: already-queued append\n // continuations outrun any effect flush, and only a synchronous\n // abort keeps them off the dead SourceBuffer (InvalidStateError).\n listen(mediaSource, 'sourceclose', teardownActors, { signal: disconnect.signal });\n\n return teardownActors;\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Specialized exports — one per media type\n// ============================================================================\n\n/**\n * Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires\n * when `mediaSource` is attached and the selected video track is\n * present in the presentation with codecs. Gates only on video state —\n * no cross-type coupling. Owns a bandwidth-sampling `trackedFetch` and\n * is sole writer of `state.bandwidthState`.\n */\nexport const setupVideoBufferActors = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId', 'bandwidthState'] as const,\n contextKeys: ['mediaSource', 'videoBufferActor', 'videoSegmentLoaderActor'] as const,\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: BufferActorsStateMap<'selectedVideoTrackId'> & {\n bandwidthState: Signal<BufferActorsState['bandwidthState']>;\n };\n context: BufferActorsContextMap<'videoBufferActor', 'videoSegmentLoaderActor'>;\n config?: SegmentLoaderActorConfig & {\n getCdnId?: GetCdnId;\n /** Optional non-zero-PTS relocation pipelines (Tier-1); the loader uses its Tier-0 default when absent. */\n videoMessagePipelines?: MessagePipelines;\n };\n }) => {\n // Bandwidth-sampling fetch. The factory accumulates EWMA state\n // internally; the callback bridges samples to engine state for ABR.\n // Created once at behavior setup time so the EWMA accumulates across\n // source resets (each per-source loader-actor receives the same\n // tracked fetch when reconstructed in `buffer-ready` entry).\n const trackedFetch = createTrackedFetch(\n state.bandwidthState.get() ?? {\n fastEstimate: 0,\n fastTotalWeight: 0,\n slowEstimate: 0,\n slowTotalWeight: 0,\n bytesSampled: 0,\n },\n (next) => state.bandwidthState.set(next)\n );\n // Engine `config` layers over the per-type defaults; `failoverFetch` reads\n // its `selectedKey` + `getCdnId` from the merged result.\n const typeConfig = { ...VIDEO_TYPE_CONFIG, ...config };\n return setupBufferActors({\n state,\n context,\n config: {\n ...typeConfig,\n messagePipelines: config.videoMessagePipelines,\n fetch: failoverFetch(trackedFetch, state, typeConfig),\n },\n });\n },\n});\n\n/**\n * Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same\n * shape as `setupVideoBufferActors`, narrowed to audio. Today supplies\n * a non-sampling `fetchStream` (no audio ABR); adding audio ABR is a\n * localized change to this setup body (swap `fetchStream` for a\n * `createTrackedFetch` call + declare `bandwidthState` writable here)\n * without touching the shared helper. See\n * `internal/design/spf/features/audio-abr.md` for the design surface\n * (bandwidth-state sharing, multi-writer coordination, EWMA mixed-\n * source sampling).\n *\n * **Mid-stream audio track switching is NOT this behavior's concern.**\n * Slot writes to `selectedAudioTrackId` (default selection, programmatic\n * filter-driven, future ABR) are owned by `switchAudioTrack` / future\n * `switchAudioQuality` in `track-switching.ts`. Flush orchestration on\n * track change is dispatched from there via `audioBufferActor.send(...)`\n * — keeping this setup behavior focused on per-source actor lifecycle.\n */\nexport const setupAudioBufferActors = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'] as const,\n contextKeys: ['mediaSource', 'audioBufferActor', 'audioSegmentLoaderActor'] as const,\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: BufferActorsStateMap<'selectedAudioTrackId'>;\n context: BufferActorsContextMap<'audioBufferActor', 'audioSegmentLoaderActor'>;\n config?: SegmentLoaderActorConfig & {\n getCdnId?: GetCdnId;\n /** Optional non-zero-PTS relocation pipelines (Tier-1); the loader uses its Tier-0 default when absent. */\n audioMessagePipelines?: MessagePipelines;\n };\n }) => {\n // Key order mirrors setupVideoBufferActors.\n const typeConfig = { ...AUDIO_TYPE_CONFIG, ...config };\n return setupBufferActors({\n state,\n context,\n config: {\n ...typeConfig,\n messagePipelines: config.audioMessagePipelines,\n fetch: failoverFetch(fetchStream, state, typeConfig),\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuIA,SAAS,kBAAyG,EAChH,OACA,SACA,UAW6D;CAC7D,MAAM,EAAE,MAAM,aAAa,UAAU,WAAW,OAAO,eAAe,YAAY,qBAAqB;CACvG,MAAM,qBAAqB,eAAqC;EAC9D,IAAI,CAAC,QAAQ,YAAY,IAAI,GAAG,OAAO;EAKvC,OAAO,UAAU,iBAAiB;GAHhC,cAAc,MAAM,aAAa,IAAI;IACpC,cAAc,MAAM,YAAY,CAAC,IAAI;EAEE,GAAG,IAAI,CAAC,IAAI,iBAAiB;CACzE,CAAC;CAED,OAAO,qBAA2C;EAChD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GAExB,gBAAgB,EACd,aAAa;IACX,MAAM,cAAc,QAAQ,YAAY,IAAI;IAO5C,MAAM,cAAc,wBADL,mBAAmB,aAAa,eADjC,iBAAiB;KAH7B,cAAc,MAAM,aAAa,IAAI;MACpC,cAAc,MAAM,YAAY,CAAC,IAAI;IAED,GAAG,IACwB,CAAC,CAClB,CAAC;IAClD,MAAM,gBAAgB,yBACpB,aACA,OACA;KAAE;KAAe;KAAY;IAAiB,GAG9C;KAAE;KAAO;KAAS;IAAO,CAC3B;IAQA,QAAQ,SAAS,CAAC,IAAI,WAAW;IACjC,QAAQ,UAAU,CAAC,IAAI,aAAa;IAOpC,MAAM,aAAa,IAAI,gBAAgB;IACvC,MAAM,uBAAuB;KAC3B,cAAc,QAAQ;KACtB,YAAY,QAAQ;KACpB,QAAQ,UAAU,CAAC,IAAI,KAAA,CAAS;KAChC,QAAQ,SAAS,CAAC,IAAI,KAAA,CAAS;KAC/B,WAAW,MAAM;IACnB;IAOA,OAAO,aAAa,eAAe,gBAAgB,EAAE,QAAQ,WAAW,OAAO,CAAC;IAEhF,OAAO;GACT,EACF;EACF;CACF,CAAC;AACH;;;;;;;;AAaA,MAAa,yBAAyB,eAAe;CACnD,WAAW;EAAC;EAAgB;EAAwB;CAAgB;CACpE,aAAa;EAAC;EAAe;EAAoB;CAAyB;CAC1E,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAWN;EAMJ,MAAM,eAAe,mBACnB,MAAM,eAAe,IAAI,KAAK;GAC5B,cAAc;GACd,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,cAAc;EAChB,IACC,SAAS,MAAM,eAAe,IAAI,IAAI,CACzC;EAGA,MAAM,aAAa;GAAE,GAAG;GAAmB,GAAG;EAAO;EACrD,OAAO,kBAAkB;GACvB;GACA;GACA,QAAQ;IACN,GAAG;IACH,kBAAkB,OAAO;IACzB,OAAO,cAAc,cAAc,OAAO,UAAU;GACtD;EACF,CAAC;CACH;AACF,CAAC;;;;;;;;;;;;;;;;;;;AAoBD,MAAa,yBAAyB,eAAe;CACnD,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa;EAAC;EAAe;EAAoB;CAAyB;CAC1E,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QASN;EAEJ,MAAM,aAAa;GAAE,GAAG;GAAmB,GAAG;EAAO;EACrD,OAAO,kBAAkB;GACvB;GACA;GACA,QAAQ;IACN,GAAG;IACH,kBAAkB,OAAO;IACzB,OAAO,cAAc,aAAa,OAAO,UAAU;GACrD;EACF,CAAC;CACH;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"setup-buffer-actors.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/setup-buffer-actors.ts"],"sourcesContent":["/**\n * **Per-type buffer + segment-loader actor setup.** Per available track type (video / audio), when `mediaSource` is\n * attached and the selected track of that type is present in the presentation with codecs (partial resolution from the\n * multivariant playlist is enough — codecs live on the `EXT-X-STREAM-INF` line, not in the per-type media playlist),\n * creates a `SourceBuffer`, a `SourceBufferActor`, and a `SegmentLoaderActor` bound to that buffer-actor; publishes the\n * per-type actor slots. On `mediaSource` detach or behavior destroy, destroys both actors in reverse order and clears\n * the per-type slots so the next source starts fresh.\n *\n * Each per-type variant (`setupVideoBufferActors` / `setupAudioBufferActors`) is a single-positive-state reactor\n * (`'preconditions-unmet'` ↔ `'buffer-ready'`) gating only on its own type. No cross-type coupling in `stateKeys` —\n * `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus `bandwidthState`, written by its trackedFetch),\n * and audio mirrors.\n *\n * # Firefox `mozHasAudio` invariant\n *\n * Appending to a video `SourceBuffer` before the audio `SourceBuffer` exists causes `mozHasAudio` to be permanently\n * false in Firefox. With the two per-type variants decoupled, the invariant is no longer structural to a single `entry`\n * body (as it was when both buffers were created in one synchronous block inside a merged behavior). It's now preserved\n * by a chain of assumptions about how this behavior composes with its upstream and downstream siblings:\n *\n * 1. **Upstream — default selections land in one `runPending`.** `selectAudioTrack` (default audio) and `switchVideoTrack`\n * (default video) both subscribe to `state.presentation` flipping to resolved; their effects run in the same\n * `runPending` iteration and write `selectedAudioTrackId` + `selectedVideoTrackId` within it.\n * 2. **Self — both per-type monitors flip in one `runPending`.** After (1), both monitors re-evaluate and flip to\n * `'buffer-ready'` in the next `runPending`. Both `entry` bodies run synchronously within that iteration — both\n * `addSourceBuffer` calls land before the iteration ends.\n * 3. **Downstream — `appendBuffer` is async.** `loadVideoSegments` / `loadAudioSegments` read the per-type\n * `xSegmentLoaderActor` slots; their effects fire in the _next_ `runPending` and the actual `appendBuffer` requires\n * a network round-trip via the `SegmentLoaderActor` — many microtasks past both `addSourceBuffer` calls.\n *\n * The cross-tick failure mode — a user-initiated audio track switch _after_ video segments have begun appending — is\n * out of scope for this behavior and would be addressed in the buffer/segment-loading path via `changeType`-aware\n * logic.\n *\n * # Sole writer\n *\n * `setupVideoBufferActors` is sole writer of `videoBufferActor` + `videoSegmentLoaderActor` (and `bandwidthState` via\n * its trackedFetch); `setupAudioBufferActors` is sole writer of `audioBufferActor` + `audioSegmentLoaderActor`. Both\n * read `mediaSource` from `setupMediaSource`. Downstream MSE behaviors (`loadVideoSegments`, `loadAudioSegments`,\n * `endOfStream`, `updateMediaSourceDuration`) only read these slots.\n */\nimport { listen } from '@videojs/utils/dom';\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 { buildMimeCodec, createSourceBuffer } from '../../../media/dom/mse/mediasource-setup';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack } from '../../../media/types';\nimport type { GetCdnId } from '../../../media/utils/cdn';\nimport { getSelectedTrack, type TrackSelectionState } from '../../../media/utils/track-selection';\nimport { hasCodecs } from '../../../media/utils/tracks';\nimport type { BandwidthState } from '../../../network/bandwidth-estimator';\nimport { createTrackedFetch, type FetchBytes, fetchStream } from '../../../network/fetch';\nimport {\n createSegmentLoaderActor,\n type SegmentLoaderActor,\n type SegmentLoaderActorConfig,\n} from '../../actors/dom/segment-loader';\nimport { createSourceBufferActor, type SourceBufferActor } from '../../actors/dom/source-buffer';\nimport { failoverFetch } from '../../primitives/failover-fetch';\nimport type { MessagePipelines } from '../../primitives/segment-load-pipeline';\nimport { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../../primitives/track-types';\n\n/** Media track type for MSE buffer setup. Text tracks are excluded as they don't use MSE SourceBuffers. */\nexport type MediaTrackType = 'video' | 'audio';\n\nexport interface BufferActorsState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n bandwidthState?: BandwidthState;\n}\n\nexport interface BufferActorsContext {\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n}\n\ntype BufferActorsFsmState = 'preconditions-unmet' | 'buffer-ready';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId';\ntype BufferActorKey = 'videoBufferActor' | 'audioBufferActor';\ntype SegmentLoaderActorKey = 'videoSegmentLoaderActor' | 'audioSegmentLoaderActor';\n\ntype BufferActorsStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<BufferActorsState['presentation']>;\n} & { [P in K]: ReadonlySignal<BufferActorsState[P]> };\n\ntype BufferActorsContextMap<A extends BufferActorKey, L extends SegmentLoaderActorKey> = {\n mediaSource: ReadonlySignal<BufferActorsContext['mediaSource']>;\n} & { [P in A]: Signal<SourceBufferActor | undefined> } & {\n [P in L]: Signal<SegmentLoaderActor | undefined>;\n};\n\n// ============================================================================\n// Specialization helper\n//\n// `setupBufferActors` has the same shape as a Behavior `setup` function:\n// `({ state, context, config }) => reactor`. Each `setupXBufferActors`\n// export below calls it from inside its own `defineBehavior` setup,\n// passing its narrowed `state` / `context` through directly and supplying\n// the per-type `selectedKey`, `actorKey`, `loaderKey`, `type` discriminator,\n// and `fetch` inline.\n//\n// Gating is per-type only: no cross-type coupling. See the file-level\n// JSDoc for how the Firefox `mozHasAudio` invariant survives the split\n// via SPF's effect coalescing.\n// ============================================================================\n\nfunction setupBufferActors<K extends SelectedTrackKey, A extends BufferActorKey, L extends SegmentLoaderActorKey>({\n state,\n context,\n config,\n}: {\n state: BufferActorsStateMap<K>;\n context: BufferActorsContextMap<A, L>;\n config: {\n type: MediaTrackType;\n selectedKey: K;\n actorKey: A;\n loaderKey: L;\n fetch: FetchBytes;\n } & SegmentLoaderActorConfig;\n}): Reactor<BufferActorsFsmState | 'destroying' | 'destroyed'> {\n const { type, selectedKey, actorKey, loaderKey, fetch, forwardBuffer, backBuffer, messagePipelines } = config;\n const derivedStateSignal = computed<BufferActorsFsmState>(() => {\n if (!context.mediaSource.get()) return 'preconditions-unmet';\n\n const selection: TrackSelectionState = {\n presentation: state.presentation.get(),\n [selectedKey]: state[selectedKey].get(),\n };\n\n return hasCodecs(getSelectedTrack(selection, type)) ? 'buffer-ready' : 'preconditions-unmet';\n });\n\n return createMachineReactor<BufferActorsFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'buffer-ready': {\n entry: () => {\n const mediaSource = context.mediaSource.get()!;\n const selection: TrackSelectionState = {\n presentation: state.presentation.get(),\n [selectedKey]: state[selectedKey].get(),\n };\n const track = getSelectedTrack(selection, type) as PartiallyResolvedTrack;\n const buffer = createSourceBuffer(mediaSource, buildMimeCodec(track));\n const bufferActor = createSourceBufferActor(buffer);\n const segmentLoader = createSegmentLoaderActor(\n bufferActor,\n fetch,\n { forwardBuffer, backBuffer, messagePipelines },\n // Thread the composition deps opaquely to steps (relocation reads\n // `state.mediaContainerData`); the loader/this behavior never read them.\n { state, context, config }\n );\n\n // Synchronous slot writes — load-bearing for the Firefox\n // `mozHasAudio` invariant (see file-level JSDoc). Both per-type\n // entries' `addSourceBuffer` calls must land in the same\n // `runPending` iteration; synchronous writes keep the contiguous\n // JS frame. Downstream `loadXSegments` effects fire in the\n // *next* `runPending`.\n context[actorKey].set(bufferActor);\n context[loaderKey].set(segmentLoader);\n\n // State-exit cleanup — fires when `mediaSource` detaches, the\n // selection unsets, or the behavior is destroyed. Destroy the\n // loader before its upstream buffer-actor so any in-flight\n // `appendBuffer` is aborted before the buffer-actor's own\n // teardown. Idempotent, so the sourceclose listener below shares it.\n const disconnect = new AbortController();\n const teardownActors = () => {\n segmentLoader.destroy();\n bufferActor.destroy();\n context[loaderKey].set(undefined);\n context[actorKey].set(undefined);\n disconnect.abort();\n };\n\n // Also tear down when the UA closes the MediaSource out from under\n // us (notably an AirPlay handoff — see `setupAirPlay`). Must be a\n // DOM listener, not reactive cleanup: already-queued append\n // continuations outrun any effect flush, and only a synchronous\n // abort keeps them off the dead SourceBuffer (InvalidStateError).\n listen(mediaSource, 'sourceclose', teardownActors, { signal: disconnect.signal });\n\n return teardownActors;\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Specialized exports — one per media type\n// ============================================================================\n\n/**\n * Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires when `mediaSource` is attached and the selected\n * video track is present in the presentation with codecs. Gates only on video state — no cross-type coupling. Owns a\n * bandwidth-sampling `trackedFetch` and is sole writer of `state.bandwidthState`.\n */\nexport const setupVideoBufferActors = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId', 'bandwidthState'] as const,\n contextKeys: ['mediaSource', 'videoBufferActor', 'videoSegmentLoaderActor'] as const,\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: BufferActorsStateMap<'selectedVideoTrackId'> & {\n bandwidthState: Signal<BufferActorsState['bandwidthState']>;\n };\n context: BufferActorsContextMap<'videoBufferActor', 'videoSegmentLoaderActor'>;\n config?: SegmentLoaderActorConfig & {\n getCdnId?: GetCdnId;\n /** Optional non-zero-PTS relocation pipelines (Tier-1); the loader uses its Tier-0 default when absent. */\n videoMessagePipelines?: MessagePipelines;\n };\n }) => {\n // Bandwidth-sampling fetch. The factory accumulates EWMA state\n // internally; the callback bridges samples to engine state for ABR.\n // Created once at behavior setup time so the EWMA accumulates across\n // source resets (each per-source loader-actor receives the same\n // tracked fetch when reconstructed in `buffer-ready` entry).\n const trackedFetch = createTrackedFetch(\n state.bandwidthState.get() ?? {\n fastEstimate: 0,\n fastTotalWeight: 0,\n slowEstimate: 0,\n slowTotalWeight: 0,\n bytesSampled: 0,\n },\n (next) => state.bandwidthState.set(next)\n );\n // Engine `config` layers over the per-type defaults; `failoverFetch` reads\n // its `selectedKey` + `getCdnId` from the merged result.\n const typeConfig = { ...VIDEO_TYPE_CONFIG, ...config };\n\n return setupBufferActors({\n state,\n context,\n config: {\n ...typeConfig,\n messagePipelines: config.videoMessagePipelines,\n fetch: failoverFetch(trackedFetch, state, typeConfig),\n },\n });\n },\n});\n\n/**\n * Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same shape as `setupVideoBufferActors`, narrowed to\n * audio. Today supplies a non-sampling `fetchStream` (no audio ABR); adding audio ABR is a localized change to this\n * setup body (swap `fetchStream` for a `createTrackedFetch` call + declare `bandwidthState` writable here) without\n * touching the shared helper. See `internal/design/spf/features/audio-abr.md` for the design surface (bandwidth-state\n * sharing, multi-writer coordination, EWMA mixed- source sampling).\n *\n * **Mid-stream audio track switching is NOT this behavior's concern.** Slot writes to `selectedAudioTrackId` (default\n * selection, programmatic filter-driven, future ABR) are owned by `switchAudioTrack` / future `switchAudioQuality` in\n * `track-switching.ts`. Flush orchestration on track change is dispatched from there via `audioBufferActor.send(...)` —\n * keeping this setup behavior focused on per-source actor lifecycle.\n */\nexport const setupAudioBufferActors = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'] as const,\n contextKeys: ['mediaSource', 'audioBufferActor', 'audioSegmentLoaderActor'] as const,\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: BufferActorsStateMap<'selectedAudioTrackId'>;\n context: BufferActorsContextMap<'audioBufferActor', 'audioSegmentLoaderActor'>;\n config?: SegmentLoaderActorConfig & {\n getCdnId?: GetCdnId;\n /** Optional non-zero-PTS relocation pipelines (Tier-1); the loader uses its Tier-0 default when absent. */\n audioMessagePipelines?: MessagePipelines;\n };\n }) => {\n // Key order mirrors setupVideoBufferActors.\n const typeConfig = { ...AUDIO_TYPE_CONFIG, ...config };\n\n return setupBufferActors({\n state,\n context,\n config: {\n ...typeConfig,\n messagePipelines: config.audioMessagePipelines,\n fetch: failoverFetch(fetchStream, state, typeConfig),\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiHA,SAAS,kBAAyG,EAChH,OACA,SACA,UAW6D;CAC7D,MAAM,EAAE,MAAM,aAAa,UAAU,WAAW,OAAO,eAAe,YAAY,qBAAqB;CACvG,MAAM,qBAAqB,eAAqC;EAC9D,IAAI,CAAC,QAAQ,YAAY,IAAI,GAAG,OAAO;EAEvC,MAAM,YAAiC;GACrC,cAAc,MAAM,aAAa,IAAI;IACpC,cAAc,MAAM,YAAY,CAAC,IAAI;EACxC;EAEA,OAAO,UAAU,iBAAiB,WAAW,IAAI,CAAC,IAAI,iBAAiB;CACzE,CAAC;CAED,OAAO,qBAA2C;EAChD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GAExB,gBAAgB,EACd,aAAa;IACX,MAAM,cAAc,QAAQ,YAAY,IAAI;IAC5C,MAAM,YAAiC;KACrC,cAAc,MAAM,aAAa,IAAI;MACpC,cAAc,MAAM,YAAY,CAAC,IAAI;IACxC;IACA,MAAM,QAAQ,iBAAiB,WAAW,IAAI;IAC9C,MAAM,SAAS,mBAAmB,aAAa,eAAe,KAAK,CAAC;IACpE,MAAM,cAAc,wBAAwB,MAAM;IAClD,MAAM,gBAAgB,yBACpB,aACA,OACA;KAAE;KAAe;KAAY;IAAiB,GAG9C;KAAE;KAAO;KAAS;IAAO,CAC3B;IAQA,QAAQ,SAAS,CAAC,IAAI,WAAW;IACjC,QAAQ,UAAU,CAAC,IAAI,aAAa;IAOpC,MAAM,aAAa,IAAI,gBAAgB;IACvC,MAAM,uBAAuB;KAC3B,cAAc,QAAQ;KACtB,YAAY,QAAQ;KACpB,QAAQ,UAAU,CAAC,IAAI,KAAA,CAAS;KAChC,QAAQ,SAAS,CAAC,IAAI,KAAA,CAAS;KAC/B,WAAW,MAAM;IACnB;IAOA,OAAO,aAAa,eAAe,gBAAgB,EAAE,QAAQ,WAAW,OAAO,CAAC;IAEhF,OAAO;GACT,EACF;EACF;CACF,CAAC;AACH;;;;;;AAWA,MAAa,yBAAyB,eAAe;CACnD,WAAW;EAAC;EAAgB;EAAwB;CAAgB;CACpE,aAAa;EAAC;EAAe;EAAoB;CAAyB;CAC1E,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAWN;EAMJ,MAAM,eAAe,mBACnB,MAAM,eAAe,IAAI,KAAK;GAC5B,cAAc;GACd,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,cAAc;EAChB,IACC,SAAS,MAAM,eAAe,IAAI,IAAI,CACzC;EAGA,MAAM,aAAa;GAAE,GAAG;GAAmB,GAAG;EAAO;EAErD,OAAO,kBAAkB;GACvB;GACA;GACA,QAAQ;IACN,GAAG;IACH,kBAAkB,OAAO;IACzB,OAAO,cAAc,cAAc,OAAO,UAAU;GACtD;EACF,CAAC;CACH;AACF,CAAC;;;;;;;;;;;;;AAcD,MAAa,yBAAyB,eAAe;CACnD,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa;EAAC;EAAe;EAAoB;CAAyB;CAC1E,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QASN;EAEJ,MAAM,aAAa;GAAE,GAAG;GAAmB,GAAG;EAAO;EAErD,OAAO,kBAAkB;GACvB;GACA;GACA,QAAQ;IACN,GAAG;IACH,kBAAkB,OAAO;IACzB,OAAO,cAAc,aAAa,OAAO,UAAU;GACrD;EACF,CAAC;CACH;AACF,CAAC"}
|
|
@@ -6,58 +6,44 @@ import { attachMediaSource, createMediaSource, waitForMediaSourceOpen } from "..
|
|
|
6
6
|
import { listen } from "@videojs/utils/dom";
|
|
7
7
|
//#region src/playback/behaviors/dom/setup-mediasource.ts
|
|
8
8
|
/**
|
|
9
|
-
* **Own the MediaSource lifecycle for the current source.** When a resolved
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* MediaSource and clears the slot so the next source starts fresh.
|
|
9
|
+
* **Own the MediaSource lifecycle for the current source.** When a resolved presentation and a mediaElement are both in
|
|
10
|
+
* scope, creates a MediaSource, attaches it to the element, waits for `'open'`, and publishes it on
|
|
11
|
+
* `context.mediaSource`. On source change or behavior destroy, detaches the MediaSource and clears the slot so the next
|
|
12
|
+
* source starts fresh.
|
|
14
13
|
*
|
|
15
|
-
* Single-positive-state reactor (`'preconditions-unmet'` ↔ `'mediasource-attached'`):
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `'preconditions-unmet'` so the entry's state-exit cleanup detaches the old
|
|
21
|
-
* MediaSource before the new one is built.
|
|
14
|
+
* Single-positive-state reactor (`'preconditions-unmet'` ↔ `'mediasource-attached'`): state derivation gates on
|
|
15
|
+
* `mediaElement + isResolvedPresentation`. Riding the resolver's resolved/unresolved lifecycle makes direct URL
|
|
16
|
+
* replacement structural — `resolvePresentation` routes the presentation back through unresolved on URL change, which
|
|
17
|
+
* drives this reactor through `'preconditions-unmet'` so the entry's state-exit cleanup detaches the old MediaSource
|
|
18
|
+
* before the new one is built.
|
|
22
19
|
*
|
|
23
20
|
* The entry resolves preconditions in sequence before publishing:
|
|
24
21
|
*
|
|
25
|
-
* 1. **Create + attach** — `createMediaSource` + `attachMediaSource` run
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* `context.mediaSource`. Downstream `setupVideoBufferActors` /
|
|
34
|
-
* `setupAudioBufferActors` call `addSourceBuffer` directly, which
|
|
35
|
-
* throws on non-open, so publish-only-when-open is the load-bearing
|
|
36
|
-
* contract.
|
|
22
|
+
* 1. **Create + attach** — `createMediaSource` + `attachMediaSource` run synchronously on entry. The `detach` closure
|
|
23
|
+
* returned by `attachMediaSource` is captured for state-exit cleanup, so the cleanup is always bound to its setup
|
|
24
|
+
* even if the wait below is aborted.
|
|
25
|
+
* 2. **Wait for `'open'`** — `waitForMediaSourceOpen` defers until the first `sourceopen` event (or any readyState
|
|
26
|
+
* transition out of `'closed'`).
|
|
27
|
+
* 3. **Publish on `'open'`** — re-check `readyState === 'open'` after the await (covers `'ended'` / `'closed'` race)
|
|
28
|
+
* before writing to `context.mediaSource`. Downstream `setupVideoBufferActors` / `setupAudioBufferActors` call
|
|
29
|
+
* `addSourceBuffer` directly, which throws on non-open, so publish-only-when-open is the load-bearing contract.
|
|
37
30
|
*
|
|
38
|
-
* State-exit cleanup aborts the in-flight wait, detaches the MediaSource,
|
|
39
|
-
*
|
|
40
|
-
* publish racing the slot clear), then detach, then clear.
|
|
31
|
+
* State-exit cleanup aborts the in-flight wait, detaches the MediaSource, and clears `context.mediaSource`. Order:
|
|
32
|
+
* abort first (prevents a late publish racing the slot clear), then detach, then clear.
|
|
41
33
|
*
|
|
42
34
|
* # Sourceclose recovery
|
|
43
35
|
*
|
|
44
|
-
* The behavior owns one **unclosed** MediaSource per source identity. The UA can
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* the
|
|
51
|
-
*
|
|
52
|
-
* observed `loadingSuspended` (attaching runs `element.load()` — new loading
|
|
53
|
-
* work, e.g. resource selection under an active AirPlay receiver), and
|
|
54
|
-
* happens only while the machine is out, so a suspension can never tear
|
|
55
|
-
* down an existing attachment.
|
|
36
|
+
* The behavior owns one **unclosed** MediaSource per source identity. The UA can close the attached MediaSource out
|
|
37
|
+
* from under the engine (Safari on an AirPlay handoff — see `setupAirPlay` — or a ManagedMediaSource evicted under
|
|
38
|
+
* memory pressure), and a closed MediaSource can never reopen. The `sourceclose` listener tears the attachment down
|
|
39
|
+
* synchronously; every teardown records a local close-fact, which holds the machine out until the fact is consumed —
|
|
40
|
+
* then the re-derive comes back in with a fresh MediaSource for the _same_ source. Cause-agnostic. Consumption honors
|
|
41
|
+
* an observed `loadingSuspended` (attaching runs `element.load()` — new loading work, e.g. resource selection under an
|
|
42
|
+
* active AirPlay receiver), and happens only while the machine is out, so a suspension can never tear down an existing
|
|
43
|
+
* attachment.
|
|
56
44
|
*
|
|
57
|
-
* Sole writer of `context.mediaSource`; other MSE behaviors
|
|
58
|
-
*
|
|
59
|
-
* `updateMediaSourceDuration`, `endOfStream`, `loadVideoSegments`) only
|
|
60
|
-
* read.
|
|
45
|
+
* Sole writer of `context.mediaSource`; other MSE behaviors (`setupVideoBufferActors`, `setupAudioBufferActors`,
|
|
46
|
+
* `updateMediaSourceDuration`, `endOfStream`, `loadVideoSegments`) only read.
|
|
61
47
|
*/
|
|
62
48
|
function deriveState(presentation, mediaElement, mediaSourceClosed) {
|
|
63
49
|
if (!mediaElement || !isResolvedPresentation(presentation)) return "preconditions-unmet";
|