@videojs/spf 10.0.0-beta.24 → 10.0.0-beta.26
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 +5 -0
- package/dist/default/background-video.js +3 -0
- package/dist/default/core/actors/create-machine-actor.js.map +1 -1
- package/dist/default/core/actors/create-transition-actor.js.map +1 -1
- package/dist/default/core/composition/create-composition.js +2 -2
- package/dist/default/core/composition/create-composition.js.map +1 -1
- package/dist/default/core/composition/share-signals.js +13 -11
- package/dist/default/core/composition/share-signals.js.map +1 -1
- package/dist/default/core/machine.js.map +1 -1
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
- package/dist/default/core/signals/effect.js.map +1 -1
- package/dist/default/core/signals/primitives.js.map +1 -1
- package/dist/default/core/tasks/task.js.map +1 -1
- package/dist/default/hls.js +4 -1
- package/dist/default/media/abr/quality-selection.js +6 -68
- package/dist/default/media/abr/quality-selection.js.map +1 -1
- package/dist/default/media/buffer/back-buffer.js.map +1 -1
- package/dist/default/media/buffer/forward-buffer.js +37 -3
- package/dist/default/media/buffer/forward-buffer.js.map +1 -1
- package/dist/default/media/dom/capabilities.js +70 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/default/media/dom/mse/append-segment.js.map +1 -1
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
- package/dist/default/media/dom/mse/duration.js +27 -10
- package/dist/default/media/dom/mse/duration.js.map +1 -1
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
- package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
- package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
- package/dist/default/media/dom/text/text-track-slots.js +3 -3
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
- package/dist/default/media/hls/parse-attributes.js +17 -3
- package/dist/default/media/hls/parse-attributes.js.map +1 -1
- package/dist/default/media/hls/parse-media-playlist.js +29 -1
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
- package/dist/default/media/hls/parse-multivariant.js +15 -6
- package/dist/default/media/hls/parse-multivariant.js.map +1 -1
- package/dist/default/media/hls/resolve-url.js.map +1 -1
- 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/select-tracks.js +98 -13
- package/dist/default/media/primitives/select-tracks.js.map +1 -1
- 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.map +1 -1
- 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.map +1 -1
- package/dist/default/media/utils/track-selection.js +1 -1
- package/dist/default/media/utils/track-selection.js.map +1 -1
- package/dist/default/media/utils/tracks.js +31 -4
- package/dist/default/media/utils/tracks.js.map +1 -1
- package/dist/default/media-tracks.js +2 -0
- package/dist/default/network/bandwidth-estimator.js.map +1 -1
- package/dist/default/network/chunked-stream-iterable.js.map +1 -1
- package/dist/default/network/ewma.js +1 -1
- package/dist/default/network/ewma.js.map +1 -1
- package/dist/default/network/fetch.js +7 -1
- package/dist/default/network/fetch.js.map +1 -1
- package/dist/default/playback/actors/dom/segment-loader.js +103 -106
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
- package/dist/default/playback/actors/dom/source-buffer.js +3 -1
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
- package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +21 -43
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
- 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 +25 -102
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
- package/dist/default/playback/behaviors/dom/load-segments.js +6 -2
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
- 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/setup-buffer-actors.js +50 -18
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +43 -18
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-current-time.js +6 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +3 -2
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
- package/dist/default/playback/behaviors/establish-start-media-time.js +123 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
- package/dist/default/playback/behaviors/resolve-track.js +45 -22
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
- package/dist/default/playback/behaviors/select-tracks.js +46 -77
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
- 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.map +1 -1
- package/dist/default/playback/behaviors/track-switching.js +403 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- package/dist/default/playback/engines/background-video/adapter.js +158 -0
- package/dist/default/playback/engines/background-video/adapter.js.map +1 -0
- package/dist/default/playback/engines/background-video/engine.js +75 -0
- package/dist/default/playback/engines/background-video/engine.js.map +1 -0
- package/dist/default/playback/engines/hls/adapter-audio-only.js +124 -0
- package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/adapter.js +20 -11
- package/dist/default/playback/engines/hls/adapter.js.map +1 -1
- package/dist/default/playback/engines/hls/engine-audio-only.js +94 -0
- package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +38 -8
- package/dist/default/playback/engines/hls/engine.js.map +1 -1
- 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/relocation-pipelines.js +203 -0
- package/dist/default/playback/primitives/relocation-pipelines.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/text-segment-load-pipeline.js +37 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/{dev/playback/behaviors → default/playback/primitives}/track-types.js +1 -1
- package/dist/default/playback/primitives/track-types.js.map +1 -0
- package/dist/dev/background-video.d.ts +3 -0
- package/dist/dev/background-video.js +3 -0
- package/dist/dev/core/actors/actor.d.ts +0 -1
- package/dist/dev/core/actors/actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
- package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
- package/dist/dev/core/composition/create-composition.d.ts +10 -11
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
- package/dist/dev/core/composition/create-composition.js +2 -2
- package/dist/dev/core/composition/create-composition.js.map +1 -1
- package/dist/dev/core/composition/share-signals.d.ts +11 -10
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
- package/dist/dev/core/composition/share-signals.js +13 -11
- package/dist/dev/core/composition/share-signals.js.map +1 -1
- package/dist/dev/core/machine.d.ts +1 -0
- package/dist/dev/core/machine.d.ts.map +1 -1
- package/dist/dev/core/machine.js.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
- package/dist/dev/core/signals/effect.d.ts.map +1 -1
- package/dist/dev/core/signals/effect.js.map +1 -1
- package/dist/dev/core/signals/primitives.d.ts +1 -2
- package/dist/dev/core/signals/primitives.d.ts.map +1 -1
- package/dist/dev/core/signals/primitives.js.map +1 -1
- package/dist/dev/core/tasks/task.d.ts +2 -2
- package/dist/dev/core/tasks/task.d.ts.map +1 -1
- package/dist/dev/core/tasks/task.js.map +1 -1
- package/dist/dev/hls.d.ts +5 -1
- package/dist/dev/hls.js +4 -1
- package/dist/dev/index.d.ts +0 -1
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
- package/dist/dev/media/abr/quality-selection.js +6 -68
- package/dist/dev/media/abr/quality-selection.js.map +1 -1
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
- package/dist/dev/media/buffer/back-buffer.js.map +1 -1
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
- package/dist/dev/media/buffer/forward-buffer.js +37 -3
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
- package/dist/dev/media/dom/capabilities.js +70 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
- package/dist/dev/media/dom/mse/duration.js +27 -10
- package/dist/dev/media/dom/mse/duration.js.map +1 -1
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
- package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
- package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
- package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
- package/dist/dev/media/dom/text/text-track-slots.js +3 -3
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
- package/dist/dev/media/hls/parse-attributes.js +17 -3
- package/dist/dev/media/hls/parse-attributes.js.map +1 -1
- package/dist/dev/media/hls/parse-media-playlist.js +29 -1
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
- package/dist/dev/media/hls/parse-multivariant.js +15 -6
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
- package/dist/dev/media/hls/resolve-url.js.map +1 -1
- 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/select-tracks.d.ts +35 -0
- package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +98 -13
- package/dist/dev/media/primitives/select-tracks.js.map +1 -1
- 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 +69 -2
- package/dist/dev/media/types/index.d.ts.map +1 -1
- package/dist/dev/media/types/index.js.map +1 -1
- 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.map +1 -1
- package/dist/dev/media/utils/track-selection.js +1 -1
- package/dist/dev/media/utils/track-selection.js.map +1 -1
- package/dist/dev/media/utils/tracks.js +31 -4
- package/dist/dev/media/utils/tracks.js.map +1 -1
- package/dist/dev/media-tracks.d.ts +3 -0
- package/dist/dev/media-tracks.js +2 -0
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
- package/dist/dev/network/bandwidth-estimator.js.map +1 -1
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
- package/dist/dev/network/ewma.js +1 -1
- package/dist/dev/network/ewma.js.map +1 -1
- package/dist/dev/network/fetch.js +7 -1
- package/dist/dev/network/fetch.js.map +1 -1
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
- package/dist/dev/playback/actors/dom/segment-loader.js +103 -106
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +10 -25
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
- package/dist/dev/playback/actors/dom/source-buffer.js +3 -1
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
- package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
- package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +11 -2
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +21 -43
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
- 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 +25 -102
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/load-segments.js +6 -2
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
- 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/setup-buffer-actors.js +50 -18
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +43 -18
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-current-time.js +6 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +3 -2
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +31 -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 +123 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
- package/dist/dev/playback/behaviors/resolve-track.js +45 -22
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
- package/dist/dev/playback/behaviors/select-tracks.d.ts +14 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +46 -77
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
- 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.map +1 -1
- package/dist/dev/playback/behaviors/track-switching.js +403 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/background-video/adapter.d.ts +59 -0
- package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/engines/background-video/adapter.js +158 -0
- package/dist/dev/playback/engines/background-video/adapter.js.map +1 -0
- package/dist/dev/playback/engines/background-video/engine.d.ts +106 -0
- package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/background-video/engine.js +75 -0
- package/dist/dev/playback/engines/background-video/engine.js.map +1 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +46 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.js +124 -0
- package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -0
- package/dist/dev/playback/engines/hls/adapter.d.ts +3 -5
- package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/adapter.js +20 -11
- package/dist/dev/playback/engines/hls/adapter.js.map +1 -1
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +156 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js +94 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +109 -4
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
- package/dist/dev/playback/engines/hls/engine.js +38 -8
- package/dist/dev/playback/engines/hls/engine.js.map +1 -1
- 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/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/relocation-pipelines.js +203 -0
- package/dist/dev/playback/primitives/relocation-pipelines.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/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/{default/playback/behaviors → dev/playback/primitives}/track-types.js +1 -1
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +17 -7
- package/dist/default/playback/behaviors/quality-switching.js +0 -96
- package/dist/default/playback/behaviors/quality-switching.js.map +0 -1
- package/dist/default/playback/behaviors/track-types.js.map +0 -1
- package/dist/dev/playback/behaviors/quality-switching.js +0 -96
- package/dist/dev/playback/behaviors/quality-switching.js.map +0 -1
- package/dist/dev/playback/behaviors/track-types.js.map +0 -1
|
@@ -5,27 +5,44 @@ import { loadAudioSegments, loadTextTrackSegments, loadVideoSegments } from "../
|
|
|
5
5
|
import { setupTextTrackActors } from "../../behaviors/dom/setup-text-track-actors.js";
|
|
6
6
|
import { trackCurrentTime } from "../../behaviors/dom/track-current-time.js";
|
|
7
7
|
import { trackLoadTriggers } from "../../behaviors/dom/track-load-triggers.js";
|
|
8
|
+
import { deriveSharedMinStartMediaTime, establishStartMediaTime } from "../../behaviors/establish-start-media-time.js";
|
|
9
|
+
import { attachMediaSourceAsSourceElement } from "../../../media/dom/mse/mediasource-setup.js";
|
|
10
|
+
import { canPlayTrack } from "../../../media/dom/capabilities.js";
|
|
8
11
|
import { addSubtitlesTracksToMedia, getShowingSubtitlesTrackFromMedia, removeAllSubtitlesTracksFromMedia } from "../../../media/dom/text/text-track-slots.js";
|
|
9
12
|
import { parseMultivariantPlaylist } from "../../../media/hls/parse-multivariant.js";
|
|
10
13
|
import { getResolvedSelectedTrackDuration } from "../../../media/utils/track-selection.js";
|
|
11
14
|
import { calculatePresentationDuration } from "../../behaviors/calculate-presentation-duration.js";
|
|
15
|
+
import { deriveCdnPriority } from "../../behaviors/derive-cdn-priority.js";
|
|
16
|
+
import { setupAirPlay } from "../../behaviors/dom/airplay.js";
|
|
17
|
+
import { applyStartPosition } from "../../behaviors/dom/apply-start-position.js";
|
|
12
18
|
import { endOfStream } from "../../behaviors/dom/end-of-stream.js";
|
|
19
|
+
import { recoverEndStall } from "../../behaviors/dom/recover-end-stall.js";
|
|
13
20
|
import { setupAudioBufferActors, setupVideoBufferActors } from "../../behaviors/dom/setup-buffer-actors.js";
|
|
14
21
|
import { setupMediaSource } from "../../behaviors/dom/setup-mediasource.js";
|
|
15
22
|
import { syncTextTracks } from "../../behaviors/dom/sync-text-tracks.js";
|
|
16
23
|
import { updateMediaSourceDuration } from "../../behaviors/dom/update-mediasource-duration.js";
|
|
17
|
-
import { switchVideoQuality } from "../../behaviors/quality-switching.js";
|
|
18
24
|
import { resolvePresentation } from "../../behaviors/resolve-presentation.js";
|
|
19
25
|
import { resolveAudioTrack, resolveTextTrack, resolveVideoTrack } from "../../behaviors/resolve-track.js";
|
|
20
|
-
import {
|
|
26
|
+
import { setupFailoverMonitor } from "../../behaviors/setup-failover-monitor.js";
|
|
21
27
|
import { syncPreload } from "../../behaviors/sync-preload.js";
|
|
28
|
+
import { switchAudioTrack, switchTextTrack, switchVideoTrack } from "../../behaviors/track-switching.js";
|
|
29
|
+
import { relocatingTextPipelines, relocationPipelinesFor } from "../../primitives/relocation-pipelines.js";
|
|
22
30
|
//#region src/playback/engines/hls/engine.ts
|
|
23
31
|
/**
|
|
24
32
|
* Generic `shareSignals` instantiated against the HLS engine's full state
|
|
25
33
|
* and context — captures composition signal refs into the consumer's
|
|
26
|
-
* `onSignalsReady` callback at setup time
|
|
34
|
+
* `onSignalsReady` callback at setup time, and materializes input slots that no
|
|
35
|
+
* composed behavior produces: `user*TrackSelection` (track-switching only reads
|
|
36
|
+
* them). `failedCdns` is owned by `setupFailoverMonitor`, so it's already
|
|
37
|
+
* materialized and reachable on the `onSignalsReady` refs without being listed
|
|
38
|
+
* here.
|
|
27
39
|
*/
|
|
28
|
-
const shareSignals = makeShareSignals(
|
|
40
|
+
const shareSignals = makeShareSignals([
|
|
41
|
+
"userVideoTrackSelection",
|
|
42
|
+
"userAudioTrackSelection",
|
|
43
|
+
"userTextTrackSelection",
|
|
44
|
+
"disableRemotePlayback"
|
|
45
|
+
]);
|
|
29
46
|
/**
|
|
30
47
|
* Create an HLS playback engine.
|
|
31
48
|
*
|
|
@@ -53,34 +70,47 @@ const shareSignals = makeShareSignals();
|
|
|
53
70
|
* ```
|
|
54
71
|
*/
|
|
55
72
|
function createSimpleHlsEngine(config = {}) {
|
|
73
|
+
const deriveStartMediaTime = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;
|
|
56
74
|
const finalConfig = {
|
|
57
75
|
...config,
|
|
76
|
+
deriveStartMediaTime,
|
|
77
|
+
attachMediaSource: attachMediaSourceAsSourceElement,
|
|
78
|
+
canPlayTrack: config.canPlayTrack ?? canPlayTrack,
|
|
58
79
|
resolveTextTrackSegment: config.resolveTextTrackSegment ?? resolveVttSegment,
|
|
80
|
+
textMessagePipelines: relocatingTextPipelines,
|
|
59
81
|
resolveDuration: config.resolveDuration ?? getResolvedSelectedTrackDuration,
|
|
60
82
|
parsePresentation: config.parsePresentation ?? parseMultivariantPlaylist,
|
|
61
83
|
addSubtitlesTracksToMedia: config.addSubtitlesTracksToMedia ?? addSubtitlesTracksToMedia,
|
|
62
84
|
getShowingSubtitlesTrackFromMedia: config.getShowingSubtitlesTrackFromMedia ?? getShowingSubtitlesTrackFromMedia,
|
|
63
|
-
removeAllSubtitlesTracksFromMedia: config.removeAllSubtitlesTracksFromMedia ?? removeAllSubtitlesTracksFromMedia
|
|
85
|
+
removeAllSubtitlesTracksFromMedia: config.removeAllSubtitlesTracksFromMedia ?? removeAllSubtitlesTracksFromMedia,
|
|
86
|
+
videoMessagePipelines: relocationPipelinesFor("video", deriveStartMediaTime),
|
|
87
|
+
audioMessagePipelines: relocationPipelinesFor("audio", deriveStartMediaTime)
|
|
64
88
|
};
|
|
65
89
|
return createComposition([
|
|
66
90
|
syncPreload,
|
|
67
91
|
trackLoadTriggers,
|
|
68
92
|
resolvePresentation,
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
deriveCdnPriority,
|
|
94
|
+
setupFailoverMonitor,
|
|
71
95
|
resolveVideoTrack,
|
|
72
96
|
resolveAudioTrack,
|
|
73
97
|
resolveTextTrack,
|
|
74
98
|
calculatePresentationDuration,
|
|
75
99
|
setupMediaSource,
|
|
76
100
|
updateMediaSourceDuration,
|
|
101
|
+
establishStartMediaTime,
|
|
77
102
|
setupVideoBufferActors,
|
|
78
103
|
setupAudioBufferActors,
|
|
104
|
+
setupAirPlay,
|
|
79
105
|
trackCurrentTime,
|
|
80
|
-
|
|
106
|
+
applyStartPosition,
|
|
107
|
+
switchVideoTrack,
|
|
108
|
+
switchAudioTrack,
|
|
109
|
+
switchTextTrack,
|
|
81
110
|
loadVideoSegments,
|
|
82
111
|
loadAudioSegments,
|
|
83
112
|
endOfStream,
|
|
113
|
+
recoverEndStall,
|
|
84
114
|
syncTextTracks,
|
|
85
115
|
setupTextTrackActors,
|
|
86
116
|
loadTextTrackSegments,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","names":[],"sources":["../../../../../src/playback/engines/hls/engine.ts"],"sourcesContent":["import {\n type Composition,\n type ContextSignals,\n createComposition,\n type StateSignals,\n} from '../../../core/composition/create-composition';\nimport { makeShareSignals, type ShareSignalsConfig } from '../../../core/composition/share-signals';\nimport type { QualityConfig } from '../../../media/abr/quality-selection';\nimport type { BackBufferConfig } from '../../../media/buffer/back-buffer';\nimport type { ForwardBufferConfig } from '../../../media/buffer/forward-buffer';\nimport { resolveVttSegment } from '../../../media/dom/text/resolve-vtt-segment';\nimport {\n addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia,\n} from '../../../media/dom/text/text-track-slots';\nimport { parseMultivariantPlaylist } from '../../../media/hls/parse-multivariant';\nimport type { MaybeResolvedPresentation, VideoTrack } from '../../../media/types';\nimport { getResolvedSelectedTrackDuration } from '../../../media/utils/track-selection';\nimport type { BandwidthConfig, BandwidthState } from '../../../network/bandwidth-estimator';\nimport type { SegmentLoaderActor } from '../../actors/dom/segment-loader';\nimport type { SourceBufferActor } from '../../actors/dom/source-buffer';\nimport type { TextTracksActor } from '../../actors/dom/text-tracks';\nimport type { TextTrackSegmentLoaderActor, TextTrackSegmentResolver } from '../../actors/text-track-segment-loader';\nimport {\n calculatePresentationDuration,\n type PresentationDurationResolver,\n} from '../../behaviors/calculate-presentation-duration';\nimport { endOfStream } from '../../behaviors/dom/end-of-stream';\nimport { loadAudioSegments, loadTextTrackSegments, loadVideoSegments } from '../../behaviors/dom/load-segments';\nimport { setupAudioBufferActors, setupVideoBufferActors } from '../../behaviors/dom/setup-buffer-actors';\nimport { setupMediaSource } from '../../behaviors/dom/setup-mediasource';\nimport { setupTextTrackActors } from '../../behaviors/dom/setup-text-track-actors';\nimport { syncTextTracks } from '../../behaviors/dom/sync-text-tracks';\nimport { trackCurrentTime } from '../../behaviors/dom/track-current-time';\nimport { trackLoadTriggers } from '../../behaviors/dom/track-load-triggers';\nimport { updateMediaSourceDuration } from '../../behaviors/dom/update-mediasource-duration';\nimport { switchVideoQuality } from '../../behaviors/quality-switching';\nimport { type ParsePresentation, resolvePresentation } from '../../behaviors/resolve-presentation';\nimport { resolveAudioTrack, resolveTextTrack, resolveVideoTrack } from '../../behaviors/resolve-track';\nimport { selectAudioTrack, selectTextTrack } from '../../behaviors/select-tracks';\nimport { syncPreload } from '../../behaviors/sync-preload';\n\n// ============================================================================\n// HLS Engine State & Context\n// ============================================================================\n\n/**\n * State shape for the HLS playback engine.\n *\n * This is the union of all state required by the behaviors composed into\n * the HLS engine. Each behavior declares its own state interface; this\n * type satisfies all of them.\n */\nexport interface SimpleHlsEngineState {\n /**\n * The presentation being played. A caller writes `{ url }`;\n * `resolvePresentation` parses the manifest and populates the rest.\n */\n presentation?: MaybeResolvedPresentation;\n preload?: 'auto' | 'metadata' | 'none';\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n bandwidthState?: BandwidthState;\n userVideoTrackSelection?: Partial<VideoTrack>;\n currentTime?: number;\n loadActivated?: boolean;\n}\n\n/**\n * Context shape for the HLS playback engine.\n *\n * Platform objects and actor references managed by HLS behaviors.\n */\nexport interface SimpleHlsEngineContext {\n mediaElement?: HTMLMediaElement | undefined;\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n textTracksActor?: TextTracksActor;\n textTrackSegmentLoaderActor?: TextTrackSegmentLoaderActor;\n}\n\n/**\n * The composition signal refs handed to `onSignalsReady` callers — the\n * canonical way to drive the engine externally (writes) or observe its\n * state (reads) without touching `composition.state` / `composition.context`\n * directly.\n */\nexport type SimpleHlsEngineSignals = {\n state: StateSignals<SimpleHlsEngineState>;\n context: ContextSignals<SimpleHlsEngineContext>;\n};\n\n/**\n * Configuration for the HLS playback engine.\n *\n * Each option is consumed by the appropriate behavior — the engine itself\n * has no config beyond what its behaviors read.\n */\nexport interface SimpleHlsEngineConfig extends ShareSignalsConfig<SimpleHlsEngineState, SimpleHlsEngineContext> {\n /**\n * Bandwidth estimate in bps to use before enough samples have been\n * collected. Default: `DEFAULT_INITIAL_BANDWIDTH` (5 Mbps).\n */\n initialBandwidth?: number;\n preferredAudioLanguage?: string;\n preferredSubtitleLanguage?: string;\n includeForcedTracks?: boolean;\n enableDefaultTrack?: boolean;\n /**\n * Resolver that turns a text-track segment fetch into VTT cues.\n * Defaults to the DOM-bound `resolveVttSegment` resolver, which uses an\n * offscreen `<track>` element to parse WebVTT.\n */\n resolveTextTrackSegment?: TextTrackSegmentResolver<VTTCue>;\n /**\n * Resolver for `presentation.duration`. Defaults to picking the first\n * resolved selected track's duration (video preferred, audio fallback) —\n * appropriate for VoD and audio-only. Live engines should supply a\n * resolver that returns `Number.POSITIVE_INFINITY` once the presentation\n * is established as live; downstream `updateMediaSourceDuration` propagates\n * that value to `mediaSource.duration` per the MSE spec.\n */\n resolveDuration?: PresentationDurationResolver;\n /**\n * Manifest parser handed to `resolvePresentation`. Defaults to the HLS\n * multivariant-playlist parser; supply your own for alternate format\n * support without forking the engine.\n */\n parsePresentation?: ParsePresentation;\n /**\n * Allocate SPF-owned text-track slots on the media element. Defaults to\n * the standard `<track>`-element implementation in\n * `media/dom/text/text-track-slots`.\n */\n addSubtitlesTracksToMedia?: typeof addSubtitlesTracksToMedia;\n /**\n * Return the SPF-owned subtitle/caption `TextTrack` currently in showing\n * mode. Defaults to the standard selector-based implementation in\n * `media/dom/text/text-track-slots`.\n */\n getShowingSubtitlesTrackFromMedia?: typeof getShowingSubtitlesTrackFromMedia;\n /**\n * Evict all SPF-owned text-track slots from the media element. Defaults to\n * the standard selector-based implementation in\n * `media/dom/text/text-track-slots`.\n */\n removeAllSubtitlesTracksFromMedia?: typeof removeAllSubtitlesTracksFromMedia;\n /**\n * Forward-buffer tuning. `bufferDuration` controls how far ahead of the\n * playhead segments are loaded (and where forward-flush kicks in).\n * Defaults: see `DEFAULT_FORWARD_BUFFER_CONFIG` (30 seconds). Threaded to\n * segment-loader actors (v/a + text) at construction time and to\n * `loadXSegments` dispatchers for the load-message range.\n */\n forwardBuffer?: Partial<ForwardBufferConfig>;\n /**\n * Back-buffer tuning. `keepSegments` controls how many segments stay\n * behind the playhead before eviction. Defaults: see\n * `DEFAULT_BACK_BUFFER_CONFIG` (2 segments). Threaded to the v/a\n * segment-loader actor only (text tracks don't use back-buffer eviction).\n */\n backBuffer?: Partial<BackBufferConfig>;\n /**\n * Bandwidth-estimator tuning. Overrides any field of `BandwidthConfig`\n * (`fastHalfLife`, `slowHalfLife`, `minTotalBytes`, `minBytes`,\n * `minDuration`). `bandwidth.minTotalBytes` supersedes the flat\n * `minTotalBytes` field above. Defaults: see `DEFAULT_BANDWIDTH_CONFIG`.\n */\n bandwidth?: Partial<BandwidthConfig>;\n /**\n * Quality-selection tuning. `safetyMargin` is the bandwidth-headroom\n * multiplier used by `selectQuality`; `upgradeMargin` is the hysteresis\n * ratio gating ABR upgrades. Defaults: `DEFAULT_QUALITY_CONFIG` (0.85 / 1.15).\n */\n quality?: Partial<QualityConfig>;\n}\n\n// ============================================================================\n// HLS Playback Engine\n// ============================================================================\n\n/**\n * Generic `shareSignals` instantiated against the HLS engine's full state\n * and context — captures composition signal refs into the consumer's\n * `onSignalsReady` callback at setup time.\n */\nconst shareSignals = makeShareSignals<SimpleHlsEngineState, SimpleHlsEngineContext>();\n\n/**\n * Create an HLS playback engine.\n *\n * Composes SPF behaviors into a reactive pipeline for HLS playback over MSE:\n * manifest resolution, track selection, ABR, segment loading, and\n * end-of-stream coordination.\n *\n * @example\n * ```ts\n * let signals: SimpleHlsEngineSignals;\n * const engine = createSimpleHlsEngine({\n * initialBandwidth: 2_000_000,\n * preferredAudioLanguage: 'en',\n * onSignalsReady: (refs) => {\n * signals = refs;\n * },\n * });\n *\n * signals.context.mediaElement.set(videoEl);\n * signals.state.presentation.set({ url: 'https://example.com/stream.m3u8' });\n *\n * videoEl.play();\n *\n * await engine.destroy();\n * ```\n */\nexport function createSimpleHlsEngine(\n config: SimpleHlsEngineConfig = {}\n): Composition<SimpleHlsEngineState, SimpleHlsEngineContext> {\n const finalConfig = {\n ...config,\n resolveTextTrackSegment: config.resolveTextTrackSegment ?? resolveVttSegment,\n resolveDuration: config.resolveDuration ?? getResolvedSelectedTrackDuration,\n parsePresentation: config.parsePresentation ?? parseMultivariantPlaylist,\n addSubtitlesTracksToMedia: config.addSubtitlesTracksToMedia ?? addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia: config.getShowingSubtitlesTrackFromMedia ?? getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia: config.removeAllSubtitlesTracksFromMedia ?? removeAllSubtitlesTracksFromMedia,\n };\n\n return createComposition(\n [\n syncPreload,\n trackLoadTriggers,\n resolvePresentation,\n\n // Track selection (reads config for initial preferences).\n // Video selection lives in switchVideoQuality, which owns the\n // default-pick + ABR-driven adjustment for selectedVideoTrackId.\n selectAudioTrack,\n selectTextTrack,\n\n // Resolve selected tracks (fetch media playlists)\n resolveVideoTrack,\n resolveAudioTrack,\n resolveTextTrack,\n\n // Presentation duration\n calculatePresentationDuration,\n\n // MSE setup. Video cluster is registered first so that, when both\n // per-type variants flip to `'buffer-ready'` on the shared gate's\n // monitor evaluation, `addSourceBuffer(video)` runs before\n // `addSourceBuffer(audio)` — see the Firefox `mozHasAudio` invariant\n // in setup-buffer-actors.ts.\n setupMediaSource,\n updateMediaSourceDuration,\n setupVideoBufferActors,\n setupAudioBufferActors,\n\n // Playback tracking\n trackCurrentTime,\n switchVideoQuality,\n\n // Segment loading\n loadVideoSegments,\n loadAudioSegments,\n\n // End of stream coordination\n endOfStream,\n\n // Text tracks\n syncTextTracks,\n setupTextTrackActors,\n loadTextTrackSegments,\n\n // Behavior whose sole purpose is to use a callback to allow for signal writing from the outside (e.g. an adapter)\n // NOTE: While not required, adding at the end since behaviors are setup in order, so this increases the likelihood\n // that initial signal setup will have occurred before shareSignals' callback is invoked. (CJP)\n shareSignals,\n ],\n {\n config: finalConfig,\n // Seed bandwidthState so switchVideoQuality fires on initial subscribe\n // with the `initialBandwidth` fallback rather than waiting for the\n // first chunk. The empty sample buffer means `getBandwidthEstimate`\n // returns the configured initial bandwidth until real samples land.\n initialState: {\n bandwidthState: {\n fastEstimate: 0,\n fastTotalWeight: 0,\n slowEstimate: 0,\n slowTotalWeight: 0,\n bytesSampled: 0,\n },\n },\n }\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+LA,MAAM,eAAe,kBAAgE;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BrF,SAAgB,sBACd,SAAgC,EAAE,EACyB;CAC3D,MAAM,cAAc;EAClB,GAAG;EACH,yBAAyB,OAAO,2BAA2B;EAC3D,iBAAiB,OAAO,mBAAmB;EAC3C,mBAAmB,OAAO,qBAAqB;EAC/C,2BAA2B,OAAO,6BAA6B;EAC/D,mCAAmC,OAAO,qCAAqC;EAC/E,mCAAmC,OAAO,qCAAqC;EAChF;AAED,QAAO,kBACL;EACE;EACA;EACA;EAKA;EACA;EAGA;EACA;EACA;EAGA;EAOA;EACA;EACA;EACA;EAGA;EACA;EAGA;EACA;EAGA;EAGA;EACA;EACA;EAKA;EACD,EACD;EACE,QAAQ;EAKR,cAAc,EACZ,gBAAgB;GACd,cAAc;GACd,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,cAAc;GACf,EACF;EACF,CACF"}
|
|
1
|
+
{"version":3,"file":"engine.js","names":[],"sources":["../../../../../src/playback/engines/hls/engine.ts"],"sourcesContent":["import {\n type Composition,\n type ContextSignals,\n createComposition,\n type StateSignals,\n} from '../../../core/composition/create-composition';\nimport { makeShareSignals, type ShareSignalsConfig } from '../../../core/composition/share-signals';\nimport type { QualityConfig } from '../../../media/abr/quality-selection';\nimport type { BackBufferConfig } from '../../../media/buffer/back-buffer';\nimport type { ForwardBufferConfig } from '../../../media/buffer/forward-buffer';\nimport { canPlayTrack } from '../../../media/dom/capabilities';\nimport { attachMediaSourceAsSourceElement } from '../../../media/dom/mse/mediasource-setup';\nimport { resolveVttSegment } from '../../../media/dom/text/resolve-vtt-segment';\nimport {\n addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia,\n} from '../../../media/dom/text/text-track-slots';\nimport { parseMultivariantPlaylist } from '../../../media/hls/parse-multivariant';\nimport type {\n AudioTrack,\n CanPlayTrack,\n MaybeResolvedPresentation,\n MediaContainerData,\n TextTrack,\n VideoTrack,\n} from '../../../media/types';\nimport type { GetCdnId } from '../../../media/utils/cdn';\nimport { getResolvedSelectedTrackDuration } from '../../../media/utils/track-selection';\nimport type { BandwidthConfig, BandwidthState } from '../../../network/bandwidth-estimator';\nimport type { SegmentLoaderActor } from '../../actors/dom/segment-loader';\nimport type { SourceBufferActor } from '../../actors/dom/source-buffer';\nimport type { TextTracksActor } from '../../actors/dom/text-tracks';\nimport type { TextTrackSegmentLoaderActor } from '../../actors/text-track-segment-loader';\nimport {\n calculatePresentationDuration,\n type PresentationDurationResolver,\n} from '../../behaviors/calculate-presentation-duration';\nimport { deriveCdnPriority } from '../../behaviors/derive-cdn-priority';\nimport { setupAirPlay } from '../../behaviors/dom/airplay';\nimport { applyStartPosition } from '../../behaviors/dom/apply-start-position';\nimport { endOfStream } from '../../behaviors/dom/end-of-stream';\nimport { loadAudioSegments, loadTextTrackSegments, loadVideoSegments } from '../../behaviors/dom/load-segments';\nimport { recoverEndStall } from '../../behaviors/dom/recover-end-stall';\nimport { setupAudioBufferActors, setupVideoBufferActors } from '../../behaviors/dom/setup-buffer-actors';\nimport { setupMediaSource } from '../../behaviors/dom/setup-mediasource';\nimport { setupTextTrackActors } from '../../behaviors/dom/setup-text-track-actors';\nimport { syncTextTracks } from '../../behaviors/dom/sync-text-tracks';\nimport { trackCurrentTime } from '../../behaviors/dom/track-current-time';\nimport { trackLoadTriggers } from '../../behaviors/dom/track-load-triggers';\nimport { updateMediaSourceDuration } from '../../behaviors/dom/update-mediasource-duration';\n// Non-zero-PTS relocation (spike): remove this import, the composed reactor, the\n// `video/audio/textMessagePipelines` finalConfig entries, the `mediaContainerData`\n// state slot, and the `deriveStartMediaTime` config field to drop relocation entirely\n// (text then falls back to the plain `resolveVttSegment` resolver).\nimport {\n type DeriveStartMediaTime,\n deriveSharedMinStartMediaTime,\n establishStartMediaTime,\n} from '../../behaviors/establish-start-media-time';\nimport { type ParsePresentation, resolvePresentation } from '../../behaviors/resolve-presentation';\nimport { resolveAudioTrack, resolveTextTrack, resolveVideoTrack } from '../../behaviors/resolve-track';\nimport { type FailoverMonitorConfig, setupFailoverMonitor } from '../../behaviors/setup-failover-monitor';\nimport { syncPreload } from '../../behaviors/sync-preload';\nimport { switchAudioTrack, switchTextTrack, switchVideoTrack } from '../../behaviors/track-switching';\nimport { relocatingTextPipelines, relocationPipelinesFor } from '../../primitives/relocation-pipelines';\nimport type { TextTrackSegmentResolver } from '../../primitives/text-segment-load-pipeline';\n\n// ============================================================================\n// HLS Engine State & Context\n// ============================================================================\n\n/**\n * State shape for the HLS playback engine.\n *\n * This is the union of all state required by the behaviors composed into\n * the HLS engine. Each behavior declares its own state interface; this\n * type satisfies all of them.\n */\nexport interface SimpleHlsEngineState {\n /**\n * The presentation being played. A caller writes `{ url }`;\n * `resolvePresentation` parses the manifest and populates the rest.\n */\n presentation?: MaybeResolvedPresentation;\n preload?: 'auto' | 'metadata' | 'none';\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n bandwidthState?: BandwidthState;\n // Non-zero-PTS relocation (spike): transient per-track container data owned by\n // `establishStartMediaTime`. Remove with the composed reactor.\n mediaContainerData?: Record<string, MediaContainerData>;\n userVideoTrackSelection?: Partial<VideoTrack>;\n /**\n * Consumer-driven constraint narrowing the audio candidate set. Sibling\n * of `userVideoTrackSelection`. Partial-track shape — `{ language: 'es' }`,\n * `{ id: 'audio-en' }`, etc. `selectAudioTrack` reads this and re-picks\n * when it changes. Multi-language-audio Tier 2 programmatic-write path.\n */\n userAudioTrackSelection?: Partial<AudioTrack>;\n /**\n * Consumer-driven *intent* for text selection, resolved into\n * `selectedTextTrackId` by `switchTextTrack`. A language-based partial\n * (`{ language: 'es' }`) selects captions, `'off'` disables them, and absence\n * means auto (the engine's `preferredSubtitleLanguage` / DEFAULT-track policy).\n * Also the write path for the DOM caption UI (via `syncTextTracks`); unlike the\n * resolved id it persists across source changes (sticky preference).\n */\n userTextTrackSelection?: Partial<TextTrack> | 'off';\n /**\n * The CDNs the source is served from (track-URL origins), in manifest\n * priority order — most-preferred first (mirrors HLS content steering's\n * `PATHWAY-PRIORITY`). Owned by `deriveCdnPriority`, read by\n * `track-switching`'s `preferActiveCdn` scope, which narrows to the\n * highest-priority CDN with surviving tracks so video / audio / text stay on\n * one host. Only meaningful for redundant-stream sources; a single-CDN source\n * has one entry.\n */\n cdnPriority?: string[];\n /**\n * CDN ids (origins) currently in failover cooldown — written by the CDN\n * monitor when a host fails too often, read by `track-switching`'s\n * `excludeFailedCdns` hard constraint, which prunes their tracks so the\n * active-CDN scope falls to the next CDN in `cdnPriority`. Empty / absent\n * means all CDNs are eligible.\n */\n failedCdns?: string[];\n currentTime?: number;\n loadActivated?: boolean;\n /**\n * One-shot command: start the current source at this position\n * (presentation-timeline seconds). Written by consumers or by\n * `setupAirPlay`'s session-end snapshot; consumed (cleared) by\n * `applyStartPosition` once the element seeks. See\n * `behaviors/dom/apply-start-position.ts`.\n */\n startPosition?: number;\n /**\n * Intent-level loading policy: initiate no new loading work while `true`.\n * Written by `setupAirPlay` (the only behavior declaring the key) while a\n * remote-playback session owns presentation; observed by the\n * `loadXSegments` dispatchers (park in `'dormant'`) and by\n * `setupMediaSource` (a pending rebuild waits). See\n * `SegmentLoadingState['loadingSuspended']`.\n */\n loadingSuspended?: boolean;\n /**\n * Author intent for the AirPlay/remote-playback picker, written by the media\n * adapter's `disableRemotePlayback` IDL property. `true` is an explicit\n * opt-out: `setupAirPlay` reads it at attach and sets nothing up, leaving the\n * element's remote playback disabled. Distinct from the underlying\n * `<video>.disableRemotePlayback`, which stays programmatically managed\n * (ManagedMediaSource / AirPlay).\n */\n disableRemotePlayback?: boolean;\n}\n\n/**\n * Context shape for the HLS playback engine.\n *\n * Platform objects and actor references managed by HLS behaviors.\n */\nexport interface SimpleHlsEngineContext {\n mediaElement?: HTMLMediaElement | undefined;\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n textTracksActor?: TextTracksActor;\n textTrackSegmentLoaderActor?: TextTrackSegmentLoaderActor;\n}\n\n/**\n * The composition signal refs handed to `onSignalsReady` callers — the\n * canonical way to drive the engine externally (writes) or observe its\n * state (reads) without touching `composition.state` / `composition.context`\n * directly.\n */\nexport type SimpleHlsEngineSignals = {\n state: StateSignals<SimpleHlsEngineState>;\n context: ContextSignals<SimpleHlsEngineContext>;\n};\n\n/**\n * Configuration for the HLS playback engine.\n *\n * Each option is consumed by the appropriate behavior — the engine itself\n * has no config beyond what its behaviors read.\n */\nexport interface SimpleHlsEngineConfig extends ShareSignalsConfig<SimpleHlsEngineState, SimpleHlsEngineContext> {\n /**\n * Bandwidth estimate in bps to use before enough samples have been\n * collected. Default: `DEFAULT_INITIAL_BANDWIDTH` (5 Mbps).\n */\n initialBandwidth?: number;\n /**\n * Codec capability probe injected into `track-switching`'s\n * `excludeUnplayableTracks` constraint — drops renditions the environment\n * can't decode before selection. Defaults to the `MediaSource.isTypeSupported`\n * -backed `canPlayTrack`; supply your own to override (e.g. force-exclude a\n * codec).\n */\n canPlayTrack?: CanPlayTrack;\n preferredAudioLanguage?: string;\n preferredSubtitleLanguage?: string;\n includeForcedTracks?: boolean;\n enableDefaultTrack?: boolean;\n /**\n * Resolver that turns a text-track segment fetch into VTT cues.\n * Defaults to the DOM-bound `resolveVttSegment` resolver, which uses an\n * offscreen `<track>` element to parse WebVTT.\n */\n resolveTextTrackSegment?: TextTrackSegmentResolver<VTTCue>;\n /**\n * Resolver for `presentation.duration`. Defaults to picking the first\n * resolved selected track's duration (video preferred, audio fallback) —\n * appropriate for VoD and audio-only. Live engines should supply a\n * resolver that returns `Number.POSITIVE_INFINITY` once the presentation\n * is established as live; downstream `updateMediaSourceDuration` propagates\n * that value to `mediaSource.duration` per the MSE spec.\n */\n resolveDuration?: PresentationDurationResolver;\n /**\n * Manifest parser handed to `resolvePresentation`. Defaults to the HLS\n * multivariant-playlist parser; supply your own for alternate format\n * support without forking the engine.\n */\n parsePresentation?: ParsePresentation;\n /**\n * Allocate SPF-owned text-track slots on the media element. Defaults to\n * the standard `<track>`-element implementation in\n * `media/dom/text/text-track-slots`.\n */\n addSubtitlesTracksToMedia?: typeof addSubtitlesTracksToMedia;\n /**\n * Return the SPF-owned subtitle/caption `TextTrack` currently in showing\n * mode. Defaults to the standard selector-based implementation in\n * `media/dom/text/text-track-slots`.\n */\n getShowingSubtitlesTrackFromMedia?: typeof getShowingSubtitlesTrackFromMedia;\n /**\n * Evict all SPF-owned text-track slots from the media element. Defaults to\n * the standard selector-based implementation in\n * `media/dom/text/text-track-slots`.\n */\n removeAllSubtitlesTracksFromMedia?: typeof removeAllSubtitlesTracksFromMedia;\n /**\n * Forward-buffer tuning. `bufferDuration` controls how far ahead of the\n * playhead segments are loaded (and where forward-flush kicks in).\n * Defaults: see `DEFAULT_FORWARD_BUFFER_CONFIG` (30 seconds). Threaded to\n * segment-loader actors (v/a + text) at construction time and to\n * `loadXSegments` dispatchers for the load-message range.\n */\n forwardBuffer?: Partial<ForwardBufferConfig>;\n /**\n * Back-buffer tuning. `keepSegments` controls how many segments stay\n * behind the playhead before eviction. Defaults: see\n * `DEFAULT_BACK_BUFFER_CONFIG` (2 segments). Threaded to the v/a\n * segment-loader actor only (text tracks don't use back-buffer eviction).\n */\n backBuffer?: Partial<BackBufferConfig>;\n /**\n * Bandwidth-estimator tuning. Overrides any field of `BandwidthConfig`\n * (`fastHalfLife`, `slowHalfLife`, `minTotalBytes`, `minBytes`,\n * `minDuration`). `bandwidth.minTotalBytes` supersedes the flat\n * `minTotalBytes` field above. Defaults: see `DEFAULT_BANDWIDTH_CONFIG`.\n */\n bandwidth?: Partial<BandwidthConfig>;\n /**\n * Quality-selection tuning. `safetyMargin` is the bandwidth-headroom\n * multiplier used by `selectQuality`; `upgradeMargin` is the hysteresis\n * ratio gating ABR upgrades. Defaults: `DEFAULT_QUALITY_CONFIG` (0.85 / 1.15).\n */\n quality?: Partial<QualityConfig>;\n /**\n * Multi-CDN failover monitor tuning. `cooldownMs` is how long a CDN stays\n * excluded after a failed fetch trips it. Defaults:\n * `DEFAULT_FAILOVER_MONITOR_CONFIG` (300s). Only meaningful for redundant-stream\n * sources.\n */\n failover?: Partial<FailoverMonitorConfig>;\n /**\n * How to derive a CDN grouping key from a track URL — used to build\n * `cdnPriority`, to record the failover trip in `failedCdns`, and by the\n * track-switching CDN scope + failover constraint. One function, read by all of\n * them, so the keys stay comparable. Defaults to the URL origin; override to\n * key on something else (e.g. Mux's `cdn=` query param).\n */\n getCdnId?: GetCdnId;\n /**\n * Non-zero-PTS relocation (spike): the reduce seam consumed by the\n * `establishStartMediaTime` reactor. Defaults to per-track own origin (Tier 1);\n * a Tier-2 variant returns the shared `min` across selected A/V. Relocation is\n * composed into the standard engine below — see the marked block — so this only\n * needs setting to swap the tier policy. See\n * `internal/design/spf/presentation-timeline-model.md`.\n */\n deriveStartMediaTime?: DeriveStartMediaTime;\n /**\n * Proximity window (seconds) for the `recoverEndStall` behavior — how close the\n * playhead must be to the reachable buffered end for a `waiting` to be treated as the\n * end-of-stream freeze and nudged to `ended`. Defaults to `0.2`. See\n * `behaviors/dom/recover-end-stall`.\n */\n endStallNudgeWindow?: number;\n}\n\n// ============================================================================\n// HLS Playback Engine\n// ============================================================================\n\n/**\n * Generic `shareSignals` instantiated against the HLS engine's full state\n * and context — captures composition signal refs into the consumer's\n * `onSignalsReady` callback at setup time, and materializes input slots that no\n * composed behavior produces: `user*TrackSelection` (track-switching only reads\n * them). `failedCdns` is owned by `setupFailoverMonitor`, so it's already\n * materialized and reachable on the `onSignalsReady` refs without being listed\n * here.\n */\nconst shareSignals = makeShareSignals<SimpleHlsEngineState, SimpleHlsEngineContext>([\n 'userVideoTrackSelection',\n 'userAudioTrackSelection',\n 'userTextTrackSelection',\n 'disableRemotePlayback',\n]);\n\n/**\n * Create an HLS playback engine.\n *\n * Composes SPF behaviors into a reactive pipeline for HLS playback over MSE:\n * manifest resolution, track selection, ABR, segment loading, and\n * end-of-stream coordination.\n *\n * @example\n * ```ts\n * let signals: SimpleHlsEngineSignals;\n * const engine = createSimpleHlsEngine({\n * initialBandwidth: 2_000_000,\n * preferredAudioLanguage: 'en',\n * onSignalsReady: (refs) => {\n * signals = refs;\n * },\n * });\n *\n * signals.context.mediaElement.set(videoEl);\n * signals.state.presentation.set({ url: 'https://example.com/stream.m3u8' });\n *\n * videoEl.play();\n *\n * await engine.destroy();\n * ```\n */\nexport function createSimpleHlsEngine(\n config: SimpleHlsEngineConfig = {}\n): Composition<SimpleHlsEngineState, SimpleHlsEngineContext> {\n // Non-zero-PTS relocation (spike): resolve the coordination seam once so the reactor\n // (model `startMediaTime`) and the loader stamps (buffer `timestampOffset`) apply the\n // SAME derive. Default is shared-`min` across selected A/V (subsumes per-type).\n const deriveStartMediaTime = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;\n const finalConfig = {\n ...config,\n deriveStartMediaTime,\n // Baked (not user-overridable): this engine composes `setupAirPlay`,\n // whose native fallback `<source>` requires the MSE attachment to keep\n // sibling source alternatives part of resource selection.\n attachMediaSource: attachMediaSourceAsSourceElement,\n canPlayTrack: config.canPlayTrack ?? canPlayTrack,\n resolveTextTrackSegment: config.resolveTextTrackSegment ?? resolveVttSegment,\n // Non-zero-PTS relocation (spike): the text pipeline rebases cues onto the\n // relocated 0-based timeline. Remove `textMessagePipelines` to drop text relocation.\n textMessagePipelines: relocatingTextPipelines,\n resolveDuration: config.resolveDuration ?? getResolvedSelectedTrackDuration,\n parsePresentation: config.parsePresentation ?? parseMultivariantPlaylist,\n addSubtitlesTracksToMedia: config.addSubtitlesTracksToMedia ?? addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia: config.getShowingSubtitlesTrackFromMedia ?? getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia: config.removeAllSubtitlesTracksFromMedia ?? removeAllSubtitlesTracksFromMedia,\n // Non-zero-PTS relocation (spike): the discover/stamp steps `establishStartMediaTime`\n // pairs with. They apply the same `deriveStartMediaTime` seam as the reactor. Remove\n // these two lines with the reactor.\n videoMessagePipelines: relocationPipelinesFor('video', deriveStartMediaTime),\n audioMessagePipelines: relocationPipelinesFor('audio', deriveStartMediaTime),\n };\n\n return createComposition(\n [\n syncPreload,\n trackLoadTriggers,\n resolvePresentation,\n\n // Session-level CDN priority for redundant-stream sources. Owns\n // `cdnPriority`; `track-switching`'s preferActiveCdn scope reads it so\n // every type stays on one CDN. No-op for single-CDN sources.\n //\n // Placed before switch* so `cdnPriority` is set before the first pick —\n // but this ordering is only *mildly* load-bearing, not required for\n // correctness. Selection is reactive: a late `cdnPriority` re-fires the\n // pick and converges on the same result (see the late-arrival test in\n // track-switching.test.ts). Order affects only a transient, and only for\n // an *asymmetric* manifest (a type listing a non-primary CDN first):\n // composing this after switch* would let that type fire one wasted\n // media-playlist fetch to the wrong CDN before correcting. Symmetric\n // redundant streams (the norm) never hit it — the first-listed CDN is\n // already the primary we'd pick anyway.\n deriveCdnPriority,\n\n // CDN failover cooldown: owns the expiry half of failover — watches\n // `failedCdns` (tripped directly by track resolution on a failed\n // media-playlist fetch) and removes each CDN once its cooldown lapses.\n setupFailoverMonitor,\n\n // Resolve selected tracks (fetch media playlists). Composed before the\n // switch* slot owners; selection is reactive, so a resolve* re-fires once\n // its switch* sets the id (same convergence for all three types).\n resolveVideoTrack,\n resolveAudioTrack,\n resolveTextTrack,\n\n // Presentation duration\n calculatePresentationDuration,\n\n // MSE setup. Video cluster is registered first so that, when both\n // per-type variants flip to `'buffer-ready'` on the shared gate's\n // monitor evaluation, `addSourceBuffer(video)` runs before\n // `addSourceBuffer(audio)` — see the Firefox `mozHasAudio` invariant\n // in setup-buffer-actors.ts.\n setupMediaSource,\n updateMediaSourceDuration,\n\n // ── Non-zero-PTS relocation (spike) ──────────────────────────────────\n // Establishes per-track `startMediaTime` and publishes the relocating\n // segment-loader pipelines to context. MUST precede `setup*BufferActors`\n // so the pipelines are published before the loaders read them. Remove this\n // one line (+ the import, the `mediaContainerData`/`*MessagePipelines`\n // slots including `textMessagePipelines`, and the `deriveStartMediaTime`\n // config) to drop relocation and test the Tier-0 baseline / bundle size.\n establishStartMediaTime,\n // ─────────────────────────────────────────────────────────────────────\n\n setupVideoBufferActors,\n setupAudioBufferActors,\n\n // AirPlay/MSE bridge (WebKit only; no-op elsewhere).\n setupAirPlay,\n\n // Playback tracking\n trackCurrentTime,\n // After trackCurrentTime: the one-shot currentTime seed must land after\n // the mirror's attach-time sync (see apply-start-position.ts).\n applyStartPosition,\n switchVideoTrack,\n switchAudioTrack,\n // Mid-stream audio-buffer flush on language switch is handled in\n // `segment-loader`'s `planTasks` (predicate: language differs from\n // the previously-buffered track) — not in switchAudioTrack itself.\n\n // Text selection: resolves `userTextTrackSelection` intent (incl. 'off',\n // or the configured preferred-language / DEFAULT-track policy) against the\n // failed-CDN-pruned, active-CDN-scoped text renditions. Optional selection\n // (captions are opt-in), so it can resolve to none.\n switchTextTrack,\n\n // Segment loading\n loadVideoSegments,\n loadAudioSegments,\n\n // End of stream coordination\n endOfStream,\n // Force native `ended` when Chrome freezes the playhead a few frames short of a\n // skewed-A/V end after `endOfStream` (audio-clock stall). Inert otherwise.\n recoverEndStall,\n\n // Text tracks\n syncTextTracks,\n setupTextTrackActors,\n loadTextTrackSegments,\n\n // Behavior whose sole purpose is to use a callback to allow for signal writing from the outside (e.g. an adapter)\n // NOTE: While not required, adding at the end since behaviors are setup in order, so this increases the likelihood\n // that initial signal setup will have occurred before shareSignals' callback is invoked. (CJP)\n shareSignals,\n ],\n {\n config: finalConfig,\n // Seed bandwidthState so switchVideoTrack fires on initial subscribe\n // with the `initialBandwidth` fallback rather than waiting for the\n // first chunk. The empty sample buffer means `getBandwidthEstimate`\n // returns the configured initial bandwidth until real samples land.\n initialState: {\n bandwidthState: {\n fastEstimate: 0,\n fastTotalWeight: 0,\n slowEstimate: 0,\n slowTotalWeight: 0,\n bytesSampled: 0,\n },\n },\n }\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkUA,MAAM,eAAe,iBAA+D;CAClF;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BD,SAAgB,sBACd,SAAgC,CAAC,GAC0B;CAI3D,MAAM,uBAAuB,OAAO,wBAAwB;CAC5D,MAAM,cAAc;EAClB,GAAG;EACH;EAIA,mBAAmB;EACnB,cAAc,OAAO,gBAAgB;EACrC,yBAAyB,OAAO,2BAA2B;EAG3D,sBAAsB;EACtB,iBAAiB,OAAO,mBAAmB;EAC3C,mBAAmB,OAAO,qBAAqB;EAC/C,2BAA2B,OAAO,6BAA6B;EAC/D,mCAAmC,OAAO,qCAAqC;EAC/E,mCAAmC,OAAO,qCAAqC;EAI/E,uBAAuB,uBAAuB,SAAS,oBAAoB;EAC3E,uBAAuB,uBAAuB,SAAS,oBAAoB;CAC7E;CAEA,OAAO,kBACL;EACE;EACA;EACA;EAgBA;EAKA;EAKA;EACA;EACA;EAGA;EAOA;EACA;EASA;EAGA;EACA;EAGA;EAGA;EAGA;EACA;EACA;EASA;EAGA;EACA;EAGA;EAGA;EAGA;EACA;EACA;EAKA;CACF,GACA;EACE,QAAQ;EAKR,cAAc,EACZ,gBAAgB;GACd,cAAc;GACd,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,cAAc;EAChB,EACF;CACF,CACF;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MediaContainerData } from "../../media/types/index.js";
|
|
2
|
+
//#region src/playback/primitives/derive-start-media-time.d.ts
|
|
3
|
+
/** The selected v/a track ids a {@link DeriveStartMediaTime} may coordinate across. */
|
|
4
|
+
interface DeriveStartMediaTimeContext {
|
|
5
|
+
selectedVideoTrackId?: string;
|
|
6
|
+
selectedAudioTrackId?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reduce the discovered {@link MediaContainerData} (keyed by track type) into each type's
|
|
10
|
+
* `startMediaTime` origin; `undefined` means "not ready yet". Pure and injected — the single
|
|
11
|
+
* coordination seam the reactor and the loader stamp share.
|
|
12
|
+
*/
|
|
13
|
+
type DeriveStartMediaTime = (containerData: Record<string, MediaContainerData>, ctx: DeriveStartMediaTimeContext) => Record<string, number | undefined>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { DeriveStartMediaTime, DeriveStartMediaTimeContext };
|
|
16
|
+
//# sourceMappingURL=derive-start-media-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive-start-media-time.d.ts","names":[],"sources":["../../../../src/playback/primitives/derive-start-media-time.ts"],"mappings":";;;UAWiB;EACf;EACA;;;;;;;KAQU,wBACV,eAAe,eAAe,qBAC9B,KAAK,gCACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { findTrackById } from "../../media/utils/tracks.js";
|
|
3
|
+
import { addFailedCdn, getCdnId } from "../../media/utils/cdn.js";
|
|
4
|
+
//#region src/playback/primitives/failover-fetch.ts
|
|
5
|
+
/**
|
|
6
|
+
* Decorate a fetch so a failed request trips the **selected track's** CDN into
|
|
7
|
+
* `failedCdns`. The decorated fetch's type is preserved, so this wraps both
|
|
8
|
+
* `resolve-track`'s playlist `FetchText` and the segment loaders' `FetchBytes`.
|
|
9
|
+
*
|
|
10
|
+
* The CDN id comes from the selected track's media-playlist URL, never the
|
|
11
|
+
* failed addressable: a segment URL resolves relative to its playlist and, per
|
|
12
|
+
* RFC 3986, drops the playlist's query string (`…/r.m3u8?cdn=fastly` → `…/0.ts`),
|
|
13
|
+
* so a query-keyed `getCdnId` (e.g. Mux's `cdn=`) keyed on it would derive an id
|
|
14
|
+
* that never matches the ones `deriveCdnPriority` / track-switching build from
|
|
15
|
+
* `track.url`. The in-flight fetch belongs to the selected track — a source or
|
|
16
|
+
* track switch aborts it, and aborts don't trip — so the selected track is the
|
|
17
|
+
* right CDN to fail over. For `resolve-track` the resolving track *is* the
|
|
18
|
+
* selected track, so this is identical to keying on its addressable.
|
|
19
|
+
*
|
|
20
|
+
* No-op when no failover monitor is composed (it owns the signal) or the
|
|
21
|
+
* selected track can't be located.
|
|
22
|
+
*/
|
|
23
|
+
function failoverFetch(baseFetch, state, config) {
|
|
24
|
+
const getCdnId$1 = config.getCdnId ?? getCdnId;
|
|
25
|
+
return (async (addressable, options) => {
|
|
26
|
+
try {
|
|
27
|
+
return await baseFetch(addressable, options);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (!options?.signal?.aborted && state.failedCdns) {
|
|
30
|
+
const presentation = state.presentation.get();
|
|
31
|
+
const trackId = state[config.selectedKey].get();
|
|
32
|
+
const track = presentation && trackId ? findTrackById(presentation, trackId) : void 0;
|
|
33
|
+
if (track) update(state.failedCdns, (cdns) => addFailedCdn(cdns, getCdnId$1(track.url)));
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { failoverFetch };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=failover-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover-fetch.js","names":["getCdnId","defaultGetCdnId"],"sources":["../../../../src/playback/primitives/failover-fetch.ts"],"sourcesContent":["import { type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../media/types';\nimport { addFailedCdn, getCdnId as defaultGetCdnId, type GetCdnId } from '../../media/utils/cdn';\nimport { findTrackById } from '../../media/utils/tracks';\nimport type { FetchOptions, Resource } from '../../network/fetch';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\n/**\n * State a failover-decorated fetch reads: the presentation, the per-type\n * selected-track slot, and the failover monitor's `failedCdns`.\n *\n * `failedCdns` is *optional* — the failover monitor owns that slot, so a\n * behavior's narrow state is assignable here without the behavior declaring it\n * (and the intersection shares keys, so it isn't a weak type). When no monitor\n * is composed the slot is absent and tracking no-ops.\n */\ntype FailoverState<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n failedCdns?: Signal<string[] | undefined>;\n} & { [P in K]: ReadonlySignal<string | undefined> };\n\n/** Any `Resource`-addressable fetch — both `FetchText` and `FetchBytes` qualify. */\ntype FailoverableFetch = (addressable: Resource, options?: FetchOptions) => Promise<unknown>;\n\n/**\n * Decorate a fetch so a failed request trips the **selected track's** CDN into\n * `failedCdns`. The decorated fetch's type is preserved, so this wraps both\n * `resolve-track`'s playlist `FetchText` and the segment loaders' `FetchBytes`.\n *\n * The CDN id comes from the selected track's media-playlist URL, never the\n * failed addressable: a segment URL resolves relative to its playlist and, per\n * RFC 3986, drops the playlist's query string (`…/r.m3u8?cdn=fastly` → `…/0.ts`),\n * so a query-keyed `getCdnId` (e.g. Mux's `cdn=`) keyed on it would derive an id\n * that never matches the ones `deriveCdnPriority` / track-switching build from\n * `track.url`. The in-flight fetch belongs to the selected track — a source or\n * track switch aborts it, and aborts don't trip — so the selected track is the\n * right CDN to fail over. For `resolve-track` the resolving track *is* the\n * selected track, so this is identical to keying on its addressable.\n *\n * No-op when no failover monitor is composed (it owns the signal) or the\n * selected track can't be located.\n */\nexport function failoverFetch<K extends SelectedTrackKey, Fetch extends FailoverableFetch>(\n baseFetch: Fetch,\n state: FailoverState<K>,\n config: { selectedKey: K; getCdnId?: GetCdnId }\n): Fetch {\n const getCdnId = config.getCdnId ?? defaultGetCdnId;\n return (async (addressable: Resource, options?: FetchOptions) => {\n try {\n return await baseFetch(addressable, options);\n } catch (error) {\n if (!options?.signal?.aborted && state.failedCdns) {\n const presentation = state.presentation.get();\n const trackId = state[config.selectedKey].get();\n const track = presentation && trackId ? findTrackById(presentation, trackId) : undefined;\n if (track) update(state.failedCdns, (cdns) => addFailedCdn(cdns, getCdnId(track.url)));\n }\n throw error;\n }\n }) as Fetch;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,cACd,WACA,OACA,QACO;CACP,MAAMA,aAAW,OAAO,YAAYC;CACpC,QAAQ,OAAO,aAAuB,YAA2B;EAC/D,IAAI;GACF,OAAO,MAAM,UAAU,aAAa,OAAO;EAC7C,SAAS,OAAO;GACd,IAAI,CAAC,SAAS,QAAQ,WAAW,MAAM,YAAY;IACjD,MAAM,eAAe,MAAM,aAAa,IAAI;IAC5C,MAAM,UAAU,MAAM,OAAO,YAAY,CAAC,IAAI;IAC9C,MAAM,QAAQ,gBAAgB,UAAU,cAAc,cAAc,OAAO,IAAI,KAAA;IAC/E,IAAI,OAAO,OAAO,MAAM,aAAa,SAAS,aAAa,MAAMD,WAAS,MAAM,GAAG,CAAC,CAAC;GACvF;GACA,MAAM;EACR;CACF;AACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/playback/primitives/head-peek.ts
|
|
2
|
+
/**
|
|
3
|
+
* Eager head-of-stream peek. Pulls chunks from an async byte stream only until
|
|
4
|
+
* `tryParse` signals it has read what it needs (or the stream ends), then returns
|
|
5
|
+
* a stream that re-emits the pulled head followed by the untouched tail — so
|
|
6
|
+
* reading a head-of-stream mp4 box (`mdhd` / `tfdt`) doesn't break streaming of
|
|
7
|
+
* the append. Once the caller has what it wants, the rest streams as normal.
|
|
8
|
+
*/
|
|
9
|
+
function concat(chunks) {
|
|
10
|
+
if (chunks.length === 1) return chunks[0];
|
|
11
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
12
|
+
const out = new Uint8Array(total);
|
|
13
|
+
let offset = 0;
|
|
14
|
+
for (const chunk of chunks) {
|
|
15
|
+
out.set(chunk, offset);
|
|
16
|
+
offset += chunk.length;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
/** Re-emit the eagerly-pulled head chunks, then stream the untouched tail. */
|
|
21
|
+
async function* reassemble(head, tail) {
|
|
22
|
+
yield* head;
|
|
23
|
+
for (let next = await tail.next(); !next.done; next = await tail.next()) yield next.value;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pull chunks until `tryParse(accumulatedHead)` returns `true` — it found and read
|
|
27
|
+
* its box — or the stream ends, then return a stream re-emitting the pulled head
|
|
28
|
+
* followed by the untouched tail. `tryParse` is called with the growing head after
|
|
29
|
+
* each chunk; it performs the side effect (publishing the parsed value) and returns
|
|
30
|
+
* whether it's done.
|
|
31
|
+
*/
|
|
32
|
+
async function peekHead(data, tryParse) {
|
|
33
|
+
const iterator = data[Symbol.asyncIterator]();
|
|
34
|
+
const head = [];
|
|
35
|
+
for (let next = await iterator.next(); !next.done; next = await iterator.next()) {
|
|
36
|
+
head.push(next.value);
|
|
37
|
+
if (tryParse(concat(head))) break;
|
|
38
|
+
}
|
|
39
|
+
return reassemble(head, iterator);
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { peekHead };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=head-peek.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"head-peek.js","names":[],"sources":["../../../../src/playback/primitives/head-peek.ts"],"sourcesContent":["/**\n * Eager head-of-stream peek. Pulls chunks from an async byte stream only until\n * `tryParse` signals it has read what it needs (or the stream ends), then returns\n * a stream that re-emits the pulled head followed by the untouched tail — so\n * reading a head-of-stream mp4 box (`mdhd` / `tfdt`) doesn't break streaming of\n * the append. Once the caller has what it wants, the rest streams as normal.\n */\n\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n if (chunks.length === 1) return chunks[0]!;\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n return out;\n}\n\n/** Re-emit the eagerly-pulled head chunks, then stream the untouched tail. */\nasync function* reassemble(head: Uint8Array[], tail: AsyncIterator<Uint8Array>): AsyncIterable<Uint8Array> {\n yield* head;\n for (let next = await tail.next(); !next.done; next = await tail.next()) yield next.value;\n}\n\n/**\n * Pull chunks until `tryParse(accumulatedHead)` returns `true` — it found and read\n * its box — or the stream ends, then return a stream re-emitting the pulled head\n * followed by the untouched tail. `tryParse` is called with the growing head after\n * each chunk; it performs the side effect (publishing the parsed value) and returns\n * whether it's done.\n */\nexport async function peekHead(\n data: AsyncIterable<Uint8Array>,\n tryParse: (bytes: Uint8Array) => boolean\n): Promise<AsyncIterable<Uint8Array>> {\n const iterator = data[Symbol.asyncIterator]();\n const head: Uint8Array[] = [];\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n head.push(next.value);\n if (tryParse(concat(head))) break;\n }\n return reassemble(head, iterator);\n}\n"],"mappings":";;;;;;;;AAQA,SAAS,OAAO,QAAkC;CAChD,IAAI,OAAO,WAAW,GAAG,OAAO,OAAO;CACvC,MAAM,QAAQ,OAAO,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CACrD,MAAM,MAAM,IAAI,WAAW,KAAK;CAChC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,IAAI,OAAO,MAAM;EACrB,UAAU,MAAM;CAClB;CACA,OAAO;AACT;;AAGA,gBAAgB,WAAW,MAAoB,MAA4D;CACzG,OAAO;CACP,KAAK,IAAI,OAAO,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,MAAM,KAAK,KAAK,GAAG,MAAM,KAAK;AACtF;;;;;;;;AASA,eAAsB,SACpB,MACA,UACoC;CACpC,MAAM,WAAW,KAAK,OAAO,cAAc,CAAC;CAC5C,MAAM,OAAqB,CAAC;CAC5B,KAAK,IAAI,OAAO,MAAM,SAAS,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,MAAM,SAAS,KAAK,GAAG;EAC/E,KAAK,KAAK,KAAK,KAAK;EACpB,IAAI,SAAS,OAAO,IAAI,CAAC,GAAG;CAC9B;CACA,OAAO,WAAW,MAAM,QAAQ;AAClC"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { peek, update } from "../../core/signals/primitives.js";
|
|
2
|
+
import { effect } from "../../core/signals/effect.js";
|
|
3
|
+
import { resolveVttSegmentMetadata } from "../../media/text/resolve-vtt-metadata.js";
|
|
4
|
+
import { findTrackById, getTracksByType } from "../../media/utils/tracks.js";
|
|
5
|
+
import { dispatchCuesStep, textStepWiring } from "./text-segment-load-pipeline.js";
|
|
6
|
+
import { dispatchStep, fetchStep } from "./segment-load-pipeline.js";
|
|
7
|
+
import { findMediaTrack, readBaseMediaDecodeTime } from "../../media/mp4/timestamp-origin.js";
|
|
8
|
+
import { peekHead } from "./head-peek.js";
|
|
9
|
+
//#region src/playback/primitives/relocation-pipelines.ts
|
|
10
|
+
/** Assert the relocation state view from the opaque step deps — this module knows the slots the composition provides. */
|
|
11
|
+
function relocationState(deps) {
|
|
12
|
+
return deps.state;
|
|
13
|
+
}
|
|
14
|
+
function containerSlot(deps) {
|
|
15
|
+
return relocationState(deps).mediaContainerData;
|
|
16
|
+
}
|
|
17
|
+
/** Synchronous RMW of the per-type entry — disjoint keys across producers, so no lost update. */
|
|
18
|
+
function writeContainer(slot, trackType, patch) {
|
|
19
|
+
update(slot, (current) => ({
|
|
20
|
+
...current,
|
|
21
|
+
[trackType]: {
|
|
22
|
+
...current?.[trackType],
|
|
23
|
+
...patch
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve once `read()` returns a number. Shared by the A/V stamp (waits for the
|
|
29
|
+
* `derive`d origin — immediate for per-type, the shared-`min` barrier for coordinated)
|
|
30
|
+
* and the text step (waits for the primary A/V origin). No bound: for fMP4 the origin
|
|
31
|
+
* always establishes.
|
|
32
|
+
*/
|
|
33
|
+
function awaitDefined(read) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
let stop;
|
|
36
|
+
stop = effect(() => {
|
|
37
|
+
const value = read();
|
|
38
|
+
if (value !== void 0) {
|
|
39
|
+
stop?.();
|
|
40
|
+
resolve(value);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Relocation pipelines for one track type — a plain config `messagePipelines`.
|
|
47
|
+
* Keyed by **track type** (`'video'` / `'audio'`), so ABR rungs of a type share the
|
|
48
|
+
* origin (discover skips once the type's value is present). The steps read/write
|
|
49
|
+
* `state.mediaContainerData[trackType]` via their call-time `deps`; the stamp applies
|
|
50
|
+
* the same `derive` seam the reactor uses (pass the composition's resolved
|
|
51
|
+
* `deriveStartMediaTime` so the buffer offset and the model's `startMediaTime` agree).
|
|
52
|
+
*/
|
|
53
|
+
function relocationPipelinesFor(trackType, derive) {
|
|
54
|
+
const handlerType = trackType === "video" ? "vide" : "soun";
|
|
55
|
+
/**
|
|
56
|
+
* Init step: head-peek the buffered media track's `track_id` + `mdhd` timescale into
|
|
57
|
+
* `mediaContainerData[trackType]`. Matching by handler (`vide`/`soun`) skips a muxed
|
|
58
|
+
* `clcp` caption track, and the `track_id` lets `readSegmentOrigin` read *this*
|
|
59
|
+
* track's `tfdt` rather than the first `traf` in the segment.
|
|
60
|
+
*/
|
|
61
|
+
const readInitTrackInfo = async (frame, _signal, deps) => {
|
|
62
|
+
const { op } = frame;
|
|
63
|
+
if (op.type !== "append-init" || !frame.data) return;
|
|
64
|
+
const slot = containerSlot(deps);
|
|
65
|
+
if (peek(slot)?.[trackType]?.timescale !== void 0) return;
|
|
66
|
+
frame.data = await peekHead(frame.data, (bytes) => {
|
|
67
|
+
const track = findMediaTrack(bytes, handlerType);
|
|
68
|
+
if (track === void 0) return false;
|
|
69
|
+
writeContainer(slot, trackType, {
|
|
70
|
+
trackId: track.trackId,
|
|
71
|
+
timescale: track.timescale
|
|
72
|
+
});
|
|
73
|
+
return true;
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Media-segment step: head-peek the `tfdt` baseMediaDecodeTime of the media track's
|
|
78
|
+
* `traf` (matched by the `track_id` discovered from the init), recording the
|
|
79
|
+
* segment's 0-based `startTime` with it — the origin is `bmdt/ts − segmentStartTime`,
|
|
80
|
+
* so the first *loaded* segment need not be the 0th. Without a discovered `track_id`
|
|
81
|
+
* (non-fMP4 / mock init) there's no media track to relocate, so the step no-ops and
|
|
82
|
+
* the append stays native.
|
|
83
|
+
*/
|
|
84
|
+
const readSegmentOrigin = async (frame, _signal, deps) => {
|
|
85
|
+
const { op } = frame;
|
|
86
|
+
if (op.type !== "append-segment" || !frame.data) return;
|
|
87
|
+
const slot = containerSlot(deps);
|
|
88
|
+
const container = peek(slot)?.[trackType];
|
|
89
|
+
if (container?.baseMediaDecodeTime !== void 0) return;
|
|
90
|
+
const { trackId } = container ?? {};
|
|
91
|
+
if (trackId === void 0) return;
|
|
92
|
+
const segmentStartTime = op.meta.startTime;
|
|
93
|
+
frame.data = await peekHead(frame.data, (bytes) => {
|
|
94
|
+
const baseMediaDecodeTime = readBaseMediaDecodeTime(bytes, trackId);
|
|
95
|
+
if (baseMediaDecodeTime === void 0) return false;
|
|
96
|
+
writeContainer(slot, trackType, {
|
|
97
|
+
baseMediaDecodeTime,
|
|
98
|
+
segmentStartTime
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Stamp step — tier-agnostic apply. Relocate by the `derive`d `startMediaTime` for
|
|
105
|
+
* this type (`offset = −startMediaTime`). Applies the **same** `derive` the reactor
|
|
106
|
+
* uses, over the shared `mediaContainerData` slot — so the buffer offset matches the
|
|
107
|
+
* model's stamped `startMediaTime`, and it's robust to `established` + late tracks
|
|
108
|
+
* (the slot persists; the model value may not be re-stamped after the reactor goes
|
|
109
|
+
* sticky). Awaited: per-type resolves at once (own origin discovered earlier in this
|
|
110
|
+
* pipeline); shared-`min` waits until every selected A/V origin is in — the barrier,
|
|
111
|
+
* filled by the other type's discover step. A derived `0` (0-PTS / below threshold)
|
|
112
|
+
* leaves the append native — setting `timestampOffset` at all can ripple.
|
|
113
|
+
*/
|
|
114
|
+
const stampStartMediaTime = async (frame, signal, deps) => {
|
|
115
|
+
if (frame.op.type !== "append-segment") return;
|
|
116
|
+
const state = relocationState(deps);
|
|
117
|
+
const own = peek(state.mediaContainerData)?.[trackType];
|
|
118
|
+
if (own?.timescale === void 0 || own.baseMediaDecodeTime === void 0 || own.segmentStartTime === void 0) return;
|
|
119
|
+
const startMediaTime = await awaitDefined(() => {
|
|
120
|
+
const containerData = state.mediaContainerData.get();
|
|
121
|
+
if (!containerData) return void 0;
|
|
122
|
+
return derive(containerData, {
|
|
123
|
+
selectedVideoTrackId: state.selectedVideoTrackId?.get(),
|
|
124
|
+
selectedAudioTrackId: state.selectedAudioTrackId?.get()
|
|
125
|
+
})[trackType];
|
|
126
|
+
});
|
|
127
|
+
if (signal.aborted || startMediaTime === 0) return;
|
|
128
|
+
frame.meta = {
|
|
129
|
+
...frame.meta ?? frame.op.meta,
|
|
130
|
+
timestampOffset: -startMediaTime
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
return () => ({
|
|
134
|
+
remove: [dispatchStep],
|
|
135
|
+
"append-init": [
|
|
136
|
+
fetchStep,
|
|
137
|
+
readInitTrackInfo,
|
|
138
|
+
dispatchStep
|
|
139
|
+
],
|
|
140
|
+
"append-segment": [
|
|
141
|
+
fetchStep,
|
|
142
|
+
readSegmentOrigin,
|
|
143
|
+
stampStartMediaTime,
|
|
144
|
+
dispatchStep
|
|
145
|
+
]
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve step for the relocation text pipeline. Reuses the injected host resolver
|
|
150
|
+
* (the loader's folded `resolveSegment`, via `textStepWiring`) for cues and fetches
|
|
151
|
+
* the `X-TIMESTAMP-MAP` header in parallel, stashing it on `frame.metadata` for
|
|
152
|
+
* `relocateCuesStep`. Replaces the
|
|
153
|
+
* base `resolveCuesStep` (which fetches cues only) — text's native `<track>` parser
|
|
154
|
+
* discards the header, so the map needs its own raw-bytes fetch.
|
|
155
|
+
*/
|
|
156
|
+
const resolveWithMetadataStep = async (frame, signal, deps) => {
|
|
157
|
+
const [cues, metadata] = await Promise.all([textStepWiring(deps).resolveSegment(frame.op.segment.url), resolveVttSegmentMetadata(frame.op.segment.url)]);
|
|
158
|
+
if (signal.aborted) return;
|
|
159
|
+
frame.cues = cues;
|
|
160
|
+
frame.metadata = metadata;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Relocate step — shifts each cue onto the 0-based presentation timeline:
|
|
164
|
+
* `cueFinal = cueNative − startMediaTime`, where `startMediaTime` is the primary
|
|
165
|
+
* A/V track's origin (selected **video**, else **audio** — the single-anchor rule,
|
|
166
|
+
* and defensive like the reactor's optional selection) and `cueNative` folds in the
|
|
167
|
+
* `X-TIMESTAMP-MAP` correction (`mpegts/90000 − local`) for map-bearing VTT (Apple)
|
|
168
|
+
* or is the absolute cue time (no map, e.g. Mux). Text can resolve before A/V
|
|
169
|
+
* establishes, so the origin is awaited; fMP4 always establishes it (0-PTS → 0),
|
|
170
|
+
* and a text-only source (no A/V selected) simply gets offset 0.
|
|
171
|
+
*/
|
|
172
|
+
const relocateCuesStep = async (frame, signal, deps) => {
|
|
173
|
+
if (!frame.cues?.length) return;
|
|
174
|
+
const state = deps.state;
|
|
175
|
+
const startMediaTime = await awaitDefined(() => {
|
|
176
|
+
const presentation = state.presentation.get();
|
|
177
|
+
if (!presentation) return void 0;
|
|
178
|
+
const primaryId = state.selectedVideoTrackId.get() ?? state.selectedAudioTrackId.get();
|
|
179
|
+
if (primaryId !== void 0) return findTrackById(presentation, primaryId)?.startMediaTime;
|
|
180
|
+
return getTracksByType(presentation, "video").length > 0 || getTracksByType(presentation, "audio").length > 0 ? void 0 : 0;
|
|
181
|
+
});
|
|
182
|
+
if (signal.aborted) return;
|
|
183
|
+
const { timestampMap } = frame.metadata ?? {};
|
|
184
|
+
const delta = (timestampMap ? timestampMap.mpegts / 9e4 - timestampMap.local : 0) - startMediaTime;
|
|
185
|
+
if (delta !== 0) for (const cue of frame.cues) {
|
|
186
|
+
cue.startTime += delta;
|
|
187
|
+
cue.endTime += delta;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Relocation text pipeline — the text analog of `relocationPipelinesFor(type)`.
|
|
192
|
+
* `resolveWithMetadata` (cues + `X-TIMESTAMP-MAP`) → `relocateCues` (shift by the
|
|
193
|
+
* primary A/V origin) → `dispatchCues`.
|
|
194
|
+
*/
|
|
195
|
+
const relocatingTextPipelines = () => [
|
|
196
|
+
resolveWithMetadataStep,
|
|
197
|
+
relocateCuesStep,
|
|
198
|
+
dispatchCuesStep
|
|
199
|
+
];
|
|
200
|
+
//#endregion
|
|
201
|
+
export { relocatingTextPipelines, relocationPipelinesFor };
|
|
202
|
+
|
|
203
|
+
//# sourceMappingURL=relocation-pipelines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relocation-pipelines.js","names":[],"sources":["../../../../src/playback/primitives/relocation-pipelines.ts"],"sourcesContent":["/**\n * Non-zero-PTS relocation pipelines — the config-supplied, loader-facing half of the\n * `establishStartMediaTime` behavior (`../behaviors/establish-start-media-time`). The\n * reactor there owns the lifecycle and the `derive` coordination seam; this file supplies\n * the `messagePipelines` the segment/text loaders run to *fill and act on* that behavior's\n * state. It's the relocation analog of `track-switching`'s config-supplied constraint/rule\n * chain — a pluggable strategy handed to the machinery via config, not applied inline —\n * supplied to the loaders rather than applied by the behavior, so the two coordinate\n * through the shared `mediaContainerData` / `startMediaTime` slots alone, never by import.\n *\n * DOM-free: it composes the loader vocabularies (`segment-load-pipeline`,\n * `text-segment-load-pipeline`) through their structural sink seams and shifts cues via\n * the structural `Cue` type, so it names no `SourceBuffer`, `TextTracksActor`, or\n * `VTTCue`. The A/V pipeline is a plain `messagePipelines` array:\n * - `discover` — init `track_id` + `mdhd` timescale for the buffered media track, then\n * that same track's `tfdt` baseMediaDecodeTime, matched by `track_id` so a muxed\n * segment reads the media track's origin, not the first `traf` — writes\n * `state.mediaContainerData`.\n * - `stamp` — reads that track's derived origin back and relocates via\n * `timestampOffset = −startMediaTime`.\n * Steps read composition `state` from their call-time `deps` (no closures, no context).\n *\n * The text half (`relocatingTextPipelines`) is the same idea for the text-segment loader:\n * a `resolveWithMetadata → relocateCues → dispatchCues` pipeline that shifts cues onto the\n * same 0-based timeline, reading the primary A/V track's `startMediaTime` (the reactor's\n * consumed value) via `deps`.\n */\nimport type { StateSignals } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { peek, type Signal, update } from '../../core/signals/primitives';\nimport { findMediaTrack, type MediaHandlerType, readBaseMediaDecodeTime } from '../../media/mp4/timestamp-origin';\nimport { resolveVttSegmentMetadata, type TextSegmentMetadata } from '../../media/text/resolve-vtt-metadata';\nimport type { Cue, MaybeResolvedPresentation, MediaContainerData } from '../../media/types';\nimport { findTrackById, getTracksByType } from '../../media/utils/tracks';\nimport type { DeriveStartMediaTime } from './derive-start-media-time';\nimport { peekHead } from './head-peek';\nimport { dispatchStep, fetchStep, type LoadStep, type MessagePipelines, type StepDeps } from './segment-load-pipeline';\nimport {\n dispatchCuesStep,\n type TextFrame,\n type TextLoadStep,\n type TextStepDeps,\n textStepWiring,\n} from './text-segment-load-pipeline';\n\n/** The composition slots these steps read — the view asserted from the opaque `deps.state`. */\ninterface RelocationSlots {\n presentation?: MaybeResolvedPresentation;\n mediaContainerData?: Record<string, MediaContainerData>;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\ntype ContainerSlot = Signal<Record<string, MediaContainerData> | undefined>;\n\n/** Assert the relocation state view from the opaque step deps — this module knows the slots the composition provides. */\nfunction relocationState(deps: StepDeps): StateSignals<RelocationSlots> {\n return deps.state as unknown as StateSignals<RelocationSlots>;\n}\n\nfunction containerSlot(deps: StepDeps): ContainerSlot {\n return relocationState(deps).mediaContainerData;\n}\n\n/** Synchronous RMW of the per-type entry — disjoint keys across producers, so no lost update. */\nfunction writeContainer(slot: ContainerSlot, trackType: string, patch: Partial<MediaContainerData>): void {\n update(slot, (current) => ({ ...current, [trackType]: { ...current?.[trackType], ...patch } }));\n}\n\n/**\n * Resolve once `read()` returns a number. Shared by the A/V stamp (waits for the\n * `derive`d origin — immediate for per-type, the shared-`min` barrier for coordinated)\n * and the text step (waits for the primary A/V origin). No bound: for fMP4 the origin\n * always establishes.\n */\nfunction awaitDefined(read: () => number | undefined): Promise<number> {\n return new Promise((resolve) => {\n let stop: (() => void) | undefined;\n stop = effect(() => {\n const value = read();\n if (value !== undefined) {\n stop?.();\n resolve(value);\n }\n });\n });\n}\n\n/**\n * Relocation pipelines for one track type — a plain config `messagePipelines`.\n * Keyed by **track type** (`'video'` / `'audio'`), so ABR rungs of a type share the\n * origin (discover skips once the type's value is present). The steps read/write\n * `state.mediaContainerData[trackType]` via their call-time `deps`; the stamp applies\n * the same `derive` seam the reactor uses (pass the composition's resolved\n * `deriveStartMediaTime` so the buffer offset and the model's `startMediaTime` agree).\n */\nexport function relocationPipelinesFor(trackType: 'video' | 'audio', derive: DeriveStartMediaTime): MessagePipelines {\n const handlerType: MediaHandlerType = trackType === 'video' ? 'vide' : 'soun';\n\n /**\n * Init step: head-peek the buffered media track's `track_id` + `mdhd` timescale into\n * `mediaContainerData[trackType]`. Matching by handler (`vide`/`soun`) skips a muxed\n * `clcp` caption track, and the `track_id` lets `readSegmentOrigin` read *this*\n * track's `tfdt` rather than the first `traf` in the segment.\n */\n const readInitTrackInfo: LoadStep = async (frame, _signal, deps) => {\n const { op } = frame;\n if (op.type !== 'append-init' || !frame.data) return;\n const slot = containerSlot(deps);\n if (peek(slot)?.[trackType]?.timescale !== undefined) return; // already have it (any rung of this type)\n frame.data = await peekHead(frame.data, (bytes) => {\n const track = findMediaTrack(bytes, handlerType);\n if (track === undefined) return false;\n writeContainer(slot, trackType, { trackId: track.trackId, timescale: track.timescale });\n return true;\n });\n };\n\n /**\n * Media-segment step: head-peek the `tfdt` baseMediaDecodeTime of the media track's\n * `traf` (matched by the `track_id` discovered from the init), recording the\n * segment's 0-based `startTime` with it — the origin is `bmdt/ts − segmentStartTime`,\n * so the first *loaded* segment need not be the 0th. Without a discovered `track_id`\n * (non-fMP4 / mock init) there's no media track to relocate, so the step no-ops and\n * the append stays native.\n */\n const readSegmentOrigin: LoadStep = async (frame, _signal, deps) => {\n const { op } = frame;\n if (op.type !== 'append-segment' || !frame.data) return;\n const slot = containerSlot(deps);\n const container = peek(slot)?.[trackType];\n if (container?.baseMediaDecodeTime !== undefined) return; // established\n const { trackId } = container ?? {};\n if (trackId === undefined) return; // init didn't identify a media track — nothing to match\n const segmentStartTime = op.meta.startTime;\n frame.data = await peekHead(frame.data, (bytes) => {\n const baseMediaDecodeTime = readBaseMediaDecodeTime(bytes, trackId);\n if (baseMediaDecodeTime === undefined) return false;\n writeContainer(slot, trackType, { baseMediaDecodeTime, segmentStartTime });\n return true;\n });\n };\n\n /**\n * Stamp step — tier-agnostic apply. Relocate by the `derive`d `startMediaTime` for\n * this type (`offset = −startMediaTime`). Applies the **same** `derive` the reactor\n * uses, over the shared `mediaContainerData` slot — so the buffer offset matches the\n * model's stamped `startMediaTime`, and it's robust to `established` + late tracks\n * (the slot persists; the model value may not be re-stamped after the reactor goes\n * sticky). Awaited: per-type resolves at once (own origin discovered earlier in this\n * pipeline); shared-`min` waits until every selected A/V origin is in — the barrier,\n * filled by the other type's discover step. A derived `0` (0-PTS / below threshold)\n * leaves the append native — setting `timestampOffset` at all can ripple.\n */\n const stampStartMediaTime: LoadStep = async (frame, signal, deps) => {\n if (frame.op.type !== 'append-segment') return;\n const state = relocationState(deps);\n // Liveness guard: if THIS type's own origin wasn't discovered — `readSegmentOrigin`\n // ran earlier in this pipeline and found no `tfdt` (mock / TS / containerless) — the\n // source isn't relocatable, so leave the append native and DON'T wait. Only a\n // discoverable type awaits the derived origin (which for shared-`min` legitimately\n // blocks on the other selected type — the barrier).\n const own = peek(state.mediaContainerData)?.[trackType];\n if (own?.timescale === undefined || own.baseMediaDecodeTime === undefined || own.segmentStartTime === undefined) {\n return;\n }\n const startMediaTime = await awaitDefined(() => {\n const containerData = state.mediaContainerData.get();\n if (!containerData) return undefined;\n return derive(containerData, {\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n })[trackType];\n });\n if (signal.aborted || startMediaTime === 0) return;\n frame.meta = { ...(frame.meta ?? frame.op.meta), timestampOffset: -startMediaTime };\n };\n\n return () => ({\n remove: [dispatchStep],\n 'append-init': [fetchStep, readInitTrackInfo, dispatchStep],\n 'append-segment': [fetchStep, readSegmentOrigin, stampStartMediaTime, dispatchStep],\n });\n}\n\n// ============================================================================\n// TEXT (cue relocation)\n// ============================================================================\n\n/**\n * Resolve step for the relocation text pipeline. Reuses the injected host resolver\n * (the loader's folded `resolveSegment`, via `textStepWiring`) for cues and fetches\n * the `X-TIMESTAMP-MAP` header in parallel, stashing it on `frame.metadata` for\n * `relocateCuesStep`. Replaces the\n * base `resolveCuesStep` (which fetches cues only) — text's native `<track>` parser\n * discards the header, so the map needs its own raw-bytes fetch.\n */\nconst resolveWithMetadataStep = async <C extends Cue>(\n frame: TextFrame<C>,\n signal: AbortSignal,\n deps: TextStepDeps\n): Promise<void> => {\n const [cues, metadata] = await Promise.all([\n textStepWiring<C>(deps).resolveSegment(frame.op.segment.url),\n resolveVttSegmentMetadata(frame.op.segment.url),\n ]);\n if (signal.aborted) return;\n frame.cues = cues;\n frame.metadata = metadata;\n};\n\n/**\n * Relocate step — shifts each cue onto the 0-based presentation timeline:\n * `cueFinal = cueNative − startMediaTime`, where `startMediaTime` is the primary\n * A/V track's origin (selected **video**, else **audio** — the single-anchor rule,\n * and defensive like the reactor's optional selection) and `cueNative` folds in the\n * `X-TIMESTAMP-MAP` correction (`mpegts/90000 − local`) for map-bearing VTT (Apple)\n * or is the absolute cue time (no map, e.g. Mux). Text can resolve before A/V\n * establishes, so the origin is awaited; fMP4 always establishes it (0-PTS → 0),\n * and a text-only source (no A/V selected) simply gets offset 0.\n */\nconst relocateCuesStep = async <C extends Cue>(\n frame: TextFrame<C>,\n signal: AbortSignal,\n deps: TextStepDeps\n): Promise<void> => {\n if (!frame.cues?.length) return;\n const state = deps.state as unknown as StateSignals<RelocationSlots>;\n const startMediaTime = await awaitDefined(() => {\n const presentation = state.presentation.get();\n if (!presentation) return undefined;\n const primaryId = state.selectedVideoTrackId.get() ?? state.selectedAudioTrackId.get();\n if (primaryId !== undefined) {\n // A/V selected: use its origin once stamped (undefined until then → keep waiting).\n return findTrackById(presentation, primaryId)?.startMediaTime;\n }\n // No A/V selected. Distinguish \"not selected YET\" from \"text-only source\". If the\n // presentation has A/V tracks, one WILL be selected and stamped — wait for it, or the\n // cues get shifted by the full `mapCorrection` (e.g. +10s on Apple X-TIMESTAMP-MAP\n // sources) when text relocation wins the race against A/V selection. Only a genuinely\n // text-only source (no A/V origin ever coming) legitimately relocates by offset 0.\n const hasAv =\n getTracksByType(presentation, 'video').length > 0 || getTracksByType(presentation, 'audio').length > 0;\n return hasAv ? undefined : 0;\n });\n if (signal.aborted) return;\n const { timestampMap } = (frame.metadata as TextSegmentMetadata | undefined) ?? {};\n const mapCorrection = timestampMap ? timestampMap.mpegts / 90000 - timestampMap.local : 0;\n const delta = mapCorrection - startMediaTime;\n if (delta !== 0) {\n for (const cue of frame.cues) {\n cue.startTime += delta;\n cue.endTime += delta;\n }\n }\n};\n\n/**\n * Relocation text pipeline — the text analog of `relocationPipelinesFor(type)`.\n * `resolveWithMetadata` (cues + `X-TIMESTAMP-MAP`) → `relocateCues` (shift by the\n * primary A/V origin) → `dispatchCues`.\n */\nexport const relocatingTextPipelines = <C extends Cue>(): TextLoadStep<C>[] => [\n resolveWithMetadataStep,\n relocateCuesStep,\n dispatchCuesStep,\n];\n"],"mappings":";;;;;;;;;;AAwDA,SAAS,gBAAgB,MAA+C;CACtE,OAAO,KAAK;AACd;AAEA,SAAS,cAAc,MAA+B;CACpD,OAAO,gBAAgB,IAAI,CAAC,CAAC;AAC/B;;AAGA,SAAS,eAAe,MAAqB,WAAmB,OAA0C;CACxG,OAAO,OAAO,aAAa;EAAE,GAAG;GAAU,YAAY;GAAE,GAAG,UAAU;GAAY,GAAG;EAAM;CAAE,EAAE;AAChG;;;;;;;AAQA,SAAS,aAAa,MAAiD;CACrE,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI;EACJ,OAAO,aAAa;GAClB,MAAM,QAAQ,KAAK;GACnB,IAAI,UAAU,KAAA,GAAW;IACvB,OAAO;IACP,QAAQ,KAAK;GACf;EACF,CAAC;CACH,CAAC;AACH;;;;;;;;;AAUA,SAAgB,uBAAuB,WAA8B,QAAgD;CACnH,MAAM,cAAgC,cAAc,UAAU,SAAS;;;;;;;CAQvE,MAAM,oBAA8B,OAAO,OAAO,SAAS,SAAS;EAClE,MAAM,EAAE,OAAO;EACf,IAAI,GAAG,SAAS,iBAAiB,CAAC,MAAM,MAAM;EAC9C,MAAM,OAAO,cAAc,IAAI;EAC/B,IAAI,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,cAAc,KAAA,GAAW;EACtD,MAAM,OAAO,MAAM,SAAS,MAAM,OAAO,UAAU;GACjD,MAAM,QAAQ,eAAe,OAAO,WAAW;GAC/C,IAAI,UAAU,KAAA,GAAW,OAAO;GAChC,eAAe,MAAM,WAAW;IAAE,SAAS,MAAM;IAAS,WAAW,MAAM;GAAU,CAAC;GACtF,OAAO;EACT,CAAC;CACH;;;;;;;;;CAUA,MAAM,oBAA8B,OAAO,OAAO,SAAS,SAAS;EAClE,MAAM,EAAE,OAAO;EACf,IAAI,GAAG,SAAS,oBAAoB,CAAC,MAAM,MAAM;EACjD,MAAM,OAAO,cAAc,IAAI;EAC/B,MAAM,YAAY,KAAK,IAAI,CAAC,GAAG;EAC/B,IAAI,WAAW,wBAAwB,KAAA,GAAW;EAClD,MAAM,EAAE,YAAY,aAAa,CAAC;EAClC,IAAI,YAAY,KAAA,GAAW;EAC3B,MAAM,mBAAmB,GAAG,KAAK;EACjC,MAAM,OAAO,MAAM,SAAS,MAAM,OAAO,UAAU;GACjD,MAAM,sBAAsB,wBAAwB,OAAO,OAAO;GAClE,IAAI,wBAAwB,KAAA,GAAW,OAAO;GAC9C,eAAe,MAAM,WAAW;IAAE;IAAqB;GAAiB,CAAC;GACzE,OAAO;EACT,CAAC;CACH;;;;;;;;;;;;CAaA,MAAM,sBAAgC,OAAO,OAAO,QAAQ,SAAS;EACnE,IAAI,MAAM,GAAG,SAAS,kBAAkB;EACxC,MAAM,QAAQ,gBAAgB,IAAI;EAMlC,MAAM,MAAM,KAAK,MAAM,kBAAkB,CAAC,GAAG;EAC7C,IAAI,KAAK,cAAc,KAAA,KAAa,IAAI,wBAAwB,KAAA,KAAa,IAAI,qBAAqB,KAAA,GACpG;EAEF,MAAM,iBAAiB,MAAM,mBAAmB;GAC9C,MAAM,gBAAgB,MAAM,mBAAmB,IAAI;GACnD,IAAI,CAAC,eAAe,OAAO,KAAA;GAC3B,OAAO,OAAO,eAAe;IAC3B,sBAAsB,MAAM,sBAAsB,IAAI;IACtD,sBAAsB,MAAM,sBAAsB,IAAI;GACxD,CAAC,CAAC,CAAC;EACL,CAAC;EACD,IAAI,OAAO,WAAW,mBAAmB,GAAG;EAC5C,MAAM,OAAO;GAAE,GAAI,MAAM,QAAQ,MAAM,GAAG;GAAO,iBAAiB,CAAC;EAAe;CACpF;CAEA,cAAc;EACZ,QAAQ,CAAC,YAAY;EACrB,eAAe;GAAC;GAAW;GAAmB;EAAY;EAC1D,kBAAkB;GAAC;GAAW;GAAmB;GAAqB;EAAY;CACpF;AACF;;;;;;;;;AAcA,MAAM,0BAA0B,OAC9B,OACA,QACA,SACkB;CAClB,MAAM,CAAC,MAAM,YAAY,MAAM,QAAQ,IAAI,CACzC,eAAkB,IAAI,CAAC,CAAC,eAAe,MAAM,GAAG,QAAQ,GAAG,GAC3D,0BAA0B,MAAM,GAAG,QAAQ,GAAG,CAChD,CAAC;CACD,IAAI,OAAO,SAAS;CACpB,MAAM,OAAO;CACb,MAAM,WAAW;AACnB;;;;;;;;;;;AAYA,MAAM,mBAAmB,OACvB,OACA,QACA,SACkB;CAClB,IAAI,CAAC,MAAM,MAAM,QAAQ;CACzB,MAAM,QAAQ,KAAK;CACnB,MAAM,iBAAiB,MAAM,mBAAmB;EAC9C,MAAM,eAAe,MAAM,aAAa,IAAI;EAC5C,IAAI,CAAC,cAAc,OAAO,KAAA;EAC1B,MAAM,YAAY,MAAM,qBAAqB,IAAI,KAAK,MAAM,qBAAqB,IAAI;EACrF,IAAI,cAAc,KAAA,GAEhB,OAAO,cAAc,cAAc,SAAS,CAAC,EAAE;EASjD,OADE,gBAAgB,cAAc,OAAO,CAAC,CAAC,SAAS,KAAK,gBAAgB,cAAc,OAAO,CAAC,CAAC,SAAS,IACxF,KAAA,IAAY;CAC7B,CAAC;CACD,IAAI,OAAO,SAAS;CACpB,MAAM,EAAE,iBAAkB,MAAM,YAAgD,CAAC;CAEjF,MAAM,SADgB,eAAe,aAAa,SAAS,MAAQ,aAAa,QAAQ,KAC1D;CAC9B,IAAI,UAAU,GACZ,KAAK,MAAM,OAAO,MAAM,MAAM;EAC5B,IAAI,aAAa;EACjB,IAAI,WAAW;CACjB;AAEJ;;;;;;AAOA,MAAa,gCAAkE;CAC7E;CACA;CACA;AACF"}
|