@videojs/html 10.0.0-beta.10 → 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 (143) hide show
  1. package/cdn/audio-minimal.dev.js +4 -3
  2. package/cdn/audio-minimal.dev.js.map +1 -1
  3. package/cdn/audio-minimal.js +1 -1
  4. package/cdn/audio-minimal.js.map +1 -1
  5. package/cdn/audio.dev.js +4 -3
  6. package/cdn/audio.dev.js.map +1 -1
  7. package/cdn/audio.js +1 -1
  8. package/cdn/audio.js.map +1 -1
  9. package/cdn/background.dev.js +3 -2
  10. package/cdn/background.dev.js.map +1 -1
  11. package/cdn/background.js +1 -1
  12. package/cdn/background.js.map +1 -1
  13. package/cdn/{context-DTY0nOpS.js → context-Be8C5kVd.js} +70 -2
  14. package/cdn/context-Be8C5kVd.js.map +1 -0
  15. package/cdn/context-CUBywtsB.js +14 -0
  16. package/cdn/context-CUBywtsB.js.map +1 -0
  17. package/cdn/{create-player-Cwxvswyv.js → create-player-AcfnN3li.js} +3 -3
  18. package/cdn/{create-player-Cwxvswyv.js.map → create-player-AcfnN3li.js.map} +1 -1
  19. package/cdn/create-player-s_qISCpw.js +7 -0
  20. package/cdn/{create-player-BTIU8EwT.js.map → create-player-s_qISCpw.js.map} +1 -1
  21. package/cdn/{proxy-2oO2ph3m.js → custom-media-element-DqevSVgS.js} +6 -6
  22. package/cdn/custom-media-element-DqevSVgS.js.map +1 -0
  23. package/cdn/{proxy-dR7IDk37.js → custom-media-element-moFa3UZp.js} +2 -48
  24. package/cdn/custom-media-element-moFa3UZp.js.map +1 -0
  25. package/cdn/delegate-CzAcT1xm.js +44 -0
  26. package/cdn/delegate-CzAcT1xm.js.map +1 -0
  27. package/cdn/delegate-Uc-6tQDR.js +2 -0
  28. package/cdn/delegate-Uc-6tQDR.js.map +1 -0
  29. package/cdn/{listen-DX5vU4s4.js → listen-4jqsRSKo.js} +1 -1
  30. package/cdn/{listen-DX5vU4s4.js.map → listen-4jqsRSKo.js.map} +1 -1
  31. package/cdn/{listen-BXAYCbZA.js → listen-YSH3Jfyk.js} +1 -1
  32. package/cdn/{listen-BXAYCbZA.js.map → listen-YSH3Jfyk.js.map} +1 -1
  33. package/cdn/media/dash-video.dev.js +4 -2
  34. package/cdn/media/dash-video.dev.js.map +1 -1
  35. package/cdn/media/dash-video.js +2 -2
  36. package/cdn/media/dash-video.js.map +1 -1
  37. package/cdn/media/hls-video.dev.js +5 -3
  38. package/cdn/media/hls-video.dev.js.map +1 -1
  39. package/cdn/media/hls-video.js +2 -2
  40. package/cdn/media/hls-video.js.map +1 -1
  41. package/cdn/media/simple-hls-video.dev.js +684 -546
  42. package/cdn/media/simple-hls-video.dev.js.map +1 -1
  43. package/cdn/media/simple-hls-video.js +1 -1
  44. package/cdn/media/simple-hls-video.js.map +1 -1
  45. package/cdn/{media-attach-mixin-tFNcHnvo.js → media-attach-mixin-D5_nfJpa.js} +2 -2
  46. package/cdn/{media-attach-mixin-tFNcHnvo.js.map → media-attach-mixin-D5_nfJpa.js.map} +1 -1
  47. package/cdn/{media-attach-mixin-ChyNp2eK.js → media-attach-mixin-U_KQB_9O.js} +2 -2
  48. package/cdn/{media-attach-mixin-ChyNp2eK.js.map → media-attach-mixin-U_KQB_9O.js.map} +1 -1
  49. package/cdn/{player-BHhLXO-R.js → player-C46h14iP.js} +2 -2
  50. package/cdn/{player-BHhLXO-R.js.map → player-C46h14iP.js.map} +1 -1
  51. package/cdn/{player-DEfj0RU6.js → player-CvrOeLpy.js} +2 -2
  52. package/cdn/{player-DEfj0RU6.js.map → player-CvrOeLpy.js.map} +1 -1
  53. package/cdn/{poster-Dd0F1rRd.js → poster-Olv5zDI_.js} +4 -4
  54. package/cdn/{poster-Dd0F1rRd.js.map → poster-Olv5zDI_.js.map} +1 -1
  55. package/cdn/{poster-DwQ3RAch.js → poster-odJ4iwIv.js} +2 -2
  56. package/cdn/{poster-DwQ3RAch.js.map → poster-odJ4iwIv.js.map} +1 -1
  57. package/cdn/video-minimal.dev.js +4 -3
  58. package/cdn/video-minimal.dev.js.map +1 -1
  59. package/cdn/video-minimal.js +1 -1
  60. package/cdn/video-minimal.js.map +1 -1
  61. package/cdn/video.dev.js +4 -4
  62. package/cdn/video.js +1 -1
  63. package/cdn/{volume-slider-DgJ0rAfC.js → volume-slider-D7BOdSDF.js} +3 -3
  64. package/cdn/{volume-slider-DgJ0rAfC.js.map → volume-slider-D7BOdSDF.js.map} +1 -1
  65. package/cdn/{volume-slider-Pd0AMTCH.js → volume-slider-DPeFF5tt.js} +2 -2
  66. package/cdn/{volume-slider-Pd0AMTCH.js.map → volume-slider-DPeFF5tt.js.map} +1 -1
  67. package/dist/default/index.js +2 -1
  68. package/dist/default/ui/alert-dialog/alert-dialog-element.js +1 -1
  69. package/dist/default/ui/buffering-indicator/buffering-indicator-element.js +1 -1
  70. package/dist/default/ui/captions-button/captions-button-element.js +1 -1
  71. package/dist/default/ui/controls/controls-element.js +1 -1
  72. package/dist/default/ui/fullscreen-button/fullscreen-button-element.js +1 -1
  73. package/dist/default/ui/mute-button/mute-button-element.js +1 -1
  74. package/dist/default/ui/pip-button/pip-button-element.js +1 -1
  75. package/dist/default/ui/play-button/play-button-element.js +1 -1
  76. package/dist/default/ui/playback-rate-button/playback-rate-button-element.js +1 -1
  77. package/dist/default/ui/popover/popover-element.js +1 -1
  78. package/dist/default/ui/poster/poster-element.js +1 -1
  79. package/dist/default/ui/seek-button/seek-button-element.js +1 -1
  80. package/dist/default/ui/slider/slider-element.js +1 -1
  81. package/dist/default/ui/thumbnail/thumbnail-element.js +1 -1
  82. package/dist/default/ui/time/time-element.js +1 -1
  83. package/dist/default/ui/time-slider/time-slider-element.js +1 -1
  84. package/dist/default/ui/tooltip/tooltip-element.js +1 -1
  85. package/dist/default/ui/tooltip/tooltip-group-element.js +1 -1
  86. package/dist/default/ui/volume-slider/volume-slider-element.js +1 -1
  87. package/dist/dev/index.d.ts +2 -1
  88. package/dist/dev/index.js +2 -1
  89. package/dist/dev/ui/alert-dialog/alert-dialog-description-element.d.ts +1 -1
  90. package/dist/dev/ui/alert-dialog/alert-dialog-element.js +1 -1
  91. package/dist/dev/ui/alert-dialog/alert-dialog-title-element.d.ts +1 -1
  92. package/dist/dev/ui/buffering-indicator/buffering-indicator-element.js +1 -1
  93. package/dist/dev/ui/captions-button/captions-button-element.d.ts +1 -1
  94. package/dist/dev/ui/captions-button/captions-button-element.js +1 -1
  95. package/dist/dev/ui/context-part-element.d.ts +1 -1
  96. package/dist/dev/ui/controls/controls-element.js +1 -1
  97. package/dist/dev/ui/controls/controls-group-element.d.ts +1 -1
  98. package/dist/dev/ui/fullscreen-button/fullscreen-button-element.d.ts +1 -1
  99. package/dist/dev/ui/fullscreen-button/fullscreen-button-element.js +1 -1
  100. package/dist/dev/ui/media-button-element.d.ts +1 -1
  101. package/dist/dev/ui/media-ui-element.d.ts +1 -1
  102. package/dist/dev/ui/mute-button/mute-button-element.d.ts +1 -1
  103. package/dist/dev/ui/mute-button/mute-button-element.js +1 -1
  104. package/dist/dev/ui/pip-button/pip-button-element.d.ts +1 -1
  105. package/dist/dev/ui/pip-button/pip-button-element.js +1 -1
  106. package/dist/dev/ui/play-button/play-button-element.d.ts +1 -1
  107. package/dist/dev/ui/play-button/play-button-element.js +1 -1
  108. package/dist/dev/ui/playback-rate-button/playback-rate-button-element.d.ts +1 -1
  109. package/dist/dev/ui/playback-rate-button/playback-rate-button-element.js +1 -1
  110. package/dist/dev/ui/popover/popover-element.d.ts +1 -1
  111. package/dist/dev/ui/popover/popover-element.js +1 -1
  112. package/dist/dev/ui/poster/poster-element.d.ts +1 -1
  113. package/dist/dev/ui/poster/poster-element.js +1 -1
  114. package/dist/dev/ui/seek-button/seek-button-element.d.ts +1 -1
  115. package/dist/dev/ui/seek-button/seek-button-element.js +1 -1
  116. package/dist/dev/ui/slider/context.d.ts +1 -1
  117. package/dist/dev/ui/slider/slider-buffer-element.d.ts +1 -1
  118. package/dist/dev/ui/slider/slider-element.d.ts +1 -1
  119. package/dist/dev/ui/slider/slider-element.js +1 -1
  120. package/dist/dev/ui/slider/slider-fill-element.d.ts +1 -1
  121. package/dist/dev/ui/slider/slider-track-element.d.ts +1 -1
  122. package/dist/dev/ui/thumbnail/thumbnail-element.d.ts +1 -1
  123. package/dist/dev/ui/thumbnail/thumbnail-element.js +1 -1
  124. package/dist/dev/ui/time/time-element.d.ts +1 -1
  125. package/dist/dev/ui/time/time-element.js +1 -1
  126. package/dist/dev/ui/time-slider/time-slider-element.d.ts +1 -1
  127. package/dist/dev/ui/time-slider/time-slider-element.js +1 -1
  128. package/dist/dev/ui/tooltip/tooltip-element.d.ts +1 -1
  129. package/dist/dev/ui/tooltip/tooltip-element.js +1 -1
  130. package/dist/dev/ui/tooltip/tooltip-group-element.js +1 -1
  131. package/dist/dev/ui/volume-slider/volume-slider-element.d.ts +1 -1
  132. package/dist/dev/ui/volume-slider/volume-slider-element.js +1 -1
  133. package/package.json +10 -10
  134. package/cdn/context-C_e06fGU.js +0 -13
  135. package/cdn/context-C_e06fGU.js.map +0 -1
  136. package/cdn/context-DTY0nOpS.js.map +0 -1
  137. package/cdn/create-player-BTIU8EwT.js +0 -7
  138. package/cdn/proxy-2oO2ph3m.js.map +0 -1
  139. package/cdn/proxy-6KS6wy69.js +0 -2
  140. package/cdn/proxy-6KS6wy69.js.map +0 -1
  141. package/cdn/proxy-XzDf9gyk.js +0 -66
  142. package/cdn/proxy-XzDf9gyk.js.map +0 -1
  143. package/cdn/proxy-dR7IDk37.js.map +0 -1
