@banou/media-player 0.10.0 → 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/README.md CHANGED
@@ -150,10 +150,39 @@ chrome at once.
150
150
 
151
151
  ## What it does
152
152
 
153
- Play and pause, seek with a preview thumbnail and a keyframe-accurate scrub, volume on a log curve,
154
- mute, playback speed, audio track selection, subtitle track selection, picture in picture, fullscreen,
155
- and keyboard shortcuts. Nothing is persisted: volume, speed and track choices start at their defaults
156
- every load.
153
+ Play and pause, seek with a preview thumbnail and a keyframe-accurate scrub, chapters on the seekbar,
154
+ an offer to skip an opening or ending, volume on a log curve, mute, playback speed, audio track
155
+ selection, subtitle track selection, picture in picture, fullscreen, and keyboard shortcuts. Nothing
156
+ is persisted: volume, speed and track choices start at their defaults every load.
157
+
158
+ ### Chapters
159
+
160
+ Chapters come from the container, and the seekbar draws them as segments with a break at each
161
+ boundary. Hovering one lifts it above its neighbours and names it beside the time in the preview.
162
+
163
+ Pass `chapters` to supply them yourself, on either arm, and they win over whatever the file declared:
164
+
165
+ ```tsx
166
+ <MediaPlayer {...source} chapters={[{ start: 0, end: 88, title: 'Opening' }]} />
167
+ ```
168
+
169
+ They are `{ start, end, title }` in seconds, ordered and non-overlapping, and they need not cover the
170
+ whole duration. A local file gets them from libav automatically, so this is for a source that knows
171
+ chapters the container does not.
172
+
173
+ ### Skip Opening and Skip Ending
174
+
175
+ When a chapter looks like an opening or an ending, a button offers to jump past it, appearing a
176
+ second before the chapter and staying six seconds. It never skips on its own, so a wrong guess costs
177
+ a button nobody presses rather than a jump out of the episode.
178
+
179
+ The guess is made from the chapter title, against the names releases actually use (`OP`, `Opening`,
180
+ `ED`, `Ending`, `Credits`, and the same names carrying a song), and it defers across the whole file:
181
+ `Intro` is the opening where nothing else claims to be, and the cold open where an `OP` follows it.
182
+ A file whose chapters carry no usable name at all falls back to shape, where a chapter of about
183
+ ninety seconds in each half of the runtime is the opening and the ending. Nothing shorter than
184
+ fifteen seconds is ever offered, and a disc that is mostly themes offers nothing, because there the
185
+ themes are what is being watched.
157
186
 
158
187
  ### Picture in picture keeps the subtitles
159
188
 
@@ -1,5 +1,5 @@
1
1
  export { startPlayback, terminateRemuxer, MediaElementError, isMediaElementError, DEFAULT_BUFFER_SIZE } from './playback';
2
- export type { PlaybackOptions, PlaybackController, MediaIndex, AudioStream } from './playback';
2
+ export type { PlaybackOptions, PlaybackController, MediaIndex, MediaChapter, AudioStream } from './playback';
3
3
  export { createSubtitleRenderer, SUBTITLES_OFF } from './subtitles';
4
4
  export type { SubtitleRenderer, SubtitleRendererOptions, SubtitleStream } from './subtitles';
5
5
  export { createThumbnailGenerator } from './thumbnails';
@@ -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-YeMWShCv.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 };
@@ -6,6 +6,21 @@ export type MediaIndex = {
6
6
  pos: number;
7
7
  timestamp: number;
8
8
  };
9
+ /**
10
+ * One named span of the timeline, in SECONDS.
11
+ *
12
+ * Chapters are not required to tile the duration: a container routinely declares a last chapter that
13
+ * ends fractionally before the file does, and nothing guarantees the first starts at zero. Anything
14
+ * drawing them has to treat the gaps as ordinary un-named time rather than assume full cover.
15
+ *
16
+ * libav also reports an `index`, dropped here the way `MediaIndex` drops it: array position already
17
+ * carries it, and a caller supplying their own chapters should not have to number them.
18
+ */
19
+ export type MediaChapter = {
20
+ start: number;
21
+ end: number;
22
+ title: string;
23
+ };
9
24
  export type PlaybackOptions = {
10
25
  videoElement: HTMLVideoElement;
11
26
  /**
@@ -49,6 +64,8 @@ export type PlaybackController = {
49
64
  selectSubtitleStream: (streamIndex: number | undefined) => void;
50
65
  /** Keyframe index of the input, which is what maps a downloaded byte range onto the timeline. */
51
66
  indexes: MediaIndex[];
67
+ /** Named spans the container declared, empty when it declared none. */
68
+ chapters: MediaChapter[];
52
69
  duration: number;
53
70
  videoMimeType: string;
54
71
  audioMimeType: string;
@@ -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,152 +237,153 @@ 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 ?? [],
377
+ chapters: e.chapters ?? [],
377
378
  duration: e.info.input.duration,
378
379
  videoMimeType: e.info.output.videoMimeType,
379
380
  audioMimeType: e.info.output.audioMimeType
380
381
  };
381
382
  } catch (e) {
382
- throw F(), e;
383
+ throw P(), e;
383
384
  }
