@videojs/html 10.0.0-beta.2 → 10.0.0-beta.4

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 (175) hide show
  1. package/cdn/audio-minimal.css +1 -1
  2. package/cdn/audio-minimal.dev.js +197 -126
  3. package/cdn/audio-minimal.dev.js.map +1 -1
  4. package/cdn/audio-minimal.js +5 -5
  5. package/cdn/audio-minimal.js.map +1 -1
  6. package/cdn/audio.css +1 -1
  7. package/cdn/audio.dev.js +191 -120
  8. package/cdn/audio.dev.js.map +1 -1
  9. package/cdn/audio.js +5 -5
  10. package/cdn/audio.js.map +1 -1
  11. package/cdn/background.dev.js +40 -25
  12. package/cdn/background.dev.js.map +1 -1
  13. package/cdn/background.js +4 -4
  14. package/cdn/background.js.map +1 -1
  15. package/cdn/media/hls-video.dev.js +0 -1
  16. package/cdn/media/hls-video.dev.js.map +1 -1
  17. package/cdn/media/hls-video.js +1 -1
  18. package/cdn/media/hls-video.js.map +1 -1
  19. package/cdn/media/simple-hls-video.dev.js +178 -158
  20. package/cdn/media/simple-hls-video.dev.js.map +1 -1
  21. package/cdn/media/simple-hls-video.js +1 -1
  22. package/cdn/media/simple-hls-video.js.map +1 -1
  23. package/cdn/video-minimal.css +1 -1
  24. package/cdn/video-minimal.dev.js +220 -161
  25. package/cdn/video-minimal.dev.js.map +1 -1
  26. package/cdn/video-minimal.js +5 -5
  27. package/cdn/video-minimal.js.map +1 -1
  28. package/cdn/video.css +1 -1
  29. package/cdn/video.dev.js +217 -158
  30. package/cdn/video.dev.js.map +1 -1
  31. package/cdn/video.js +4 -4
  32. package/cdn/video.js.map +1 -1
  33. package/dist/default/_virtual/inline-css_src/define/audio/minimal-skin.js +1 -1
  34. package/dist/default/_virtual/inline-css_src/define/audio/minimal-skin.js.map +1 -1
  35. package/dist/default/_virtual/inline-css_src/define/audio/skin.js +1 -1
  36. package/dist/default/_virtual/inline-css_src/define/audio/skin.js.map +1 -1
  37. package/dist/default/_virtual/inline-css_src/define/base.js +6 -0
  38. package/dist/default/_virtual/inline-css_src/define/base.js.map +1 -0
  39. package/dist/default/_virtual/inline-css_src/define/video/minimal-skin.js +1 -1
  40. package/dist/default/_virtual/inline-css_src/define/video/minimal-skin.js.map +1 -1
  41. package/dist/default/_virtual/inline-css_src/define/video/skin.js +1 -1
  42. package/dist/default/_virtual/inline-css_src/define/video/skin.js.map +1 -1
  43. package/dist/default/define/audio/minimal-skin.css +8 -2
  44. package/dist/default/define/audio/minimal-skin.js +2 -1
  45. package/dist/default/define/audio/minimal-skin.js.map +1 -1
  46. package/dist/default/define/audio/minimal-skin.tailwind.js +5 -2
  47. package/dist/default/define/audio/minimal-skin.tailwind.js.map +1 -1
  48. package/dist/default/define/audio/skin.css +7 -5
  49. package/dist/default/define/audio/skin.js +2 -1
  50. package/dist/default/define/audio/skin.js.map +1 -1
  51. package/dist/default/define/audio/skin.tailwind.js +5 -2
  52. package/dist/default/define/audio/skin.tailwind.js.map +1 -1
  53. package/dist/default/define/base.css +25 -0
  54. package/dist/default/define/shared.css +3 -0
  55. package/dist/default/define/skin-mixin.js +10 -18
  56. package/dist/default/define/skin-mixin.js.map +1 -1
  57. package/dist/default/define/video/minimal-skin.css +35 -73
  58. package/dist/default/define/video/minimal-skin.js +2 -1
  59. package/dist/default/define/video/minimal-skin.js.map +1 -1
  60. package/dist/default/define/video/minimal-skin.tailwind.js +4 -4
  61. package/dist/default/define/video/minimal-skin.tailwind.js.map +1 -1
  62. package/dist/default/define/video/skin.css +32 -71
  63. package/dist/default/define/video/skin.js +2 -1
  64. package/dist/default/define/video/skin.js.map +1 -1
  65. package/dist/default/define/video/skin.tailwind.js +5 -4
  66. package/dist/default/define/video/skin.tailwind.js.map +1 -1
  67. package/dist/default/skins/dist/default/default/tailwind/audio.tailwind.js +3 -21
  68. package/dist/default/skins/dist/default/default/tailwind/audio.tailwind.js.map +1 -1
  69. package/dist/default/skins/dist/default/default/tailwind/components/overlay.js +1 -1
  70. package/dist/default/skins/dist/default/default/tailwind/components/overlay.js.map +1 -1
  71. package/dist/default/skins/dist/default/default/tailwind/components/root.js +1 -1
  72. package/dist/default/skins/dist/default/default/tailwind/components/root.js.map +1 -1
  73. package/dist/default/skins/dist/default/default/tailwind/video.tailwind.js +8 -5
  74. package/dist/default/skins/dist/default/default/tailwind/video.tailwind.js.map +1 -1
  75. package/dist/default/skins/dist/default/minimal/tailwind/audio.tailwind.js +3 -22
  76. package/dist/default/skins/dist/default/minimal/tailwind/audio.tailwind.js.map +1 -1
  77. package/dist/default/skins/dist/default/minimal/tailwind/components/overlay.js +1 -1
  78. package/dist/default/skins/dist/default/minimal/tailwind/components/overlay.js.map +1 -1
  79. package/dist/default/skins/dist/default/minimal/tailwind/components/popup.js +1 -1
  80. package/dist/default/skins/dist/default/minimal/tailwind/components/popup.js.map +1 -1
  81. package/dist/default/skins/dist/default/minimal/tailwind/video.tailwind.js +6 -4
  82. package/dist/default/skins/dist/default/minimal/tailwind/video.tailwind.js.map +1 -1
  83. package/dist/default/skins/dist/default/{default/tailwind/components → shared/tailwind}/icon-state.js +1 -1
  84. package/dist/default/skins/dist/default/shared/tailwind/icon-state.js.map +1 -0
  85. package/dist/{dev/skins/dist/default/default/tailwind/components → default/skins/dist/default/shared/tailwind}/tooltip-state.js +1 -1
  86. package/dist/default/skins/dist/default/shared/tailwind/tooltip-state.js.map +1 -0
  87. package/dist/default/store/container-mixin.js +22 -10
  88. package/dist/default/store/container-mixin.js.map +1 -1
  89. package/dist/default/ui/tooltip/tooltip-group-element.js +4 -1
  90. package/dist/default/ui/tooltip/tooltip-group-element.js.map +1 -1
  91. package/dist/dev/_virtual/inline-css_src/define/audio/minimal-skin.js +1 -1
  92. package/dist/dev/_virtual/inline-css_src/define/audio/minimal-skin.js.map +1 -1
  93. package/dist/dev/_virtual/inline-css_src/define/audio/skin.js +1 -1
  94. package/dist/dev/_virtual/inline-css_src/define/audio/skin.js.map +1 -1
  95. package/dist/dev/_virtual/inline-css_src/define/base.js +6 -0
  96. package/dist/dev/_virtual/inline-css_src/define/base.js.map +1 -0
  97. package/dist/dev/_virtual/inline-css_src/define/video/minimal-skin.js +1 -1
  98. package/dist/dev/_virtual/inline-css_src/define/video/minimal-skin.js.map +1 -1
  99. package/dist/dev/_virtual/inline-css_src/define/video/skin.js +1 -1
  100. package/dist/dev/_virtual/inline-css_src/define/video/skin.js.map +1 -1
  101. package/dist/dev/define/audio/minimal-skin.css +8 -2
  102. package/dist/dev/define/audio/minimal-skin.d.ts.map +1 -1
  103. package/dist/dev/define/audio/minimal-skin.js +67 -64
  104. package/dist/dev/define/audio/minimal-skin.js.map +1 -1
  105. package/dist/dev/define/audio/minimal-skin.tailwind.d.ts.map +1 -1
  106. package/dist/dev/define/audio/minimal-skin.tailwind.js +71 -66
  107. package/dist/dev/define/audio/minimal-skin.tailwind.js.map +1 -1
  108. package/dist/dev/define/audio/skin.css +7 -5
  109. package/dist/dev/define/audio/skin.d.ts.map +1 -1
  110. package/dist/dev/define/audio/skin.js +59 -56
  111. package/dist/dev/define/audio/skin.js.map +1 -1
  112. package/dist/dev/define/audio/skin.tailwind.d.ts.map +1 -1
  113. package/dist/dev/define/audio/skin.tailwind.js +64 -59
  114. package/dist/dev/define/audio/skin.tailwind.js.map +1 -1
  115. package/dist/dev/define/base.css +25 -0
  116. package/dist/dev/define/shared.css +3 -0
  117. package/dist/dev/define/skin-mixin.d.ts +2 -2
  118. package/dist/dev/define/skin-mixin.d.ts.map +1 -1
  119. package/dist/dev/define/skin-mixin.js +10 -32
  120. package/dist/dev/define/skin-mixin.js.map +1 -1
  121. package/dist/dev/define/video/minimal-skin.css +35 -73
  122. package/dist/dev/define/video/minimal-skin.d.ts.map +1 -1
  123. package/dist/dev/define/video/minimal-skin.js +92 -101
  124. package/dist/dev/define/video/minimal-skin.js.map +1 -1
  125. package/dist/dev/define/video/minimal-skin.tailwind.d.ts.map +1 -1
  126. package/dist/dev/define/video/minimal-skin.tailwind.js +98 -108
  127. package/dist/dev/define/video/minimal-skin.tailwind.js.map +1 -1
  128. package/dist/dev/define/video/skin.css +32 -71
  129. package/dist/dev/define/video/skin.d.ts.map +1 -1
  130. package/dist/dev/define/video/skin.js +82 -91
  131. package/dist/dev/define/video/skin.js.map +1 -1
  132. package/dist/dev/define/video/skin.tailwind.d.ts.map +1 -1
  133. package/dist/dev/define/video/skin.tailwind.js +93 -102
  134. package/dist/dev/define/video/skin.tailwind.js.map +1 -1
  135. package/dist/dev/skins/dist/default/default/tailwind/audio.tailwind.js +3 -21
  136. package/dist/dev/skins/dist/default/default/tailwind/audio.tailwind.js.map +1 -1
  137. package/dist/dev/skins/dist/default/default/tailwind/components/overlay.js +1 -1
  138. package/dist/dev/skins/dist/default/default/tailwind/components/overlay.js.map +1 -1
  139. package/dist/dev/skins/dist/default/default/tailwind/components/root.js +1 -1
  140. package/dist/dev/skins/dist/default/default/tailwind/components/root.js.map +1 -1
  141. package/dist/dev/skins/dist/default/default/tailwind/video.tailwind.js +8 -5
  142. package/dist/dev/skins/dist/default/default/tailwind/video.tailwind.js.map +1 -1
  143. package/dist/dev/skins/dist/default/minimal/tailwind/audio.tailwind.js +3 -22
  144. package/dist/dev/skins/dist/default/minimal/tailwind/audio.tailwind.js.map +1 -1
  145. package/dist/dev/skins/dist/default/minimal/tailwind/components/overlay.js +1 -1
  146. package/dist/dev/skins/dist/default/minimal/tailwind/components/overlay.js.map +1 -1
  147. package/dist/dev/skins/dist/default/minimal/tailwind/components/popup.js +1 -1
  148. package/dist/dev/skins/dist/default/minimal/tailwind/components/popup.js.map +1 -1
  149. package/dist/dev/skins/dist/default/minimal/tailwind/video.tailwind.js +6 -4
  150. package/dist/dev/skins/dist/default/minimal/tailwind/video.tailwind.js.map +1 -1
  151. package/dist/dev/skins/dist/default/{default/tailwind/components → shared/tailwind}/icon-state.js +1 -1
  152. package/dist/dev/skins/dist/default/shared/tailwind/icon-state.js.map +1 -0
  153. package/dist/{default/skins/dist/default/minimal/tailwind/components → dev/skins/dist/default/shared/tailwind}/tooltip-state.js +1 -1
  154. package/dist/dev/skins/dist/default/shared/tailwind/tooltip-state.js.map +1 -0
  155. package/dist/dev/store/container-mixin.js +22 -10
  156. package/dist/dev/store/container-mixin.js.map +1 -1
  157. package/dist/dev/ui/tooltip/tooltip-group-element.js +4 -1
  158. package/dist/dev/ui/tooltip/tooltip-group-element.js.map +1 -1
  159. package/package.json +7 -7
  160. package/dist/default/skins/dist/default/default/tailwind/components/icon-state.js.map +0 -1
  161. package/dist/default/skins/dist/default/default/tailwind/components/tooltip-state.js +0 -28
  162. package/dist/default/skins/dist/default/default/tailwind/components/tooltip-state.js.map +0 -1
  163. package/dist/default/skins/dist/default/minimal/tailwind/components/error.js +0 -15
  164. package/dist/default/skins/dist/default/minimal/tailwind/components/error.js.map +0 -1
  165. package/dist/default/skins/dist/default/minimal/tailwind/components/icon-state.js +0 -29
  166. package/dist/default/skins/dist/default/minimal/tailwind/components/icon-state.js.map +0 -1
  167. package/dist/default/skins/dist/default/minimal/tailwind/components/tooltip-state.js.map +0 -1
  168. package/dist/dev/skins/dist/default/default/tailwind/components/icon-state.js.map +0 -1
  169. package/dist/dev/skins/dist/default/default/tailwind/components/tooltip-state.js.map +0 -1
  170. package/dist/dev/skins/dist/default/minimal/tailwind/components/error.js +0 -15
  171. package/dist/dev/skins/dist/default/minimal/tailwind/components/error.js.map +0 -1
  172. package/dist/dev/skins/dist/default/minimal/tailwind/components/icon-state.js +0 -29
  173. package/dist/dev/skins/dist/default/minimal/tailwind/components/icon-state.js.map +0 -1
  174. package/dist/dev/skins/dist/default/minimal/tailwind/components/tooltip-state.js +0 -28
  175. package/dist/dev/skins/dist/default/minimal/tailwind/components/tooltip-state.js.map +0 -1
