@videojs/spf 10.0.0-beta.26 → 10.0.0-beta.28

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.
Files changed (238) hide show
  1. package/dist/default/core/signals/when.js +38 -0
  2. package/dist/default/core/signals/when.js.map +1 -0
  3. package/dist/default/core/tasks/delayed-reschedule.js +31 -0
  4. package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
  5. package/dist/default/core/tasks/task.js +133 -3
  6. package/dist/default/core/tasks/task.js.map +1 -1
  7. package/dist/default/hls-audio.js +3 -0
  8. package/dist/default/hls-background-video.js +3 -0
  9. package/dist/default/hls-video.js +4 -0
  10. package/dist/default/hls.js +6 -5
  11. package/dist/default/media/dom/capabilities.js +2 -0
  12. package/dist/default/media/dom/capabilities.js.map +1 -1
  13. package/dist/default/media/errors.js +62 -0
  14. package/dist/default/media/errors.js.map +1 -0
  15. package/dist/default/media/hls/parse-media-playlist.js +167 -16
  16. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  17. package/dist/default/media/hls/parse-multivariant.js +14 -3
  18. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  19. package/dist/default/media/hls/reload-policy.js +71 -0
  20. package/dist/default/media/hls/reload-policy.js.map +1 -0
  21. package/dist/default/media/live-window.js +36 -0
  22. package/dist/default/media/live-window.js.map +1 -0
  23. package/dist/default/media/primitives/select-tracks.js +1 -24
  24. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  25. package/dist/default/media/types/index.js +15 -1
  26. package/dist/default/media/types/index.js.map +1 -1
  27. package/dist/default/media/utils/tracks.js +14 -0
  28. package/dist/default/media/utils/tracks.js.map +1 -1
  29. package/dist/default/mux-audio.js +3 -0
  30. package/dist/default/mux-background-video.js +3 -0
  31. package/dist/default/mux-video.js +3 -0
  32. package/dist/default/playback/adapters/hls-audio/adapter.js +186 -0
  33. package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
  34. package/dist/default/playback/adapters/hls-audio/media.js +9 -0
  35. package/dist/default/playback/adapters/hls-audio/media.js.map +1 -0
  36. package/dist/default/playback/adapters/hls-background-video/adapter.js +123 -0
  37. package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
  38. package/dist/default/playback/adapters/hls-background-video/host.js +60 -0
  39. package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
  40. package/dist/default/playback/adapters/hls-background-video/media.js +21 -0
  41. package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -0
  42. package/dist/default/playback/adapters/hls-video/adapter.js +316 -0
  43. package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
  44. package/dist/default/playback/adapters/hls-video/error-surface.js +51 -0
  45. package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
  46. package/dist/default/playback/adapters/hls-video/media-tracks.js +126 -0
  47. package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
  48. package/dist/default/playback/adapters/hls-video/media.js +11 -0
  49. package/dist/default/playback/adapters/hls-video/media.js.map +1 -0
  50. package/dist/default/playback/adapters/mux-audio/media.js +26 -0
  51. package/dist/default/playback/adapters/mux-audio/media.js.map +1 -0
  52. package/dist/default/playback/adapters/mux-video/adapter.js +92 -0
  53. package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -0
  54. package/dist/default/playback/adapters/mux-video/media.js +23 -0
  55. package/dist/default/playback/adapters/mux-video/media.js.map +1 -0
  56. package/dist/default/playback/behaviors/collect-errors.js +77 -0
  57. package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
  58. package/dist/default/playback/behaviors/dom/end-of-stream.js +1 -0
  59. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  60. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +132 -0
  61. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  62. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
  63. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  64. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +29 -7
  65. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  66. package/dist/default/playback/behaviors/establish-start-media-time.js +78 -4
  67. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -1
  68. package/dist/default/playback/behaviors/resolve-track.js +29 -8
  69. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  70. package/dist/default/playback/behaviors/track-switching.js +18 -9
  71. package/dist/default/playback/behaviors/track-switching.js.map +1 -1
  72. package/dist/default/playback/engines/hls/engine-audio-only.js +9 -5
  73. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
  74. package/dist/{dev/playback/engines/background-video/engine.js → default/playback/engines/hls/engine-background-video.js} +3 -3
  75. package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
  76. package/dist/default/playback/engines/hls/engine.js +19 -6
  77. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  78. package/dist/default/playback/primitives/error-messages.js +39 -0
  79. package/dist/default/playback/primitives/error-messages.js.map +1 -0
  80. package/dist/default/playback/primitives/live-window.js +66 -0
  81. package/dist/default/playback/primitives/live-window.js.map +1 -0
  82. package/dist/default/playback/primitives/report-track-conditions.js +86 -0
  83. package/dist/default/playback/primitives/report-track-conditions.js.map +1 -0
  84. package/dist/dev/core/signals/when.js +38 -0
  85. package/dist/dev/core/signals/when.js.map +1 -0
  86. package/dist/dev/core/tasks/delayed-reschedule.js +31 -0
  87. package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
  88. package/dist/dev/core/tasks/task.d.ts +52 -1
  89. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  90. package/dist/dev/core/tasks/task.js +133 -3
  91. package/dist/dev/core/tasks/task.js.map +1 -1
  92. package/dist/dev/hls-audio.d.ts +3 -0
  93. package/dist/dev/hls-audio.js +3 -0
  94. package/dist/dev/hls-background-video.d.ts +3 -0
  95. package/dist/dev/hls-background-video.js +3 -0
  96. package/dist/dev/hls-video.d.ts +5 -0
  97. package/dist/dev/hls-video.js +4 -0
  98. package/dist/dev/hls.d.ts +6 -5
  99. package/dist/dev/hls.js +6 -5
  100. package/dist/dev/media/dom/capabilities.js +2 -0
  101. package/dist/dev/media/dom/capabilities.js.map +1 -1
  102. package/dist/dev/media/errors.d.ts +82 -0
  103. package/dist/dev/media/errors.d.ts.map +1 -0
  104. package/dist/dev/media/errors.js +62 -0
  105. package/dist/dev/media/errors.js.map +1 -0
  106. package/dist/dev/media/hls/parse-media-playlist.js +167 -16
  107. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  108. package/dist/dev/media/hls/parse-multivariant.js +14 -3
  109. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  110. package/dist/dev/media/hls/reload-policy.js +71 -0
  111. package/dist/dev/media/hls/reload-policy.js.map +1 -0
  112. package/dist/dev/media/live-window.js +36 -0
  113. package/dist/dev/media/live-window.js.map +1 -0
  114. package/dist/dev/media/primitives/select-tracks.js +1 -24
  115. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  116. package/dist/dev/media/types/index.d.ts +125 -9
  117. package/dist/dev/media/types/index.d.ts.map +1 -1
  118. package/dist/dev/media/types/index.js +15 -1
  119. package/dist/dev/media/types/index.js.map +1 -1
  120. package/dist/dev/media/utils/tracks.js +14 -0
  121. package/dist/dev/media/utils/tracks.js.map +1 -1
  122. package/dist/dev/mux-audio.d.ts +4 -0
  123. package/dist/dev/mux-audio.js +3 -0
  124. package/dist/dev/mux-background-video.d.ts +3 -0
  125. package/dist/dev/mux-background-video.js +3 -0
  126. package/dist/dev/mux-video.d.ts +4 -0
  127. package/dist/dev/mux-video.js +3 -0
  128. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +54 -0
  129. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
  130. package/dist/dev/playback/adapters/hls-audio/adapter.js +186 -0
  131. package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
  132. package/dist/dev/playback/adapters/hls-audio/media.d.ts +10 -0
  133. package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -0
  134. package/dist/dev/playback/adapters/hls-audio/media.js +9 -0
  135. package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -0
  136. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +59 -0
  137. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
  138. package/dist/dev/playback/adapters/hls-background-video/adapter.js +123 -0
  139. package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
  140. package/dist/dev/playback/adapters/hls-background-video/host.d.ts +38 -0
  141. package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
  142. package/dist/dev/playback/adapters/hls-background-video/host.js +60 -0
  143. package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
  144. package/dist/dev/playback/adapters/hls-background-video/media.d.ts +20 -0
  145. package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -0
  146. package/dist/dev/playback/adapters/hls-background-video/media.js +21 -0
  147. package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -0
  148. package/dist/dev/playback/adapters/hls-video/adapter.d.ts +63 -0
  149. package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
  150. package/dist/dev/playback/adapters/hls-video/adapter.js +316 -0
  151. package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
  152. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +18 -0
  153. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
  154. package/dist/dev/playback/adapters/hls-video/error-surface.js +51 -0
  155. package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
  156. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +22 -0
  157. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
  158. package/dist/dev/playback/adapters/hls-video/media-tracks.js +126 -0
  159. package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
  160. package/dist/dev/playback/adapters/hls-video/media.d.ts +10 -0
  161. package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -0
  162. package/dist/dev/playback/adapters/hls-video/media.js +11 -0
  163. package/dist/dev/playback/adapters/hls-video/media.js.map +1 -0
  164. package/dist/dev/playback/adapters/mux-audio/media.d.ts +28 -0
  165. package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -0
  166. package/dist/dev/playback/adapters/mux-audio/media.js +26 -0
  167. package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -0
  168. package/dist/dev/playback/adapters/mux-video/adapter.d.ts +33 -0
  169. package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -0
  170. package/dist/dev/playback/adapters/mux-video/adapter.js +92 -0
  171. package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -0
  172. package/dist/dev/playback/adapters/mux-video/media.d.ts +24 -0
  173. package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -0
  174. package/dist/dev/playback/adapters/mux-video/media.js +23 -0
  175. package/dist/dev/playback/adapters/mux-video/media.js.map +1 -0
  176. package/dist/dev/playback/behaviors/collect-errors.js +77 -0
  177. package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
  178. package/dist/dev/playback/behaviors/dom/end-of-stream.js +1 -0
  179. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  180. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +132 -0
  181. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  182. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
  183. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  184. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +29 -7
  185. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  186. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +5 -0
  187. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -1
  188. package/dist/dev/playback/behaviors/establish-start-media-time.js +78 -4
  189. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -1
  190. package/dist/dev/playback/behaviors/resolve-track.js +29 -8
  191. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  192. package/dist/dev/playback/behaviors/track-switching.js +18 -9
  193. package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
  194. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +34 -17
  195. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
  196. package/dist/dev/playback/engines/hls/engine-audio-only.js +9 -5
  197. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
  198. package/dist/dev/playback/engines/{background-video/engine.d.ts → hls/engine-background-video.d.ts} +7 -7
  199. package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
  200. package/dist/{default/playback/engines/background-video/engine.js → dev/playback/engines/hls/engine-background-video.js} +3 -3
  201. package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
  202. package/dist/dev/playback/engines/hls/engine.d.ts +43 -15
  203. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  204. package/dist/dev/playback/engines/hls/engine.js +19 -6
  205. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  206. package/dist/dev/playback/primitives/error-messages.js +39 -0
  207. package/dist/dev/playback/primitives/error-messages.js.map +1 -0
  208. package/dist/dev/playback/primitives/live-window.js +66 -0
  209. package/dist/dev/playback/primitives/live-window.js.map +1 -0
  210. package/dist/dev/playback/primitives/report-track-conditions.d.ts +11 -0
  211. package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
  212. package/dist/dev/playback/primitives/report-track-conditions.js +86 -0
  213. package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -0
  214. package/package.json +32 -6
  215. package/dist/default/background-video.js +0 -3
  216. package/dist/default/playback/engines/background-video/adapter.js +0 -158
  217. package/dist/default/playback/engines/background-video/adapter.js.map +0 -1
  218. package/dist/default/playback/engines/background-video/engine.js.map +0 -1
  219. package/dist/default/playback/engines/hls/adapter-audio-only.js +0 -124
  220. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +0 -1
  221. package/dist/default/playback/engines/hls/adapter.js +0 -120
  222. package/dist/default/playback/engines/hls/adapter.js.map +0 -1
  223. package/dist/dev/background-video.d.ts +0 -3
  224. package/dist/dev/background-video.js +0 -3
  225. package/dist/dev/playback/engines/background-video/adapter.d.ts +0 -59
  226. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +0 -1
  227. package/dist/dev/playback/engines/background-video/adapter.js +0 -158
  228. package/dist/dev/playback/engines/background-video/adapter.js.map +0 -1
  229. package/dist/dev/playback/engines/background-video/engine.d.ts.map +0 -1
  230. package/dist/dev/playback/engines/background-video/engine.js.map +0 -1
  231. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +0 -46
  232. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +0 -1
  233. package/dist/dev/playback/engines/hls/adapter-audio-only.js +0 -124
  234. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +0 -1
  235. package/dist/dev/playback/engines/hls/adapter.d.ts +0 -42
  236. package/dist/dev/playback/engines/hls/adapter.d.ts.map +0 -1
  237. package/dist/dev/playback/engines/hls/adapter.js +0 -120
  238. package/dist/dev/playback/engines/hls/adapter.js.map +0 -1