@@ -1,9 +1,11 @@
1
1
  import { n as isNil } from "../predicate-BG-dj_kF.js";
2
- import { t as listen } from "../listen-BXAYCbZA.js";
3
- import { t as MediaAttachMixin } from "../media-attach-mixin-ChyNp2eK.js";
4
- import { n as DelegateMixin, t as CustomMediaMixin } from "../proxy-dR7IDk37.js";
2
+ import "../context-Be8C5kVd.js";
3
+ import { t as listen } from "../listen-YSH3Jfyk.js";
4
+ import { t as DelegateMixin } from "../delegate-CzAcT1xm.js";
5
+ import { t as MediaAttachMixin } from "../media-attach-mixin-U_KQB_9O.js";
6
+ import { t as CustomMediaMixin } from "../custom-media-element-moFa3UZp.js";
5
7
 
6
- //#region ../spf/dist/adapter-CflgYzjF.js
8
+ //#region ../spf/dist/dev/core/state/create-state.js
7
9
  /**
8
10
  * Reactive state container with selectors, custom equality, and batched updates.
9
11
  *
@@ -61,11 +63,11 @@ var StateContainer = class {
61
63
  }
62
64
  subscribe(selectorOrListener, maybeListener, options) {
63
65
  if (maybeListener === void 0) {
64
- const listener$1 = selectorOrListener;
65
- this.#listeners.add(listener$1);
66
- listener$1(this.current);
66
+ const listener = selectorOrListener;
67
+ this.#listeners.add(listener);
68
+ listener(this.current);
67
69
  return () => {
68
- this.#listeners.delete(listener$1);
70
+ this.#listeners.delete(listener);
69
71
  };
70
72
  }
71
73
  const selector = selectorOrListener;
@@ -142,6 +144,432 @@ var StateContainer = class {
142
144
  function createState(initial, config) {
143
145
  return new StateContainer(initial, config);
144
146
  }
147
+
148
+ //#endregion
149
+ //#region ../spf/dist/dev/core/abr/ewma.js
150
+ /**
151
+ * Exponentially Weighted Moving Average (EWMA)
152
+ *
153
+ * Pure functional implementation of EWMA calculations.
154
+ * Based on Shaka Player's EWMA algorithm.
155
+ */
156
+ /**
157
+ * Calculate alpha (decay factor) from half-life.
158
+ *
159
+ * Alpha determines how quickly old data "expires":
160
+ * - alpha close to 1 = slow decay (long memory)
161
+ * - alpha close to 0 = fast decay (short memory)
162
+ *
163
+ * @param halfLife - The quantity of prior samples (by weight) that make up
164
+ * half of the new estimate. Must be positive.
165
+ * @returns Alpha value between 0 and 1
166
+ *
167
+ * @example
168
+ * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life
169
+ */
170
+ function calculateAlpha(halfLife) {
171
+ return Math.exp(Math.log(.5) / halfLife);
172
+ }
173
+ /**
174
+ * Calculate exponentially weighted moving average.
175
+ *
176
+ * Updates an estimate by blending a new value with the previous estimate,
177
+ * weighted by the sample duration. Longer samples have more influence.
178
+ *
179
+ * @param prevEstimate - Previous EWMA estimate
180
+ * @param value - New sample value to incorporate
181
+ * @param weight - Sample weight (typically duration in seconds)
182
+ * @param halfLife - Half-life for decay (typically 2-5 seconds)
183
+ * @returns Updated EWMA estimate
184
+ *
185
+ * @example
186
+ * let estimate = 0;
187
+ * estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample
188
+ * estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample
189
+ */
190
+ function calculateEwma(prevEstimate, value, weight, halfLife) {
191
+ const adjAlpha = calculateAlpha(halfLife) ** weight;
192
+ return value * (1 - adjAlpha) + adjAlpha * prevEstimate;
193
+ }
194
+ /**
195
+ * Apply zero-factor correction to EWMA estimate.
196
+ *
197
+ * The zero-factor correction compensates for bias when starting from zero.
198
+ * Without this correction, early estimates would be artificially low.
199
+ *
200
+ * As totalWeight increases, the correction factor approaches 1, meaning
201
+ * the estimate becomes more reliable and needs less correction.
202
+ *
203
+ * @param estimate - Raw EWMA estimate (uncorrected)
204
+ * @param totalWeight - Accumulated weight from all samples
205
+ * @param halfLife - Half-life used in EWMA calculation
206
+ * @returns Corrected estimate, or 0 if totalWeight is 0
207
+ *
208
+ * @example
209
+ * const raw = calculateEwma(0, 1_000_000, 1, 2);
210
+ * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000
211
+ */
212
+ function applyZeroFactor(estimate, totalWeight, halfLife) {
213
+ if (totalWeight === 0) return 0;
214
+ return estimate / (1 - calculateAlpha(halfLife) ** totalWeight);
215
+ }
216
+
217
+ //#endregion
218
+ //#region ../spf/dist/dev/core/abr/bandwidth-estimator.js
219
+ /**
220
+ * Dual EWMA Bandwidth Estimator
221
+ *
222
+ * Estimates available bandwidth using two EWMA calculations with different
223
+ * half-lives, taking the minimum of both. This approach (from Shaka Player):
224
+ *
225
+ * - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops
226
+ * - **Slow EWMA** (5s half-life): Provides stability during fluctuations
227
+ * - **min(fast, slow)**: Adapts down quickly, up slowly
228
+ *
229
+ * This naturally provides asymmetric behavior needed for good QoE:
230
+ * avoiding stalls (quick downgrade) while preventing oscillation (slow upgrade).
231
+ */
232
+ /**
233
+ * Default bandwidth estimator configuration.
234
+ *
235
+ * Values match Shaka Player defaults based on experimentation.
236
+ */
237
+ const DEFAULT_BANDWIDTH_CONFIG = {
238
+ fastHalfLife: 2,
239
+ slowHalfLife: 5,
240
+ minTotalBytes: 128e3,
241
+ minBytes: 16e3,
242
+ minDuration: 5
243
+ };
244
+ /**
245
+ * Add a bandwidth sample from a segment download.
246
+ *
247
+ * Samples are filtered based on:
248
+ * - Minimum bytes (filters TTFB-dominated small segments)
249
+ * - Minimum duration (filters cached responses)
250
+ *
251
+ * Valid samples update both fast and slow EWMA estimates.
252
+ *
253
+ * @param state - Current estimator state
254
+ * @param durationMs - Download duration in milliseconds
255
+ * @param numBytes - Number of bytes downloaded
256
+ * @param config - Optional estimator configuration (uses defaults if not provided)
257
+ * @returns New estimator state with sample incorporated (or unchanged if filtered)
258
+ *
259
+ * @example
260
+ * let state = { fastEstimate: 0, fastTotalWeight: 0, ... };
261
+ * // Sample: 1MB in 1 second
262
+ * state = sampleBandwidth(state, 1000, 1_000_000);
263
+ */
264
+ function sampleBandwidth(state, durationMs, numBytes, config = DEFAULT_BANDWIDTH_CONFIG) {
265
+ const updatedBytesSampled = state.bytesSampled + numBytes;
266
+ if (numBytes < config.minBytes) return {
267
+ ...state,
268
+ bytesSampled: updatedBytesSampled
269
+ };
270
+ if (durationMs < config.minDuration) return {
271
+ ...state,
272
+ bytesSampled: updatedBytesSampled
273
+ };
274
+ const bandwidth = 8e3 * numBytes / durationMs;
275
+ const weight = durationMs / 1e3;
276
+ return {
277
+ fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),
278
+ fastTotalWeight: state.fastTotalWeight + weight,
279
+ slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),
280
+ slowTotalWeight: state.slowTotalWeight + weight,
281
+ bytesSampled: updatedBytesSampled
282
+ };
283
+ }
284
+ /**
285
+ * Get the current bandwidth estimate.
286
+ *
287
+ * Returns the **minimum** of the fast and slow EWMA estimates.
288
+ * This provides the key asymmetric behavior:
289
+ * - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)
290
+ * - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)
291
+ *
292
+ * Uses default estimate until enough data has been sampled.
293
+ *
294
+ * @param state - Current estimator state
295
+ * @param defaultEstimate - Fallback estimate before sufficient samples (bps)
296
+ * @param config - Optional estimator configuration (uses defaults if not provided)
297
+ * @returns Bandwidth estimate in bits per second
298
+ *
299
+ * @example
300
+ * const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default
301
+ */
302
+ function getBandwidthEstimate(state, defaultEstimate, config = DEFAULT_BANDWIDTH_CONFIG) {
303
+ if (state.bytesSampled < config.minTotalBytes) return defaultEstimate;
304
+ const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);
305
+ const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);
306
+ return Math.min(fastEstimate, slowEstimate);
307
+ }
308
+
309
+ //#endregion
310
+ //#region ../spf/dist/dev/core/buffer/forward-buffer.js
311
+ /**
312
+ * Default forward buffer configuration.
313
+ */
314
+ const DEFAULT_FORWARD_BUFFER_CONFIG = { bufferDuration: 30 };
315
+ /**
316
+ * Get segments that need to be loaded for forward buffer.
317
+ *
318
+ * Determines which segments to load to maintain target buffer duration.
319
+ * Handles discontiguous buffering (gaps after seeks).
320
+ *
321
+ * Algorithm:
322
+ * 1. Calculate target time: currentTime + bufferDuration
323
+ * 2. Find all segments in range [currentTime, targetTime)
324
+ * 3. Filter out segments already buffered at that time position
325
+ * 4. Return segments to load (fills gaps + extends to target)
326
+ *
327
+ * @param segments - All available segments from playlist
328
+ * @param bufferedSegments - Segments already buffered (ordered by startTime)
329
+ * @param currentTime - Current playback position in seconds
330
+ * @param config - Optional forward buffer configuration
331
+ * @returns Array of segments to load (empty if buffer is sufficient)
332
+ *
333
+ * @example
334
+ * // After seek: buffered [0-12, 18-30], playing at 7s
335
+ * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });
336
+ * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)
337
+ */
338
+ /**
339
+ * Calculate the start time from which to flush forward buffer content.
340
+ *
341
+ * Content that starts at or beyond `currentTime + bufferDuration` is no
342
+ * longer needed for the current playback position and should be removed
343
+ * from the SourceBuffer. This prevents unbounded accumulation of scattered
344
+ * SourceBuffer content after seeks, which can cause QuotaExceededError on
345
+ * long-form content.
346
+ *
347
+ * Returns `Infinity` when nothing needs flushing (no buffered segments
348
+ * exist beyond the threshold).
349
+ *
350
+ * @param bufferedSegments - Segments currently tracked in the buffer model
351
+ * @param currentTime - Current playback position in seconds
352
+ * @param config - Optional forward buffer configuration
353
+ * @returns Start time to flush from (flush range: [flushStart, Infinity)),
354
+ * or Infinity if no flush is needed
355
+ *
356
+ * @example
357
+ * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30
358
+ * const flushStart = calculateForwardFlushPoint(segments, 0);
359
+ * // Returns 30 — flush [30, Infinity), keep [0, 30)
360
+ */
361
+ function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
362
+ if (bufferedSegments.length === 0) return Infinity;
363
+ const threshold = currentTime + config.bufferDuration;
364
+ const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);
365
+ if (beyond.length === 0) return Infinity;
366
+ return Math.min(...beyond.map((seg) => seg.startTime));
367
+ }
368
+ function getSegmentsToLoad(segments, bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
369
+ if (segments.length === 0) return [];
370
+ const targetTime = currentTime + config.bufferDuration;
371
+ const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));
372
+ return segments.filter((seg) => {
373
+ const segmentEnd = seg.startTime + seg.duration;
374
+ const isInRange = seg.startTime < targetTime && segmentEnd > currentTime;
375
+ const isNotBuffered = !bufferedStartTimes.has(seg.startTime);
376
+ return isInRange && isNotBuffered;
377
+ });
378
+ }
379
+
380
+ //#endregion
381
+ //#region ../spf/dist/dev/core/types/index.js
382
+ function isResolvedTrack(track) {
383
+ return "segments" in track;
384
+ }
385
+ /**
386
+ * Check if a presentation has duration (at least one track resolved).
387
+ * Narrows type to include required duration.
388
+ */
389
+ function hasPresentationDuration(presentation) {
390
+ return presentation.duration !== void 0;
391
+ }
392
+
393
+ //#endregion
394
+ //#region ../spf/dist/dev/dom/network/chunked-stream-iterable.js
395
+ const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17;
396
+ /**
397
+ * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an
398
+ * `AsyncIterable<Uint8Array>` that yields chunks no smaller than
399
+ * `minChunkSize` bytes. Smaller network chunks are accumulated and yielded
400
+ * together once the threshold is met. Any remainder is flushed on stream end.
401
+ *
402
+ * Errors from the underlying stream propagate naturally — the reader lock is
403
+ * always released via `finally`.
404
+ */
405
+ var ChunkedStreamIterable = class {
406
+ minChunkSize;
407
+ #readableStream;
408
+ constructor(readableStream, { minChunkSize = DEFAULT_MIN_CHUNK_SIZE } = {}) {
409
+ this.#readableStream = readableStream;
410
+ this.minChunkSize = minChunkSize;
411
+ }
412
+ async *[Symbol.asyncIterator]() {
413
+ let pending;
414
+ const reader = this.#readableStream.getReader();
415
+ try {
416
+ while (true) {
417
+ const { done, value } = await reader.read();
418
+ if (done) {
419
+ if (pending) yield pending;
420
+ break;
421
+ }
422
+ pending = pending ? concat(pending, value) : value;
423
+ if (pending.length >= this.minChunkSize) {
424
+ yield pending;
425
+ pending = void 0;
426
+ }
427
+ }
428
+ } finally {
429
+ reader.releaseLock();
430
+ }
431
+ }
432
+ };
433
+ function concat(a, b) {
434
+ const result = new Uint8Array(a.length + b.length);
435
+ result.set(a);
436
+ result.set(b, a.length);
437
+ return result;
438
+ }
439
+
440
+ //#endregion
441
+ //#region ../spf/dist/dev/dom/network/fetch.js
442
+ /**
443
+ * Fetch resolvable from AddressableObject.
444
+ *
445
+ * Handles byte range requests if byteRange is present.
446
+ * Returns native fetch Response for composability (can extract text, stream, etc.).
447
+ *
448
+ * @param addressable - Resource to fetch (url + optional byteRange)
449
+ * @returns Promise resolving to Response
450
+ *
451
+ * @example
452
+ * const response = await fetchResolvable({ url: 'https://example.com/segment.m4s' });
453
+ * const text = await getResponseText(response);
454
+ *
455
+ * @example
456
+ * // With byte range
457
+ * const response = await fetchResolvable({
458
+ * url: 'https://example.com/file.mp4',
459
+ * byteRange: { start: 1000, end: 1999 }
460
+ * });
461
+ */
462
+ async function fetchResolvable(addressable, options) {
463
+ const headers = new Headers(options?.headers);
464
+ if (addressable.byteRange) {
465
+ const { start, end } = addressable.byteRange;
466
+ headers.set("Range", `bytes=${start}-${end}`);
467
+ }
468
+ const request = new Request(addressable.url, {
469
+ method: "GET",
470
+ headers,
471
+ ...options
472
+ });
473
+ return fetch(request);
474
+ }
475
+ /**
476
+ * Extract text from Response.
477
+ *
478
+ * Accepts minimal Response-like object (just needs text() method).
479
+ * Returns promise from response.text().
480
+ *
481
+ * @param response - Response-like object with text() method
482
+ * @returns Promise resolving to text content
483
+ *
484
+ * @example
485
+ * const response = await fetchResolvable(addressable);
486
+ * const text = await getResponseText(response);
487
+ */
488
+ function getResponseText(response) {
489
+ return response.text();
490
+ }
491
+
492
+ //#endregion
493
+ //#region ../spf/dist/dev/core/reactive/combine-latest.js
494
+ /**
495
+ * Combines multiple Observable sources into a single Observable.
496
+ *
497
+ * Emits an array of latest values whenever any source emits.
498
+ * Only emits after all sources have emitted at least once.
499
+ *
500
+ * Supports selector-based subscriptions (fires only when the selected value
501
+ * changes, per the optional equalityFn) mirroring the createState API.
502
+ *
503
+ * @param sources - Array of Observable sources
504
+ * @returns Combined Observable
505
+ *
506
+ * @example
507
+ * ```ts
508
+ * const state = createState({ count: 0 });
509
+ * const events = createEventStream<Action>();
510
+ *
511
+ * combineLatest([state, events]).subscribe(([state, event]) => {
512
+ * if (event.type === 'PLAY' && state.count > 0) {
513
+ * // React to event + state condition
514
+ * }
515
+ * });
516
+ * ```
517
+ *
518
+ * @example Selector subscription
519
+ * ```ts
520
+ * combineLatest([state, owners]).subscribe(
521
+ * ([s, o]) => deriveKey(s, o),
522
+ * (key) => { ... },
523
+ * { equalityFn: keyEq }
524
+ * );
525
+ * ```
526
+ */
527
+ function combineLatest(sources) {
528
+ const subscribeToSources = (listener) => {
529
+ const latest = new Array(sources.length);
530
+ const hasValue = new Array(sources.length).fill(false);
531
+ const unsubscribers = [];
532
+ for (let i = 0; i < sources.length; i++) {
533
+ const unsubscribe = sources[i].subscribe((value) => {
534
+ latest[i] = value;
535
+ hasValue[i] = true;
536
+ if (hasValue.every((has) => has)) listener([...latest]);
537
+ });
538
+ unsubscribers.push(unsubscribe);
539
+ }
540
+ return () => {
541
+ for (const unsubscribe of unsubscribers) unsubscribe();
542
+ };
543
+ };
544
+ return { subscribe(listenerOrSelector, maybeListener, options) {
545
+ if (maybeListener === void 0) return subscribeToSources(listenerOrSelector);
546
+ const selector = listenerOrSelector;
547
+ const listener = maybeListener;
548
+ const equalityFn = options?.equalityFn ?? Object.is;
549
+ let prevSelected;
550
+ let initialized = false;
551
+ return subscribeToSources((values) => {
552
+ const nextSelected = selector(values);
553
+ if (!initialized || !equalityFn(prevSelected, nextSelected)) {
554
+ prevSelected = nextSelected;
555
+ initialized = true;
556
+ listener(nextSelected);
557
+ }
558
+ });
559
+ } };
560
+ }
561
+
562
+ //#endregion
563
+ //#region ../spf/dist/dev/core/hls/resolve-url.js
564
+ /**
565
+ * Resolve a potentially relative URL against a base URL using native URL API.
566
+ */
567
+ function resolveUrl(url, baseUrl) {
568
+ return new URL(url, baseUrl).href;
569
+ }
570
+
571
+ //#endregion
572
+ //#region ../spf/dist/dev/core/hls/parse-attributes.js
145
573
  /**
146
574
  * Parse HLS attribute list from a tag line.
147
575
  * Handles both quoted and unquoted values.
@@ -271,12 +699,9 @@ function matchTag(line, tag) {
271
699
  if (!line.startsWith(prefix)) return null;
272
700
  return createAttributeList(line.slice(prefix.length));
273
701
  }
274
- /**
275
- * Resolve a potentially relative URL against a base URL using native URL API.
276
- */
277
- function resolveUrl(url, baseUrl) {
278
- return new URL(url, baseUrl).href;
279
- }
702
+
703
+ //#endregion
704
+ //#region ../spf/dist/dev/core/hls/parse-media-playlist.js
280
705
  /**
281
706
  * Parse HLS media playlist and resolve track with segments.
282
707
  *
@@ -352,6 +777,9 @@ function parseMediaPlaylist(text, unresolved) {
352
777
  initialization
353
778
  };
354
779
  }
780
+
781
+ //#endregion
782
+ //#region ../spf/dist/dev/core/utils/generate-id.js
355
783
  /**
356
784
  * Generate unique ID for HAM objects.
357
785
  *
@@ -368,6 +796,9 @@ function parseMediaPlaylist(text, unresolved) {
368
796
  function generateId() {
369
797
  return `${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
370
798
  }
799
+
800
+ //#endregion
801
+ //#region ../spf/dist/dev/core/hls/parse-multivariant.js
371
802
  /**
372
803
  * Parse HLS multivariant playlist into a Presentation.
373
804
  *
@@ -460,262 +891,123 @@ function parseMultivariantPlaylist(text, unresolved) {
460
891
  bandwidth: stream.bandwidth,
461
892
  mimeType: "video/mp4",
462
893
  codecs: []
463
- };
464
- if (stream.resolution?.width !== void 0) track.width = stream.resolution.width;
465
- if (stream.resolution?.height !== void 0) track.height = stream.resolution.height;
466
- if (codecs?.video) track.codecs = [codecs.video];
467
- if (stream.frameRate) track.frameRate = stream.frameRate;
468
- if (stream.audioGroupId) track.audioGroupId = stream.audioGroupId;
469
- return track;
470
- });
471
- const audioOnlyTracks = audioOnlyStreams.map((stream) => {
472
- const codecs = stream.codecs ? parseCodecs(stream.codecs) : void 0;
473
- return {
474
- type: "audio",
475
- id: generateId(),
476
- url: stream.uri,
477
- bandwidth: stream.bandwidth,
478
- mimeType: "audio/mp4",
479
- codecs: codecs?.audio ? [codecs.audio] : [],
480
- groupId: stream.audioGroupId || "default",
481
- name: "Default",
482
- sampleRate: 48e3,
483
- channels: 2
484
- };
485
- });
486
- const audioTracks = [...audioRenditions.map((rendition) => {
487
- let audioCodecs;
488
- for (const stream of streams) if (stream.audioGroupId === rendition.groupId && stream.codecs) {
489
- const codecs = parseCodecs(stream.codecs);
490
- if (codecs.audio) {
491
- audioCodecs = [codecs.audio];
492
- break;
493
- }
494
- }
495
- const track = {
496
- type: "audio",
497
- id: generateId(),
498
- url: rendition.uri ?? "",
499
- groupId: rendition.groupId,
500
- name: rendition.name,
501
- mimeType: "audio/mp4",
502
- bandwidth: 0,
503
- sampleRate: 48e3,
504
- channels: 2,
505
- codecs: []
506
- };
507
- if (rendition.language) track.language = rendition.language;
508
- if (audioCodecs) track.codecs = audioCodecs;
509
- if (rendition.default) track.default = rendition.default;
510
- if (rendition.autoselect) track.autoselect = rendition.autoselect;
511
- return track;
512
- }), ...audioOnlyTracks];
513
- const textTracks = subtitleRenditions.map((rendition) => {
514
- const track = {
515
- type: "text",
516
- id: generateId(),
517
- url: rendition.uri,
518
- groupId: rendition.groupId,
519
- label: rendition.name,
520
- kind: "subtitles",
521
- mimeType: "text/vtt",
522
- bandwidth: 0
523
- };
524
- if (rendition.language) track.language = rendition.language;
525
- if (rendition.default && rendition.autoselect) track.default = true;
526
- if (rendition.autoselect) track.autoselect = rendition.autoselect;
527
- if (rendition.forced) track.forced = rendition.forced;
528
- return track;
529
- });
530
- const selectionSets = [];
531
- if (videoTracks.length > 0) {
532
- const videoSwitchingSet = {
533
- id: generateId(),
534
- type: "video",
535
- tracks: videoTracks
536
- };
537
- const videoSelectionSet = {
538
- id: generateId(),
539
- type: "video",
540
- switchingSets: [videoSwitchingSet]
541
- };
542
- selectionSets.push(videoSelectionSet);
543
- }
544
- if (audioTracks.length > 0) {
545
- const audioSwitchingSet = {
546
- id: generateId(),
547
- type: "audio",
548
- tracks: audioTracks
549
- };
550
- const audioSelectionSet = {
551
- id: generateId(),
552
- type: "audio",
553
- switchingSets: [audioSwitchingSet]
554
- };
555
- selectionSets.push(audioSelectionSet);
556
- }
557
- if (textTracks.length > 0) {
558
- const textSwitchingSet = {
559
- id: generateId(),
560
- type: "text",
561
- tracks: textTracks
562
- };
563
- const textSelectionSet = {
564
- id: generateId(),
565
- type: "text",
566
- switchingSets: [textSwitchingSet]
567
- };
568
- selectionSets.push(textSelectionSet);
569
- }
570
- return {
571
- id: generateId(),
572
- url: unresolved.url,
573
- startTime: 0,
574
- selectionSets
575
- };
576
- }
577
- /**
578
- * Exponentially Weighted Moving Average (EWMA)
579
- *
580
- * Pure functional implementation of EWMA calculations.
581
- * Based on Shaka Player's EWMA algorithm.
582
- */
583
- /**
584
- * Calculate alpha (decay factor) from half-life.
585
- *
586
- * Alpha determines how quickly old data "expires":
587
- * - alpha close to 1 = slow decay (long memory)
588
- * - alpha close to 0 = fast decay (short memory)
589
- *
590
- * @param halfLife - The quantity of prior samples (by weight) that make up
591
- * half of the new estimate. Must be positive.
592
- * @returns Alpha value between 0 and 1
593
- *
594
- * @example
595
- * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life
596
- */
597
- function calculateAlpha(halfLife) {
598
- return Math.exp(Math.log(.5) / halfLife);
599
- }
600
- /**
601
- * Calculate exponentially weighted moving average.
602
- *
603
- * Updates an estimate by blending a new value with the previous estimate,
604
- * weighted by the sample duration. Longer samples have more influence.
605
- *
606
- * @param prevEstimate - Previous EWMA estimate
607
- * @param value - New sample value to incorporate
608
- * @param weight - Sample weight (typically duration in seconds)
609
- * @param halfLife - Half-life for decay (typically 2-5 seconds)
610
- * @returns Updated EWMA estimate
611
- *
612
- * @example
613
- * let estimate = 0;
614
- * estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample
615
- * estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample
616
- */
617
- function calculateEwma(prevEstimate, value, weight, halfLife) {
618
- const adjAlpha = calculateAlpha(halfLife) ** weight;
619
- return value * (1 - adjAlpha) + adjAlpha * prevEstimate;
620
- }
621
- /**
622
- * Apply zero-factor correction to EWMA estimate.
623
- *
624
- * The zero-factor correction compensates for bias when starting from zero.
625
- * Without this correction, early estimates would be artificially low.
626
- *
627
- * As totalWeight increases, the correction factor approaches 1, meaning
628
- * the estimate becomes more reliable and needs less correction.
629
- *
630
- * @param estimate - Raw EWMA estimate (uncorrected)
631
- * @param totalWeight - Accumulated weight from all samples
632
- * @param halfLife - Half-life used in EWMA calculation
633
- * @returns Corrected estimate, or 0 if totalWeight is 0
634
- *
635
- * @example
636
- * const raw = calculateEwma(0, 1_000_000, 1, 2);
637
- * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000
638
- */
639
- function applyZeroFactor(estimate, totalWeight, halfLife) {
640
- if (totalWeight === 0) return 0;
641
- return estimate / (1 - calculateAlpha(halfLife) ** totalWeight);
642
- }
643
- /**
644
- * Default bandwidth estimator configuration.
645
- *
646
- * Values match Shaka Player defaults based on experimentation.
647
- */
648
- const DEFAULT_BANDWIDTH_CONFIG = {
649
- fastHalfLife: 2,
650
- slowHalfLife: 5,
651
- minTotalBytes: 128e3,
652
- minBytes: 16e3,
653
- minDuration: 5
654
- };
655
- /**
656
- * Add a bandwidth sample from a segment download.
657
- *
658
- * Samples are filtered based on:
659
- * - Minimum bytes (filters TTFB-dominated small segments)
660
- * - Minimum duration (filters cached responses)
661
- *
662
- * Valid samples update both fast and slow EWMA estimates.
663
- *
664
- * @param state - Current estimator state
665
- * @param durationMs - Download duration in milliseconds
666
- * @param numBytes - Number of bytes downloaded
667
- * @param config - Optional estimator configuration (uses defaults if not provided)
668
- * @returns New estimator state with sample incorporated (or unchanged if filtered)
669
- *
670
- * @example
671
- * let state = { fastEstimate: 0, fastTotalWeight: 0, ... };
672
- * // Sample: 1MB in 1 second
673
- * state = sampleBandwidth(state, 1000, 1_000_000);
674
- */
675
- function sampleBandwidth(state, durationMs, numBytes, config = DEFAULT_BANDWIDTH_CONFIG) {
676
- const updatedBytesSampled = state.bytesSampled + numBytes;
677
- if (numBytes < config.minBytes) return {
678
- ...state,
679
- bytesSampled: updatedBytesSampled
680
- };
681
- if (durationMs < config.minDuration) return {
682
- ...state,
683
- bytesSampled: updatedBytesSampled
684
- };
685
- const bandwidth = 8e3 * numBytes / durationMs;
686
- const weight = durationMs / 1e3;
894
+ };
895
+ if (stream.resolution?.width !== void 0) track.width = stream.resolution.width;
896
+ if (stream.resolution?.height !== void 0) track.height = stream.resolution.height;
897
+ if (codecs?.video) track.codecs = [codecs.video];
898
+ if (stream.frameRate) track.frameRate = stream.frameRate;
899
+ if (stream.audioGroupId) track.audioGroupId = stream.audioGroupId;
900
+ return track;
901
+ });
902
+ const audioOnlyTracks = audioOnlyStreams.map((stream) => {
903
+ const codecs = stream.codecs ? parseCodecs(stream.codecs) : void 0;
904
+ return {
905
+ type: "audio",
906
+ id: generateId(),
907
+ url: stream.uri,
908
+ bandwidth: stream.bandwidth,
909
+ mimeType: "audio/mp4",
910
+ codecs: codecs?.audio ? [codecs.audio] : [],
911
+ groupId: stream.audioGroupId || "default",
912
+ name: "Default",
913
+ sampleRate: 48e3,
914
+ channels: 2
915
+ };
916
+ });
917
+ const audioTracks = [...audioRenditions.map((rendition) => {
918
+ let audioCodecs;
919
+ for (const stream of streams) if (stream.audioGroupId === rendition.groupId && stream.codecs) {
920
+ const codecs = parseCodecs(stream.codecs);
921
+ if (codecs.audio) {
922
+ audioCodecs = [codecs.audio];
923
+ break;
924
+ }
925
+ }
926
+ const track = {
927
+ type: "audio",
928
+ id: generateId(),
929
+ url: rendition.uri ?? "",
930
+ groupId: rendition.groupId,
931
+ name: rendition.name,
932
+ mimeType: "audio/mp4",
933
+ bandwidth: 0,
934
+ sampleRate: 48e3,
935
+ channels: 2,
936
+ codecs: []
937
+ };
938
+ if (rendition.language) track.language = rendition.language;
939
+ if (audioCodecs) track.codecs = audioCodecs;
940
+ if (rendition.default) track.default = rendition.default;
941
+ if (rendition.autoselect) track.autoselect = rendition.autoselect;
942
+ return track;
943
+ }), ...audioOnlyTracks];
944
+ const textTracks = subtitleRenditions.map((rendition) => {
945
+ const track = {
946
+ type: "text",
947
+ id: generateId(),
948
+ url: rendition.uri,
949
+ groupId: rendition.groupId,
950
+ label: rendition.name,
951
+ kind: "subtitles",
952
+ mimeType: "text/vtt",
953
+ bandwidth: 0
954
+ };
955
+ if (rendition.language) track.language = rendition.language;
956
+ if (rendition.default && rendition.autoselect) track.default = true;
957
+ if (rendition.autoselect) track.autoselect = rendition.autoselect;
958
+ if (rendition.forced) track.forced = rendition.forced;
959
+ return track;
960
+ });
961
+ const selectionSets = [];
962
+ if (videoTracks.length > 0) {
963
+ const videoSwitchingSet = {
964
+ id: generateId(),
965
+ type: "video",
966
+ tracks: videoTracks
967
+ };
968
+ const videoSelectionSet = {
969
+ id: generateId(),
970
+ type: "video",
971
+ switchingSets: [videoSwitchingSet]
972
+ };
973
+ selectionSets.push(videoSelectionSet);
974
+ }
975
+ if (audioTracks.length > 0) {
976
+ const audioSwitchingSet = {
977
+ id: generateId(),
978
+ type: "audio",
979
+ tracks: audioTracks
980
+ };
981
+ const audioSelectionSet = {
982
+ id: generateId(),
983
+ type: "audio",
984
+ switchingSets: [audioSwitchingSet]
985
+ };
986
+ selectionSets.push(audioSelectionSet);
987
+ }
988
+ if (textTracks.length > 0) {
989
+ const textSwitchingSet = {
990
+ id: generateId(),
991
+ type: "text",
992
+ tracks: textTracks
993
+ };
994
+ const textSelectionSet = {
995
+ id: generateId(),
996
+ type: "text",
997
+ switchingSets: [textSwitchingSet]
998
+ };
999
+ selectionSets.push(textSelectionSet);
1000
+ }
687
1001
  return {
688
- fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),
689
- fastTotalWeight: state.fastTotalWeight + weight,
690
- slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),
691
- slowTotalWeight: state.slowTotalWeight + weight,
692
- bytesSampled: updatedBytesSampled
1002
+ id: generateId(),
1003
+ url: unresolved.url,
1004
+ startTime: 0,
1005
+ selectionSets
693
1006
  };