@@ -320,7 +320,6 @@ function CustomMediaMixin(superclass, { tag, is }) {
320
320
  }
321
321
  connectedCallback() {
322
322
  this.#init();
323
- this.setAttribute("data-media-element", "");
324
323
  }
325
324
  };
326
325
  }
@@ -368,15 +367,12 @@ function listen(target, type, listener, options) {
368
367
 
369
368
  //#endregion
370
369
  //#region ../utils/dist/predicate/predicate.js
371
- function isUndefined(value) {
372
- return typeof value === "undefined";
373
- }
374
370
  function isNil(value) {
375
371
  return value == null;
376
372
  }
377
373
 
378
374
  //#endregion
379
- //#region ../spf/dist/adapter-CMw-rvbk.js
375
+ //#region ../spf/dist/adapter-ClnRGbQf.js
380
376
  /**
381
377
  * Reactive state container with selectors, custom equality, and batched updates.
382
378
  *
@@ -1405,6 +1401,50 @@ function isCodecSupported(mimeCodec) {
1405
1401
  if (!supportsMediaSource()) return false;
1406
1402
  return MediaSource.isTypeSupported(mimeCodec);
1407
1403
  }
1404
+ const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17;
1405
+ /**
1406
+ * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an
1407
+ * `AsyncIterable<Uint8Array>` that yields chunks no smaller than
1408
+ * `minChunkSize` bytes. Smaller network chunks are accumulated and yielded
1409
+ * together once the threshold is met. Any remainder is flushed on stream end.
1410
+ *
1411
+ * Errors from the underlying stream propagate naturally — the reader lock is
1412
+ * always released via `finally`.
1413
+ */
1414
+ var ChunkedStreamIterable = class {
1415
+ minChunkSize;
1416
+ #readableStream;
1417
+ constructor(readableStream, { minChunkSize = DEFAULT_MIN_CHUNK_SIZE } = {}) {
1418
+ this.#readableStream = readableStream;
1419
+ this.minChunkSize = minChunkSize;
1420
+ }
1421
+ async *[Symbol.asyncIterator]() {
1422
+ let pending;
1423
+ const reader = this.#readableStream.getReader();
1424
+ try {
1425
+ while (true) {
1426
+ const { done, value } = await reader.read();
1427
+ if (done) {
1428
+ if (pending) yield pending;
1429
+ break;
1430
+ }
1431
+ pending = pending ? concat(pending, value) : value;
1432
+ if (pending.length >= this.minChunkSize) {
1433
+ yield pending;
1434
+ pending = void 0;
1435
+ }
1436
+ }
1437
+ } finally {
1438
+ reader.releaseLock();
1439
+ }
1440
+ }
1441
+ };
1442
+ function concat(a, b) {
1443
+ const result = new Uint8Array(a.length + b.length);
1444
+ result.set(a);
1445
+ result.set(b, a.length);
1446
+ return result;
1447
+ }
1408
1448
  /**
1409
1449
  * Fetch resolvable from AddressableObject.
1410
1450
  *
@@ -1439,16 +1479,6 @@ async function fetchResolvable(addressable, options) {
1439
1479
  return fetch(request);
1440
1480
  }
1441
1481
  /**
1442
- * Fetch resolvable as bytes.
1443
- *
1444
- * Convenience wrapper around fetchResolvable that resolves the body as an
1445
- * ArrayBuffer. Use this when you need the raw bytes (e.g. segment appends).
1446
- * For text or streaming consumption, use fetchResolvable directly.
1447
- */
1448
- async function fetchResolvableBytes(addressable, options) {
1449
- return (await fetchResolvable(addressable, options)).arrayBuffer();
1450
- }
1451
- /**
1452
1482
  * Extract text from Response.
1453
1483
  *
1454
1484
  * Accepts minimal Response-like object (just needs text() method).
@@ -1732,14 +1762,6 @@ const SelectedTrackIdKeyByType = {
1732
1762
  text: "selectedTextTrackId"
1733
1763
  };
1734
1764
  /**
1735
- * Map track type to buffer owner property key.
1736
- * Used for SourceBuffer references in owners.
1737
- */
1738
- const BufferKeyByType = {
1739
- video: "videoBuffer",
1740
- audio: "audioBuffer"
1741
- };
1742
- /**
1743
1765
  * Get selected track from state by type.
1744
1766
  * Returns properly typed track (partially or fully resolved) or undefined.
1745
1767
  * Type parameter T is inferred from the type argument.
@@ -1770,7 +1792,9 @@ function getSelectedTrack(state, type) {
1770
1792
  * operation (if still needed) or preempts it.
1771
1793
  *
1772
1794
  * @param sourceBufferActor - Shared SourceBufferActor reference (not owned)
1773
- * @param fetchBytes - Tracked fetch closure (owns throughput sampling)
1795
+ * @param fetchBytes - Tracked fetch closure (owns throughput sampling for segments).
1796
+ * Accepts an optional `minChunkSize` in options; init segments pass `Infinity`
1797
+ * so the entire body accumulates as one chunk before appending.
1774
1798
  */
1775
1799
  function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1776
1800
  let pendingTasks = null;
@@ -1780,7 +1804,7 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1780
1804
  let running = false;
1781
1805
  let destroyed = false;
1782
1806
  const getBufferedSegments = (allSegments) => {
1783
- const bufferedIds = new Set(sourceBufferActor.snapshot.context.segments.map((s) => s.id));
1807
+ const bufferedIds = new Set(sourceBufferActor.snapshot.context.segments.filter((s) => !s.partial).map((s) => s.id));
1784
1808
  return allSegments.filter((s) => bufferedIds.has(s.id));
1785
1809
  };
1786
1810
  /**
@@ -1821,12 +1845,14 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1821
1845
  if (actorCtx.initTrackId !== track.id) tasks.push({
1822
1846
  type: "append-init",
1823
1847
  meta: { trackId: track.id },
1824
- url: track.initialization.url
1848
+ url: track.initialization.url,
1849
+ ...track.initialization.byteRange !== void 0 && { byteRange: track.initialization.byteRange }
1825
1850
  });
1826
1851
  if (range) {
1827
1852
  const EPSILON = 1e-4;
1828
1853
  const segmentsToLoad = getSegmentsToLoad(track.segments, bufferedSegments, currentTime).filter((seg) => {
1829
1854
  const existing = actorCtx.segments.find((s) => Math.abs(s.startTime - seg.startTime) < EPSILON);
1855
+ if (existing?.partial) return true;
1830
1856
  if (!existing?.trackBandwidth || !track.bandwidth) return true;
1831
1857
  return track.bandwidth > existing.trackBandwidth;
1832
1858
  });
@@ -1839,7 +1865,8 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1839
1865
  trackId: track.id,
1840
1866
  trackBandwidth: track.bandwidth
1841
1867
  },
1842
- url: segment.url
1868
+ url: segment.url,
1869
+ ...segment.byteRange !== void 0 && { byteRange: segment.byteRange }
1843
1870
  });
1844
1871
  }
1845
1872
  return tasks;
@@ -1861,7 +1888,10 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1861
1888
  if (task.type === "append-init") {
1862
1889
  inFlightInitTrackId = task.meta.trackId;
1863
1890
  if (!signal.aborted) {
1864
- const data = await fetchBytes(task, { signal });
1891
+ const data = await fetchBytes(task, {
1892
+ signal,
1893
+ minChunkSize: Infinity
1894
+ });
1865
1895
  const isTrackSwitch = pendingTasks?.some((t) => t.type === "append-init" && t.meta.trackId !== task.meta.trackId);
1866
1896
  if (!signal.aborted || !isTrackSwitch) {
1867
1897
  const appendSignal = signal.aborted ? new AbortController().signal : signal;
@@ -1876,10 +1906,10 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1876
1906
  }
1877
1907
  inFlightSegmentId = task.meta.id;
1878
1908
  if (!signal.aborted) {
1879
- const data = await fetchBytes(task, { signal });
1909
+ const stream = await fetchBytes(task, { signal });
1880
1910
  if (!signal.aborted) await sourceBufferActor.send({
1881
1911
  type: "append-segment",
1882
- data,
1912
+ data: stream,
1883
1913
  meta: task.meta
1884
1914
  }, signal);
1885
1915
  }
@@ -1938,32 +1968,42 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes) {
1938
1968
  }
1939
1969
  };
1940
1970
  }
1941
- const ActorKeyByType$1 = {
1971
+ const ActorKeyByType = {
1942
1972
  video: "videoBufferActor",
1943
1973
  audio: "audioBufferActor"
1944
1974
  };
1945
- /**
1946
- * Creates a fetch function that transparently samples bandwidth after each
1947
- * completed request. Callers receive bytes; throughput tracking is invisible.
1948
- *
1949
- * `onSample` is an optional callback invoked after each sample is recorded,
1950
- * used for bridging throughput state outward (e.g. migration bridge to global
1951
- * state). A callback is used rather than a subscription so that no immediate
1952
- * fire occurs at setup time — subscriptions fire on registration and would
1953
- * trigger spurious state changes before any work has started.
1954
- */
1955
1975
  function createTrackedFetch(throughput, onSample) {
1956
1976
  return async (addressable, options) => {
1957
- const start = performance.now();
1958
- const data = await fetchResolvableBytes(addressable, options);
1959
- const elapsed = performance.now() - start;
1960
- const next = sampleBandwidth(throughput.current, elapsed, data.byteLength);
1961
- throughput.patch(next);
1962
- throughput.flush();
1963
- onSample?.(next);
1964
- return data;
1977
+ const { minChunkSize, ...fetchOptions } = options ?? {};
1978
+ const response = await fetchResolvable(addressable, fetchOptions);
1979
+ if (!response.body) throw new Error("Response has no body");
1980
+ const body = response.body;
1981
+ return { [Symbol.asyncIterator]: async function* () {
1982
+ let chunkStart = performance.now();
1983
+ for await (const chunk of new ChunkedStreamIterable(body, ...minChunkSize !== void 0 ? [{ minChunkSize }] : [])) {
1984
+ const elapsed = performance.now() - chunkStart;
1985
+ const next = sampleBandwidth(throughput.current, elapsed, chunk.byteLength);
1986
+ throughput.patch(next);
1987
+ throughput.flush();
1988
+ onSample?.(next);
1989
+ yield chunk;
1990
+ chunkStart = performance.now();
1991
+ }
1992
+ } };
1965
1993
  };
1966
1994
  }
1995
+ /**
1996
+ * Non-tracking fetch: eagerly starts the request and returns the response body
1997
+ * as a lazy chunk iterable. Used for audio tracks which don't sample bandwidth.
1998
+ * Pass `minChunkSize: Infinity` to accumulate the full body as a single chunk
1999
+ * (equivalent to arrayBuffer() but through the same streaming path).
2000
+ */
2001
+ async function fetchStream(addressable, options) {
2002
+ const { minChunkSize, ...fetchOptions } = options ?? {};
2003
+ const response = await fetchResolvable(addressable, fetchOptions);
2004
+ if (!response.body) throw new Error("Response has no body");
2005
+ return new ChunkedStreamIterable(response.body, ...minChunkSize !== void 0 ? [{ minChunkSize }] : []);
2006
+ }
1967
2007
  function selectLoadingInputs([segmentsCanLoad, state], type) {
1968
2008
  const { playbackInitiated, preload, currentTime } = state;
1969
2009
  return {
@@ -2046,7 +2086,7 @@ function loadingInputsEq(prevState, curState) {
2046
2086
  */
2047
2087
  function loadSegments({ state, owners }, config) {
2048
2088
  const { type } = config;
2049
- const actorKey = ActorKeyByType$1[type];
2089
+ const actorKey = ActorKeyByType[type];
2050
2090
  const initialBandwidth = state.current.bandwidthState;
2051
2091
  const throughput = createState(initialBandwidth ?? {
2052
2092
  fastEstimate: 0,
@@ -2058,7 +2098,7 @@ function loadSegments({ state, owners }, config) {
2058
2098
  const fetchBytes = type === "video" ? createTrackedFetch(throughput, initialBandwidth !== void 0 ? (next) => {
2059
2099
  state.patch({ bandwidthState: next });
2060
2100
  state.flush();
2061
- } : void 0) : fetchResolvableBytes;
2101
+ } : void 0) : fetchStream;
2062
2102
  const segmentLoader = createState(void 0);
2063
2103
  const unsubActorLifecycle = owners.subscribe((o) => o[actorKey], (actor) => {
2064
2104
  if (actor) segmentLoader.patch(createSegmentLoaderActor(actor, fetchBytes));
@@ -2380,25 +2420,23 @@ function trackPlaybackInitiated({ state, owners, events }) {
2380
2420
  };
2381
2421
  }
2382
2422
  /**
2383
- * Segment appender helper (P11)
2384
- *
2385
- * Appends media segments (ArrayBuffer) to SourceBuffer.
2386
- */
2387
- /**
2388
- * Append a media segment to a SourceBuffer.
2389
- *
2390
- * Waits for the SourceBuffer to be ready (not updating), then appends
2391
- * the segment data. Returns a promise that resolves when append completes.
2423
+ * Append media data to a SourceBuffer.
2392
2424
  *
2393
- * @param sourceBuffer - The SourceBuffer to append to
2394
- * @param segmentData - The segment data as ArrayBuffer
2395
- * @returns Promise that resolves when append completes
2425
+ * Accepts either a full ArrayBuffer (single append) or an AsyncIterable of
2426
+ * Uint8Array chunks (one append per chunk, in order). Waits for `updateend`
2427
+ * between each call so appends are serialized correctly.
2396
2428
  *
2397
- * @example
2398
- * const data = await fetch(segmentUrl).then(r => r.arrayBuffer());
2399
- * await appendSegment(videoSourceBuffer, data);
2429
+ * Errors from the SourceBuffer (`error` event) or from the iterable are
2430
+ * propagated as rejections.
2400
2431
  */
2401
- async function appendSegment(sourceBuffer, segmentData) {
2432
+ async function appendSegment(sourceBuffer, data, signal) {
2433
+ if (data instanceof ArrayBuffer) await appendChunk(sourceBuffer, data);
2434
+ else for await (const chunk of data) {
2435
+ if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
2436
+ await appendChunk(sourceBuffer, chunk);
2437
+ }
2438
+ }
2439
+ async function appendChunk(sourceBuffer, data) {
2402
2440
  if (sourceBuffer.updating) await new Promise((resolve) => {
2403
2441
  const onUpdateEnd = () => {
2404
2442
  sourceBuffer.removeEventListener("updateend", onUpdateEnd);
@@ -2422,7 +2460,7 @@ async function appendSegment(sourceBuffer, segmentData) {
2422
2460
  sourceBuffer.addEventListener("updateend", onUpdateEnd);
2423
2461
  sourceBuffer.addEventListener("error", onError);
2424
2462
  try {
2425
- sourceBuffer.appendBuffer(segmentData);
2463
+ sourceBuffer.appendBuffer(data);
2426
2464
  } catch (error) {
2427
2465
  cleanup();
2428
2466
  reject(error);
@@ -2852,7 +2890,7 @@ function isLastSegmentAppended(segments, actor) {
2852
2890
  if (segments.length === 0) return true;
2853
2891
  const lastSeg = segments[segments.length - 1];
2854
2892
  if (!lastSeg) return false;
2855
- return actor?.snapshot.context.segments.some((s) => s.id === lastSeg.id) ?? false;
2893
+ return actor?.snapshot.context.segments.some((s) => s.id === lastSeg.id && !s.partial) ?? false;
2856
2894
  }
2857
2895
  /**
2858
2896
  * Check if the last segment has been appended for each selected track.
@@ -3076,14 +3114,26 @@ function appendInitTask(message, { signal, getCtx, sourceBuffer }) {
3076
3114
  };
3077
3115
  }, { signal });
3078
3116
  }
3079
- function appendSegmentTask(message, { signal, getCtx, sourceBuffer }) {
3117
+ function appendSegmentTask(message, { signal, getCtx, sourceBuffer, onPartialContext }) {
3080
3118
  return new Task(async (taskSignal) => {
3081
3119
  const ctx = getCtx();
3082
3120
  if (taskSignal.aborted) return ctx;
3083
- await appendSegment(sourceBuffer, message.data);
3084
3121
  const { meta } = message;
3085
3122
  const EPSILON = 1e-4;
3086
3123
  const filtered = ctx.segments.filter((s) => Math.abs(s.startTime - meta.startTime) >= EPSILON);
3124
+ if (!(message.data instanceof ArrayBuffer)) onPartialContext({
3125
+ ...ctx,
3126
+ segments: [...filtered, {
3127
+ id: meta.id,
3128
+ startTime: meta.startTime,
3129
+ duration: meta.duration,
3130
+ trackId: meta.trackId,
3131
+ ...meta.trackBandwidth !== void 0 && { trackBandwidth: meta.trackBandwidth },
3132
+ partial: true
3133
+ }],
3134
+ bufferedRanges: ctx.bufferedRanges
3135
+ });
3136
+ await appendSegment(sourceBuffer, message.data, taskSignal);
3087
3137
  return {
3088
3138
  ...ctx,
3089
3139
  segments: [...filtered, {
@@ -3158,10 +3208,18 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
3158
3208
  send(message, signal) {
3159
3209
  if (state.current.status !== "idle") return Promise.reject(new SourceBufferActorError(`send() called while actor is ${state.current.status}`));
3160
3210
  state.patch({ status: "updating" });
3211
+ const onPartialContext = (ctx) => {
3212
+ state.patch({
3213
+ status: "updating",
3214
+ context: ctx
3215
+ });
3216
+ state.flush();
3217
+ };
3161
3218
  const task = messageToTask(message, {
3162
3219
  signal,
3163
3220
  getCtx: () => state.current.context,
3164
- sourceBuffer
3221
+ sourceBuffer,
3222
+ onPartialContext
3165
3223
  });
3166
3224
  return runner.schedule(task).then(applyResult).catch(handleError);
3167
3225
  },
@@ -3170,11 +3228,19 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
3170
3228
  if (messages.length === 0) return Promise.resolve();
3171
3229
  state.patch({ status: "updating" });
3172
3230
  let workingCtx = state.current.context;
3231
+ const onPartialContext = (ctx) => {
3232
+ state.patch({
3233
+ status: "updating",
3234
+ context: ctx
3235
+ });
3236
+ state.flush();
3237
+ };
3173
3238
  for (const message of messages.slice(0, -1)) {
3174
3239
  const task = messageToTask(message, {
3175
3240
  signal,
3176
3241
  getCtx: () => workingCtx,
3177
- sourceBuffer
3242
+ sourceBuffer,
3243
+ onPartialContext
3178
3244
  });
3179
3245
  runner.schedule(task).then((newCtx) => {
3180
3246
  workingCtx = newCtx;
@@ -3183,7 +3249,8 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
3183
3249
  const lastTask = messageToTask(messages[messages.length - 1], {
3184
3250
  signal,
3185
3251
  getCtx: () => workingCtx,
3186
- sourceBuffer
3252
+ sourceBuffer,
3253
+ onPartialContext
3187
3254
  });
3188
3255
  return runner.schedule(lastTask).then(applyResult).catch(handleError);
3189
3256
  },
@@ -3194,30 +3261,6 @@ function createSourceBufferActor(sourceBuffer, initialContext) {
3194
3261
  }
3195
3262
  };
3196
3263
  }
3197
- /** Map track type to SourceBufferActor owner property key. */
3198
- const ActorKeyByType = {
3199
- video: "videoBufferActor",
3200
- audio: "audioBufferActor"
3201
- };
3202
- /**
3203
- * Setup SourceBuffer task (module-level, pure).
3204
- * Creates SourceBuffer for resolved track and waits a frame before completing.
3205
- */
3206
- const setupSourceBufferTask = async ({ currentState, currentOwners }, context) => {
3207
- const track = getSelectedTrack(currentState, context.config.type);
3208
- if (!track || !isResolvedTrack(track)) return;
3209
- if (!track.codecs || track.codecs.length === 0) return;
3210
- const mimeCodec = buildMimeCodec(track);
3211
- const buffer = createSourceBuffer(currentOwners.mediaSource, mimeCodec);
3212
- const actor = createSourceBufferActor(buffer);
3213
- const bufferKey = BufferKeyByType[context.config.type];
3214
- const actorKey = ActorKeyByType[context.config.type];
3215
- context.owners.patch({
3216
- [bufferKey]: buffer,
3217
- [actorKey]: actor
3218
- });
3219
- await new Promise((resolve) => requestAnimationFrame(resolve));
3220
- };
3221
3264
  /**
3222
3265
  * Build MIME codec string from track metadata.
3223
3266
  *
@@ -3233,66 +3276,47 @@ function buildMimeCodec(track) {
3233
3276
  return `${track.mimeType}; codecs="${codecString}"`;
3234
3277
  }
3235
3278
  /**
3236
- * Check if we can setup SourceBuffer for track type.
3237
- *
3238
- * Requires:
3239
- * - MediaSource exists in owners
3240
- * - Track is selected
3241
- *
3242
- * Note: We don't check mediaSource.readyState because owners holds references
3243
- * to mutable objects. Changes to properties on those objects won't trigger
3244
- * observations. Instead, setupMediaSource only patches owners.mediaSource after
3245
- * it's already open, so if it exists in owners, it's ready to use.
3246
- *
3247
- * Note: Track does not need to be resolved yet. The orchestration will wait
3248
- * for the track to be resolved (via resolveTrack) before creating the SourceBuffer.
3249
- */
3250
- function canSetupBuffer(state, owners, type) {
3251
- if (!owners.mediaSource) return false;
3252
- if (!getSelectedTrack(state, type)) return false;
3253
- return true;
3254
- }
3255
- /**
3256
- * Check if we should create SourceBuffer (not already created).
3257
- */
3258
- function shouldSetupBuffer(owners, type) {
3259
- const bufferKey = BufferKeyByType[type];
3260
- return isUndefined(owners[bufferKey]);
3261
- }
3262
- /**
3263
- * Setup SourceBuffer orchestration.
3279
+ * Setup all needed SourceBuffers as a single coordinated operation.
3264
3280
  *
3265
- * Triggers when:
3266
- * - MediaSource exists and is in 'open' state
3267
- * - Track is selected (same condition as resolveTrack)
3268
- *
3269
- * Creates SourceBuffer when track becomes resolved with codecs.
3270
- * This allows setupSourceBuffer to run in parallel with resolveTrack.
3281
+ * Waits until ALL selected tracks (video and/or audio) are resolved with
3282
+ * codecs, then creates every SourceBuffer in one synchronous block before
3283
+ * patching owners. This guarantees that downstream consumers (e.g.
3284
+ * loadSegments) never see a partial set of SourceBuffers — preventing the
3285
+ * Firefox bug where appending to a video SourceBuffer before the audio
3286
+ * SourceBuffer exists causes mozHasAudio to be permanently false.
3271
3287
  *
3272
- * Note: Text tracks don't use SourceBuffers and should be handled separately.
3288
+ * Handles video-only, audio-only, and combined presentations correctly:
3289
+ * only the tracks that are actually selected are waited on and created.
3273
3290
  *
3274
- * Generic over track type - create one orchestration per track type:
3275
3291
  * @example
3276
- * const videoCleanup = setupSourceBuffer({ state, owners }, { type: 'video' });
3277
- * const audioCleanup = setupSourceBuffer({ state, owners }, { type: 'audio' });
3292
+ * const cleanup = setupSourceBuffers({ state, owners });
3278
3293
  */
3279
- function setupSourceBuffer({ state, owners }, config) {
3280
- let currentTask = null;
3294
+ function setupSourceBuffers({ state, owners }) {
3295
+ let setupDone = false;
3281
3296
  return combineLatest([state, owners]).subscribe(async ([currentState, currentOwners]) => {
3282
- if (currentTask) return;
3283
- if (!canSetupBuffer(currentState, currentOwners, config.type) || !shouldSetupBuffer(currentOwners, config.type)) return;
3284
- currentTask = setupSourceBufferTask({
3285
- currentState,
3286
- currentOwners
3287
- }, {
3288
- owners,
3289
- config
3290
- });
3291
- try {
3292
- await currentTask;
3293
- } finally {
3294
- currentTask = null;
3295
- }
3297
+ if (setupDone) return;
3298
+ if (!currentOwners.mediaSource) return;
3299
+ const videoSelected = !!currentState.selectedVideoTrackId;
3300
+ const audioSelected = !!currentState.selectedAudioTrackId;
3301
+ if (!videoSelected && !audioSelected) return;
3302
+ const videoTrack = videoSelected ? getSelectedTrack(currentState, "video") : null;
3303
+ const audioTrack = audioSelected ? getSelectedTrack(currentState, "audio") : null;
3304
+ if (videoSelected && (!videoTrack || !isResolvedTrack(videoTrack) || !videoTrack.codecs?.length)) return;
3305
+ if (audioSelected && (!audioTrack || !isResolvedTrack(audioTrack) || !audioTrack.codecs?.length)) return;
3306
+ setupDone = true;
3307
+ const patch = {};
3308
+ if (videoSelected && videoTrack && isResolvedTrack(videoTrack)) {
3309
+ const buffer = createSourceBuffer(currentOwners.mediaSource, buildMimeCodec(videoTrack));
3310
+ patch.videoBuffer = buffer;
3311
+ patch.videoBufferActor = createSourceBufferActor(buffer);
3312
+ }
3313
+ if (audioSelected && audioTrack && isResolvedTrack(audioTrack)) {
3314
+ const buffer = createSourceBuffer(currentOwners.mediaSource, buildMimeCodec(audioTrack));
3315
+ patch.audioBuffer = buffer;
3316
+ patch.audioBufferActor = createSourceBufferActor(buffer);
3317
+ }
3318
+ owners.patch(patch);
3319
+ await new Promise((resolve) => requestAnimationFrame(resolve));
3296
3320
  });
3297
3321
  }
3298
3322
  /**
@@ -3629,14 +3653,10 @@ function createPlaybackEngine(config = {}) {
3629
3653
  state,
3630
3654
  owners
3631
3655
  }),
3632
- setupSourceBuffer({
3656
+ setupSourceBuffers({
3633
3657
  state,
3634
3658
  owners
3635
- }, { type: "video" }),
3636
- setupSourceBuffer({
3637
- state,
3638
- owners
3639
- }, { type: "audio" }),
3659
+ }),
3640
3660
  trackCurrentTime({
3641
3661
  state,
3642
3662
  owners