@@ -0,0 +1,126 @@
1
+ import { computed, untrack } from "../../../core/signals/primitives.js";
2
+ import { effect } from "../../../core/signals/effect.js";
3
+ import { dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection } from "../../../media/media-tracks/media-tracks.js";
4
+ //#region src/playback/adapters/hls-video/media-tracks.ts
5
+ const toVideoKey = (rendition) => ({
6
+ width: rendition.width,
7
+ height: rendition.height,
8
+ bandwidth: rendition.bitrate
9
+ });
10
+ const toAudioKey = (track) => ({
11
+ language: track.language,
12
+ name: track.label
13
+ });
14
+ /** Two track lists carry the same set when their id sequences match. */
15
+ const sameIds = (a, b) => a.length === b.length && a.every((item, i) => item.id === b[i].id);
16
+ /**
17
+ * Projects the SPF engine's presentation onto the media element's
18
+ * `videoRenditions` / `audioTracks` lists, and wires user selection back to the
19
+ * engine's `userVideoTrackSelection` / `userAudioTrackSelection` signals.
20
+ *
21
+ * Requires the media-tracks mixin (track-list infrastructure) earlier in the
22
+ * chain so the host exposes `addVideoTrack`, `videoRenditions`, and friends.
23
+ */
24
+ function HlsVideoMediaMediaTracksMixin(BaseClass) {
25
+ class HlsVideoMediaMediaTracks extends BaseClass {
26
+ #abort = new AbortController();
27
+ #destroyed = false;
28
+ #renditions = [];
29
+ #audioTracks = [];
30
+ constructor(...args) {
31
+ super(...args);
32
+ const { state } = this.engine;
33
+ const { signal } = this.#abort;
34
+ const renditionsSignal = computed(() => dedupedVideoTracks(state.presentation.get()), { equals: sameIds });
35
+ const audioTracksSignal = computed(() => dedupedAudioTracks(state.presentation.get()), { equals: sameIds });
36
+ const reflectRenditions = () => {
37
+ const renditions = renditionsSignal.get();
38
+ this.#renditions = renditions;
39
+ this.#removeVideoTracks();
40
+ if (!renditions.length) return;
41
+ const videoTrack = this.addVideoTrack("main");
42
+ videoTrack.selected = true;
43
+ const resolved = untrack(() => findVideoTrackById(state.presentation.get(), state.selectedVideoTrackId.get()));
44
+ for (const rendition of renditions) {
45
+ const domRendition = videoTrack.addRendition("", rendition.width, rendition.height, rendition.codecs.join(","), rendition.bandwidth, rendition.frameRate ? frameRateToNumber(rendition.frameRate) : void 0);
46
+ domRendition.id = rendition.id;
47
+ domRendition.active = isSameVideoTrack(toVideoKey(domRendition), resolved);
48
+ }
49
+ };
50
+ const reflectSelectedVideo = () => {
51
+ const resolved = findVideoTrackById(state.presentation.get(), state.selectedVideoTrackId.get());
52
+ for (const rendition of this.videoRenditions) rendition.active = isSameVideoTrack(toVideoKey(rendition), resolved);
53
+ };
54
+ const reflectAudioTracks = () => {
55
+ const tracks = audioTracksSignal.get();
56
+ this.#audioTracks = tracks;
57
+ this.#removeAudioTracks();
58
+ if (!tracks.length) return;
59
+ const resolved = untrack(() => findAudioTrackById(state.presentation.get(), state.selectedAudioTrackId.get()));
60
+ for (const track of tracks) {
61
+ const domTrack = this.addAudioTrack(track.default ? "main" : "alternative", track.name, track.language ?? "");
62
+ domTrack.id = track.id;
63
+ domTrack.enabled = isSameAudioTrack(toAudioKey(domTrack), resolved);
64
+ }
65
+ };
66
+ const reflectSelectedAudio = () => {
67
+ const resolved = findAudioTrackById(state.presentation.get(), state.selectedAudioTrackId.get());
68
+ for (const track of this.audioTracks) track.enabled = isSameAudioTrack(toAudioKey(track), resolved);
69
+ };
70
+ const sourceUrl = computed(() => state.presentation.get()?.url);
71
+ const resetSelectionOnSourceChange = () => {
72
+ sourceUrl.get();
73
+ state.userVideoTrackSelection.set(void 0);
74
+ state.userAudioTrackSelection.set(void 0);
75
+ };
76
+ const effectCleanups = [
77
+ effect(reflectRenditions),
78
+ effect(reflectSelectedVideo),
79
+ effect(reflectAudioTracks),
80
+ effect(reflectSelectedAudio),
81
+ effect(resetSelectionOnSourceChange)
82
+ ];
83
+ this.videoRenditions.addEventListener("change", this.#selectRendition, { signal });
84
+ this.audioTracks.addEventListener("change", this.#selectAudio, { signal });
85
+ signal.addEventListener("abort", () => effectCleanups.forEach((cleanup) => cleanup()), { once: true });
86
+ }
87
+ destroy() {
88
+ if (this.#destroyed) return;
89
+ this.#destroyed = true;
90
+ this.#abort.abort();
91
+ this.#removeVideoTracks();
92
+ this.#removeAudioTracks();
93
+ super.destroy?.();
94
+ }
95
+ #selectRendition = () => {
96
+ const { userVideoTrackSelection } = this.engine.state;
97
+ const index = this.videoRenditions.selectedIndex;
98
+ const domRendition = index < 0 ? void 0 : this.videoRenditions[index];
99
+ const rendition = this.#renditions.find((candidate) => candidate.id === domRendition?.id);
100
+ userVideoTrackSelection.set(toUserVideoTrackSelection(rendition));
101
+ };
102
+ #selectAudio = () => {
103
+ const { presentation, selectedAudioTrackId, userAudioTrackSelection } = this.engine.state;
104
+ const resolved = findAudioTrackById(presentation.get(), selectedAudioTrackId.get());
105
+ const current = [...this.audioTracks].find((track) => isSameAudioTrack(toAudioKey(track), resolved));
106
+ const enabled = [...this.audioTracks].filter((track) => track.enabled);
107
+ const target = enabled.find((track) => track !== current) ?? enabled[0];
108
+ if (!target) return;
109
+ for (const track of enabled) if (track !== target) track.enabled = false;
110
+ if (target === current) return;
111
+ const audioTrack = this.#audioTracks.find((candidate) => candidate.id === target.id);
112
+ userAudioTrackSelection.set(toUserAudioTrackSelection(audioTrack));
113
+ };
114
+ #removeVideoTracks() {
115
+ for (const videoTrack of [...this.videoTracks]) this.removeVideoTrack(videoTrack);
116
+ }
117
+ #removeAudioTracks() {
118
+ for (const audioTrack of [...this.audioTracks]) this.removeAudioTrack(audioTrack);
119
+ }
120
+ }
121
+ return HlsVideoMediaMediaTracks;
122
+ }
123
+ //#endregion
124
+ export { HlsVideoMediaMediaTracksMixin };
125
+
126
+ //# sourceMappingURL=media-tracks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-tracks.js","names":["#abort","#renditions","#removeVideoTracks","#audioTracks","#removeAudioTracks","#selectRendition","#selectAudio","#destroyed"],"sources":["../../../../../src/playback/adapters/hls-video/media-tracks.ts"],"sourcesContent":["import type {\n AudioTrackLike,\n MediaAudioTrackCapability,\n MediaVideoRenditionCapability,\n MediaVideoTrackCapability,\n VideoRenditionLike,\n} from '@videojs/media';\nimport type { Constructor } from '@videojs/utils/types';\nimport type { Composition } from '../../../core/composition/create-composition';\nimport { effect } from '../../../core/signals/effect';\nimport { computed, untrack } from '../../../core/signals/primitives';\nimport {\n type AudioTrack,\n dedupedAudioTracks,\n dedupedVideoTracks,\n findAudioTrackById,\n findVideoTrackById,\n frameRateToNumber,\n isSameAudioTrack,\n isSameVideoTrack,\n toUserAudioTrackSelection,\n toUserVideoTrackSelection,\n type VideoTrack,\n} from '../../../media/media-tracks';\nimport type { HlsVideoEngineContext, HlsVideoEngineState } from '../../engines/hls/engine';\n\n// Translate a DOM rendition/track into the SPF dedupe-key shape\nconst toVideoKey = (rendition: VideoRenditionLike) => ({\n width: rendition.width,\n height: rendition.height,\n bandwidth: rendition.bitrate!,\n});\n\nconst toAudioKey = (track: AudioTrackLike) => ({ language: track.language, name: track.label });\n\ntype HlsVideoEngineHost = {\n readonly engine: Composition<HlsVideoEngineState, HlsVideoEngineContext>;\n destroy?(): void;\n};\n\ntype MediaTracksHost = HlsVideoEngineHost &\n MediaVideoTrackCapability &\n MediaAudioTrackCapability &\n MediaVideoRenditionCapability;\n\n/** Two track lists carry the same set when their id sequences match. */\nconst sameIds = (a: { id: string }[], b: { id: string }[]): boolean =>\n a.length === b.length && a.every((item, i) => item.id === b[i]!.id);\n\n/**\n * Projects the SPF engine's presentation onto the media element's\n * `videoRenditions` / `audioTracks` lists, and wires user selection back to the\n * engine's `userVideoTrackSelection` / `userAudioTrackSelection` signals.\n *\n * Requires the media-tracks mixin (track-list infrastructure) earlier in the\n * chain so the host exposes `addVideoTrack`, `videoRenditions`, and friends.\n */\nexport function HlsVideoMediaMediaTracksMixin<Base extends Constructor<MediaTracksHost>>(BaseClass: Base) {\n class HlsVideoMediaMediaTracks extends (BaseClass as Constructor<MediaTracksHost>) {\n #abort = new AbortController();\n #destroyed = false;\n // Memoized SPF model — the last `computed` result per type — so a DOM\n // selection maps back to the engine's match criteria by id via the SPF\n // selection helpers (the DOM lists carry only DOM-shape props).\n #renditions: VideoTrack[] = [];\n #audioTracks: AudioTrack[] = [];\n\n constructor(...args: any[]) {\n super(...args);\n\n const { state } = this.engine;\n const { signal } = this.#abort;\n\n const renditionsSignal = computed(() => dedupedVideoTracks(state.presentation.get()), { equals: sameIds });\n const audioTracksSignal = computed(() => dedupedAudioTracks(state.presentation.get()), { equals: sameIds });\n\n // Rebuild video renditions when the set changes; seed `active` from the\n // current resolved selection (read untracked — `active` deltas are the\n // reflection effect's job).\n const reflectRenditions = () => {\n const renditions = renditionsSignal.get();\n this.#renditions = renditions;\n this.#removeVideoTracks();\n if (!renditions.length) return;\n\n // \"video track\" here is the Media UI Extensions concept, which maps onto\n // the SPF model differently than the name suggests:\n // MUE track -> SPF selection set\n // MUE rendition -> SPF track\n // @see https://github.com/video-dev/media-ui-extensions/blob/main/proposals/0011-renditions-list.md\n //\n // Every rendition hangs off a single \"main\" video track. Multi-track\n // video (multiple camera angles, CMAF multi-track) isn't supported yet,\n // so exactly one video track is always exposed; when it lands, this\n // becomes one track (selection set) per angle.\n const videoTrack = this.addVideoTrack('main');\n videoTrack.selected = true;\n\n const resolved = untrack(() => findVideoTrackById(state.presentation.get(), state.selectedVideoTrackId.get()));\n for (const rendition of renditions) {\n const domRendition = videoTrack.addRendition(\n '',\n rendition.width,\n rendition.height,\n rendition.codecs.join(','),\n rendition.bandwidth,\n rendition.frameRate ? frameRateToNumber(rendition.frameRate) : undefined\n );\n domRendition.id = rendition.id;\n domRendition.active = isSameVideoTrack(toVideoKey(domRendition), resolved);\n }\n };\n\n const reflectSelectedVideo = () => {\n const resolved = findVideoTrackById(state.presentation.get(), state.selectedVideoTrackId.get());\n for (const rendition of this.videoRenditions) {\n rendition.active = isSameVideoTrack(toVideoKey(rendition), resolved);\n }\n };\n\n // Rebuild audio tracks when the set changes; seed `enabled` from the\n // current resolved selection.\n const reflectAudioTracks = () => {\n const tracks = audioTracksSignal.get();\n this.#audioTracks = tracks;\n this.#removeAudioTracks();\n if (!tracks.length) return;\n\n const resolved = untrack(() => findAudioTrackById(state.presentation.get(), state.selectedAudioTrackId.get()));\n for (const track of tracks) {\n const domTrack = this.addAudioTrack(track.default ? 'main' : 'alternative', track.name, track.language ?? '');\n domTrack.id = track.id;\n domTrack.enabled = isSameAudioTrack(toAudioKey(domTrack), resolved);\n }\n };\n\n const reflectSelectedAudio = () => {\n const resolved = findAudioTrackById(state.presentation.get(), state.selectedAudioTrackId.get());\n for (const track of this.audioTracks) {\n track.enabled = isSameAudioTrack(toAudioKey(track), resolved);\n }\n };\n\n // A source change (new manifest URL) invalidates any pinned selection:\n // rendition identity (width/height/bandwidth) and audio identity\n // (language/name) don't reliably carry across sources, so drop both pins\n // and let the engine re-pick for the new source.\n const sourceUrl = computed(() => state.presentation.get()?.url);\n const resetSelectionOnSourceChange = () => {\n sourceUrl.get();\n state.userVideoTrackSelection.set(undefined);\n state.userAudioTrackSelection.set(undefined);\n };\n\n const effectCleanups = [\n effect(reflectRenditions),\n effect(reflectSelectedVideo),\n effect(reflectAudioTracks),\n effect(reflectSelectedAudio),\n effect(resetSelectionOnSourceChange),\n ];\n\n this.videoRenditions.addEventListener('change', this.#selectRendition, { signal });\n this.audioTracks.addEventListener('change', this.#selectAudio, { signal });\n signal.addEventListener('abort', () => effectCleanups.forEach((cleanup) => cleanup()), { once: true });\n }\n\n destroy(): void {\n if (this.#destroyed) return;\n this.#destroyed = true;\n this.#abort.abort();\n this.#removeVideoTracks();\n this.#removeAudioTracks();\n super.destroy?.();\n }\n\n // Manual quality pin → the chosen rendition's width/height/bandwidth as the\n // match criteria (the properties renditions were deduped on).\n // `selectedIndex === -1` (Auto) clears the pin, resuming ABR.\n #selectRendition = () => {\n const { userVideoTrackSelection } = this.engine.state;\n const index = this.videoRenditions.selectedIndex;\n const domRendition = index < 0 ? undefined : this.videoRenditions[index];\n const rendition = this.#renditions.find((candidate) => candidate.id === domRendition?.id);\n userVideoTrackSelection.set(toUserVideoTrackSelection(rendition));\n };\n\n #selectAudio = () => {\n const { presentation, selectedAudioTrackId, userAudioTrackSelection } = this.engine.state;\n const resolved = findAudioTrackById(presentation.get(), selectedAudioTrackId.get());\n const current = [...this.audioTracks].find((track) => isSameAudioTrack(toAudioKey(track), resolved));\n\n // `enabled` is not exclusive like video `selected`, so prefer a newly\n // enabled track over the one that is already playing.\n const enabled = [...this.audioTracks].filter((track) => track.enabled);\n const target = enabled.find((track) => track !== current) ?? enabled[0];\n if (!target) return;\n\n // Disable the rest so future change events resolve unambiguously.\n for (const track of enabled) {\n if (track !== target) track.enabled = false;\n }\n // Skip the write when the enabled track is already the resolved one\n // that's this projection's own reflection (or a no-op re-affirm), not a user switch.\n if (target === current) return;\n\n const audioTrack = this.#audioTracks.find((candidate) => candidate.id === target.id);\n userAudioTrackSelection.set(toUserAudioTrackSelection(audioTrack));\n };\n\n #removeVideoTracks() {\n for (const videoTrack of [...this.videoTracks]) this.removeVideoTrack(videoTrack);\n }\n\n #removeAudioTracks() {\n for (const audioTrack of [...this.audioTracks]) this.removeAudioTrack(audioTrack);\n }\n }\n\n return HlsVideoMediaMediaTracks as unknown as Base;\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { HlsVideoMediaAPI } from "./adapter.js";
2
+ import { HTMLVideoElementHost } from "@videojs/media/dom/video-host";
3
+ //#region src/playback/adapters/hls-video/media.d.ts
4
+ declare const HlsVideoMediaBase: import("@videojs/media/media-tracks").WithMediaTracks<import("@videojs/utils/types").Constructor<HTMLVideoElementHost & HlsVideoMediaAPI, any[]> & Omit<typeof HTMLVideoElementHost, "prototype"> & {
5
+ readonly alternativeMediaSuggestion: string | undefined;
6
+ }>;
7
+ declare class HlsVideoMedia extends HlsVideoMediaBase {}
8
+ //#endregion
9
+ export { HlsVideoMedia };
10
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","names":[],"sources":["../../../../../src/playback/adapters/hls-video/media.ts"],"mappings":";;;cAKM,yDAAiB,+CAAA,YAAA,uBAAA,2BAAA,YAAA;;;cAEV,sBAAsB"}
@@ -0,0 +1,11 @@
1
+ import { HlsVideoMediaMixin } from "./adapter.js";
2
+ import { HlsVideoMediaMediaTracksMixin } from "./media-tracks.js";
3
+ import { HTMLVideoElementHost } from "@videojs/media/dom/video-host";
4
+ import { MediaTracksMixin } from "@videojs/media/media-tracks";
5
+ //#region src/playback/adapters/hls-video/media.ts
6
+ const HlsVideoMediaBase = HlsVideoMediaMediaTracksMixin(MediaTracksMixin(HlsVideoMediaMixin(HTMLVideoElementHost)));
7
+ var HlsVideoMedia = class extends HlsVideoMediaBase {};
8
+ //#endregion
9
+ export { HlsVideoMedia };
10
+
11
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../../../../src/playback/adapters/hls-video/media.ts"],"sourcesContent":["import { HTMLVideoElementHost } from '@videojs/media/dom/video-host';\nimport { MediaTracksMixin } from '@videojs/media/media-tracks';\nimport { HlsVideoMediaMixin } from './adapter';\nimport { HlsVideoMediaMediaTracksMixin } from './media-tracks';\n\nconst HlsVideoMediaBase = HlsVideoMediaMediaTracksMixin(MediaTracksMixin(HlsVideoMediaMixin(HTMLVideoElementHost)));\n\nexport class HlsVideoMedia extends HlsVideoMediaBase {}\n"],"mappings":";;;;;AAKA,MAAM,oBAAoB,8BAA8B,iBAAiB,mBAAmB,oBAAoB,CAAC,CAAC;AAElH,IAAa,gBAAb,cAAmC,kBAAkB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { HlsAudioMedia } from "../hls-audio/media.js";
2
+ import { MuxMediaAPI } from "../mux-video/adapter.js";
3
+ import "../../../mux-audio.js";
4
+ //#region src/playback/adapters/mux-audio/media.d.ts
5
+ declare const MuxAudioMediaBase: import("@videojs/utils/types").Constructor<HlsAudioMedia & MuxMediaAPI, any[]> & Omit<typeof HlsAudioMedia, "prototype"> & {
6
+ readonly alternativeMediaSuggestion: string | undefined;
7
+ };
8
+ /**
9
+ * The Mux Media over the SPF audio-only HLS engine.
10
+ *
11
+ * Same Mux surface as the video flavor — `src`, the structured `source`, and the
12
+ * derived `contentData` all come from the shared mixin — over the subtractive
13
+ * engine, so only the audio renditions of whatever the playback ID names are
14
+ * fetched. That is the one place this diverges from the hls.js-backed
15
+ * `<mux-audio>`, which runs the full engine and downloads video renditions it
16
+ * never shows.
17
+ *
18
+ * `contentData` is kept rather than dropped, for the same reason its hls.js
19
+ * counterpart has it: a playback ID played as audio is usually a *video* asset,
20
+ * whose poster and storyboard exist and which an audio skin may well want. The
21
+ * element ignores it either way. Mux publishes neither for a genuinely
22
+ * audio-only asset, so those URLs 404 — see the known shortcoming on the video
23
+ * flavor, which shares the derivation.
24
+ */
25
+ declare class MuxAudioMedia extends MuxAudioMediaBase {}
26
+ //#endregion
27
+ export { MuxAudioMedia };
28
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","names":[],"sources":["../../../../../src/playback/adapters/mux-audio/media.ts"],"mappings":";;;;cASM,kDAAiB,YAAA,gBAAA,sBAAA,YAAA;;;;;;;;;;;;;;;;;;;;cAmBV,sBAAsB"}
@@ -0,0 +1,26 @@
1
+ import { HlsAudioMedia } from "../hls-audio/media.js";
2
+ import { MuxMediaMixin } from "../mux-video/adapter.js";
3
+ //#region src/playback/adapters/mux-audio/media.ts
4
+ const MuxAudioMediaBase = MuxMediaMixin(HlsAudioMedia);
5
+ /**
6
+ * The Mux Media over the SPF audio-only HLS engine.
7
+ *
8
+ * Same Mux surface as the video flavor — `src`, the structured `source`, and the
9
+ * derived `contentData` all come from the shared mixin — over the subtractive
10
+ * engine, so only the audio renditions of whatever the playback ID names are
11
+ * fetched. That is the one place this diverges from the hls.js-backed
12
+ * `<mux-audio>`, which runs the full engine and downloads video renditions it
13
+ * never shows.
14
+ *
15
+ * `contentData` is kept rather than dropped, for the same reason its hls.js
16
+ * counterpart has it: a playback ID played as audio is usually a *video* asset,
17
+ * whose poster and storyboard exist and which an audio skin may well want. The
18
+ * element ignores it either way. Mux publishes neither for a genuinely
19
+ * audio-only asset, so those URLs 404 — see the known shortcoming on the video
20
+ * flavor, which shares the derivation.
21
+ */
22
+ var MuxAudioMedia = class extends MuxAudioMediaBase {};
23
+ //#endregion
24
+ export { MuxAudioMedia };
25
+
26
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../../../../src/playback/adapters/mux-audio/media.ts"],"sourcesContent":["// Deliberately the mixin's own module, not `../mux-video`: that barrel reaches\n// `MuxVideoMedia`, and through it `HlsVideoMedia` and the full HLS engine. The\n// mixin alone carries no engine, which is what keeps this entry point\n// audio-only. Same shape as `hls-audio` importing its error surface\n// from `../hls-video`.\n\nimport { HlsAudioMedia } from '../hls-audio/media';\nimport { MuxMediaMixin } from '../mux-video/adapter';\n\nconst MuxAudioMediaBase = MuxMediaMixin(HlsAudioMedia);\n\n/**\n * The Mux Media over the SPF audio-only HLS engine.\n *\n * Same Mux surface as the video flavor — `src`, the structured `source`, and the\n * derived `contentData` all come from the shared mixin — over the subtractive\n * engine, so only the audio renditions of whatever the playback ID names are\n * fetched. That is the one place this diverges from the hls.js-backed\n * `<mux-audio>`, which runs the full engine and downloads video renditions it\n * never shows.\n *\n * `contentData` is kept rather than dropped, for the same reason its hls.js\n * counterpart has it: a playback ID played as audio is usually a *video* asset,\n * whose poster and storyboard exist and which an audio skin may well want. The\n * element ignores it either way. Mux publishes neither for a genuinely\n * audio-only asset, so those URLs 404 — see the known shortcoming on the video\n * flavor, which shares the derivation.\n */\nexport class MuxAudioMedia extends MuxAudioMediaBase {}\n"],"mappings":";;;AASA,MAAM,oBAAoB,cAAc,aAAa;;;;;;;;;;;;;;;;;;AAmBrD,IAAa,gBAAb,cAAmC,kBAAkB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { MuxContentData, MuxSourceBase } from "@videojs/media/dom/mux/source";
2
+ import { Constructor, MixinReturn } from "@videojs/utils/types";
3
+ //#region src/playback/adapters/mux-video/adapter.d.ts
4
+ interface MuxMediaProps {
5
+ src: string;
6
+ source: MuxSourceBase | null;
7
+ }
8
+ declare const muxMediaDefaultProps: MuxMediaProps;
9
+ interface MuxMediaAPI extends MuxMediaProps {
10
+ readonly contentData: MuxContentData;
11
+ }
12
+ /**
13
+ * Mux identity over any SPF Media: the structured `source`, the `src` derived
14
+ * from it, and the image URLs it describes.
15
+ *
16
+ * Everything here is Mux identity, so it carries no engine and both flavors get
17
+ * it unchanged — the video Media over the full HLS engine, the audio-only Media
18
+ * over the subtractive one. A mixin rather than a shared base class because each
19
+ * flavor extends a different SPF Media, so there is no common class to put this
20
+ * on, only a common `src` accessor to write through.
21
+ *
22
+ * Unlike the hls.js-backed `MuxMedia`, there is no inherited `source` to
23
+ * delegate to — the SPF Medias know only `src` — so this owns the structured
24
+ * source and dispatches `sourcechange` itself.
25
+ *
26
+ * @fires sourcechange - Fired when `source` changes, either directly or by parsing a new `src`. Read `source` for the new value.
27
+ */
28
+ declare function MuxMediaMixin<Base extends Constructor<any>>(BaseClass: Base): MixinReturn<Base, MuxMediaAPI> & {
29
+ readonly alternativeMediaSuggestion: string | undefined;
30
+ };
31
+ //#endregion
32
+ export { MuxMediaAPI, MuxMediaMixin, MuxMediaProps, muxMediaDefaultProps };
33
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","names":[],"sources":["../../../../../src/playback/adapters/mux-video/adapter.ts"],"mappings":";;;UAUiB;EACf;EACA,QAAQ;;cAGG,sBAAsB;UAKlB,oBAAoB;WAC1B,aAAa;;;;;;;;;;;;;;;;;;iBAmBR,cAAc,aAAa,kBAAkB,WAAW,OAkFpC,YAAY,MAAM;WACzC"}
@@ -0,0 +1,92 @@
1
+ import { createMuxPosterURL, createMuxStoryboardURL, createMuxVideoURL, parseMuxVideoURL } from "@videojs/media/dom/mux/source";
2
+ //#region src/playback/adapters/mux-video/adapter.ts
3
+ const muxMediaDefaultProps = {
4
+ src: "",
5
+ source: null
6
+ };
7
+ /**
8
+ * Mux identity over any SPF Media: the structured `source`, the `src` derived
9
+ * from it, and the image URLs it describes.
10
+ *
11
+ * Everything here is Mux identity, so it carries no engine and both flavors get
12
+ * it unchanged — the video Media over the full HLS engine, the audio-only Media
13
+ * over the subtractive one. A mixin rather than a shared base class because each
14
+ * flavor extends a different SPF Media, so there is no common class to put this
15
+ * on, only a common `src` accessor to write through.
16
+ *
17
+ * Unlike the hls.js-backed `MuxMedia`, there is no inherited `source` to
18
+ * delegate to — the SPF Medias know only `src` — so this owns the structured
19
+ * source and dispatches `sourcechange` itself.
20
+ *
21
+ * @fires sourcechange - Fired when `source` changes, either directly or by parsing a new `src`. Read `source` for the new value.
22
+ */
23
+ function MuxMediaMixin(BaseClass) {
24
+ class MuxMediaImpl extends BaseClass {
25
+ /**
26
+ * Named on the error copy when this engine can't play a source: the
27
+ * hls.js-backed Mux Media plays the MPEG-TS and DRM-protected sources that
28
+ * SPF does not, and it backs both `<mux-video>` and `<mux-audio>`.
29
+ *
30
+ * Names the flavor rather than an import path, because one Media is reached
31
+ * through three of them — `@videojs/html`, `@videojs/react`, and this package
32
+ * — and each has a different counterpart. The flavor suffix is the one thing
33
+ * common to the layers a consumer imports elements and components from.
34
+ */
35
+ static get alternativeMediaSuggestion() {
36
+ return "Try the hls.js-backed Mux media instead: import the `hls-js` flavor in place of the `spf` one.";
37
+ }
38
+ #source = muxMediaDefaultProps.source;
39
+ /**
40
+ * Media source URL. Setting a Mux stream URL
41
+ * (`https://stream.mux.com/<playback-id>.m3u8?...`) extracts the playback ID
42
+ * and query params into `source`; other URLs are kept as a plain `source.src`.
43
+ *
44
+ * Only playback options carry over. Mux identity comes from the URL, and the
45
+ * signed `poster`, `storyboard`, and `drm` tokens are scoped to a playback ID,
46
+ * so carrying them onto a different source would build rejected URLs.
47
+ */
48
+ get src() {
49
+ return super.src;
50
+ }
51
+ set src(value) {
52
+ if (super.src === value) return;
53
+ this.source = parseMuxVideoURL(value) ?? (value ? { src: value } : null);
54
+ }
55
+ /**
56
+ * Structured Mux source. Setting it derives `src` from the playback ID, custom
57
+ * domain, and `playback` params (appended as `snake_case` query params). A
58
+ * `playback.token` replaces all other params — signed URLs bake them into the
59
+ * token.
60
+ */
61
+ get source() {
62
+ return this.#source;
63
+ }
64
+ set source(value) {
65
+ const source = value ?? null;
66
+ if (source === this.#source) return;
67
+ this.#source = source;
68
+ super.src = source && (createMuxVideoURL(source) ?? source.src) || "";
69
+ this.dispatchEvent?.(new Event("sourcechange"));
70
+ }
71
+ /**
72
+ * Image URLs `source` describes rather than plays: `poster` from its `poster`
73
+ * params, `storyboard` from its `storyboard` params.
74
+ *
75
+ * Read-only and re-derived on read, so read it again after `sourcechange`.
76
+ * Nothing here is applied for you.
77
+ */
78
+ get contentData() {
79
+ const poster = createMuxPosterURL(this.#source);
80
+ const storyboard = createMuxStoryboardURL(this.#source);
81
+ return {
82
+ ...poster && { poster },
83
+ ...storyboard && { storyboard }
84
+ };
85
+ }
86
+ }
87
+ return MuxMediaImpl;
88
+ }
89
+ //#endregion
90
+ export { MuxMediaMixin, muxMediaDefaultProps };
91
+
92
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","names":["#source"],"sources":["../../../../../src/playback/adapters/mux-video/adapter.ts"],"sourcesContent":["import {\n createMuxPosterURL,\n createMuxStoryboardURL,\n createMuxVideoURL,\n type MuxContentData,\n type MuxSourceBase,\n parseMuxVideoURL,\n} from '@videojs/media/dom/mux/source';\nimport type { Constructor, MixinReturn } from '@videojs/utils/types';\n\nexport interface MuxMediaProps {\n src: string;\n source: MuxSourceBase | null;\n}\n\nexport const muxMediaDefaultProps: MuxMediaProps = {\n src: '',\n source: null,\n};\n\nexport interface MuxMediaAPI extends MuxMediaProps {\n readonly contentData: MuxContentData;\n}\n\n/**\n * Mux identity over any SPF Media: the structured `source`, the `src` derived\n * from it, and the image URLs it describes.\n *\n * Everything here is Mux identity, so it carries no engine and both flavors get\n * it unchanged — the video Media over the full HLS engine, the audio-only Media\n * over the subtractive one. A mixin rather than a shared base class because each\n * flavor extends a different SPF Media, so there is no common class to put this\n * on, only a common `src` accessor to write through.\n *\n * Unlike the hls.js-backed `MuxMedia`, there is no inherited `source` to\n * delegate to — the SPF Medias know only `src` — so this owns the structured\n * source and dispatches `sourcechange` itself.\n *\n * @fires sourcechange - Fired when `source` changes, either directly or by parsing a new `src`. Read `source` for the new value.\n */\nexport function MuxMediaMixin<Base extends Constructor<any>>(BaseClass: Base) {\n class MuxMediaImpl extends BaseClass {\n /**\n * Named on the error copy when this engine can't play a source: the\n * hls.js-backed Mux Media plays the MPEG-TS and DRM-protected sources that\n * SPF does not, and it backs both `<mux-video>` and `<mux-audio>`.\n *\n * Names the flavor rather than an import path, because one Media is reached\n * through three of them — `@videojs/html`, `@videojs/react`, and this package\n * — and each has a different counterpart. The flavor suffix is the one thing\n * common to the layers a consumer imports elements and components from.\n */\n static get alternativeMediaSuggestion(): string | undefined {\n return 'Try the hls.js-backed Mux media instead: import the `hls-js` flavor in place of the `spf` one.';\n }\n\n #source: MuxSourceBase | null = muxMediaDefaultProps.source;\n\n /**\n * Media source URL. Setting a Mux stream URL\n * (`https://stream.mux.com/<playback-id>.m3u8?...`) extracts the playback ID\n * and query params into `source`; other URLs are kept as a plain `source.src`.\n *\n * Only playback options carry over. Mux identity comes from the URL, and the\n * signed `poster`, `storyboard`, and `drm` tokens are scoped to a playback ID,\n * so carrying them onto a different source would build rejected URLs.\n */\n get src(): string {\n return super.src;\n }\n\n set src(value: string) {\n // A URL already describing the current source leaves it alone. The elements\n // reflect the derived `src` back to the host, and re-deriving would drop the\n // params a Mux URL does not carry, such as `poster`.\n if (super.src === value) return;\n\n this.source = parseMuxVideoURL(value) ?? (value ? { src: value } : null);\n }\n\n /**\n * Structured Mux source. Setting it derives `src` from the playback ID, custom\n * domain, and `playback` params (appended as `snake_case` query params). A\n * `playback.token` replaces all other params — signed URLs bake them into the\n * token.\n */\n get source(): MuxSourceBase | null {\n return this.#source;\n }\n\n set source(value: MuxSourceBase | null) {\n const source = value ?? null;\n // Changing anything takes a new object, so handing the same one back costs\n // nothing.\n if (source === this.#source) return;\n\n this.#source = source;\n super.src = (source && (createMuxVideoURL(source) ?? source.src)) || '';\n\n this.dispatchEvent?.(new Event('sourcechange'));\n }\n\n /**\n * Image URLs `source` describes rather than plays: `poster` from its `poster`\n * params, `storyboard` from its `storyboard` params.\n *\n * Read-only and re-derived on read, so read it again after `sourcechange`.\n * Nothing here is applied for you.\n */\n get contentData(): MuxContentData {\n const poster = createMuxPosterURL(this.#source);\n const storyboard = createMuxStoryboardURL(this.#source);\n\n return {\n ...(poster && { poster }),\n ...(storyboard && { storyboard }),\n };\n }\n }\n\n // `MixinReturn` sources statics from `Base`, so this mixin's own needs adding\n // back to the type or callers can't read it.\n return MuxMediaImpl as unknown as MixinReturn<Base, MuxMediaAPI> & {\n readonly alternativeMediaSuggestion: string | undefined;\n };\n}\n"],"mappings":";;AAeA,MAAa,uBAAsC;CACjD,KAAK;CACL,QAAQ;AACV;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cAA6C,WAAiB;CAC5E,MAAM,qBAAqB,UAAU;;;;;;;;;;;EAWnC,WAAW,6BAAiD;GAC1D,OAAO;EACT;EAEA,UAAgC,qBAAqB;;;;;;;;;;EAWrD,IAAI,MAAc;GAChB,OAAO,MAAM;EACf;EAEA,IAAI,IAAI,OAAe;GAIrB,IAAI,MAAM,QAAQ,OAAO;GAEzB,KAAK,SAAS,iBAAiB,KAAK,MAAM,QAAQ,EAAE,KAAK,MAAM,IAAI;EACrE;;;;;;;EAQA,IAAI,SAA+B;GACjC,OAAO,KAAKA;EACd;EAEA,IAAI,OAAO,OAA6B;GACtC,MAAM,SAAS,SAAS;GAGxB,IAAI,WAAW,KAAKA,SAAS;GAE7B,KAAKA,UAAU;GACf,MAAM,MAAO,WAAW,kBAAkB,MAAM,KAAK,OAAO,QAAS;GAErE,KAAK,gBAAgB,IAAI,MAAM,cAAc,CAAC;EAChD;;;;;;;;EASA,IAAI,cAA8B;GAChC,MAAM,SAAS,mBAAmB,KAAKA,OAAO;GAC9C,MAAM,aAAa,uBAAuB,KAAKA,OAAO;GAEtD,OAAO;IACL,GAAI,UAAU,EAAE,OAAO;IACvB,GAAI,cAAc,EAAE,WAAW;GACjC;EACF;CACF;CAIA,OAAO;AAGT"}
@@ -0,0 +1,24 @@
1
+ import { HlsVideoMedia } from "../hls-video/media.js";
2
+ import { MuxMediaAPI } from "./adapter.js";
3
+ //#region src/playback/adapters/mux-video/media.d.ts
4
+ declare const MuxVideoMediaBase: import("@videojs/utils/types").Constructor<HlsVideoMedia & MuxMediaAPI, any[]> & Omit<typeof HlsVideoMedia, "prototype"> & {
5
+ readonly alternativeMediaSuggestion: string | undefined;
6
+ };
7
+ /**
8
+ * The Mux Media over the SPF HLS engine.
9
+ *
10
+ * Mirrors `@videojs/media/dom/mux`'s hls.js-backed `MuxMedia` — same class shape,
11
+ * same `src`/`source` relationship, same derived `contentData` — over a different
12
+ * engine, though named for the flavor rather than sharing that class's name. It
13
+ * carries no `engine` or `preferPlayback`: SPF publishes no
14
+ * engine-shaped config for a consumer to pass, so the source is Mux identity and
15
+ * nothing else.
16
+ *
17
+ * `source.drm` is accepted but inert. SPF prunes encrypted renditions and reports
18
+ * unsupported DRM, and `alternativeMediaSuggestion` points at the hls.js-backed
19
+ * import, so a protected source fails with copy that says where to go.
20
+ */
21
+ declare class MuxVideoMedia extends MuxVideoMediaBase {}
22
+ //#endregion
23
+ export { MuxVideoMedia };
24
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","names":[],"sources":["../../../../../src/playback/adapters/mux-video/media.ts"],"mappings":";;;cAGM,kDAAiB,YAAA,gBAAA,sBAAA,YAAA;;;;;;;;;;;;;;;;;cAgBV,sBAAsB"}
@@ -0,0 +1,23 @@
1
+ import { HlsVideoMedia } from "../hls-video/media.js";
2
+ import { MuxMediaMixin } from "./adapter.js";
3
+ //#region src/playback/adapters/mux-video/media.ts
4
+ const MuxVideoMediaBase = MuxMediaMixin(HlsVideoMedia);
5
+ /**
6
+ * The Mux Media over the SPF HLS engine.
7
+ *
8
+ * Mirrors `@videojs/media/dom/mux`'s hls.js-backed `MuxMedia` — same class shape,
9
+ * same `src`/`source` relationship, same derived `contentData` — over a different
10
+ * engine, though named for the flavor rather than sharing that class's name. It
11
+ * carries no `engine` or `preferPlayback`: SPF publishes no
12
+ * engine-shaped config for a consumer to pass, so the source is Mux identity and
13
+ * nothing else.
14
+ *
15
+ * `source.drm` is accepted but inert. SPF prunes encrypted renditions and reports
16
+ * unsupported DRM, and `alternativeMediaSuggestion` points at the hls.js-backed
17
+ * import, so a protected source fails with copy that says where to go.
18
+ */
19
+ var MuxVideoMedia = class extends MuxVideoMediaBase {};
20
+ //#endregion
21
+ export { MuxVideoMedia };
22
+
23
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../../../../src/playback/adapters/mux-video/media.ts"],"sourcesContent":["import { HlsVideoMedia } from '../hls-video/media';\nimport { MuxMediaMixin } from './adapter';\n\nconst MuxVideoMediaBase = MuxMediaMixin(HlsVideoMedia);\n\n/**\n * The Mux Media over the SPF HLS engine.\n *\n * Mirrors `@videojs/media/dom/mux`'s hls.js-backed `MuxMedia` — same class shape,\n * same `src`/`source` relationship, same derived `contentData` — over a different\n * engine, though named for the flavor rather than sharing that class's name. It\n * carries no `engine` or `preferPlayback`: SPF publishes no\n * engine-shaped config for a consumer to pass, so the source is Mux identity and\n * nothing else.\n *\n * `source.drm` is accepted but inert. SPF prunes encrypted renditions and reports\n * unsupported DRM, and `alternativeMediaSuggestion` points at the hls.js-backed\n * import, so a protected source fails with copy that says where to go.\n */\nexport class MuxVideoMedia extends MuxVideoMediaBase {}\n"],"mappings":";;;AAGA,MAAM,oBAAoB,cAAc,aAAa;;;;;;;;;;;;;;;AAgBrD,IAAa,gBAAb,cAAmC,kBAAkB,CAAC"}
@@ -0,0 +1,77 @@
1
+ import { computed, update } from "../../core/signals/primitives.js";
2
+ import { defineBehavior } from "../../core/composition/create-composition.js";
3
+ import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
4
+ import { isResolvedPresentation } from "../../media/types/index.js";
5
+ //#region src/playback/behaviors/collect-errors.ts
6
+ /**
7
+ * **Owns the engine's error sequence.** Reporters append through
8
+ * {@link emitError}; this behavior owns the slot and its per-source lifecycle,
9
+ * clearing it on exit so a new source starts clean and the sequence can't grow
10
+ * unbounded across a session.
11
+ *
12
+ * Same split as `setupFailoverMonitor` and `failedCdns`: writes come from
13
+ * wherever the condition is detected, one behavior owns the slot. Deliberately
14
+ * has no `effects` — it holds no policy and derives nothing. Severity is decided
15
+ * at the adapter, not here (see `internal/design/spf/features/errors.md`), which
16
+ * is why this is a lifecycle owner rather than an error *handler*.
17
+ *
18
+ * Clearing binds to *exit* of `presentation-resolved`, mirroring the sibling
19
+ * mixins' clear-on-teardown (`emptied` / `MEDIA_DETACHED`). A live reload swaps
20
+ * the presentation object without leaving the resolved state, so it doesn't
21
+ * clear — only an actual source change or destroy does. Known gap: a
22
+ * resolved→resolved source swap that never passes through unresolved carries the
23
+ * prior source's errors forward; `resolve-track` guards the same transition with
24
+ * a commit-time id check, and doing likewise here is a follow-up.
25
+ *
26
+ * The vocabulary itself ({@link SvtaError} and the codes) is DOM- and
27
+ * signal-free in `media/errors`; only the write seam lives here, with the slot
28
+ * it writes.
29
+ */
30
+ /**
31
+ * Append `error` to the engine's error sequence. No-op when no owner is
32
+ * composed. Replaces the array rather than mutating it, so signal consumers
33
+ * notify; duplicates are kept, since a repeated condition is a real observation.
34
+ * Writes go through `update` so concurrent reporters can't lose each other's
35
+ * appends.
36
+ *
37
+ * Every emission is logged, deliberately *before* the owner check. A condition
38
+ * emitted with no `collectErrors` composed is dropped on the floor — that's the
39
+ * case where a log is the only evidence it happened at all, so gating the log on
40
+ * the same check would hide exactly what's worth seeing. Emissions that *are*
41
+ * collected still get logged, because reaching `state.errors` is no guarantee of
42
+ * reaching a person: only *verdicts* are promoted to the media surface, so every
43
+ * cause (and any non-fatal notice) is otherwise invisible outside a debugger.
44
+ *
45
+ * Ungated rather than `__DEV__`-only, matching the other reporting paths in this
46
+ * package (`resolve-presentation`, `track-switching`, the segment actors).
47
+ */
48
+ function emitError(state, error) {
49
+ console.error("[spf] reported condition", error);
50
+ if (!state.errors) return;
51
+ update(state.errors, (errors) => [...errors ?? [], error]);
52
+ }
53
+ /**
54
+ * Own `errors` for the resolved source's lifetime.
55
+ *
56
+ * @example
57
+ * const reactor = collectErrors.setup({ state });
58
+ */
59
+ const collectErrors = defineBehavior({
60
+ stateKeys: ["presentation", "errors"],
61
+ contextKeys: [],
62
+ setup: ({ state }) => {
63
+ const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
64
+ return createMachineReactor({
65
+ initial: "presentation-unresolved",
66
+ monitor: () => derivedStateSignal.get(),
67
+ states: {
68
+ "presentation-unresolved": {},
69
+ "presentation-resolved": { entry: () => () => state.errors.set(void 0) }
70
+ }
71
+ });
72
+ }
73
+ });
74
+ //#endregion
75
+ export { collectErrors, emitError };
76
+
77
+ //# sourceMappingURL=collect-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-errors.js","names":[],"sources":["../../../../src/playback/behaviors/collect-errors.ts"],"sourcesContent":["/**\n * **Owns the engine's error sequence.** Reporters append through\n * {@link emitError}; this behavior owns the slot and its per-source lifecycle,\n * clearing it on exit so a new source starts clean and the sequence can't grow\n * unbounded across a session.\n *\n * Same split as `setupFailoverMonitor` and `failedCdns`: writes come from\n * wherever the condition is detected, one behavior owns the slot. Deliberately\n * has no `effects` — it holds no policy and derives nothing. Severity is decided\n * at the adapter, not here (see `internal/design/spf/features/errors.md`), which\n * is why this is a lifecycle owner rather than an error *handler*.\n *\n * Clearing binds to *exit* of `presentation-resolved`, mirroring the sibling\n * mixins' clear-on-teardown (`emptied` / `MEDIA_DETACHED`). A live reload swaps\n * the presentation object without leaving the resolved state, so it doesn't\n * clear — only an actual source change or destroy does. Known gap: a\n * resolved→resolved source swap that never passes through unresolved carries the\n * prior source's errors forward; `resolve-track` guards the same transition with\n * a commit-time id check, and doing likewise here is a follow-up.\n *\n * The vocabulary itself ({@link SvtaError} and the codes) is DOM- and\n * signal-free in `media/errors`; only the write seam lives here, with the slot\n * it writes.\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport type { SvtaError } from '../../media/errors';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\n\nexport interface CollectErrorsState {\n presentation?: MaybeResolvedPresentation;\n errors?: SvtaError[];\n}\n\n/**\n * State an error reporter writes into. The slot is *optional*: a behavior reports\n * through this seam without declaring ownership in its own typed slice, and\n * emission no-ops when `collectErrors` isn't composed. Same contract as\n * `failedCdns` / `failoverFetch`.\n */\nexport interface ErrorEmitterState {\n errors?: Signal<SvtaError[] | undefined>;\n}\n\n/**\n * Append `error` to the engine's error sequence. No-op when no owner is\n * composed. Replaces the array rather than mutating it, so signal consumers\n * notify; duplicates are kept, since a repeated condition is a real observation.\n * Writes go through `update` so concurrent reporters can't lose each other's\n * appends.\n *\n * Every emission is logged, deliberately *before* the owner check. A condition\n * emitted with no `collectErrors` composed is dropped on the floor — that's the\n * case where a log is the only evidence it happened at all, so gating the log on\n * the same check would hide exactly what's worth seeing. Emissions that *are*\n * collected still get logged, because reaching `state.errors` is no guarantee of\n * reaching a person: only *verdicts* are promoted to the media surface, so every\n * cause (and any non-fatal notice) is otherwise invisible outside a debugger.\n *\n * Ungated rather than `__DEV__`-only, matching the other reporting paths in this\n * package (`resolve-presentation`, `track-switching`, the segment actors).\n */\nexport function emitError(state: ErrorEmitterState, error: SvtaError): void {\n console.error('[spf] reported condition', error);\n if (!state.errors) return;\n update(state.errors, (errors) => [...(errors ?? []), error]);\n}\n\n/**\n * Own `errors` for the resolved source's lifetime.\n *\n * @example\n * const reactor = collectErrors.setup({ state });\n */\nexport const collectErrors = defineBehavior({\n stateKeys: ['presentation', 'errors'],\n contextKeys: [],\n setup: ({\n state,\n }: {\n state: {\n presentation: ReadonlySignal<CollectErrorsState['presentation']>;\n errors: Signal<CollectErrorsState['errors']>;\n };\n }) => {\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Cleanup-binds-to-setup: reset for the next source on exit (src\n // unload + destroy).\n entry: () => () => state.errors.set(undefined),\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,SAAgB,UAAU,OAA0B,OAAwB;CAC1E,QAAQ,MAAM,4BAA4B,KAAK;CAC/C,IAAI,CAAC,MAAM,QAAQ;CACnB,OAAO,MAAM,SAAS,WAAW,CAAC,GAAI,UAAU,CAAC,GAAI,KAAK,CAAC;AAC7D;;;;;;;AAQA,MAAa,gBAAgB,eAAe;CAC1C,WAAW,CAAC,gBAAgB,QAAQ;CACpC,aAAa,CAAC;CACd,QAAQ,EACN,YAMI;EACJ,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;EAEA,OAAO,qBAAqB;GAC1B,SAAS;GACT,eAAe,mBAAmB,IAAI;GACtC,QAAQ;IACN,2BAA2B,CAAC;IAC5B,yBAAyB,EAGvB,mBAAmB,MAAM,OAAO,IAAI,KAAA,CAAS,EAC/C;GACF;EACF,CAAC;CACH;AACF,CAAC"}
@@ -30,6 +30,7 @@ function deriveState(presentation, mediaSource, msIsOpen, videoBufferActor, audi
30
30
  if (!initTrackId) return "preconditions-unmet";
31
31
  const track = findTrackById(presentation, initTrackId);
32
32
  if (!track || !isResolvedTrack(track)) return "preconditions-unmet";
33
+ if (!Number.isFinite(track.duration)) return "preconditions-unmet";
33
34
  if (!isLastSegmentAppended(track.segments, appended)) return "preconditions-unmet";
34
35
  if (track.segments.length > 0) {
35
36
  const start = track.segments[track.segments.length - 1].startTime;