384
- }, ne = 5, re = 500, j = 320, ie = 1e6, ae = 3, M = 1e4, N = async (e) => {
385
- 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({
386
387
  publicPath: n,
387
388
  workerUrl: r,
388
389
  workerOptions: { type: "module" },
@@ -390,36 +391,39 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
390
391
  read: a
391
392
  });
392
393
  try {
393
- let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? ne, n / re), a = [];
394
- for (let [e, o] of t.indexes.entries()) {
395
- let s = a.at(-1);
396
- s && o.timestamp - s.timestamp < r || a.push({
397
- timestamp: o.timestamp,
398
- endTime: n,
399
- startByte: o.pos,
400
- endByte: Math.min((t.indexes[e + 1]?.pos ?? i) + ie, i),
401
- done: !1,
402
- attempts: 0
403
- });
404
- }
405
- for (let [e, t] of a.entries()) t.endTime = a[e + 1]?.timestamp ?? n;
406
- a.length > 1 && a.at(-1).timestamp > n - r * 2 && a.pop();
407
- let l = [], u = !1, d = [], f = !1, p, m = () => {
408
- 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;
409
413
  if (e !== void 0) {
410
- 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);
411
415
  if (t >= 0) return t;
412
416
  }
413
417
  return 0;
414
- }, h = () => {
418
+ }, b = () => {
415
419
  let e = [];
416
- for (let [t, n] of l.entries()) {
420
+ for (let [t, n] of u.entries()) {
417
421
  n.startTime - (e.at(-1)?.endTime ?? 0) > .01 && e.push({
418
422
  url: "",
419
423
  startTime: e.at(-1)?.endTime ?? 0,
420
424
  endTime: n.startTime
421
425
  }), e.push(n);
422
- let r = l[t + 1];
426
+ let r = u[t + 1];
423
427
  r && r.startTime - n.endTime > .01 && e.push({
424
428
  url: "",
425
429
  startTime: n.endTime,
@@ -432,48 +436,83 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
432
436
  startTime: t,
433
437
  endTime: n
434
438
  }), o(e);
435
- }, g = async (e) => {
436
- if (u) return;
437
- 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))));
438
442
  r.getContext("2d").drawImage(n, 0, 0, r.width, r.height), n.close();
439
443
  let i = await r.convertToBlob({
440
444
  type: "image/webp",
441
445
  quality: .7
442
446
  });
443
- u || (l = [...l, {
447
+ d || (u = [...u, {
444
448
  url: URL.createObjectURL(i),
445
449
  startTime: e.timestamp,
446
450
  endTime: e.endTime
447
- }].sort((e, t) => e.startTime - t.startTime), h());
448
- }, _ = () => {
449
- if (f || u || !d.length) return;
450
- let e = d.splice(m(), 1)[0];
451
- f = !0, g(e).catch(() => {
452
- 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;
453
466
  }).finally(() => {
454
- f = !1, _();
467
+ p = !1, S();
455
468
  });
456
- }, v = (e) => {
457
- 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);
458
494
  };
459
- return h(), {
495
+ return b(), {
460
496
  update: (e) => {
461
- 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();
462
501
  },
463
502
  prioritize: (e) => {
464
- u || (p = e);
503
+ d || (m = e);
465
504
  },
466
505
  destroy: () => {
467
- u = !0, d.length = 0;
468
- for (let e of l) URL.revokeObjectURL(e.url);
469
- l = [], A(c);
506
+ d = !0, f.length = 0;
507
+ for (let e of u) URL.revokeObjectURL(e.url);
508
+ u = [], A(c);
470
509
  }
471
510
  };
472
511
  } catch (e) {
473
512
  throw A(c), e;
474
513
  }
475
- }, 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) => {
476
- 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) => {
477
516
  r.drawImage(t, 0, 0, e.width, e.height);
478
517
  let i = n.querySelector("canvas");
479
518
  i && i.width > 0 && i.height > 0 && r.drawImage(i, 0, 0, e.width, e.height);
@@ -543,7 +582,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
543
582
  };
544
583
  if (a = f, u.srcObject = f.stream, (e.container ?? t.parentElement ?? document.body).appendChild(u), v(), s = setInterval(() => {
545
584
  t.paused && h();
546
- }, I), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
585
+ }, se), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
547
586
  let t = () => {
548
587
  clearTimeout(n), e();
549
588
  }, n = setTimeout(t, 1e3);
@@ -589,4 +628,4 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
589
628
  };
590
629
  };
591
630
  //#endregion
592
- 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.d.ts CHANGED
@@ -10,5 +10,5 @@ export { useSeekThumbnails } from './react/hooks/use-thumbnails';
10
10
  export { usePictureInPicture } from './react/hooks/use-picture-in-picture';
11
11
  export { inputToRemuxerInput } from './utils/source';
12
12
  export type { RemuxerInput } from './utils/source';
13
- export type { AudioStream, MediaIndex, PictureInPictureController, PlaybackController, PlaybackOptions, SubtitleStream, ThumbnailImage, } from './engine';
13
+ export type { AudioStream, MediaChapter, MediaIndex, PictureInPictureController, PlaybackController, PlaybackOptions, SubtitleStream, ThumbnailImage, } from './engine';
14
14
  export { startPlayback, createThumbnailGenerator, createSubtitleRenderer, createPictureInPicture, SUBTITLES_OFF, } from './engine';