@banou/media-player 0.10.13 → 0.10.15

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-DdbOiEX3.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-DQ537ehC.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, P = () => {
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: F, unbufferRange: se, updateTimestampOffset: ce, endOfStream: le } = o(A, m);
241
- await F(e.data), e.subtitles?.length && s.pushFragments(e.subtitles), te?.();
242
- let I = !1, L = !1, R = 0, z, ue = () => z ?? n.currentTime, B = !1, de = 0, V = 0, H = null, U = 0, W = 0, G = !1, fe = 0, K = !0, q = (e, t, n = !1) => {
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), (!G || n) && (G = !0, ne?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
245
- }, pe = async (e = !1) => {
246
- if (R > 0 && !e) return;
247
- let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(A), s = (e, t) => (K = !1, se(e, t));
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
- }, me = () => {
250
+ }, he = () => {
251
251
  let e = i(A);
252
252
  if (!e.length) return !0;
253
- let t = ue(), n = e.find((e) => e.start <= t + T && t < e.end);
253
+ let t = de(), n = e.find((e) => e.start <= t + T && t < e.end);
254
254
  return n && n.end >= t + y ? !1 : !n || n.end < t + b;
255
- }, he = (e) => i(A).find((t) => t.start <= e + T && e < t.end), ge = () => he(n.currentTime), _e = (e = n.currentTime) => {
256
- let t = he(e);
257
- return e >= W - .1 || !!t && t.end >= W - .1;
258
- }, ve = () => {
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 && (W = Math.max(W, ...e));
261
- }, J = async (e) => {
262
- L = !0, H = null, U = 0, fe = e, K = !0;
263
- let t = ++V;
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 !== V || (a && (B = !0), i?.length && s.pushFragments(i), await ce(r), N || t !== V)) return;
267
- n.byteLength && (await F(n), ve());
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 === V && (K = !1), q(e, N || t !== V);
269
+ t === B && (K = !1), q(e, N || t !== B);
270
270
  } finally {
271
- t === V && (L = !1);
271
+ t === B && (I = !1);
272
272
  }
273
- }, ye = async () => {
274
- let e = H;
273
+ }, xe = async () => {
274
+ let e = V;
275
275
  if (e) try {
276
- await F(e), ve(), H === e && (H = null, U = 0);
276
+ await P(e), ye(), V === e && (V = null, H = 0);
277
277
  } catch (t) {
278
- if (H !== e) return;
278
+ if (V !== e) return;
279
279
  if (t?.name === "QuotaExceededError") {
280
- let t = await pe(!0).then(() => !0, () => !1);
281
- if (H !== e) return;
280
+ let t = await me(!0).then(() => !0, () => !1);
281
+ if (V !== e) return;
282
282
  if (t) {
283
- B = !1, await J(ge()?.end ?? n.currentTime);
283
+ z = !1, await J(_e()?.end ?? n.currentTime);
284
284
  return;
285
285
  }
286
286
  }
287
- if (U += 1, U < C) return;
288
- throw H = null, t;
287
+ if (H += 1, H < C) return;
288
+ throw V = null, t;
289
289
  }
290
- }, be = async () => {
291
- if (I || L || Q !== void 0 || N || !H && (B || !me())) return;
292
- let e = ++de, t = V, n = () => N || e !== de || t !== V;
293
- I = !0;
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 (!H) {
295
+ if (!V) {
296
296
  let { data: e, subtitles: t, finished: r } = await j.read();
297
- if (n() || (r && (B = !0), t?.length && s.pushFragments(t), !e.byteLength)) return;
298
- H = e, U = 0;
297
+ if (n() || (r && (z = !0), t?.length && s.pushFragments(t), !e.byteLength)) return;
298
+ V = e, H = 0;
299
299
  }
300
- await ye(), !n() && G && (G = !1, re?.());
300
+ await xe(), !n() && W && (W = !1, re?.());
301
301
  } catch (e) {
302
302
  q(e, n());
303
303
  } finally {
304
- e === de && (I = !1);
304
+ e === pe && (F = !1);
305
305
  }
306
- }, xe = (e) => e >= fe && Se(e) && Ce(e), Se = (e) => i(A).some((t) => t.start <= e && e < t.end), Y = (e) => {
307
- let t = he(e);
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
- }, Ce = (e) => Y(e) >= E || B && _e(e), we = async (e) => {
310
- if (!N && !(Se(e) && Ce(e))) {
311
- B = !1, R++, z = e;
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 = V;
315
- for (let n = 0; n < ee && !(N || t !== V || B || Y(e) >= E); n++) {
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 be(), Y(e) <= t) break;
317
+ if (await Se(), Y(e) <= t) break;
318
318
  }
319
319
  } finally {
320
- R--, z === e && (z = void 0);
320
+ L--, R === e && (R = void 0);
321
321
  }