694
1007
  }
695
- /**
696
- * Get the current bandwidth estimate.
697
- *
698
- * Returns the **minimum** of the fast and slow EWMA estimates.
699
- * This provides the key asymmetric behavior:
700
- * - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)
701
- * - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)
702
- *
703
- * Uses default estimate until enough data has been sampled.
704
- *
705
- * @param state - Current estimator state
706
- * @param defaultEstimate - Fallback estimate before sufficient samples (bps)
707
- * @param config - Optional estimator configuration (uses defaults if not provided)
708
- * @returns Bandwidth estimate in bits per second
709
- *
710
- * @example
711
- * const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default
712
- */
713
- function getBandwidthEstimate(state, defaultEstimate, config = DEFAULT_BANDWIDTH_CONFIG) {
714
- if (state.bytesSampled < config.minTotalBytes) return defaultEstimate;
715
- const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);
716
- const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);
717
- return Math.min(fastEstimate, slowEstimate);
718
- }
1008
+
1009
+ //#endregion
1010
+ //#region ../spf/dist/dev/core/abr/quality-selection.js
719
1011
  /**
720
1012
  * Default quality selection configuration.
721
1013
  * Values match Shaka Player upgrade threshold (0.85 = 15% headroom).
@@ -764,125 +1056,53 @@ function selectQuality(tracks, currentBandwidth, config = DEFAULT_QUALITY_CONFIG
764
1056
  function hasHigherResolution(trackA, trackB) {
765
1057
  return (trackA.width ?? 0) * (trackA.height ?? 0) > (trackB.width ?? 0) * (trackB.height ?? 0);
766
1058
  }
1059
+
1060
+ //#endregion
1061
+ //#region ../spf/dist/dev/core/buffer/back-buffer.js
767
1062
  /**
768
- * Default back buffer configuration.
769
- */
770
- const DEFAULT_BACK_BUFFER_CONFIG = { keepSegments: 2 };
771
- /**
772
- * Calculate back buffer flush point.
773
- *
774
- * Determines where to flush old segments from the back buffer.
775
- * Keeps a fixed number of segments behind the current playback position.
776
- *
777
- * Algorithm:
778
- * 1. Find segments before currentTime
779
- * 2. Count back N segments (keepSegments)
780
- * 3. Return startTime of segment N+1 back (flush everything before this)
781
- *
782
- * @param segments - Available segments (should be sorted by startTime)
783
- * @param currentTime - Current playback position in seconds
784
- * @param config - Optional back buffer configuration
785
- * @returns Time in seconds to flush up to (flush range: [0, flushEnd))
786
- *
787
- * @example
788
- * const segments = [
789
- * { startTime: 0, duration: 6, ... },
790
- * { startTime: 6, duration: 6, ... },
791
- * { startTime: 12, duration: 6, ... },
792
- * { startTime: 18, duration: 6, ... },
793
- * ];
794
- *
795
- * // Playing at 18s, keep 2 segments
796
- * const flushEnd = calculateBackBufferFlushPoint(segments, 18);
797
- * // Returns 6 (flush [0, 6), keep [6-18))
798
- */
799
- function calculateBackBufferFlushPoint(segments, currentTime, config = DEFAULT_BACK_BUFFER_CONFIG) {
800
- if (segments.length === 0) return 0;
801
- const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);
802
- if (segmentsBefore.length === 0) return 0;
803
- const segmentsToFlush = segmentsBefore.length - config.keepSegments;
804
- if (segmentsToFlush <= 0) return 0;
805
- if (segmentsToFlush >= segmentsBefore.length) return currentTime;
806
- return segmentsBefore[segmentsToFlush].startTime;
807
- }
808
- /**
809
- * Default forward buffer configuration.
810
- */
811
- const DEFAULT_FORWARD_BUFFER_CONFIG = { bufferDuration: 30 };
812
- /**
813
- * Get segments that need to be loaded for forward buffer.
814
- *
815
- * Determines which segments to load to maintain target buffer duration.
816
- * Handles discontiguous buffering (gaps after seeks).
817
- *
818
- * Algorithm:
819
- * 1. Calculate target time: currentTime + bufferDuration
820
- * 2. Find all segments in range [currentTime, targetTime)
821
- * 3. Filter out segments already buffered at that time position
822
- * 4. Return segments to load (fills gaps + extends to target)
823
- *
824
- * @param segments - All available segments from playlist
825
- * @param bufferedSegments - Segments already buffered (ordered by startTime)
826
- * @param currentTime - Current playback position in seconds
827
- * @param config - Optional forward buffer configuration
828
- * @returns Array of segments to load (empty if buffer is sufficient)
829
- *
830
- * @example
831
- * // After seek: buffered [0-12, 18-30], playing at 7s
832
- * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });
833
- * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)
834
- */
835
- /**
836
- * Calculate the start time from which to flush forward buffer content.
837
- *
838
- * Content that starts at or beyond `currentTime + bufferDuration` is no
839
- * longer needed for the current playback position and should be removed
840
- * from the SourceBuffer. This prevents unbounded accumulation of scattered
841
- * SourceBuffer content after seeks, which can cause QuotaExceededError on
842
- * long-form content.
843
- *
844
- * Returns `Infinity` when nothing needs flushing (no buffered segments
845
- * exist beyond the threshold).
846
- *
847
- * @param bufferedSegments - Segments currently tracked in the buffer model
848
- * @param currentTime - Current playback position in seconds
849
- * @param config - Optional forward buffer configuration
850
- * @returns Start time to flush from (flush range: [flushStart, Infinity)),
851
- * or Infinity if no flush is needed
852
- *
853
- * @example
854
- * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30
855
- * const flushStart = calculateForwardFlushPoint(segments, 0);
856
- * // Returns 30 — flush [30, Infinity), keep [0, 30)
857
- */
858
- function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
859
- if (bufferedSegments.length === 0) return Infinity;
860
- const threshold = currentTime + config.bufferDuration;
861
- const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);
862
- if (beyond.length === 0) return Infinity;
863
- return Math.min(...beyond.map((seg) => seg.startTime));
864
- }
865
- function getSegmentsToLoad(segments, bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
866
- if (segments.length === 0) return [];
867
- const targetTime = currentTime + config.bufferDuration;
868
- const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));
869
- return segments.filter((seg) => {
870
- const segmentEnd = seg.startTime + seg.duration;
871
- const isInRange = seg.startTime < targetTime && segmentEnd > currentTime;
872
- const isNotBuffered = !bufferedStartTimes.has(seg.startTime);
873
- return isInRange && isNotBuffered;
874
- });
875
- }
876
- function isResolvedTrack(track) {
877
- return "segments" in track;
878
- }
879
- /**
880
- * Check if a presentation has duration (at least one track resolved).
881
- * Narrows type to include required duration.
1063
+ * Default back buffer configuration.
882
1064
  */
