@banou/media-player 0.10.1 → 0.10.2
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 +1 -1
- package/dist/{engine-b5_5HscR.js → engine-DqyZgmTv.js} +172 -134
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/lib/engine/thumbnails.ts +135 -19
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-DqyZgmTv.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 };
|
|
@@ -180,7 +180,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
180
180
|
clearTimeout(n), t.terminate();
|
|
181
181
|
});
|
|
182
182
|
}, te = async (t) => {
|
|
183
|
-
let { videoElement: n, subtitleContainer: r, read: a, length: s, publicPath: c, libavWorkerUrl: l, jassubWorkerUrl: u, jassubWasmUrl: d, jassubLegacyWasmUrl: f, defaultFontUrl: p, bufferSize: m = k, audioStreamIndex: _, onReady: te, onError: ne, onRecovered: re, onSeek:
|
|
183
|
+
let { videoElement: n, subtitleContainer: r, read: a, length: s, publicPath: c, libavWorkerUrl: l, jassubWorkerUrl: u, jassubWasmUrl: d, jassubLegacyWasmUrl: f, defaultFontUrl: p, bufferSize: m = k, audioStreamIndex: _, onReady: te, onError: ne, onRecovered: re, onSeek: ie, onSubtitleStreams: ae, onAudioStreams: oe } = t, j = await e({
|
|
184
184
|
publicPath: c,
|
|
185
185
|
workerUrl: l,
|
|
186
186
|
workerOptions: { type: "module" },
|
|
@@ -188,18 +188,18 @@ 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
|
-
}),
|
|
192
|
-
if (!
|
|
193
|
-
|
|
194
|
-
for (let e of [...
|
|
191
|
+
}), M = [], N = !1, P = () => {
|
|
192
|
+
if (!N) {
|
|
193
|
+
N = !0;
|
|
194
|
+
for (let e of [...M].reverse()) try {
|
|
195
195
|
e();
|
|
196
196
|
} catch {}
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
-
|
|
199
|
+
M.push(() => A(j));
|
|
200
200
|
try {
|
|
201
|
-
let e = await
|
|
202
|
-
|
|
201
|
+
let e = await j.init(), t = e.audioStreams ?? [], a = t.some((e) => e.streamIndex === _) ? _ : t[0]?.streamIndex ?? -1;
|
|
202
|
+
oe?.(t, a);
|
|
203
203
|
let s = h({
|
|
204
204
|
video: n,
|
|
205
205
|
container: r,
|
|
@@ -207,13 +207,13 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
207
207
|
wasmUrl: d,
|
|
208
208
|
legacyWasmUrl: f,
|
|
209
209
|
defaultFontUrl: p,
|
|
210
|
-
onStreams:
|
|
210
|
+
onStreams: ae
|
|
211
211
|
});
|
|
212
|
-
if (
|
|
212
|
+
if (M.push(() => s.destroy()), e.attachments?.length && s.pushAttachments(e.attachments), !e.info.output.videoMimeType) throw Error("The video codec in this file could not be identified");
|
|
213
213
|
let c = [e.info.output.videoMimeType, e.info.output.audioMimeType].filter(Boolean).join(","), l = `video/mp4; codecs="${c}"`;
|
|
214
214
|
if (!MediaSource.isTypeSupported(l)) throw Error(`This browser cannot play the codecs in this file: ${c}`);
|
|
215
215
|
let m = new MediaSource(), k = URL.createObjectURL(m);
|
|
216
|
-
|
|
216
|
+
M.push(() => URL.revokeObjectURL(k)), n.src = k, M.push(() => {
|
|
217
217
|
try {
|
|
218
218
|
n.pause();
|
|
219
219
|
} catch {}
|
|
@@ -237,140 +237,140 @@ 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 R = !1, z = !1, B = 0, V, ce = () => V ?? n.currentTime, H = !1,
|
|
240
|
+
}), { appendBuffer: F, unbufferRange: I, updateTimestampOffset: se, endOfStream: L } = o(A, m);
|
|
241
|
+
await F(e.data), e.subtitles?.length && s.pushFragments(e.subtitles), te?.();
|
|
242
|
+
let R = !1, z = !1, B = 0, V, ce = () => V ?? n.currentTime, H = !1, le = 0, U = 0, W = null, G = 0, ue = 0, K = !1, de = 0, q = (e, t, n = !1) => {
|
|
243
243
|
let r = e?.message === "Cancelled";
|
|
244
|
-
t && r || (console.error(e), !(
|
|
245
|
-
},
|
|
244
|
+
t && r || (console.error(e), !(K && !n) && (K = !0, ne?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
|
|
245
|
+
}, fe = async (e = !1) => {
|
|
246
246
|
if (B > 0 && !e) return;
|
|
247
247
|
let t = n.currentTime, r = e ? x : v, a = e ? S : y;
|
|
248
|
-
for (let { start: e, end: n } of i(A)) e < t + r && await
|
|
249
|
-
},
|
|
248
|
+
for (let { start: e, end: n } of i(A)) e < t + r && await I(e, t + r), n > t + a && await I(t + a, n);
|
|
249
|
+
}, pe = () => {
|
|
250
250
|
let e = i(A);
|
|
251
251
|
if (!e.length) return !0;
|
|
252
252
|
let t = ce(), n = e.find((e) => e.start <= t + T && t < e.end);
|
|
253
253
|
return n && n.end >= t + y ? !1 : !n || n.end < t + b;
|
|
254
|
-
},
|
|
254
|
+
}, me = () => {
|
|
255
255
|
let e = n.currentTime;
|
|
256
256
|
return i(A).find((t) => t.start <= e + T && e < t.end);
|
|
257
|
-
}, me = () => {
|
|
258
|
-
let e = pe();
|
|
259
|
-
return !!e && e.end >= le - .1;
|
|
260
257
|
}, he = () => {
|
|
258
|
+
let e = me();
|
|
259
|
+
return !!e && e.end >= ue - .1;
|
|
260
|
+
}, ge = () => {
|
|
261
261
|
let e = i(A).map((e) => e.end);
|
|
262
|
-
e.length && (
|
|
263
|
-
},
|
|
264
|
-
z = !0,
|
|
265
|
-
let t = ++
|
|
262
|
+
e.length && (ue = Math.max(ue, ...e));
|
|
263
|
+
}, J = async (e) => {
|
|
264
|
+
z = !0, W = null, G = 0, de = e;
|
|
265
|
+
let t = ++U;
|
|
266
266
|
try {
|
|
267
|
-
let { data: n, pts: r, subtitles: i, finished: a } = await
|
|
268
|
-
if (
|
|
269
|
-
n.byteLength && (await
|
|
267
|
+
let { data: n, pts: r, subtitles: i, finished: a } = await j.seek(e);
|
|
268
|
+
if (N || t !== U || (a && (H = !0), i?.length && s.pushFragments(i), await se(r), N || t !== U)) return;
|
|
269
|
+
n.byteLength && (await F(n), ge());
|
|
270
270
|
} catch (e) {
|
|
271
|
-
|
|
271
|
+
q(e, N || t !== U);
|
|
272
272
|
} finally {
|
|
273
|
-
t ===
|
|
273
|
+
t === U && (z = !1);
|
|
274
274
|
}
|
|
275
|
-
},
|
|
276
|
-
let e =
|
|
275
|
+
}, _e = async () => {
|
|
276
|
+
let e = W;
|
|
277
277
|
if (e) try {
|
|
278
|
-
await
|
|
278
|
+
await F(e), ge(), W === e && (W = null, G = 0);
|
|
279
279
|
} catch (t) {
|
|
280
|
-
if (
|
|
280
|
+
if (W !== e) return;
|
|
281
281
|
if (t?.name === "QuotaExceededError") {
|
|
282
|
-
let t = await
|
|
283
|
-
if (
|
|
282
|
+
let t = await fe(!0).then(() => !0, () => !1);
|
|
283
|
+
if (W !== e) return;
|
|
284
284
|
if (t) {
|
|
285
|
-
H = !1, await
|
|
285
|
+
H = !1, await J(me()?.end ?? n.currentTime);
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
|
-
if (
|
|
290
|
-
throw
|
|
289
|
+
if (G += 1, G < C) return;
|
|
290
|
+
throw W = null, t;
|
|
291
291
|
}
|
|
292
|
-
},
|
|
293
|
-
if (R || z ||
|
|
294
|
-
let e = ++
|
|
292
|
+
}, ve = async () => {
|
|
293
|
+
if (R || z || Q !== void 0 || N || !W && (H || !pe())) return;
|
|
294
|
+
let e = ++le, t = U, n = () => N || e !== le || t !== U;
|
|
295
295
|
R = !0;
|
|
296
296
|
try {
|
|
297
|
-
if (!
|
|
298
|
-
let { data: e, subtitles: t, finished: r } = await
|
|
297
|
+
if (!W) {
|
|
298
|
+
let { data: e, subtitles: t, finished: r } = await j.read();
|
|
299
299
|
if (n() || (r && (H = !0), t?.length && s.pushFragments(t), !e.byteLength)) return;
|
|
300
|
-
|
|
300
|
+
W = e, G = 0;
|
|
301
301
|
}
|
|
302
|
-
await
|
|
302
|
+
await _e(), !n() && K && (K = !1, re?.());
|
|
303
303
|
} catch (e) {
|
|
304
|
-
|
|
304
|
+
q(e, n());
|
|
305
305
|
} finally {
|
|
306
|
-
e ===
|
|
306
|
+
e === le && (R = !1);
|
|
307
307
|
}
|
|
308
|
-
},
|
|
308
|
+
}, ye = (e) => e >= de && !!me(), be = (e) => i(A).some((t) => t.start <= e + T && e < t.end), Y = (e) => {
|
|
309
309
|
let t = i(A).find((t) => t.start <= e + T && e < t.end);
|
|
310
310
|
return t ? t.end - e : 0;
|
|
311
|
-
},
|
|
312
|
-
if (!
|
|
311
|
+
}, xe = async (e) => {
|
|
312
|
+
if (!N && !(Y(e) >= E)) {
|
|
313
313
|
H = !1, B++, V = e;
|
|
314
314
|
try {
|
|
315
|
-
|
|
316
|
-
let t =
|
|
317
|
-
for (let n = 0; n < ee && !(
|
|
318
|
-
let t =
|
|
319
|
-
if (await
|
|
315
|
+
be(e) || await J(e);
|
|
316
|
+
let t = U;
|
|
317
|
+
for (let n = 0; n < ee && !(N || t !== U || H || Y(e) >= E); n++) {
|
|
318
|
+
let t = Y(e);
|
|
319
|
+
if (await ve(), Y(e) <= t) break;
|
|
320
320
|
}
|
|
321
321
|
} finally {
|
|
322
322
|
B--, V === e && (V = void 0);
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
-
},
|
|
326
|
-
let t = performance.now(), n = t -
|
|
325
|
+
}, Se = 0, X = null, Z, Ce = (e) => {
|
|
326
|
+
let t = performance.now(), n = t - Se;
|
|
327
327
|
if (n >= D) {
|
|
328
|
-
|
|
328
|
+
Se = t, X = null, ie?.(e);
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
let e =
|
|
334
|
-
|
|
331
|
+
X = e, !Z && (Z = setTimeout(() => {
|
|
332
|
+
Z = void 0;
|
|
333
|
+
let e = X;
|
|
334
|
+
X = null, !(e === null || N) && (Se = performance.now(), ie?.(e));
|
|
335
335
|
}, D - n));
|
|
336
336
|
};
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
M.push(() => {
|
|
338
|
+
Z && clearTimeout(Z);
|
|
339
339
|
});
|
|
340
|
-
let
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
let we = 0, Q;
|
|
341
|
+
M.push(() => {
|
|
342
|
+
Q && clearTimeout(Q);
|
|
343
343
|
});
|
|
344
|
-
let
|
|
344
|
+
let Te = () => {
|
|
345
345
|
let t = n.currentTime, r = e.info.input.duration || n.duration;
|
|
346
|
-
r > 0 &&
|
|
347
|
-
let i = performance.now(), a = i -
|
|
348
|
-
if (
|
|
349
|
-
if (H = !1,
|
|
350
|
-
|
|
346
|
+
r > 0 && Ce(Math.min(Math.max(t / r, 0), 1));
|
|
347
|
+
let i = performance.now(), a = i - we < O;
|
|
348
|
+
if (we = i, !ye(t) && !(z && Math.abs(t - de) < .001)) {
|
|
349
|
+
if (H = !1, Q && clearTimeout(Q), !a) {
|
|
350
|
+
Q = void 0, J(t);
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
|
-
|
|
354
|
-
if (
|
|
353
|
+
Q = setTimeout(() => {
|
|
354
|
+
if (Q = void 0, N) return;
|
|
355
355
|
let e = n.currentTime;
|
|
356
|
-
|
|
356
|
+
ye(e) || (H = !1, J(e));
|
|
357
357
|
}, O);
|
|
358
358
|
}
|
|
359
359
|
};
|
|
360
|
-
n.addEventListener("seeking",
|
|
361
|
-
let
|
|
362
|
-
if (
|
|
363
|
-
|
|
360
|
+
n.addEventListener("seeking", Te), M.push(() => n.removeEventListener("seeking", Te));
|
|
361
|
+
let $ = !1, Ee = () => {
|
|
362
|
+
if ($) return;
|
|
363
|
+
$ = !0;
|
|
364
364
|
let e = n.error;
|
|
365
|
-
|
|
365
|
+
q(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
|
|
366
366
|
};
|
|
367
|
-
n.addEventListener("error", Ee),
|
|
367
|
+
n.addEventListener("error", Ee), M.push(() => n.removeEventListener("error", Ee));
|
|
368
368
|
let De = setInterval(() => {
|
|
369
|
-
|
|
369
|
+
$ || n.error || (fe().catch(() => {}), ve().catch((e) => q(e, !1)), H && !W && he() && m.readyState === "open" && L().catch(() => {}));
|
|
370
370
|
}, 100);
|
|
371
|
-
return
|
|
372
|
-
destroy:
|
|
373
|
-
prepareSeek:
|
|
371
|
+
return M.push(() => clearInterval(De)), {
|
|
372
|
+
destroy: P,
|
|
373
|
+
prepareSeek: xe,
|
|
374
374
|
videoElement: n,
|
|
375
375
|
selectSubtitleStream: (e) => s.selectStream(e),
|
|
376
376
|
indexes: e.indexes ?? [],
|
|
@@ -380,10 +380,10 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
380
380
|
audioMimeType: e.info.output.audioMimeType
|
|
381
381
|
};
|
|
382
382
|
} catch (e) {
|
|
383
|
-
throw
|
|
383
|
+
throw P(), e;
|
|
384
384
|
}
|
|
385
|
-
}, ne = 5, re = 500,
|
|
386
|
-
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s =
|
|
385
|
+
}, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, j = 1e4, M = 1.5, N = 4e6, P = async (e) => {
|
|
386
|
+
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = ie } = e, c = await t({
|
|
387
387
|
publicPath: n,
|
|
388
388
|
workerUrl: r,
|
|
389
389
|
workerOptions: { type: "module" },
|
|
@@ -391,36 +391,39 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
391
391
|
read: a
|
|
392
392
|
});
|
|
393
393
|
try {
|
|
394
|
-
let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? ne, n / re), a = []
|
|
395
|
-
|
|
396
|
-
let
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
394
|
+
let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? ne, n / re), a = [], l = (e) => {
|
|
395
|
+
let t = [];
|
|
396
|
+
for (let [a, o] of e.entries()) {
|
|
397
|
+
let s = t.at(-1);
|
|
398
|
+
s && o.timestamp - s.timestamp < r || t.push({
|
|
399
|
+
timestamp: o.timestamp,
|
|
400
|
+
endTime: n,
|
|
401
|
+
startByte: o.pos,
|
|
402
|
+
endByte: Math.min((e[a + 1]?.pos ?? i) + ae, i),
|
|
403
|
+
done: !1,
|
|
404
|
+
attempts: 0
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
for (let [e, r] of t.entries()) r.endTime = t[e + 1]?.timestamp ?? n;
|
|
408
|
+
return t.length > 1 && t.at(-1).timestamp > n - r * 2 && t.pop(), t;
|
|
409
|
+
};
|
|
410
|
+
a = l(t.indexes);
|
|
411
|
+
let u = [], d = !1, f = [], p = !1, m, h = -1, g = !1, _ = !1, v, y = () => {
|
|
412
|
+
let e = m;
|
|
410
413
|
if (e !== void 0) {
|
|
411
|
-
let t =
|
|
414
|
+
let t = f.findIndex(({ timestamp: t, endTime: n }) => t <= e && e < n);
|
|
412
415
|
if (t >= 0) return t;
|
|
413
416
|
}
|
|
414
417
|
return 0;
|
|
415
|
-
},
|
|
418
|
+
}, b = () => {
|
|
416
419
|
let e = [];
|
|
417
|
-
for (let [t, n] of
|
|
420
|
+
for (let [t, n] of u.entries()) {
|
|
418
421
|
n.startTime - (e.at(-1)?.endTime ?? 0) > .01 && e.push({
|
|
419
422
|
url: "",
|
|
420
423
|
startTime: e.at(-1)?.endTime ?? 0,
|
|
421
424
|
endTime: n.startTime
|
|
422
425
|
}), e.push(n);
|
|
423
|
-
let r =
|
|
426
|
+
let r = u[t + 1];
|
|
424
427
|
r && r.startTime - n.endTime > .01 && e.push({
|
|
425
428
|
url: "",
|
|
426
429
|
startTime: n.endTime,
|
|
@@ -433,48 +436,83 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
433
436
|
startTime: t,
|
|
434
437
|
endTime: n
|
|
435
438
|
}), o(e);
|
|
436
|
-
},
|
|
437
|
-
if (
|
|
438
|
-
let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")),
|
|
439
|
+
}, x = async (e) => {
|
|
440
|
+
if (d) return;
|
|
441
|
+
let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), j))]), n = await createImageBitmap(new Blob([t], { type: "image/png" })), r = new OffscreenCanvas(s, Math.max(1, Math.round(n.height * (s / n.width))));
|
|
439
442
|
r.getContext("2d").drawImage(n, 0, 0, r.width, r.height), n.close();
|
|
440
443
|
let i = await r.convertToBlob({
|
|
441
444
|
type: "image/webp",
|
|
442
445
|
quality: .7
|
|
443
446
|
});
|
|
444
|
-
|
|
447
|
+
d || (u = [...u, {
|
|
445
448
|
url: URL.createObjectURL(i),
|
|
446
449
|
startTime: e.timestamp,
|
|
447
450
|
endTime: e.endTime
|
|
448
|
-
}].sort((e, t) => e.startTime - t.startTime),
|
|
449
|
-
},
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
}].sort((e, t) => e.startTime - t.startTime), b());
|
|
452
|
+
}, S = () => {
|
|
453
|
+
if (p || d || _) return;
|
|
454
|
+
if (g) {
|
|
455
|
+
g = !1;
|
|
456
|
+
let e = v;
|
|
457
|
+
w(T(e)).then(() => {
|
|
458
|
+
E(e), S();
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (!f.length) return;
|
|
463
|
+
let e = f.splice(y(), 1)[0];
|
|
464
|
+
p = !0, x(e).catch(() => {
|
|
465
|
+
e.attempts += 1, e.done = e.attempts >= oe;
|
|
454
466
|
}).finally(() => {
|
|
455
|
-
|
|
467
|
+
p = !1, S();
|
|
456
468
|
});
|
|
457
|
-
},
|
|
458
|
-
e.done = !0,
|
|
469
|
+
}, C = (e) => {
|
|
470
|
+
e.done = !0, f.push(e), S();
|
|
471
|
+
}, w = async (e) => {
|
|
472
|
+
_ = !0;
|
|
473
|
+
try {
|
|
474
|
+
let t = await c.init();
|
|
475
|
+
if (d) return;
|
|
476
|
+
h = e;
|
|
477
|
+
let n = l(t.indexes);
|
|
478
|
+
if (!n.length) return;
|
|
479
|
+
let r = new Map(u.map((e) => [e.startTime, e]));
|
|
480
|
+
for (let e of n) r.has(e.timestamp) && (e.done = !0);
|
|
481
|
+
a = n;
|
|
482
|
+
let i = new Set(n.map((e) => e.timestamp));
|
|
483
|
+
for (let e of u) i.has(e.startTime) || URL.revokeObjectURL(e.url);
|
|
484
|
+
u = n.filter((e) => r.has(e.timestamp)).map((e) => ({
|
|
485
|
+
url: r.get(e.timestamp).url,
|
|
486
|
+
startTime: e.timestamp,
|
|
487
|
+
endTime: e.endTime
|
|
488
|
+
})), f.length = 0, b();
|
|
489
|
+
} catch {} finally {
|
|
490
|
+
_ = !1;
|
|
491
|
+
}
|
|
492
|
+
}, T = (e) => e ? e.reduce((e, [, t]) => Math.max(e, t), 0) : i, E = (e) => {
|
|
493
|
+
for (let t of a) t.done || (!e || e.some(([e, n]) => e <= t.startByte && t.endByte <= n)) && C(t);
|
|
459
494
|
};
|
|
460
|
-
return
|
|
495
|
+
return b(), {
|
|
461
496
|
update: (e) => {
|
|
462
|
-
if (
|
|
497
|
+
if (d) return;
|
|
498
|
+
v = e;
|
|
499
|
+
let t = T(e);
|
|
500
|
+
h < 0 ? h = t : (t >= Math.max(h * M, h + N) || t >= i && h < i) && (g = !0), E(e), S();
|
|
463
501
|
},
|
|
464
502
|
prioritize: (e) => {
|
|
465
|
-
|
|
503
|
+
d || (m = e);
|
|
466
504
|
},
|
|
467
505
|
destroy: () => {
|
|
468
|
-
|
|
469
|
-
for (let e of
|
|
470
|
-
|
|
506
|
+
d = !0, f.length = 0;
|
|
507
|
+
for (let e of u) URL.revokeObjectURL(e.url);
|
|
508
|
+
u = [], A(c);
|
|
471
509
|
}
|
|
472
510
|
};
|
|
473
511
|
} catch (e) {
|
|
474
512
|
throw A(c), e;
|
|
475
513
|
}
|
|
476
|
-
},
|
|
477
|
-
let { video: t, subtitles: n } = e, r = e.mode ??
|
|
514
|
+
}, F = 1280, I = 1920, se = 250, L = 100, R = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, z = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, B = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, V = () => R() ? z() ? "window" : B() ? "burn-in" : null : null, ce = (e) => {
|
|
515
|
+
let { video: t, subtitles: n } = e, r = e.mode ?? V() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? I : F), 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) => {
|
|
478
516
|
r.drawImage(t, 0, 0, e.width, e.height);
|
|
479
517
|
let i = n.querySelector("canvas");
|
|
480
518
|
i && i.width > 0 && i.height > 0 && r.drawImage(i, 0, 0, e.width, e.height);
|
|
@@ -544,7 +582,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
544
582
|
};
|
|
545
583
|
if (a = f, u.srcObject = f.stream, (e.container ?? t.parentElement ?? document.body).appendChild(u), v(), s = setInterval(() => {
|
|
546
584
|
t.paused && h();
|
|
547
|
-
},
|
|
585
|
+
}, se), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
|
|
548
586
|
let t = () => {
|
|
549
587
|
clearTimeout(n), e();
|
|
550
588
|
}, n = setTimeout(t, 1e3);
|
|
@@ -590,4 +628,4 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
590
628
|
};
|
|
591
629
|
};
|
|
592
630
|
//#endregion
|
|
593
|
-
export { g as a, A as c, i as d, o as f, k as i, s as l,
|
|
631
|
+
export { g as a, A as c, i as d, o as f, k as i, s as l, V as n, _ as o, P as r, te as s, ce 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-DqyZgmTv.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 } from "react";
|
|
3
3
|
import { css as h, keyframes as g } from "@emotion/react";
|
|
4
4
|
import { createPlayer as _, useContainerAttach as v, useMediaAttach as y } from "@videojs/react";
|
package/package.json
CHANGED
|
@@ -25,6 +25,17 @@ const READAHEAD = 1_000_000
|
|
|
25
25
|
const MAX_ATTEMPTS = 3
|
|
26
26
|
// a keyframe decode can hang without ever settling
|
|
27
27
|
const KEYFRAME_TIMEOUT = 10_000
|
|
28
|
+
/**
|
|
29
|
+
* How much more of the file has to become readable before the index is walked again.
|
|
30
|
+
*
|
|
31
|
+
* An index is only as complete as the bytes behind it, and over a torrent those arrive for minutes
|
|
32
|
+
* after the player starts. Re-walking on every range change would demux the file over and over; a
|
|
33
|
+
* multiplier makes the number of walks logarithmic in the file size instead, so a download that
|
|
34
|
+
* starts at 2% re-indexes around eight times on its way to whole rather than hundreds.
|
|
35
|
+
*/
|
|
36
|
+
const REINDEX_GROWTH = 1.5
|
|
37
|
+
/** And never re-walk for a trickle, however early. */
|
|
38
|
+
const REINDEX_MIN_BYTES = 4_000_000
|
|
28
39
|
|
|
29
40
|
export type ThumbnailGenerator = {
|
|
30
41
|
/** Report which byte ranges are readable. Called with no argument when the whole file is. */
|
|
@@ -60,22 +71,37 @@ export const createThumbnailGenerator = async (options: ThumbnailGeneratorOption
|
|
|
60
71
|
const interval = Math.max(options.interval ?? INTERVAL, duration / MAX_THUMBNAILS)
|
|
61
72
|
|
|
62
73
|
type Slot = { timestamp: number, endTime: number, startByte: number, endByte: number, done: boolean, attempts: number }
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
let slots: Slot[] = []
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The slot list for one index, which is only ever as complete as the bytes it was built from.
|
|
78
|
+
*
|
|
79
|
+
* libav walks the clusters it can read and stops, WITHOUT failing: on a file whose first tenth is
|
|
80
|
+
* readable it reports a single keyframe and a correct duration, because the duration comes from
|
|
81
|
+
* the header. That single entry then becomes a single slot whose endTime falls through to the
|
|
82
|
+
* duration, which is one preview covering the whole seekbar.
|
|
83
|
+
*/
|
|
84
|
+
const buildSlots = (indexes: typeof metadata.indexes): Slot[] => {
|
|
85
|
+
const built: Slot[] = []
|
|
86
|
+
for (const [i, index] of indexes.entries()) {
|
|
87
|
+
const last = built.at(-1)
|
|
88
|
+
if (last && index.timestamp - last.timestamp < interval) continue
|
|
89
|
+
built.push({
|
|
90
|
+
timestamp: index.timestamp,
|
|
91
|
+
endTime: duration,
|
|
92
|
+
startByte: index.pos,
|
|
93
|
+
endByte: Math.min((indexes[i + 1]?.pos ?? length) + READAHEAD, length),
|
|
94
|
+
done: false,
|
|
95
|
+
attempts: 0,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
for (const [i, slot] of built.entries()) slot.endTime = built[i + 1]?.timestamp ?? duration
|
|
99
|
+
// reading the last keyframe runs the demuxer into EOF, which crashes the libav build
|
|
100
|
+
if (built.length > 1 && (built.at(-1)!.timestamp > duration - interval * 2)) built.pop()
|
|
101
|
+
return built
|
|
75
102
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (slots.length > 1 && (slots.at(-1)!.timestamp > duration - interval * 2)) slots.pop()
|
|
103
|
+
|
|
104
|
+
slots = buildSlots(metadata.indexes)
|
|
79
105
|
|
|
80
106
|
let thumbnails: ThumbnailImage[] = []
|
|
81
107
|
let destroyed = false
|
|
@@ -93,6 +119,19 @@ export const createThumbnailGenerator = async (options: ThumbnailGeneratorOption
|
|
|
93
119
|
/** Where the pointer is on the seekbar, or undefined when it is off it. */
|
|
94
120
|
let priorityTime: number | undefined
|
|
95
121
|
|
|
122
|
+
/**
|
|
123
|
+
* How much of the file was readable when the current index was built.
|
|
124
|
+
*
|
|
125
|
+
* Negative until the first update, which establishes it: the boot walk has just happened against
|
|
126
|
+
* whatever was readable then, so the first report of the ranges is the baseline rather than a
|
|
127
|
+
* reason to walk again.
|
|
128
|
+
*/
|
|
129
|
+
let indexedBytes = -1
|
|
130
|
+
let reindexWanted = false
|
|
131
|
+
let reindexing = false
|
|
132
|
+
/** The ranges last reported, so a walk deferred behind a decode can still claim against them. */
|
|
133
|
+
let lastRanges: [number, number][] | undefined
|
|
134
|
+
|
|
96
135
|
/*
|
|
97
136
|
* The slot to decode next: the one under the pointer when it is still waiting, else the oldest claim.
|
|
98
137
|
*
|
|
@@ -146,7 +185,25 @@ export const createThumbnailGenerator = async (options: ThumbnailGeneratorOption
|
|
|
146
185
|
|
|
147
186
|
// one decode at a time, because there is one wasm worker behind them all
|
|
148
187
|
const pump = () => {
|
|
149
|
-
if (running || destroyed ||
|
|
188
|
+
if (running || destroyed || reindexing) return
|
|
189
|
+
/*
|
|
190
|
+
* A deferred index walk goes first.
|
|
191
|
+
*
|
|
192
|
+
* There is one worker behind both, and a walk started while a decode holds it would have them
|
|
193
|
+
* interleaved on the same demuxer. Taking it here means the walk happens at the one moment
|
|
194
|
+
* nothing else is using it, and it happens BEFORE the next decode so that decode comes from
|
|
195
|
+
* the new slot list rather than the stale one.
|
|
196
|
+
*/
|
|
197
|
+
if (reindexWanted) {
|
|
198
|
+
reindexWanted = false
|
|
199
|
+
const ranges = lastRanges
|
|
200
|
+
void reindex(readableTo(ranges)).then(() => {
|
|
201
|
+
claimReadable(ranges)
|
|
202
|
+
pump()
|
|
203
|
+
})
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
if (!pending.length) return
|
|
150
207
|
const slot = pending.splice(nextIndex(), 1)[0]!
|
|
151
208
|
running = true
|
|
152
209
|
void decode(slot)
|
|
@@ -167,15 +224,74 @@ export const createThumbnailGenerator = async (options: ThumbnailGeneratorOption
|
|
|
167
224
|
pump()
|
|
168
225
|
}
|
|
169
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Walk the index again now that more of the file can be read, and keep what is already drawn.
|
|
229
|
+
*
|
|
230
|
+
* A preview survives if its slot survives, which is why the previous timestamps are matched
|
|
231
|
+
* rather than the list being thrown away: the first slot is almost always still the first slot,
|
|
232
|
+
* and re-decoding it would throw away work and flicker the seekbar for no reason. What DOES
|
|
233
|
+
* change is its endTime, since a slot that used to run to the end of the file now runs only as
|
|
234
|
+
* far as the neighbour the new index revealed.
|
|
235
|
+
*/
|
|
236
|
+
const reindex = async (readable: number) => {
|
|
237
|
+
reindexing = true
|
|
238
|
+
try {
|
|
239
|
+
const next = await remuxer.init()
|
|
240
|
+
if (destroyed) return
|
|
241
|
+
indexedBytes = readable
|
|
242
|
+
const rebuilt = buildSlots(next.indexes)
|
|
243
|
+
if (!rebuilt.length) return
|
|
244
|
+
|
|
245
|
+
const drawn = new Map(thumbnails.map((t) => [t.startTime, t]))
|
|
246
|
+
for (const slot of rebuilt) {
|
|
247
|
+
if (drawn.has(slot.timestamp)) slot.done = true
|
|
248
|
+
}
|
|
249
|
+
slots = rebuilt
|
|
250
|
+
// a preview whose slot is gone is no longer addressable, and its blob would leak
|
|
251
|
+
const kept = new Set(rebuilt.map((slot) => slot.timestamp))
|
|
252
|
+
for (const t of thumbnails) if (!kept.has(t.startTime)) URL.revokeObjectURL(t.url)
|
|
253
|
+
thumbnails = rebuilt
|
|
254
|
+
.filter((slot) => drawn.has(slot.timestamp))
|
|
255
|
+
.map((slot) => ({ url: drawn.get(slot.timestamp)!.url, startTime: slot.timestamp, endTime: slot.endTime }))
|
|
256
|
+
// anything claimed against the old list is stale, and the update below re-claims from the new one
|
|
257
|
+
pending.length = 0
|
|
258
|
+
emit()
|
|
259
|
+
} catch {
|
|
260
|
+
// an index that could not be re-read leaves the old one in place, and the next update retries
|
|
261
|
+
} finally {
|
|
262
|
+
reindexing = false
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The end of what can be read, which is what an index walk can reach. */
|
|
267
|
+
const readableTo = (ranges?: [number, number][]) =>
|
|
268
|
+
ranges ? ranges.reduce((most, [, to]) => Math.max(most, to), 0) : length
|
|
269
|
+
|
|
270
|
+
const claimReadable = (ranges?: [number, number][]) => {
|
|
271
|
+
for (const slot of slots) {
|
|
272
|
+
if (slot.done) continue
|
|
273
|
+
if (!ranges || ranges.some(([from, to]) => from <= slot.startByte && slot.endByte <= to)) claim(slot)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
170
277
|
emit()
|
|
171
278
|
|
|
172
279
|
return {
|
|
173
280
|
update: (ranges) => {
|
|
174
281
|
if (destroyed) return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
282
|
+
lastRanges = ranges
|
|
283
|
+
const readable = readableTo(ranges)
|
|
284
|
+
if (indexedBytes < 0) indexedBytes = readable
|
|
285
|
+
else {
|
|
286
|
+
const grown = readable >= Math.max(indexedBytes * REINDEX_GROWTH, indexedBytes + REINDEX_MIN_BYTES)
|
|
287
|
+
// and one last walk when the file becomes whole, so a finished download is fully indexed
|
|
288
|
+
const whole = readable >= length && indexedBytes < length
|
|
289
|
+
if (grown || whole) reindexWanted = true
|
|
178
290
|
}
|
|
291
|
+
claimReadable(ranges)
|
|
292
|
+
// claims first, so a walk that has to wait for a decode does not hold up the previews that
|
|
293
|
+
// the current index can already produce
|
|
294
|
+
pump()
|
|
179
295
|
},
|
|
180
296
|
prioritize: (time) => {
|
|
181
297
|
if (destroyed) return
|