@videojs/spf 10.0.0-beta.9 → 10.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -38
- package/dist/default/core/actors/create-machine-actor.js +89 -0
- package/dist/default/core/actors/create-machine-actor.js.map +1 -0
- package/dist/default/core/actors/create-transition-actor.js +44 -0
- package/dist/default/core/actors/create-transition-actor.js.map +1 -0
- package/dist/default/core/composition/create-composition.js +82 -0
- package/dist/default/core/composition/create-composition.js.map +1 -0
- package/dist/default/core/composition/share-signals.js +34 -0
- package/dist/default/core/composition/share-signals.js.map +1 -0
- package/dist/default/core/machine.js +25 -0
- package/dist/default/core/machine.js.map +1 -0
- package/dist/default/core/reactors/create-machine-reactor.js +84 -0
- package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/default/core/signals/effect.js +61 -0
- package/dist/default/core/signals/effect.js.map +1 -0
- package/dist/default/core/signals/primitives.js +50 -0
- package/dist/default/core/signals/primitives.js.map +1 -0
- package/dist/default/core/signals/when.js +36 -0
- package/dist/default/core/signals/when.js.map +1 -0
- package/dist/default/core/tasks/delayed-reschedule.js +27 -0
- package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/default/core/tasks/task.js +298 -0
- package/dist/default/core/tasks/task.js.map +1 -0
- package/dist/default/dom.js +9 -0
- package/dist/default/hls-audio.js +3 -0
- package/dist/default/hls-background-video.js +3 -0
- package/dist/default/hls-video.js +4 -0
- package/dist/default/hls.js +10 -0
- package/dist/default/index.js +22 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/media/abr/quality-selection.js +17 -0
- package/dist/default/media/abr/quality-selection.js.map +1 -0
- package/dist/default/media/buffer/back-buffer.js +42 -0
- package/dist/default/media/buffer/back-buffer.js.map +1 -0
- package/dist/default/media/buffer/forward-buffer.js +110 -0
- package/dist/default/media/buffer/forward-buffer.js.map +1 -0
- package/dist/default/media/dom/capabilities.js +61 -0
- package/dist/default/media/dom/capabilities.js.map +1 -0
- package/dist/default/media/dom/mse/append-segment.js +58 -0
- package/dist/default/media/dom/mse/append-segment.js.map +1 -0
- package/dist/default/media/dom/mse/buffer-flusher.js +55 -0
- package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/default/media/dom/mse/duration.js +87 -0
- package/dist/default/media/dom/mse/duration.js.map +1 -0
- package/dist/default/media/dom/mse/end-of-stream.js +17 -0
- package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/default/media/dom/mse/mediasource-setup.js +227 -0
- package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/default/media/dom/screen.js +95 -0
- package/dist/default/media/dom/screen.js.map +1 -0
- package/dist/default/media/dom/text/resolve-vtt-segment.js +50 -0
- package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/default/media/dom/text/text-track-slots.js +61 -0
- package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/default/media/errors.js +60 -0
- package/dist/default/media/errors.js.map +1 -0
- package/dist/default/media/hls/parse-attributes.js +131 -0
- package/dist/default/media/hls/parse-attributes.js.map +1 -0
- package/dist/default/media/hls/parse-media-playlist.js +253 -0
- package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/default/media/hls/parse-multivariant.js +234 -0
- package/dist/default/media/hls/parse-multivariant.js.map +1 -0
- package/dist/default/media/hls/reload-policy.js +63 -0
- package/dist/default/media/hls/reload-policy.js.map +1 -0
- package/dist/default/media/hls/resolve-url.js +9 -0
- package/dist/default/media/hls/resolve-url.js.map +1 -0
- package/dist/default/media/live-window.js +32 -0
- package/dist/default/media/live-window.js.map +1 -0
- package/dist/default/media/media-tracks/media-tracks.js +96 -0
- package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/default/media/mp4/box.js +57 -0
- package/dist/default/media/mp4/box.js.map +1 -0
- package/dist/default/media/mp4/timestamp-origin.js +95 -0
- package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/default/media/primitives/resolution.js +26 -0
- package/dist/default/media/primitives/resolution.js.map +1 -0
- package/dist/default/media/primitives/select-tracks.js +97 -0
- package/dist/default/media/primitives/select-tracks.js.map +1 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js +40 -0
- package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/default/media/text/resolve-vtt-metadata.js +22 -0
- package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/default/media/types/index.js +42 -0
- package/dist/default/media/types/index.js.map +1 -0
- package/dist/default/media/utils/cdn.js +50 -0
- package/dist/default/media/utils/cdn.js.map +1 -0
- package/dist/default/media/utils/preload.js +21 -0
- package/dist/default/media/utils/preload.js.map +1 -0
- package/dist/default/media/utils/track-selection.js +43 -0
- package/dist/default/media/utils/track-selection.js.map +1 -0
- package/dist/default/media/utils/tracks.js +146 -0
- package/dist/default/media/utils/tracks.js.map +1 -0
- package/dist/default/media-tracks.js +2 -0
- package/dist/default/network/bandwidth-estimator.js +95 -0
- package/dist/default/network/bandwidth-estimator.js.map +1 -0
- package/dist/default/network/chunked-stream-iterable.js +47 -0
- package/dist/default/network/chunked-stream-iterable.js.map +1 -0
- package/dist/default/network/ewma.js +68 -0
- package/dist/default/network/ewma.js.map +1 -0
- package/dist/default/network/fetch.js +108 -0
- package/dist/default/network/fetch.js.map +1 -0
- package/dist/default/playback/actors/dom/segment-loader.js +233 -0
- package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/default/playback/actors/dom/source-buffer.js +139 -0
- package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/default/playback/actors/dom/text-tracks.js +46 -0
- package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/default/playback/actors/text-track-segment-loader.js +138 -0
- package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js +8 -0
- package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-audio/mixin.js +178 -0
- package/dist/default/playback/adapters/hls-audio/mixin.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js +17 -0
- package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/host.js +56 -0
- package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/default/playback/adapters/hls-background-video/mixin.js +191 -0
- package/dist/default/playback/adapters/hls-background-video/mixin.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/adapter.js +11 -0
- package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js +46 -0
- package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js +125 -0
- package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/default/playback/adapters/hls-video/mixin.js +299 -0
- package/dist/default/playback/adapters/hls-video/mixin.js.map +1 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js +33 -0
- package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/default/playback/behaviors/collect-errors.js +98 -0
- package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js +61 -0
- package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/default/playback/behaviors/dom/airplay.js +188 -0
- package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js +81 -0
- package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js +97 -0
- package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/default/playback/behaviors/dom/load-segments.js +169 -0
- package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/default/playback/behaviors/dom/recover-end-stall.js +72 -0
- package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +113 -0
- package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +181 -0
- package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js +100 -0
- package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +40 -0
- package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +25 -0
- package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js +109 -0
- package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js +50 -0
- package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js +86 -0
- package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js +35 -0
- package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js +53 -0
- package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js +28 -0
- package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +98 -0
- package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js +187 -0
- package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-presentation.js +76 -0
- package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/default/playback/behaviors/resolve-track.js +134 -0
- package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/default/playback/behaviors/select-tracks.js +189 -0
- package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js +61 -0
- package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/default/playback/behaviors/sync-preload.js +59 -0
- package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/default/playback/behaviors/track-switching.js +407 -0
- package/dist/default/playback/behaviors/track-switching.js.map +1 -0
- 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-background-video.js +87 -0
- package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/default/playback/engines/hls/engine.js +144 -0
- package/dist/default/playback/engines/hls/engine.js.map +1 -0
- package/dist/default/playback/primitives/error-messages.js +42 -0
- package/dist/default/playback/primitives/error-messages.js.map +1 -0
- package/dist/default/playback/primitives/failover-fetch.js +37 -0
- package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/default/playback/primitives/head-peek.js +41 -0
- package/dist/default/playback/primitives/head-peek.js.map +1 -0
- package/dist/default/playback/primitives/live-window.js +63 -0
- package/dist/default/playback/primitives/live-window.js.map +1 -0
- package/dist/default/playback/primitives/relocation-pipelines.js +191 -0
- package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/default/playback/primitives/report-track-conditions.js +74 -0
- package/dist/default/playback/primitives/report-track-conditions.js.map +1 -0
- package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
- package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/selection-rules.js +115 -0
- package/dist/default/playback/primitives/selection-rules.js.map +1 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js +36 -0
- package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/default/playback/primitives/track-types.js +53 -0
- package/dist/default/playback/primitives/track-types.js.map +1 -0
- package/dist/dev/core/actors/actor.d.ts +26 -0
- package/dist/dev/core/actors/actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts +119 -0
- package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-machine-actor.js +89 -0
- package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts +37 -0
- package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
- package/dist/dev/core/actors/create-transition-actor.js +44 -0
- package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
- package/dist/dev/core/composition/create-composition.d.ts +241 -0
- package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
- package/dist/dev/core/composition/create-composition.js +82 -0
- package/dist/dev/core/composition/create-composition.js.map +1 -0
- package/dist/dev/core/composition/share-signals.d.ts +39 -0
- package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
- package/dist/dev/core/composition/share-signals.js +34 -0
- package/dist/dev/core/composition/share-signals.js.map +1 -0
- package/dist/dev/core/machine.d.ts +21 -0
- package/dist/dev/core/machine.d.ts.map +1 -0
- package/dist/dev/core/machine.js +25 -0
- package/dist/dev/core/machine.js.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts +87 -0
- package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
- package/dist/dev/core/reactors/create-machine-reactor.js +84 -0
- package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
- package/dist/dev/core/signals/effect.d.ts +14 -0
- package/dist/dev/core/signals/effect.d.ts.map +1 -0
- package/dist/dev/core/signals/effect.js +61 -0
- package/dist/dev/core/signals/effect.js.map +1 -0
- package/dist/dev/core/signals/primitives.d.ts +44 -0
- package/dist/dev/core/signals/primitives.d.ts.map +1 -0
- package/dist/dev/core/signals/primitives.js +50 -0
- package/dist/dev/core/signals/primitives.js.map +1 -0
- package/dist/dev/core/signals/when.js +36 -0
- package/dist/dev/core/signals/when.js.map +1 -0
- package/dist/dev/core/tasks/delayed-reschedule.js +27 -0
- package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
- package/dist/dev/core/tasks/task.d.ts +144 -0
- package/dist/dev/core/tasks/task.d.ts.map +1 -0
- package/dist/dev/core/tasks/task.js +298 -0
- package/dist/dev/core/tasks/task.js.map +1 -0
- package/dist/dev/dom.d.ts +9 -0
- package/dist/dev/dom.js +9 -0
- package/dist/dev/hls-audio.d.ts +3 -0
- package/dist/dev/hls-audio.js +3 -0
- package/dist/dev/hls-background-video.d.ts +4 -0
- package/dist/dev/hls-background-video.js +3 -0
- package/dist/dev/hls-video.d.ts +5 -0
- package/dist/dev/hls-video.js +4 -0
- package/dist/dev/hls.d.ts +11 -0
- package/dist/dev/hls.js +10 -0
- package/dist/dev/index.d.ts +23 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +22 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/media/abr/quality-selection.d.ts +19 -0
- package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
- package/dist/dev/media/abr/quality-selection.js +17 -0
- package/dist/dev/media/abr/quality-selection.js.map +1 -0
- package/dist/dev/media/buffer/back-buffer.d.ts +9 -0
- package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/back-buffer.js +42 -0
- package/dist/dev/media/buffer/back-buffer.js.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts +9 -0
- package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
- package/dist/dev/media/buffer/forward-buffer.js +110 -0
- package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
- package/dist/dev/media/dom/capabilities.js +61 -0
- package/dist/dev/media/dom/capabilities.js.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts +16 -0
- package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/append-segment.js +58 -0
- package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts +24 -0
- package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
- package/dist/dev/media/dom/mse/buffer-flusher.js +55 -0
- package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
- package/dist/dev/media/dom/mse/duration.js +87 -0
- package/dist/dev/media/dom/mse/duration.js.map +1 -0
- package/dist/dev/media/dom/mse/end-of-stream.js +17 -0
- package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js +227 -0
- package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
- package/dist/dev/media/dom/screen.d.ts +7 -0
- package/dist/dev/media/dom/screen.d.ts.map +1 -0
- package/dist/dev/media/dom/screen.js +95 -0
- package/dist/dev/media/dom/screen.js.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +6 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.js +50 -0
- package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts +24 -0
- package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
- package/dist/dev/media/dom/text/text-track-slots.js +61 -0
- package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
- package/dist/dev/media/errors.d.ts +68 -0
- package/dist/dev/media/errors.d.ts.map +1 -0
- package/dist/dev/media/errors.js +60 -0
- package/dist/dev/media/errors.js.map +1 -0
- package/dist/dev/media/hls/parse-attributes.js +131 -0
- package/dist/dev/media/hls/parse-attributes.js.map +1 -0
- package/dist/dev/media/hls/parse-media-playlist.js +253 -0
- package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
- package/dist/dev/media/hls/parse-multivariant.js +234 -0
- package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
- package/dist/dev/media/hls/reload-policy.js +63 -0
- package/dist/dev/media/hls/reload-policy.js.map +1 -0
- package/dist/dev/media/hls/resolve-url.js +9 -0
- package/dist/dev/media/hls/resolve-url.js.map +1 -0
- package/dist/dev/media/live-window.js +32 -0
- package/dist/dev/media/live-window.js.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts +46 -0
- package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
- package/dist/dev/media/media-tracks/media-tracks.js +96 -0
- package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
- package/dist/dev/media/mp4/box.js +57 -0
- package/dist/dev/media/mp4/box.js.map +1 -0
- package/dist/dev/media/mp4/timestamp-origin.js +95 -0
- package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
- package/dist/dev/media/primitives/resolution.d.ts +22 -0
- package/dist/dev/media/primitives/resolution.d.ts.map +1 -0
- package/dist/dev/media/primitives/resolution.js +26 -0
- package/dist/dev/media/primitives/resolution.js.map +1 -0
- package/dist/dev/media/primitives/select-tracks.js +97 -0
- package/dist/dev/media/primitives/select-tracks.js.map +1 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js +40 -0
- package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js +22 -0
- package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
- package/dist/dev/media/types/index.d.ts +306 -0
- package/dist/dev/media/types/index.d.ts.map +1 -0
- package/dist/dev/media/types/index.js +42 -0
- package/dist/dev/media/types/index.js.map +1 -0
- package/dist/dev/media/utils/cdn.d.ts +11 -0
- package/dist/dev/media/utils/cdn.d.ts.map +1 -0
- package/dist/dev/media/utils/cdn.js +50 -0
- package/dist/dev/media/utils/cdn.js.map +1 -0
- package/dist/dev/media/utils/preload.js +21 -0
- package/dist/dev/media/utils/preload.js.map +1 -0
- package/dist/dev/media/utils/track-selection.js +43 -0
- package/dist/dev/media/utils/track-selection.js.map +1 -0
- package/dist/dev/media/utils/tracks.js +146 -0
- package/dist/dev/media/utils/tracks.js.map +1 -0
- package/dist/dev/media-tracks.d.ts +3 -0
- package/dist/dev/media-tracks.js +2 -0
- package/dist/dev/network/bandwidth-estimator.d.ts +48 -0
- package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
- package/dist/dev/network/bandwidth-estimator.js +95 -0
- package/dist/dev/network/bandwidth-estimator.js.map +1 -0
- package/dist/dev/network/chunked-stream-iterable.js +47 -0
- package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
- package/dist/dev/network/ewma.js +68 -0
- package/dist/dev/network/ewma.js.map +1 -0
- package/dist/dev/network/fetch.js +108 -0
- package/dist/dev/network/fetch.js.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts +44 -0
- package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/segment-loader.js +233 -0
- package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts +45 -0
- package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
- package/dist/dev/playback/actors/dom/source-buffer.js +139 -0
- package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
- package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -0
- package/dist/dev/playback/actors/dom/text-tracks.js +46 -0
- package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts +48 -0
- package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js +138 -0
- package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
- package/dist/dev/playback/actors/text-tracks.d.ts +30 -0
- package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +11 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js +8 -0
- package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.d.ts +52 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.js +178 -0
- package/dist/dev/playback/adapters/hls-audio/mixin.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +19 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.js +17 -0
- package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts +34 -0
- package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js +56 -0
- package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.d.ts +69 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.js +191 -0
- package/dist/dev/playback/adapters/hls-background-video/mixin.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts +11 -0
- package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js +11 -0
- package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +17 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.js +46 -0
- package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +21 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js +125 -0
- package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
- package/dist/dev/playback/adapters/hls-video/mixin.d.ts +62 -0
- package/dist/dev/playback/adapters/hls-video/mixin.d.ts.map +1 -0
- package/dist/dev/playback/adapters/hls-video/mixin.js +299 -0
- package/dist/dev/playback/adapters/hls-video/mixin.js.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +23 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js +33 -0
- package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/collect-errors.d.ts +2 -0
- package/dist/dev/playback/behaviors/collect-errors.js +98 -0
- package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js +61 -0
- package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/airplay.js +188 -0
- package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js +81 -0
- package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js +97 -0
- package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts +69 -0
- package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js +169 -0
- package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js +72 -0
- package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +113 -0
- package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +181 -0
- package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js +100 -0
- package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +28 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +40 -0
- package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +25 -0
- package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +109 -0
- package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +33 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js +50 -0
- package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +41 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js +86 -0
- package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +33 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js +35 -0
- package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts +8 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js +53 -0
- package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +28 -0
- package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +98 -0
- package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +32 -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 +187 -0
- package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts +8 -0
- package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js +76 -0
- package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
- package/dist/dev/playback/behaviors/resolve-track.js +134 -0
- package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts +61 -0
- package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/select-tracks.js +189 -0
- package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +11 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js +61 -0
- package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
- package/dist/dev/playback/behaviors/sync-preload.js +59 -0
- package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.d.ts +110 -0
- package/dist/dev/playback/behaviors/track-switching.d.ts.map +1 -0
- package/dist/dev/playback/behaviors/track-switching.js +407 -0
- package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +160 -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-background-video.d.ts +131 -0
- package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js +87 -0
- package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
- package/dist/dev/playback/engines/hls/engine.d.ts +289 -0
- package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
- package/dist/dev/playback/engines/hls/engine.js +144 -0
- package/dist/dev/playback/engines/hls/engine.js.map +1 -0
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
- package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
- package/dist/dev/playback/primitives/error-messages.js +42 -0
- package/dist/dev/playback/primitives/error-messages.js.map +1 -0
- package/dist/dev/playback/primitives/failover-fetch.js +37 -0
- package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
- package/dist/dev/playback/primitives/head-peek.js +41 -0
- package/dist/dev/playback/primitives/head-peek.js.map +1 -0
- package/dist/dev/playback/primitives/live-window.js +63 -0
- package/dist/dev/playback/primitives/live-window.js.map +1 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js +191 -0
- package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts +8 -0
- package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
- package/dist/dev/playback/primitives/report-track-conditions.js +74 -0
- package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
- package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
- package/dist/dev/playback/primitives/selection-rules.d.ts +66 -0
- package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -0
- package/dist/dev/playback/primitives/selection-rules.js +115 -0
- package/dist/dev/playback/primitives/selection-rules.js.map +1 -0
- package/dist/dev/playback/primitives/source-buffer-messages.d.ts +40 -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 +56 -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 +36 -0
- package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
- package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
- package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
- package/dist/dev/playback/primitives/track-types.js +53 -0
- package/dist/dev/playback/primitives/track-types.js.map +1 -0
- package/package.json +60 -41
- package/dist/adapter-25AtMN8s.d.ts +0 -719
- package/dist/adapter-25AtMN8s.d.ts.map +0 -1
- package/dist/adapter-CflgYzjF.js +0 -3556
- package/dist/adapter-CflgYzjF.js.map +0 -1
- package/dist/dom/index.d.ts +0 -221
- package/dist/dom/index.d.ts.map +0 -1
- package/dist/dom/index.js +0 -38
- package/dist/dom/index.js.map +0 -1
- package/dist/dom/playback-engine/index.d.ts +0 -2
- package/dist/dom/playback-engine/index.js +0 -4
- package/dist/index.d.ts +0 -13
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/src-DRsS_H80.js +0 -14
- package/dist/src-DRsS_H80.js.map +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { findBox, iterateBoxesOfType, readFourCC, readFullBoxVersion, toDataView } from "./box.js";
|
|
2
|
+
//#region src/media/mp4/timestamp-origin.ts
|
|
3
|
+
/**
|
|
4
|
+
* Decode-time origin extraction from fMP4/CMAF segments.
|
|
5
|
+
*
|
|
6
|
+
* Non-zero-PTS sources encode media at a non-zero start time (an instant clip starting at asset second 60, an Apple
|
|
7
|
+
* bipbop asset starting at 10s, …). To relocate such a source onto a 0-based presentation timeline via
|
|
8
|
+
* `SourceBuffer.timestampOffset`, the engine needs that start time — the **decode-time origin** — read straight from
|
|
9
|
+
* the container:
|
|
10
|
+
*
|
|
11
|
+
* - `tfdt.baseMediaDecodeTime` (from a media segment's `moof`) — the decode time of the segment's first sample, in the
|
|
12
|
+
* track's media timescale. This is a DTS: relocating by `−(baseMediaDecodeTime / timescale)` lands the earliest DTS
|
|
13
|
+
* at exactly 0, so a negative-DTS append failure on Chromium is impossible by construction.
|
|
14
|
+
* - `mdhd.timescale` (from the init segment's `moov`) — ticks per second for that track, needed to convert the raw tick
|
|
15
|
+
* count to seconds.
|
|
16
|
+
*
|
|
17
|
+
* ## Presumptive vs. track-selected reads
|
|
18
|
+
*
|
|
19
|
+
* The two variants share only the leaf field-readers (`mdhd` timescale, `tfdt` baseMediaDecodeTime) and the box walker.
|
|
20
|
+
* Everything else differs, and the split is deliberate so the presumptive pair is _proportionally_ smaller under
|
|
21
|
+
* tree-shaking:
|
|
22
|
+
*
|
|
23
|
+
* - **Presumptive** — {@link readFirstMediaTimescale} / {@link readFirstBaseMediaDecodeTime} read the first `mdhd`
|
|
24
|
+
* timescale and first `tfdt` baseMediaDecodeTime. There's no `track_id` (nothing to match against) and no
|
|
25
|
+
* `trak`/`traf` iteration — a direct `findBox` to the first leaf. Correct when the init/segment holds a single media
|
|
26
|
+
* track (the common CMAF case). A caption-free platform imports only this pair and tree-shakes away all the matching
|
|
27
|
+
* machinery below.
|
|
28
|
+
* - **Track-selected** — {@link findMediaTrack} / {@link readBaseMediaDecodeTime}. `findMediaTrack` returns `{ trackId,
|
|
29
|
+
* timescale }` for the `trak` whose `hdlr` handler matches; `readBaseMediaDecodeTime` takes that `trackId` and reads
|
|
30
|
+
* the `traf` whose `tfhd.track_id` matches. The `track_id` is the join that ties one track's timescale to the _same_
|
|
31
|
+
* track's baseMediaDecodeTime — required when a source muxes CEA-608/708 captions (a `clcp` track shares the same
|
|
32
|
+
* `moov` and `moof`, each track with its own timescale + baseMediaDecodeTime, so a presumptive read there risks
|
|
33
|
+
* `300000 / 6000 = 50s` instead of `60000 / 6000 = 10s`). This pair adds `trak`/`traf` iteration plus the handler and
|
|
34
|
+
* `track_id` reads. We only ever ask for buffered media handlers (`vide` / `soun`); the caption track is never
|
|
35
|
+
* selected — we read the origin, not the captions (caption _rendering_ is out of scope).
|
|
36
|
+
*
|
|
37
|
+
* Both raw values are returned un-divided to leave room for an edit-list (`elst`) presentation-time correction term if
|
|
38
|
+
* a source ever carries one — the validated streams do not.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The `track_ID` + timescale of the `trak` whose `mdhd` handler matches `handlerType`, skipping a muxed `clcp` caption
|
|
42
|
+
* track. `undefined` if no matching track exists.
|
|
43
|
+
*/
|
|
44
|
+
function findMediaTrack(initSegment, handlerType) {
|
|
45
|
+
const view = toDataView(initSegment);
|
|
46
|
+
const moov = findBox(view, ["moov"]);
|
|
47
|
+
if (!moov) return void 0;
|
|
48
|
+
for (const trak of iterateBoxesOfType(view, "trak", moov.dataStart, moov.end)) {
|
|
49
|
+
if (readTrakHandler(view, trak) !== handlerType) continue;
|
|
50
|
+
const tkhd = findBox(view, ["tkhd"], trak.dataStart, trak.end);
|
|
51
|
+
const mdhd = findBox(view, ["mdia", "mdhd"], trak.dataStart, trak.end);
|
|
52
|
+
if (!tkhd || !mdhd) return void 0;
|
|
53
|
+
return {
|
|
54
|
+
trackId: view.getUint32(tkhd.dataStart + 4 + (readFullBoxVersion(view, tkhd.dataStart) === 1 ? 16 : 8)),
|
|
55
|
+
timescale: readMdhdTimescale(view, mdhd)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `baseMediaDecodeTime` (in the track's media timescale) from the `traf` matching `trackId` (via `tfhd.track_id`) —
|
|
61
|
+
* required for muxed multi-`traf` segments. `undefined` if no matching `tfdt` exists.
|
|
62
|
+
*/
|
|
63
|
+
function readBaseMediaDecodeTime(mediaSegment, trackId) {
|
|
64
|
+
const view = toDataView(mediaSegment);
|
|
65
|
+
const moof = findBox(view, ["moof"]);
|
|
66
|
+
if (!moof) return void 0;
|
|
67
|
+
for (const traf of iterateBoxesOfType(view, "traf", moof.dataStart, moof.end)) {
|
|
68
|
+
if (readTrafTrackId(view, traf) !== trackId) continue;
|
|
69
|
+
const tfdt = findBox(view, ["tfdt"], traf.dataStart, traf.end);
|
|
70
|
+
return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : void 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** `mdhd.timescale`: FullBox version(1)+flags(3) + dates (v0: 4+4, v1: 8+8) + timescale. */
|
|
74
|
+
function readMdhdTimescale(view, mdhd) {
|
|
75
|
+
return view.getUint32(mdhd.dataStart + 4 + (readFullBoxVersion(view, mdhd.dataStart) === 1 ? 16 : 8));
|
|
76
|
+
}
|
|
77
|
+
/** `tfdt.baseMediaDecodeTime`: FullBox, then the value — v0: (4), v1: (8). */
|
|
78
|
+
function readTfdtBaseMediaDecodeTime(view, tfdt) {
|
|
79
|
+
const at = tfdt.dataStart + 4;
|
|
80
|
+
return readFullBoxVersion(view, tfdt.dataStart) === 1 ? Number(view.getBigUint64(at)) : view.getUint32(at);
|
|
81
|
+
}
|
|
82
|
+
/** `hdlr.handler_type` for a `trak`: FullBox version(1)+flags(3) + pre_defined(4) + handler_type(4). */
|
|
83
|
+
function readTrakHandler(view, trak) {
|
|
84
|
+
const hdlr = findBox(view, ["mdia", "hdlr"], trak.dataStart, trak.end);
|
|
85
|
+
return hdlr ? readFourCC(view, hdlr.dataStart + 8) : void 0;
|
|
86
|
+
}
|
|
87
|
+
/** `tfhd.track_id` for a `traf`: FullBox version(1)+flags(3) + track_id(4). */
|
|
88
|
+
function readTrafTrackId(view, traf) {
|
|
89
|
+
const tfhd = findBox(view, ["tfhd"], traf.dataStart, traf.end);
|
|
90
|
+
return tfhd ? view.getUint32(tfhd.dataStart + 4) : void 0;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { findMediaTrack, readBaseMediaDecodeTime };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=timestamp-origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamp-origin.js","names":[],"sources":["../../../../src/media/mp4/timestamp-origin.ts"],"sourcesContent":["/**\n * Decode-time origin extraction from fMP4/CMAF segments.\n *\n * Non-zero-PTS sources encode media at a non-zero start time (an instant clip starting at asset second 60, an Apple\n * bipbop asset starting at 10s, …). To relocate such a source onto a 0-based presentation timeline via\n * `SourceBuffer.timestampOffset`, the engine needs that start time — the **decode-time origin** — read straight from\n * the container:\n *\n * - `tfdt.baseMediaDecodeTime` (from a media segment's `moof`) — the decode time of the segment's first sample, in the\n * track's media timescale. This is a DTS: relocating by `−(baseMediaDecodeTime / timescale)` lands the earliest DTS\n * at exactly 0, so a negative-DTS append failure on Chromium is impossible by construction.\n * - `mdhd.timescale` (from the init segment's `moov`) — ticks per second for that track, needed to convert the raw tick\n * count to seconds.\n *\n * ## Presumptive vs. track-selected reads\n *\n * The two variants share only the leaf field-readers (`mdhd` timescale, `tfdt` baseMediaDecodeTime) and the box walker.\n * Everything else differs, and the split is deliberate so the presumptive pair is _proportionally_ smaller under\n * tree-shaking:\n *\n * - **Presumptive** — {@link readFirstMediaTimescale} / {@link readFirstBaseMediaDecodeTime} read the first `mdhd`\n * timescale and first `tfdt` baseMediaDecodeTime. There's no `track_id` (nothing to match against) and no\n * `trak`/`traf` iteration — a direct `findBox` to the first leaf. Correct when the init/segment holds a single media\n * track (the common CMAF case). A caption-free platform imports only this pair and tree-shakes away all the matching\n * machinery below.\n * - **Track-selected** — {@link findMediaTrack} / {@link readBaseMediaDecodeTime}. `findMediaTrack` returns `{ trackId,\n * timescale }` for the `trak` whose `hdlr` handler matches; `readBaseMediaDecodeTime` takes that `trackId` and reads\n * the `traf` whose `tfhd.track_id` matches. The `track_id` is the join that ties one track's timescale to the _same_\n * track's baseMediaDecodeTime — required when a source muxes CEA-608/708 captions (a `clcp` track shares the same\n * `moov` and `moof`, each track with its own timescale + baseMediaDecodeTime, so a presumptive read there risks\n * `300000 / 6000 = 50s` instead of `60000 / 6000 = 10s`). This pair adds `trak`/`traf` iteration plus the handler and\n * `track_id` reads. We only ever ask for buffered media handlers (`vide` / `soun`); the caption track is never\n * selected — we read the origin, not the captions (caption _rendering_ is out of scope).\n *\n * Both raw values are returned un-divided to leave room for an edit-list (`elst`) presentation-time correction term if\n * a source ever carries one — the validated streams do not.\n */\nimport { type Box, findBox, iterateBoxesOfType, readFourCC, readFullBoxVersion, toDataView } from './box';\n\n/** MSE-buffered media handler types (`mdhd`/`hdlr`). Captions/subtitles excluded. */\nexport type MediaHandlerType = 'vide' | 'soun';\n\nexport interface MediaTrackInfo {\n /** `tkhd.track_id` — used to match the corresponding `traf` in media segments. */\n trackId: number;\n /** `mdhd.timescale` — ticks per second for this track. */\n timescale: number;\n}\n\n// --- presumptive: first leaf, no track_id, no iteration -----------------------\n\n/**\n * Presumptive: the `timescale` of the **first** `mdhd` in an init segment. Correct only for single-media-track inits —\n * for muxed captions use {@link findMediaTrack}. `undefined` if no `mdhd` exists.\n */\nexport function readFirstMediaTimescale(initSegment: ArrayBuffer | Uint8Array): number | undefined {\n const view = toDataView(initSegment);\n const mdhd = findBox(view, ['moov', 'trak', 'mdia', 'mdhd']);\n\n return mdhd ? readMdhdTimescale(view, mdhd) : undefined;\n}\n\n/**\n * Presumptive: `baseMediaDecodeTime` from the **first** `tfdt` of a media segment. Correct only for single-`traf`\n * segments — for muxed captions use {@link readBaseMediaDecodeTime}. `undefined` if no `tfdt` exists.\n */\nexport function readFirstBaseMediaDecodeTime(mediaSegment: ArrayBuffer | Uint8Array): number | undefined {\n const view = toDataView(mediaSegment);\n const tfdt = findBox(view, ['moof', 'traf', 'tfdt']);\n\n return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : undefined;\n}\n\n// --- track-selected: iterate + match by handler / track_id --------------------\n\n/**\n * The `track_ID` + timescale of the `trak` whose `mdhd` handler matches `handlerType`, skipping a muxed `clcp` caption\n * track. `undefined` if no matching track exists.\n */\nexport function findMediaTrack(\n initSegment: ArrayBuffer | Uint8Array,\n handlerType: MediaHandlerType\n): MediaTrackInfo | undefined {\n const view = toDataView(initSegment);\n const moov = findBox(view, ['moov']);\n if (!moov) return undefined;\n\n for (const trak of iterateBoxesOfType(view, 'trak', moov.dataStart, moov.end)) {\n if (readTrakHandler(view, trak) !== handlerType) continue;\n\n const tkhd = findBox(view, ['tkhd'], trak.dataStart, trak.end);\n const mdhd = findBox(view, ['mdia', 'mdhd'], trak.dataStart, trak.end);\n if (!tkhd || !mdhd) return undefined;\n\n // tkhd FullBox: version(1)+flags(3), creation/modification dates (v0: 4+4, v1:\n // 8+8), then track_id.\n const trackId = view.getUint32(tkhd.dataStart + 4 + (readFullBoxVersion(view, tkhd.dataStart) === 1 ? 16 : 8));\n\n return { trackId, timescale: readMdhdTimescale(view, mdhd) };\n }\n\n return undefined;\n}\n\n/**\n * `baseMediaDecodeTime` (in the track's media timescale) from the `traf` matching `trackId` (via `tfhd.track_id`) —\n * required for muxed multi-`traf` segments. `undefined` if no matching `tfdt` exists.\n */\nexport function readBaseMediaDecodeTime(mediaSegment: ArrayBuffer | Uint8Array, trackId: number): number | undefined {\n const view = toDataView(mediaSegment);\n const moof = findBox(view, ['moof']);\n if (!moof) return undefined;\n\n for (const traf of iterateBoxesOfType(view, 'traf', moof.dataStart, moof.end)) {\n if (readTrafTrackId(view, traf) !== trackId) continue;\n\n const tfdt = findBox(view, ['tfdt'], traf.dataStart, traf.end);\n\n return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : undefined;\n }\n\n return undefined;\n}\n\n// --- shared leaf field-readers ------------------------------------------------\n\n/** `mdhd.timescale`: FullBox version(1)+flags(3) + dates (v0: 4+4, v1: 8+8) + timescale. */\nfunction readMdhdTimescale(view: DataView, mdhd: Box): number {\n return view.getUint32(mdhd.dataStart + 4 + (readFullBoxVersion(view, mdhd.dataStart) === 1 ? 16 : 8));\n}\n\n/** `tfdt.baseMediaDecodeTime`: FullBox, then the value — v0: (4), v1: (8). */\nfunction readTfdtBaseMediaDecodeTime(view: DataView, tfdt: Box): number {\n const at = tfdt.dataStart + 4;\n\n return readFullBoxVersion(view, tfdt.dataStart) === 1 ? Number(view.getBigUint64(at)) : view.getUint32(at);\n}\n\n// --- track-selection readers (referenced only by the track-selected variants) -\n\n/** `hdlr.handler_type` for a `trak`: FullBox version(1)+flags(3) + pre_defined(4) + handler_type(4). */\nfunction readTrakHandler(view: DataView, trak: Box): string | undefined {\n const hdlr = findBox(view, ['mdia', 'hdlr'], trak.dataStart, trak.end);\n\n return hdlr ? readFourCC(view, hdlr.dataStart + 8) : undefined;\n}\n\n/** `tfhd.track_id` for a `traf`: FullBox version(1)+flags(3) + track_id(4). */\nfunction readTrafTrackId(view: DataView, traf: Box): number | undefined {\n const tfhd = findBox(view, ['tfhd'], traf.dataStart, traf.end);\n\n return tfhd ? view.getUint32(tfhd.dataStart + 4) : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,SAAgB,eACd,aACA,aAC4B;CAC5B,MAAM,OAAO,WAAW,WAAW;CACnC,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,CAAC;CACnC,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,KAAK,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,KAAK,WAAW,KAAK,GAAG,GAAG;EAC7E,IAAI,gBAAgB,MAAM,IAAI,MAAM,aAAa;EAEjD,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;EAC7D,MAAM,OAAO,QAAQ,MAAM,CAAC,QAAQ,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;EACrE,IAAI,CAAC,QAAQ,CAAC,MAAM,OAAO,KAAA;EAM3B,OAAO;GAAE,SAFO,KAAK,UAAU,KAAK,YAAY,KAAK,mBAAmB,MAAM,KAAK,SAAS,MAAM,IAAI,KAAK,EAE5F;GAAG,WAAW,kBAAkB,MAAM,IAAI;EAAE;CAC7D;AAGF;;;;;AAMA,SAAgB,wBAAwB,cAAwC,SAAqC;CACnH,MAAM,OAAO,WAAW,YAAY;CACpC,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,CAAC;CACnC,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,KAAK,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,KAAK,WAAW,KAAK,GAAG,GAAG;EAC7E,IAAI,gBAAgB,MAAM,IAAI,MAAM,SAAS;EAE7C,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;EAE7D,OAAO,OAAO,4BAA4B,MAAM,IAAI,IAAI,KAAA;CAC1D;AAGF;;AAKA,SAAS,kBAAkB,MAAgB,MAAmB;CAC5D,OAAO,KAAK,UAAU,KAAK,YAAY,KAAK,mBAAmB,MAAM,KAAK,SAAS,MAAM,IAAI,KAAK,EAAE;AACtG;;AAGA,SAAS,4BAA4B,MAAgB,MAAmB;CACtE,MAAM,KAAK,KAAK,YAAY;CAE5B,OAAO,mBAAmB,MAAM,KAAK,SAAS,MAAM,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC,IAAI,KAAK,UAAU,EAAE;AAC3G;;AAKA,SAAS,gBAAgB,MAAgB,MAA+B;CACtE,MAAM,OAAO,QAAQ,MAAM,CAAC,QAAQ,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;CAErE,OAAO,OAAO,WAAW,MAAM,KAAK,YAAY,CAAC,IAAI,KAAA;AACvD;;AAGA,SAAS,gBAAgB,MAAgB,MAA+B;CACtE,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;CAE7D,OAAO,OAAO,KAAK,UAAU,KAAK,YAAY,CAAC,IAAI,KAAA;AACrD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/media/primitives/resolution.ts
|
|
2
|
+
/**
|
|
3
|
+
* Apply `scale` to a size and normalize it to whole pixels, or `undefined` where that leaves nothing to describe.
|
|
4
|
+
*
|
|
5
|
+
* Rounded because pixels are whole and a fractional scale doesn't divide a surface evenly. A non-positive or non-finite
|
|
6
|
+
* axis yields `undefined` rather than a zero-area reading: an element that isn't being rendered reports `0 × 0` and a
|
|
7
|
+
* nonsense dimension reports `NaN`, and the caps consuming this read absence as "unknown, don't cap" while an area of
|
|
8
|
+
* zero would read as a cap of zero — pinning every source to its smallest rendition.
|
|
9
|
+
*
|
|
10
|
+
* @param size - Dimensions to project, in the units `scale` converts from
|
|
11
|
+
* @param scale - Multiplier for both axes, e.g. `devicePixelRatio`; defaults to 1
|
|
12
|
+
* @returns The scaled, whole-pixel resolution, or `undefined` when either axis
|
|
13
|
+
* doesn't survive it
|
|
14
|
+
*/
|
|
15
|
+
function scaleResolution(size, scale = 1) {
|
|
16
|
+
const width = Math.round(size.width * scale);
|
|
17
|
+
const height = Math.round(size.height * scale);
|
|
18
|
+
return width > 0 && height > 0 ? {
|
|
19
|
+
width,
|
|
20
|
+
height
|
|
21
|
+
} : void 0;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { scaleResolution };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=resolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolution.js","names":[],"sources":["../../../../src/media/primitives/resolution.ts"],"sourcesContent":["/**\n * Pixel dimensions, and the one projection every reader of them needs.\n *\n * Shared by the surfaces a rendition cap measures — a screen (`media/dom/screen`), a player element\n * (`behaviors/dom/track-player-resolution`) — and by the caps that compare renditions against them. Lives outside the\n * DOM layer because scaling and rounding two numbers needs no DOM, which is also what lets the DOM-free selection rules\n * name the type they read instead of restating its shape.\n */\n\n/**\n * A surface's pixel dimensions.\n *\n * A width and a height rather than a `\"720p\"`-style tier, because a tier only describes a surface once you assume its\n * aspect ratio — the assumption that mis-measures an anamorphic or otherwise non-16:9 rendition.\n */\nexport interface Resolution {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Apply `scale` to a size and normalize it to whole pixels, or `undefined` where that leaves nothing to describe.\n *\n * Rounded because pixels are whole and a fractional scale doesn't divide a surface evenly. A non-positive or non-finite\n * axis yields `undefined` rather than a zero-area reading: an element that isn't being rendered reports `0 × 0` and a\n * nonsense dimension reports `NaN`, and the caps consuming this read absence as \"unknown, don't cap\" while an area of\n * zero would read as a cap of zero — pinning every source to its smallest rendition.\n *\n * @param size - Dimensions to project, in the units `scale` converts from\n * @param scale - Multiplier for both axes, e.g. `devicePixelRatio`; defaults to 1\n * @returns The scaled, whole-pixel resolution, or `undefined` when either axis\n * doesn't survive it\n */\nexport function scaleResolution(size: Resolution, scale = 1): Resolution | undefined {\n const width = Math.round(size.width * scale);\n const height = Math.round(size.height * scale);\n\n return width > 0 && height > 0 ? { width, height } : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;AAiCA,SAAgB,gBAAgB,MAAkB,QAAQ,GAA2B;CACnF,MAAM,QAAQ,KAAK,MAAM,KAAK,QAAQ,KAAK;CAC3C,MAAM,SAAS,KAAK,MAAM,KAAK,SAAS,KAAK;CAE7C,OAAO,QAAQ,KAAK,SAAS,IAAI;EAAE;EAAO;CAAO,IAAI,KAAA;AACvD"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//#region src/media/primitives/select-tracks.ts
|
|
2
|
+
/**
|
|
3
|
+
* Test whether a track matches a partial-track description: every present, defined field of `filter` equals the
|
|
4
|
+
* track's. Absent or `undefined` filter fields don't constrain. Used to narrow candidates by a user selection (`{ id
|
|
5
|
+
* }`, `{ language }`, `{ height }`, …).
|
|
6
|
+
*
|
|
7
|
+
* @param track - The track to test
|
|
8
|
+
* @param filter - Partial-track description; only present, defined fields constrain
|
|
9
|
+
* @returns `true` when the track matches every constraining field
|
|
10
|
+
*/
|
|
11
|
+
function matchesPartialTrack(track, filter) {
|
|
12
|
+
for (const key in filter) {
|
|
13
|
+
const filterValue = filter[key];
|
|
14
|
+
if (filterValue !== void 0 && track[key] !== filterValue) return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
/** Missing dimensions are treated as area `0`, so a track without them ranks last. */
|
|
19
|
+
function pixelArea(track) {
|
|
20
|
+
return (track.width ?? 0) * (track.height ?? 0);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Narrow to the tracks at or below `maxPixelArea`, and nothing else: no ordering, no fallback. Survivors keep their
|
|
24
|
+
* incoming order, and an empty result is a real answer — "none of these fit".
|
|
25
|
+
*
|
|
26
|
+
* Deliberately only the filter, because as a selection rule this composes under `applyRules`, which already owns both
|
|
27
|
+
* halves a caller might expect here: an empty result is skipped, so a preference can never narrow the candidate set to
|
|
28
|
+
* nothing; and ordering the survivors is a separate rule's job ({@link byDescendingResolution}, bandwidth ABR). Doing
|
|
29
|
+
* either here would duplicate the composer and give one rule two responsibilities.
|
|
30
|
+
*/
|
|
31
|
+
function tracksUnderPixelArea(tracks, maxPixelArea = Number.POSITIVE_INFINITY) {
|
|
32
|
+
return tracks.filter((track) => pixelArea(track) <= maxPixelArea);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The smallest track area that still covers `minPixelArea`, or `undefined` when no track reaches it.
|
|
36
|
+
*
|
|
37
|
+
* The cap a surface-size rule wants when it should round _up_ to the ladder: a surface between two tiers is covered by
|
|
38
|
+
* the upper one, and capping at the surface's own area instead would serve a picture smaller than the surface and
|
|
39
|
+
* upscale it.
|
|
40
|
+
*
|
|
41
|
+
* Tracks declaring no dimensions compare as area `0` and so never cover anything, which keeps them out of the cap
|
|
42
|
+
* rather than pinning it to zero.
|
|
43
|
+
*/
|
|
44
|
+
function smallestCoveringPixelArea(tracks, minPixelArea) {
|
|
45
|
+
const covering = tracks.map(pixelArea).filter((area) => area >= minPixelArea);
|
|
46
|
+
return covering.length ? Math.min(...covering) : void 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compare two tracks by resolution, largest first, with bandwidth as the tiebreak for renditions of identical
|
|
50
|
+
* dimensions. Missing dimensions are treated as area `0`, so a track without them sorts last.
|
|
51
|
+
*
|
|
52
|
+
* A comparator rather than a "highest track" function: the selection-rule chain takes the head of the list it produces,
|
|
53
|
+
* so ranking never has to collapse to a single track. `preferHighestResolution` is `sort` over this and nothing more.
|
|
54
|
+
*/
|
|
55
|
+
function byDescendingResolution(a, b) {
|
|
56
|
+
return pixelArea(b) - pixelArea(a) || (b.bandwidth ?? 0) - (a.bandwidth ?? 0);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Default audio policy over a candidate list: the three-tier pick a selection-rule chain applies once it has narrowed
|
|
60
|
+
* the candidates.
|
|
61
|
+
*
|
|
62
|
+
* Priority: `preferredAudioLanguage` match → `DEFAULT=YES` → first track.
|
|
63
|
+
*/
|
|
64
|
+
function pickAudioTrackFromTracks(tracks, config) {
|
|
65
|
+
if (config?.preferredAudioLanguage) {
|
|
66
|
+
const languageMatch = tracks.find((track) => track.language === config.preferredAudioLanguage);
|
|
67
|
+
if (languageMatch) return languageMatch.id;
|
|
68
|
+
}
|
|
69
|
+
const defaultTrack = tracks.find((track) => track.default === true);
|
|
70
|
+
if (defaultTrack) return defaultTrack.id;
|
|
71
|
+
return tracks[0]?.id;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Default text-track policy over a candidate list: the opt-in three-tier pick `switchTextTrack`'s terminal applies once
|
|
75
|
+
* it has narrowed the renditions to the constrained, CDN-scoped set.
|
|
76
|
+
*
|
|
77
|
+
* Priority: `preferredSubtitleLanguage` match → `DEFAULT=YES + AUTOSELECT=YES` (only when `enableDefaultTrack`) →
|
|
78
|
+
* `undefined` (opt-in). FORCED tracks are excluded unless `includeForcedTracks` (Apple-spec: a regular track must carry
|
|
79
|
+
* forced content when both exist, so a forced-only track is redundant).
|
|
80
|
+
*/
|
|
81
|
+
function pickTextTrackFromTracks(tracks, config) {
|
|
82
|
+
const availableTracks = config?.includeForcedTracks ? tracks : tracks.filter((track) => !track.forced);
|
|
83
|
+
if (availableTracks.length === 0) return void 0;
|
|
84
|
+
const { preferredSubtitleLanguage, enableDefaultTrack = false } = config ?? {};
|
|
85
|
+
if (preferredSubtitleLanguage) {
|
|
86
|
+
const languageMatch = availableTracks.find((track) => track.language === preferredSubtitleLanguage);
|
|
87
|
+
if (languageMatch) return languageMatch.id;
|
|
88
|
+
}
|
|
89
|
+
if (enableDefaultTrack) {
|
|
90
|
+
const defaultTrack = availableTracks.find((track) => track.default === true);
|
|
91
|
+
if (defaultTrack) return defaultTrack.id;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { byDescendingResolution, matchesPartialTrack, pickAudioTrackFromTracks, pickTextTrackFromTracks, smallestCoveringPixelArea, tracksUnderPixelArea };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=select-tracks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-tracks.js","names":[],"sources":["../../../../src/media/primitives/select-tracks.ts"],"sourcesContent":["import type { MaybeResolvedPresentation, PartiallyResolvedTextTrack, TextTrack } from '../types';\n\n/** State shape for track selection. */\nexport interface TrackSelectionState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n}\n\n/** Configuration for audio track selection. */\nexport interface AudioSelectionConfig {\n /** Preferred audio language (ISO 639 code, e.g., \"en\", \"es\"). If not specified, selects first audio track. */\n preferredAudioLanguage?: string;\n}\n\n/** Configuration for text track selection. */\nexport interface TextSelectionConfig {\n /** Preferred subtitle language (ISO 639 code, e.g., \"en\", \"es\"). If specified, selects matching track if available. */\n preferredSubtitleLanguage?: string;\n\n /**\n * Include FORCED subtitle tracks in selection. Default: false (follows hls.js/http-streaming pattern)\n *\n * Note: Per Apple's HLS spec, if content has forced and regular subtitles in the same language, the regular track\n * MUST contain both forced and regular content. Therefore, forced-only tracks are redundant and excluded by default.\n */\n includeForcedTracks?: boolean;\n\n /**\n * Auto-select DEFAULT track (requires DEFAULT=YES + AUTOSELECT=YES in HLS). Default: false (user opt-in, matches\n * hls.js/http-streaming)\n *\n * When enabled, tracks marked with both DEFAULT=YES and AUTOSELECT=YES will be automatically selected if no user\n * preference matches.\n */\n enableDefaultTrack?: boolean;\n}\n\n// =============================================================================\n// Helper Functions (Pure Selection Logic)\n//\n// Candidate-list policies and track geometry, for the selection rules in\n// `playback/behaviors/select-tracks.ts` and `playback/behaviors/track-switching.ts`\n// to compose. Nothing here consults a whole presentation or returns a single id:\n// narrowing a list and ordering a list are the two shapes a rule can take, and the\n// rule chain takes the head of what they leave.\n// =============================================================================\n\n/**\n * Test whether a track matches a partial-track description: every present, defined field of `filter` equals the\n * track's. Absent or `undefined` filter fields don't constrain. Used to narrow candidates by a user selection (`{ id\n * }`, `{ language }`, `{ height }`, …).\n *\n * @param track - The track to test\n * @param filter - Partial-track description; only present, defined fields constrain\n * @returns `true` when the track matches every constraining field\n */\nexport function matchesPartialTrack<T>(track: T, filter: Partial<T>): boolean {\n for (const key in filter) {\n const filterValue = filter[key as keyof T];\n if (filterValue !== undefined && track[key as keyof T] !== filterValue) return false;\n }\n\n return true;\n}\n\ntype RankableTrack = { id: string; width?: number; height?: number; bandwidth?: number };\n\n/** Missing dimensions are treated as area `0`, so a track without them ranks last. */\nfunction pixelArea(track: RankableTrack): number {\n return (track.width ?? 0) * (track.height ?? 0);\n}\n\n/**\n * Narrow to the tracks at or below `maxPixelArea`, and nothing else: no ordering, no fallback. Survivors keep their\n * incoming order, and an empty result is a real answer — \"none of these fit\".\n *\n * Deliberately only the filter, because as a selection rule this composes under `applyRules`, which already owns both\n * halves a caller might expect here: an empty result is skipped, so a preference can never narrow the candidate set to\n * nothing; and ordering the survivors is a separate rule's job ({@link byDescendingResolution}, bandwidth ABR). Doing\n * either here would duplicate the composer and give one rule two responsibilities.\n */\nexport function tracksUnderPixelArea<T extends RankableTrack>(\n tracks: readonly T[],\n maxPixelArea: number = Number.POSITIVE_INFINITY\n): readonly T[] {\n return tracks.filter((track) => pixelArea(track) <= maxPixelArea);\n}\n\n/**\n * The smallest track area that still covers `minPixelArea`, or `undefined` when no track reaches it.\n *\n * The cap a surface-size rule wants when it should round _up_ to the ladder: a surface between two tiers is covered by\n * the upper one, and capping at the surface's own area instead would serve a picture smaller than the surface and\n * upscale it.\n *\n * Tracks declaring no dimensions compare as area `0` and so never cover anything, which keeps them out of the cap\n * rather than pinning it to zero.\n */\nexport function smallestCoveringPixelArea(tracks: readonly RankableTrack[], minPixelArea: number): number | undefined {\n const covering = tracks.map(pixelArea).filter((area) => area >= minPixelArea);\n\n return covering.length ? Math.min(...covering) : undefined;\n}\n\n/**\n * Compare two tracks by resolution, largest first, with bandwidth as the tiebreak for renditions of identical\n * dimensions. Missing dimensions are treated as area `0`, so a track without them sorts last.\n *\n * A comparator rather than a \"highest track\" function: the selection-rule chain takes the head of the list it produces,\n * so ranking never has to collapse to a single track. `preferHighestResolution` is `sort` over this and nothing more.\n */\nexport function byDescendingResolution(a: RankableTrack, b: RankableTrack): number {\n return pixelArea(b) - pixelArea(a) || (b.bandwidth ?? 0) - (a.bandwidth ?? 0);\n}\n\n/**\n * Default audio policy over a candidate list: the three-tier pick a selection-rule chain applies once it has narrowed\n * the candidates.\n *\n * Priority: `preferredAudioLanguage` match → `DEFAULT=YES` → first track.\n */\nexport function pickAudioTrackFromTracks(\n tracks: readonly { id: string; language?: string | undefined; default?: boolean | undefined }[],\n config?: AudioSelectionConfig\n): string | undefined {\n // Try preferred language first\n if (config?.preferredAudioLanguage) {\n const languageMatch = tracks.find((track) => track.language === config.preferredAudioLanguage);\n if (languageMatch) return languageMatch.id;\n }\n\n // Try default track\n const defaultTrack = tracks.find((track) => track.default === true);\n if (defaultTrack) return defaultTrack.id;\n\n // Fall back to first track\n return tracks[0]?.id;\n}\n\n/**\n * Default text-track policy over a candidate list: the opt-in three-tier pick `switchTextTrack`'s terminal applies once\n * it has narrowed the renditions to the constrained, CDN-scoped set.\n *\n * Priority: `preferredSubtitleLanguage` match → `DEFAULT=YES + AUTOSELECT=YES` (only when `enableDefaultTrack`) →\n * `undefined` (opt-in). FORCED tracks are excluded unless `includeForcedTracks` (Apple-spec: a regular track must carry\n * forced content when both exist, so a forced-only track is redundant).\n */\nexport function pickTextTrackFromTracks(\n tracks: readonly (PartiallyResolvedTextTrack | TextTrack)[],\n config?: TextSelectionConfig\n): string | undefined {\n const availableTracks = config?.includeForcedTracks ? tracks : tracks.filter((track) => !track.forced);\n if (availableTracks.length === 0) return undefined;\n\n const { preferredSubtitleLanguage, enableDefaultTrack = false } = config ?? {};\n\n if (preferredSubtitleLanguage) {\n const languageMatch = availableTracks.find((track) => track.language === preferredSubtitleLanguage);\n if (languageMatch) return languageMatch.id;\n }\n\n if (enableDefaultTrack) {\n const defaultTrack = availableTracks.find((track) => track.default === true);\n if (defaultTrack) return defaultTrack.id;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;AA0DA,SAAgB,oBAAuB,OAAU,QAA6B;CAC5E,KAAK,MAAM,OAAO,QAAQ;EACxB,MAAM,cAAc,OAAO;EAC3B,IAAI,gBAAgB,KAAA,KAAa,MAAM,SAAoB,aAAa,OAAO;CACjF;CAEA,OAAO;AACT;;AAKA,SAAS,UAAU,OAA8B;CAC/C,QAAQ,MAAM,SAAS,MAAM,MAAM,UAAU;AAC/C;;;;;;;;;;AAWA,SAAgB,qBACd,QACA,eAAuB,OAAO,mBAChB;CACd,OAAO,OAAO,QAAQ,UAAU,UAAU,KAAK,KAAK,YAAY;AAClE;;;;;;;;;;;AAYA,SAAgB,0BAA0B,QAAkC,cAA0C;CACpH,MAAM,WAAW,OAAO,IAAI,SAAS,CAAC,CAAC,QAAQ,SAAS,QAAQ,YAAY;CAE5E,OAAO,SAAS,SAAS,KAAK,IAAI,GAAG,QAAQ,IAAI,KAAA;AACnD;;;;;;;;AASA,SAAgB,uBAAuB,GAAkB,GAA0B;CACjF,OAAO,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,aAAa;AAC7E;;;;;;;AAQA,SAAgB,yBACd,QACA,QACoB;CAEpB,IAAI,QAAQ,wBAAwB;EAClC,MAAM,gBAAgB,OAAO,MAAM,UAAU,MAAM,aAAa,OAAO,sBAAsB;EAC7F,IAAI,eAAe,OAAO,cAAc;CAC1C;CAGA,MAAM,eAAe,OAAO,MAAM,UAAU,MAAM,YAAY,IAAI;CAClE,IAAI,cAAc,OAAO,aAAa;CAGtC,OAAO,OAAO,EAAE,EAAE;AACpB;;;;;;;;;AAUA,SAAgB,wBACd,QACA,QACoB;CACpB,MAAM,kBAAkB,QAAQ,sBAAsB,SAAS,OAAO,QAAQ,UAAU,CAAC,MAAM,MAAM;CACrG,IAAI,gBAAgB,WAAW,GAAG,OAAO,KAAA;CAEzC,MAAM,EAAE,2BAA2B,qBAAqB,UAAU,UAAU,CAAC;CAE7E,IAAI,2BAA2B;EAC7B,MAAM,gBAAgB,gBAAgB,MAAM,UAAU,MAAM,aAAa,yBAAyB;EAClG,IAAI,eAAe,OAAO,cAAc;CAC1C;CAEA,IAAI,oBAAoB;EACtB,MAAM,eAAe,gBAAgB,MAAM,UAAU,MAAM,YAAY,IAAI;EAC3E,IAAI,cAAc,OAAO,aAAa;CACxC;AAGF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/media/text/parse-vtt-timestamp-map.ts
|
|
2
|
+
const TIMESTAMP_MAP_PREFIX = "X-TIMESTAMP-MAP=";
|
|
3
|
+
/**
|
|
4
|
+
* Scrape a WebVTT segment's `X-TIMESTAMP-MAP` header into a {@link TimestampMap} — the only header line we need to
|
|
5
|
+
* correlate LOCAL cue times with the media presentation timeline. Deliberately _not_ a WebVTT parser: cue parsing stays
|
|
6
|
+
* with the browser's native `<track>` parser (which drops this line); this reads just the one header field the native
|
|
7
|
+
* path discards.
|
|
8
|
+
*
|
|
9
|
+
* Returns `undefined` when the segment carries no map (e.g. cues already in absolute presentation time) — per the HLS
|
|
10
|
+
* spec that means LOCAL 0 maps to MPEGTS 0. Tolerant of attribute order and `[HH:]MM:SS.mmm` LOCAL forms.
|
|
11
|
+
*/
|
|
12
|
+
function parseVttTimestampMap(text) {
|
|
13
|
+
const timestampMapLine = text.split(/\r\n|\r|\n/).find((line) => line.startsWith(TIMESTAMP_MAP_PREFIX));
|
|
14
|
+
return timestampMapLine ? parseTimestampMapBody(timestampMapLine.slice(16)) : void 0;
|
|
15
|
+
}
|
|
16
|
+
const TimeStampMapParserMap = {
|
|
17
|
+
LOCAL: parseWebVttTimestamp,
|
|
18
|
+
MPEGTS: (v) => +v
|
|
19
|
+
};
|
|
20
|
+
function parseTimestampMapBody(body) {
|
|
21
|
+
return Object.fromEntries(body.split(",").map((kvStr) => {
|
|
22
|
+
const [k, v] = kvStr.split(/:(.*)/).map((kOrV) => kOrV.trim());
|
|
23
|
+
return [k?.toLowerCase(), TimeStampMapParserMap[k](v)];
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/** Seconds-per-unit for the `[HH:]MM:SS.mmm` parts, right-aligned so a missing HH just drops the leading weight. */
|
|
27
|
+
const VTT_TIMESTAMP_WEIGHTS = [
|
|
28
|
+
3600,
|
|
29
|
+
60,
|
|
30
|
+
1,
|
|
31
|
+
.001
|
|
32
|
+
];
|
|
33
|
+
function parseWebVttTimestamp(value) {
|
|
34
|
+
const parts = value.split(/[:.]/);
|
|
35
|
+
return parts.reduce((acc, val, i) => acc + +val * (VTT_TIMESTAMP_WEIGHTS[i + 4 - parts.length] ?? 0), 0);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { parseVttTimestampMap };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=parse-vtt-timestamp-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-vtt-timestamp-map.js","names":[],"sources":["../../../../src/media/text/parse-vtt-timestamp-map.ts"],"sourcesContent":["/**\n * WebVTT-in-HLS `X-TIMESTAMP-MAP`: correlates a cue's LOCAL (in-file) time with an MPEG-2 presentation timestamp, so\n * LOCAL-authored cues can be placed on the media presentation timeline. Stored raw — the LOCAL→native correction is\n * `mpegts / 90000 - local` — so it stays independent of how (or whether) the presentation is later re-origined. See the\n * HLS spec, RFC 8216bis §3.5.\n */\nexport interface TimestampMap {\n /** The MPEG-2 presentation timestamp, in 90 kHz ticks, as authored. */\n mpegts: number;\n /** The LOCAL cue time the `mpegts` value maps to, in seconds. */\n local: number;\n}\n\nconst TIMESTAMP_MAP_PREFIX = 'X-TIMESTAMP-MAP=';\n\n/**\n * Scrape a WebVTT segment's `X-TIMESTAMP-MAP` header into a {@link TimestampMap} — the only header line we need to\n * correlate LOCAL cue times with the media presentation timeline. Deliberately _not_ a WebVTT parser: cue parsing stays\n * with the browser's native `<track>` parser (which drops this line); this reads just the one header field the native\n * path discards.\n *\n * Returns `undefined` when the segment carries no map (e.g. cues already in absolute presentation time) — per the HLS\n * spec that means LOCAL 0 maps to MPEGTS 0. Tolerant of attribute order and `[HH:]MM:SS.mmm` LOCAL forms.\n */\nexport function parseVttTimestampMap(text: string): TimestampMap | undefined {\n const timestampMapLine = text.split(/\\r\\n|\\r|\\n/).find((line) => line.startsWith(TIMESTAMP_MAP_PREFIX));\n\n return timestampMapLine ? parseTimestampMapBody(timestampMapLine.slice(TIMESTAMP_MAP_PREFIX.length)) : undefined;\n}\n\nconst TimeStampMapParserMap = {\n LOCAL: parseWebVttTimestamp,\n MPEGTS: (v: string) => +v,\n} as const;\n\ntype TimeStampMapParserMap = typeof TimeStampMapParserMap;\n\nfunction parseTimestampMapBody(body: string): TimestampMap | undefined {\n return Object.fromEntries(\n body.split(',').map((kvStr) => {\n const [k, v] = kvStr.split(/:(.*)/).map((kOrV) => kOrV.trim());\n\n return [k?.toLowerCase(), TimeStampMapParserMap[k as keyof TimeStampMapParserMap](v as string)];\n })\n ) as TimestampMap;\n}\n\n/** Seconds-per-unit for the `[HH:]MM:SS.mmm` parts, right-aligned so a missing HH just drops the leading weight. */\nconst VTT_TIMESTAMP_WEIGHTS = [3600, 60, 1, 0.001];\n\nfunction parseWebVttTimestamp(value: string): number {\n const parts = value.split(/[:.]/);\n\n return parts.reduce((acc, val, i) => acc + +val * (VTT_TIMESTAMP_WEIGHTS[i + 4 - parts.length] ?? 0), 0);\n}\n"],"mappings":";AAaA,MAAM,uBAAuB;;;;;;;;;;AAW7B,SAAgB,qBAAqB,MAAwC;CAC3E,MAAM,mBAAmB,KAAK,MAAM,YAAY,CAAC,CAAC,MAAM,SAAS,KAAK,WAAW,oBAAoB,CAAC;CAEtG,OAAO,mBAAmB,sBAAsB,iBAAiB,MAAM,EAA2B,CAAC,IAAI,KAAA;AACzG;AAEA,MAAM,wBAAwB;CAC5B,OAAO;CACP,SAAS,MAAc,CAAC;AAC1B;AAIA,SAAS,sBAAsB,MAAwC;CACrE,OAAO,OAAO,YACZ,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,UAAU;EAC7B,MAAM,CAAC,GAAG,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC,KAAK,SAAS,KAAK,KAAK,CAAC;EAE7D,OAAO,CAAC,GAAG,YAAY,GAAG,sBAAsB,EAAiC,CAAC,CAAW,CAAC;CAChG,CAAC,CACH;AACF;;AAGA,MAAM,wBAAwB;CAAC;CAAM;CAAI;CAAG;AAAK;AAEjD,SAAS,qBAAqB,OAAuB;CACnD,MAAM,QAAQ,MAAM,MAAM,MAAM;CAEhC,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,OAAO,sBAAsB,IAAI,IAAI,MAAM,WAAW,IAAI,CAAC;AACzG"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { parseVttTimestampMap } from "./parse-vtt-timestamp-map.js";
|
|
2
|
+
//#region src/media/text/resolve-vtt-metadata.ts
|
|
3
|
+
/**
|
|
4
|
+
* Header-level text-segment metadata — the `X-TIMESTAMP-MAP` correlation scraped from a VTT segment's raw bytes.
|
|
5
|
+
* DOM-free (a plain fetch + regex parse): the native `<track>` parser in `media/dom/text` (`resolveVttSegment`)
|
|
6
|
+
* discards this header, so a caller that needs it (e.g. non-zero-PTS relocation) fetches the bytes itself.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Fetch a VTT segment and scrape only its header metadata (no cue parsing).
|
|
10
|
+
*
|
|
11
|
+
* The native `<track>` parser (`media/dom/text`'s `resolveVttSegment`) discards `X-TIMESTAMP-MAP`, so reading it
|
|
12
|
+
* requires the raw bytes. This is a separate, caller-controlled fetch — the caller decides _when_ metadata is needed
|
|
13
|
+
* (e.g. once per source) rather than paying for it on every segment.
|
|
14
|
+
*/
|
|
15
|
+
async function resolveVttSegmentMetadata(url) {
|
|
16
|
+
const text = await fetch(url).then((response) => response.text());
|
|
17
|
+
return { timestampMap: parseVttTimestampMap(text) };
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { resolveVttSegmentMetadata };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=resolve-vtt-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-vtt-metadata.js","names":[],"sources":["../../../../src/media/text/resolve-vtt-metadata.ts"],"sourcesContent":["/**\n * Header-level text-segment metadata — the `X-TIMESTAMP-MAP` correlation scraped from a VTT segment's raw bytes.\n * DOM-free (a plain fetch + regex parse): the native `<track>` parser in `media/dom/text` (`resolveVttSegment`)\n * discards this header, so a caller that needs it (e.g. non-zero-PTS relocation) fetches the bytes itself.\n */\nimport { parseVttTimestampMap, type TimestampMap } from './parse-vtt-timestamp-map';\n\n/**\n * Header-level metadata for a text segment, surfaced alongside its cues. Each field is present only when the segment\n * declared it.\n */\nexport interface TextSegmentMetadata {\n timestampMap?: TimestampMap;\n}\n\n/**\n * Fetch a VTT segment and scrape only its header metadata (no cue parsing).\n *\n * The native `<track>` parser (`media/dom/text`'s `resolveVttSegment`) discards `X-TIMESTAMP-MAP`, so reading it\n * requires the raw bytes. This is a separate, caller-controlled fetch — the caller decides _when_ metadata is needed\n * (e.g. once per source) rather than paying for it on every segment.\n */\nexport async function resolveVttSegmentMetadata(url: string): Promise<TextSegmentMetadata> {\n const text = await fetch(url).then((response) => response.text());\n\n return { timestampMap: parseVttTimestampMap(text) };\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,eAAsB,0BAA0B,KAA2C;CACzF,MAAM,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,aAAa,SAAS,KAAK,CAAC;CAEhE,OAAO,EAAE,cAAc,qBAAqB,IAAI,EAAE;AACpD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/media/types/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Floating-point tolerance for matching segments by `startTime`. Two segments are considered the same position when
|
|
4
|
+
* `Math.abs(a.startTime - b.startTime) < SEGMENT_TIME_EPSILON`. Used by the source-buffer dedup and segment-loader
|
|
5
|
+
* quality-aware filter to tolerate sub-millisecond drift in segment timestamps across multiple playlists / quality
|
|
6
|
+
* levels.
|
|
7
|
+
*/
|
|
8
|
+
const SEGMENT_TIME_EPSILON = 1e-4;
|
|
9
|
+
/** Key under `Ham.metadata` where {@link MediaPlaylistMetadata} is stored. */
|
|
10
|
+
const MEDIA_PLAYLIST_METADATA_KEY = "mediaPlaylist";
|
|
11
|
+
/** Typed read of the media-playlist metadata stashed in `ham.metadata`. */
|
|
12
|
+
function getMediaPlaylistMetadata(ham) {
|
|
13
|
+
return ham.metadata?.[MEDIA_PLAYLIST_METADATA_KEY];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Derive {@link StreamType} from a media playlist's metadata. Per the model, only `#EXT-X-PLAYLIST-TYPE:VOD` marks
|
|
17
|
+
* on-demand; everything else (EVENT, or the tag absent) is live — completeness (`endList`) never factors in.
|
|
18
|
+
*/
|
|
19
|
+
function deriveStreamType(metadata) {
|
|
20
|
+
return metadata?.playlistType === "VOD" ? "on-demand" : "live";
|
|
21
|
+
}
|
|
22
|
+
function isResolvedTrack(track) {
|
|
23
|
+
return "segments" in track;
|
|
24
|
+
}
|
|
25
|
+
/** Check if a presentation has duration (at least one track resolved). Narrows type to include required duration. */
|
|
26
|
+
function hasPresentationDuration(presentation) {
|
|
27
|
+
return presentation.duration !== void 0;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Narrows a `MaybeResolvedPresentation` to a fully resolved `Presentation`.
|
|
31
|
+
*
|
|
32
|
+
* A presentation is resolved once `resolvePresentation` has parsed the manifest and populated both `id` and
|
|
33
|
+
* `selectionSets`. Both must be present — a partial value with only one of them isn't usable, and letting it through
|
|
34
|
+
* would have downstream behaviors crash when they access `selectionSets`.
|
|
35
|
+
*/
|
|
36
|
+
function isResolvedPresentation(presentation) {
|
|
37
|
+
return presentation !== void 0 && presentation.id !== void 0 && presentation.selectionSets !== void 0;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { MEDIA_PLAYLIST_METADATA_KEY, SEGMENT_TIME_EPSILON, deriveStreamType, getMediaPlaylistMetadata, hasPresentationDuration, isResolvedPresentation, isResolvedTrack };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/media/types/index.ts"],"sourcesContent":["/**\n * Core SPF Types\n *\n * Based on CMAF-HAM (Common Media Application Format - Hypothetical Application Model) Protocol-agnostic representation\n * of streaming media content.\n *\n * @see https://github.com/streaming-video-technology-alliance/common-media-library\n */\n\n// =============================================================================\n// Base Types\n// =============================================================================\n\n/** Base identifier type for all HAM objects. */\nexport interface Ham {\n id: string;\n /**\n * Format-/protocol-specific values that aren't part of the generic CMAF-HAM model — kept in an open bag so the model\n * stays format-neutral (mirrors how CMAF-HAM itself stashes protocol extras rather than growing the model). Typed\n * reads go through dedicated accessors (e.g. `getMediaPlaylistMetadata`).\n */\n metadata?: Record<string, unknown>;\n}\n\n/** Addressable resource with optional byte range. */\nexport interface AddressableObject {\n url: string;\n byteRange?: {\n start: number;\n end: number;\n };\n}\n\n// =============================================================================\n// Platform-agnostic Media Element\n// =============================================================================\n\n/**\n * Platform-agnostic media element interface. Captures minimal shape needed for orchestration without DOM dependencies.\n * HTMLMediaElement satisfies this interface.\n */\nexport interface MediaElementLike {\n preload: string;\n /**\n * DOM attribute-reflection surface, present on real elements. Where it exists, reflected IDL properties like\n * `preload` report a browser-dependent UA default even when no attribute was authored — so consumers that need author\n * intent must gate property reads on attribute presence. A non-DOM implementation may omit it; its properties carry\n * no UA defaults and are authored by construction.\n */\n hasAttribute?(qualifiedName: string): boolean;\n}\n\n// =============================================================================\n// Time and Duration\n// =============================================================================\n\n/** Time span with start time and duration. Used for segments and other timed ranges. */\nexport interface TimeSpan {\n startTime: number;\n duration: number;\n}\n\n// =============================================================================\n// Enums\n// =============================================================================\n\n/** Track content type. */\nexport type TrackType = 'video' | 'audio' | 'text';\n\n// =============================================================================\n// Frame Rate\n// =============================================================================\n\n/**\n * Video frame rate expressed as numerator/denominator.\n *\n * Examples: - 30 fps: { frameRateNumerator: 30 } - 29.97 fps: { frameRateNumerator: 30000, frameRateDenominator: 1001 }\n */\nexport interface FrameRate {\n frameRateNumerator: number;\n frameRateDenominator?: number;\n}\n\n// =============================================================================\n// Partially Resolved Tracks (before media playlist is fetched)\n// =============================================================================\n\n/**\n * Generic type for partially resolved tracks. Removes fields that come from media playlist parsing.\n *\n * @param T - Track type to make partially resolved (must extend Track)\n */\nexport type PartiallyResolved<T extends Track = Track> = Omit<T, 'segments' | 'initialization' | keyof TimeSpan> & {\n segments?: never;\n duration?: never;\n startTime?: never;\n initialization?: never;\n};\n\n/**\n * Partially resolved video track from multivariant playlist. Has metadata but no segments or initialization yet (media\n * playlist not fetched).\n */\nexport type PartiallyResolvedVideoTrack = PartiallyResolved<VideoTrack>;\n\n/**\n * Partially resolved audio track from multivariant playlist. Has metadata but no segments or initialization yet (media\n * playlist not fetched).\n */\nexport type PartiallyResolvedAudioTrack = PartiallyResolved<AudioTrack>;\n\n// =============================================================================\n// Resolved Track Types (with segments from media playlist)\n// =============================================================================\n\n/**\n * Base track type containing common properties for all resolved tracks. A resolved track has segments, duration, and\n * initialization data. All URLs are fully qualified (parsers resolve relative URLs).\n */\n/**\n * Track startTime is always 0 — the presentation-timeline origin (and future multi-period base). The live\n * sliding-window edge is `segments[0].startTime`, derived — never stored here.\n */\nexport type Track = Ham &\n AddressableObject &\n TimeSpan & {\n type: TrackType;\n codecs?: string[]; // Optional per HLS spec\n mimeType: string;\n language?: string | undefined;\n bandwidth: number;\n initialization?: AddressableObject;\n segments: Segment[];\n /**\n * Media-timeline (decode/encode) coordinate of the track's timeline origin (presentation-0) — the media-time base\n * value of the coordinate model, peer to `startTime` (presentation). Derived from the container\n * (`tfdt.baseMediaDecodeTime ÷ mdhd.timescale`); the relocation offset is `−startMediaTime` (targets presentation-0\n * — `Track.startTime` plays no part), never stored.\n *\n * Optional: `undefined` means not yet established, or never establishable (e.g. text tracks carry no container\n * origin); a near-zero/native origin is established as `0` (no relocation). Established once per source by the\n * `establishStartMediaTime` reactor. See `internal/design/spf/presentation-timeline-model.md`.\n */\n startMediaTime?: number;\n /**\n * Wall-clock time (epoch seconds) at the track's timeline origin (presentation-0) — pure playlist arithmetic over\n * any PDT-bearing segment: `segment.startDate − segment.startTime`, invariant along a linear timeline. Optional:\n * absent when no segment carries `startDate`.\n *\n * The wall-clock member of the coordinate triple (peer to `startTime` and `startMediaTime`). For live it is the\n * anchor segments are PDT-placed against on every parse; equal `startDate` across tracks marks the same\n * presentation instant. See `internal/design/spf/live-presentation-timeline-model.md`.\n */\n startDate?: number;\n };\n\n/**\n * Per-track-type origin-establishment data, accumulated across appends (the media track's `track_id` + `mdhd` timescale\n * from the init, `tfdt` baseMediaDecodeTime of that same track from the first media segment) — hence optional. The\n * transient input the `establishStartMediaTime` reactor reduces into `Track.startMediaTime`.\n *\n * `trackId` is the ISO-BMFF `track_ID` of the buffered media track (`vide`/`soun`), read from the init's `tkhd`; it\n * ties the timescale to the _same_ track's `baseMediaDecodeTime` (matched via `tfhd.track_id`) so a muxed segment\n * carrying a second track (e.g. `clcp` captions) reads the right `tfdt` rather than the first one.\n *\n * `segmentStartTime` is the 0-based presentation start of the segment `baseMediaDecodeTime` was read from — _not_ a\n * container value (it's the playlist position), but co-located because the origin is `baseMediaDecodeTime/timescale −\n * segmentStartTime`: the first _loaded_ segment isn't necessarily the 0th (a non-zero initial `currentTime`, or\n * live/DVR), so the decode time alone isn't the stream origin.\n */\nexport interface MediaContainerData {\n trackId?: number;\n timescale?: number;\n baseMediaDecodeTime?: number;\n segmentStartTime?: number;\n}\n\n/**\n * Raw media-segment bytes — a complete buffer or a byte stream. The transport-neutral payload the loader pipeline\n * carries; `AppendData` (the MSE `SourceBuffer` append input in `media/dom/mse`) is an alias of this at the DOM\n * boundary.\n */\nexport type SegmentData = ArrayBuffer | AsyncIterable<Uint8Array>;\n\n/** Resolved video track with segments. */\nexport type VideoTrack = Track &\n Required<Pick<Track, 'initialization' | 'codecs'>> & {\n type: 'video';\n\n // Optional metadata from multivariant (per HLS spec)\n width?: number;\n height?: number;\n frameRate?: FrameRate;\n /**\n * Audio groups (`EXT-X-STREAM-INF:AUDIO`) this video rendition can pair with. A list because one rendition is\n * typically listed across multiple `EXT-X-STREAM-INF` entries — one per audio group (the HLS cross-product) — which\n * the parser collapses into a single track carrying every group it advertised.\n */\n audioGroupIds?: string[];\n };\n\n/** Resolved audio track with segments. */\nexport type AudioTrack = Track &\n Required<Pick<Track, 'initialization' | 'codecs'>> & {\n type: 'audio';\n groupId: string;\n name: string;\n sampleRate: number;\n channels: number;\n default?: boolean;\n autoselect?: boolean;\n };\n\n/** Resolved text track with segments. */\nexport type TextTrack = Track & {\n type: 'text';\n groupId: string;\n label: string;\n kind: 'subtitles' | 'captions';\n default?: boolean;\n autoselect?: boolean;\n forced?: boolean;\n};\n\n/**\n * Predicate that answers \"can this environment decode this track?\" — the capability-probing surface, read by the\n * track-switching hard-constraint pre-pass (`excludeUnplayableTracks`) to drop undecodable renditions before selection.\n * Kept DOM-free here (a plain function type over a minimal track shape) so DOM-free behaviors can consume it; the DOM\n * implementation (`canPlayTrack` in `media/dom/capabilities.ts`) wraps `MediaSource.isTypeSupported`.\n *\n * Takes the minimal codec-bearing shape both video and audio candidates carry. `mimeType` is optional so unprobeable\n * candidates (no MIME) can be passed straight through as playable rather than dropped.\n */\nexport type CanPlayTrack = (track: {\n mimeType?: string;\n codecs?: string[];\n metadata?: Record<string, unknown>;\n}) => boolean;\n\n/**\n * Minimal text-track cue shape — start time, end time, and display text.\n *\n * Host-agnostic representation. `VTTCue` structurally satisfies this interface, so DOM consumers pass `VTTCue` values\n * directly. Non-DOM hosts (workers, test fakes, non-browser engines) can satisfy the same shape without pulling in DOM\n * types.\n */\nexport interface Cue {\n startTime: number;\n endTime: number;\n text: string;\n}\n\n/**\n * Media element with an iterable text-track list, host-agnostic.\n *\n * Extends `MediaElementLike` with the minimum surface needed to observe which text tracks are currently mounted on the\n * media. `HTMLMediaElement` structurally satisfies this (its `textTracks` is a `TextTrackList`, which is iterable with\n * `{ id }` items).\n */\nexport interface MediaElementWithTextTracks extends MediaElementLike {\n readonly textTracks: Iterable<{ readonly id: string }>;\n}\n\n/**\n * Partially resolved text track from multivariant playlist. Has metadata but no segments or initialization yet (media\n * playlist not fetched).\n */\nexport type PartiallyResolvedTextTrack = PartiallyResolved<TextTrack>;\n\n/** Union of all resolved track types. */\nexport type ResolvedTrack = VideoTrack | AudioTrack | TextTrack;\n\n/** Union of all partially resolved track types. */\nexport type PartiallyResolvedTrack =\n | PartiallyResolvedVideoTrack\n | PartiallyResolvedAudioTrack\n | PartiallyResolvedTextTrack;\n\n// =============================================================================\n// Switching and Selection Sets\n// =============================================================================\n\n/**\n * Generic switching set type. A group of tracks that can be switched between seamlessly.\n *\n * @param T - Track type (VideoTrack, AudioTrack, or TextTrack)\n */\nexport type SwitchingSetOf<T extends Track = Track> = Ham & {\n type: T['type'];\n tracks: (PartiallyResolved<T> | T)[];\n};\n\n/** Video switching set - contains only video tracks (partially resolved or fully resolved). */\nexport type VideoSwitchingSet = SwitchingSetOf<VideoTrack>;\n\n/** Audio switching set - contains only audio tracks (partially resolved or fully resolved). */\nexport type AudioSwitchingSet = SwitchingSetOf<AudioTrack>;\n\n/** Text switching set - contains only text tracks (partially resolved or fully resolved). */\nexport type TextSwitchingSet = SwitchingSetOf<TextTrack>;\n\n/** Switching set - a group of tracks that can be switched between seamlessly. Discriminated by track type. */\nexport type SwitchingSet = VideoSwitchingSet | AudioSwitchingSet | TextSwitchingSet;\n\n/**\n * Generic selection set type. Groups switching sets by track type.\n *\n * @param T - Track type (VideoTrack, AudioTrack, or TextTrack)\n */\nexport type SelectionSetOf<T extends Track = Track> = Ham & {\n type: T['type'];\n switchingSets: SwitchingSetOf<T>[];\n};\n\n/** Video selection set - contains only video switching sets. */\nexport type VideoSelectionSet = SelectionSetOf<VideoTrack>;\n\n/** Audio selection set - contains only audio switching sets. */\nexport type AudioSelectionSet = SelectionSetOf<AudioTrack>;\n\n/** Text selection set - contains only text switching sets. */\nexport type TextSelectionSet = SelectionSetOf<TextTrack>;\n\n/** Selection set - groups switching sets by track type. Discriminated union ensures type-safe track access. */\nexport type SelectionSet = VideoSelectionSet | AudioSelectionSet | TextSelectionSet;\n\n// =============================================================================\n// Segment\n// =============================================================================\n\n/**\n * Media segment with timing information. Follows CMAF-HAM composition pattern.\n *\n * `startDate` is the absolute wall-clock time of the segment's first sample, in **epoch seconds** (unit-consistent with\n * `startTime`/`duration`), derived from `#EXT-X-PROGRAM-DATE-TIME` (explicit or interpolated forward via `EXTINF`).\n * Unlike the per-track-relative `startTime`, it is comparable across tracks, so it is the cross-track sync anchor for\n * demuxed audio/video and the exact recovery value on a full live-window turnover. Optional: absent when the source\n * carries no PDT (allowed by RFC 8216, required by Apple's HLS authoring spec — so present on conformant content).\n */\nexport type Segment = Ham & AddressableObject & TimeSpan & { startDate?: number };\n\n/**\n * Floating-point tolerance for matching segments by `startTime`. Two segments are considered the same position when\n * `Math.abs(a.startTime - b.startTime) < SEGMENT_TIME_EPSILON`. Used by the source-buffer dedup and segment-loader\n * quality-aware filter to tolerate sub-millisecond drift in segment timestamps across multiple playlists / quality\n * levels.\n */\nexport const SEGMENT_TIME_EPSILON = 0.0001;\n\n// =============================================================================\n// Media Playlist Metadata\n// =============================================================================\n\n/**\n * Playlist-level metadata surfaced from a parsed media playlist. HLS delivery specifics — not part of the generic\n * CMAF-HAM model — so they live under `Ham.metadata` (read via `getMediaPlaylistMetadata`) rather than as first-class\n * `Track` fields:\n *\n * - `targetDuration` (`#EXT-X-TARGETDURATION`) — reload-cadence basis.\n * - `mediaSequence` (`#EXT-X-MEDIA-SEQUENCE`, default 0) — sequence number of `segments[0]`; the join key for merging\n * successive reload snapshots.\n * - `playlistType` (`#EXT-X-PLAYLIST-TYPE`) — `VOD` / `EVENT` / undefined.\n * - `endList` (`#EXT-X-ENDLIST`) — playlist is complete; stop reloading.\n */\nexport interface MediaPlaylistMetadata {\n targetDuration: number;\n mediaSequence: number;\n playlistType?: 'VOD' | 'EVENT';\n endList: boolean;\n /**\n * Whether this rendition carries encrypted segments — any `#EXT-X-KEY` whose `METHOD` isn't `NONE`. Detection only:\n * enough to tell that playback needs decryption support, not enough to perform it.\n *\n * Deliberately _not_ a model-level `protection` shape. CMAF-HAM puts `protection` on `SwitchingSet`, but that can't\n * express two real cases: a clear lead (`METHOD=NONE` segments followed by encrypted ones — protection varies along\n * the timeline within one rendition) or key rotation (its single `defaultKid` can't represent a key changing over\n * time). Modeling it properly belongs to DRM support; until then this records the one fact a playlist reliably gives\n * us. Per-rendition because that's HLS's granularity — `EXT-X-KEY` is a media-playlist tag.\n *\n * Conservative for a clear lead: a rendition whose opening segments are clear still reads as encrypted, so it's\n * judged unplayable rather than played until it breaks.\n */\n encrypted?: boolean;\n /**\n * `EXT-X-SERVER-CONTROL` `HOLD-BACK` (seconds) — the server's declared distance from the live edge for clients\n * playing _complete_ segments. Absent when the server doesn't advertise it, in which case the spec default (3 ×\n * target duration) applies. Deliberately HLS vocabulary living in the playlist metadata rather than on `Track`:\n * whether a wall-clock holdback generalizes across delivery formats is unresolved.\n *\n * `PART-HOLD-BACK` is **not** captured — it only applies to clients playing partial segments, and using it while\n * fetching whole segments would put the playhead ahead of the last complete segment. Add it with LL-HLS support.\n */\n holdBack?: number;\n /**\n * Whether the server is delivering this rendition as Low-Latency HLS — any of `#EXT-X-PART`, `#EXT-X-PART-INF`, or\n * `EXT-X-SERVER-CONTROL`'s `PART-HOLD-BACK`.\n *\n * Detection only, and deliberately so: partial segments are ignored by the parser and the loader fetches whole\n * segments, so an LL-HLS playlist plays as standard live at standard latency. Recording the fact is what lets a\n * composition _say_ that rather than silently under-delivering the latency the publisher configured.\n */\n lowLatency?: boolean;\n}\n\n/** Key under `Ham.metadata` where {@link MediaPlaylistMetadata} is stored. */\nexport const MEDIA_PLAYLIST_METADATA_KEY = 'mediaPlaylist';\n\n/** Typed read of the media-playlist metadata stashed in `ham.metadata`. */\nexport function getMediaPlaylistMetadata(ham: Pick<Ham, 'metadata'>): MediaPlaylistMetadata | undefined {\n return ham.metadata?.[MEDIA_PLAYLIST_METADATA_KEY] as MediaPlaylistMetadata | undefined;\n}\n\n// =============================================================================\n// Stream Type\n// =============================================================================\n\n/**\n * The source's semantic nature — live vs on-demand. A model concept (consumer-facing), distinct from completeness /\n * duration: a live stream that has _ended_ is still `'live'`.\n */\nexport type StreamType = 'live' | 'on-demand';\n\n/**\n * Derive {@link StreamType} from a media playlist's metadata. Per the model, only `#EXT-X-PLAYLIST-TYPE:VOD` marks\n * on-demand; everything else (EVENT, or the tag absent) is live — completeness (`endList`) never factors in.\n */\nexport function deriveStreamType(metadata: MediaPlaylistMetadata | undefined): StreamType {\n return metadata?.playlistType === 'VOD' ? 'on-demand' : 'live';\n}\n\n// =============================================================================\n// Media Playlist Info\n// =============================================================================\n\n/** Intermediate representation of a parsed media playlist. Used internally before assembling into full Track structure. */\nexport interface MediaPlaylistInfo {\n version: number;\n targetDuration: number;\n playlistType: 'VOD' | 'EVENT' | undefined;\n initSegment: AddressableObject | null;\n segments: Segment[];\n duration: number;\n endList: boolean;\n}\n\n// =============================================================================\n// Presentation\n// =============================================================================\n\n/**\n * Presentation - a single playable period of content. Uses TimeSpan fields (startTime always 0, duration optional until\n * track resolved).\n *\n * Extends AddressableObject so `url` contains the original manifest URL. All URLs are fully qualified (parsers resolve\n * relative URLs).\n */\nexport type Presentation = Ham &\n AddressableObject &\n Partial<TimeSpan> & {\n selectionSets: SelectionSet[];\n /**\n * Live vs on-demand — the source's semantic nature. Populated once a media playlist is parsed (derived from\n * `#EXT-X-PLAYLIST-TYPE` via `deriveStreamType`); orthogonal to duration / completeness.\n */\n streamType?: StreamType;\n };\n\n/**\n * State-shaped presentation that may or may not be resolved yet.\n *\n * The lifecycle is a single value: a caller writes `{ url }`, and the resolver populates the rest in place. `url` is\n * always present; resolved fields (`id`, `selectionSets`, duration) appear once parsing succeeds.\n *\n * Use `isResolvedPresentation` to narrow to `Presentation`.\n */\nexport type MaybeResolvedPresentation = AddressableObject & Partial<Omit<Presentation, keyof AddressableObject>>;\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/** Check if a track is resolved (has segments). Works for all track types with overloaded signatures for type narrowing. */\nexport function isResolvedTrack(track: PartiallyResolvedVideoTrack | VideoTrack): track is VideoTrack;\nexport function isResolvedTrack(track: PartiallyResolvedAudioTrack | AudioTrack): track is AudioTrack;\nexport function isResolvedTrack(track: PartiallyResolvedTextTrack | TextTrack): track is TextTrack;\nexport function isResolvedTrack(track: PartiallyResolvedTrack | ResolvedTrack): track is ResolvedTrack;\nexport function isResolvedTrack(track: PartiallyResolvedTrack | ResolvedTrack): track is ResolvedTrack {\n return 'segments' in track;\n}\n\n/** Check if a presentation has duration (at least one track resolved). Narrows type to include required duration. */\nexport function hasPresentationDuration(\n presentation: MaybeResolvedPresentation\n): presentation is MaybeResolvedPresentation & { duration: number } {\n return presentation.duration !== undefined;\n}\n\n/**\n * Narrows a `MaybeResolvedPresentation` to a fully resolved `Presentation`.\n *\n * A presentation is resolved once `resolvePresentation` has parsed the manifest and populated both `id` and\n * `selectionSets`. Both must be present — a partial value with only one of them isn't usable, and letting it through\n * would have downstream behaviors crash when they access `selectionSets`.\n */\nexport function isResolvedPresentation(\n presentation: MaybeResolvedPresentation | undefined\n): presentation is Presentation {\n return presentation !== undefined && presentation.id !== undefined && presentation.selectionSets !== undefined;\n}\n"],"mappings":";;;;;;;AA2VA,MAAa,uBAAuB;;AA0DpC,MAAa,8BAA8B;;AAG3C,SAAgB,yBAAyB,KAA+D;CACtG,OAAO,IAAI,WAAW;AACxB;;;;;AAgBA,SAAgB,iBAAiB,UAAyD;CACxF,OAAO,UAAU,iBAAiB,QAAQ,cAAc;AAC1D;AA0DA,SAAgB,gBAAgB,OAAuE;CACrG,OAAO,cAAc;AACvB;;AAGA,SAAgB,wBACd,cACkE;CAClE,OAAO,aAAa,aAAa,KAAA;AACnC;;;;;;;;AASA,SAAgB,uBACd,cAC8B;CAC9B,OAAO,iBAAiB,KAAA,KAAa,aAAa,OAAO,KAAA,KAAa,aAAa,kBAAkB,KAAA;AACvG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/media/utils/cdn.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default {@link GetCdnId}: the URL's origin (scheme + host + port); falls back to the raw string when the URL can't be
|
|
4
|
+
* parsed, so the return value is always a stable grouping key.
|
|
5
|
+
*/
|
|
6
|
+
function getCdnId(url) {
|
|
7
|
+
try {
|
|
8
|
+
return new URL(url).origin;
|
|
9
|
+
} catch {
|
|
10
|
+
return url;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const CDN_TYPE_PRIORITY = {
|
|
14
|
+
video: 0,
|
|
15
|
+
audio: 1,
|
|
16
|
+
text: 2
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The distinct CDNs a presentation's tracks are served from, ordered video CDNs first, then audio, then text (manifest
|
|
20
|
+
* order within a type). The head is the primary CDN — the one a sticky pick defaults to — and is always video-derived
|
|
21
|
+
* when the source has video. Returns `[]` for an unresolved presentation with no tracks.
|
|
22
|
+
*
|
|
23
|
+
* Redundant-stream sources list the same content on multiple hosts (e.g. Mux's `?redundant_streams=true`), so each host
|
|
24
|
+
* contributes its own candidate tracks; this collapses them to the set of CDNs across every track type. The CDN-id
|
|
25
|
+
* derivation defaults to {@link getCdnId}; pass a consumer-configured `getId` to key on something other than origin.
|
|
26
|
+
*/
|
|
27
|
+
function getOrderedCdnIds(presentation, getId = getCdnId) {
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
const ids = [];
|
|
30
|
+
const selectionSets = [...presentation.selectionSets ?? []].sort((a, b) => CDN_TYPE_PRIORITY[a.type] - CDN_TYPE_PRIORITY[b.type]);
|
|
31
|
+
for (const selectionSet of selectionSets) for (const switchingSet of selectionSet.switchingSets) for (const track of switchingSet.tracks) {
|
|
32
|
+
const id = getId(track.url);
|
|
33
|
+
if (seen.has(id)) continue;
|
|
34
|
+
seen.add(id);
|
|
35
|
+
ids.push(id);
|
|
36
|
+
}
|
|
37
|
+
return ids;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Add a CDN id to a failed-CDN list, preserving order and ignoring duplicates. Idempotent: re-adding an already-present
|
|
41
|
+
* id returns the same array reference (so a no-op trip doesn't churn the `failedCdns` signal). The failover trip in
|
|
42
|
+
* `resolve-track` and the segment loaders feed this into `failedCdns` via `update`.
|
|
43
|
+
*/
|
|
44
|
+
function addFailedCdn(failed, cdn) {
|
|
45
|
+
return failed?.includes(cdn) ? failed : [...failed ?? [], cdn];
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { addFailedCdn, getCdnId, getOrderedCdnIds };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=cdn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn.js","names":[],"sources":["../../../../src/media/utils/cdn.ts"],"sourcesContent":["import type { MaybeResolvedPresentation, TrackType } from '../types';\n\n/**\n * Derive a stable grouping key for the CDN a URL is served from. Synchronous and pure (deliberately not a `resolve*` —\n * no fetch). Consumers override the default via the engine's `getCdnId` config (e.g. to key on Mux's `cdn=` query param\n * instead of the host); every CDN-identity site reads that same function so keys stay comparable across `cdnPriority`,\n * `failedCdns`, and the track-switching constraint + scope.\n */\nexport type GetCdnId = (url: string) => string;\n\n/**\n * Default {@link GetCdnId}: the URL's origin (scheme + host + port); falls back to the raw string when the URL can't be\n * parsed, so the return value is always a stable grouping key.\n */\nexport function getCdnId(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return url;\n }\n}\n\n// Track-type priority for CDN ordering: video first, then audio, then text.\n// Selection sets are visited in this order so the head of the returned list is\n// always video-derived. `preferActiveCdn` anchors every track type to the\n// first CDN with surviving tracks (the head), so this makes \"the primary CDN\n// is the video CDN\" a guarantee of `getOrderedCdnIds` rather than a side effect\n// of the order tracks happen to be parsed in.\nconst CDN_TYPE_PRIORITY: Record<TrackType, number> = { video: 0, audio: 1, text: 2 };\n\n/**\n * The distinct CDNs a presentation's tracks are served from, ordered video CDNs first, then audio, then text (manifest\n * order within a type). The head is the primary CDN — the one a sticky pick defaults to — and is always video-derived\n * when the source has video. Returns `[]` for an unresolved presentation with no tracks.\n *\n * Redundant-stream sources list the same content on multiple hosts (e.g. Mux's `?redundant_streams=true`), so each host\n * contributes its own candidate tracks; this collapses them to the set of CDNs across every track type. The CDN-id\n * derivation defaults to {@link getCdnId}; pass a consumer-configured `getId` to key on something other than origin.\n */\nexport function getOrderedCdnIds(presentation: MaybeResolvedPresentation, getId: GetCdnId = getCdnId): string[] {\n const seen = new Set<string>();\n const ids: string[] = [];\n // Stable sort keeps manifest order among same-type selection sets.\n const selectionSets = [...(presentation.selectionSets ?? [])].sort(\n (a, b) => CDN_TYPE_PRIORITY[a.type] - CDN_TYPE_PRIORITY[b.type]\n );\n\n for (const selectionSet of selectionSets) {\n for (const switchingSet of selectionSet.switchingSets) {\n for (const track of switchingSet.tracks) {\n const id = getId(track.url);\n if (seen.has(id)) continue;\n\n seen.add(id);\n ids.push(id);\n }\n }\n }\n\n return ids;\n}\n\n/**\n * Add a CDN id to a failed-CDN list, preserving order and ignoring duplicates. Idempotent: re-adding an already-present\n * id returns the same array reference (so a no-op trip doesn't churn the `failedCdns` signal). The failover trip in\n * `resolve-track` and the segment loaders feed this into `failedCdns` via `update`.\n */\nexport function addFailedCdn(failed: string[] | undefined, cdn: string): string[] {\n return failed?.includes(cdn) ? failed : [...(failed ?? []), cdn];\n}\n"],"mappings":";;;;;AAcA,SAAgB,SAAS,KAAqB;CAC5C,IAAI;EACF,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;CACtB,QAAQ;EACN,OAAO;CACT;AACF;AAQA,MAAM,oBAA+C;CAAE,OAAO;CAAG,OAAO;CAAG,MAAM;AAAE;;;;;;;;;;AAWnF,SAAgB,iBAAiB,cAAyC,QAAkB,UAAoB;CAC9G,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,MAAgB,CAAC;CAEvB,MAAM,gBAAgB,CAAC,GAAI,aAAa,iBAAiB,CAAC,CAAE,CAAC,CAAC,MAC3D,GAAG,MAAM,kBAAkB,EAAE,QAAQ,kBAAkB,EAAE,KAC5D;CAEA,KAAK,MAAM,gBAAgB,eACzB,KAAK,MAAM,gBAAgB,aAAa,eACtC,KAAK,MAAM,SAAS,aAAa,QAAQ;EACvC,MAAM,KAAK,MAAM,MAAM,GAAG;EAC1B,IAAI,KAAK,IAAI,EAAE,GAAG;EAElB,KAAK,IAAI,EAAE;EACX,IAAI,KAAK,EAAE;CACb;CAIJ,OAAO;AACT;;;;;;AAOA,SAAgB,aAAa,QAA8B,KAAuB;CAChF,OAAO,QAAQ,SAAS,GAAG,IAAI,SAAS,CAAC,GAAI,UAAU,CAAC,GAAI,GAAG;AACjE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/media/utils/preload.ts
|
|
2
|
+
function isStandardPreload(value) {
|
|
3
|
+
return value === "auto" || value === "metadata" || value === "none";
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Default `preload` value used as the fallback across behaviors (`syncPreload`, `resolvePresentation`,
|
|
7
|
+
* `isBlockingPreload`). SPF's own default, deliberately independent of the `<video>`/`<audio>` element's implicit
|
|
8
|
+
* default — that one is UA policy and browser-dependent (`'metadata'` Chromium, `'auto'` WebKit), never a source.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_PRELOAD = "metadata";
|
|
11
|
+
/**
|
|
12
|
+
* True when the preload value blocks initial resolution / loading. Falsy values (undefined, empty) fall back to
|
|
13
|
+
* `defaultPreload` (default `DEFAULT_PRELOAD`); the resolved value blocks iff it is `'none'`.
|
|
14
|
+
*/
|
|
15
|
+
function isBlockingPreload(preload, defaultPreload = DEFAULT_PRELOAD) {
|
|
16
|
+
return (preload || defaultPreload) === "none";
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { DEFAULT_PRELOAD, isBlockingPreload, isStandardPreload };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=preload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preload.js","names":[],"sources":["../../../../src/media/utils/preload.ts"],"sourcesContent":["/**\n * The W3C-standard set of `<video>`/`<audio>` `preload` attribute values. SPF allows extended values (e.g. `'canplay'`)\n * on `state.preload`, which are _not_ reflected to the DOM — predicate below is the discriminator.\n */\nexport type StandardPreload = 'auto' | 'metadata' | 'none';\n\nexport function isStandardPreload(value: unknown): value is StandardPreload {\n return value === 'auto' || value === 'metadata' || value === 'none';\n}\n\n/**\n * Default `preload` value used as the fallback across behaviors (`syncPreload`, `resolvePresentation`,\n * `isBlockingPreload`). SPF's own default, deliberately independent of the `<video>`/`<audio>` element's implicit\n * default — that one is UA policy and browser-dependent (`'metadata'` Chromium, `'auto'` WebKit), never a source.\n */\nexport const DEFAULT_PRELOAD = 'metadata';\n\n/**\n * True when the preload value blocks initial resolution / loading. Falsy values (undefined, empty) fall back to\n * `defaultPreload` (default `DEFAULT_PRELOAD`); the resolved value blocks iff it is `'none'`.\n */\nexport function isBlockingPreload(\n preload: string | undefined,\n defaultPreload: StandardPreload = DEFAULT_PRELOAD\n): boolean {\n return (preload || defaultPreload) === 'none';\n}\n"],"mappings":";AAMA,SAAgB,kBAAkB,OAA0C;CAC1E,OAAO,UAAU,UAAU,UAAU,cAAc,UAAU;AAC/D;;;;;;AAOA,MAAa,kBAAkB;;;;;AAM/B,SAAgB,kBACd,SACA,iBAAkC,iBACzB;CACT,QAAQ,WAAW,oBAAoB;AACzC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isResolvedTrack } from "../types/index.js";
|
|
2
|
+
//#region src/media/utils/track-selection.ts
|
|
3
|
+
/** Map track type to selected track ID property key in state. */
|
|
4
|
+
const SelectedTrackIdKeyByType = {
|
|
5
|
+
video: "selectedVideoTrackId",
|
|
6
|
+
audio: "selectedAudioTrackId",
|
|
7
|
+
text: "selectedTextTrackId"
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Get selected track from state by type. Returns properly typed track (partially or fully resolved) or undefined. Type
|
|
11
|
+
* parameter T is inferred from the type argument.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const videoTrack = getSelectedTrack(state, 'video');
|
|
15
|
+
* if (videoTrack && isResolvedTrack(videoTrack)) {
|
|
16
|
+
* // videoTrack is VideoTrack
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
function getSelectedTrack(state, type) {
|
|
20
|
+
const { presentation } = state;
|
|
21
|
+
if (!presentation?.selectionSets) return void 0;
|
|
22
|
+
const trackId = state[SelectedTrackIdKeyByType[type]];
|
|
23
|
+
return presentation.selectionSets.find(({ type: selectionSetType }) => selectionSetType === type)?.switchingSets[0]?.tracks.find(({ id }) => id === trackId);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns the duration of the first resolved selected track, preferring video over audio. A track is "resolved" once
|
|
27
|
+
* its media playlist has been parsed (per {@link isResolvedTrack}). Returns `undefined` if neither selected track is
|
|
28
|
+
* resolved.
|
|
29
|
+
*/
|
|
30
|
+
function getResolvedSelectedTrackDuration(state) {
|
|
31
|
+
if (state.selectedVideoTrackId) {
|
|
32
|
+
const video = getSelectedTrack(state, "video");
|
|
33
|
+
if (video && isResolvedTrack(video)) return video.duration;
|
|
34
|
+
}
|
|
35
|
+
if (state.selectedAudioTrackId) {
|
|
36
|
+
const audio = getSelectedTrack(state, "audio");
|
|
37
|
+
if (audio && isResolvedTrack(audio)) return audio.duration;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { SelectedTrackIdKeyByType, getResolvedSelectedTrackDuration, getSelectedTrack };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=track-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-selection.js","names":[],"sources":["../../../../src/media/utils/track-selection.ts"],"sourcesContent":["import type {\n AudioTrack,\n MaybeResolvedPresentation,\n PartiallyResolvedAudioTrack,\n PartiallyResolvedTextTrack,\n PartiallyResolvedVideoTrack,\n TextTrack,\n TrackType,\n VideoTrack,\n} from '../types';\nimport { isResolvedTrack } from '../types';\n\n/** State shape for track selection. Minimal shape containing presentation and selected track IDs. */\nexport interface TrackSelectionState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string | undefined;\n selectedAudioTrackId?: string | undefined;\n selectedTextTrackId?: string | undefined;\n}\n\n/** Map track type to selected track ID property key in state. */\nexport const SelectedTrackIdKeyByType = {\n video: 'selectedVideoTrackId',\n audio: 'selectedAudioTrackId',\n text: 'selectedTextTrackId',\n} as const;\n\n/**\n * Get selected track from state by type. Returns properly typed track (partially or fully resolved) or undefined. Type\n * parameter T is inferred from the type argument.\n *\n * @example\n * const videoTrack = getSelectedTrack(state, 'video');\n * if (videoTrack && isResolvedTrack(videoTrack)) {\n * // videoTrack is VideoTrack\n * }\n */\nexport function getSelectedTrack<T extends TrackType>(\n state: TrackSelectionState,\n type: T\n): T extends 'video'\n ? PartiallyResolvedVideoTrack | VideoTrack | undefined\n : T extends 'audio'\n ? PartiallyResolvedAudioTrack | AudioTrack | undefined\n : T extends 'text'\n ? PartiallyResolvedTextTrack | TextTrack | undefined\n : never {\n const { presentation } = state;\n if (!presentation?.selectionSets) return undefined as any;\n\n // Get track ID based on type\n const trackIdKey = SelectedTrackIdKeyByType[type];\n const trackId = state[trackIdKey];\n\n return presentation.selectionSets\n .find(({ type: selectionSetType }) => selectionSetType === type)\n ?.switchingSets[0]?.tracks.find(({ id }) => id === trackId) as any;\n}\n\n/**\n * Returns the duration of the first resolved selected track, preferring video over audio. A track is \"resolved\" once\n * its media playlist has been parsed (per {@link isResolvedTrack}). Returns `undefined` if neither selected track is\n * resolved.\n */\nexport function getResolvedSelectedTrackDuration(state: TrackSelectionState): number | undefined {\n if (state.selectedVideoTrackId) {\n const video = getSelectedTrack(state, 'video');\n if (video && isResolvedTrack(video)) return video.duration;\n }\n\n if (state.selectedAudioTrackId) {\n const audio = getSelectedTrack(state, 'audio');\n if (audio && isResolvedTrack(audio)) return audio.duration;\n }\n\n return undefined;\n}\n"],"mappings":";;;AAqBA,MAAa,2BAA2B;CACtC,OAAO;CACP,OAAO;CACP,MAAM;AACR;;;;;;;;;;;AAYA,SAAgB,iBACd,OACA,MAOY;CACZ,MAAM,EAAE,iBAAiB;CACzB,IAAI,CAAC,cAAc,eAAe,OAAO,KAAA;CAIzC,MAAM,UAAU,MADG,yBAAyB;CAG5C,OAAO,aAAa,cACjB,MAAM,EAAE,MAAM,uBAAuB,qBAAqB,IAAI,CAAC,EAC9D,cAAc,EAAE,EAAE,OAAO,MAAM,EAAE,SAAS,OAAO,OAAO;AAC9D;;;;;;AAOA,SAAgB,iCAAiC,OAAgD;CAC/F,IAAI,MAAM,sBAAsB;EAC9B,MAAM,QAAQ,iBAAiB,OAAO,OAAO;EAC7C,IAAI,SAAS,gBAAgB,KAAK,GAAG,OAAO,MAAM;CACpD;CAEA,IAAI,MAAM,sBAAsB;EAC9B,MAAM,QAAQ,iBAAiB,OAAO,OAAO;EAC7C,IAAI,SAAS,gBAAgB,KAAK,GAAG,OAAO,MAAM;CACpD;AAGF"}
|