883
- function hasPresentationDuration(presentation) {
884
- return presentation.duration !== void 0;
1065
+ const DEFAULT_BACK_BUFFER_CONFIG = { keepSegments: 2 };
1066
+ /**
1067
+ * Calculate back buffer flush point.
1068
+ *
1069
+ * Determines where to flush old segments from the back buffer.
1070
+ * Keeps a fixed number of segments behind the current playback position.
1071
+ *
1072
+ * Algorithm:
1073
+ * 1. Find segments before currentTime
1074
+ * 2. Count back N segments (keepSegments)
1075
+ * 3. Return startTime of segment N+1 back (flush everything before this)
1076
+ *
1077
+ * @param segments - Available segments (should be sorted by startTime)
1078
+ * @param currentTime - Current playback position in seconds
1079
+ * @param config - Optional back buffer configuration
1080
+ * @returns Time in seconds to flush up to (flush range: [0, flushEnd))
1081
+ *
1082
+ * @example
1083
+ * const segments = [
1084
+ * { startTime: 0, duration: 6, ... },
1085
+ * { startTime: 6, duration: 6, ... },
1086
+ * { startTime: 12, duration: 6, ... },
1087
+ * { startTime: 18, duration: 6, ... },
1088
+ * ];
1089
+ *
1090
+ * // Playing at 18s, keep 2 segments
1091
+ * const flushEnd = calculateBackBufferFlushPoint(segments, 18);
1092
+ * // Returns 6 (flush [0, 6), keep [6-18))
1093
+ */
1094
+ function calculateBackBufferFlushPoint(segments, currentTime, config = DEFAULT_BACK_BUFFER_CONFIG) {
1095
+ if (segments.length === 0) return 0;
1096
+ const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);
1097
+ if (segmentsBefore.length === 0) return 0;
1098
+ const segmentsToFlush = segmentsBefore.length - config.keepSegments;
1099
+ if (segmentsToFlush <= 0) return 0;
1100
+ if (segmentsToFlush >= segmentsBefore.length) return currentTime;
1101
+ return segmentsBefore[segmentsToFlush].startTime;
885
1102
  }
