@videojs/spf 10.0.0-beta.1 → 10.0.0-beta.11

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 (242) hide show
  1. package/README.md +16 -40
  2. package/dist/default/all.js +16 -0
  3. package/dist/default/core/abr/bandwidth-estimator.js +96 -0
  4. package/dist/default/core/abr/bandwidth-estimator.js.map +1 -0
  5. package/dist/default/core/abr/ewma.js +71 -0
  6. package/dist/default/core/abr/ewma.js.map +1 -0
  7. package/dist/default/core/abr/quality-selection.js +53 -0
  8. package/dist/default/core/abr/quality-selection.js.map +1 -0
  9. package/dist/default/core/buffer/back-buffer.js +46 -0
  10. package/dist/default/core/buffer/back-buffer.js.map +1 -0
  11. package/dist/default/core/buffer/forward-buffer.js +73 -0
  12. package/dist/default/core/buffer/forward-buffer.js.map +1 -0
  13. package/dist/default/core/events/create-event-stream.js +48 -0
  14. package/dist/default/core/events/create-event-stream.js.map +1 -0
  15. package/dist/default/core/features/calculate-presentation-duration.js +51 -0
  16. package/dist/default/core/features/calculate-presentation-duration.js.map +1 -0
  17. package/dist/default/core/features/quality-switching.js +68 -0
  18. package/dist/default/core/features/quality-switching.js.map +1 -0
  19. package/dist/default/core/features/resolve-presentation.js +99 -0
  20. package/dist/default/core/features/resolve-presentation.js.map +1 -0
  21. package/dist/default/core/features/resolve-track.js +77 -0
  22. package/dist/default/core/features/resolve-track.js.map +1 -0
  23. package/dist/default/core/features/select-tracks.js +147 -0
  24. package/dist/default/core/features/select-tracks.js.map +1 -0
  25. package/dist/default/core/hls/parse-attributes.js +134 -0
  26. package/dist/default/core/hls/parse-attributes.js.map +1 -0
  27. package/dist/default/core/hls/parse-media-playlist.js +83 -0
  28. package/dist/default/core/hls/parse-media-playlist.js.map +1 -0
  29. package/dist/default/core/hls/parse-multivariant.js +215 -0
  30. package/dist/default/core/hls/parse-multivariant.js.map +1 -0
  31. package/dist/default/core/hls/resolve-url.js +11 -0
  32. package/dist/default/core/hls/resolve-url.js.map +1 -0
  33. package/dist/default/core/reactive/combine-latest.js +72 -0
  34. package/dist/default/core/reactive/combine-latest.js.map +1 -0
  35. package/dist/default/core/state/create-state.js +143 -0
  36. package/dist/default/core/state/create-state.js.map +1 -0
  37. package/dist/default/core/task.js +122 -0
  38. package/dist/default/core/task.js.map +1 -0
  39. package/dist/default/core/types/index.js +15 -0
  40. package/dist/default/core/types/index.js.map +1 -0
  41. package/dist/default/core/utils/generate-id.js +21 -0
  42. package/dist/default/core/utils/generate-id.js.map +1 -0
  43. package/dist/default/core/utils/track-selection.js +31 -0
  44. package/dist/default/core/utils/track-selection.js.map +1 -0
  45. package/dist/default/dom/features/end-of-stream.js +169 -0
  46. package/dist/default/dom/features/end-of-stream.js.map +1 -0
  47. package/dist/default/dom/features/load-segments.js +188 -0
  48. package/dist/default/dom/features/load-segments.js.map +1 -0
  49. package/dist/default/dom/features/load-text-track-cues.js +151 -0
  50. package/dist/default/dom/features/load-text-track-cues.js.map +1 -0
  51. package/dist/default/dom/features/segment-loader-actor.js +197 -0
  52. package/dist/default/dom/features/segment-loader-actor.js.map +1 -0
  53. package/dist/default/dom/features/setup-mediasource.js +55 -0
  54. package/dist/default/dom/features/setup-mediasource.js.map +1 -0
  55. package/dist/default/dom/features/setup-sourcebuffer.js +70 -0
  56. package/dist/default/dom/features/setup-sourcebuffer.js.map +1 -0
  57. package/dist/default/dom/features/setup-text-tracks.js +88 -0
  58. package/dist/default/dom/features/setup-text-tracks.js.map +1 -0
  59. package/dist/default/dom/features/sync-selected-text-track-from-dom.js +63 -0
  60. package/dist/default/dom/features/sync-selected-text-track-from-dom.js.map +1 -0
  61. package/dist/default/dom/features/sync-text-track-modes.js +38 -0
  62. package/dist/default/dom/features/sync-text-track-modes.js.map +1 -0
  63. package/dist/default/dom/features/track-current-time.js +46 -0
  64. package/dist/default/dom/features/track-current-time.js.map +1 -0
  65. package/dist/default/dom/features/track-playback-initiated.js +55 -0
  66. package/dist/default/dom/features/track-playback-initiated.js.map +1 -0
  67. package/dist/{dom/index.js → default/dom/features/track-playback-rate.js} +2 -4
  68. package/dist/default/dom/features/track-playback-rate.js.map +1 -0
  69. package/dist/default/dom/features/update-duration.js +72 -0
  70. package/dist/default/dom/features/update-duration.js.map +1 -0
  71. package/dist/default/dom/media/append-segment.js +60 -0
  72. package/dist/default/dom/media/append-segment.js.map +1 -0
  73. package/dist/default/dom/media/buffer-flusher.js +55 -0
  74. package/dist/default/dom/media/buffer-flusher.js.map +1 -0
  75. package/dist/default/dom/media/mediasource-setup.js +154 -0
  76. package/dist/default/dom/media/mediasource-setup.js.map +1 -0
  77. package/dist/default/dom/media/source-buffer-actor.js +186 -0
  78. package/dist/default/dom/media/source-buffer-actor.js.map +1 -0
  79. package/dist/default/dom/network/chunked-stream-iterable.js +49 -0
  80. package/dist/default/dom/network/chunked-stream-iterable.js.map +1 -0
  81. package/dist/default/dom/network/fetch.js +56 -0
  82. package/dist/default/dom/network/fetch.js.map +1 -0
  83. package/dist/default/dom/playback-engine/adapter.js +95 -0
  84. package/dist/default/dom/playback-engine/adapter.js.map +1 -0
  85. package/dist/default/dom/playback-engine/engine.js +176 -0
  86. package/dist/default/dom/playback-engine/engine.js.map +1 -0
  87. package/dist/default/dom/playback-engine.js +4 -0
  88. package/dist/default/dom/text/parse-vtt-segment.js +56 -0
  89. package/dist/default/dom/text/parse-vtt-segment.js.map +1 -0
  90. package/dist/default/dom.js +11 -0
  91. package/dist/{src-DRsS_H80.js → default/index.js} +2 -2
  92. package/dist/default/index.js.map +1 -0
  93. package/dist/dev/all.js +16 -0
  94. package/dist/dev/core/abr/bandwidth-estimator.d.ts +35 -0
  95. package/dist/dev/core/abr/bandwidth-estimator.d.ts.map +1 -0
  96. package/dist/dev/core/abr/bandwidth-estimator.js +96 -0
  97. package/dist/dev/core/abr/bandwidth-estimator.js.map +1 -0
  98. package/dist/dev/core/abr/ewma.js +71 -0
  99. package/dist/dev/core/abr/ewma.js.map +1 -0
  100. package/dist/dev/core/abr/quality-selection.js +53 -0
  101. package/dist/dev/core/abr/quality-selection.js.map +1 -0
  102. package/dist/dev/core/actor.d.ts +31 -0
  103. package/dist/dev/core/actor.d.ts.map +1 -0
  104. package/dist/dev/core/buffer/back-buffer.js +46 -0
  105. package/dist/dev/core/buffer/back-buffer.js.map +1 -0
  106. package/dist/dev/core/buffer/forward-buffer.js +73 -0
  107. package/dist/dev/core/buffer/forward-buffer.js.map +1 -0
  108. package/dist/dev/core/events/create-event-stream.d.ts +48 -0
  109. package/dist/dev/core/events/create-event-stream.d.ts.map +1 -0
  110. package/dist/dev/core/events/create-event-stream.js +48 -0
  111. package/dist/dev/core/events/create-event-stream.js.map +1 -0
  112. package/dist/dev/core/features/calculate-presentation-duration.js +51 -0
  113. package/dist/dev/core/features/calculate-presentation-duration.js.map +1 -0
  114. package/dist/dev/core/features/quality-switching.js +68 -0
  115. package/dist/dev/core/features/quality-switching.js.map +1 -0
  116. package/dist/dev/core/features/resolve-presentation.d.ts +16 -0
  117. package/dist/dev/core/features/resolve-presentation.d.ts.map +1 -0
  118. package/dist/dev/core/features/resolve-presentation.js +99 -0
  119. package/dist/dev/core/features/resolve-presentation.js.map +1 -0
  120. package/dist/dev/core/features/resolve-track.d.ts +13 -0
  121. package/dist/dev/core/features/resolve-track.d.ts.map +1 -0
  122. package/dist/dev/core/features/resolve-track.js +77 -0
  123. package/dist/dev/core/features/resolve-track.js.map +1 -0
  124. package/dist/dev/core/features/select-tracks.d.ts +11 -0
  125. package/dist/dev/core/features/select-tracks.d.ts.map +1 -0
  126. package/dist/dev/core/features/select-tracks.js +147 -0
  127. package/dist/dev/core/features/select-tracks.js.map +1 -0
  128. package/dist/dev/core/hls/parse-attributes.js +134 -0
  129. package/dist/dev/core/hls/parse-attributes.js.map +1 -0
  130. package/dist/dev/core/hls/parse-media-playlist.js +83 -0
  131. package/dist/dev/core/hls/parse-media-playlist.js.map +1 -0
  132. package/dist/dev/core/hls/parse-multivariant.js +215 -0
  133. package/dist/dev/core/hls/parse-multivariant.js.map +1 -0
  134. package/dist/dev/core/hls/resolve-url.js +11 -0
  135. package/dist/dev/core/hls/resolve-url.js.map +1 -0
  136. package/dist/dev/core/reactive/combine-latest.js +72 -0
  137. package/dist/dev/core/reactive/combine-latest.js.map +1 -0
  138. package/dist/dev/core/state/create-state.d.ts +104 -0
  139. package/dist/dev/core/state/create-state.d.ts.map +1 -0
  140. package/dist/dev/core/state/create-state.js +143 -0
  141. package/dist/dev/core/state/create-state.js.map +1 -0
  142. package/dist/dev/core/task.js +122 -0
  143. package/dist/dev/core/task.js.map +1 -0
  144. package/dist/dev/core/types/index.d.ts +173 -0
  145. package/dist/dev/core/types/index.d.ts.map +1 -0
  146. package/dist/dev/core/types/index.js +15 -0
  147. package/dist/dev/core/types/index.js.map +1 -0
  148. package/dist/dev/core/utils/generate-id.js +21 -0
  149. package/dist/dev/core/utils/generate-id.js.map +1 -0
  150. package/dist/dev/core/utils/track-selection.d.ts +16 -0
  151. package/dist/dev/core/utils/track-selection.d.ts.map +1 -0
  152. package/dist/dev/core/utils/track-selection.js +31 -0
  153. package/dist/dev/core/utils/track-selection.js.map +1 -0
  154. package/dist/dev/dom/features/end-of-stream.js +169 -0
  155. package/dist/dev/dom/features/end-of-stream.js.map +1 -0
  156. package/dist/dev/dom/features/load-segments.d.ts +72 -0
  157. package/dist/dev/dom/features/load-segments.d.ts.map +1 -0
  158. package/dist/dev/dom/features/load-segments.js +188 -0
  159. package/dist/dev/dom/features/load-segments.js.map +1 -0
  160. package/dist/dev/dom/features/load-text-track-cues.d.ts +61 -0
  161. package/dist/dev/dom/features/load-text-track-cues.d.ts.map +1 -0
  162. package/dist/dev/dom/features/load-text-track-cues.js +151 -0
  163. package/dist/dev/dom/features/load-text-track-cues.js.map +1 -0
  164. package/dist/dev/dom/features/segment-loader-actor.js +197 -0
  165. package/dist/dev/dom/features/segment-loader-actor.js.map +1 -0
  166. package/dist/dev/dom/features/setup-mediasource.js +55 -0
  167. package/dist/dev/dom/features/setup-mediasource.js.map +1 -0
  168. package/dist/dev/dom/features/setup-sourcebuffer.d.ts +9 -0
  169. package/dist/dev/dom/features/setup-sourcebuffer.d.ts.map +1 -0
  170. package/dist/dev/dom/features/setup-sourcebuffer.js +70 -0
  171. package/dist/dev/dom/features/setup-sourcebuffer.js.map +1 -0
  172. package/dist/dev/dom/features/setup-text-tracks.js +88 -0
  173. package/dist/dev/dom/features/setup-text-tracks.js.map +1 -0
  174. package/dist/dev/dom/features/sync-selected-text-track-from-dom.js +63 -0
  175. package/dist/dev/dom/features/sync-selected-text-track-from-dom.js.map +1 -0
  176. package/dist/dev/dom/features/sync-text-track-modes.js +38 -0
  177. package/dist/dev/dom/features/sync-text-track-modes.js.map +1 -0
  178. package/dist/dev/dom/features/track-current-time.d.ts +40 -0
  179. package/dist/dev/dom/features/track-current-time.d.ts.map +1 -0
  180. package/dist/dev/dom/features/track-current-time.js +46 -0
  181. package/dist/dev/dom/features/track-current-time.js.map +1 -0
  182. package/dist/dev/dom/features/track-playback-initiated.d.ts +53 -0
  183. package/dist/dev/dom/features/track-playback-initiated.d.ts.map +1 -0
  184. package/dist/dev/dom/features/track-playback-initiated.js +55 -0
  185. package/dist/dev/dom/features/track-playback-initiated.js.map +1 -0
  186. package/dist/dev/dom/features/track-playback-rate.d.ts +34 -0
  187. package/dist/dev/dom/features/track-playback-rate.d.ts.map +1 -0
  188. package/dist/dev/dom/features/track-playback-rate.js +36 -0
  189. package/dist/dev/dom/features/track-playback-rate.js.map +1 -0
  190. package/dist/dev/dom/features/update-duration.js +72 -0
  191. package/dist/dev/dom/features/update-duration.js.map +1 -0
  192. package/dist/dev/dom/media/append-segment.d.ts +24 -0
  193. package/dist/dev/dom/media/append-segment.d.ts.map +1 -0
  194. package/dist/dev/dom/media/append-segment.js +60 -0
  195. package/dist/dev/dom/media/append-segment.js.map +1 -0
  196. package/dist/dev/dom/media/buffer-flusher.d.ts +24 -0
  197. package/dist/dev/dom/media/buffer-flusher.d.ts.map +1 -0
  198. package/dist/dev/dom/media/buffer-flusher.js +55 -0
  199. package/dist/dev/dom/media/buffer-flusher.js.map +1 -0
  200. package/dist/dev/dom/media/mediasource-setup.js +154 -0
  201. package/dist/dev/dom/media/mediasource-setup.js.map +1 -0
  202. package/dist/dev/dom/media/source-buffer-actor.d.ts +56 -0
  203. package/dist/dev/dom/media/source-buffer-actor.d.ts.map +1 -0
  204. package/dist/dev/dom/media/source-buffer-actor.js +186 -0
  205. package/dist/dev/dom/media/source-buffer-actor.js.map +1 -0
  206. package/dist/dev/dom/network/chunked-stream-iterable.js +49 -0
  207. package/dist/dev/dom/network/chunked-stream-iterable.js.map +1 -0
  208. package/dist/dev/dom/network/fetch.js +56 -0
  209. package/dist/dev/dom/network/fetch.js.map +1 -0
  210. package/dist/dev/dom/playback-engine/adapter.d.ts +41 -0
  211. package/dist/dev/dom/playback-engine/adapter.d.ts.map +1 -0
  212. package/dist/dev/dom/playback-engine/adapter.js +95 -0
  213. package/dist/dev/dom/playback-engine/adapter.js.map +1 -0
  214. package/dist/dev/dom/playback-engine/engine.d.ts +135 -0
  215. package/dist/dev/dom/playback-engine/engine.d.ts.map +1 -0
  216. package/dist/dev/dom/playback-engine/engine.js +176 -0
  217. package/dist/dev/dom/playback-engine/engine.js.map +1 -0
  218. package/dist/dev/dom/playback-engine.d.ts +3 -0
  219. package/dist/dev/dom/playback-engine.js +4 -0
  220. package/dist/dev/dom/text/parse-vtt-segment.d.ts +12 -0
  221. package/dist/dev/dom/text/parse-vtt-segment.d.ts.map +1 -0
  222. package/dist/dev/dom/text/parse-vtt-segment.js +56 -0
  223. package/dist/dev/dom/text/parse-vtt-segment.js.map +1 -0
  224. package/dist/dev/dom.d.ts +10 -0
  225. package/dist/dev/dom.js +11 -0
  226. package/dist/dev/index.d.ts.map +1 -0
  227. package/dist/dev/index.js +14 -0
  228. package/dist/dev/index.js.map +1 -0
  229. package/package.json +22 -22
  230. package/dist/adapter-CMw-rvbk.js +0 -3512
  231. package/dist/adapter-CMw-rvbk.js.map +0 -1
  232. package/dist/adapter-l8iJN_GI.d.ts +0 -689
  233. package/dist/adapter-l8iJN_GI.d.ts.map +0 -1
  234. package/dist/dom/index.d.ts +0 -243
  235. package/dist/dom/index.d.ts.map +0 -1
  236. package/dist/dom/index.js.map +0 -1
  237. package/dist/dom/playback-engine/index.d.ts +0 -2
  238. package/dist/dom/playback-engine/index.js +0 -4
  239. package/dist/index.d.ts.map +0 -1
  240. package/dist/index.js +0 -3
  241. package/dist/src-DRsS_H80.js.map +0 -1
  242. /package/dist/{index.d.ts → dev/index.d.ts} +0 -0
