@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.
@@ -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-b5_5HscR.js";
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: j, onSubtitleStreams: ie, onAudioStreams: ae } = t, M = await e({
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
- }), N = [], P = !1, F = () => {
192
- if (!P) {
193
- P = !0;
194
- for (let e of [...N].reverse()) try {
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
- N.push(() => A(M));
199
+ M.push(() => A(j));
200
200
  try {
201
- let e = await M.init(), t = e.audioStreams ?? [], a = t.some((e) => e.streamIndex === _) ? _ : t[0]?.streamIndex ?? -1;
202
- ae?.(t, a);
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: ie
210
+ onStreams: ae
211
211
  });
212
- if (N.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");
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
- N.push(() => URL.revokeObjectURL(k)), n.src = k, N.push(() => {
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: I, unbufferRange: L, updateTimestampOffset: oe, endOfStream: se } = o(A, m);
241
- await I(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, U = 0, W = 0, G = null, K = 0, le = 0, q = !1, ue = 0, J = (e, t, n = !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), !(q && !n) && (q = !0, ne?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
245
- }, de = async (e = !1) => {
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 L(e, t + r), n > t + a && await L(t + a, n);
249
- }, fe = () => {
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
- }, pe = () => {
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 && (le = Math.max(le, ...e));
263
- }, Y = async (e) => {
264
- z = !0, G = null, K = 0, ue = e;
265
- let t = ++W;
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 M.seek(e);
268
- if (P || t !== W || (a && (H = !0), i?.length && s.pushFragments(i), await oe(r), P || t !== W)) return;
269
- n.byteLength && (await I(n), he());
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
- J(e, P || t !== W);
271
+ q(e, N || t !== U);
272
272
  } finally {
273
- t === W && (z = !1);
273
+ t === U && (z = !1);
274
274
  }
275
- }, ge = async () => {
276
- let e = G;
275
+ }, _e = async () => {
276
+ let e = W;
277
277
  if (e) try {
278
- await I(e), he(), G === e && (G = null, K = 0);
278
+ await F(e), ge(), W === e && (W = null, G = 0);
279
279
  } catch (t) {
280
- if (G !== e) return;
280
+ if (W !== e) return;
281
281
  if (t?.name === "QuotaExceededError") {
282
- let t = await de(!0).then(() => !0, () => !1);
283
- if (G !== e) return;
282
+ let t = await fe(!0).then(() => !0, () => !1);
283
+ if (W !== e) return;
284
284
  if (t) {
285
- H = !1, await Y(pe()?.end ?? n.currentTime);
285
+ H = !1, await J(me()?.end ?? n.currentTime);
286
286
  return;
287
287
  }
288
288
  }
289
- if (K += 1, K < C) return;
290
- throw G = null, t;
289
+ if (G += 1, G < C) return;
290
+ throw W = null, t;
291
291
  }
292
- }, _e = async () => {
293
- if (R || z || $ !== void 0 || P || !G && (H || !fe())) return;
294
- let e = ++U, t = W, n = () => P || e !== U || t !== W;
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 (!G) {
298
- let { data: e, subtitles: t, finished: r } = await M.read();
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
- G = e, K = 0;
300
+ W = e, G = 0;
301
301
  }
302
- await ge(), !n() && q && (q = !1, re?.());
302
+ await _e(), !n() && K && (K = !1, re?.());
303
303
  } catch (e) {
304
- J(e, n());
304
+ q(e, n());
305
305
  } finally {
306
- e === U && (R = !1);
306
+ e === le && (R = !1);
307
307
  }
308
- }, ve = (e) => e >= ue && !!pe(), ye = (e) => i(A).some((t) => t.start <= e + T && e < t.end), X = (e) => {
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
- }, be = async (e) => {
312
- if (!P && !(X(e) >= E)) {
311
+ }, xe = async (e) => {
312
+ if (!N && !(Y(e) >= E)) {
313
313
  H = !1, B++, V = e;
314
314
  try {
315
- ye(e) || await Y(e);
316
- let t = W;
317
- for (let n = 0; n < ee && !(P || t !== W || H || X(e) >= E); n++) {
318
- let t = X(e);
319
- if (await _e(), X(e) <= t) break;
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
- }, xe = 0, Z = null, Q, Se = (e) => {
326
- let t = performance.now(), n = t - xe;
325
+ }, Se = 0, X = null, Z, Ce = (e) => {
326
+ let t = performance.now(), n = t - Se;
327
327
  if (n >= D) {
328
- xe = t, Z = null, j?.(e);
328
+ Se = t, X = null, ie?.(e);
329
329
  return;
330
330
  }
331
- Z = e, !Q && (Q = setTimeout(() => {
332
- Q = void 0;
333
- let e = Z;
334
- Z = null, !(e === null || P) && (xe = performance.now(), j?.(e));
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
- N.push(() => {
338
- Q && clearTimeout(Q);
337
+ M.push(() => {
338
+ Z && clearTimeout(Z);
339
339
  });
340
- let Ce = 0, $;
341
- N.push(() => {
342
- $ && clearTimeout($);
340
+ let we = 0, Q;
341
+ M.push(() => {
342
+ Q && clearTimeout(Q);
343
343
  });
344
- let we = () => {
344
+ let Te = () => {
345
345
  let t = n.currentTime, r = e.info.input.duration || n.duration;
346
- r > 0 && Se(Math.min(Math.max(t / r, 0), 1));
347
- let i = performance.now(), a = i - Ce < O;
348
- if (Ce = i, !ve(t) && !(z && Math.abs(t - ue) < .001)) {
349
- if (H = !1, $ && clearTimeout($), !a) {
350
- $ = void 0, Y(t);
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
- $ = setTimeout(() => {
354
- if ($ = void 0, P) return;
353
+ Q = setTimeout(() => {
354
+ if (Q = void 0, N) return;
355
355
  let e = n.currentTime;
356
- ve(e) || (H = !1, Y(e));
356
+ ye(e) || (H = !1, J(e));
357
357
  }, O);
358
358
  }
359
359
  };
360
- n.addEventListener("seeking", we), N.push(() => n.removeEventListener("seeking", we));
361
- let Te = !1, Ee = () => {
362
- if (Te) return;
363
- Te = !0;
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
- J(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
365
+ q(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
366
366
  };
367
- n.addEventListener("error", Ee), N.push(() => n.removeEventListener("error", Ee));
367
+ n.addEventListener("error", Ee), M.push(() => n.removeEventListener("error", Ee));
368
368
  let De = setInterval(() => {
369
- Te || n.error || (de().catch(() => {}), _e().catch((e) => J(e, !1)), H && !G && me() && m.readyState === "open" && se().catch(() => {}));
369
+ $ || n.error || (fe().catch(() => {}), ve().catch((e) => q(e, !1)), H && !W && he() && m.readyState === "open" && L().catch(() => {}));
370
370
  }, 100);
371
- return N.push(() => clearInterval(De)), {
372
- destroy: F,
373
- prepareSeek: be,
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 F(), e;
383
+ throw P(), e;
384
384
  }
385
- }, ne = 5, re = 500, j = 320, ie = 1e6, ae = 3, M = 1e4, N = async (e) => {
386
- let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = j } = e, c = await t({
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
- for (let [e, o] of t.indexes.entries()) {
396
- let s = a.at(-1);
397
- s && o.timestamp - s.timestamp < r || a.push({
398
- timestamp: o.timestamp,
399
- endTime: n,
400
- startByte: o.pos,
401
- endByte: Math.min((t.indexes[e + 1]?.pos ?? i) + ie, i),
402
- done: !1,
403
- attempts: 0
404
- });
405
- }
406
- for (let [e, t] of a.entries()) t.endTime = a[e + 1]?.timestamp ?? n;
407
- a.length > 1 && a.at(-1).timestamp > n - r * 2 && a.pop();
408
- let l = [], u = !1, d = [], f = !1, p, m = () => {
409
- let e = p;
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 = d.findIndex(({ timestamp: t, endTime: n }) => t <= e && e < n);
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
- }, h = () => {
418
+ }, b = () => {
416
419
  let e = [];
417
- for (let [t, n] of l.entries()) {
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 = l[t + 1];
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
- }, g = async (e) => {
437
- if (u) return;
438
- let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), M))]), n = await createImageBitmap(new Blob([t], { type: "image/png" })), r = new OffscreenCanvas(s, Math.max(1, Math.round(n.height * (s / n.width))));
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
- u || (l = [...l, {
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), h());
449
- }, _ = () => {
450
- if (f || u || !d.length) return;
451
- let e = d.splice(m(), 1)[0];
452
- f = !0, g(e).catch(() => {
453
- e.attempts += 1, e.done = e.attempts >= ae;
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
- f = !1, _();
467
+ p = !1, S();
456
468
  });
457
- }, v = (e) => {
458
- e.done = !0, d.push(e), _();
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 h(), {
495
+ return b(), {
461
496
  update: (e) => {
462
- if (!u) for (let t of a) t.done || (!e || e.some(([e, n]) => e <= t.startByte && t.endByte <= n)) && v(t);
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
- u || (p = e);
503
+ d || (m = e);
466
504
  },
467
505
  destroy: () => {
468
- u = !0, d.length = 0;
469
- for (let e of l) URL.revokeObjectURL(e.url);
470
- l = [], A(c);
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
- }, P = 1280, F = 1920, I = 250, L = 100, oe = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, se = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, R = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, z = () => oe() ? se() ? "window" : R() ? "burn-in" : null : null, B = (e) => {
477
- let { video: t, subtitles: n } = e, r = e.mode ?? z() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? F : P), 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
+ }, 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
- }, I), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
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, z as n, _ as o, N as r, te as s, B as t, h as u };
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-b5_5HscR.js";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banou/media-player",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
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",
@@ -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
- const slots: Slot[] = []
64
- for (const [i, index] of metadata.indexes.entries()) {
65
- const last = slots.at(-1)
66
- if (last && index.timestamp - last.timestamp < interval) continue
67
- slots.push({
68
- timestamp: index.timestamp,
69
- endTime: duration,
70
- startByte: index.pos,
71
- endByte: Math.min((metadata.indexes[i + 1]?.pos ?? length) + READAHEAD, length),
72
- done: false,
73
- attempts: 0,
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
- for (const [i, slot] of slots.entries()) slot.endTime = slots[i + 1]?.timestamp ?? duration
77
- // reading the last keyframe runs the demuxer into EOF, which crashes the libav build
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 || !pending.length) return
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
- for (const slot of slots) {
176
- if (slot.done) continue
177
- if (!ranges || ranges.some(([from, to]) => from <= slot.startByte && slot.endByte <= to)) claim(slot)
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