1103
+
1104
+ //#endregion
1105
+ //#region ../spf/dist/dev/dom/media/mediasource-setup.js
886
1106
  /**
887
1107
  * MediaSource Setup
888
1108
  *
@@ -943,13 +1163,13 @@ function attachMediaSource(mediaSource, mediaElement) {
943
1163
  if (supportsManagedMediaSource() && mediaSource instanceof ManagedMediaSource) {
944
1164
  mediaElement.disableRemotePlayback = true;
945
1165
  mediaElement.srcObject = mediaSource;
946
- const detach$1 = () => {
1166
+ const detach = () => {
947
1167
  mediaElement.srcObject = null;
948
1168
  mediaElement.load();
949
1169
  };
950
1170
  return {
951
1171
  url: "",
952
- detach: detach$1
1172
+ detach
953
1173
  };
954
1174
  }
955
1175
  const url = URL.createObjectURL(mediaSource);
@@ -1032,99 +1252,9 @@ function isCodecSupported(mimeCodec) {
1032
1252
  if (!supportsMediaSource()) return false;
1033
1253
  return MediaSource.isTypeSupported(mimeCodec);
1034
1254
  }
1035
- const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17;
1036
- /**
1037
- * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an
1038
- * `AsyncIterable<Uint8Array>` that yields chunks no smaller than
1039
- * `minChunkSize` bytes. Smaller network chunks are accumulated and yielded
1040
- * together once the threshold is met. Any remainder is flushed on stream end.
1041
- *
1042
- * Errors from the underlying stream propagate naturally — the reader lock is
1043
- * always released via `finally`.
1044
- */
1045
- var ChunkedStreamIterable = class {
1046
- minChunkSize;
1047
- #readableStream;
1048
- constructor(readableStream, { minChunkSize = DEFAULT_MIN_CHUNK_SIZE } = {}) {
1049
- this.#readableStream = readableStream;
1050
- this.minChunkSize = minChunkSize;
1051
- }
1052
- async *[Symbol.asyncIterator]() {
1053
- let pending;
1054
- const reader = this.#readableStream.getReader();
1055
- try {
1056
- while (true) {
1057
- const { done, value } = await reader.read();
1058
- if (done) {
1059
- if (pending) yield pending;
1060
- break;
1061
- }
1062
- pending = pending ? concat(pending, value) : value;
1063
- if (pending.length >= this.minChunkSize) {
1064
- yield pending;
1065
- pending = void 0;
1066
- }
1067
- }
1068
- } finally {
1069
- reader.releaseLock();
1070
- }
1071
- }
1072
- };
1073
- function concat(a, b) {
1074
- const result = new Uint8Array(a.length + b.length);
1075
- result.set(a);
1076
- result.set(b, a.length);
1077
- return result;
1078
- }
1079
- /**
1080
- * Fetch resolvable from AddressableObject.
1081
- *
1082
- * Handles byte range requests if byteRange is present.
1083
- * Returns native fetch Response for composability (can extract text, stream, etc.).
1084
- *
1085
- * @param addressable - Resource to fetch (url + optional byteRange)
1086
- * @returns Promise resolving to Response
1087
- *
1088
- * @example
1089
- * const response = await fetchResolvable({ url: 'https://example.com/segment.m4s' });
1090
- * const text = await getResponseText(response);
1091
- *
1092
- * @example
1093
- * // With byte range
1094
- * const response = await fetchResolvable({
1095
- * url: 'https://example.com/file.mp4',
1096
- * byteRange: { start: 1000, end: 1999 }
1097
- * });
1098
- */
1099
- async function fetchResolvable(addressable, options) {
1100
- const headers = new Headers(options?.headers);
1101
- if (addressable.byteRange) {
1102
- const { start, end } = addressable.byteRange;
1103
- headers.set("Range", `bytes=${start}-${end}`);
1104
- }
1105
- const request = new Request(addressable.url, {
1106
- method: "GET",
1107
- headers,
1108
- ...options
1109
- });
1110
- return fetch(request);
1111
- }
1112
- /**
1113
- * Extract text from Response.
1114
- *
1115
- * Accepts minimal Response-like object (just needs text() method).
1116
- * Returns promise from response.text().
1117
- *
1118
- * @param response - Response-like object with text() method
1119
- * @returns Promise resolving to text content
1120
- *
1121
- * @example
1122
- * const response = await fetchResolvable(addressable);
1123
- * const text = await getResponseText(response);
1124
- */
1125
- function getResponseText(response) {
1126
- return response.text();
1127
- }
1255
+
1256
+ //#endregion
1257
+ //#region ../spf/dist/dev/core/events/create-event-stream.js
1128
1258
  /**
1129
1259
  * Minimal event stream with Observable-like shape.
1130
1260
  *
@@ -1168,73 +1298,9 @@ function createEventStream() {
1168
1298
  }
1169
1299
  };
1170
1300
  }
1171
- /**
1172
- * Combines multiple Observable sources into a single Observable.
1173
- *
1174
- * Emits an array of latest values whenever any source emits.
1175
- * Only emits after all sources have emitted at least once.
1176
- *
1177
- * Supports selector-based subscriptions (fires only when the selected value
1178
- * changes, per the optional equalityFn) mirroring the createState API.
1179
- *
1180
- * @param sources - Array of Observable sources
1181
- * @returns Combined Observable
1182
- *
1183
- * @example
1184
- * ```ts
1185
- * const state = createState({ count: 0 });
1186
- * const events = createEventStream<Action>();
1187
- *
1188
- * combineLatest([state, events]).subscribe(([state, event]) => {
1189
- * if (event.type === 'PLAY' && state.count > 0) {
1190
- * // React to event + state condition
1191
- * }
1192
- * });
1193
- * ```
1194
- *
1195
- * @example Selector subscription
1196
- * ```ts
1197
- * combineLatest([state, owners]).subscribe(
1198
- * ([s, o]) => deriveKey(s, o),
1199
- * (key) => { ... },
1200
- * { equalityFn: keyEq }
1201
- * );
1202
- * ```
1203
- */
1204
- function combineLatest(sources) {
1205
- const subscribeToSources = (listener) => {
1206
- const latest = new Array(sources.length);
1207
- const hasValue = new Array(sources.length).fill(false);
1208
- const unsubscribers = [];
1209
- for (let i = 0; i < sources.length; i++) {
1210
- const unsubscribe = sources[i].subscribe((value) => {
1211
- latest[i] = value;
1212
- hasValue[i] = true;
1213
- if (hasValue.every((has) => has)) listener([...latest]);
1214
- });
1215
- unsubscribers.push(unsubscribe);
1216
- }
1217
- return () => {
1218
- for (const unsubscribe of unsubscribers) unsubscribe();
1219
- };
1220
- };
1221
- return { subscribe(listenerOrSelector, maybeListener, options) {
1222
- if (maybeListener === void 0) return subscribeToSources(listenerOrSelector);
1223
- const selector = listenerOrSelector;
1224
- const listener = maybeListener;
1225
- const equalityFn = options?.equalityFn ?? Object.is;
1226
- let prevSelected;
1227
- let initialized = false;
1228
- return subscribeToSources((values) => {
1229
- const nextSelected = selector(values);
1230
- if (!initialized || !equalityFn(prevSelected, nextSelected)) {
1231
- prevSelected = nextSelected;
1232
- initialized = true;
1233
- listener(nextSelected);
1234
- }
1235
- });
1236
- } };
1237
- }
1301
+
1302
+ //#endregion
1303
+ //#region ../spf/dist/dev/core/features/resolve-presentation.js
1238
1304
  /**
1239
1305
  * Type guard to check if presentation is unresolved.
1240
1306
  */
