@videojs/spf 10.0.0-beta.3 → 10.0.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -38
- package/dist/default/core/actors/create-machine-actor.js +91 -0
- package/dist/default/core/actors/create-machine-actor.js.map +1 -0
- package/dist/default/core/actors/create-transition-actor.js +46 -0
- package/dist/default/core/actors/create-transition-actor.js.map +1 -0
- package/dist/default/core/composition/create-composition.js +85 -0
- package/dist/default/core/composition/create-composition.js.map +1 -0
- package/dist/default/core/composition/share-signals.js +39 -0
- package/dist/default/core/composition/share-signals.js.map +1 -0
- package/dist/default/core/machine.js +26 -0
- package/dist/default/core/machine.js.map +1 -0
- package/dist/default/core/reactors/create-machine-reactor.js +83 -0
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/default/core/signals/effect.js +41 -0
- package/dist/default/core/signals/effect.js.map +1 -0
- package/dist/default/core/signals/primitives.js +54 -0
- package/dist/default/core/signals/primitives.js.map +1 -0
- package/dist/default/core/signals/when.js +38 -0
- package/dist/default/core/signals/when.js.map +1 -0
- package/dist/default/core/tasks/delayed-reschedule.js +31 -0
- package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/default/core/tasks/task.js +321 -0
- package/dist/default/core/tasks/task.js.map +1 -0
- package/dist/default/dom.js +9 -0
- package/dist/default/hls-audio.js +3 -0
- package/dist/default/hls-background-video.js +3 -0
- package/dist/default/hls-video.js +4 -0
- package/dist/default/hls.js +8 -0
- package/dist/default/index.js +23 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/media/abr/quality-selection.js +21 -0
- package/dist/default/media/abr/quality-selection.js.map +1 -0
- package/dist/default/media/buffer/back-buffer.js +46 -0
- package/dist/default/media/buffer/back-buffer.js.map +1 -0
- package/dist/default/media/buffer/forward-buffer.js +119 -0
- package/dist/default/media/buffer/forward-buffer.js.map +1 -0
- package/dist/default/media/dom/capabilities.js +72 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/default/media/dom/mse/append-segment.js +60 -0
- package/dist/default/media/dom/mse/append-segment.js.map +1 -0
- package/dist/default/media/dom/mse/buffer-flusher.js +55 -0
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/default/media/dom/mse/duration.js +95 -0
- package/dist/default/media/dom/mse/duration.js.map +1 -0
- package/dist/default/media/dom/mse/end-of-stream.js +20 -0
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/default/media/dom/mse/mediasource-setup.js +242 -0
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/default/media/dom/screen.js +112 -0
- package/dist/default/media/dom/screen.js.map +1 -0
- package/dist/default/media/dom/text/resolve-vtt-segment.js +50 -0
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/default/media/dom/text/text-track-slots.js +69 -0
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/default/media/errors.js +69 -0
- package/dist/default/media/errors.js.map +1 -0
- package/dist/default/media/hls/parse-attributes.js +148 -0
- package/dist/default/media/hls/parse-attributes.js.map +1 -0
- package/dist/default/media/hls/parse-media-playlist.js +261 -0
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/default/media/hls/parse-multivariant.js +234 -0
- package/dist/default/media/hls/parse-multivariant.js.map +1 -0
- package/dist/default/media/hls/reload-policy.js +71 -0
- package/dist/default/media/hls/reload-policy.js.map +1 -0
- package/dist/default/media/hls/resolve-url.js +11 -0
- package/dist/default/media/hls/resolve-url.js.map +1 -0
- package/dist/default/media/live-window.js +36 -0
- package/dist/default/media/live-window.js.map +1 -0
- package/dist/default/media/media-tracks/media-tracks.js +101 -0
- package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/default/media/mp4/box.js +58 -0
- package/dist/default/media/mp4/box.js.map +1 -0
- package/dist/default/media/mp4/timestamp-origin.js +107 -0
- package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/default/media/primitives/resolution.js +29 -0
- package/dist/default/media/primitives/resolution.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js +107 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
- package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/default/media/types/index.js +50 -0
- package/dist/default/media/types/index.js.map +1 -0
- package/dist/default/media/utils/cdn.js +56 -0
- package/dist/default/media/utils/cdn.js.map +1 -0
- package/dist/default/media/utils/preload.js +22 -0
- package/dist/default/media/utils/preload.js.map +1 -0
- package/dist/default/media/utils/track-selection.js +47 -0
- package/dist/default/media/utils/track-selection.js.map +1 -0
- package/dist/default/media/utils/tracks.js +144 -0
- package/dist/default/media/utils/tracks.js.map +1 -0
- package/dist/default/media-tracks.js +2 -0
- package/dist/default/mux-audio.js +3 -0
- package/dist/default/mux-background-video.js +3 -0
- package/dist/default/mux-video.js +3 -0
- package/dist/default/network/bandwidth-estimator.js +96 -0
- package/dist/default/network/bandwidth-estimator.js.map +1 -0
- package/dist/default/network/chunked-stream-iterable.js +49 -0
- package/dist/default/network/chunked-stream-iterable.js.map +1 -0
- package/dist/default/network/ewma.js +71 -0
- package/dist/default/network/ewma.js.map +1 -0
- package/dist/default/network/fetch.js +115 -0
- package/dist/default/network/fetch.js.map +1 -0
- package/dist/default/playback/actors/dom/segment-loader.js +249 -0
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/default/playback/actors/dom/source-buffer.js +139 -0
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/default/playback/actors/dom/text-tracks.js +46 -0
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/default/playback/actors/text-track-segment-loader.js +149 -0
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js +186 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/media.js +9 -0
- package/dist/default/playback/adapters/hls-audio/media.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js +211 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/host.js +60 -0
- package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/media.js +21 -0
- package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/adapter.js +316 -0
- package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js +51 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js +126 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/media.js +11 -0
- package/dist/default/playback/adapters/hls-video/media.js.map +1 -0
- package/dist/default/playback/adapters/mux-audio/media.js +26 -0
- package/dist/default/playback/adapters/mux-audio/media.js.map +1 -0
- package/dist/default/playback/adapters/mux-video/adapter.js +105 -0
- package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/mux-video/media.js +23 -0
- package/dist/default/playback/adapters/mux-video/media.js.map +1 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/default/playback/behaviors/collect-errors.js +111 -0
- package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/default/playback/behaviors/dom/airplay.js +211 -0
- package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js +102 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/default/playback/behaviors/dom/load-segments.js +181 -0
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
- package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +132 -0
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +205 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +114 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +40 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js +57 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js +29 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +113 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js +197 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-track.js +141 -0
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/default/playback/behaviors/select-tracks.js +216 -0
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/default/playback/behaviors/sync-preload.js +59 -0
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/default/playback/behaviors/track-switching.js +391 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js +98 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/engine-background-video.js +92 -0
- package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +147 -0
- package/dist/default/playback/engines/hls/engine.js.map +1 -0
- package/dist/default/playback/primitives/error-messages.js +51 -0
- package/dist/default/playback/primitives/error-messages.js.map +1 -0
- package/dist/default/playback/primitives/failover-fetch.js +42 -0
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/default/playback/primitives/head-peek.js +44 -0
- package/dist/default/playback/primitives/head-peek.js.map +1 -0
- package/dist/default/playback/primitives/live-window.js +66 -0
- package/dist/default/playback/primitives/live-window.js.map +1 -0
- package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
- package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/default/playback/primitives/report-track-conditions.js +86 -0
- package/dist/default/playback/primitives/report-track-conditions.js.map +1 -0
- package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
- package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/selection-rules.js +86 -0
- package/dist/default/playback/primitives/selection-rules.js.map +1 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/track-types.js +61 -0
- package/dist/default/playback/primitives/track-types.js.map +1 -0
- package/dist/dev/core/actors/actor.d.ts +26 -0
- package/dist/dev/core/actors/actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts +128 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.js +91 -0
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts +41 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.js +46 -0
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
- package/dist/dev/core/composition/create-composition.d.ts +273 -0
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
- package/dist/dev/core/composition/create-composition.js +85 -0
- package/dist/dev/core/composition/create-composition.js.map +1 -0
- package/dist/dev/core/composition/share-signals.d.ts +46 -0
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
- package/dist/dev/core/composition/share-signals.js +39 -0
- package/dist/dev/core/composition/share-signals.js.map +1 -0
- package/dist/dev/core/machine.d.ts +21 -0
- package/dist/dev/core/machine.d.ts.map +1 -0
- package/dist/dev/core/machine.js +26 -0
- package/dist/dev/core/machine.js.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +93 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.js +83 -0
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/dev/core/signals/effect.d.ts +15 -0
- package/dist/dev/core/signals/effect.d.ts.map +1 -0
- package/dist/dev/core/signals/effect.js +41 -0
- package/dist/dev/core/signals/effect.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +46 -0
- package/dist/dev/core/signals/primitives.d.ts.map +1 -0
- package/dist/dev/core/signals/primitives.js +54 -0
- package/dist/dev/core/signals/primitives.js.map +1 -0
- package/dist/dev/core/signals/when.js +38 -0
- package/dist/dev/core/signals/when.js.map +1 -0
- package/dist/dev/core/tasks/delayed-reschedule.js +31 -0
- package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/dev/core/tasks/task.d.ts +166 -0
- package/dist/dev/core/tasks/task.d.ts.map +1 -0
- package/dist/dev/core/tasks/task.js +321 -0
- package/dist/dev/core/tasks/task.js.map +1 -0
- package/dist/dev/dom.d.ts +9 -0
- package/dist/dev/dom.js +9 -0
- package/dist/dev/hls-audio.d.ts +3 -0
- package/dist/dev/hls-audio.js +3 -0
- package/dist/dev/hls-background-video.d.ts +4 -0
- package/dist/dev/hls-background-video.js +3 -0
- package/dist/dev/hls-video.d.ts +5 -0
- package/dist/dev/hls-video.js +4 -0
- package/dist/dev/hls.d.ts +9 -0
- package/dist/dev/hls.js +8 -0
- package/dist/dev/index.d.ts +24 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +23 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/media/abr/quality-selection.d.ts +22 -0
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
- package/dist/dev/media/abr/quality-selection.js +21 -0
- package/dist/dev/media/abr/quality-selection.js.map +1 -0
- package/dist/dev/media/buffer/back-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/back-buffer.js +46 -0
- package/dist/dev/media/buffer/back-buffer.js.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts +14 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.js +119 -0
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
- package/dist/dev/media/dom/capabilities.js +72 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts +18 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.js +60 -0
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts +24 -0
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/buffer-flusher.js +55 -0
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/dev/media/dom/mse/duration.js +95 -0
- package/dist/dev/media/dom/mse/duration.js.map +1 -0
- package/dist/dev/media/dom/mse/end-of-stream.js +20 -0
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js +242 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/dev/media/dom/screen.d.ts +7 -0
- package/dist/dev/media/dom/screen.d.ts.map +1 -0
- package/dist/dev/media/dom/screen.js +112 -0
- package/dist/dev/media/dom/screen.js.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +6 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.js +50 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts +30 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.js +69 -0
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/dev/media/errors.d.ts +82 -0
- package/dist/dev/media/errors.d.ts.map +1 -0
- package/dist/dev/media/errors.js +69 -0
- package/dist/dev/media/errors.js.map +1 -0
- package/dist/dev/media/hls/parse-attributes.js +148 -0
- package/dist/dev/media/hls/parse-attributes.js.map +1 -0
- package/dist/dev/media/hls/parse-media-playlist.js +261 -0
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/dev/media/hls/parse-multivariant.js +234 -0
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
- package/dist/dev/media/hls/reload-policy.js +71 -0
- package/dist/dev/media/hls/reload-policy.js.map +1 -0
- package/dist/dev/media/hls/resolve-url.js +11 -0
- package/dist/dev/media/hls/resolve-url.js.map +1 -0
- package/dist/dev/media/live-window.js +36 -0
- package/dist/dev/media/live-window.js.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.js +101 -0
- package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/dev/media/mp4/box.js +58 -0
- package/dist/dev/media/mp4/box.js.map +1 -0
- package/dist/dev/media/mp4/timestamp-origin.js +107 -0
- package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/dev/media/primitives/resolution.d.ts +24 -0
- package/dist/dev/media/primitives/resolution.d.ts.map +1 -0
- package/dist/dev/media/primitives/resolution.js +29 -0
- package/dist/dev/media/primitives/resolution.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +107 -0
- package/dist/dev/media/primitives/select-tracks.js.map +1 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/dev/media/types/index.d.ts +377 -0
- package/dist/dev/media/types/index.d.ts.map +1 -0
- package/dist/dev/media/types/index.js +50 -0
- package/dist/dev/media/types/index.js.map +1 -0
- package/dist/dev/media/utils/cdn.d.ts +13 -0
- package/dist/dev/media/utils/cdn.d.ts.map +1 -0
- package/dist/dev/media/utils/cdn.js +56 -0
- package/dist/dev/media/utils/cdn.js.map +1 -0
- package/dist/dev/media/utils/preload.js +22 -0
- package/dist/dev/media/utils/preload.js.map +1 -0
- package/dist/dev/media/utils/track-selection.js +47 -0
- package/dist/dev/media/utils/track-selection.js.map +1 -0
- package/dist/dev/media/utils/tracks.js +144 -0
- package/dist/dev/media/utils/tracks.js.map +1 -0
- package/dist/dev/media-tracks.d.ts +3 -0
- package/dist/dev/media-tracks.js +2 -0
- package/dist/dev/mux-audio.d.ts +4 -0
- package/dist/dev/mux-audio.js +3 -0
- package/dist/dev/mux-background-video.d.ts +4 -0
- package/dist/dev/mux-background-video.js +3 -0
- package/dist/dev/mux-video.d.ts +4 -0
- package/dist/dev/mux-video.js +3 -0
- package/dist/dev/network/bandwidth-estimator.d.ts +50 -0
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
- package/dist/dev/network/bandwidth-estimator.js +96 -0
- package/dist/dev/network/bandwidth-estimator.js.map +1 -0
- package/dist/dev/network/chunked-stream-iterable.js +49 -0
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
- package/dist/dev/network/ewma.js +71 -0
- package/dist/dev/network/ewma.js.map +1 -0
- package/dist/dev/network/fetch.js +115 -0
- package/dist/dev/network/fetch.js.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +48 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.js +249 -0
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +47 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.js +139 -0
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -0
- package/dist/dev/playback/actors/dom/text-tracks.js +46 -0
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +54 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js +149 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/text-tracks.d.ts +33 -0
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +54 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js +186 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/media.d.ts +10 -0
- package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/media.js +9 -0
- package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +77 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.js +211 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts +38 -0
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js +60 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/media.d.ts +20 -0
- package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/media.js +21 -0
- package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts +63 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js +316 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +18 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.js +51 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +22 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js +126 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media.d.ts +10 -0
- package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media.js +11 -0
- package/dist/dev/playback/adapters/hls-video/media.js.map +1 -0
- package/dist/dev/playback/adapters/mux-audio/media.d.ts +28 -0
- package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/mux-audio/media.js +26 -0
- package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -0
- package/dist/dev/playback/adapters/mux-video/adapter.d.ts +34 -0
- package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/mux-video/adapter.js +105 -0
- package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/mux-video/media.d.ts +24 -0
- package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -0
- package/dist/dev/playback/adapters/mux-video/media.js +23 -0
- package/dist/dev/playback/adapters/mux-video/media.js.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +26 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +36 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/collect-errors.js +111 -0
- package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
- package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +102 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +72 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js +181 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +132 -0
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +205 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +114 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +28 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +40 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +124 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +33 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js +52 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +47 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +94 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +33 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js +37 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts +8 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js +57 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +29 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +113 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +36 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js +197 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +8 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js +85 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-track.js +141 -0
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts +72 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +216 -0
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +14 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/dev/playback/behaviors/sync-preload.js +59 -0
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.js +391 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +173 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js +98 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts +158 -0
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js +92 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +329 -0
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine.js +147 -0
- package/dist/dev/playback/engines/hls/engine.js.map +1 -0
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
- package/dist/dev/playback/primitives/error-messages.js +51 -0
- package/dist/dev/playback/primitives/error-messages.js.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js +42 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/dev/playback/primitives/head-peek.js +44 -0
- package/dist/dev/playback/primitives/head-peek.js.map +1 -0
- package/dist/dev/playback/primitives/live-window.js +66 -0
- package/dist/dev/playback/primitives/live-window.js.map +1 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts +11 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.js +86 -0
- package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
- package/dist/dev/playback/primitives/selection-rules.d.ts +38 -0
- package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -0
- package/dist/dev/playback/primitives/selection-rules.js +86 -0
- package/dist/dev/playback/primitives/selection-rules.js.map +1 -0
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
- package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
- package/dist/dev/playback/primitives/track-types.js +61 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +63 -26
- package/dist/adapter-CMw-rvbk.js +0 -3512
- package/dist/adapter-CMw-rvbk.js.map +0 -1
- package/dist/adapter-l8iJN_GI.d.ts +0 -689
- package/dist/adapter-l8iJN_GI.d.ts.map +0 -1
- package/dist/dom/index.d.ts +0 -243
- package/dist/dom/index.d.ts.map +0 -1
- package/dist/dom/index.js +0 -38
- package/dist/dom/index.js.map +0 -1
- package/dist/dom/playback-engine/index.d.ts +0 -2
- package/dist/dom/playback-engine/index.js +0 -4
- package/dist/index.d.ts +0 -13
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/src-DRsS_H80.js +0 -14
- package/dist/src-DRsS_H80.js.map +0 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { computed, peek } from "../../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../../core/composition/create-composition.js";
|
|
3
|
+
import { effect } from "../../../core/signals/effect.js";
|
|
4
|
+
import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
|
|
5
|
+
import { isWebKitAirPlayCapable, listen } from "@videojs/utils/dom";
|
|
6
|
+
//#region src/playback/behaviors/dom/airplay.ts
|
|
7
|
+
/**
|
|
8
|
+
* **Bridge MSE playback to AirPlay on WebKit.**
|
|
9
|
+
* MSE streams can't be handed to an AirPlay receiver directly.
|
|
10
|
+
* The WebKit-recommended workaround is to append a fallback
|
|
11
|
+
* `<source type="application/x-mpegURL">` carrying the original manifest URL:
|
|
12
|
+
* Safari exposes the AirPlay picker and, when a wireless target is selected,
|
|
13
|
+
* plays that native-HLS source on the receiver. The session state (WebKit's
|
|
14
|
+
* wireless flag, falling edge debounced — see `REMOTE_INACTIVE_SETTLE_MS`) is
|
|
15
|
+
* written straight to its policy consequences, declared here so the
|
|
16
|
+
* cause→policy mapping stays with the feature:
|
|
17
|
+
*
|
|
18
|
+
* - `state.loadingSuspended` — held while the session is live. Observed by
|
|
19
|
+
* the `loadXSegments` dispatchers (no fetching alongside the receiver) and
|
|
20
|
+
* by `setupMediaSource` (its post-close rebuild waits — attaching runs
|
|
21
|
+
* `element.load()` under the live receiver, which destroys a session still
|
|
22
|
+
* being established). The suspension this behavior holds doubles as its own
|
|
23
|
+
* session fact — same writer, same edges.
|
|
24
|
+
* - `state.startPosition` — one-shot command: the position is captured from
|
|
25
|
+
* the element at the session's settled end (still receiver-mirrored) and
|
|
26
|
+
* written once the rebuild's `load()` resets the element (its `'emptied'`),
|
|
27
|
+
* so `applyStartPosition` applies it to the rebuilt source — never to the
|
|
28
|
+
* pre-rebuild element — and starts it where the receiver left off. The
|
|
29
|
+
* playing state rides the same snapshot but stays behavior-local: this
|
|
30
|
+
* behavior itself calls `play()` once the command has been *consumed* — i.e.
|
|
31
|
+
* after the seek — when the receiver was playing at session end. The whole
|
|
32
|
+
* restore is bound to the presentation the session owned and retracted if
|
|
33
|
+
* that changes.
|
|
34
|
+
*
|
|
35
|
+
* A source change during a live session releases the hold rather than deferring
|
|
36
|
+
* until the session ends, so the rebuild runs and WebKit switches the receiver
|
|
37
|
+
* to the newly-built AirPlay alternate. Measured, not contracted — see the
|
|
38
|
+
* effect below.
|
|
39
|
+
* https://webkit.org/blog/15036/how-to-use-media-source-extensions-with-airplay/
|
|
40
|
+
*
|
|
41
|
+
* Single-positive-state reactor (`'preconditions-unmet'` ↔ `'airplay-capable'`):
|
|
42
|
+
* gated on a WebKit-AirPlay-capable media element being in scope. The entry —
|
|
43
|
+
* gated on `context.mediaSource` — appends the fallback `<source>` (kept
|
|
44
|
+
* current from `state.presentation`) and enables the AirPlay picker once the
|
|
45
|
+
* MediaSource is open, removing the source the moment the MediaSource detaches
|
|
46
|
+
* so it never survives an MSE teardown. State-exit cleanup (author opt-out,
|
|
47
|
+
* detach, source reset, behavior destroy) removes the source and restores the
|
|
48
|
+
* element's `disableRemotePlayback` default. No-op on non-WebKit platforms
|
|
49
|
+
* (Chromium, Firefox) — `deriveState` never leaves `'preconditions-unmet'`.
|
|
50
|
+
*
|
|
51
|
+
* MMS and AirPlay want *opposite* values of `disableRemotePlayback` on the same
|
|
52
|
+
* element, so it is **sequenced**:
|
|
53
|
+
*
|
|
54
|
+
* - **MMS needs `true` to open.** `setupMediaSource` sets
|
|
55
|
+
* `disableRemotePlayback = true` when it attaches a ManagedMediaSource —
|
|
56
|
+
* Safari won't fire `sourceopen` (and MSE playback never starts) otherwise.
|
|
57
|
+
* - **AirPlay needs `false` to offer the picker.** Flipping to `false` *before*
|
|
58
|
+
* the source opens would prevent `sourceopen`, so the flip is gated on
|
|
59
|
+
* `context.mediaSource` — which `setupMediaSource` publishes exactly once the
|
|
60
|
+
* MS is open. Re-fires per source (the slot clears + republishes on reset).
|
|
61
|
+
* - **Author opt-out wins.** `state.disableRemotePlayback` is the author's
|
|
62
|
+
* intent, written only by the media adapter's IDL property; MMS/programmatic
|
|
63
|
+
* code touch the element's own `disableRemotePlayback` instead. A `true`
|
|
64
|
+
* there is unambiguously the author's choice to disable remote playback, so
|
|
65
|
+
* it holds the machine in `'preconditions-unmet'` and nothing is set up.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* How long WebKit's wireless flag must read *inactive* before the
|
|
69
|
+
* session-driven `loadingSuspended` clears.
|
|
70
|
+
*
|
|
71
|
+
* Measured on Safari 26.4 (macOS): when an AirPlay session engages, Safari
|
|
72
|
+
* closes the ManagedMediaSource and — while its pipeline switches to the
|
|
73
|
+
* native-HLS fallback source — transiently reports
|
|
74
|
+
* `webkitCurrentPlaybackTargetIsWireless === false`, firing the changed
|
|
75
|
+
* event. Trusting an instantaneous inactive reading would release
|
|
76
|
+
* `setupMediaSource`'s rebuild hold mid-handoff; its recovery `load()` then
|
|
77
|
+
* destroys the very session being established. Rising edges apply
|
|
78
|
+
* immediately; only the falling edge waits out this settle window.
|
|
79
|
+
*/
|
|
80
|
+
const REMOTE_INACTIVE_SETTLE_MS = 1e3;
|
|
81
|
+
function deriveState(mediaElement, authorDisabledRemotePlayback) {
|
|
82
|
+
if (!mediaElement || !isWebKitAirPlayCapable(mediaElement)) return "preconditions-unmet";
|
|
83
|
+
if (authorDisabledRemotePlayback) return "preconditions-unmet";
|
|
84
|
+
return "airplay-capable";
|
|
85
|
+
}
|
|
86
|
+
function setupAirPlaySetup({ state, context }) {
|
|
87
|
+
const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), state.disableRemotePlayback.get()));
|
|
88
|
+
return createMachineReactor({
|
|
89
|
+
initial: "preconditions-unmet",
|
|
90
|
+
monitor: () => derivedStateSignal.get(),
|
|
91
|
+
states: {
|
|
92
|
+
"preconditions-unmet": {},
|
|
93
|
+
"airplay-capable": { entry: () => {
|
|
94
|
+
const mediaElement = context.mediaElement.get();
|
|
95
|
+
const isSessionActive = () => !!mediaElement.webkitCurrentPlaybackTargetIsWireless;
|
|
96
|
+
let settleTimer;
|
|
97
|
+
let sessionPresentationUrl;
|
|
98
|
+
let pendingRestore;
|
|
99
|
+
/** Presentation an already-written `state.startPosition` belongs to. */
|
|
100
|
+
let restoreOwnerUrl;
|
|
101
|
+
let resumeWhenRestored = false;
|
|
102
|
+
const sync = () => {
|
|
103
|
+
if (isSessionActive()) {
|
|
104
|
+
clearTimeout(settleTimer);
|
|
105
|
+
settleTimer = void 0;
|
|
106
|
+
if (!peek(state.loadingSuspended)) sessionPresentationUrl = peek(state.presentation)?.url;
|
|
107
|
+
state.loadingSuspended.set(true);
|
|
108
|
+
} else if (peek(state.loadingSuspended)) settleTimer ??= setTimeout(() => {
|
|
109
|
+
settleTimer = void 0;
|
|
110
|
+
const stillActive = isSessionActive();
|
|
111
|
+
if (!stillActive) {
|
|
112
|
+
const ownerUrl = sessionPresentationUrl;
|
|
113
|
+
sessionPresentationUrl = void 0;
|
|
114
|
+
if (peek(state.presentation)?.url === ownerUrl) pendingRestore = {
|
|
115
|
+
position: mediaElement.currentTime,
|
|
116
|
+
wasPlaying: !mediaElement.paused,
|
|
117
|
+
presentationUrl: ownerUrl
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
state.loadingSuspended.set(stillActive);
|
|
121
|
+
}, REMOTE_INACTIVE_SETTLE_MS);
|
|
122
|
+
else state.loadingSuspended.set(false);
|
|
123
|
+
};
|
|
124
|
+
const listenerCleanup = new AbortController();
|
|
125
|
+
listen(mediaElement, "webkitcurrentplaybacktargetiswirelesschanged", sync, { signal: listenerCleanup.signal });
|
|
126
|
+
listen(mediaElement, "emptied", () => {
|
|
127
|
+
if (!pendingRestore) return;
|
|
128
|
+
const { position, wasPlaying, presentationUrl } = pendingRestore;
|
|
129
|
+
pendingRestore = void 0;
|
|
130
|
+
if (peek(state.presentation)?.url !== presentationUrl) return;
|
|
131
|
+
state.startPosition.set(position);
|
|
132
|
+
restoreOwnerUrl = presentationUrl;
|
|
133
|
+
resumeWhenRestored = wasPlaying;
|
|
134
|
+
}, { signal: listenerCleanup.signal });
|
|
135
|
+
const disposeSourceChangeEnd = effect(() => {
|
|
136
|
+
const url = state.presentation.get()?.url;
|
|
137
|
+
if (!peek(state.loadingSuspended) || url === sessionPresentationUrl) return;
|
|
138
|
+
sessionPresentationUrl = void 0;
|
|
139
|
+
clearTimeout(settleTimer);
|
|
140
|
+
settleTimer = void 0;
|
|
141
|
+
state.loadingSuspended.set(false);
|
|
142
|
+
});
|
|
143
|
+
const disposeRestoreWatch = effect(() => {
|
|
144
|
+
const url = state.presentation.get()?.url;
|
|
145
|
+
const position = state.startPosition.get();
|
|
146
|
+
if (!restoreOwnerUrl) return;
|
|
147
|
+
if (url !== restoreOwnerUrl) {
|
|
148
|
+
restoreOwnerUrl = void 0;
|
|
149
|
+
resumeWhenRestored = false;
|
|
150
|
+
if (position !== void 0) state.startPosition.set(void 0);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (position !== void 0) return;
|
|
154
|
+
restoreOwnerUrl = void 0;
|
|
155
|
+
if (!resumeWhenRestored) return;
|
|
156
|
+
resumeWhenRestored = false;
|
|
157
|
+
mediaElement.play().catch((err) => {
|
|
158
|
+
console.warn("[setupAirPlay] session-end resume play() rejected — staying paused:", err);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
let sourceEl = null;
|
|
162
|
+
const disposeSource = effect(() => {
|
|
163
|
+
const hasMediaSource = !!context.mediaSource.get();
|
|
164
|
+
const sessionActive = !!state.loadingSuspended.get();
|
|
165
|
+
const url = state.presentation.get()?.url ?? "";
|
|
166
|
+
if (!hasMediaSource && !sessionActive) {
|
|
167
|
+
sourceEl?.remove();
|
|
168
|
+
sourceEl = null;
|
|
169
|
+
} else if (hasMediaSource && (!sourceEl || sourceEl.parentNode !== mediaElement)) {
|
|
170
|
+
sourceEl = document.createElement("source");
|
|
171
|
+
sourceEl.type = "application/x-mpegURL";
|
|
172
|
+
mediaElement.append(sourceEl);
|
|
173
|
+
mediaElement.disableRemotePlayback = false;
|
|
174
|
+
}
|
|
175
|
+
if (sourceEl) sourceEl.src = url;
|
|
176
|
+
});
|
|
177
|
+
sync();
|
|
178
|
+
return () => {
|
|
179
|
+
disposeSource();
|
|
180
|
+
disposeSourceChangeEnd();
|
|
181
|
+
disposeRestoreWatch();
|
|
182
|
+
listenerCleanup.abort();
|
|
183
|
+
clearTimeout(settleTimer);
|
|
184
|
+
sourceEl?.remove();
|
|
185
|
+
sourceEl = null;
|
|
186
|
+
mediaElement.disableRemotePlayback = true;
|
|
187
|
+
state.loadingSuspended.set(false);
|
|
188
|
+
if (restoreOwnerUrl) {
|
|
189
|
+
restoreOwnerUrl = void 0;
|
|
190
|
+
resumeWhenRestored = false;
|
|
191
|
+
if (peek(state.startPosition) !== void 0) state.startPosition.set(void 0);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
} }
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const setupAirPlay = defineBehavior({
|
|
199
|
+
stateKeys: [
|
|
200
|
+
"presentation",
|
|
201
|
+
"disableRemotePlayback",
|
|
202
|
+
"loadingSuspended",
|
|
203
|
+
"startPosition"
|
|
204
|
+
],
|
|
205
|
+
contextKeys: ["mediaElement", "mediaSource"],
|
|
206
|
+
setup: setupAirPlaySetup
|
|
207
|
+
});
|
|
208
|
+
//#endregion
|
|
209
|
+
export { setupAirPlay };
|
|
210
|
+
|
|
211
|
+
//# sourceMappingURL=airplay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"airplay.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/airplay.ts"],"sourcesContent":["/**\n * **Bridge MSE playback to AirPlay on WebKit.**\n * MSE streams can't be handed to an AirPlay receiver directly.\n * The WebKit-recommended workaround is to append a fallback\n * `<source type=\"application/x-mpegURL\">` carrying the original manifest URL:\n * Safari exposes the AirPlay picker and, when a wireless target is selected,\n * plays that native-HLS source on the receiver. The session state (WebKit's\n * wireless flag, falling edge debounced — see `REMOTE_INACTIVE_SETTLE_MS`) is\n * written straight to its policy consequences, declared here so the\n * cause→policy mapping stays with the feature:\n *\n * - `state.loadingSuspended` — held while the session is live. Observed by\n * the `loadXSegments` dispatchers (no fetching alongside the receiver) and\n * by `setupMediaSource` (its post-close rebuild waits — attaching runs\n * `element.load()` under the live receiver, which destroys a session still\n * being established). The suspension this behavior holds doubles as its own\n * session fact — same writer, same edges.\n * - `state.startPosition` — one-shot command: the position is captured from\n * the element at the session's settled end (still receiver-mirrored) and\n * written once the rebuild's `load()` resets the element (its `'emptied'`),\n * so `applyStartPosition` applies it to the rebuilt source — never to the\n * pre-rebuild element — and starts it where the receiver left off. The\n * playing state rides the same snapshot but stays behavior-local: this\n * behavior itself calls `play()` once the command has been *consumed* — i.e.\n * after the seek — when the receiver was playing at session end. The whole\n * restore is bound to the presentation the session owned and retracted if\n * that changes.\n *\n * A source change during a live session releases the hold rather than deferring\n * until the session ends, so the rebuild runs and WebKit switches the receiver\n * to the newly-built AirPlay alternate. Measured, not contracted — see the\n * effect below.\n * https://webkit.org/blog/15036/how-to-use-media-source-extensions-with-airplay/\n *\n * Single-positive-state reactor (`'preconditions-unmet'` ↔ `'airplay-capable'`):\n * gated on a WebKit-AirPlay-capable media element being in scope. The entry —\n * gated on `context.mediaSource` — appends the fallback `<source>` (kept\n * current from `state.presentation`) and enables the AirPlay picker once the\n * MediaSource is open, removing the source the moment the MediaSource detaches\n * so it never survives an MSE teardown. State-exit cleanup (author opt-out,\n * detach, source reset, behavior destroy) removes the source and restores the\n * element's `disableRemotePlayback` default. No-op on non-WebKit platforms\n * (Chromium, Firefox) — `deriveState` never leaves `'preconditions-unmet'`.\n *\n * MMS and AirPlay want *opposite* values of `disableRemotePlayback` on the same\n * element, so it is **sequenced**:\n *\n * - **MMS needs `true` to open.** `setupMediaSource` sets\n * `disableRemotePlayback = true` when it attaches a ManagedMediaSource —\n * Safari won't fire `sourceopen` (and MSE playback never starts) otherwise.\n * - **AirPlay needs `false` to offer the picker.** Flipping to `false` *before*\n * the source opens would prevent `sourceopen`, so the flip is gated on\n * `context.mediaSource` — which `setupMediaSource` publishes exactly once the\n * MS is open. Re-fires per source (the slot clears + republishes on reset).\n * - **Author opt-out wins.** `state.disableRemotePlayback` is the author's\n * intent, written only by the media adapter's IDL property; MMS/programmatic\n * code touch the element's own `disableRemotePlayback` instead. A `true`\n * there is unambiguously the author's choice to disable remote playback, so\n * it holds the machine in `'preconditions-unmet'` and nothing is set up.\n */\n\nimport { isWebKitAirPlayCapable, listen, type WebKitVideoElement } 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 { effect } from '../../../core/signals/effect';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport type { StartPositionState } from './apply-start-position';\nimport type { SegmentLoadingState } from './load-segments';\n\n/**\n * How long WebKit's wireless flag must read *inactive* before the\n * session-driven `loadingSuspended` clears.\n *\n * Measured on Safari 26.4 (macOS): when an AirPlay session engages, Safari\n * closes the ManagedMediaSource and — while its pipeline switches to the\n * native-HLS fallback source — transiently reports\n * `webkitCurrentPlaybackTargetIsWireless === false`, firing the changed\n * event. Trusting an instantaneous inactive reading would release\n * `setupMediaSource`'s rebuild hold mid-handoff; its recovery `load()` then\n * destroys the very session being established. Rising edges apply\n * immediately; only the falling edge waits out this settle window.\n */\nconst REMOTE_INACTIVE_SETTLE_MS = 1000;\n\ntype AirPlayFsmState = 'preconditions-unmet' | 'airplay-capable';\n\nfunction deriveState(\n mediaElement: HTMLMediaElement | undefined,\n authorDisabledRemotePlayback: boolean | undefined\n): AirPlayFsmState {\n if (!mediaElement || !isWebKitAirPlayCapable(mediaElement)) return 'preconditions-unmet';\n if (authorDisabledRemotePlayback) return 'preconditions-unmet';\n return 'airplay-capable';\n}\n\nfunction setupAirPlaySetup({\n state,\n context,\n}: {\n state: {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n disableRemotePlayback: ReadonlySignal<boolean | undefined>;\n loadingSuspended: Signal<SegmentLoadingState['loadingSuspended']>;\n startPosition: Signal<StartPositionState['startPosition']>;\n };\n context: {\n mediaElement: ReadonlySignal<HTMLMediaElement | undefined>;\n mediaSource: ReadonlySignal<MediaSource | undefined>;\n };\n}): Reactor<AirPlayFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), state.disableRemotePlayback.get()));\n\n return createMachineReactor<AirPlayFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'airplay-capable': {\n entry: () => {\n const mediaElement = context.mediaElement.get() as WebKitVideoElement;\n\n // WebKit's wireless flag is the only session signal. The standard\n // Remote Playback API's `remote.state` is deliberately not consulted:\n // it doesn't track AirPlay sessions reliably on WebKit (same reason\n // `remotePlaybackFeature` in @videojs/core skips it), and a stale\n // `'connected'` would pin `loadingSuspended` on — stranding the\n // rebuild in a way the settle window below can't recover from.\n const isSessionActive = () => !!mediaElement.webkitCurrentPlaybackTargetIsWireless;\n\n let settleTimer: ReturnType<typeof setTimeout> | undefined;\n // Session-end restore. The position/playing snapshot is taken at the\n // settled falling edge (the element still mirrors the receiver) but\n // acted on only at the element's next `'emptied'` — once the\n // rebuild's `load()` has reset the element. Writing it earlier would\n // hand `applyStartPosition` a command against the pre-rebuild element\n // (readyState still >= HAVE_METADATA), which it would apply and\n // consume before the rebuilt source exists.\n //\n // The restore is bound to the presentation the session owned, and\n // that identity is re-checked at *every* async hop — settle timer,\n // `'emptied'`, and the outstanding-command effect below. Checking at\n // only one hop leaves the others open: a source change inside the\n // settle window, or between `'emptied'` and the element becoming\n // seekable, would otherwise strand the old session's position (and an\n // unrequested `play()`) on the new source.\n let sessionPresentationUrl: string | undefined;\n let pendingRestore:\n | { position: number; wasPlaying: boolean; presentationUrl: string | undefined }\n | undefined;\n /** Presentation an already-written `state.startPosition` belongs to. */\n let restoreOwnerUrl: string | undefined;\n let resumeWhenRestored = false;\n\n const sync = () => {\n if (isSessionActive()) {\n clearTimeout(settleTimer);\n settleTimer = undefined;\n // Rising edge — the presentation the receiver is taking over.\n if (!peek(state.loadingSuspended)) sessionPresentationUrl = peek(state.presentation)?.url;\n state.loadingSuspended.set(true);\n } else if (peek(state.loadingSuspended)) {\n // Falling edge: don't trust an instantaneous inactive reading —\n // re-check once the flag has settled.\n settleTimer ??= setTimeout(() => {\n settleTimer = undefined;\n const stillActive = isSessionActive();\n if (!stillActive) {\n const ownerUrl = sessionPresentationUrl;\n sessionPresentationUrl = undefined;\n // Snapshot only while the session's own presentation is still\n // current. `currentTime` here belongs to that presentation —\n // during a session `setupMediaSource` has already torn down,\n // so a source change inside this window resets neither the\n // element nor its position, and pairing that position with the\n // incoming URL would smuggle it onto the new source.\n if (peek(state.presentation)?.url === ownerUrl) {\n pendingRestore = {\n position: mediaElement.currentTime,\n wasPlaying: !mediaElement.paused,\n presentationUrl: ownerUrl,\n };\n }\n }\n state.loadingSuspended.set(stillActive);\n }, REMOTE_INACTIVE_SETTLE_MS);\n } else {\n state.loadingSuspended.set(false);\n }\n };\n const listenerCleanup = new AbortController();\n listen(mediaElement, 'webkitcurrentplaybacktargetiswirelesschanged', sync, {\n signal: listenerCleanup.signal,\n });\n listen(\n mediaElement,\n 'emptied',\n () => {\n if (!pendingRestore) return;\n const { position, wasPlaying, presentationUrl } = pendingRestore;\n pendingRestore = undefined;\n if (peek(state.presentation)?.url !== presentationUrl) return;\n state.startPosition.set(position);\n restoreOwnerUrl = presentationUrl;\n resumeWhenRestored = wasPlaying;\n },\n { signal: listenerCleanup.signal }\n );\n\n // A source change during a live session releases the hold, letting the\n // rebuild run instead of waiting for the session to end.\n //\n // Doing nothing is not a neutral choice — it silently defers the\n // change. `<source>` children are only consulted while resource\n // selection runs, so the effect below rewriting the fallback's `src`\n // does nothing to what the receiver is already playing, and the\n // rebuild is held by `loadingSuspended`, so the receiver stays on the\n // outgoing stream until the user disengages.\n //\n // Releasing the hold lets the rebuild proceed, and the receiver ends\n // up on the new stream — measured on Safari 26.4. Note where the\n // handover actually happens, because it is not the rebuild's `load()`:\n // clearing the hold also makes the source effect below see an inactive\n // session with no `context.mediaSource`, so it drops the fallback, and\n // the rebuild's resource selection runs with the MSE source alone. The\n // switch comes afterwards, when that effect re-creates the fallback\n // against the new presentation once the MediaSource opens and WebKit\n // moves the live session onto the newly-appeared alternate.\n //\n // That leaves a window where an active session has no compatible\n // alternate on the element, and it survives it. Holding the fallback\n // across the rebuild instead would close the window and make the\n // handover selection-driven, but it swaps a measured path for an\n // argued one — don't without a device pass.\n //\n // Measured, not contracted: this is under-specified territory, so\n // nothing here depends on the handover succeeding. If a UA drops the\n // session instead, the falling edge runs its ordinary course; the\n // restore is suppressed either way because the snapshot is bound to\n // the presentation the session owned.\n //\n // Note this is *not* an attempt to end the session. Setting\n // `disableRemotePlayback = true` was tried: the Remote Playback API\n // requires it to disconnect (spec §5.3.2) but WebKit does not honor\n // that for AirPlay — the session survived, measured on Safari 26.4.\n // Deliberately not written here, so that behavior can't silently flip\n // to \"receiver drops\" if WebKit ever implements the disconnect.\n const disposeSourceChangeEnd = effect(() => {\n const url = state.presentation.get()?.url;\n // `loadingSuspended` is this behavior's own session fact; peeked so\n // clearing it below doesn't re-trigger.\n if (!peek(state.loadingSuspended) || url === sessionPresentationUrl) return;\n sessionPresentationUrl = undefined;\n clearTimeout(settleTimer);\n settleTimer = undefined;\n state.loadingSuspended.set(false);\n });\n\n // Watches an outstanding restore command to its end. Two outcomes:\n //\n // - **Consumed** (`startPosition` back to `undefined`): the element is\n // at the restored position, so resuming is safe. Keying the resume\n // off consumption rather than off `'loadedmetadata'` orders it after\n // `applyStartPosition`'s seek — arming a listener for the same event\n // would put `play()` ahead of the seek, since this behavior registers\n // synchronously while `applyStartPosition` only reaches its state a\n // microtask later. The rebuild's load algorithm forced `paused =\n // true`; a rejection here (autoplay policy) degrades to\n // paused-at-position. Requires a `startPosition` consumer in the\n // composition (`applyStartPosition`) — without one the position is\n // never restored either, so resuming would be wrong anyway.\n // - **Superseded** (presentation changed): retract the command and\n // disarm, so neither the seek nor the resume reaches the new source.\n const disposeRestoreWatch = effect(() => {\n const url = state.presentation.get()?.url;\n const position = state.startPosition.get();\n if (!restoreOwnerUrl) return;\n\n if (url !== restoreOwnerUrl) {\n restoreOwnerUrl = undefined;\n resumeWhenRestored = false;\n if (position !== undefined) state.startPosition.set(undefined);\n return;\n }\n\n if (position !== undefined) return;\n restoreOwnerUrl = undefined;\n if (!resumeWhenRestored) return;\n resumeWhenRestored = false;\n mediaElement.play().catch((err) => {\n console.warn('[setupAirPlay] session-end resume play() rejected — staying paused:', err);\n });\n });\n\n // This effect combines:\n // - adding a native HLS fallback source when the mediaSource is\n // attached / removing it when it's detached — UNLESS a session is\n // live: during an AirPlay session the engine detaches its dead\n // MediaSource (see setupMediaSource's sourceclose recovery) while\n // the receiver is playing exactly this fallback source, so it\n // must survive until the session's falling edge (the rebuild's\n // republish then adopts it again).\n // - keeping this sourceEl's src in sync with the current presentation.\n // The created source is also cleaned on state exit.\n // The dependence on context.mediaSource means the source is appended\n // only after the MMS has been attached and opened (therefore we can\n // flip disableRemotePlayback). Being in this state also implies\n // that the author has not disabled remote playback.\n let sourceEl: HTMLSourceElement | null = null;\n const disposeSource = effect(() => {\n const hasMediaSource = !!context.mediaSource.get();\n const sessionActive = !!state.loadingSuspended.get();\n const url = state.presentation.get()?.url ?? '';\n\n if (!hasMediaSource && !sessionActive) {\n sourceEl?.remove();\n sourceEl = null;\n } else if (hasMediaSource && (!sourceEl || sourceEl.parentNode !== mediaElement)) {\n sourceEl = document.createElement('source');\n sourceEl.type = 'application/x-mpegURL';\n mediaElement.append(sourceEl);\n mediaElement.disableRemotePlayback = false;\n }\n if (sourceEl) sourceEl.src = url;\n });\n\n // AirPlay may already be active at (re)attach.\n sync();\n\n return () => {\n disposeSource();\n disposeSourceChangeEnd();\n disposeRestoreWatch();\n listenerCleanup.abort();\n clearTimeout(settleTimer);\n sourceEl?.remove();\n sourceEl = null;\n // Undo the picker enable: hand the element back to its MMS-default\n // `disableRemotePlayback = true`.\n mediaElement.disableRemotePlayback = true;\n // Don't strand the engine held/suspended if we tear down\n // mid-session (author opt-out, detach, destroy).\n state.loadingSuspended.set(false);\n // Nor leave a restore command behind for the next source to apply.\n if (restoreOwnerUrl) {\n restoreOwnerUrl = undefined;\n resumeWhenRestored = false;\n if (peek(state.startPosition) !== undefined) state.startPosition.set(undefined);\n }\n };\n },\n },\n },\n });\n}\n\nexport const setupAirPlay = defineBehavior({\n stateKeys: ['presentation', 'disableRemotePlayback', 'loadingSuspended', 'startPosition'],\n contextKeys: ['mediaElement', 'mediaSource'],\n setup: setupAirPlaySetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,MAAM,4BAA4B;AAIlC,SAAS,YACP,cACA,8BACiB;CACjB,IAAI,CAAC,gBAAgB,CAAC,uBAAuB,YAAY,GAAG,OAAO;CACnE,IAAI,8BAA8B,OAAO;CACzC,OAAO;AACT;AAEA,SAAS,kBAAkB,EACzB,OACA,WAYwD;CACxD,MAAM,qBAAqB,eAAe,YAAY,QAAQ,aAAa,IAAI,GAAG,MAAM,sBAAsB,IAAI,CAAC,CAAC;CAEpH,OAAO,qBAAsC;EAC3C,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GAExB,mBAAmB,EACjB,aAAa;IACX,MAAM,eAAe,QAAQ,aAAa,IAAI;IAQ9C,MAAM,wBAAwB,CAAC,CAAC,aAAa;IAE7C,IAAI;IAgBJ,IAAI;IACJ,IAAI;;IAIJ,IAAI;IACJ,IAAI,qBAAqB;IAEzB,MAAM,aAAa;KACjB,IAAI,gBAAgB,GAAG;MACrB,aAAa,WAAW;MACxB,cAAc,KAAA;MAEd,IAAI,CAAC,KAAK,MAAM,gBAAgB,GAAG,yBAAyB,KAAK,MAAM,YAAY,CAAC,EAAE;MACtF,MAAM,iBAAiB,IAAI,IAAI;KACjC,OAAO,IAAI,KAAK,MAAM,gBAAgB,GAGpC,gBAAgB,iBAAiB;MAC/B,cAAc,KAAA;MACd,MAAM,cAAc,gBAAgB;MACpC,IAAI,CAAC,aAAa;OAChB,MAAM,WAAW;OACjB,yBAAyB,KAAA;OAOzB,IAAI,KAAK,MAAM,YAAY,CAAC,EAAE,QAAQ,UACpC,iBAAiB;QACf,UAAU,aAAa;QACvB,YAAY,CAAC,aAAa;QAC1B,iBAAiB;OACnB;MAEJ;MACA,MAAM,iBAAiB,IAAI,WAAW;KACxC,GAAG,yBAAyB;UAE5B,MAAM,iBAAiB,IAAI,KAAK;IAEpC;IACA,MAAM,kBAAkB,IAAI,gBAAgB;IAC5C,OAAO,cAAc,gDAAgD,MAAM,EACzE,QAAQ,gBAAgB,OAC1B,CAAC;IACD,OACE,cACA,iBACM;KACJ,IAAI,CAAC,gBAAgB;KACrB,MAAM,EAAE,UAAU,YAAY,oBAAoB;KAClD,iBAAiB,KAAA;KACjB,IAAI,KAAK,MAAM,YAAY,CAAC,EAAE,QAAQ,iBAAiB;KACvD,MAAM,cAAc,IAAI,QAAQ;KAChC,kBAAkB;KAClB,qBAAqB;IACvB,GACA,EAAE,QAAQ,gBAAgB,OAAO,CACnC;IAwCA,MAAM,yBAAyB,aAAa;KAC1C,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,EAAE;KAGtC,IAAI,CAAC,KAAK,MAAM,gBAAgB,KAAK,QAAQ,wBAAwB;KACrE,yBAAyB,KAAA;KACzB,aAAa,WAAW;KACxB,cAAc,KAAA;KACd,MAAM,iBAAiB,IAAI,KAAK;IAClC,CAAC;IAiBD,MAAM,sBAAsB,aAAa;KACvC,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,EAAE;KACtC,MAAM,WAAW,MAAM,cAAc,IAAI;KACzC,IAAI,CAAC,iBAAiB;KAEtB,IAAI,QAAQ,iBAAiB;MAC3B,kBAAkB,KAAA;MAClB,qBAAqB;MACrB,IAAI,aAAa,KAAA,GAAW,MAAM,cAAc,IAAI,KAAA,CAAS;MAC7D;KACF;KAEA,IAAI,aAAa,KAAA,GAAW;KAC5B,kBAAkB,KAAA;KAClB,IAAI,CAAC,oBAAoB;KACzB,qBAAqB;KACrB,aAAa,KAAK,CAAC,CAAC,OAAO,QAAQ;MACjC,QAAQ,KAAK,uEAAuE,GAAG;KACzF,CAAC;IACH,CAAC;IAgBD,IAAI,WAAqC;IACzC,MAAM,gBAAgB,aAAa;KACjC,MAAM,iBAAiB,CAAC,CAAC,QAAQ,YAAY,IAAI;KACjD,MAAM,gBAAgB,CAAC,CAAC,MAAM,iBAAiB,IAAI;KACnD,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,EAAE,OAAO;KAE7C,IAAI,CAAC,kBAAkB,CAAC,eAAe;MACrC,UAAU,OAAO;MACjB,WAAW;KACb,OAAO,IAAI,mBAAmB,CAAC,YAAY,SAAS,eAAe,eAAe;MAChF,WAAW,SAAS,cAAc,QAAQ;MAC1C,SAAS,OAAO;MAChB,aAAa,OAAO,QAAQ;MAC5B,aAAa,wBAAwB;KACvC;KACA,IAAI,UAAU,SAAS,MAAM;IAC/B,CAAC;IAGD,KAAK;IAEL,aAAa;KACX,cAAc;KACd,uBAAuB;KACvB,oBAAoB;KACpB,gBAAgB,MAAM;KACtB,aAAa,WAAW;KACxB,UAAU,OAAO;KACjB,WAAW;KAGX,aAAa,wBAAwB;KAGrC,MAAM,iBAAiB,IAAI,KAAK;KAEhC,IAAI,iBAAiB;MACnB,kBAAkB,KAAA;MAClB,qBAAqB;MACrB,IAAI,KAAK,MAAM,aAAa,MAAM,KAAA,GAAW,MAAM,cAAc,IAAI,KAAA,CAAS;KAChF;IACF;GACF,EACF;EACF;CACF,CAAC;AACH;AAEA,MAAa,eAAe,eAAe;CACzC,WAAW;EAAC;EAAgB;EAAyB;EAAoB;CAAe;CACxF,aAAa,CAAC,gBAAgB,aAAa;CAC3C,OAAO;AACT,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { computed } from "../../../core/signals/primitives.js";
|
|
2
|
+
import { defineBehavior } from "../../../core/composition/create-composition.js";
|
|
3
|
+
import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedPresentation } from "../../../media/types/index.js";
|
|
5
|
+
import { listen } from "@videojs/utils/dom";
|
|
6
|
+
import { isUndefined } from "@videojs/utils/predicate";
|
|
7
|
+
//#region src/playback/behaviors/dom/apply-start-position.ts
|
|
8
|
+
/**
|
|
9
|
+
* **Start playback of a source at a requested position.** `state.startPosition`
|
|
10
|
+
* is a one-shot command — "when the current source can seek, start there" —
|
|
11
|
+
* the SPF analogue of hls.js's `startPosition` (and the primitive `EXT-X-START`,
|
|
12
|
+
* resume-where-you-left-off, and MediaSource-recovery restore build on).
|
|
13
|
+
* Consumers (adapters, `setupAirPlay`'s session-end snapshot) write it;
|
|
14
|
+
* this behavior is its sole consumer and clears it after applying, so a
|
|
15
|
+
* stale position can never replay against a later source or rebuild.
|
|
16
|
+
*
|
|
17
|
+
* Single-positive-state reactor (`'preconditions-unmet'` ↔ `'position-pending'`):
|
|
18
|
+
* gated on `mediaElement + resolved presentation + startPosition` defined.
|
|
19
|
+
* The entry applies the command in two steps:
|
|
20
|
+
*
|
|
21
|
+
* 1. **Seed `state.currentTime` immediately.** The segment loaders anchor
|
|
22
|
+
* their load window on `state.currentTime`; seeding points the *first*
|
|
23
|
+
* fetches at the requested position instead of 0. Multi-writer with
|
|
24
|
+
* `trackCurrentTime` (ongoing DOM mirror) — legitimate: different decision
|
|
25
|
+
* domains (element-derived mirror vs one-shot command), and before
|
|
26
|
+
* HAVE_METADATA no `timeupdate`/`seeking` fires to overwrite the seed.
|
|
27
|
+
* Compose this behavior *after* `trackCurrentTime` so the seed lands after
|
|
28
|
+
* the mirror's attach-time sync.
|
|
29
|
+
* 2. **Seek the element at metadata.** `element.currentTime = position` once
|
|
30
|
+
* `readyState >= HAVE_METADATA` (immediately if already there, else on
|
|
31
|
+
* `loadedmetadata`), then clear `startPosition` (consume). The element
|
|
32
|
+
* clamps the seek to its seekable range per spec, and the resulting
|
|
33
|
+
* `seeking` event flows back through `trackCurrentTime` — from here the
|
|
34
|
+
* ordinary seek path owns the position.
|
|
35
|
+
*
|
|
36
|
+
* Position only — playing/paused is deliberately out of scope. The media
|
|
37
|
+
* element load algorithm forces `paused = true`, so a source that was
|
|
38
|
+
* playing before a rebuild comes back paused at the restored position;
|
|
39
|
+
* resume intent belongs to whoever commands the start (e.g. `setupAirPlay`
|
|
40
|
+
* restores its session-end playing state itself).
|
|
41
|
+
*
|
|
42
|
+
* Deliberately NOT relying on the pre-metadata "default playback start
|
|
43
|
+
* position" write (setting `currentTime` at HAVE_NOTHING): cross-browser MSE
|
|
44
|
+
* behavior there is inconsistent; the explicit `loadedmetadata` sequencing is
|
|
45
|
+
* deterministic everywhere.
|
|
46
|
+
*
|
|
47
|
+
* State-exit cleanup (source reset, element detach, destroy) drops the
|
|
48
|
+
* pending `loadedmetadata` listener. An *unapplied* command survives a source
|
|
49
|
+
* reset — "start the source I'm loading at P" holds while the presentation
|
|
50
|
+
* routes through unresolved — but is consumed the moment it applies.
|
|
51
|
+
*/
|
|
52
|
+
function deriveState(presentation, mediaElement, startPosition) {
|
|
53
|
+
if (!mediaElement || !isResolvedPresentation(presentation)) return "preconditions-unmet";
|
|
54
|
+
if (isUndefined(startPosition)) return "preconditions-unmet";
|
|
55
|
+
return "position-pending";
|
|
56
|
+
}
|
|
57
|
+
function applyStartPositionSetup({ state, context }) {
|
|
58
|
+
const derivedStateSignal = computed(() => deriveState(state.presentation.get(), context.mediaElement.get(), state.startPosition.get()));
|
|
59
|
+
return createMachineReactor({
|
|
60
|
+
initial: "preconditions-unmet",
|
|
61
|
+
monitor: () => derivedStateSignal.get(),
|
|
62
|
+
states: {
|
|
63
|
+
"preconditions-unmet": {},
|
|
64
|
+
"position-pending": { entry: () => {
|
|
65
|
+
const mediaElement = context.mediaElement.get();
|
|
66
|
+
const position = state.startPosition.get();
|
|
67
|
+
state.currentTime.set(position);
|
|
68
|
+
const apply = () => {
|
|
69
|
+
mediaElement.currentTime = position;
|
|
70
|
+
state.startPosition.set(void 0);
|
|
71
|
+
};
|
|
72
|
+
if (mediaElement.readyState >= HTMLMediaElement.HAVE_METADATA) {
|
|
73
|
+
apply();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
return listen(mediaElement, "loadedmetadata", apply, { once: true });
|
|
77
|
+
} }
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const applyStartPosition = defineBehavior({
|
|
82
|
+
stateKeys: [
|
|
83
|
+
"presentation",
|
|
84
|
+
"startPosition",
|
|
85
|
+
"currentTime"
|
|
86
|
+
],
|
|
87
|
+
contextKeys: ["mediaElement"],
|
|
88
|
+
setup: applyStartPositionSetup
|
|
89
|
+
});
|
|
90
|
+
//#endregion
|
|
91
|
+
export { applyStartPosition };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=apply-start-position.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-start-position.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/apply-start-position.ts"],"sourcesContent":["/**\n * **Start playback of a source at a requested position.** `state.startPosition`\n * is a one-shot command — \"when the current source can seek, start there\" —\n * the SPF analogue of hls.js's `startPosition` (and the primitive `EXT-X-START`,\n * resume-where-you-left-off, and MediaSource-recovery restore build on).\n * Consumers (adapters, `setupAirPlay`'s session-end snapshot) write it;\n * this behavior is its sole consumer and clears it after applying, so a\n * stale position can never replay against a later source or rebuild.\n *\n * Single-positive-state reactor (`'preconditions-unmet'` ↔ `'position-pending'`):\n * gated on `mediaElement + resolved presentation + startPosition` defined.\n * The entry applies the command in two steps:\n *\n * 1. **Seed `state.currentTime` immediately.** The segment loaders anchor\n * their load window on `state.currentTime`; seeding points the *first*\n * fetches at the requested position instead of 0. Multi-writer with\n * `trackCurrentTime` (ongoing DOM mirror) — legitimate: different decision\n * domains (element-derived mirror vs one-shot command), and before\n * HAVE_METADATA no `timeupdate`/`seeking` fires to overwrite the seed.\n * Compose this behavior *after* `trackCurrentTime` so the seed lands after\n * the mirror's attach-time sync.\n * 2. **Seek the element at metadata.** `element.currentTime = position` once\n * `readyState >= HAVE_METADATA` (immediately if already there, else on\n * `loadedmetadata`), then clear `startPosition` (consume). The element\n * clamps the seek to its seekable range per spec, and the resulting\n * `seeking` event flows back through `trackCurrentTime` — from here the\n * ordinary seek path owns the position.\n *\n * Position only — playing/paused is deliberately out of scope. The media\n * element load algorithm forces `paused = true`, so a source that was\n * playing before a rebuild comes back paused at the restored position;\n * resume intent belongs to whoever commands the start (e.g. `setupAirPlay`\n * restores its session-end playing state itself).\n *\n * Deliberately NOT relying on the pre-metadata \"default playback start\n * position\" write (setting `currentTime` at HAVE_NOTHING): cross-browser MSE\n * behavior there is inconsistent; the explicit `loadedmetadata` sequencing is\n * deterministic everywhere.\n *\n * State-exit cleanup (source reset, element detach, destroy) drops the\n * pending `loadedmetadata` listener. An *unapplied* command survives a source\n * reset — \"start the source I'm loading at P\" holds while the presentation\n * routes through unresolved — but is consumed the moment it applies.\n */\n\nimport { listen } from '@videojs/utils/dom';\nimport { isUndefined } from '@videojs/utils/predicate';\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport type { Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../../media/types';\n\nexport interface StartPositionState {\n presentation?: MaybeResolvedPresentation;\n /**\n * One-shot start-position command in presentation-timeline seconds.\n * Written by consumers (adapter, recovery snapshot); consumed (cleared)\n * by `applyStartPosition` once the element seeks.\n */\n startPosition?: number;\n currentTime?: number;\n}\n\nexport interface StartPositionContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\ntype StartPositionFsmState = 'preconditions-unmet' | 'position-pending';\n\nfunction deriveState(\n presentation: MaybeResolvedPresentation | undefined,\n mediaElement: HTMLMediaElement | undefined,\n startPosition: number | undefined\n): StartPositionFsmState {\n if (!mediaElement || !isResolvedPresentation(presentation)) return 'preconditions-unmet';\n if (isUndefined(startPosition)) return 'preconditions-unmet';\n return 'position-pending';\n}\n\nfunction applyStartPositionSetup({\n state,\n context,\n}: {\n state: {\n presentation: ReadonlySignal<StartPositionState['presentation']>;\n startPosition: Signal<StartPositionState['startPosition']>;\n currentTime: Signal<StartPositionState['currentTime']>;\n };\n context: {\n mediaElement: ReadonlySignal<StartPositionContext['mediaElement']>;\n };\n}): Reactor<StartPositionFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() =>\n deriveState(state.presentation.get(), context.mediaElement.get(), state.startPosition.get())\n );\n\n return createMachineReactor<StartPositionFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'position-pending': {\n // entry body is auto-untracked. Consuming the command flips the\n // monitor back to 'preconditions-unmet'; the state-exit cleanup then\n // drops the (already-fired) listener.\n entry: () => {\n const mediaElement = context.mediaElement.get()!;\n const position = state.startPosition.get()!;\n\n // Step 1 — point the loaders' first load window at the position.\n state.currentTime.set(position);\n\n // Step 2 — seek once the element can honor it, then consume.\n const apply = () => {\n mediaElement.currentTime = position;\n state.startPosition.set(undefined);\n };\n\n if (mediaElement.readyState >= HTMLMediaElement.HAVE_METADATA) {\n apply();\n return;\n }\n return listen(mediaElement, 'loadedmetadata', apply, { once: true });\n },\n },\n },\n });\n}\n\nexport const applyStartPosition = defineBehavior({\n stateKeys: ['presentation', 'startPosition', 'currentTime'],\n contextKeys: ['mediaElement'],\n setup: applyStartPositionSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,SAAS,YACP,cACA,cACA,eACuB;CACvB,IAAI,CAAC,gBAAgB,CAAC,uBAAuB,YAAY,GAAG,OAAO;CACnE,IAAI,YAAY,aAAa,GAAG,OAAO;CACvC,OAAO;AACT;AAEA,SAAS,wBAAwB,EAC/B,OACA,WAU8D;CAC9D,MAAM,qBAAqB,eACzB,YAAY,MAAM,aAAa,IAAI,GAAG,QAAQ,aAAa,IAAI,GAAG,MAAM,cAAc,IAAI,CAAC,CAC7F;CAEA,OAAO,qBAA4C;EACjD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GAExB,oBAAoB,EAIlB,aAAa;IACX,MAAM,eAAe,QAAQ,aAAa,IAAI;IAC9C,MAAM,WAAW,MAAM,cAAc,IAAI;IAGzC,MAAM,YAAY,IAAI,QAAQ;IAG9B,MAAM,cAAc;KAClB,aAAa,cAAc;KAC3B,MAAM,cAAc,IAAI,KAAA,CAAS;IACnC;IAEA,IAAI,aAAa,cAAc,iBAAiB,eAAe;KAC7D,MAAM;KACN;IACF;IACA,OAAO,OAAO,cAAc,kBAAkB,OAAO,EAAE,MAAM,KAAK,CAAC;GACrE,EACF;EACF;CACF,CAAC;AACH;AAEA,MAAa,qBAAqB,eAAe;CAC/C,WAAW;EAAC;EAAgB;EAAiB;CAAa;CAC1D,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { computed, signal } from "../../../core/signals/primitives.js";
|
|
2
|
+
import { effect } from "../../../core/signals/effect.js";
|
|
3
|
+
import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
|
|
4
|
+
import { isResolvedTrack } from "../../../media/types/index.js";
|
|
5
|
+
import { findTrackById } from "../../../media/utils/tracks.js";
|
|
6
|
+
import { onMediaSourceReadyStateChange } from "../../../media/dom/mse/mediasource-setup.js";
|
|
7
|
+
import { getMaxBufferedEnd, waitForSourceBuffersReady } from "../../../media/dom/mse/duration.js";
|
|
8
|
+
import { isLastSegmentAppended } from "../../../media/dom/mse/end-of-stream.js";
|
|
9
|
+
//#region src/playback/behaviors/dom/end-of-stream.ts
|
|
10
|
+
/**
|
|
11
|
+
* Slack (seconds) on the "playhead has reached the last segment" gate. A tiny final
|
|
12
|
+
* segment (e.g. Apple's ~44ms last segment) starts right at the buffered end, and the
|
|
13
|
+
* browser freezes the playhead ~50–70ms short of that end (its render horizon), so a
|
|
14
|
+
* strict `currentTime >= lastSegStart` would never open — deadlocking `endOfStream`
|
|
15
|
+
* (the MediaSource stays `'open'`, so the browser keeps the playhead frozen waiting for
|
|
16
|
+
* data/EOS that never comes). This slack lets a playhead stalled just short of the final
|
|
17
|
+
* segment still finalize. Firing slightly early is harmless: the last segment is already
|
|
18
|
+
* appended (the gate above), so no more data is expected.
|
|
19
|
+
*/
|
|
20
|
+
const LAST_SEGMENT_REACHED_SLACK = .5;
|
|
21
|
+
function deriveState(presentation, mediaSource, msIsOpen, videoBufferActor, audioBufferActor, currentTime) {
|
|
22
|
+
if (!mediaSource || !presentation || !msIsOpen) return "preconditions-unmet";
|
|
23
|
+
const actors = [videoBufferActor, audioBufferActor].filter((a) => a !== void 0);
|
|
24
|
+
if (actors.length === 0) return "preconditions-unmet";
|
|
25
|
+
let lastSegStart;
|
|
26
|
+
for (const actor of actors) {
|
|
27
|
+
const snapshot = actor.snapshot.get();
|
|
28
|
+
if (snapshot.value !== "idle") return "preconditions-unmet";
|
|
29
|
+
const { initTrackId, segments: appended } = snapshot.context;
|
|
30
|
+
if (!initTrackId) return "preconditions-unmet";
|
|
31
|
+
const track = findTrackById(presentation, initTrackId);
|
|
32
|
+
if (!track || !isResolvedTrack(track)) return "preconditions-unmet";
|
|
33
|
+
if (!Number.isFinite(track.duration)) return "preconditions-unmet";
|
|
34
|
+
if (!isLastSegmentAppended(track.segments, appended)) return "preconditions-unmet";
|
|
35
|
+
if (track.segments.length > 0) {
|
|
36
|
+
const start = track.segments[track.segments.length - 1].startTime;
|
|
37
|
+
if (lastSegStart === void 0 || start > lastSegStart) lastSegStart = start;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (lastSegStart !== void 0 && (currentTime ?? 0) < lastSegStart - LAST_SEGMENT_REACHED_SLACK) return "preconditions-unmet";
|
|
41
|
+
return "eos-ready";
|
|
42
|
+
}
|
|
43
|
+
function endOfStreamSetup({ state, context }) {
|
|
44
|
+
const msIsOpen = signal(false);
|
|
45
|
+
const cleanupMsListener = effect(() => {
|
|
46
|
+
const mediaSource = context.mediaSource.get();
|
|
47
|
+
if (!mediaSource) {
|
|
48
|
+
msIsOpen.set(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
msIsOpen.set(mediaSource.readyState === "open");
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
onMediaSourceReadyStateChange(mediaSource, controller.signal, (rs) => {
|
|
54
|
+
msIsOpen.set(rs === "open");
|
|
55
|
+
});
|
|
56
|
+
return () => controller.abort();
|
|
57
|
+
});
|
|
58
|
+
const derivedStateSignal = computed(() => deriveState(state.presentation.get(), context.mediaSource.get(), msIsOpen.get(), context.videoBufferActor?.get(), context.audioBufferActor?.get(), state.currentTime.get()));
|
|
59
|
+
const reactor = createMachineReactor({
|
|
60
|
+
initial: "preconditions-unmet",
|
|
61
|
+
monitor: () => derivedStateSignal.get(),
|
|
62
|
+
states: {
|
|
63
|
+
"preconditions-unmet": {},
|
|
64
|
+
"eos-ready": { entry: () => {
|
|
65
|
+
const mediaSource = context.mediaSource.get();
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
const endStreamWhenReady = async () => {
|
|
68
|
+
await waitForSourceBuffersReady(mediaSource.sourceBuffers, controller.signal);
|
|
69
|
+
if (controller.signal.aborted) return;
|
|
70
|
+
const bufferedEnd = getMaxBufferedEnd(mediaSource.sourceBuffers);
|
|
71
|
+
if (bufferedEnd > 0) mediaSource.duration = bufferedEnd;
|
|
72
|
+
mediaSource.endOfStream();
|
|
73
|
+
};
|
|
74
|
+
endStreamWhenReady().catch((err) => console.error("Failed to call endOfStream:", err));
|
|
75
|
+
return () => controller.abort();
|
|
76
|
+
} }
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return () => {
|
|
80
|
+
cleanupMsListener();
|
|
81
|
+
reactor.destroy();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `endOfStream` uses a manual `Behavior<>` literal (rather than
|
|
86
|
+
* `defineBehavior`) because it reads `videoBufferActor` /
|
|
87
|
+
* `audioBufferActor` defensively without declaring them in its
|
|
88
|
+
* contextKeys — those slots are contributed by other behaviors and
|
|
89
|
+
* compose conditionally per engine variant. The `Behavior<>` literal
|
|
90
|
+
* opts out of the exhaustiveness check so the typed context shape can
|
|
91
|
+
* include the optional fields used at runtime. See the comment on
|
|
92
|
+
* `endOfStreamSetup`'s context param for the discipline.
|
|
93
|
+
*/
|
|
94
|
+
const endOfStream = {
|
|
95
|
+
stateKeys: ["presentation", "currentTime"],
|
|
96
|
+
contextKeys: ["mediaSource"],
|
|
97
|
+
setup: endOfStreamSetup
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
export { endOfStream };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=end-of-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"end-of-stream.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/end-of-stream.ts"],"sourcesContent":["/**\n * **Drive each `open → ended` transition of the MediaSource.** Calls\n * `MediaSource.endOfStream()` once every active buffer actor's\n * currently-loading track is *complete* (finite `Track.duration` — the parser's\n * completeness signal), its temporally last segments are fully appended, and\n * the user has reached them — letting the browser finalize duration and\n * fire `ended` on the media element. The completeness gate keeps it inert for\n * ongoing live (`Track.duration === Infinity`), whose \"last segment\" is only\n * the rolling edge; a live stream opts in when its duration turns finite.\n *\n * Re-fires on every subsequent `open → ended → open` cycle. Per the MSE\n * spec, `appendBuffer()` after `endOfStream()` transitions the MediaSource\n * back to `'open'`; seek-back replays and back-buffer refills that re-load\n * earlier segments take this path, so the behavior must call\n * `endOfStream()` again once the last segments are reappended. The reactor's\n * `'preconditions-unmet'` ↔ `'eos-ready'` cycle *is* the re-arm mechanism:\n * a successful `endOfStream()` flips `mediaSource.readyState` to `'ended'`,\n * which (via the local `msIsOpen` signal) exits `'eos-ready'`; the next\n * `'open'` re-evaluates preconditions and may re-enter.\n *\n * # Tracking what's in the buffer\n *\n * Each `SourceBufferActor` knows which track it's currently loading via\n * `initTrackId` (set on the most recent `append-init` message) and which\n * segments it has appended. `deriveState` iterates over the available\n * buffer actors (`[videoBufferActor, audioBufferActor].filter(Boolean)`),\n * resolves each to its track via `findTrackById`, and checks that track's\n * last segment is appended. This means audio-only / video-only / mixed\n * configurations compose uniformly — the body iterates whatever's in\n * scope. No reliance on `selectedTrackId` slots: the actor's view IS the\n * source of truth for \"what's being loaded into this buffer.\"\n *\n * **Two-fire on mid-end ABR switches**: when a quality switch occurs near\n * end-of-stream, the actor's `initTrackId` still reflects the *old* track\n * until the new init segment is appended. The reactor may fire\n * `endOfStream()` against the old track's last-segment-appended state,\n * then the new init's `appendBuffer()` re-opens the MS, which re-arms us\n * to fire again once the new track's last segment lands. Functionally\n * correct (the browser re-fires `ended` after the re-arm), at the cost of\n * one extra call. Accepted as the price of dropping `selectedTrackId`\n * dependence.\n *\n * # Buffer-actor idle gate\n *\n * Each actor must be `'idle'` (no queued or in-flight tasks) before we\n * fire. `buffer.updating === false` alone is insufficient: for chunked\n * fMP4 streaming, `updateend` fires after each chunk while the actor's\n * for-await loop synchronously enqueues the next `appendBuffer()` — so\n * the buffer flips `!updating → updating` across a microtask boundary.\n * The actor's `'updating'` state spans the entire multi-chunk append, so\n * `actor.snapshot.value === 'idle'` is the canonical \"no more pending or\n * in-flight work\" oracle.\n *\n * This bet (actor models the pipeline; `mediaSource.sourceBuffers` models\n * DOM state) is durable as long as the segment loader is the sole writer\n * of `appendBuffer()` calls. A future loop-mode that auto-fetches earlier\n * segments mid-`ended` would require a broader coordination story\n * between SourceBuffers and the MediaSource — possibly via a\n * `MediaSourceActor` — and that's where this gate would want\n * re-evaluating.\n *\n * # currentTime gate\n *\n * `currentTime` must have reached (within {@link LAST_SEGMENT_REACHED_SLACK})\n * at least one active track's last segment startTime. Prevents `'eos-ready'`\n * entry when a back-buffer `remove()` / `appendBuffer()` briefly re-opens the\n * MediaSource while the user is mid-stream. HLS rendition time-alignment means\n * any active track works as the reference. The slack absorbs the near-end\n * playhead freeze (see the constant) so a tiny final segment doesn't deadlock.\n *\n * # MS readyState — local subscription\n *\n * The behavior subscribes to `mediaSource`'s readyState changes inside\n * its setup (via `onMediaSourceReadyStateChange`) and mirrors `'open'` to\n * a behavior-local `msIsOpen` signal that `deriveState` reads. No shared\n * `mediaSourceReadyState` slot dependency. Anticipates a future\n * `MediaSourceActor` whose snapshot would expose the same signal — the\n * consumer code wouldn't change.\n *\n * # `endOfStream` entry sequence\n *\n * 1. Wait for all SourceBuffers to be idle (DOM-level — defensive,\n * should already hold given the actor-idle gate in `deriveState`).\n * 2. Set `mediaSource.duration` from `getMaxBufferedEnd` to match actual\n * container timestamps (`endOfStream()` only clamps up implicitly;\n * setting it explicitly here keeps the final value deterministic).\n * 3. Call `mediaSource.endOfStream()`.\n *\n * State-exit cleanup (`controller.abort()`) cancels any in-flight wait\n * on source unload, presentation replace, or behavior destroy.\n *\n * # Coordination with `updateMediaSourceDuration`\n *\n * `updateMediaSourceDuration` writes the initial `mediaSource.duration`\n * from `presentation.duration` once per source; this behavior writes the\n * final value from the buffered end. Decision domains don't overlap.\n */\nimport type { Behavior } from '../../../core/composition/create-composition';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { effect } from '../../../core/signals/effect';\nimport { computed, type ReadonlySignal, signal } from '../../../core/signals/primitives';\nimport { getMaxBufferedEnd, waitForSourceBuffersReady } from '../../../media/dom/mse/duration';\nimport { isLastSegmentAppended } from '../../../media/dom/mse/end-of-stream';\nimport { onMediaSourceReadyStateChange } from '../../../media/dom/mse/mediasource-setup';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport { isResolvedTrack } from '../../../media/types';\nimport { findTrackById } from '../../../media/utils/tracks';\nimport type { SourceBufferActor } from '../../actors/dom/source-buffer';\n\nexport interface EndOfStreamState {\n presentation?: MaybeResolvedPresentation;\n currentTime?: number;\n}\n\nexport interface EndOfStreamContext {\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n}\n\ntype EndOfStreamFsmState = 'preconditions-unmet' | 'eos-ready';\n\n/**\n * Slack (seconds) on the \"playhead has reached the last segment\" gate. A tiny final\n * segment (e.g. Apple's ~44ms last segment) starts right at the buffered end, and the\n * browser freezes the playhead ~50–70ms short of that end (its render horizon), so a\n * strict `currentTime >= lastSegStart` would never open — deadlocking `endOfStream`\n * (the MediaSource stays `'open'`, so the browser keeps the playhead frozen waiting for\n * data/EOS that never comes). This slack lets a playhead stalled just short of the final\n * segment still finalize. Firing slightly early is harmless: the last segment is already\n * appended (the gate above), so no more data is expected.\n */\nconst LAST_SEGMENT_REACHED_SLACK = 0.5;\n\nfunction deriveState(\n presentation: MaybeResolvedPresentation | undefined,\n mediaSource: MediaSource | undefined,\n msIsOpen: boolean,\n videoBufferActor: SourceBufferActor | undefined,\n audioBufferActor: SourceBufferActor | undefined,\n currentTime: number | undefined\n): EndOfStreamFsmState {\n if (!mediaSource || !presentation || !msIsOpen) return 'preconditions-unmet';\n\n const actors = [videoBufferActor, audioBufferActor].filter((a): a is SourceBufferActor => a !== undefined);\n // No active buffer actors means setup hasn't completed wiring yet.\n if (actors.length === 0) return 'preconditions-unmet';\n\n // Track the latest last-segment startTime across actors as the\n // currentTime reference. HLS rendition time-alignment makes any active\n // track usable; using the max is safest when timings drift slightly.\n let lastSegStart: number | undefined;\n\n for (const actor of actors) {\n const snapshot = actor.snapshot.get();\n if (snapshot.value !== 'idle') return 'preconditions-unmet';\n\n const { initTrackId, segments: appended } = snapshot.context;\n if (!initTrackId) return 'preconditions-unmet';\n\n const track = findTrackById(presentation, initTrackId);\n if (!track || !isResolvedTrack(track)) return 'preconditions-unmet';\n // Only a complete playlist has a true last segment: firing on an ongoing\n // live window would pin a finite (live-edge) duration and end the stream,\n // only for the next reload's appends to reopen it and re-fire on a loop.\n // Independent of the currentTime slack below — this asks \"is there an end at\n // all,\" the slack asks \"has the playhead effectively reached it.\"\n if (!Number.isFinite(track.duration)) return 'preconditions-unmet';\n if (!isLastSegmentAppended(track.segments, appended)) return 'preconditions-unmet';\n\n if (track.segments.length > 0) {\n const start = track.segments[track.segments.length - 1]!.startTime;\n if (lastSegStart === undefined || start > lastSegStart) lastSegStart = start;\n }\n }\n\n // Slack absorbs the near-end playhead freeze so a tiny final segment doesn't deadlock.\n if (lastSegStart !== undefined && (currentTime ?? 0) < lastSegStart - LAST_SEGMENT_REACHED_SLACK) {\n return 'preconditions-unmet';\n }\n\n return 'eos-ready';\n}\n\nfunction endOfStreamSetup({\n state,\n context,\n}: {\n state: {\n presentation: ReadonlySignal<EndOfStreamState['presentation']>;\n currentTime: ReadonlySignal<EndOfStreamState['currentTime']>;\n };\n context: {\n mediaSource: ReadonlySignal<EndOfStreamContext['mediaSource']>;\n // See behavior definition for details on these optional context signals.\n videoBufferActor?: ReadonlySignal<SourceBufferActor | undefined>;\n audioBufferActor?: ReadonlySignal<SourceBufferActor | undefined>;\n };\n}): () => void {\n // Behavior-local mirror of `mediaSource.readyState === 'open'`. Subscribes\n // to MS events whenever a MediaSource is in scope; tears down when the\n // MS is swapped or the behavior destroys. Anticipates a future\n // `MediaSourceActor` whose snapshot would expose the same value — the\n // consumer code (deriveState) wouldn't change.\n const msIsOpen = signal(false);\n const cleanupMsListener = effect(() => {\n const mediaSource = context.mediaSource.get();\n if (!mediaSource) {\n msIsOpen.set(false);\n return;\n }\n msIsOpen.set(mediaSource.readyState === 'open');\n const controller = new AbortController();\n onMediaSourceReadyStateChange(mediaSource, controller.signal, (rs) => {\n msIsOpen.set(rs === 'open');\n });\n return () => controller.abort();\n });\n\n const derivedStateSignal = computed(() =>\n deriveState(\n state.presentation.get(),\n context.mediaSource.get(),\n msIsOpen.get(),\n context.videoBufferActor?.get(),\n context.audioBufferActor?.get(),\n state.currentTime.get()\n )\n );\n\n const reactor = createMachineReactor<EndOfStreamFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'eos-ready': {\n // entry body is auto-untracked. deriveState handles source resets\n // and the MS readyState flip from 'open' to 'ended' that\n // endOfStream() produces; this entry resolves the MSE-spec\n // preconditions and binds its cleanup (abort) to state exit +\n // destroy.\n entry: () => {\n const mediaSource = context.mediaSource.get()!;\n const controller = new AbortController();\n\n const endStreamWhenReady = async () => {\n // Defensive: the actor-idle gate in deriveState should mean\n // buffers are already settled, but a microtask boundary\n // between deriveState's read and entry firing leaves a thin\n // gap. The DOM-level wait closes it.\n await waitForSourceBuffersReady(mediaSource.sourceBuffers, controller.signal);\n if (controller.signal.aborted) return;\n\n // MSE spec: duration cannot be less than any buffered range, and\n // endOfStream() will only clamp up implicitly. Setting it\n // explicitly here from actual container timestamps keeps the\n // final value deterministic for assets whose declared duration\n // disagrees with the buffered end (common with CMAF).\n const bufferedEnd = getMaxBufferedEnd(mediaSource.sourceBuffers);\n if (bufferedEnd > 0) mediaSource.duration = bufferedEnd;\n\n mediaSource.endOfStream();\n };\n\n endStreamWhenReady().catch((err) => console.error('Failed to call endOfStream:', err));\n\n return () => controller.abort();\n },\n },\n },\n });\n\n return () => {\n cleanupMsListener();\n reactor.destroy();\n };\n}\n\n/**\n * `endOfStream` uses a manual `Behavior<>` literal (rather than\n * `defineBehavior`) because it reads `videoBufferActor` /\n * `audioBufferActor` defensively without declaring them in its\n * contextKeys — those slots are contributed by other behaviors and\n * compose conditionally per engine variant. The `Behavior<>` literal\n * opts out of the exhaustiveness check so the typed context shape can\n * include the optional fields used at runtime. See the comment on\n * `endOfStreamSetup`'s context param for the discipline.\n */\nexport const endOfStream: Behavior<\n {\n presentation: ReadonlySignal<EndOfStreamState['presentation']>;\n currentTime: ReadonlySignal<EndOfStreamState['currentTime']>;\n },\n { mediaSource: ReadonlySignal<EndOfStreamContext['mediaSource']> }\n> = {\n stateKeys: ['presentation', 'currentTime'],\n contextKeys: ['mediaSource'],\n setup: endOfStreamSetup,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoIA,MAAM,6BAA6B;AAEnC,SAAS,YACP,cACA,aACA,UACA,kBACA,kBACA,aACqB;CACrB,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,UAAU,OAAO;CAEvD,MAAM,SAAS,CAAC,kBAAkB,gBAAgB,CAAC,CAAC,QAAQ,MAA8B,MAAM,KAAA,CAAS;CAEzG,IAAI,OAAO,WAAW,GAAG,OAAO;CAKhC,IAAI;CAEJ,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,MAAM,SAAS,IAAI;EACpC,IAAI,SAAS,UAAU,QAAQ,OAAO;EAEtC,MAAM,EAAE,aAAa,UAAU,aAAa,SAAS;EACrD,IAAI,CAAC,aAAa,OAAO;EAEzB,MAAM,QAAQ,cAAc,cAAc,WAAW;EACrD,IAAI,CAAC,SAAS,CAAC,gBAAgB,KAAK,GAAG,OAAO;EAM9C,IAAI,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG,OAAO;EAC7C,IAAI,CAAC,sBAAsB,MAAM,UAAU,QAAQ,GAAG,OAAO;EAE7D,IAAI,MAAM,SAAS,SAAS,GAAG;GAC7B,MAAM,QAAQ,MAAM,SAAS,MAAM,SAAS,SAAS,EAAE,CAAE;GACzD,IAAI,iBAAiB,KAAA,KAAa,QAAQ,cAAc,eAAe;EACzE;CACF;CAGA,IAAI,iBAAiB,KAAA,MAAc,eAAe,KAAK,eAAe,4BACpE,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,iBAAiB,EACxB,OACA,WAYa;CAMb,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,oBAAoB,aAAa;EACrC,MAAM,cAAc,QAAQ,YAAY,IAAI;EAC5C,IAAI,CAAC,aAAa;GAChB,SAAS,IAAI,KAAK;GAClB;EACF;EACA,SAAS,IAAI,YAAY,eAAe,MAAM;EAC9C,MAAM,aAAa,IAAI,gBAAgB;EACvC,8BAA8B,aAAa,WAAW,SAAS,OAAO;GACpE,SAAS,IAAI,OAAO,MAAM;EAC5B,CAAC;EACD,aAAa,WAAW,MAAM;CAChC,CAAC;CAED,MAAM,qBAAqB,eACzB,YACE,MAAM,aAAa,IAAI,GACvB,QAAQ,YAAY,IAAI,GACxB,SAAS,IAAI,GACb,QAAQ,kBAAkB,IAAI,GAC9B,QAAQ,kBAAkB,IAAI,GAC9B,MAAM,YAAY,IAAI,CACxB,CACF;CAEA,MAAM,UAAU,qBAA0C;EACxD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GAExB,aAAa,EAMX,aAAa;IACX,MAAM,cAAc,QAAQ,YAAY,IAAI;IAC5C,MAAM,aAAa,IAAI,gBAAgB;IAEvC,MAAM,qBAAqB,YAAY;KAKrC,MAAM,0BAA0B,YAAY,eAAe,WAAW,MAAM;KAC5E,IAAI,WAAW,OAAO,SAAS;KAO/B,MAAM,cAAc,kBAAkB,YAAY,aAAa;KAC/D,IAAI,cAAc,GAAG,YAAY,WAAW;KAE5C,YAAY,YAAY;IAC1B;IAEA,mBAAmB,CAAC,CAAC,OAAO,QAAQ,QAAQ,MAAM,+BAA+B,GAAG,CAAC;IAErF,aAAa,WAAW,MAAM;GAChC,EACF;EACF;CACF,CAAC;CAED,aAAa;EACX,kBAAkB;EAClB,QAAQ,QAAQ;CAClB;AACF;;;;;;;;;;;AAYA,MAAa,cAMT;CACF,WAAW,CAAC,gBAAgB,aAAa;CACzC,aAAa,CAAC,aAAa;CAC3B,OAAO;AACT"}
|