322
322
  }
323
- }, Te = 0, X = null, Z, Ee = (e) => {
324
- let t = performance.now(), n = t - Te;
323
+ }, De = 0, X = null, Z, Oe = (e) => {
324
+ let t = performance.now(), n = t - De;
325
325
  if (n >= D) {
326
- Te = t, X = null, ie?.(e);
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) && (Te = performance.now(), ie?.(e));
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 De = 0, Q;
338
+ let ke = 0, Q;
339
339
  M.push(() => {
340
340
  Q && clearTimeout(Q);
341
341
  });
342
- let Oe = () => {
342
+ let Ae = () => {
343
343
  let t = n.currentTime, r = e.info.input.duration || n.duration;
344
- r > 0 && Ee(Math.min(Math.max(t / r, 0), 1));
345
- let i = performance.now(), a = i - De < O;
346
- if (De = i, !xe(t) && !(K && (L || Y(t) > 0) && Math.abs(t - fe) < .001)) {
347
- if (B = !1, Q && clearTimeout(Q), !a) {
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
- xe(e) || (B = !1, J(e));
354
+ Ce(e) || (z = !1, J(e));
355
355
  }, O);
356
356
  }
357
357
  };
358
- n.addEventListener("seeking", Oe), M.push(() => n.removeEventListener("seeking", Oe));
359
- let $ = !1, ke = () => {
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", ke), M.push(() => n.removeEventListener("error", ke));
366
- let Ae = setInterval(() => {
367
- $ || n.error || (pe().catch(() => {}), be().catch((e) => q(e, !1)), B && !H && _e() && m.readyState === "open" && le().catch(() => {}));
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(Ae)), {
370
- destroy: P,
371
- prepareSeek: we,
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 P(), e;
381
+ throw se(), e;
382
382
  }
383
- }, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, j = 1e4, M = 1.5, N = 4e6, P = 3e4, F = async (e) => {
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")), P))]);
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
- }, se = 1280, ce = 1920, le = 250, I = 100, L = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, R = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, z = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, ue = () => L() ? R() ? "window" : z() ? "burn-in" : null : null, B = (e) => {
513
- let { video: t, subtitles: n } = e, r = e.mode ?? ue() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? ce : se), 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) => {
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, I);
542
+ setTimeout(e, F);
543
543
  })).catch(() => {}).finally(() => {
544
544
  e.pause(), setTimeout(() => {
545
545
  l = !1;
546
- }, I);
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
- }, le), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
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, ue as n, _ as o, F as r, te as s, B as t, h as u };
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-DdbOiEX3.js";
1
+ import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-DQ537ehC.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";
@@ -2123,15 +2123,6 @@ ${gt}
2123
2123
  })]
2124
2124
  });