@@ -1325,6 +1391,9 @@ function resolvePresentation({ state, events }) {
1325
1391
  cleanup();
1326
1392
  };
1327
1393
  }
1394
+
1395
+ //#endregion
1396
+ //#region ../spf/dist/dev/core/features/quality-switching.js
1328
1397
  /**
1329
1398
  * Default quality switching configuration.
1330
1399
  */
@@ -1385,6 +1454,9 @@ function switchQuality({ state }, config = {}) {
1385
1454
  state.patch({ selectedVideoTrackId: optimal.id });
1386
1455
  });
1387
1456
  }
1457
+
1458
+ //#endregion
1459
+ //#region ../spf/dist/dev/core/utils/track-selection.js
1388
1460
  /**
1389
1461
  * Map track type to selected track ID property key in state.
1390
1462
  */
@@ -1411,6 +1483,9 @@ function getSelectedTrack(state, type) {
1411
1483
  const trackId = state[SelectedTrackIdKeyByType[type]];
1412
1484
  return presentation.selectionSets.find(({ type: selectionSetType }) => selectionSetType === type)?.switchingSets[0]?.tracks.find(({ id }) => id === trackId);
1413
1485
  }
1486
+
1487
+ //#endregion
1488
+ //#region ../spf/dist/dev/dom/features/segment-loader-actor.js
1414
1489
  /**
1415
1490
  * Creates a SegmentLoaderActor for one track type (video or audio).
1416
1491
  *
@@ -1600,6 +1675,9 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1600
1675
  }
1601
1676
  };
1602
1677
  }
1678
+
1679
+ //#endregion
1680
+ //#region ../spf/dist/dev/dom/features/load-segments.js
1603
1681
  const ActorKeyByType = {
1604
1682
  video: "videoBufferActor",
1605
1683
  audio: "audioBufferActor"
@@ -1750,7 +1828,7 @@ function loadSegments({ state, owners }, config) {
1750
1828
  const segmentLoaderActorExists = !!currentSegmentLoader;
1751
1829
  segmentsCanLoad.patch(trackResolved && segmentLoaderActorExists);
1752
1830
  });
1753
- const unsubscribeShouldLoadSegments = combineLatest([segmentsCanLoad, state]).subscribe(([segmentsCanLoad$1, state$1]) => selectLoadingInputs([segmentsCanLoad$1, state$1], type), ({ preload, playbackInitiated, currentTime, track }) => {
1831
+ const unsubscribeShouldLoadSegments = combineLatest([segmentsCanLoad, state]).subscribe(([segmentsCanLoad, state]) => selectLoadingInputs([segmentsCanLoad, state], type), ({ preload, playbackInitiated, currentTime, track }) => {
1754
1832
  if (!(preload === "auto" || !!playbackInitiated))
1755
1833
  /** @ts-expect-error */