@@ -0,0 +1,31 @@
1
+ //#region src/core/actor.d.ts
2
+ /**
3
+ * Generic actor types.
4
+ *
5
+ * An actor owns its snapshot (finite status + non-finite context) and
6
+ * notifies observers when it changes. Mirrors the XState snapshot model:
7
+ * `snapshot.status` is the bounded operational mode, `snapshot.context`
8
+ * holds arbitrary non-finite data.
9
+ */
10
+ /** Complete actor snapshot: finite status + non-finite context. */
11
+ interface ActorSnapshot<Status extends string, Context> {
12
+ status: Status;
13
+ context: Context;
14
+ }
15
+ /** Generic actor interface: owns its snapshot and notifies observers. */
16
+ interface Actor<Status extends string, Context> {
17
+ /** Current snapshot. */
18
+ readonly snapshot: ActorSnapshot<Status, Context>;
19
+ /**
20
+ * Subscribe to snapshot changes. Fires immediately with the current
21
+ * snapshot, then on every subsequent change.
22
+ *
23
+ * @returns Unsubscribe function.
24
+ */
25
+ subscribe(listener: (snapshot: ActorSnapshot<Status, Context>) => void): () => void;
26
+ /** Tear down the actor. */
27
+ destroy(): void;
28
+ }
29
+ //#endregion
30
+ export { Actor };
31
+ //# sourceMappingURL=actor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor.d.ts","names":[],"sources":["../../../src/core/actor.ts"],"mappings":";;AAUA;;;;;;;;UAAiB,aAAA;EACf,MAAA,EAAQ,MAAA;EACR,OAAA,EAAS,OAAA;AAAA;AAIX;AAAA,UAAiB,KAAA;EAAK;EAAA,SAEX,QAAA,EAAU,aAAA,CAAc,MAAA,EAAQ,OAAA;EAAA;;;;;;EAQzC,SAAA,CAAU,QAAA,GAAW,QAAA,EAAU,aAAA,CAAc,MAAA,EAAQ,OAAA;EAVhC;EAarB,OAAA;AAAA"}
@@ -0,0 +1,46 @@
1
+ //#region src/core/buffer/back-buffer.ts
2
+ /**
3
+ * Default back buffer configuration.
4
+ */
5
+ const DEFAULT_BACK_BUFFER_CONFIG = { keepSegments: 2 };
6
+ /**
7
+ * Calculate back buffer flush point.
8
+ *
9
+ * Determines where to flush old segments from the back buffer.
10
+ * Keeps a fixed number of segments behind the current playback position.
11
+ *
12
+ * Algorithm:
13
+ * 1. Find segments before currentTime
14
+ * 2. Count back N segments (keepSegments)
15
+ * 3. Return startTime of segment N+1 back (flush everything before this)
16
+ *
17
+ * @param segments - Available segments (should be sorted by startTime)
18
+ * @param currentTime - Current playback position in seconds
19
+ * @param config - Optional back buffer configuration
20
+ * @returns Time in seconds to flush up to (flush range: [0, flushEnd))
21
+ *
22
+ * @example
23
+ * const segments = [
24
+ * { startTime: 0, duration: 6, ... },
25
+ * { startTime: 6, duration: 6, ... },
26
+ * { startTime: 12, duration: 6, ... },
27
+ * { startTime: 18, duration: 6, ... },
28
+ * ];
29
+ *
30
+ * // Playing at 18s, keep 2 segments
31
+ * const flushEnd = calculateBackBufferFlushPoint(segments, 18);
32
+ * // Returns 6 (flush [0, 6), keep [6-18))
33
+ */
34
+ function calculateBackBufferFlushPoint(segments, currentTime, config = DEFAULT_BACK_BUFFER_CONFIG) {
35
+ if (segments.length === 0) return 0;
36
+ const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);
37
+ if (segmentsBefore.length === 0) return 0;
38
+ const segmentsToFlush = segmentsBefore.length - config.keepSegments;
39
+ if (segmentsToFlush <= 0) return 0;
40
+ if (segmentsToFlush >= segmentsBefore.length) return currentTime;
41
+ return segmentsBefore[segmentsToFlush].startTime;
42
+ }
43
+
44
+ //#endregion
45
+ export { DEFAULT_BACK_BUFFER_CONFIG, calculateBackBufferFlushPoint };
46
+ //# sourceMappingURL=back-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"back-buffer.js","names":[],"sources":["../../../../src/core/buffer/back-buffer.ts"],"sourcesContent":["/**\n * Back Buffer Strategy (Simple)\n *\n * Calculates flush points for back buffer management.\n * V1 uses simple \"keep N segments\" strategy.\n */\n\nimport type { Segment } from '../types';\n\n/**\n * Back buffer configuration.\n */\nexport interface BackBufferConfig {\n /**\n * Number of segments to keep behind current playback position.\n * Default: 2 segments.\n */\n keepSegments: number;\n}\n\n/**\n * Default back buffer configuration.\n */\nexport const DEFAULT_BACK_BUFFER_CONFIG: BackBufferConfig = {\n keepSegments: 2,\n};\n\n/**\n * Calculate back buffer flush point.\n *\n * Determines where to flush old segments from the back buffer.\n * Keeps a fixed number of segments behind the current playback position.\n *\n * Algorithm:\n * 1. Find segments before currentTime\n * 2. Count back N segments (keepSegments)\n * 3. Return startTime of segment N+1 back (flush everything before this)\n *\n * @param segments - Available segments (should be sorted by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional back buffer configuration\n * @returns Time in seconds to flush up to (flush range: [0, flushEnd))\n *\n * @example\n * const segments = [\n * { startTime: 0, duration: 6, ... },\n * { startTime: 6, duration: 6, ... },\n * { startTime: 12, duration: 6, ... },\n * { startTime: 18, duration: 6, ... },\n * ];\n *\n * // Playing at 18s, keep 2 segments\n * const flushEnd = calculateBackBufferFlushPoint(segments, 18);\n * // Returns 6 (flush [0, 6), keep [6-18))\n */\nexport function calculateBackBufferFlushPoint(\n segments: Segment[],\n currentTime: number,\n config: BackBufferConfig = DEFAULT_BACK_BUFFER_CONFIG\n): number {\n if (segments.length === 0) {\n return 0;\n }\n\n // Find all segments before current time (not including current segment)\n const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);\n\n // If no segments before current time, nothing to flush\n if (segmentsBefore.length === 0) {\n return 0;\n }\n\n // Calculate how many segments to flush\n // Keep last N segments, flush the rest\n const segmentsToFlush = segmentsBefore.length - config.keepSegments;\n\n // If we don't have enough segments to flush, keep everything\n if (segmentsToFlush <= 0) {\n return 0;\n }\n\n // If we want to flush all segments, return currentTime\n // (flush everything before current playback position)\n if (segmentsToFlush >= segmentsBefore.length) {\n return currentTime;\n }\n\n // Return the startTime of the first segment we want to keep\n // Everything before this will be flushed\n return segmentsBefore[segmentsToFlush]!.startTime;\n}\n"],"mappings":";;;;AAuBA,MAAa,6BAA+C,EAC1D,cAAc,GACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BD,SAAgB,8BACd,UACA,aACA,SAA2B,4BACnB;AACR,KAAI,SAAS,WAAW,EACtB,QAAO;CAIT,MAAM,iBAAiB,SAAS,QAAQ,QAAQ,IAAI,YAAY,YAAY;AAG5E,KAAI,eAAe,WAAW,EAC5B,QAAO;CAKT,MAAM,kBAAkB,eAAe,SAAS,OAAO;AAGvD,KAAI,mBAAmB,EACrB,QAAO;AAKT,KAAI,mBAAmB,eAAe,OACpC,QAAO;AAKT,QAAO,eAAe,iBAAkB"}
@@ -0,0 +1,73 @@
1
+ //#region src/core/buffer/forward-buffer.ts
2
+ /**
3
+ * Default forward buffer configuration.
4
+ */
5
+ const DEFAULT_FORWARD_BUFFER_CONFIG = { bufferDuration: 30 };
6
+ /**
7
+ * Get segments that need to be loaded for forward buffer.
8
+ *
9
+ * Determines which segments to load to maintain target buffer duration.
10
+ * Handles discontiguous buffering (gaps after seeks).
11
+ *
12
+ * Algorithm:
13
+ * 1. Calculate target time: currentTime + bufferDuration
14
+ * 2. Find all segments in range [currentTime, targetTime)
15
+ * 3. Filter out segments already buffered at that time position
16
+ * 4. Return segments to load (fills gaps + extends to target)
17
+ *
18
+ * @param segments - All available segments from playlist
19
+ * @param bufferedSegments - Segments already buffered (ordered by startTime)
20
+ * @param currentTime - Current playback position in seconds
21
+ * @param config - Optional forward buffer configuration
22
+ * @returns Array of segments to load (empty if buffer is sufficient)
23
+ *
24
+ * @example
25
+ * // After seek: buffered [0-12, 18-30], playing at 7s
26
+ * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });
27
+ * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)
28
+ */
29
+ /**
30
+ * Calculate the start time from which to flush forward buffer content.
31
+ *
32
+ * Content that starts at or beyond `currentTime + bufferDuration` is no
33
+ * longer needed for the current playback position and should be removed
34
+ * from the SourceBuffer. This prevents unbounded accumulation of scattered
35
+ * SourceBuffer content after seeks, which can cause QuotaExceededError on
36
+ * long-form content.
37
+ *
38
+ * Returns `Infinity` when nothing needs flushing (no buffered segments
39
+ * exist beyond the threshold).
40
+ *
41
+ * @param bufferedSegments - Segments currently tracked in the buffer model
42
+ * @param currentTime - Current playback position in seconds
43
+ * @param config - Optional forward buffer configuration
44
+ * @returns Start time to flush from (flush range: [flushStart, Infinity)),
45
+ * or Infinity if no flush is needed
46
+ *
47
+ * @example
48
+ * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30
49
+ * const flushStart = calculateForwardFlushPoint(segments, 0);
50
+ * // Returns 30 — flush [30, Infinity), keep [0, 30)
51
+ */
52
+ function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
53
+ if (bufferedSegments.length === 0) return Infinity;
54
+ const threshold = currentTime + config.bufferDuration;
55
+ const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);
56
+ if (beyond.length === 0) return Infinity;
57
+ return Math.min(...beyond.map((seg) => seg.startTime));
58
+ }
59
+ function getSegmentsToLoad(segments, bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
60
+ if (segments.length === 0) return [];
61
+ const targetTime = currentTime + config.bufferDuration;
62
+ const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));
63
+ return segments.filter((seg) => {
64
+ const segmentEnd = seg.startTime + seg.duration;
65
+ const isInRange = seg.startTime < targetTime && segmentEnd > currentTime;
66
+ const isNotBuffered = !bufferedStartTimes.has(seg.startTime);
67
+ return isInRange && isNotBuffered;
68
+ });
69
+ }
70
+
71
+ //#endregion
72
+ export { DEFAULT_FORWARD_BUFFER_CONFIG, calculateForwardFlushPoint, getSegmentsToLoad };
73
+ //# sourceMappingURL=forward-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-buffer.js","names":[],"sources":["../../../../src/core/buffer/forward-buffer.ts"],"sourcesContent":["/**\n * Forward Buffer Strategy (Simple)\n *\n * Determines which segments to load for forward buffer management.\n * V1 uses simple fixed-duration strategy (buffer N seconds ahead).\n */\n\nimport type { Segment } from '../types';\n\n/**\n * Forward buffer configuration.\n */\nexport interface ForwardBufferConfig {\n /**\n * Duration in seconds to buffer ahead of current playback position.\n * Default: 30 seconds.\n */\n bufferDuration: number;\n}\n\n/**\n * Default forward buffer configuration.\n */\nexport const DEFAULT_FORWARD_BUFFER_CONFIG: ForwardBufferConfig = {\n bufferDuration: 30,\n};\n\n/**\n * Get segments that need to be loaded for forward buffer.\n *\n * Determines which segments to load to maintain target buffer duration.\n * Handles discontiguous buffering (gaps after seeks).\n *\n * Algorithm:\n * 1. Calculate target time: currentTime + bufferDuration\n * 2. Find all segments in range [currentTime, targetTime)\n * 3. Filter out segments already buffered at that time position\n * 4. Return segments to load (fills gaps + extends to target)\n *\n * @param segments - All available segments from playlist\n * @param bufferedSegments - Segments already buffered (ordered by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Array of segments to load (empty if buffer is sufficient)\n *\n * @example\n * // After seek: buffered [0-12, 18-30], playing at 7s\n * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });\n * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)\n */\n/**\n * Calculate the start time from which to flush forward buffer content.\n *\n * Content that starts at or beyond `currentTime + bufferDuration` is no\n * longer needed for the current playback position and should be removed\n * from the SourceBuffer. This prevents unbounded accumulation of scattered\n * SourceBuffer content after seeks, which can cause QuotaExceededError on\n * long-form content.\n *\n * Returns `Infinity` when nothing needs flushing (no buffered segments\n * exist beyond the threshold).\n *\n * @param bufferedSegments - Segments currently tracked in the buffer model\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Start time to flush from (flush range: [flushStart, Infinity)),\n * or Infinity if no flush is needed\n *\n * @example\n * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30\n * const flushStart = calculateForwardFlushPoint(segments, 0);\n * // Returns 30 — flush [30, Infinity), keep [0, 30)\n */\nexport function calculateForwardFlushPoint(\n bufferedSegments: readonly Segment[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): number {\n if (bufferedSegments.length === 0) return Infinity;\n\n const threshold = currentTime + config.bufferDuration;\n\n // Find segments that start at or beyond the threshold\n const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);\n\n if (beyond.length === 0) return Infinity;\n\n // Flush from the earliest such segment onward\n return Math.min(...beyond.map((seg) => seg.startTime));\n}\n\nexport function getSegmentsToLoad(\n segments: readonly Segment[],\n bufferedSegments: readonly Segment[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): Segment[] {\n if (segments.length === 0) {\n return [];\n }\n\n // Calculate target buffer end time\n const targetTime = currentTime + config.bufferDuration;\n\n // Create set of buffered segment start times for fast lookup\n // V1 simple: if ANY segment is buffered at a given time, don't load for that time\n // V2 (future): would compare by startTime + bitrate/track for quality switching\n const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));\n\n // Find segments to load:\n // - Overlaps buffer window [currentTime, targetTime)\n // - Not already buffered at that time position\n const toLoad = segments.filter((seg) => {\n // Segment must overlap the buffer window\n const segmentEnd = seg.startTime + seg.duration;\n const isInRange = seg.startTime < targetTime && segmentEnd > currentTime;\n\n // Must not have a segment buffered at this time position\n const isNotBuffered = !bufferedStartTimes.has(seg.startTime);\n\n return isInRange && isNotBuffered;\n });\n\n return toLoad;\n}\n"],"mappings":";;;;AAuBA,MAAa,gCAAqD,EAChE,gBAAgB,IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDD,SAAgB,2BACd,kBACA,aACA,SAA8B,+BACtB;AACR,KAAI,iBAAiB,WAAW,EAAG,QAAO;CAE1C,MAAM,YAAY,cAAc,OAAO;CAGvC,MAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,aAAa,UAAU;AAE3E,KAAI,OAAO,WAAW,EAAG,QAAO;AAGhC,QAAO,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,IAAI,UAAU,CAAC;;AAGxD,SAAgB,kBACd,UACA,kBACA,aACA,SAA8B,+BACnB;AACX,KAAI,SAAS,WAAW,EACtB,QAAO,EAAE;CAIX,MAAM,aAAa,cAAc,OAAO;CAKxC,MAAM,qBAAqB,IAAI,IAAI,iBAAiB,KAAK,QAAQ,IAAI,UAAU,CAAC;AAgBhF,QAXe,SAAS,QAAQ,QAAQ;EAEtC,MAAM,aAAa,IAAI,YAAY,IAAI;EACvC,MAAM,YAAY,IAAI,YAAY,cAAc,aAAa;EAG7D,MAAM,gBAAgB,CAAC,mBAAmB,IAAI,IAAI,UAAU;AAE5D,SAAO,aAAa;GACpB"}
@@ -0,0 +1,48 @@
1
+ //#region src/core/events/create-event-stream.d.ts
2
+ /**
3
+ * Minimal event stream with Observable-like shape.
4
+ *
5
+ * Simple Subject/Observable-like implementation for dispatching discrete events.
6
+ * Events are dispatched synchronously to all subscribers.
7
+ */
8
+ declare const EVENT_STREAM_SYMBOL: unique symbol;
9
+ /**
10
+ * Event listener function.
11
+ */
12
+ type EventListener<T extends Pick<Event, 'type'>> = (event: T) => void;
13
+ /**
14
+ * Event stream interface.
15
+ */
16
+ interface EventStream<T extends Pick<Event, 'type'> = {
17
+ type: string;
18
+ }> {
19
+ [EVENT_STREAM_SYMBOL]: true;
20
+ dispatch(event: T): void;
21
+ subscribe(listener: EventListener<T>): () => void;
22
+ }
23
+ /**
24
+ * Creates a minimal event stream for dispatching discrete events.
25
+ *
26
+ * Events are dispatched synchronously to all subscribers.
27
+ * Conforms to Observable-like shape for future compatibility.
28
+ *
29
+ * Events must have a 'type' property for discriminated union type narrowing.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * type Action = { type: 'PLAY' } | { type: 'PAUSE' };
34
+ * const events = createEventStream<Action>();
35
+ *
36
+ * events.subscribe((action) => {
37
+ * if (action.type === 'PLAY') {
38
+ * // Type narrowed to { type: 'PLAY' }
39
+ * }
40
+ * });
41
+ *
42
+ * events.dispatch({ type: 'PLAY' });
43
+ * ```
44
+ */
45
+ declare function createEventStream<T extends Pick<Event, 'type'>>(): EventStream<T>;
46
+ //#endregion
47
+ export { EventStream, createEventStream };
48
+ //# sourceMappingURL=create-event-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-event-stream.d.ts","names":[],"sources":["../../../../src/core/events/create-event-stream.ts"],"mappings":";;;;;;;cAOM,mBAAA;;;;KAKM,aAAA,WAAwB,IAAA,CAAK,KAAA,cAAmB,KAAA,EAAO,CAAA;;;;UAKlD,WAAA,WAAsB,IAAA,CAAK,KAAA;EAAmB,IAAA;AAAA;EAAA,CAC5D,mBAAA;EACD,QAAA,CAAS,KAAA,EAAO,CAAA;EAChB,SAAA,CAAU,QAAA,EAAU,aAAA,CAAc,CAAA;AAAA;AAHpC;;;;;;;;;;;;;;;;;;;;;;AAAA,iBA4BgB,iBAAA,WAA4B,IAAA,CAAK,KAAA,UAAA,CAAA,GAAmB,WAAA,CAAY,CAAA"}
@@ -0,0 +1,48 @@
1
+ //#region src/core/events/create-event-stream.ts
2
+ /**
3
+ * Minimal event stream with Observable-like shape.
4
+ *
5
+ * Simple Subject/Observable-like implementation for dispatching discrete events.
6
+ * Events are dispatched synchronously to all subscribers.
7
+ */
8
+ const EVENT_STREAM_SYMBOL = Symbol("@videojs/event-stream");
9
+ /**
10
+ * Creates a minimal event stream for dispatching discrete events.
11
+ *
12
+ * Events are dispatched synchronously to all subscribers.
13
+ * Conforms to Observable-like shape for future compatibility.
14
+ *
15
+ * Events must have a 'type' property for discriminated union type narrowing.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * type Action = { type: 'PLAY' } | { type: 'PAUSE' };
20
+ * const events = createEventStream<Action>();
21
+ *
22
+ * events.subscribe((action) => {
23
+ * if (action.type === 'PLAY') {
24
+ * // Type narrowed to { type: 'PLAY' }
25
+ * }
26
+ * });
27
+ *
28
+ * events.dispatch({ type: 'PLAY' });
29
+ * ```
30
+ */
31
+ function createEventStream() {
32
+ const subscribers = /* @__PURE__ */ new Set();
33
+ return {
34
+ [EVENT_STREAM_SYMBOL]: true,
35
+ dispatch(event) {
36
+ const current = Array.from(subscribers);
37
+ for (const listener of current) listener(event);
38
+ },
39
+ subscribe(listener) {
40
+ subscribers.add(listener);
41
+ return () => subscribers.delete(listener);
42
+ }
43
+ };
44
+ }
45
+
46
+ //#endregion
47
+ export { createEventStream };
48
+ //# sourceMappingURL=create-event-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-event-stream.js","names":[],"sources":["../../../../src/core/events/create-event-stream.ts"],"sourcesContent":["/**\n * Minimal event stream with Observable-like shape.\n *\n * Simple Subject/Observable-like implementation for dispatching discrete events.\n * Events are dispatched synchronously to all subscribers.\n */\n\nconst EVENT_STREAM_SYMBOL = Symbol('@videojs/event-stream');\n\n/**\n * Event listener function.\n */\nexport type EventListener<T extends Pick<Event, 'type'>> = (event: T) => void;\n\n/**\n * Event stream interface.\n */\nexport interface EventStream<T extends Pick<Event, 'type'> = { type: string }> {\n [EVENT_STREAM_SYMBOL]: true;\n dispatch(event: T): void;\n subscribe(listener: EventListener<T>): () => void;\n}\n\n/**\n * Creates a minimal event stream for dispatching discrete events.\n *\n * Events are dispatched synchronously to all subscribers.\n * Conforms to Observable-like shape for future compatibility.\n *\n * Events must have a 'type' property for discriminated union type narrowing.\n *\n * @example\n * ```ts\n * type Action = { type: 'PLAY' } | { type: 'PAUSE' };\n * const events = createEventStream<Action>();\n *\n * events.subscribe((action) => {\n * if (action.type === 'PLAY') {\n * // Type narrowed to { type: 'PLAY' }\n * }\n * });\n *\n * events.dispatch({ type: 'PLAY' });\n * ```\n */\nexport function createEventStream<T extends Pick<Event, 'type'>>(): EventStream<T> {\n const subscribers = new Set<EventListener<T>>();\n\n return {\n [EVENT_STREAM_SYMBOL]: true,\n\n dispatch(event: T): void {\n // Snapshot subscribers to avoid notifying those added during dispatch\n const current = Array.from(subscribers);\n for (const listener of current) {\n listener(event);\n }\n },\n\n subscribe(listener: EventListener<T>): () => void {\n subscribers.add(listener);\n return () => subscribers.delete(listener);\n },\n };\n}\n\n/**\n * Type guard to check if value is an EventStream.\n */\nexport function isEventStream(value: unknown): value is EventStream {\n return typeof value === 'object' && value !== null && EVENT_STREAM_SYMBOL in value;\n}\n"],"mappings":";;;;;;;AAOA,MAAM,sBAAsB,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;AAsC3D,SAAgB,oBAAmE;CACjF,MAAM,8BAAc,IAAI,KAAuB;AAE/C,QAAO;GACJ,sBAAsB;EAEvB,SAAS,OAAgB;GAEvB,MAAM,UAAU,MAAM,KAAK,YAAY;AACvC,QAAK,MAAM,YAAY,QACrB,UAAS,MAAM;;EAInB,UAAU,UAAwC;AAChD,eAAY,IAAI,SAAS;AACzB,gBAAa,YAAY,OAAO,SAAS;;EAE5C"}
@@ -0,0 +1,51 @@
1
+ import { isResolvedTrack } from "../types/index.js";
2
+ import { combineLatest } from "../reactive/combine-latest.js";
3
+ import { getSelectedTrack } from "../utils/track-selection.js";
4
+
5
+ //#region src/core/features/calculate-presentation-duration.ts
6
+ /**
7
+ * Check if we can calculate presentation duration (have required data).
8
+ */
9
+ function canCalculateDuration(state) {
10
+ if (!state.presentation) return false;
11
+ return !!(state.selectedVideoTrackId || state.selectedAudioTrackId);
12
+ }
13
+ /**
14
+ * Check if we should calculate presentation duration (conditions met).
15
+ */
16
+ function shouldCalculateDuration(state) {
17
+ if (!canCalculateDuration(state)) return false;
18
+ const { presentation } = state;
19
+ if (presentation.duration !== void 0) return false;
20
+ const videoTrack = state.selectedVideoTrackId ? getSelectedTrack(state, "video") : void 0;
21
+ const audioTrack = state.selectedAudioTrackId ? getSelectedTrack(state, "audio") : void 0;
22
+ return !!(videoTrack && isResolvedTrack(videoTrack) || audioTrack && isResolvedTrack(audioTrack));
23
+ }
24
+ /**
25
+ * Get duration from the first resolved track (prefer video, fallback to audio).
26
+ */
27
+ function getDurationFromResolvedTracks(state) {
28
+ const videoTrack = state.selectedVideoTrackId ? getSelectedTrack(state, "video") : void 0;
29
+ if (videoTrack && isResolvedTrack(videoTrack)) return videoTrack.duration;
30
+ const audioTrack = state.selectedAudioTrackId ? getSelectedTrack(state, "audio") : void 0;
31
+ if (audioTrack && isResolvedTrack(audioTrack)) return audioTrack.duration;
32
+ }
33
+ /**
34
+ * Calculate and set presentation duration from resolved tracks.
35
+ */
36
+ function calculatePresentationDuration({ state }) {
37
+ return combineLatest([state]).subscribe(([currentState]) => {
38
+ if (!shouldCalculateDuration(currentState)) return;
39
+ const duration = getDurationFromResolvedTracks(currentState);
40
+ if (duration === void 0 || !Number.isFinite(duration)) return;
41
+ const { presentation } = currentState;
42
+ state.patch({ presentation: {
43
+ ...presentation,
44
+ duration
45
+ } });
46
+ });
47
+ }
48
+
49
+ //#endregion
50
+ export { calculatePresentationDuration };
51
+ //# sourceMappingURL=calculate-presentation-duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculate-presentation-duration.js","names":[],"sources":["../../../../src/core/features/calculate-presentation-duration.ts"],"sourcesContent":["import { combineLatest } from '../reactive/combine-latest';\nimport type { WritableState } from '../state/create-state';\nimport type { AudioTrack, Presentation, VideoTrack } from '../types';\nimport { isResolvedTrack } from '../types';\nimport { getSelectedTrack } from '../utils/track-selection';\n\nexport interface PresentationDurationState {\n presentation?: Presentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * Check if we can calculate presentation duration (have required data).\n */\nexport function canCalculateDuration(state: PresentationDurationState): boolean {\n if (!state.presentation) return false;\n // Need at least one selected track\n return !!(state.selectedVideoTrackId || state.selectedAudioTrackId);\n}\n\n/**\n * Check if we should calculate presentation duration (conditions met).\n */\nexport function shouldCalculateDuration(state: PresentationDurationState): boolean {\n if (!canCalculateDuration(state)) return false;\n\n const { presentation } = state;\n\n // Don't recalculate if already set\n if (presentation!.duration !== undefined) return false;\n\n // Check if any selected track is resolved\n const videoTrack = state.selectedVideoTrackId ? getSelectedTrack(state, 'video') : undefined;\n const audioTrack = state.selectedAudioTrackId ? getSelectedTrack(state, 'audio') : undefined;\n\n // At least one track must be resolved (has segments and duration)\n return !!((videoTrack && isResolvedTrack(videoTrack)) || (audioTrack && isResolvedTrack(audioTrack)));\n}\n\n/**\n * Get duration from the first resolved track (prefer video, fallback to audio).\n */\nexport function getDurationFromResolvedTracks(state: PresentationDurationState): number | undefined {\n // Try video track first\n const videoTrack = state.selectedVideoTrackId\n ? (getSelectedTrack(state, 'video') as VideoTrack | undefined)\n : undefined;\n if (videoTrack && isResolvedTrack(videoTrack)) {\n return videoTrack.duration;\n }\n\n // Fallback to audio track\n const audioTrack = state.selectedAudioTrackId\n ? (getSelectedTrack(state, 'audio') as AudioTrack | undefined)\n : undefined;\n if (audioTrack && isResolvedTrack(audioTrack)) {\n return audioTrack.duration;\n }\n\n return undefined;\n}\n\n/**\n * Calculate and set presentation duration from resolved tracks.\n */\nexport function calculatePresentationDuration({\n state,\n}: {\n state: WritableState<PresentationDurationState>;\n}): () => void {\n return combineLatest([state]).subscribe(([currentState]: [PresentationDurationState]) => {\n if (!shouldCalculateDuration(currentState)) return;\n\n const duration = getDurationFromResolvedTracks(currentState);\n if (duration === undefined || !Number.isFinite(duration)) return;\n\n const { presentation } = currentState;\n\n // Patch presentation with duration\n state.patch({\n presentation: {\n ...presentation!,\n duration,\n },\n });\n });\n}\n"],"mappings":";;;;;;;;AAeA,SAAgB,qBAAqB,OAA2C;AAC9E,KAAI,CAAC,MAAM,aAAc,QAAO;AAEhC,QAAO,CAAC,EAAE,MAAM,wBAAwB,MAAM;;;;;AAMhD,SAAgB,wBAAwB,OAA2C;AACjF,KAAI,CAAC,qBAAqB,MAAM,CAAE,QAAO;CAEzC,MAAM,EAAE,iBAAiB;AAGzB,KAAI,aAAc,aAAa,OAAW,QAAO;CAGjD,MAAM,aAAa,MAAM,uBAAuB,iBAAiB,OAAO,QAAQ,GAAG;CACnF,MAAM,aAAa,MAAM,uBAAuB,iBAAiB,OAAO,QAAQ,GAAG;AAGnF,QAAO,CAAC,EAAG,cAAc,gBAAgB,WAAW,IAAM,cAAc,gBAAgB,WAAW;;;;;AAMrG,SAAgB,8BAA8B,OAAsD;CAElG,MAAM,aAAa,MAAM,uBACpB,iBAAiB,OAAO,QAAQ,GACjC;AACJ,KAAI,cAAc,gBAAgB,WAAW,CAC3C,QAAO,WAAW;CAIpB,MAAM,aAAa,MAAM,uBACpB,iBAAiB,OAAO,QAAQ,GACjC;AACJ,KAAI,cAAc,gBAAgB,WAAW,CAC3C,QAAO,WAAW;;;;;AAStB,SAAgB,8BAA8B,EAC5C,SAGa;AACb,QAAO,cAAc,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,kBAA+C;AACvF,MAAI,CAAC,wBAAwB,aAAa,CAAE;EAE5C,MAAM,WAAW,8BAA8B,aAAa;AAC5D,MAAI,aAAa,UAAa,CAAC,OAAO,SAAS,SAAS,CAAE;EAE1D,MAAM,EAAE,iBAAiB;AAGzB,QAAM,MAAM,EACV,cAAc;GACZ,GAAG;GACH;GACD,EACF,CAAC;GACF"}
@@ -0,0 +1,68 @@
1
+ import { getBandwidthEstimate } from "../abr/bandwidth-estimator.js";
2
+ import { selectQuality } from "../abr/quality-selection.js";
3
+
4
+ //#region src/core/features/quality-switching.ts
5
+ /**
6
+ * Default quality switching configuration.
7
+ */
8
+ const DEFAULT_SWITCHING_CONFIG = {
9
+ safetyMargin: .85,
10
+ minUpgradeInterval: 8e3,
11
+ defaultBandwidth: 5e6
12
+ };
13
+ /**
14
+ * Get all video tracks from a presentation's first switching set.
15
+ * Returns [] when the presentation is still unresolved (no selectionSets yet).
16
+ */
17
+ function getVideoTracks(presentation) {
18
+ return (presentation.selectionSets?.find((s) => s.type === "video"))?.switchingSets[0]?.tracks ?? [];
19
+ }
20
+ /**
21
+ * Quality switching orchestration (F9).
22
+ *
23
+ * Reacts to bandwidth estimate changes and updates `selectedVideoTrackId`
24
+ * when a different quality is optimal:
25
+ *
26
+ * - **Downgrades** happen immediately to avoid buffering stalls.
27
+ * - **Upgrades** are gated by `minUpgradeInterval` to prevent oscillation.
28
+ * - The first switch (from any track, or no track) is always immediate.
29
+ *
30
+ * Smooth switching is handled downstream: when `selectedVideoTrackId` changes,
31
+ * `resolveTrack` fetches the new playlist and `loadSegments` reloads the init
32
+ * segment, then appends media segments from the current position in the new
33
+ * quality. The browser's SourceBuffer replaces the overlapping buffered range.
34
+ *
35
+ * @example
36
+ * const cleanup = switchQuality({ state });
37
+ * // Later, when done:
38
+ * cleanup();
39
+ */
40
+ function switchQuality({ state }, config = {}) {
41
+ const safetyMargin = config.safetyMargin ?? DEFAULT_SWITCHING_CONFIG.safetyMargin;
42
+ const minUpgradeInterval = config.minUpgradeInterval ?? DEFAULT_SWITCHING_CONFIG.minUpgradeInterval;
43
+ const defaultBandwidth = config.defaultBandwidth ?? DEFAULT_SWITCHING_CONFIG.defaultBandwidth;
44
+ let lastUpgradeTime = Date.now();
45
+ let firstMeaningfulFire = true;
46
+ return state.subscribe((currentState) => {
47
+ const { presentation, bandwidthState, selectedVideoTrackId, abrDisabled } = currentState;
48
+ if (abrDisabled === true) return;
49
+ if (!presentation || !bandwidthState) return;
50
+ const videoTracks = getVideoTracks(presentation);
51
+ if (videoTracks.length === 0) return;
52
+ const isFirst = firstMeaningfulFire;
53
+ firstMeaningfulFire = false;
54
+ const optimal = selectQuality(videoTracks, getBandwidthEstimate(bandwidthState, defaultBandwidth), { safetyMargin });
55
+ if (!optimal || optimal.id === selectedVideoTrackId) return;
56
+ const currentTrack = videoTracks.find((t) => t.id === selectedVideoTrackId);
57
+ if (!currentTrack || optimal.bandwidth > currentTrack.bandwidth) {
58
+ const now = Date.now();
59
+ if (!isFirst && now - lastUpgradeTime < minUpgradeInterval) return;
60
+ lastUpgradeTime = now;
61
+ }
62
+ state.patch({ selectedVideoTrackId: optimal.id });
63
+ });
64
+ }
65
+
66
+ //#endregion
67
+ export { DEFAULT_SWITCHING_CONFIG, switchQuality };
68
+ //# sourceMappingURL=quality-switching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-switching.js","names":[],"sources":["../../../../src/core/features/quality-switching.ts"],"sourcesContent":["import type { BandwidthState } from '../abr/bandwidth-estimator';\nimport { getBandwidthEstimate } from '../abr/bandwidth-estimator';\nimport { selectQuality } from '../abr/quality-selection';\nimport type { WritableState } from '../state/create-state';\nimport type { Presentation, VideoSelectionSet } from '../types';\n\n/**\n * State shape for quality switching.\n */\nexport interface QualitySwitchingState {\n presentation?: Presentation;\n bandwidthState?: BandwidthState;\n selectedVideoTrackId?: string;\n // TODO: abrDisabled is a blunt instrument — it requires callers to know they're\n // competing with ABR and explicitly opt out. A better long-term design would\n // separate ABR selection (abrVideoTrackId) from manual selection (manualVideoTrackId)\n // and derive the effective selectedVideoTrackId as manualVideoTrackId ?? abrVideoTrackId.\n // That way the two concerns never write to the same field and consumers are unchanged.\n /** When true, ABR quality switching is suppressed. Use for manual quality selection. */\n abrDisabled?: boolean;\n}\n\n/**\n * Configuration for quality switching behavior.\n */\nexport interface QualitySwitchingConfig {\n /**\n * Safety margin for quality selection (0–1).\n * Track is selected only when bandwidth >= track.bandwidth / safetyMargin.\n * Default: 0.85 (15% headroom).\n */\n safetyMargin?: number;\n\n /**\n * Minimum milliseconds between upgrades.\n * Prevents oscillation when bandwidth fluctuates around a quality threshold.\n * Downgrades are always immediate regardless of this setting.\n * Default: 8000 (8 seconds).\n */\n minUpgradeInterval?: number;\n\n /**\n * Bandwidth estimate in bps to use before enough samples have been collected.\n * Default: 5_000_000 (5 Mbps).\n */\n defaultBandwidth?: number;\n}\n\n/**\n * Default quality switching configuration.\n */\nexport const DEFAULT_SWITCHING_CONFIG: Required<QualitySwitchingConfig> = {\n safetyMargin: 0.85,\n minUpgradeInterval: 8000,\n defaultBandwidth: 5_000_000,\n};\n\n/**\n * Get all video tracks from a presentation's first switching set.\n * Returns [] when the presentation is still unresolved (no selectionSets yet).\n */\nfunction getVideoTracks(presentation: Presentation) {\n const videoSet = presentation.selectionSets?.find((s) => s.type === 'video') as VideoSelectionSet | undefined;\n return videoSet?.switchingSets[0]?.tracks ?? [];\n}\n\n/**\n * Quality switching orchestration (F9).\n *\n * Reacts to bandwidth estimate changes and updates `selectedVideoTrackId`\n * when a different quality is optimal:\n *\n * - **Downgrades** happen immediately to avoid buffering stalls.\n * - **Upgrades** are gated by `minUpgradeInterval` to prevent oscillation.\n * - The first switch (from any track, or no track) is always immediate.\n *\n * Smooth switching is handled downstream: when `selectedVideoTrackId` changes,\n * `resolveTrack` fetches the new playlist and `loadSegments` reloads the init\n * segment, then appends media segments from the current position in the new\n * quality. The browser's SourceBuffer replaces the overlapping buffered range.\n *\n * @example\n * const cleanup = switchQuality({ state });\n * // Later, when done:\n * cleanup();\n */\nexport function switchQuality(\n { state }: { state: WritableState<QualitySwitchingState> },\n config: QualitySwitchingConfig = {}\n): () => void {\n const safetyMargin = config.safetyMargin ?? DEFAULT_SWITCHING_CONFIG.safetyMargin;\n const minUpgradeInterval = config.minUpgradeInterval ?? DEFAULT_SWITCHING_CONFIG.minUpgradeInterval;\n const defaultBandwidth = config.defaultBandwidth ?? DEFAULT_SWITCHING_CONFIG.defaultBandwidth;\n\n // Initialize to creation time so the interval starts counting immediately.\n // The first time we have enough data to make a meaningful quality decision\n // (presentation resolved + bandwidth available), the upgrade gate is skipped\n // so the initial ABR correction does not wait for minUpgradeInterval.\n let lastUpgradeTime = Date.now();\n let firstMeaningfulFire = true;\n\n return state.subscribe((currentState: QualitySwitchingState) => {\n const { presentation, bandwidthState, selectedVideoTrackId, abrDisabled } = currentState;\n\n if (abrDisabled === true) return;\n if (!presentation || !bandwidthState) return;\n\n const videoTracks = getVideoTracks(presentation);\n if (videoTracks.length === 0) return;\n\n // Consume the first-meaningful-fire flag now that we have all required data.\n const isFirst = firstMeaningfulFire;\n firstMeaningfulFire = false;\n\n const bandwidth = getBandwidthEstimate(bandwidthState, defaultBandwidth);\n const optimal = selectQuality(videoTracks as any, bandwidth, { safetyMargin });\n if (!optimal || optimal.id === selectedVideoTrackId) return;\n\n // Determine whether this is an upgrade or downgrade.\n const currentTrack = videoTracks.find((t) => t.id === selectedVideoTrackId);\n const isUpgrade = !currentTrack || optimal.bandwidth > currentTrack.bandwidth;\n\n if (isUpgrade) {\n const now = Date.now();\n // Gate upgrades with minUpgradeInterval to prevent oscillation.\n // Downgrades are always immediate; the first meaningful evaluation is always allowed.\n if (!isFirst && now - lastUpgradeTime < minUpgradeInterval) return;\n lastUpgradeTime = now;\n }\n\n state.patch({ selectedVideoTrackId: optimal.id });\n });\n}\n"],"mappings":";;;;;;;AAmDA,MAAa,2BAA6D;CACxE,cAAc;CACd,oBAAoB;CACpB,kBAAkB;CACnB;;;;;AAMD,SAAS,eAAe,cAA4B;AAElD,SADiB,aAAa,eAAe,MAAM,MAAM,EAAE,SAAS,QAAQ,GAC3D,cAAc,IAAI,UAAU,EAAE;;;;;;;;;;;;;;;;;;;;;;AAuBjD,SAAgB,cACd,EAAE,SACF,SAAiC,EAAE,EACvB;CACZ,MAAM,eAAe,OAAO,gBAAgB,yBAAyB;CACrE,MAAM,qBAAqB,OAAO,sBAAsB,yBAAyB;CACjF,MAAM,mBAAmB,OAAO,oBAAoB,yBAAyB;CAM7E,IAAI,kBAAkB,KAAK,KAAK;CAChC,IAAI,sBAAsB;AAE1B,QAAO,MAAM,WAAW,iBAAwC;EAC9D,MAAM,EAAE,cAAc,gBAAgB,sBAAsB,gBAAgB;AAE5E,MAAI,gBAAgB,KAAM;AAC1B,MAAI,CAAC,gBAAgB,CAAC,eAAgB;EAEtC,MAAM,cAAc,eAAe,aAAa;AAChD,MAAI,YAAY,WAAW,EAAG;EAG9B,MAAM,UAAU;AAChB,wBAAsB;EAGtB,MAAM,UAAU,cAAc,aADZ,qBAAqB,gBAAgB,iBAAiB,EACX,EAAE,cAAc,CAAC;AAC9E,MAAI,CAAC,WAAW,QAAQ,OAAO,qBAAsB;EAGrD,MAAM,eAAe,YAAY,MAAM,MAAM,EAAE,OAAO,qBAAqB;AAG3E,MAFkB,CAAC,gBAAgB,QAAQ,YAAY,aAAa,WAErD;GACb,MAAM,MAAM,KAAK,KAAK;AAGtB,OAAI,CAAC,WAAW,MAAM,kBAAkB,mBAAoB;AAC5D,qBAAkB;;AAGpB,QAAM,MAAM,EAAE,sBAAsB,QAAQ,IAAI,CAAC;GACjD"}
@@ -0,0 +1,16 @@
1
+ //#region src/core/features/resolve-presentation.d.ts
2
+ /**
3
+ * Action types for presentation resolution.
4
+ * Event names match HTMLMediaElement events (lowercase).
5
+ */
6
+ type PresentationAction = {
7
+ type: 'play';
8
+ } | {
9
+ type: 'pause';
10
+ } | {
11
+ type: 'load';
12
+ url: string;
13
+ };
14
+ //#endregion
15
+ export { PresentationAction };
16
+ //# sourceMappingURL=resolve-presentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-presentation.d.ts","names":[],"sources":["../../../../src/core/features/resolve-presentation.ts"],"mappings":";;;;;KA2FY,kBAAA;EAAuB,IAAA;AAAA;EAAmB,IAAA;AAAA;EAAoB,IAAA;EAAc,GAAA;AAAA"}
@@ -0,0 +1,99 @@
1
+ import { parseMultivariantPlaylist } from "../hls/parse-multivariant.js";
2
+ import { fetchResolvable, getResponseText } from "../../dom/network/fetch.js";
3
+ import { combineLatest } from "../reactive/combine-latest.js";
4
+
5
+ //#region src/core/features/resolve-presentation.ts
6
+ /**
7
+ * Type guard to check if presentation is unresolved.
8
+ */
9
+ function isUnresolved(presentation) {
10
+ return presentation !== void 0 && "url" in presentation && !("id" in presentation);
11
+ }
12
+ function canResolve(state) {
13
+ return isUnresolved(state.presentation);
14
+ }
15
+ /**
16
+ * Determines if resolution conditions are met based on preload policy and event.
17
+ *
18
+ * Resolution conditions:
19
+ * - State-driven: preload is 'auto' or 'metadata'
20
+ * - Event-driven: play event
21
+ *
22
+ * @param state - Current presentation state
23
+ * @param event - Current action/event
24
+ * @returns true if resolution conditions are met
25
+ */
26
+ function shouldResolve(state, event) {
27
+ const { preload } = state;
28
+ return ["auto", "metadata"].includes(preload) || event.type === "play";
29
+ }
30
+ /**
31
+ * Syncs preload attribute from mediaElement to state.
32
+ *
33
+ * Watches the owners state for mediaElement changes and copies the
34
+ * preload attribute to the immutable state.
35
+ *
36
+ * @param state - Immutable state container
37
+ * @param owners - Mutable platform objects container
38
+ * @returns Cleanup function to stop syncing
39
+ */
40
+ function syncPreloadAttribute(state, owners) {
41
+ return owners.subscribe((current) => {
42
+ if (state.current.preload !== void 0) return;
43
+ const preload = current.mediaElement?.preload || void 0;
44
+ state.patch({ preload });
45
+ });
46
+ }
47
+ /**
48
+ * Resolves unresolved presentations using reactive composition.
49
+ *
50
+ * Uses combineLatest to compose state + events, enabling both state-driven
51
+ * and event-driven resolution triggers.
52
+ *
53
+ * Triggers resolution when:
54
+ * - State-driven: Unresolved presentation + preload allows (auto/metadata)
55
+ * - Event-driven: PLAY event when preload="none"
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const state = createState({ presentation: undefined, preload: 'auto' });
60
+ * const events = createEventStream<PresentationAction>();
61
+ *
62
+ * const cleanup = resolvePresentation({ state, events });
63
+ *
64
+ * // State-driven: resolves immediately when preload allows
65
+ * state.patch({ presentation: { url: 'http://example.com/playlist.m3u8' } });
66
+ *
67
+ * // Event-driven: resolves on PLAY when preload="none"
68
+ * state.patch({ preload: 'none', presentation: { url: '...' } });
69
+ * events.dispatch({ type: 'PLAY' });
70
+ * ```
71
+ */
72
+ function resolvePresentation({ state, events }) {
73
+ let resolving = false;
74
+ let abortController = null;
75
+ const cleanup = combineLatest([state, events]).subscribe(async ([currentState, event]) => {
76
+ if (!canResolve(currentState) || !shouldResolve(currentState, event) || resolving) return;
77
+ try {
78
+ resolving = true;
79
+ abortController = new AbortController();
80
+ const { presentation } = currentState;
81
+ const parsed = parseMultivariantPlaylist(await getResponseText(await fetchResolvable(presentation, { signal: abortController.signal })), presentation);
82
+ state.patch({ presentation: parsed });
83
+ } catch (error) {
84
+ if (error instanceof Error && error.name === "AbortError") return;
85
+ throw error;
86
+ } finally {
87
+ resolving = false;
88
+ abortController = null;
89
+ }
90
+ });
91
+ return () => {
92
+ abortController?.abort();
93
+ cleanup();
94
+ };
95
+ }
96
+
97
+ //#endregion
98
+ export { canResolve, isUnresolved, resolvePresentation, shouldResolve, syncPreloadAttribute };
99
+ //# sourceMappingURL=resolve-presentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-presentation.js","names":[],"sources":["../../../../src/core/features/resolve-presentation.ts"],"sourcesContent":["import { fetchResolvable, getResponseText } from '../../dom/network/fetch';\nimport type { EventStream } from '../events/create-event-stream';\nimport { parseMultivariantPlaylist } from '../hls/parse-multivariant';\nimport { combineLatest } from '../reactive/combine-latest';\nimport type { WritableState } from '../state/create-state';\nimport type { AddressableObject, MediaElementLike, Presentation } from '../types';\n\n/**\n * Unresolved presentation - has a URL but no data yet.\n * Identical to AddressableObject per user requirement.\n */\nexport type UnresolvedPresentation = AddressableObject;\n\n/**\n * State shape for presentation resolution.\n */\nexport interface PresentationState {\n presentation?: UnresolvedPresentation | Presentation | undefined;\n preload?: 'auto' | 'metadata' | 'none' | undefined;\n}\n\n/**\n * Mutable platform objects.\n */\nexport interface PlatformOwners {\n mediaElement?: MediaElementLike | undefined;\n}\n\n/**\n * Type guard to check if presentation is unresolved.\n */\nexport function isUnresolved(\n presentation: UnresolvedPresentation | Presentation | undefined\n): presentation is UnresolvedPresentation {\n return presentation !== undefined && 'url' in presentation && !('id' in presentation);\n}\n\nexport function canResolve(\n state: PresentationState\n): state is PresentationState & { presentation: UnresolvedPresentation } {\n return isUnresolved(state.presentation);\n}\n\n/**\n * Determines if resolution conditions are met based on preload policy and event.\n *\n * Resolution conditions:\n * - State-driven: preload is 'auto' or 'metadata'\n * - Event-driven: play event\n *\n * @param state - Current presentation state\n * @param event - Current action/event\n * @returns true if resolution conditions are met\n */\nexport function shouldResolve(state: PresentationState, event: PresentationAction): boolean {\n const { preload } = state;\n return (\n // State-driven: preload allows (auto/metadata)\n ['auto', 'metadata'].includes(preload as any) ||\n // Event-driven: play event\n event.type === 'play'\n );\n}\n\n/**\n * Syncs preload attribute from mediaElement to state.\n *\n * Watches the owners state for mediaElement changes and copies the\n * preload attribute to the immutable state.\n *\n * @param state - Immutable state container\n * @param owners - Mutable platform objects container\n * @returns Cleanup function to stop syncing\n */\nexport function syncPreloadAttribute(\n state: WritableState<PresentationState>,\n owners: WritableState<PlatformOwners>\n): () => void {\n return owners.subscribe((current) => {\n // Only infer preload from the element when no explicit value has been set.\n // An explicit value (set via SpfMedia.preload) always wins.\n if (state.current.preload !== undefined) return;\n const preload = current.mediaElement?.preload || undefined;\n state.patch({ preload: preload as 'auto' | 'metadata' | 'none' | undefined });\n });\n}\n\n/**\n * Action types for presentation resolution.\n * Event names match HTMLMediaElement events (lowercase).\n */\nexport type PresentationAction = { type: 'play' } | { type: 'pause' } | { type: 'load'; url: string };\n\n/**\n * Resolves unresolved presentations using reactive composition.\n *\n * Uses combineLatest to compose state + events, enabling both state-driven\n * and event-driven resolution triggers.\n *\n * Triggers resolution when:\n * - State-driven: Unresolved presentation + preload allows (auto/metadata)\n * - Event-driven: PLAY event when preload=\"none\"\n *\n * @example\n * ```ts\n * const state = createState({ presentation: undefined, preload: 'auto' });\n * const events = createEventStream<PresentationAction>();\n *\n * const cleanup = resolvePresentation({ state, events });\n *\n * // State-driven: resolves immediately when preload allows\n * state.patch({ presentation: { url: 'http://example.com/playlist.m3u8' } });\n *\n * // Event-driven: resolves on PLAY when preload=\"none\"\n * state.patch({ preload: 'none', presentation: { url: '...' } });\n * events.dispatch({ type: 'PLAY' });\n * ```\n */\nexport function resolvePresentation({\n state,\n events,\n}: {\n state: WritableState<PresentationState>;\n events: EventStream<PresentationAction>;\n}): () => void {\n // This is effectively a very simple finite state model. We can formalize this if needed.\n let resolving = false;\n let abortController: AbortController | null = null;\n\n const cleanup = combineLatest([state, events]).subscribe(async ([currentState, event]) => {\n if (!canResolve(currentState) || !shouldResolve(currentState, event) || resolving) return;\n\n try {\n // This along with the resolving finite state (or more complex) could be pulled into its own abstraction.\n // Set flag before async work\n resolving = true;\n abortController = new AbortController();\n\n const { presentation } = currentState;\n // Fetch and parse playlist\n const response = await fetchResolvable(presentation, { signal: abortController.signal });\n const text = await getResponseText(response);\n const parsed = parseMultivariantPlaylist(text, presentation);\n\n // Update state with resolved presentation\n state.patch({\n presentation: parsed,\n });\n } catch (error) {\n // Ignore AbortError - this is expected when cleanup happens\n if (error instanceof Error && error.name === 'AbortError') {\n return;\n }\n throw error;\n } finally {\n // Always clear flag\n resolving = false;\n abortController = null;\n }\n });\n\n // Return cleanup function that aborts pending fetches\n return () => {\n abortController?.abort();\n cleanup();\n };\n}\n"],"mappings":";;;;;;;;AA+BA,SAAgB,aACd,cACwC;AACxC,QAAO,iBAAiB,UAAa,SAAS,gBAAgB,EAAE,QAAQ;;AAG1E,SAAgB,WACd,OACuE;AACvE,QAAO,aAAa,MAAM,aAAa;;;;;;;;;;;;;AAczC,SAAgB,cAAc,OAA0B,OAAoC;CAC1F,MAAM,EAAE,YAAY;AACpB,QAEE,CAAC,QAAQ,WAAW,CAAC,SAAS,QAAe,IAE7C,MAAM,SAAS;;;;;;;;;;;;AAcnB,SAAgB,qBACd,OACA,QACY;AACZ,QAAO,OAAO,WAAW,YAAY;AAGnC,MAAI,MAAM,QAAQ,YAAY,OAAW;EACzC,MAAM,UAAU,QAAQ,cAAc,WAAW;AACjD,QAAM,MAAM,EAAW,SAAqD,CAAC;GAC7E;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCJ,SAAgB,oBAAoB,EAClC,OACA,UAIa;CAEb,IAAI,YAAY;CAChB,IAAI,kBAA0C;CAE9C,MAAM,UAAU,cAAc,CAAC,OAAO,OAAO,CAAC,CAAC,UAAU,OAAO,CAAC,cAAc,WAAW;AACxF,MAAI,CAAC,WAAW,aAAa,IAAI,CAAC,cAAc,cAAc,MAAM,IAAI,UAAW;AAEnF,MAAI;AAGF,eAAY;AACZ,qBAAkB,IAAI,iBAAiB;GAEvC,MAAM,EAAE,iBAAiB;GAIzB,MAAM,SAAS,0BADF,MAAM,gBADF,MAAM,gBAAgB,cAAc,EAAE,QAAQ,gBAAgB,QAAQ,CAAC,CAC5C,EACG,aAAa;AAG5D,SAAM,MAAM,EACV,cAAc,QACf,CAAC;WACK,OAAO;AAEd,OAAI,iBAAiB,SAAS,MAAM,SAAS,aAC3C;AAEF,SAAM;YACE;AAER,eAAY;AACZ,qBAAkB;;GAEpB;AAGF,cAAa;AACX,mBAAiB,OAAO;AACxB,WAAS"}
@@ -0,0 +1,13 @@
1
+ //#region src/core/features/resolve-track.d.ts
2
+ /**
3
+ * Action types for track resolution.
4
+ * Event names match HTMLMediaElement events (lowercase).
5
+ */
6
+ type TrackResolutionAction = {
7
+ type: 'play';
8
+ } | {
9
+ type: 'pause';
10
+ };
11
+ //#endregion
12
+ export { TrackResolutionAction };
13
+ //# sourceMappingURL=resolve-track.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-track.d.ts","names":[],"sources":["../../../../src/core/features/resolve-track.ts"],"mappings":";;;;;KAyEY,qBAAA;EAA0B,IAAA;AAAA;EAAmB,IAAA;AAAA"}