@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.
- package/cdn/audio-minimal.css +1 -1
- package/cdn/audio-minimal.dev.js +197 -126
- package/cdn/audio-minimal.dev.js.map +1 -1
- package/cdn/audio-minimal.js +5 -5
- package/cdn/audio-minimal.js.map +1 -1
- package/cdn/audio.css +1 -1
- package/cdn/audio.dev.js +191 -120
- package/cdn/audio.dev.js.map +1 -1
- package/cdn/audio.js +5 -5
- package/cdn/audio.js.map +1 -1
- package/cdn/background.dev.js +40 -25
- package/cdn/background.dev.js.map +1 -1
- package/cdn/background.js +4 -4
- package/cdn/background.js.map +1 -1
- package/cdn/media/hls-video.dev.js +0 -1
- package/cdn/media/hls-video.dev.js.map +1 -1
- package/cdn/media/hls-video.js +1 -1
- package/cdn/media/hls-video.js.map +1 -1
- package/cdn/media/simple-hls-video.dev.js +178 -158
- package/cdn/media/simple-hls-video.dev.js.map +1 -1
- package/cdn/media/simple-hls-video.js +1 -1
- package/cdn/media/simple-hls-video.js.map +1 -1
- package/cdn/video-minimal.css +1 -1
- package/cdn/video-minimal.dev.js +220 -161
- package/cdn/video-minimal.dev.js.map +1 -1
- package/cdn/video-minimal.js +5 -5
- package/cdn/video-minimal.js.map +1 -1
- package/cdn/video.css +1 -1
- package/cdn/video.dev.js +217 -158
- package/cdn/video.dev.js.map +1 -1
- package/cdn/video.js +4 -4
- package/cdn/video.js.map +1 -1
- package/dist/default/_virtual/inline-css_src/define/audio/minimal-skin.js +1 -1
- package/dist/default/_virtual/inline-css_src/define/audio/minimal-skin.js.map +1 -1
- package/dist/default/_virtual/inline-css_src/define/audio/skin.js +1 -1
- package/dist/default/_virtual/inline-css_src/define/audio/skin.js.map +1 -1
- package/dist/default/_virtual/inline-css_src/define/base.js +6 -0
- package/dist/default/_virtual/inline-css_src/define/base.js.map +1 -0
- package/dist/default/_virtual/inline-css_src/define/video/minimal-skin.js +1 -1
- package/dist/default/_virtual/inline-css_src/define/video/minimal-skin.js.map +1 -1
- package/dist/default/_virtual/inline-css_src/define/video/skin.js +1 -1
- package/dist/default/_virtual/inline-css_src/define/video/skin.js.map +1 -1
- package/dist/default/define/audio/minimal-skin.css +8 -2
- package/dist/default/define/audio/minimal-skin.js +2 -1
- package/dist/default/define/audio/minimal-skin.js.map +1 -1
- package/dist/default/define/audio/minimal-skin.tailwind.js +5 -2
- package/dist/default/define/audio/minimal-skin.tailwind.js.map +1 -1
- package/dist/default/define/audio/skin.css +7 -5
- package/dist/default/define/audio/skin.js +2 -1
- package/dist/default/define/audio/skin.js.map +1 -1
- package/dist/default/define/audio/skin.tailwind.js +5 -2
- package/dist/default/define/audio/skin.tailwind.js.map +1 -1
- package/dist/default/define/base.css +25 -0
- package/dist/default/define/shared.css +3 -0
- package/dist/default/define/skin-mixin.js +10 -18
- package/dist/default/define/skin-mixin.js.map +1 -1
- package/dist/default/define/video/minimal-skin.css +35 -73
- package/dist/default/define/video/minimal-skin.js +2 -1
- package/dist/default/define/video/minimal-skin.js.map +1 -1
- package/dist/default/define/video/minimal-skin.tailwind.js +4 -4
- package/dist/default/define/video/minimal-skin.tailwind.js.map +1 -1
- package/dist/default/define/video/skin.css +32 -71
- package/dist/default/define/video/skin.js +2 -1
- package/dist/default/define/video/skin.js.map +1 -1
- package/dist/default/define/video/skin.tailwind.js +5 -4
- package/dist/default/define/video/skin.tailwind.js.map +1 -1
- package/dist/default/skins/dist/default/default/tailwind/audio.tailwind.js +3 -21
- package/dist/default/skins/dist/default/default/tailwind/audio.tailwind.js.map +1 -1
- package/dist/default/skins/dist/default/default/tailwind/components/overlay.js +1 -1
- package/dist/default/skins/dist/default/default/tailwind/components/overlay.js.map +1 -1
- package/dist/default/skins/dist/default/default/tailwind/components/root.js +1 -1
- package/dist/default/skins/dist/default/default/tailwind/components/root.js.map +1 -1
- package/dist/default/skins/dist/default/default/tailwind/video.tailwind.js +8 -5
- package/dist/default/skins/dist/default/default/tailwind/video.tailwind.js.map +1 -1
- package/dist/default/skins/dist/default/minimal/tailwind/audio.tailwind.js +3 -22
- package/dist/default/skins/dist/default/minimal/tailwind/audio.tailwind.js.map +1 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/overlay.js +1 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/overlay.js.map +1 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/popup.js +1 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/popup.js.map +1 -1
- package/dist/default/skins/dist/default/minimal/tailwind/video.tailwind.js +6 -4
- package/dist/default/skins/dist/default/minimal/tailwind/video.tailwind.js.map +1 -1
- package/dist/default/skins/dist/default/{default/tailwind/components → shared/tailwind}/icon-state.js +1 -1
- package/dist/default/skins/dist/default/shared/tailwind/icon-state.js.map +1 -0
- package/dist/{dev/skins/dist/default/default/tailwind/components → default/skins/dist/default/shared/tailwind}/tooltip-state.js +1 -1
- package/dist/default/skins/dist/default/shared/tailwind/tooltip-state.js.map +1 -0
- package/dist/default/store/container-mixin.js +22 -10
- package/dist/default/store/container-mixin.js.map +1 -1
- package/dist/default/ui/tooltip/tooltip-group-element.js +4 -1
- package/dist/default/ui/tooltip/tooltip-group-element.js.map +1 -1
- package/dist/dev/_virtual/inline-css_src/define/audio/minimal-skin.js +1 -1
- package/dist/dev/_virtual/inline-css_src/define/audio/minimal-skin.js.map +1 -1
- package/dist/dev/_virtual/inline-css_src/define/audio/skin.js +1 -1
- package/dist/dev/_virtual/inline-css_src/define/audio/skin.js.map +1 -1
- package/dist/dev/_virtual/inline-css_src/define/base.js +6 -0
- package/dist/dev/_virtual/inline-css_src/define/base.js.map +1 -0
- package/dist/dev/_virtual/inline-css_src/define/video/minimal-skin.js +1 -1
- package/dist/dev/_virtual/inline-css_src/define/video/minimal-skin.js.map +1 -1
- package/dist/dev/_virtual/inline-css_src/define/video/skin.js +1 -1
- package/dist/dev/_virtual/inline-css_src/define/video/skin.js.map +1 -1
- package/dist/dev/define/audio/minimal-skin.css +8 -2
- package/dist/dev/define/audio/minimal-skin.d.ts.map +1 -1
- package/dist/dev/define/audio/minimal-skin.js +67 -64
- package/dist/dev/define/audio/minimal-skin.js.map +1 -1
- package/dist/dev/define/audio/minimal-skin.tailwind.d.ts.map +1 -1
- package/dist/dev/define/audio/minimal-skin.tailwind.js +71 -66
- package/dist/dev/define/audio/minimal-skin.tailwind.js.map +1 -1
- package/dist/dev/define/audio/skin.css +7 -5
- package/dist/dev/define/audio/skin.d.ts.map +1 -1
- package/dist/dev/define/audio/skin.js +59 -56
- package/dist/dev/define/audio/skin.js.map +1 -1
- package/dist/dev/define/audio/skin.tailwind.d.ts.map +1 -1
- package/dist/dev/define/audio/skin.tailwind.js +64 -59
- package/dist/dev/define/audio/skin.tailwind.js.map +1 -1
- package/dist/dev/define/base.css +25 -0
- package/dist/dev/define/shared.css +3 -0
- package/dist/dev/define/skin-mixin.d.ts +2 -2
- package/dist/dev/define/skin-mixin.d.ts.map +1 -1
- package/dist/dev/define/skin-mixin.js +10 -32
- package/dist/dev/define/skin-mixin.js.map +1 -1
- package/dist/dev/define/video/minimal-skin.css +35 -73
- package/dist/dev/define/video/minimal-skin.d.ts.map +1 -1
- package/dist/dev/define/video/minimal-skin.js +92 -101
- package/dist/dev/define/video/minimal-skin.js.map +1 -1
- package/dist/dev/define/video/minimal-skin.tailwind.d.ts.map +1 -1
- package/dist/dev/define/video/minimal-skin.tailwind.js +98 -108
- package/dist/dev/define/video/minimal-skin.tailwind.js.map +1 -1
- package/dist/dev/define/video/skin.css +32 -71
- package/dist/dev/define/video/skin.d.ts.map +1 -1
- package/dist/dev/define/video/skin.js +82 -91
- package/dist/dev/define/video/skin.js.map +1 -1
- package/dist/dev/define/video/skin.tailwind.d.ts.map +1 -1
- package/dist/dev/define/video/skin.tailwind.js +93 -102
- package/dist/dev/define/video/skin.tailwind.js.map +1 -1
- package/dist/dev/skins/dist/default/default/tailwind/audio.tailwind.js +3 -21
- package/dist/dev/skins/dist/default/default/tailwind/audio.tailwind.js.map +1 -1
- package/dist/dev/skins/dist/default/default/tailwind/components/overlay.js +1 -1
- package/dist/dev/skins/dist/default/default/tailwind/components/overlay.js.map +1 -1
- package/dist/dev/skins/dist/default/default/tailwind/components/root.js +1 -1
- package/dist/dev/skins/dist/default/default/tailwind/components/root.js.map +1 -1
- package/dist/dev/skins/dist/default/default/tailwind/video.tailwind.js +8 -5
- package/dist/dev/skins/dist/default/default/tailwind/video.tailwind.js.map +1 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/audio.tailwind.js +3 -22
- package/dist/dev/skins/dist/default/minimal/tailwind/audio.tailwind.js.map +1 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/overlay.js +1 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/overlay.js.map +1 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/popup.js +1 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/popup.js.map +1 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/video.tailwind.js +6 -4
- package/dist/dev/skins/dist/default/minimal/tailwind/video.tailwind.js.map +1 -1
- package/dist/dev/skins/dist/default/{default/tailwind/components → shared/tailwind}/icon-state.js +1 -1
- package/dist/dev/skins/dist/default/shared/tailwind/icon-state.js.map +1 -0
- package/dist/{default/skins/dist/default/minimal/tailwind/components → dev/skins/dist/default/shared/tailwind}/tooltip-state.js +1 -1
- package/dist/dev/skins/dist/default/shared/tailwind/tooltip-state.js.map +1 -0
- package/dist/dev/store/container-mixin.js +22 -10
- package/dist/dev/store/container-mixin.js.map +1 -1
- package/dist/dev/ui/tooltip/tooltip-group-element.js +4 -1
- package/dist/dev/ui/tooltip/tooltip-group-element.js.map +1 -1
- package/package.json +7 -7
- package/dist/default/skins/dist/default/default/tailwind/components/icon-state.js.map +0 -1
- package/dist/default/skins/dist/default/default/tailwind/components/tooltip-state.js +0 -28
- package/dist/default/skins/dist/default/default/tailwind/components/tooltip-state.js.map +0 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/error.js +0 -15
- package/dist/default/skins/dist/default/minimal/tailwind/components/error.js.map +0 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/icon-state.js +0 -29
- package/dist/default/skins/dist/default/minimal/tailwind/components/icon-state.js.map +0 -1
- package/dist/default/skins/dist/default/minimal/tailwind/components/tooltip-state.js.map +0 -1
- package/dist/dev/skins/dist/default/default/tailwind/components/icon-state.js.map +0 -1
- package/dist/dev/skins/dist/default/default/tailwind/components/tooltip-state.js.map +0 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/error.js +0 -15
- package/dist/dev/skins/dist/default/minimal/tailwind/components/error.js.map +0 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/icon-state.js +0 -29
- package/dist/dev/skins/dist/default/minimal/tailwind/components/icon-state.js.map +0 -1
- package/dist/dev/skins/dist/default/minimal/tailwind/components/tooltip-state.js +0 -28
- 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-
|
|
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, {
|
|
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
|
|
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
|
|
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
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
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
|
|
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) :
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2394
|
-
*
|
|
2395
|
-
*
|
|
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
|
-
*
|
|
2398
|
-
*
|
|
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,
|
|
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(
|
|
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
|
-
*
|
|
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
|
-
*
|
|
3266
|
-
*
|
|
3267
|
-
*
|
|
3268
|
-
*
|
|
3269
|
-
*
|
|
3270
|
-
*
|
|
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
|
-
*
|
|
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
|
|
3277
|
-
* const audioCleanup = setupSourceBuffer({ state, owners }, { type: 'audio' });
|
|
3292
|
+
* const cleanup = setupSourceBuffers({ state, owners });
|
|
3278
3293
|
*/
|
|
3279
|
-
function
|
|
3280
|
-
let
|
|
3294
|
+
function setupSourceBuffers({ state, owners }) {
|
|
3295
|
+
let setupDone = false;
|
|
3281
3296
|
return combineLatest([state, owners]).subscribe(async ([currentState, currentOwners]) => {
|
|
3282
|
-
if (
|
|
3283
|
-
if (!
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
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
|
-
|
|
3656
|
+
setupSourceBuffers({
|
|
3633
3657
|
state,
|
|
3634
3658
|
owners
|
|
3635
|
-
}
|
|
3636
|
-
setupSourceBuffer({
|
|
3637
|
-
state,
|
|
3638
|
-
owners
|
|
3639
|
-
}, { type: "audio" }),
|
|
3659
|
+
}),
|
|
3640
3660
|
trackCurrentTime({
|
|
3641
3661
|
state,
|
|
3642
3662
|
owners
|