1756
1834
  segmentLoader.current?.send({
@@ -1772,6 +1850,9 @@ function loadSegments({ state, owners }, config) {
1772
1850
  unsubActorLifecycle();
1773
1851
  };
1774
1852
  }
1853
+
1854
+ //#endregion
1855
+ //#region ../spf/dist/dev/dom/text/parse-vtt-segment.js
1775
1856
  /**
1776
1857
  * Parse a VTT segment using browser's native parser.
1777
1858
  *
@@ -1823,6 +1904,9 @@ function parseVttSegment(url) {
1823
1904
  function destroyVttParser() {
1824
1905
  dummyVideo = null;
1825
1906
  }
1907
+
1908
+ //#endregion
1909
+ //#region ../spf/dist/dev/dom/features/load-text-track-cues.js
1826
1910
  function isDuplicateCue(cue, textTrack) {
1827
1911
  const { cues } = textTrack;
1828
1912
  if (!cues) return false;
@@ -1964,6 +2048,9 @@ function loadTextTrackCues({ state, owners }) {
1964
2048
  cleanup();
1965
2049
  };
1966
2050
  }
2051
+
2052
+ //#endregion
2053
+ //#region ../spf/dist/dev/dom/features/track-current-time.js
1967
2054
  /**
1968
2055
  * Track current playback position from the media element.
1969
2056
  *
@@ -2003,6 +2090,9 @@ function trackCurrentTime({ state, owners }) {
2003
2090
  unsubscribe();
2004
2091
  };
2005
2092
  }
2093
+
2094
+ //#endregion
2095
+ //#region ../spf/dist/dev/dom/features/track-playback-initiated.js
2006
2096
  /**
2007
2097
  * Track whether playback has been initiated by the user.
2008
2098
  *
@@ -2051,6 +2141,9 @@ function trackPlaybackInitiated({ state, owners, events }) {
2051
2141
  unsubscribeOwners();
2052
2142
  };
2053
2143
  }
2144
+
2145
+ //#endregion
2146
+ //#region ../spf/dist/dev/dom/media/append-segment.js
2054
2147
  /**
2055
2148
  * Append media data to a SourceBuffer.
2056
2149
  *
@@ -2106,6 +2199,9 @@ async function appendChunk(sourceBuffer, data) {
2106
2199
  }
2107
2200
  });
2108
2201
  }
2202
+
2203
+ //#endregion
2204
+ //#region ../spf/dist/dev/dom/media/buffer-flusher.js
2109
2205
  /**
2110
2206
  * Buffer flusher helper (P12)
2111
2207
  *
@@ -2156,6 +2252,9 @@ async function flushBuffer(sourceBuffer, start, end) {
2156
2252
  }
2157
2253
  });
2158
2254
  }
2255
+
2256
+ //#endregion
2257
+ //#region ../spf/dist/dev/core/features/calculate-presentation-duration.js
2159
2258
  /**
2160
2259
  * Check if we can calculate presentation duration (have required data).
2161
2260
  */
@@ -2198,6 +2297,9 @@ function calculatePresentationDuration({ state }) {
2198
2297
  } });