2125
2125
  }, It = .05, Lt = 5, Rt = 50, zt = 1e3, Bt = g`
2126
- display: flex;
2127
- flex-direction: column;
2128
- gap: calc(0.4 * var(--mp-unit));
2129
-
2130
- .hint {
2131
- opacity: 0.72;
2132
- font-size: 0.9em;
2133
- }
2134
- `, Vt = g`
2135
2126
  position: absolute;
2136
2127
  bottom: 0;
2137
2128
  width: 100%;
@@ -2261,7 +2252,7 @@ ${gt}
2261
2252
  }
2262
2253
  }
2263
2254
  }
2264
- `, Ht = () => {
2255
+ `, Vt = () => {
2265
2256
  let e = G(), t = G((e) => e.paused), n = G((e) => e.currentTime), r = G((e) => e.duration), i = G((e) => e.seekingTo), a = G((e) => e.fullscreen), o = G((e) => e.hideUI), s = G((e) => e.togglePictureInPicture), c = G((e) => e.pictureInPictureMode), l = G((e) => e.burnedInSubtitles), f = G((e) => e.passThroughPictureInPicture), h = G((e) => e.subtitleTracks), g = G((e) => e.requestSeek), [_, v] = m(null), y = p(null), b = p(void 0), x = c === "burn-in", S = h.length > 0, C = r > 0 && n === r, w = u(({ direction: t, stepSize: n }) => {
2266
2257
  let r = Ne(e.volume), i = t === "up" ? n : -n, a = Math.max(0, Math.min(1, r + i)), o = e.muted;
2267
2258
  e.setVolume(Me(a)), o && !e.muted && e.toggleMuted();
@@ -2332,7 +2323,7 @@ ${gt}
2332
2323
  e = Date.now() + zt;
2333
2324
  };
2334
2325
  }, [k]), d(() => () => clearInterval(b.current), []), /* @__PURE__ */ E("div", {
2335
- css: Vt,
2326
+ css: Bt,
2336
2327
  style: { ...o ? {
2337
2328
  opacity: "0",
2338
2329
  pointerEvents: "none"
@@ -2399,16 +2390,7 @@ ${gt}
2399
2390
  alt: ""
2400
2391
  })
2401
2392
  }),
2402
- toolTipText: /* @__PURE__ */ E("span", {
2403
- css: Bt,
2404
- children: [/* @__PURE__ */ T("span", {
2405
- className: "lead",
2406
- children: x ? S ? l ? "Subtitles are in the picture" : "Put the subtitles in the picture" : "This file has no subtitles" : "Picture in picture"
2407
- }), x && S ? /* @__PURE__ */ T("span", {
2408
- className: "hint",
2409
- children: "Then hover the video and click your browser's own pop out button"
2410
- }) : null]
2411
- })
2393
+ toolTipText: /* @__PURE__ */ T("span", { children: x ? S ? l ? "Subtitles are in the picture" : "Put the subtitles in the picture" : "This file has no subtitles" : "Picture in picture" })
2412
2394
  }) : null,
2413
2395
  /* @__PURE__ */ T($, {
2414
2396
  id: "full-screen",
@@ -2427,7 +2409,7 @@ ${gt}
2427
2409
  })]
2428
2410
  })]
2429
2411
  });
2430
- }, Ut = 9e3, Wt = g`
2412
+ }, Ht = 9e3, Ut = g`
2431
2413
  position: absolute;
2432
2414
  inset: 0;
2433
2415
  z-index: 2;
@@ -2458,22 +2440,11 @@ ${gt}
2458
2440
  opacity: 1;
2459
2441
  }
2460
2442
 
2461
- .words {
2462
- display: flex;
2463
- flex-direction: column;
2464
- gap: calc(0.3 * var(--mp-unit));
2465
- }
2466
-
2467
- .title {
2468
- ${Z.bMedium.bold}
2443
+ .text {
2444
+ ${Z.bMedium.medium}
2469
2445
  color: #fff;
2470
2446
  }
2471
2447
 
2472
- .body {
2473
- ${Z.bSmall.regular}
2474
- color: rgba(255, 255, 255, 0.75);
2475
- }
2476
-
2477
2448
  /* Points at the edge the browser's control lives on, which is the whole message. */
2478
2449
  .arrow {
2479
2450
  flex: none;
@@ -2481,7 +2452,7 @@ ${gt}
2481
2452
  line-height: 1;
2482
2453
  color: #fff;
2483
2454
  }
2484
- `, Gt = () => {
2455
+ `, Wt = () => {
2485
2456
  let e = G((e) => e.burnedInSubtitles), t = G((e) => e.pictureInPictureMode), [n, r] = m(!1);
2486
2457
  return d(() => {
2487
2458
  if (t !== "burn-in" || !e) {
@@ -2489,23 +2460,17 @@ ${gt}
2489
2460
  return;
2490
2461
  }
2491
2462
  r(!0);
2492
- let n = setTimeout(() => r(!1), Ut);
2463
+ let n = setTimeout(() => r(!1), Ht);
2493
2464
  return () => clearTimeout(n);
2494
2465
  }, [t, e]), t !== "burn-in" || !e ? null : /* @__PURE__ */ T("div", {
2495
- css: Wt,
2466
+ css: Ut,
2496
2467
  className: n ? "show" : "",
2497
2468
  role: "status",
2498
2469
  children: /* @__PURE__ */ E("div", {
2499
2470
  className: "card",
2500
- children: [/* @__PURE__ */ E("div", {
2501
- className: "words",
2502
- children: [/* @__PURE__ */ T("div", {
2503
- className: "title",
2504
- children: "Subtitles are in the picture"
2505
- }), /* @__PURE__ */ T("div", {
2506
- className: "body",
2507
- children: "Now hover the video and click your browser's own pop out button"
2508
- })]
2471
+ children: [/* @__PURE__ */ T("div", {
2472
+ className: "text",
2473
+ children: "Now click your browser's pop out button"
2509
2474
  }), /* @__PURE__ */ T("div", {
2510
2475
  className: "arrow",
2511
2476
  "aria-hidden": "true",
@@ -2513,10 +2478,10 @@ ${gt}
2513
2478
  })]
2514
2479
  })
2515
2480
  });
2516
- }, Kt = 1, qt = 6e3, Jt = 1, Yt = {
2481
+ }, Gt = 1, Kt = 6e3, qt = 1, Jt = {
2517
2482
  opening: "Skip Opening",
2518
2483
  ending: "Skip Ending"
2519
- }, Xt = g`
2484
+ }, Yt = g`
2520
2485
  position: absolute;
2521
2486
  inset: 0;
2522
2487
  z-index: 2;
@@ -2567,10 +2532,10 @@ ${gt}
2567
2532
  bottom: calc(9 * var(--mp-unit));
2568
2533
  }
2569
2534
  }
2570
- `, Zt = () => {
2535
+ `, Xt = () => {
2571
2536
  let e = G((e) => e.chapters), t = G((e) => e.currentTime), n = G((e) => e.requestSeek), r = G((e) => e.seekPreview), i = G(), a = f(() => ct(e), [e]), o = f(() => {
2572
2537
  if (typeof t != "number") return;
2573
- let n = e.findIndex((e, n) => a[n] !== void 0 && e.start - Kt <= t && t < e.end), r = n >= 0 ? a[n] : void 0;
2538
+ let n = e.findIndex((e, n) => a[n] !== void 0 && e.start - Gt <= t && t < e.end), r = n >= 0 ? a[n] : void 0;
2574
2539
  return r ? {
2575
2540
  kind: r,
2576
2541
  end: e[n].end
@@ -2582,7 +2547,7 @@ ${gt}
2582
2547
  ]), s = p(0), [c, l] = m(0);
2583
2548
  d(() => {
2584
2549
  let e = typeof t == "number" ? t : 0, n = s.current;
2585
- s.current = e, e < n - Jt && l((e) => e + 1);
2550
+ s.current = e, e < n - qt && l((e) => e + 1);
2586
2551
  }, [t]);
2587
2552
  let [u, h] = m(!1), g = o ? `${o.kind}@${o.end}` : "";
2588
2553
  return d(() => {
@@ -2591,12 +2556,12 @@ ${gt}
2591
2556
  return;
2592
2557
  }
2593
2558
  h(!0);
2594
- let e = setTimeout(() => h(!1), qt);
2559
+ let e = setTimeout(() => h(!1), Kt);
2595
2560
  return () => clearTimeout(e);
2596
2561
  }, [g, c]), d(() => {
2597
2562
  o || h(!1);
2598
2563
  }, [o]), o ? /* @__PURE__ */ T("div", {
2599
- css: Xt,
2564
+ css: Yt,
2600
2565
  className: u && !r ? "show" : "",
2601
2566
  children: /* @__PURE__ */ T("button", {
2602
2567
  type: "button",
@@ -2604,10 +2569,10 @@ ${gt}
2604
2569
  onClick: () => {
2605
2570
  h(!1), n ? n(o.end) : i.seek(o.end);
2606
2571
  },
2607
- children: Yt[o.kind]
2572
+ children: Jt[o.kind]
2608
2573
  })
2609
2574
  }) : null;
2610
- }, Qt = 3e3, $t = g`
2575
+ }, Zt = 3e3, Qt = g`
2611
2576
  position: absolute;
2612
2577
  inset: 0;
2613
2578
  z-index: 2;
@@ -2618,10 +2583,10 @@ ${gt}
2618
2583
  hoverable while faded out, popping a tooltip over nothing. It cascades where pointer-events does
2619
2584
  not, and transitioning it holds the item on screen for the length of the fade. */
2620
2585
  transition: opacity 0.1s cubic-bezier(.4,0,1,1), visibility 0.1s;
2621
- `, en = (e, t) => {
2586
+ `, $t = (e, t) => {
2622
2587
  let n = e.left - t.left, r = e.top - t.top;
2623
2588
  return `path(evenodd, 'M0 0H${t.width}V${t.height}H0Z M${n} ${r}H${n + e.width}V${r + e.height}H${n}Z')`;
2624
- }, tn = g`
2589
+ }, en = g`
2625
2590
  position: relative;
2626
2591
  background-color: black;
2627
2592
  display: flex;
@@ -2675,13 +2640,13 @@ ${gt}
2675
2640
  &.hide {
2676
2641
  cursor: none;
2677
2642
  }
2678
- `, nn = (e, t = "") => s.toArray(e).flatMap((e, n) => {
2643
+ `, tn = (e, t = "") => s.toArray(e).flatMap((e, n) => {
2679
2644
  let r = `${t}${l(e) && e.key != null ? e.key : n}`;
2680
- return l(e) && e.type === c ? nn(e.props.children, `${r}/`) : [{
2645
+ return l(e) && e.type === c ? tn(e.props.children, `${r}/`) : [{
2681
2646
  key: r,
2682
2647
  item: e
2683
2648
  }];
2684
- }), rn = ({ ref: e, onVideoRef: t, onSubtitleRef: n, overlay: r, controls: i, children: a }) => {
2649
+ }), nn = ({ ref: e, onVideoRef: t, onSubtitleRef: n, overlay: r, controls: i, children: a }) => {
2685
2650
  let o = G(), s = G((e) => e.hideUI), c = G((e) => e.setHideUI), l = G((e) => e.hasMedia), u = G((e) => e.passThroughPictureInPicture), f = p(void 0), m = p("mouse"), h = p({
2686
2651
  x: -1,
2687
2652
  y: -1
@@ -2702,15 +2667,15 @@ ${gt}
2702
2667
  return !!n && g.current.contains(n) && !n.closest(".video");
2703
2668
  }, b = () => {
2704
2669
  if (y()) {
2705
- f.current = setTimeout(b, Qt);
2670
+ f.current = setTimeout(b, Zt);
2706
2671
  return;
2707
2672
  }
2708
2673
  _();
2709
2674
  }, x = () => {
2710
- c(!1), clearTimeout(f.current), f.current = setTimeout(b, Qt);
2675
+ c(!1), clearTimeout(f.current), f.current = setTimeout(b, Zt);
2711
2676
  };
2712
2677
  return /* @__PURE__ */ E("div", {
2713
- css: tn,
2678
+ css: en,
2714
2679
  ref: v,
2715
2680
  onPointerMove: (e) => {
2716
2681
  m.current = e.pointerType, e.pointerType === "mouse" && (h.current = {
@@ -2730,8 +2695,8 @@ ${gt}
2730
2695
  className: s ? "hide" : "",
2731
2696
  children: [
2732
2697
  /* @__PURE__ */ T(ke, { onSubtitleRef: n }),
2733
- nn(r).map(({ key: e, item: t }) => /* @__PURE__ */ T("div", {
2734
- css: $t,
2698
+ tn(r).map(({ key: e, item: t }) => /* @__PURE__ */ T("div", {
2699
+ css: Qt,
2735
2700
  style: { ...s ? {
2736
2701
  opacity: "0",
2737
2702
  visibility: "hidden",
@@ -2739,12 +2704,12 @@ ${gt}
2739
2704
  } : {} },
2740
2705
  children: t
2741
2706
  }, e)),
2742
- i === !1 ? null : /* @__PURE__ */ T(Ht, {}),
2743
- /* @__PURE__ */ T(Gt, {}),
2744
- /* @__PURE__ */ T(Zt, {}),
2707
+ i === !1 ? null : /* @__PURE__ */ T(Vt, {}),
2708
+ /* @__PURE__ */ T(Wt, {}),
2709
+ /* @__PURE__ */ T(Xt, {}),
2745
2710
  u?.armed && g.current ? /* @__PURE__ */ T("div", {
2746
2711
  className: "pass-through",
2747
- style: { clipPath: en(u.armed, g.current.getBoundingClientRect()) },
2712
+ style: { clipPath: $t(u.armed, g.current.getBoundingClientRect()) },
2748
2713
  onPointerOver: u.disarm
2749
2714
  }) : null,
2750
2715
  /* @__PURE__ */ E("div", {
@@ -2763,7 +2728,7 @@ ${gt}
2763
2728
  })
2764
2729
  ]
2765
2730
  });
2766
- }, an = ({ options: e, children: t }) => {
2731
+ }, rn = ({ options: e, children: t }) => {
2767
2732
  let n = "media" in e ? e : null, r = n ? null : e, { title: i, chapters: a } = e, { size: o, downloadedRanges: s, publicPath: c, libavWorkerUrl: l, read: u, thumbnailRead: h, thumbnailsEnabled: g } = r ?? {}, _ = b(), v = y(), [x, S] = m(null), [C, w] = m(null), E = n?.media ?? x;
2768
2733
  d(() => (_?.(E), () => _?.(null)), [E, _]);
2769
2734
  let { expose: D } = e, O = D === !0 ? {} : D || void 0, k = p(O);
@@ -2868,7 +2833,7 @@ ${gt}
2868
2833
  });
2869
2834
  }, [K, X]), d(() => {
2870
2835
  a && K({ chapters: a });
2871
- }, [K, a]), /* @__PURE__ */ T(rn, {
2836
+ }, [K, a]), /* @__PURE__ */ T(nn, {
2872
2837
  ref: v,
2873
2838
  onVideoRef: n ? void 0 : S,
2874
2839
  onSubtitleRef: w,
@@ -2876,7 +2841,7 @@ ${gt}
2876
2841
  controls: e.controls,
2877
2842
  children: t
2878
2843
  });
2879
- }, on = g`
2844
+ }, an = g`
2880
2845
  /**
2881
2846
  * The chrome's whole scale, in one place.
2882
2847
  *
@@ -2946,17 +2911,17 @@ ${gt}
2946
2911
  height: 100%;
2947
2912
  width: 100%;
2948
2913
  overflow: hidden;
2949
- `, sn = (e) => /* @__PURE__ */ T("div", {
2950
- css: on,
2951
- children: /* @__PURE__ */ T(W.Provider, { children: /* @__PURE__ */ T(an, {
2914
+ `, on = (e) => /* @__PURE__ */ T("div", {
2915
+ css: an,
2916
+ children: /* @__PURE__ */ T(W.Provider, { children: /* @__PURE__ */ T(rn, {
2952
2917
  options: e,
2953
2918
  children: e.children
2954
2919
  }) })
2955
- }), cn = (e) => !!e && typeof e == "object" && "at" in e, ln = (e) => !!e && typeof e == "object" && "selection" in e, un = ({ blob: e, name: t }) => ({
2920
+ }), sn = (e) => !!e && typeof e == "object" && "at" in e, cn = (e) => !!e && typeof e == "object" && "selection" in e, ln = ({ blob: e, name: t }) => ({
2956
2921
  length: e.size,
2957
2922
  name: t ?? (e instanceof File ? e.name : void 0),
2958
2923
  read: (t, n) => e.slice(t, t + n).arrayBuffer()
2959
- }), dn = async (e, t) => {
2924
+ }), un = async (e, t) => {
2960
2925
  let n = await fetch(e, {
2961
2926
  headers: { Range: "bytes=0-1" },
2962
2927
  credentials: t
@@ -2966,8 +2931,8 @@ ${gt}
2966
2931
  if (!Number.isFinite(i) || i <= 0) throw Error("The source did not report its length");
2967
2932
  if (!r && n.status !== 206) throw Error("The source does not support range requests");
2968
2933
  return i;
2969
- }, fn = async ({ url: e, length: t, name: n, credentials: r }) => {
2970
- let i = t ?? await dn(e, r);
2934
+ }, dn = async ({ url: e, length: t, name: n, credentials: r }) => {
2935
+ let i = t ?? await un(e, r);
2971
2936
  return {
2972
2937
  length: i,
2973
2938
  name: n,
@@ -2982,9 +2947,9 @@ ${gt}
2982
2947
  return o.arrayBuffer();
2983
2948
  }
2984
2949
  };
2985
- }, pn = async (e) => {
2986
- if ("blob" in e) return un(e);
2987
- if ("url" in e) return fn(e);
2950
+ }, fn = async (e) => {
2951
+ if ("blob" in e) return ln(e);
2952
+ if ("url" in e) return dn(e);
2988
2953
  if ("read" in e) return {
2989
2954
  length: e.length,
2990
2955
  name: e.name,
@@ -2993,4 +2958,4 @@ ${gt}
2993
2958
  throw Error(`Unknown source type: ${e}`);
2994
2959
  };
2995
2960
  //#endregion
2996
- export { sn as MediaPlayer, sn as default, W as Player, e as SUBTITLES_OFF, a as createPictureInPicture, o as createSubtitleRenderer, r as createThumbnailGenerator, pn as inputToRemuxerInput, ln as isDelegatedTracks, cn as isExternalThumbnails, U as sourceFeature, i as startPlayback, de as usePictureInPicture, G as usePlayer, ue as useSeekThumbnails };
2961
+ export { on as MediaPlayer, on as default, W as Player, e as SUBTITLES_OFF, a as createPictureInPicture, o as createSubtitleRenderer, r as createThumbnailGenerator, fn as inputToRemuxerInput, cn as isDelegatedTracks, sn as isExternalThumbnails, U as sourceFeature, i as startPlayback, de as usePictureInPicture, G as usePlayer, ue as useSeekThumbnails };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banou/media-player",
3
- "version": "0.10.13",
3
+ "version": "0.10.15",
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.7",
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
@@ -364,7 +366,16 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
364
366
  const trackAppendedEnd = () => {
365
367
  const ends = getTimeRanges(sourceBuffer).map((r) => r.end)
366
368
  if (ends.length) lastAppendedEnd = Math.max(lastAppendedEnd, ...ends)
369
+ if (finished) endAppended = true
367
370
  }
371
+ /*
372
+ * Whether the end of the file is in the buffer. A seek clears `finished`, so past one it is read back
373
+ * from `buffered`, since evict() and the browser both remove data: counted from a flag alone, a move to
374
+ * the end of a file whose end had been removed signalled the end there, endOfStream cut the duration to
375
+ * what was buffered, and the playhead went back to 20 s of 30 in both engines (measured 2026-10-12).
376
+ */
377
+ const endInBuffer = () =>
378
+ finished || (endAppended && (getTimeRanges(sourceBuffer).at(-1)?.end ?? 0) >= lastAppendedEnd - 0.1)
368
379
 
369
380
  const seekTo = async (time: number) => {
370
381
  seeking = true
@@ -489,15 +500,17 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
489
500
  }
490
501
 
491
502
  /**
492
- * Whether a position has its runway, which stops at the end of the file once the remuxer has read to it.
503
+ * Whether a position has its runway, which stops at the end of the file while the end is in the buffer.
493
504
  *
494
505
  * 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`. The end is
496
- * the furthest append, the one `endOfStream` waits on, never the declared duration: that is the longer
497
- * track, while an open MediaSource's `buffered` ends at the shorter one, and evict() reopens it on
498
- * every trim (measured 2026-10-11).
506
+ * over the buffered end, after which Firefox 151 stays at the end and never fires `ended`: its demuxer
507
+ * had read the track's last frame, and once that frame is replaced it waits for one starting at the
508
+ * track's end. So the end counts while it is buffered, even with the reader re-reading toward it after
509
+ * a seek back (measured 2026-10-12). The end is the furthest append, the one `endOfStream` waits on,
510
+ * never the declared duration: that is the longer track, while an open MediaSource's `buffered` ends at
511
+ * the shorter one, and evict() reopens it on every trim (measured 2026-10-11).
499
512
  */
500
- const hasRunway = (time: number) => runwayFrom(time) >= SEEK_RUNWAY || (finished && atEnd(time))
513
+ const hasRunway = (time: number) => runwayFrom(time) >= SEEK_RUNWAY || (endInBuffer() && atEnd(time))
501
514
 
502
515
  /**
503
516
  * Put the data in place for a position the playhead has NOT moved to yet.
@@ -692,7 +705,7 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
692
705
  evict().catch(() => {})
693
706
  pump().catch((error) => reportError(error, false))
694
707
  // every remove() puts the MediaSource back to 'open', so the end has to be re-armed
695
- if (finished && !pending && atEnd() && mediaSource.readyState === 'open') endOfStream().catch(() => {})
708
+ if (endInBuffer() && !pending && atEnd() && mediaSource.readyState === 'open') endOfStream().catch(() => {})
696
709
  }, 100)
697
710
  teardown.push(() => clearInterval(interval))
698
711
 
@@ -4,7 +4,6 @@ import { css } from '@emotion/react'
4
4
  import { fonts } from '../../utils/fonts'
5
5
  import { usePlayer } from '../player'
6
6
 
7
- /** Long enough to read twice, short enough not to sit over the picture. */
8
7
  const VISIBLE_MS = 9000
9
8
 
10
9
  const style = css`
@@ -38,22 +37,11 @@ const style = css`
38
37
  opacity: 1;
39
38
  }
40
39
 
41
- .words {
42
- display: flex;
43
- flex-direction: column;
44
- gap: calc(0.3 * var(--mp-unit));
45
- }
46
-
47
- .title {
48
- ${fonts.bMedium.bold}
40
+ .text {
41
+ ${fonts.bMedium.medium}
49
42
  color: #fff;
50
43
  }
51
44
 
52
- .body {
53
- ${fonts.bSmall.regular}
54
- color: rgba(255, 255, 255, 0.75);
55
- }
56
-
57
45
  /* Points at the edge the browser's control lives on, which is the whole message. */
58
46
  .arrow {
59
47
  flex: none;
@@ -90,12 +78,7 @@ export const BurnInHint = () => {
90
78
  return (
91
79
  <div css={style} className={show ? 'show' : ''} role='status'>
92
80
  <div className='card'>
93
- <div className='words'>
94
- <div className='title'>Subtitles are in the picture</div>
95
- <div className='body'>
96
- Now hover the video and click your browser&apos;s own pop out button
97
- </div>
98
- </div>
81
+ <div className='text'>Now click your browser&apos;s pop out button</div>
99
82
  <div className='arrow' aria-hidden='true'>&rarr;</div>
100
83
  </div>
101
84
  </div>
@@ -25,23 +25,6 @@ const SEEK_STEP = 5
25
25
  const REFOCUS_POLL = 50
26
26
  const REFOCUS_AFTER = 1_000
27
27
 
28
- /**
29
- * Two stacked lines inside a tooltip.
30
- *
31
- * The width to wrap against is no longer here: TooltipDisplay bounds every chip it draws, at the
32
- * same 26 units this had imposed by hand, so a call site only says what its content is.
33
- */
34
- const tooltipLinesStyle = css`
35
- display: flex;
36
- flex-direction: column;
37
- gap: calc(0.4 * var(--mp-unit));
38
-
39
- .hint {
40
- opacity: 0.72;
41
- font-size: 0.9em;
42
- }
43
- `
44
-
45
28
  const style = css`
46
29
  position: absolute;
47
30
  bottom: 0;
@@ -384,7 +367,6 @@ export const ControlBar = () => {
384
367
  ? (
385
368
  <TooltipDisplay
386
369
  id='picture-in-picture'
387
- // anchored to its end, or the two-line burn-in copy runs off the right of the player
388
370
  tooltipPlace='top-end'
389
371
  text={
390
372
  <button
@@ -409,26 +391,14 @@ export const ControlBar = () => {
409
391
  </button>
410
392
  }
411
393
  toolTipText={
412
- // The chip is drawn in its own subtree, where the control bar's rules never
413
- // reach it: a bare `small` stays inline and runs straight on from the line above
414
- // it. Both lines carry their layout themselves.
415
- <span css={tooltipLinesStyle}>
416
- <span className='lead'>
417
- {!burnIn
418
- ? 'Picture in picture'
419
- : !hasSubtitles
420
- ? 'This file has no subtitles'
421
- : burnedInSubtitles
422
- ? 'Subtitles are in the picture'
423
- : 'Put the subtitles in the picture'}
424
- </span>
425
- {burnIn && hasSubtitles
426
- ? (
427
- <span className='hint'>
428
- Then hover the video and click your browser&apos;s own pop out button
429
- </span>
430
- )
431
- : null}
394
+ <span>
395
+ {!burnIn
396
+ ? 'Picture in picture'
397
+ : !hasSubtitles
398
+ ? 'This file has no subtitles'
399
+ : burnedInSubtitles
400
+ ? 'Subtitles are in the picture'
401
+ : 'Put the subtitles in the picture'}
432
402
  </span>
433
403
  }
434
404
  />