@banou/media-player 0.10.14 → 0.10.16
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/dist/engine/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-
|
|
1
|
+
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-C9vSEJYW.js";
|
|
2
2
|
export { i as DEFAULT_BUFFER_SIZE, e as MediaElementError, a as SUBTITLES_OFF, u as createPictureInPicture, d as createSubtitleRenderer, c as createThumbnailGenerator, n as getTimeRanges, s as isMediaElementError, o as pictureInPictureMode, l as startPlayback, t as terminateRemuxer, r as updateSourceBuffer };
|
|
@@ -188,7 +188,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
188
188
|
length: s,
|
|
189
189
|
audioStreamIndex: _,
|
|
190
190
|
read: a
|
|
191
|
-
}), M = [], N = !1,
|
|
191
|
+
}), M = [], N = !1, se = () => {
|
|
192
192
|
if (!N) {
|
|
193
193
|
N = !0;
|
|
194
194
|
for (let e of [...M].reverse()) try {
|
|
@@ -237,138 +237,138 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
237
237
|
i(), n(/* @__PURE__ */ Error("The MediaSource closed before a SourceBuffer could be created"));
|
|
238
238
|
};
|
|
239
239
|
m.addEventListener("sourceopen", a, { once: !0 }), m.addEventListener("sourceclose", o, { once: !0 }), m.addEventListener("error", o, { once: !0 });
|
|
240
|
-
}), { appendBuffer:
|
|
241
|
-
await
|
|
242
|
-
let
|
|
240
|
+
}), { appendBuffer: P, unbufferRange: ce, updateTimestampOffset: le, endOfStream: ue } = o(A, m);
|
|
241
|
+
await P(e.data), e.subtitles?.length && s.pushFragments(e.subtitles), te?.();
|
|
242
|
+
let F = !1, I = !1, L = 0, R, de = () => R ?? n.currentTime, z = !1, fe = !1, pe = 0, B = 0, V = null, H = 0, U = 0, W = !1, G = 0, K = !0, q = (e, t, n = !1) => {
|
|
243
243
|
let r = e?.message === "Cancelled";
|
|
244
|
-
t && r || (console.error(e), (!
|
|
245
|
-
},
|
|
246
|
-
if (
|
|
247
|
-
let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(A), s = (e, t) => (K = !1,
|
|
244
|
+
t && r || (console.error(e), (!W || n) && (W = !0, ne?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
|
|
245
|
+
}, me = async (e = !1) => {
|
|
246
|
+
if (L > 0 && !e) return;
|
|
247
|
+
let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(A), s = (e, t) => (K = !1, ce(e, t));
|
|
248
248
|
o.length && o[0].start < t + r && await s(0, t + r);
|
|
249
249
|
for (let { end: e } of o) e > t + a && await s(t + a, e);
|
|
250
|
-
},
|
|
250
|
+
}, he = () => {
|
|
251
251
|
let e = i(A);
|
|
252
252
|
if (!e.length) return !0;
|
|
253
|
-
let t =
|
|
254
|
-
return n && n.end >= t + y ? !1 : !n || n.end < t + b;
|
|
255
|
-
},
|
|
256
|
-
let t =
|
|
257
|
-
return e >=
|
|
258
|
-
},
|
|
253
|
+
let t = de(), n = e.find((e) => e.start <= t + T && t < e.end);
|
|
254
|
+
return be() && ve(t) && (!n || n.start <= t) || n && n.end >= t + y ? !1 : !n || n.end < t + b;
|
|
255
|
+
}, ge = (e) => i(A).find((t) => t.start <= e + T && e < t.end), _e = () => ge(n.currentTime), ve = (e = n.currentTime) => {
|
|
256
|
+
let t = ge(e);
|
|
257
|
+
return e >= U - .1 || !!t && t.end >= U - .1;
|
|
258
|
+
}, ye = () => {
|
|
259
259
|
let e = i(A).map((e) => e.end);
|
|
260
|
-
e.length && (
|
|
261
|
-
}, J = async (e) => {
|
|
262
|
-
|
|
263
|
-
let t = ++
|
|
260
|
+
e.length && (U = Math.max(U, ...e)), z && (fe = !0);
|
|
261
|
+
}, be = () => z || fe && (i(A).at(-1)?.end ?? 0) >= U - .1, J = async (e) => {
|
|
262
|
+
I = !0, V = null, H = 0, G = e, K = !0;
|
|
263
|
+
let t = ++B;
|
|
264
264
|
try {
|
|
265
265
|
let { data: n, pts: r, subtitles: i, finished: a } = await j.seek(e);
|
|
266
|
-
if (N || t !==
|
|
267
|
-
n.byteLength && (await
|
|
266
|
+
if (N || t !== B || (a && (z = !0), i?.length && s.pushFragments(i), await le(r), N || t !== B)) return;
|
|
267
|
+
n.byteLength && (await P(n), ye());
|
|
268
268
|
} catch (e) {
|
|
269
|
-
t ===
|
|
269
|
+
t === B && (K = !1), q(e, N || t !== B);
|
|
270
270
|
} finally {
|
|
271
|
-
t ===
|
|
271
|
+
t === B && (I = !1);
|
|
272
272
|
}
|
|
273
|
-
},
|
|
274
|
-
let e =
|
|
273
|
+
}, xe = async () => {
|
|
274
|
+
let e = V;
|
|
275
275
|
if (e) try {
|
|
276
|
-
await
|
|
276
|
+
await P(e), ye(), V === e && (V = null, H = 0);
|
|
277
277
|
} catch (t) {
|
|
278
|
-
if (
|
|
278
|
+
if (V !== e) return;
|
|
279
279
|
if (t?.name === "QuotaExceededError") {
|
|
280
|
-
let t = await
|
|
281
|
-
if (
|
|
280
|
+
let t = await me(!0).then(() => !0, () => !1);
|
|
281
|
+
if (V !== e) return;
|
|
282
282
|
if (t) {
|
|
283
|
-
|
|
283
|
+
z = !1, await J(_e()?.end ?? n.currentTime);
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
-
if (
|
|
288
|
-
throw
|
|
287
|
+
if (H += 1, H < C) return;
|
|
288
|
+
throw V = null, t;
|
|
289
289
|
}
|
|
290
|
-
},
|
|
291
|
-
if (
|
|
292
|
-
let e = ++
|
|
293
|
-
|
|
290
|
+
}, Se = async () => {
|
|
291
|
+
if (F || I || Q !== void 0 || N || !V && (z || !he())) return;
|
|
292
|
+
let e = ++pe, t = B, n = () => N || e !== pe || t !== B;
|
|
293
|
+
F = !0;
|
|
294
294
|
try {
|
|
295
|
-
if (!
|
|
295
|
+
if (!V) {
|
|
296
296
|
let { data: e, subtitles: t, finished: r } = await j.read();
|
|
297
|
-
if (n() || (r && (
|
|
298
|
-
|
|
297
|
+
if (n() || (r && (z = !0), t?.length && s.pushFragments(t), !e.byteLength)) return;
|
|
298
|
+
V = e, H = 0;
|
|
299
299
|
}
|
|
300
|
-
await
|
|
300
|
+
await xe(), !n() && W && (W = !1, re?.());
|
|
301
301
|
} catch (e) {
|
|
302
302
|
q(e, n());
|
|
303
303
|
} finally {
|
|
304
|
-
e ===
|
|
304
|
+
e === pe && (F = !1);
|
|
305
305
|
}
|
|
306
|
-
},
|
|
307
|
-
let t =
|
|
306
|
+
}, Ce = (e) => e >= G && we(e) && Te(e), we = (e) => i(A).some((t) => t.start <= e && e < t.end), Y = (e) => {
|
|
307
|
+
let t = ge(e);
|
|
308
308
|
return t ? t.end - e : 0;
|
|
309
|
-
},
|
|
310
|
-
if (!N && !(
|
|
311
|
-
|
|
309
|
+
}, Te = (e) => Y(e) >= E || be() && ve(e), Ee = async (e) => {
|
|
310
|
+
if (!N && !(we(e) && Te(e))) {
|
|
311
|
+
z = !1, L++, R = e;
|
|
312
312
|
try {
|
|
313
313
|
await J(e);
|
|
314
|
-
let t =
|
|
315
|
-
for (let n = 0; n < ee && !(N || t !==
|
|
314
|
+
let t = B;
|
|
315
|
+
for (let n = 0; n < ee && !(N || t !== B || z || Y(e) >= E); n++) {
|
|
316
316
|
let t = Y(e);
|
|
317
|
-
if (await
|
|
317
|
+
if (await Se(), Y(e) <= t) break;
|
|
318
318
|
}
|
|
319
319
|
} finally {
|
|
320
|
-
|
|
320
|
+
L--, R === e && (R = void 0);
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
-
},
|
|
324
|
-
let t = performance.now(), n = t -
|
|
323
|
+
}, De = 0, X = null, Z, Oe = (e) => {
|
|
324
|
+
let t = performance.now(), n = t - De;
|
|
325
325
|
if (n >= D) {
|
|
326
|
-
|
|
326
|
+
De = t, X = null, ie?.(e);
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
X = e, !Z && (Z = setTimeout(() => {
|
|
330
330
|
Z = void 0;
|
|
331
331
|
let e = X;
|
|
332
|
-
X = null, !(e === null || N) && (
|
|
332
|
+
X = null, !(e === null || N) && (De = performance.now(), ie?.(e));
|
|
333
333
|
}, D - n));
|
|
334
334
|
};
|
|
335
335
|
M.push(() => {
|
|
336
336
|
Z && clearTimeout(Z);
|
|
337
337
|
});
|
|
338
|
-
let
|
|
338
|
+
let ke = 0, Q;
|
|
339
339
|
M.push(() => {
|
|
340
340
|
Q && clearTimeout(Q);
|
|
341
341
|
});
|
|
342
|
-
let
|
|
342
|
+
let Ae = () => {
|
|
343
343
|
let t = n.currentTime, r = e.info.input.duration || n.duration;
|
|
344
|
-
r > 0 &&
|
|
345
|
-
let i = performance.now(), a = i -
|
|
346
|
-
if (
|
|
347
|
-
if (
|
|
344
|
+
r > 0 && Oe(Math.min(Math.max(t / r, 0), 1));
|
|
345
|
+
let i = performance.now(), a = i - ke < O;
|
|
346
|
+
if (ke = i, !Ce(t) && !(K && (I || Y(t) > 0) && Math.abs(t - G) < .001)) {
|
|
347
|
+
if (z = !1, Q && clearTimeout(Q), !a) {
|
|
348
348
|
Q = void 0, J(t);
|
|
349
349
|
return;
|
|
350
350
|
}
|
|
351
351
|
Q = setTimeout(() => {
|
|
352
352
|
if (Q = void 0, N) return;
|
|
353
353
|
let e = n.currentTime;
|
|
354
|
-
|
|
354
|
+
Ce(e) || (z = !1, J(e));
|
|
355
355
|
}, O);
|
|
356
356
|
}
|
|
357
357
|
};
|
|
358
|
-
n.addEventListener("seeking",
|
|
359
|
-
let $ = !1,
|
|
358
|
+
n.addEventListener("seeking", Ae), M.push(() => n.removeEventListener("seeking", Ae));
|
|
359
|
+
let $ = !1, je = () => {
|
|
360
360
|
if ($) return;
|
|
361
361
|
$ = !0;
|
|
362
362
|
let e = n.error;
|
|
363
363
|
q(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
|
|
364
364
|
};
|
|
365
|
-
n.addEventListener("error",
|
|
366
|
-
let
|
|
367
|
-
$ || n.error || (
|
|
365
|
+
n.addEventListener("error", je), M.push(() => n.removeEventListener("error", je));
|
|
366
|
+
let Me = setInterval(() => {
|
|
367
|
+
$ || n.error || (me().catch(() => {}), Se().catch((e) => q(e, !1)), be() && !V && ve() && m.readyState === "open" && ue().catch(() => {}));
|
|
368
368
|
}, 100);
|
|
369
|
-
return M.push(() => clearInterval(
|
|
370
|
-
destroy:
|
|
371
|
-
prepareSeek:
|
|
369
|
+
return M.push(() => clearInterval(Me)), {
|
|
370
|
+
destroy: se,
|
|
371
|
+
prepareSeek: Ee,
|
|
372
372
|
videoElement: n,
|
|
373
373
|
selectSubtitleStream: (e) => s.selectStream(e),
|
|
374
374
|
indexes: e.indexes ?? [],
|
|
@@ -378,9 +378,9 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
378
378
|
audioMimeType: e.info.output.audioMimeType
|
|
379
379
|
};
|
|
380
380
|
} catch (e) {
|
|
381
|
-
throw
|
|
381
|
+
throw se(), e;
|
|
382
382
|
}
|
|
383
|
-
}, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, j = 1e4, M = 1.5, N = 4e6,
|
|
383
|
+
}, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, j = 1e4, M = 1.5, N = 4e6, se = 3e4, P = async (e) => {
|
|
384
384
|
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = ie } = e, c = await t({
|
|
385
385
|
publicPath: n,
|
|
386
386
|
workerUrl: r,
|
|
@@ -469,7 +469,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
469
469
|
}, w = async (e) => {
|
|
470
470
|
_ = !0;
|
|
471
471
|
try {
|
|
472
|
-
let t = await Promise.race([c.init(), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")),
|
|
472
|
+
let t = await Promise.race([c.init(), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), se))]);
|
|
473
473
|
if (d) return;
|
|
474
474
|
h = e;
|
|
475
475
|
let n = l(t.indexes);
|
|
@@ -509,8 +509,8 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
509
509
|
} catch (e) {
|
|
510
510
|
throw A(c), e;
|
|
511
511
|
}
|
|
512
|
-
},
|
|
513
|
-
let { video: t, subtitles: n } = e, r = e.mode ??
|
|
512
|
+
}, ce = 1280, le = 1920, ue = 250, F = 100, I = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, L = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, R = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, de = () => I() ? L() ? "window" : R() ? "burn-in" : null : null, z = (e) => {
|
|
513
|
+
let { video: t, subtitles: n } = e, r = e.mode ?? de() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? le : ce), a, o, s, c = !1, l = !1, u = !1, d = !1, f, p = () => r === "burn-in" ? !!a : !!a && document.pictureInPictureElement === a.mirror, m = (e, r) => {
|
|
514
514
|
r.drawImage(t, 0, 0, e.width, e.height);
|
|
515
515
|
let i = n.querySelector("canvas");
|
|
516
516
|
i && i.width > 0 && i.height > 0 && r.drawImage(i, 0, 0, e.width, e.height);
|
|
@@ -539,11 +539,11 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
539
539
|
l = !0;
|
|
540
540
|
let { mirror: e } = a;
|
|
541
541
|
h(), e.play().then(() => new Promise((e) => {
|
|
542
|
-
setTimeout(e,
|
|
542
|
+
setTimeout(e, F);
|
|
543
543
|
})).catch(() => {}).finally(() => {
|
|
544
544
|
e.pause(), setTimeout(() => {
|
|
545
545
|
l = !1;
|
|
546
|
-
},
|
|
546
|
+
}, F);
|
|
547
547
|
});
|
|
548
548
|
}, E = [["play", () => {
|
|
549
549
|
t.play().catch(() => {});
|
|
@@ -580,7 +580,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
580
580
|
};
|
|
581
581
|
if (a = f, u.srcObject = f.stream, (e.container ?? t.parentElement ?? document.body).appendChild(u), v(), s = setInterval(() => {
|
|
582
582
|
t.paused && h();
|
|
583
|
-
},
|
|
583
|
+
}, ue), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
|
|
584
584
|
let t = () => {
|
|
585
585
|
clearTimeout(n), e();
|
|
586
586
|
}, n = setTimeout(t, 1e3);
|
|
@@ -626,4 +626,4 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
626
626
|
};
|
|
627
627
|
};
|
|
628
628
|
//#endregion
|
|
629
|
-
export { g as a, A as c, i as d, o as f, k as i, s as l,
|
|
629
|
+
export { g as a, A as c, i as d, o as f, k as i, s as l, de as n, _ as o, P as r, te as s, z as t, h as u };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-
|
|
1
|
+
import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-C9vSEJYW.js";
|
|
2
2
|
import { Children as s, Fragment as c, isValidElement as l, useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m, useSyncExternalStore as h } from "react";
|
|
3
3
|
import { css as g, keyframes as _ } from "@emotion/react";
|
|
4
4
|
import { createPlayer as v, useContainerAttach as y, useMediaAttach as b } from "@videojs/react";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@banou/media-player",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.16",
|
|
4
4
|
"description": "A video player for containers and codecs the browser cannot play natively, remuxed on the fly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@videojs/react": "10.0.0-beta.26",
|
|
45
45
|
"ass-compiler": "^0.1.16",
|
|
46
46
|
"jassub": "^2.5.16",
|
|
47
|
-
"libav-wasm": "^0.8.
|
|
47
|
+
"libav-wasm": "^0.8.9",
|
|
48
48
|
"lucide-react": "^0.536.0",
|
|
49
49
|
"osra": "^0.6.12",
|
|
50
50
|
"react-tooltip": "^5.28.0"
|
|
@@ -271,6 +271,8 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
|
|
|
271
271
|
let prepareTarget: number | undefined
|
|
272
272
|
const bufferAnchor = () => prepareTarget ?? videoElement.currentTime
|
|
273
273
|
let finished = false
|
|
274
|
+
// whether the end of the file was ever appended, which unlike `finished` a seek does not clear
|
|
275
|
+
let endAppended = false
|
|
274
276
|
// libav aborts the running task when a new one starts, so both flags need a generation token
|
|
275
277
|
let readGeneration = 0
|
|
276
278
|
let seekGeneration = 0
|
|
@@ -335,6 +337,11 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
|
|
|
335
337
|
if (!ranges.length) return true
|
|
336
338
|
const ct = bufferAnchor()
|
|
337
339
|
const range = ranges.find((r) => r.start <= ct + BOUNDARY_SLACK && ct < r.end)
|
|
340
|
+
// A range holding the playhead that runs to an end still buffered leaves nothing to read: after a seek back,
|
|
341
|
+
// reading on only appended again what was buffered, 13 reads of 32 KB on a move to 2 s before the end. From a
|
|
342
|
+
// hole under the slack before that range nothing read the hole, and playback stopped there for good with
|
|
343
|
+
// keyframes 0.833 s apart (measured 2026-10-12)
|
|
344
|
+
if (endInBuffer() && atEnd(ct) && (!range || range.start <= ct)) return false
|
|
338
345
|
/*
|
|
339
346
|
* Never read past what evict() keeps, measured on the run holding the anchor rather than
|
|
340
347
|
* across every island.
|
|
@@ -364,7 +371,16 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
|
|
|
364
371
|
const trackAppendedEnd = () => {
|
|
365
372
|
const ends = getTimeRanges(sourceBuffer).map((r) => r.end)
|
|
366
373
|
if (ends.length) lastAppendedEnd = Math.max(lastAppendedEnd, ...ends)
|
|
374
|
+
if (finished) endAppended = true
|
|
367
375
|
}
|
|
376
|
+
/*
|
|
377
|
+
* Whether the end of the file is in the buffer. A seek clears `finished`, so past one it is read back
|
|
378
|
+
* from `buffered`, since evict() and the browser both remove data: counted from a flag alone, a move to
|
|
379
|
+
* the end of a file whose end had been removed signalled the end there, endOfStream cut the duration to
|
|
380
|
+
* what was buffered, and the playhead went back to 20 s of 30 in both engines (measured 2026-10-12).
|
|
381
|
+
*/
|
|
382
|
+
const endInBuffer = () =>
|
|
383
|
+
finished || (endAppended && (getTimeRanges(sourceBuffer).at(-1)?.end ?? 0) >= lastAppendedEnd - 0.1)
|
|
368
384
|
|
|
369
385
|
const seekTo = async (time: number) => {
|
|
370
386
|
seeking = true
|
|
@@ -489,15 +505,17 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
|
|
|
489
505
|
}
|
|
490
506
|
|
|
491
507
|
/**
|
|
492
|
-
* Whether a position has its runway, which stops at the end of the file
|
|
508
|
+
* Whether a position has its runway, which stops at the end of the file while the end is in the buffer.
|
|
493
509
|
*
|
|
494
510
|
* No target in the last 3 s can have the full runway, and re-positioning the remuxer there re-appends
|
|
495
|
-
* over the buffered end, after which Firefox 151 stays at the end and never fires `ended
|
|
496
|
-
* the
|
|
497
|
-
* track
|
|
498
|
-
*
|
|
511
|
+
* over the buffered end, after which Firefox 151 stays at the end and never fires `ended`: its demuxer
|
|
512
|
+
* had read the track's last frame, and once that frame is replaced it waits for one starting at the
|
|
513
|
+
* track's end. So the end counts while it is buffered, even with the reader re-reading toward it after
|
|
514
|
+
* a seek back (measured 2026-10-12). The end is the furthest append, the one `endOfStream` waits on,
|
|
515
|
+
* never the declared duration: that is the longer track, while an open MediaSource's `buffered` ends at
|
|
516
|
+
* the shorter one, and evict() reopens it on every trim (measured 2026-10-11).
|
|
499
517
|
*/
|
|
500
|
-
const hasRunway = (time: number) => runwayFrom(time) >= SEEK_RUNWAY || (
|
|
518
|
+
const hasRunway = (time: number) => runwayFrom(time) >= SEEK_RUNWAY || (endInBuffer() && atEnd(time))
|
|
501
519
|
|
|
502
520
|
/**
|
|
503
521
|
* Put the data in place for a position the playhead has NOT moved to yet.
|
|
@@ -692,7 +710,7 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
|
|
|
692
710
|
evict().catch(() => {})
|
|
693
711
|
pump().catch((error) => reportError(error, false))
|
|
694
712
|
// every remove() puts the MediaSource back to 'open', so the end has to be re-armed
|
|
695
|
-
if (
|
|
713
|
+
if (endInBuffer() && !pending && atEnd() && mediaSource.readyState === 'open') endOfStream().catch(() => {})
|
|
696
714
|
}, 100)
|
|
697
715
|
teardown.push(() => clearInterval(interval))
|
|
698
716
|
|