2199
2298
  });
2200
2299
  }
2300
+
2301
+ //#endregion
2302
+ //#region ../spf/dist/dev/core/task.js
2201
2303
  /**
2202
2304
  * Generic reusable task that wraps an async run function.
2203
2305
  *
@@ -2313,6 +2415,9 @@ var SerialRunner = class {
2313
2415
  this.abortAll();
2314
2416
  }
2315
2417
  };
2418
+
2419
+ //#endregion
2420
+ //#region ../spf/dist/dev/core/features/resolve-track.js
2316
2421
  function canResolve(state, config) {
2317
2422
  const track = getSelectedTrack(state, config.type);
2318
2423
  if (!track) return false;
@@ -2378,6 +2483,9 @@ function resolveTrack({ state, events }, config) {
2378
2483
  cleanup();
2379
2484
  };
2380
2485
  }
2486
+
2487
+ //#endregion
2488
+ //#region ../spf/dist/dev/core/features/select-tracks.js
2381
2489
  /**
2382
2490
  * Pick text track to activate.
2383
2491
  *
@@ -2518,6 +2626,9 @@ function selectTextTrack({ state }, config = { type: "text" }) {
2518
2626
  }
2519
2627
  });
2520
2628
  }
2629
+
2630
+ //#endregion
2631
+ //#region ../spf/dist/dev/dom/features/end-of-stream.js
2521
2632
  /**
2522
2633
  * Check if the last segment of a track has been appended to a SourceBuffer.
2523
2634
  *
@@ -2678,6 +2789,9 @@ function endOfStream({ state, owners }) {
2678
2789
  cleanupCombineLatest();
2679
2790
  };
2680
2791
  }
2792
+
2793
+ //#endregion
2794
+ //#region ../spf/dist/dev/dom/features/setup-mediasource.js
2681
2795
  /**
2682
2796
  * Check if we have the minimum requirements to create MediaSource.
2683
2797
  */
@@ -2724,6 +2838,9 @@ function setupMediaSource({ state, owners }) {
2724
2838
  unsubscribe();
2725
2839
  };
2726
2840
  }
2841
+
2842
+ //#endregion
2843
+ //#region ../spf/dist/dev/dom/media/source-buffer-actor.js
2727
2844
  /**
2728
2845
  * Thrown when a message is sent to the actor in a state that does not
2729
2846
  * accept messages (currently: 'updating').
@@ -2900,6 +3017,9 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
2900
3017
  }
2901
3018
  };
2902
3019
  }
3020
+
3021
+ //#endregion
3022
+ //#region ../spf/dist/dev/dom/features/setup-sourcebuffer.js
2903
3023
  /**
2904
3024
  * Build MIME codec string from track metadata.
2905
3025
  *
@@ -2958,6 +3078,9 @@ function setupSourceBuffers({ state, owners }) {
2958
3078
  await new Promise((resolve) => requestAnimationFrame(resolve));
2959
3079
  });
2960
3080
  }
3081
+
3082
+ //#endregion
3083
+ //#region ../spf/dist/dev/dom/features/setup-text-tracks.js
2961
3084
  /**
2962
3085
  * Get all text tracks from presentation.
2963
3086
  */
@@ -3039,6 +3162,9 @@ function setupTextTracks({ state, owners }) {
3039
3162
  unsubscribe();
3040
3163
  };
3041
3164
  }
3165
+
3166
+ //#endregion
3167
+ //#region ../spf/dist/dev/dom/features/sync-selected-text-track-from-dom.js
3042
3168
  /**
3043
3169
  * Sync selectedTextTrackId from DOM text track mode changes.
3044
3170
  *
@@ -3095,6 +3221,9 @@ function syncSelectedTextTrackFromDom({ state, owners }) {
3095
3221
  unsubscribe();
3096
3222
  };
3097
3223
  }
3224
+
3225
+ //#endregion
3226
+ //#region ../spf/dist/dev/dom/features/sync-text-track-modes.js
3098
3227
  /**
3099
3228
  * Check if we can sync text track modes.
3100
3229
  *
@@ -3126,6 +3255,9 @@ function syncTextTrackModes({ state, owners }) {
3126
3255
  else trackElement.track.mode = "hidden";
3127
3256
  });
3128
3257
  }
3258
+
3259
+ //#endregion
3260
+ //#region ../spf/dist/dev/dom/features/update-duration.js
3129
3261
  /**
3130
3262
  * Check if we can update MediaSource duration (have required data).
3131
3263
  */
@@ -3190,6 +3322,9 @@ function updateDuration({ state, owners }) {
3190
3322
  unsubscribe();
3191
3323
  };
3192
3324
  }
3325
+
3326
+ //#endregion
3327
+ //#region ../spf/dist/dev/dom/playback-engine/engine.js
3193
3328
  /**
3194
3329
  * Create a POC playback engine.
3195
3330
  *
@@ -3341,6 +3476,9 @@ function createPlaybackEngine(config = {}) {
3341
3476
  }
3342
3477
  };
3343
3478
  }
3479
+
3480
+ //#endregion
3481
+ //#region ../spf/dist/dev/dom/playback-engine/adapter.js
3344
3482
  /**
3345
3483
  * HTMLMediaElement-compatible adapter for the SPF playback engine.
3346
3484
  *