@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 +33 -4
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/playback.d.ts +17 -0
- package/dist/{engine-YeMWShCv.js → engine-DqyZgmTv.js} +173 -134
- package/dist/index.d.ts +1 -1
- package/dist/index.js +625 -331
- package/dist/react/components/skip-chapter.d.ts +10 -0
- package/dist/react/player.d.ts +1 -0
- package/dist/react/source-feature.d.ts +17 -1
- package/dist/react/video-player.d.ts +13 -0
- package/dist/utils/chapters.d.ts +37 -0
- package/package.json +1 -1
- package/src/lib/engine/index.ts +1 -1
- package/src/lib/engine/playback.ts +15 -0
- package/src/lib/engine/thumbnails.ts +135 -19
- package/src/lib/index.tsx +1 -0
- package/src/lib/react/components/chrome.tsx +2 -0
- package/src/lib/react/components/progress-bar.tsx +127 -25
- package/src/lib/react/components/skip-chapter.tsx +166 -0
- package/src/lib/react/hooks/use-playback.ts +17 -2
- package/src/lib/react/source-feature.ts +10 -1
- package/src/lib/react/video-player.tsx +28 -1
- package/src/lib/utils/chapters.ts +302 -0
- package/src/lib/utils/source.ts +12 -1
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,
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/engine/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-
|
|
1
|
+
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-DqyZgmTv.js";
|
|
2
2
|
export { i as DEFAULT_BUFFER_SIZE, e as MediaElementError, a as SUBTITLES_OFF, u as createPictureInPicture, d as createSubtitleRenderer, c as createThumbnailGenerator, n as getTimeRanges, s as isMediaElementError, o as pictureInPictureMode, l as startPlayback, t as terminateRemuxer, r as updateSourceBuffer };
|
|
@@ -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:
|
|
183
|
+
let { videoElement: n, subtitleContainer: r, read: a, length: s, publicPath: c, libavWorkerUrl: l, jassubWorkerUrl: u, jassubWasmUrl: d, jassubLegacyWasmUrl: f, defaultFontUrl: p, bufferSize: m = k, audioStreamIndex: _, onReady: te, onError: ne, onRecovered: re, onSeek: ie, onSubtitleStreams: ae, onAudioStreams: oe } = t, j = await e({
|
|
184
184
|
publicPath: c,
|
|
185
185
|
workerUrl: l,
|
|
186
186
|
workerOptions: { type: "module" },
|
|
@@ -188,18 +188,18 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
188
188
|
length: s,
|
|
189
189
|
audioStreamIndex: _,
|
|
190
190
|
read: a
|
|
191
|
-
}),
|
|
192
|
-
if (!
|
|
193
|
-
|
|
194
|
-
for (let e of [...
|
|
191
|
+
}), M = [], N = !1, P = () => {
|
|
192
|
+
if (!N) {
|
|
193
|
+
N = !0;
|
|
194
|
+
for (let e of [...M].reverse()) try {
|
|
195
195
|
e();
|
|
196
196
|
} catch {}
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
-
|
|
199
|
+
M.push(() => A(j));
|
|
200
200
|
try {
|
|
201
|
-
let e = await
|
|
202
|
-
|
|
201
|
+
let e = await j.init(), t = e.audioStreams ?? [], a = t.some((e) => e.streamIndex === _) ? _ : t[0]?.streamIndex ?? -1;
|
|
202
|
+
oe?.(t, a);
|
|
203
203
|
let s = h({
|
|
204
204
|
video: n,
|
|
205
205
|
container: r,
|
|
@@ -207,13 +207,13 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
207
207
|
wasmUrl: d,
|
|
208
208
|
legacyWasmUrl: f,
|
|
209
209
|
defaultFontUrl: p,
|
|
210
|
-
onStreams:
|
|
210
|
+
onStreams: ae
|
|
211
211
|
});
|
|
212
|
-
if (
|
|
212
|
+
if (M.push(() => s.destroy()), e.attachments?.length && s.pushAttachments(e.attachments), !e.info.output.videoMimeType) throw Error("The video codec in this file could not be identified");
|
|
213
213
|
let c = [e.info.output.videoMimeType, e.info.output.audioMimeType].filter(Boolean).join(","), l = `video/mp4; codecs="${c}"`;
|
|
214
214
|
if (!MediaSource.isTypeSupported(l)) throw Error(`This browser cannot play the codecs in this file: ${c}`);
|
|
215
215
|
let m = new MediaSource(), k = URL.createObjectURL(m);
|
|
216
|
-
|
|
216
|
+
M.push(() => URL.revokeObjectURL(k)), n.src = k, M.push(() => {
|
|
217
217
|
try {
|
|
218
218
|
n.pause();
|
|
219
219
|
} catch {}
|
|
@@ -237,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:
|
|
241
|
-
await
|
|
242
|
-
let R = !1, z = !1, B = 0, V, ce = () => V ?? n.currentTime, H = !1,
|
|
240
|
+
}), { appendBuffer: F, unbufferRange: I, updateTimestampOffset: se, endOfStream: L } = o(A, m);
|
|
241
|
+
await F(e.data), e.subtitles?.length && s.pushFragments(e.subtitles), te?.();
|
|
242
|
+
let R = !1, z = !1, B = 0, V, ce = () => V ?? n.currentTime, H = !1, le = 0, U = 0, W = null, G = 0, ue = 0, K = !1, de = 0, q = (e, t, n = !1) => {
|
|
243
243
|
let r = e?.message === "Cancelled";
|
|
244
|
-
t && r || (console.error(e), !(
|
|
245
|
-
},
|
|
244
|
+
t && r || (console.error(e), !(K && !n) && (K = !0, ne?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
|
|
245
|
+
}, fe = async (e = !1) => {
|
|
246
246
|
if (B > 0 && !e) return;
|
|
247
247
|
let t = n.currentTime, r = e ? x : v, a = e ? S : y;
|
|
248
|
-
for (let { start: e, end: n } of i(A)) e < t + r && await
|
|
249
|
-
},
|
|
248
|
+
for (let { start: e, end: n } of i(A)) e < t + r && await I(e, t + r), n > t + a && await I(t + a, n);
|
|
249
|
+
}, pe = () => {
|
|
250
250
|
let e = i(A);
|
|
251
251
|
if (!e.length) return !0;
|
|
252
252
|
let t = ce(), n = e.find((e) => e.start <= t + T && t < e.end);
|
|
253
253
|
return n && n.end >= t + y ? !1 : !n || n.end < t + b;
|
|
254
|
-
},
|
|
254
|
+
}, me = () => {
|
|
255
255
|
let e = n.currentTime;
|
|
256
256
|
return i(A).find((t) => t.start <= e + T && e < t.end);
|
|
257
|
-
}, me = () => {
|
|
258
|
-
let e = pe();
|
|
259
|
-
return !!e && e.end >= le - .1;
|
|
260
257
|
}, he = () => {
|
|
258
|
+
let e = me();
|
|
259
|
+
return !!e && e.end >= ue - .1;
|
|
260
|
+
}, ge = () => {
|
|
261
261
|
let e = i(A).map((e) => e.end);
|
|
262
|
-
e.length && (
|
|
263
|
-
},
|
|
264
|
-
z = !0,
|
|
265
|
-
let t = ++
|
|
262
|
+
e.length && (ue = Math.max(ue, ...e));
|
|
263
|
+
}, J = async (e) => {
|
|
264
|
+
z = !0, W = null, G = 0, de = e;
|
|
265
|
+
let t = ++U;
|
|
266
266
|
try {
|
|
267
|
-
let { data: n, pts: r, subtitles: i, finished: a } = await
|
|
268
|
-
if (
|
|
269
|
-
n.byteLength && (await
|
|
267
|
+
let { data: n, pts: r, subtitles: i, finished: a } = await j.seek(e);
|
|
268
|
+
if (N || t !== U || (a && (H = !0), i?.length && s.pushFragments(i), await se(r), N || t !== U)) return;
|
|
269
|
+
n.byteLength && (await F(n), ge());
|
|
270
270
|
} catch (e) {
|
|
271
|
-
|
|
271
|
+
q(e, N || t !== U);
|
|
272
272
|
} finally {
|
|
273
|
-
t ===
|
|
273
|
+
t === U && (z = !1);
|
|
274
274
|
}
|
|
275
|
-
},
|
|
276
|
-
let e =
|
|
275
|
+
}, _e = async () => {
|
|
276
|
+
let e = W;
|
|
277
277
|
if (e) try {
|
|
278
|
-
await
|
|
278
|
+
await F(e), ge(), W === e && (W = null, G = 0);
|
|
279
279
|
} catch (t) {
|
|
280
|
-
if (
|
|
280
|
+
if (W !== e) return;
|
|
281
281
|
if (t?.name === "QuotaExceededError") {
|
|
282
|
-
let t = await
|
|
283
|
-
if (
|
|
282
|
+
let t = await fe(!0).then(() => !0, () => !1);
|
|
283
|
+
if (W !== e) return;
|
|
284
284
|
if (t) {
|
|
285
|
-
H = !1, await
|
|
285
|
+
H = !1, await J(me()?.end ?? n.currentTime);
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
|
-
if (
|
|
290
|
-
throw
|
|
289
|
+
if (G += 1, G < C) return;
|
|
290
|
+
throw W = null, t;
|
|
291
291
|
}
|
|
292
|
-
},
|
|
293
|
-
if (R || z ||
|
|
294
|
-
let e = ++
|
|
292
|
+
}, ve = async () => {
|
|
293
|
+
if (R || z || Q !== void 0 || N || !W && (H || !pe())) return;
|
|
294
|
+
let e = ++le, t = U, n = () => N || e !== le || t !== U;
|
|
295
295
|
R = !0;
|
|
296
296
|
try {
|
|
297
|
-
if (!
|
|
298
|
-
let { data: e, subtitles: t, finished: r } = await
|
|
297
|
+
if (!W) {
|
|
298
|
+
let { data: e, subtitles: t, finished: r } = await j.read();
|
|
299
299
|
if (n() || (r && (H = !0), t?.length && s.pushFragments(t), !e.byteLength)) return;
|
|
300
|
-
|
|
300
|
+
W = e, G = 0;
|
|
301
301
|
}
|
|
302
|
-
await
|
|
302
|
+
await _e(), !n() && K && (K = !1, re?.());
|
|
303
303
|
} catch (e) {
|
|
304
|
-
|
|
304
|
+
q(e, n());
|
|
305
305
|
} finally {
|
|
306
|
-
e ===
|
|
306
|
+
e === le && (R = !1);
|
|
307
307
|
}
|
|
308
|
-
},
|
|
308
|
+
}, ye = (e) => e >= de && !!me(), be = (e) => i(A).some((t) => t.start <= e + T && e < t.end), Y = (e) => {
|
|
309
309
|
let t = i(A).find((t) => t.start <= e + T && e < t.end);
|
|
310
310
|
return t ? t.end - e : 0;
|
|
311
|
-
},
|
|
312
|
-
if (!
|
|
311
|
+
}, xe = async (e) => {
|
|
312
|
+
if (!N && !(Y(e) >= E)) {
|
|
313
313
|
H = !1, B++, V = e;
|
|
314
314
|
try {
|
|
315
|
-
|
|
316
|
-
let t =
|
|
317
|
-
for (let n = 0; n < ee && !(
|
|
318
|
-
let t =
|
|
319
|
-
if (await
|
|
315
|
+
be(e) || await J(e);
|
|
316
|
+
let t = U;
|
|
317
|
+
for (let n = 0; n < ee && !(N || t !== U || H || Y(e) >= E); n++) {
|
|
318
|
+
let t = Y(e);
|
|
319
|
+
if (await ve(), Y(e) <= t) break;
|
|
320
320
|
}
|
|
321
321
|
} finally {
|
|
322
322
|
B--, V === e && (V = void 0);
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
-
},
|
|
326
|
-
let t = performance.now(), n = t -
|
|
325
|
+
}, Se = 0, X = null, Z, Ce = (e) => {
|
|
326
|
+
let t = performance.now(), n = t - Se;
|
|
327
327
|
if (n >= D) {
|
|
328
|
-
|
|
328
|
+
Se = t, X = null, ie?.(e);
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
let e =
|
|
334
|
-
|
|
331
|
+
X = e, !Z && (Z = setTimeout(() => {
|
|
332
|
+
Z = void 0;
|
|
333
|
+
let e = X;
|
|
334
|
+
X = null, !(e === null || N) && (Se = performance.now(), ie?.(e));
|
|
335
335
|
}, D - n));
|
|
336
336
|
};
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
M.push(() => {
|
|
338
|
+
Z && clearTimeout(Z);
|
|
339
339
|
});
|
|
340
|
-
let
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
let we = 0, Q;
|
|
341
|
+
M.push(() => {
|
|
342
|
+
Q && clearTimeout(Q);
|
|
343
343
|
});
|
|
344
|
-
let
|
|
344
|
+
let Te = () => {
|
|
345
345
|
let t = n.currentTime, r = e.info.input.duration || n.duration;
|
|
346
|
-
r > 0 &&
|
|
347
|
-
let i = performance.now(), a = i -
|
|
348
|
-
if (
|
|
349
|
-
if (H = !1,
|
|
350
|
-
|
|
346
|
+
r > 0 && Ce(Math.min(Math.max(t / r, 0), 1));
|
|
347
|
+
let i = performance.now(), a = i - we < O;
|
|
348
|
+
if (we = i, !ye(t) && !(z && Math.abs(t - de) < .001)) {
|
|
349
|
+
if (H = !1, Q && clearTimeout(Q), !a) {
|
|
350
|
+
Q = void 0, J(t);
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
|
-
|
|
354
|
-
if (
|
|
353
|
+
Q = setTimeout(() => {
|
|
354
|
+
if (Q = void 0, N) return;
|
|
355
355
|
let e = n.currentTime;
|
|
356
|
-
|
|
356
|
+
ye(e) || (H = !1, J(e));
|
|
357
357
|
}, O);
|
|
358
358
|
}
|
|
359
359
|
};
|
|
360
|
-
n.addEventListener("seeking",
|
|
361
|
-
let
|
|
362
|
-
if (
|
|
363
|
-
|
|
360
|
+
n.addEventListener("seeking", Te), M.push(() => n.removeEventListener("seeking", Te));
|
|
361
|
+
let $ = !1, Ee = () => {
|
|
362
|
+
if ($) return;
|
|
363
|
+
$ = !0;
|
|
364
364
|
let e = n.error;
|
|
365
|
-
|
|
365
|
+
q(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
|
|
366
366
|
};
|
|
367
|
-
n.addEventListener("error", Ee),
|
|
367
|
+
n.addEventListener("error", Ee), M.push(() => n.removeEventListener("error", Ee));
|
|
368
368
|
let De = setInterval(() => {
|
|
369
|
-
|
|
369
|
+
$ || n.error || (fe().catch(() => {}), ve().catch((e) => q(e, !1)), H && !W && he() && m.readyState === "open" && L().catch(() => {}));
|
|
370
370
|
}, 100);
|
|
371
|
-
return
|
|
372
|
-
destroy:
|
|
373
|
-
prepareSeek:
|
|
371
|
+
return M.push(() => clearInterval(De)), {
|
|
372
|
+
destroy: P,
|
|
373
|
+
prepareSeek: xe,
|
|
374
374
|
videoElement: n,
|
|
375
375
|
selectSubtitleStream: (e) => s.selectStream(e),
|
|
376
376
|
indexes: e.indexes ?? [],
|
|
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
|
|
383
|
+
throw P(), e;
|
|
383
384
|
}
|
|
384
|
-
}, ne = 5, re = 500,
|
|
385
|
-
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s =
|
|
385
|
+
}, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, j = 1e4, M = 1.5, N = 4e6, P = async (e) => {
|
|
386
|
+
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = ie } = e, c = await t({
|
|
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
|
-
|
|
395
|
-
let
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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 =
|
|
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
|
-
},
|
|
418
|
+
}, b = () => {
|
|
415
419
|
let e = [];
|
|
416
|
-
for (let [t, n] of
|
|
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 =
|
|
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
|
-
},
|
|
436
|
-
if (
|
|
437
|
-
let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")),
|
|
439
|
+
}, x = async (e) => {
|
|
440
|
+
if (d) return;
|
|
441
|
+
let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), j))]), n = await createImageBitmap(new Blob([t], { type: "image/png" })), r = new OffscreenCanvas(s, Math.max(1, Math.round(n.height * (s / n.width))));
|
|
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
|
-
|
|
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),
|
|
448
|
-
},
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
467
|
+
p = !1, S();
|
|
455
468
|
});
|
|
456
|
-
},
|
|
457
|
-
e.done = !0,
|
|
469
|
+
}, C = (e) => {
|
|
470
|
+
e.done = !0, f.push(e), S();
|
|
471
|
+
}, w = async (e) => {
|
|
472
|
+
_ = !0;
|
|
473
|
+
try {
|
|
474
|
+
let t = await c.init();
|
|
475
|
+
if (d) return;
|
|
476
|
+
h = e;
|
|
477
|
+
let n = l(t.indexes);
|
|
478
|
+
if (!n.length) return;
|
|
479
|
+
let r = new Map(u.map((e) => [e.startTime, e]));
|
|
480
|
+
for (let e of n) r.has(e.timestamp) && (e.done = !0);
|
|
481
|
+
a = n;
|
|
482
|
+
let i = new Set(n.map((e) => e.timestamp));
|
|
483
|
+
for (let e of u) i.has(e.startTime) || URL.revokeObjectURL(e.url);
|
|
484
|
+
u = n.filter((e) => r.has(e.timestamp)).map((e) => ({
|
|
485
|
+
url: r.get(e.timestamp).url,
|
|
486
|
+
startTime: e.timestamp,
|
|
487
|
+
endTime: e.endTime
|
|
488
|
+
})), f.length = 0, b();
|
|
489
|
+
} catch {} finally {
|
|
490
|
+
_ = !1;
|
|
491
|
+
}
|
|
492
|
+
}, T = (e) => e ? e.reduce((e, [, t]) => Math.max(e, t), 0) : i, E = (e) => {
|
|
493
|
+
for (let t of a) t.done || (!e || e.some(([e, n]) => e <= t.startByte && t.endByte <= n)) && C(t);
|
|
458
494
|
};
|
|
459
|
-
return
|
|
495
|
+
return b(), {
|
|
460
496
|
update: (e) => {
|
|
461
|
-
if (
|
|
497
|
+
if (d) return;
|
|
498
|
+
v = e;
|
|
499
|
+
let t = T(e);
|
|
500
|
+
h < 0 ? h = t : (t >= Math.max(h * M, h + N) || t >= i && h < i) && (g = !0), E(e), S();
|
|
462
501
|
},
|
|
463
502
|
prioritize: (e) => {
|
|
464
|
-
|
|
503
|
+
d || (m = e);
|
|
465
504
|
},
|
|
466
505
|
destroy: () => {
|
|
467
|
-
|
|
468
|
-
for (let e of
|
|
469
|
-
|
|
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
|
-
},
|
|
476
|
-
let { video: t, subtitles: n } = e, r = e.mode ??
|
|
514
|
+
}, F = 1280, I = 1920, se = 250, L = 100, R = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, z = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, B = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, V = () => R() ? z() ? "window" : B() ? "burn-in" : null : null, ce = (e) => {
|
|
515
|
+
let { video: t, subtitles: n } = e, r = e.mode ?? V() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? I : F), a, o, s, c = !1, l = !1, u = !1, d = !1, f, p = () => r === "burn-in" ? !!a : !!a && document.pictureInPictureElement === a.mirror, m = (e, r) => {
|
|
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
|
-
},
|
|
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,
|
|
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';
|