@banou/media-player 0.10.21 → 0.10.22
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 +6 -1
- package/dist/audio/loudness-analysis.d.ts +9 -1
- package/dist/audio/playback-tap.d.ts +11 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/playback.d.ts +21 -0
- package/dist/{engine-C4Gj_4zs.js → engine-Cr_7OImR.js} +120 -103
- package/dist/index.d.ts +1 -1
- package/dist/index.js +511 -442
- package/dist/react/hooks/use-playback.d.ts +2 -1
- package/dist/react/hooks/use-volume-normalizer.d.ts +6 -2
- package/dist/react/video-player.d.ts +10 -2
- package/dist/utils/source.d.ts +6 -1
- package/dist/volume-normalizer-worklet.js +14 -4
- package/package.json +3 -3
- package/src/lib/audio/loudness-analysis.ts +50 -17
- package/src/lib/audio/playback-tap.ts +37 -0
- package/src/lib/audio/volume-normalizer-worklet.js +14 -4
- package/src/lib/engine/index.ts +1 -1
- package/src/lib/engine/playback.ts +21 -1
- package/src/lib/index.tsx +1 -0
- package/src/lib/react/hooks/use-playback.ts +5 -1
- package/src/lib/react/hooks/use-volume-normalizer.ts +7 -4
- package/src/lib/react/video-player.tsx +26 -14
- package/src/lib/utils/source.ts +6 -2
package/README.md
CHANGED
|
@@ -44,6 +44,10 @@ const source = await inputToRemuxerInput({ url: 'https://example.com/episode.mkv
|
|
|
44
44
|
const source = await inputToRemuxerInput({ length, read })
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
A URL's source comes back with `readsDownload: true`, which the player takes beside `read` and `size`: each read of
|
|
48
|
+
it is a download, so the volume normalizer then analyses the audio playback fetched rather than fetching the file
|
|
49
|
+
again.
|
|
50
|
+
|
|
47
51
|
`usePlayer` reads and drives playback state from anywhere inside a `MediaPlayer`. It is the only hook
|
|
48
52
|
the chrome uses: the built-in video.js state and this player's own source state (tracks, thumbnails,
|
|
49
53
|
indexes, readiness) live on one store, so there is no second context to reach for.
|
|
@@ -260,7 +264,8 @@ The player saves nothing, so an app that wants the choice to stick saves what `o
|
|
|
260
264
|
passes it back.
|
|
261
265
|
|
|
262
266
|
It works in two stages. A worker reads the file a second time, through `thumbnailRead` when you pass one and only
|
|
263
|
-
within `downloadedRanges
|
|
267
|
+
within `downloadedRanges` (for a source that `readsDownload` it reads nothing and takes the audio playback fetched,
|
|
268
|
+
about 30 s ahead of the playhead), decodes its audio with WebCodecs, learns the file's normal level (the median 400 ms
|
|
264
269
|
loudness) and computes a cut with unlimited lookahead, so a cut starts before a spike: every 10 ms of the output is
|
|
265
270
|
held to 10 LU over the normal level and every 50 ms to 8 LU. It analyses what is near the playhead first and then the
|
|
266
271
|
rest of the file, keeping only 1 ms loudness readings, never the audio. Wherever that analysis has not reached the
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PlaybackTap } from './playback-tap';
|
|
1
2
|
/** The file the analysis reads, the same one playback plays. */
|
|
2
3
|
export type LoudnessSource = {
|
|
3
4
|
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
@@ -21,10 +22,17 @@ export type LoudnessAnalysis = {
|
|
|
21
22
|
* nearest the playhead first, then the rest of the file, and drops nothing it learned on a seek. Until a stretch is
|
|
22
23
|
* analysed, and for the whole file where this browser's WebCodecs cannot decode the track, the worklet's live stage
|
|
23
24
|
* plays.
|
|
25
|
+
*
|
|
26
|
+
* Given a `tap` instead, it reads nothing and analyses the audio playback remuxed for `audioStreamIndex`, which runs
|
|
27
|
+
* as far ahead of the playhead as playback buffers. It starts at the next fragment playback fetches, so what was
|
|
28
|
+
* buffered before it attached is not analysed.
|
|
24
29
|
*/
|
|
25
30
|
export declare const startLoudnessAnalysis: ({ node, element, scriptUrl, source }: {
|
|
26
31
|
node: AudioWorkletNode;
|
|
27
32
|
element: HTMLMediaElement;
|
|
28
33
|
scriptUrl: string;
|
|
29
|
-
source: LoudnessSource
|
|
34
|
+
source: LoudnessSource | {
|
|
35
|
+
tap: PlaybackTap;
|
|
36
|
+
audioStreamIndex: number;
|
|
37
|
+
};
|
|
30
38
|
}) => LoudnessAnalysis;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PlaybackOutput } from '../engine/playback';
|
|
2
|
+
/**
|
|
3
|
+
* Playback's output, for an analysis that reads nothing of its own: `push` takes what playback's remuxer produced,
|
|
4
|
+
* and `attach` hands a listener what follows for one input audio stream, starting with the last init segment and the
|
|
5
|
+
* run in progress. `join` marks a run some of whose fragments went by before the listener was attached.
|
|
6
|
+
*/
|
|
7
|
+
export type PlaybackTap = {
|
|
8
|
+
push: (output: PlaybackOutput) => void;
|
|
9
|
+
attach: (audioStreamIndex: number, listener: (output: PlaybackOutput, join?: boolean) => void) => () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare const createPlaybackTap: () => PlaybackTap;
|
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, MediaChapter, AudioStream } from './playback';
|
|
2
|
+
export type { PlaybackOptions, PlaybackController, PlaybackOutput, 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-Cr_7OImR.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 };
|
|
@@ -21,6 +21,26 @@ export type MediaChapter = {
|
|
|
21
21
|
end: number;
|
|
22
22
|
title: string;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* What the remuxer produced, in the order it was appended: the init segment and the input audio stream it carries,
|
|
26
|
+
* a run at load and at every remuxer seek (`pts` is the timestampOffset its fragments are appended at), each
|
|
27
|
+
* fragment, and the end of the file once read. The buffers are playback's own and may be appended again, so a
|
|
28
|
+
* consumer copies what it keeps.
|
|
29
|
+
*/
|
|
30
|
+
export type PlaybackOutput = {
|
|
31
|
+
type: 'init';
|
|
32
|
+
data: ArrayBuffer;
|
|
33
|
+
audioMimeType: string;
|
|
34
|
+
audioStreamIndex: number;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'run';
|
|
37
|
+
pts: number;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'fragment';
|
|
40
|
+
data: ArrayBuffer;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'end';
|
|
43
|
+
};
|
|
24
44
|
export type PlaybackOptions = {
|
|
25
45
|
videoElement: HTMLVideoElement;
|
|
26
46
|
/**
|
|
@@ -49,6 +69,7 @@ export type PlaybackOptions = {
|
|
|
49
69
|
onSeek?: (fraction: number) => void;
|
|
50
70
|
onSubtitleStreams?: (streams: SubtitleStream[]) => void;
|
|
51
71
|
onAudioStreams?: (streams: AudioStream[], selected: number) => void;
|
|
72
|
+
onOutput?: (output: PlaybackOutput) => void;
|
|
52
73
|
};
|
|
53
74
|
export type PlaybackController = {
|
|
54
75
|
destroy: () => void;
|
|
@@ -174,13 +174,13 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
174
174
|
constructor(e, t) {
|
|
175
175
|
super(e, t), this.name = "MediaElementError";
|
|
176
176
|
}
|
|
177
|
-
}, _ = (e) => !!e && typeof e == "object" && e.mediaElement === !0, v = -20, y = 60, b = 30, x = -5, S = 20, C = 5, w = 15e3, T = 1, E = 3,
|
|
177
|
+
}, _ = (e) => !!e && typeof e == "object" && e.mediaElement === !0, v = -20, y = 60, b = 30, x = -5, S = 20, C = 5, w = 15e3, T = 1, E = 3, ee = 12, D = 200, O = 250, k = 25e5, A = (e) => {
|
|
178
178
|
let { worker: t } = e, n = setTimeout(() => t.terminate(), 2e3);
|
|
179
179
|
e.destroy().catch(() => {}).finally(() => {
|
|
180
180
|
clearTimeout(n), t.terminate();
|
|
181
181
|
});
|
|
182
|
-
},
|
|
183
|
-
let { videoElement: n, subtitleContainer: r, read: a, length: s, publicPath: c, libavWorkerUrl: l, jassubWorkerUrl: u, jassubWasmUrl: d, jassubLegacyWasmUrl: f, defaultFontUrl: p, bufferSize: m =
|
|
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: ie, onSubtitleStreams: ae, onAudioStreams: oe, onOutput: j } = t, M = await e({
|
|
184
184
|
publicPath: c,
|
|
185
185
|
workerUrl: l,
|
|
186
186
|
workerOptions: { type: "module" },
|
|
@@ -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
|
-
}), N = [], P = !1,
|
|
191
|
+
}), N = [], P = !1, se = () => {
|
|
192
192
|
if (!P) {
|
|
193
193
|
P = !0;
|
|
194
194
|
for (let e of [...N].reverse()) try {
|
|
@@ -196,10 +196,10 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
196
196
|
} catch {}
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
-
N.push(() =>
|
|
199
|
+
N.push(() => A(M));
|
|
200
200
|
try {
|
|
201
201
|
let e = await M.init(), t = e.audioStreams ?? [], a = t.some((e) => e.streamIndex === _) ? _ : t[0]?.streamIndex ?? -1;
|
|
202
|
-
|
|
202
|
+
oe?.(t, a);
|
|
203
203
|
let s = h({
|
|
204
204
|
video: n,
|
|
205
205
|
container: r,
|
|
@@ -207,19 +207,19 @@ 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
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");
|
|
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
|
-
let m = new MediaSource(),
|
|
216
|
-
N.push(() => URL.revokeObjectURL(
|
|
215
|
+
let m = new MediaSource(), k = URL.createObjectURL(m);
|
|
216
|
+
N.push(() => URL.revokeObjectURL(k)), n.src = k, N.push(() => {
|
|
217
217
|
try {
|
|
218
218
|
n.pause();
|
|
219
219
|
} catch {}
|
|
220
220
|
n.removeAttribute("src"), n.load();
|
|
221
221
|
});
|
|
222
|
-
let
|
|
222
|
+
let A = await new Promise((t, n) => {
|
|
223
223
|
let r = setTimeout(() => {
|
|
224
224
|
i(), n(/* @__PURE__ */ Error("The MediaSource never opened"));
|
|
225
225
|
}, w), i = () => {
|
|
@@ -237,138 +237,155 @@ 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
|
-
|
|
240
|
+
}), { appendBuffer: F, unbufferRange: ce, updateTimestampOffset: le, endOfStream: ue } = o(A, m);
|
|
241
|
+
await F(e.data), j?.({
|
|
242
|
+
type: "init",
|
|
243
|
+
data: e.data,
|
|
244
|
+
audioMimeType: e.info.output.audioMimeType,
|
|
245
|
+
audioStreamIndex: a
|
|
246
|
+
}), j?.({
|
|
247
|
+
type: "run",
|
|
248
|
+
pts: 0
|
|
249
|
+
}), e.subtitles?.length && s.pushFragments(e.subtitles), te?.();
|
|
250
|
+
let I = !1, L = !1, R = 0, z, de = () => z ?? n.currentTime, B = !1, fe = !1, V = 0, H = 0, U = null, W = 0, G = 0, K = !1, pe = 0, q = !0, J = (e, t, n = !1) => {
|
|
243
251
|
let r = e?.message === "Cancelled";
|
|
244
|
-
t && r || (console.error(e), (!K || n) && (K = !0,
|
|
245
|
-
},
|
|
246
|
-
if (
|
|
247
|
-
let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(
|
|
252
|
+
t && r || (console.error(e), (!K || n) && (K = !0, ne?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
|
|
253
|
+
}, me = async (e = !1) => {
|
|
254
|
+
if (R > 0 && !e) return;
|
|
255
|
+
let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(A), s = (e, t) => (q = !1, ce(e, t));
|
|
248
256
|
o.length && o[0].start < t + r && await s(0, t + r);
|
|
249
257
|
for (let { end: e } of o) e > t + a && await s(t + a, e);
|
|
250
|
-
},
|
|
251
|
-
let e = i(
|
|
258
|
+
}, he = () => {
|
|
259
|
+
let e = i(A);
|
|
252
260
|
if (!e.length) return !0;
|
|
253
|
-
let t =
|
|
254
|
-
return
|
|
255
|
-
},
|
|
256
|
-
let t =
|
|
261
|
+
let t = de(), n = e.find((e) => e.start <= t + T && t < e.end);
|
|
262
|
+
return be() && ve(t) && (!n || n.start <= t) || n && n.end >= t + y ? !1 : !n || n.end < t + b;
|
|
263
|
+
}, ge = (e) => i(A).find((t) => t.start <= e + T && e < t.end), _e = () => ge(n.currentTime), ve = (e = n.currentTime) => {
|
|
264
|
+
let t = ge(e);
|
|
257
265
|
return e >= G - .1 || !!t && t.end >= G - .1;
|
|
258
|
-
},
|
|
259
|
-
let e = i(
|
|
260
|
-
e.length && (G = Math.max(G, ...e)),
|
|
261
|
-
},
|
|
262
|
-
|
|
266
|
+
}, ye = () => {
|
|
267
|
+
let e = i(A).map((e) => e.end);
|
|
268
|
+
e.length && (G = Math.max(G, ...e)), B && (fe = !0);
|
|
269
|
+
}, be = () => B || fe && (i(A).at(-1)?.end ?? 0) >= G - .1, Y = async (e) => {
|
|
270
|
+
L = !0, U = null, W = 0, pe = e, q = !0;
|
|
263
271
|
let t = ++H;
|
|
264
272
|
try {
|
|
265
273
|
let { data: n, pts: r, subtitles: i, finished: a } = await M.seek(e);
|
|
266
|
-
if (P || t !== H || (a && (
|
|
267
|
-
|
|
274
|
+
if (P || t !== H || (a && (B = !0), i?.length && s.pushFragments(i), await le(r), P || t !== H)) return;
|
|
275
|
+
j?.({
|
|
276
|
+
type: "run",
|
|
277
|
+
pts: r
|
|
278
|
+
}), n.byteLength && j?.({
|
|
279
|
+
type: "fragment",
|
|
280
|
+
data: n
|
|
281
|
+
}), a && j?.({ type: "end" }), n.byteLength && (await F(n), ye());
|
|
268
282
|
} catch (e) {
|
|
269
283
|
t === H && (q = !1), J(e, P || t !== H);
|
|
270
284
|
} finally {
|
|
271
|
-
t === H && (
|
|
285
|
+
t === H && (L = !1);
|
|
272
286
|
}
|
|
273
|
-
},
|
|
287
|
+
}, xe = async () => {
|
|
274
288
|
let e = U;
|
|
275
289
|
if (e) try {
|
|
276
|
-
await
|
|
290
|
+
await F(e), ye(), U === e && (U = null, W = 0);
|
|
277
291
|
} catch (t) {
|
|
278
292
|
if (U !== e) return;
|
|
279
293
|
if (t?.name === "QuotaExceededError") {
|
|
280
|
-
let t = await
|
|
294
|
+
let t = await me(!0).then(() => !0, () => !1);
|
|
281
295
|
if (U !== e) return;
|
|
282
296
|
if (t) {
|
|
283
|
-
|
|
297
|
+
B = !1, await Y(_e()?.end ?? n.currentTime);
|
|
284
298
|
return;
|
|
285
299
|
}
|
|
286
300
|
}
|
|
287
301
|
if (W += 1, W < C) return;
|
|
288
302
|
throw U = null, t;
|
|
289
303
|
}
|
|
290
|
-
},
|
|
291
|
-
if (
|
|
292
|
-
let e = ++
|
|
293
|
-
|
|
304
|
+
}, Se = async () => {
|
|
305
|
+
if (I || L || $ !== void 0 || P || !U && (B || !he())) return;
|
|
306
|
+
let e = ++V, t = H, n = () => P || e !== V || t !== H;
|
|
307
|
+
I = !0;
|
|
294
308
|
try {
|
|
295
309
|
if (!U) {
|
|
296
310
|
let { data: e, subtitles: t, finished: r } = await M.read();
|
|
297
|
-
if (n() || (r && (
|
|
311
|
+
if (n() || (r && (B = !0), t?.length && s.pushFragments(t), e.byteLength && j?.({
|
|
312
|
+
type: "fragment",
|
|
313
|
+
data: e
|
|
314
|
+
}), r && j?.({ type: "end" }), !e.byteLength)) return;
|
|
298
315
|
U = e, W = 0;
|
|
299
316
|
}
|
|
300
|
-
await
|
|
317
|
+
await xe(), !n() && K && (K = !1, re?.());
|
|
301
318
|
} catch (e) {
|
|
302
319
|
J(e, n());
|
|
303
320
|
} finally {
|
|
304
|
-
e ===
|
|
321
|
+
e === V && (I = !1);
|
|
305
322
|
}
|
|
306
|
-
},
|
|
307
|
-
let t =
|
|
323
|
+
}, Ce = (e) => e >= pe && we(e) && Te(e), we = (e) => i(A).some((t) => t.start <= e && e < t.end), X = (e) => {
|
|
324
|
+
let t = ge(e);
|
|
308
325
|
return t ? t.end - e : 0;
|
|
309
|
-
},
|
|
310
|
-
if (!P && !(
|
|
311
|
-
|
|
326
|
+
}, Te = (e) => X(e) >= E || be() && ve(e), Ee = async (e) => {
|
|
327
|
+
if (!P && !(we(e) && Te(e))) {
|
|
328
|
+
B = !1, R++, z = e;
|
|
312
329
|
try {
|
|
313
330
|
await Y(e);
|
|
314
331
|
let t = H;
|
|
315
|
-
for (let n = 0; n <
|
|
332
|
+
for (let n = 0; n < ee && !(P || t !== H || B || X(e) >= E); n++) {
|
|
316
333
|
let t = X(e);
|
|
317
|
-
if (await
|
|
334
|
+
if (await Se(), X(e) <= t) break;
|
|
318
335
|
}
|
|
319
336
|
} finally {
|
|
320
|
-
|
|
337
|
+
R--, z === e && (z = void 0);
|
|
321
338
|
}
|
|
322
339
|
}
|
|
323
|
-
},
|
|
324
|
-
let t = performance.now(), n = t -
|
|
325
|
-
if (n >=
|
|
326
|
-
|
|
340
|
+
}, De = 0, Z = null, Q, Oe = (e) => {
|
|
341
|
+
let t = performance.now(), n = t - De;
|
|
342
|
+
if (n >= D) {
|
|
343
|
+
De = t, Z = null, ie?.(e);
|
|
327
344
|
return;
|
|
328
345
|
}
|
|
329
346
|
Z = e, !Q && (Q = setTimeout(() => {
|
|
330
347
|
Q = void 0;
|
|
331
348
|
let e = Z;
|
|
332
|
-
Z = null, !(e === null || P) && (
|
|
333
|
-
},
|
|
349
|
+
Z = null, !(e === null || P) && (De = performance.now(), ie?.(e));
|
|
350
|
+
}, D - n));
|
|
334
351
|
};
|
|
335
352
|
N.push(() => {
|
|
336
353
|
Q && clearTimeout(Q);
|
|
337
354
|
});
|
|
338
|
-
let
|
|
355
|
+
let ke = 0, $;
|
|
339
356
|
N.push(() => {
|
|
340
357
|
$ && clearTimeout($);
|
|
341
358
|
});
|
|
342
|
-
let
|
|
359
|
+
let Ae = () => {
|
|
343
360
|
let t = n.currentTime, r = e.info.input.duration || n.duration;
|
|
344
|
-
r > 0 &&
|
|
345
|
-
let i = performance.now(), a = i -
|
|
346
|
-
if (
|
|
347
|
-
if (
|
|
361
|
+
r > 0 && Oe(Math.min(Math.max(t / r, 0), 1));
|
|
362
|
+
let i = performance.now(), a = i - ke < O;
|
|
363
|
+
if (ke = i, !Ce(t) && !(q && (L || X(t) > 0) && Math.abs(t - pe) < .001)) {
|
|
364
|
+
if (B = !1, $ && clearTimeout($), !a) {
|
|
348
365
|
$ = void 0, Y(t);
|
|
349
366
|
return;
|
|
350
367
|
}
|
|
351
368
|
$ = setTimeout(() => {
|
|
352
369
|
if ($ = void 0, P) return;
|
|
353
370
|
let e = n.currentTime;
|
|
354
|
-
|
|
355
|
-
},
|
|
371
|
+
Ce(e) || (B = !1, Y(e));
|
|
372
|
+
}, O);
|
|
356
373
|
}
|
|
357
374
|
};
|
|
358
|
-
n.addEventListener("seeking",
|
|
359
|
-
let
|
|
360
|
-
if (
|
|
361
|
-
|
|
375
|
+
n.addEventListener("seeking", Ae), N.push(() => n.removeEventListener("seeking", Ae));
|
|
376
|
+
let je = !1, Me = () => {
|
|
377
|
+
if (je) return;
|
|
378
|
+
je = !0;
|
|
362
379
|
let e = n.error;
|
|
363
380
|
J(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
|
|
364
381
|
};
|
|
365
|
-
n.addEventListener("error",
|
|
366
|
-
let
|
|
367
|
-
|
|
382
|
+
n.addEventListener("error", Me), N.push(() => n.removeEventListener("error", Me));
|
|
383
|
+
let Ne = setInterval(() => {
|
|
384
|
+
je || n.error || (me().catch(() => {}), Se().catch((e) => J(e, !1)), be() && !U && ve() && m.readyState === "open" && ue(() => !L).catch(() => {}));
|
|
368
385
|
}, 100);
|
|
369
|
-
return N.push(() => clearInterval(
|
|
370
|
-
destroy:
|
|
371
|
-
prepareSeek:
|
|
386
|
+
return N.push(() => clearInterval(Ne)), {
|
|
387
|
+
destroy: se,
|
|
388
|
+
prepareSeek: Ee,
|
|
372
389
|
videoElement: n,
|
|
373
390
|
selectSubtitleStream: (e) => s.selectStream(e),
|
|
374
391
|
indexes: e.indexes ?? [],
|
|
@@ -378,10 +395,10 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
378
395
|
audioMimeType: e.info.output.audioMimeType
|
|
379
396
|
};
|
|
380
397
|
} catch (e) {
|
|
381
|
-
throw
|
|
398
|
+
throw se(), e;
|
|
382
399
|
}
|
|
383
|
-
},
|
|
384
|
-
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s =
|
|
400
|
+
}, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, j = 1e4, M = 1.5, N = 4e6, P = 3e4, se = async (e) => {
|
|
401
|
+
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = ie } = e, c = await t({
|
|
385
402
|
publicPath: n,
|
|
386
403
|
workerUrl: r,
|
|
387
404
|
workerOptions: { type: "module" },
|
|
@@ -389,7 +406,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
389
406
|
read: a
|
|
390
407
|
});
|
|
391
408
|
try {
|
|
392
|
-
let t = await c.init(), n = t.duration, r = Math.max(e.interval ??
|
|
409
|
+
let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? ne, n / re), a = [], l = (e) => {
|
|
393
410
|
let t = [];
|
|
394
411
|
for (let [a, o] of e.entries()) {
|
|
395
412
|
let s = t.at(-1);
|
|
@@ -397,7 +414,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
397
414
|
timestamp: o.timestamp,
|
|
398
415
|
endTime: n,
|
|
399
416
|
startByte: o.pos,
|
|
400
|
-
endByte: Math.min((e[a + 1]?.pos ?? i) +
|
|
417
|
+
endByte: Math.min((e[a + 1]?.pos ?? i) + ae, i),
|
|
401
418
|
done: !1,
|
|
402
419
|
attempts: 0
|
|
403
420
|
});
|
|
@@ -436,7 +453,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
436
453
|
}), o(e);
|
|
437
454
|
}, x = async (e) => {
|
|
438
455
|
if (d) return;
|
|
439
|
-
let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")),
|
|
456
|
+
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))));
|
|
440
457
|
r.getContext("2d").drawImage(n, 0, 0, r.width, r.height), n.close();
|
|
441
458
|
let i = await r.convertToBlob({
|
|
442
459
|
type: "image/webp",
|
|
@@ -460,7 +477,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
460
477
|
if (!f.length) return;
|
|
461
478
|
let e = f.splice(y(), 1)[0];
|
|
462
479
|
p = !0, x(e).catch(() => {
|
|
463
|
-
e.attempts += 1, e.done = e.attempts >=
|
|
480
|
+
e.attempts += 1, e.done = e.attempts >= oe;
|
|
464
481
|
}).finally(() => {
|
|
465
482
|
p = !1, S();
|
|
466
483
|
});
|
|
@@ -469,7 +486,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
469
486
|
}, w = async (e) => {
|
|
470
487
|
_ = !0;
|
|
471
488
|
try {
|
|
472
|
-
let t = await Promise.race([c.init(), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")),
|
|
489
|
+
let t = await Promise.race([c.init(), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), P))]);
|
|
473
490
|
if (d) return;
|
|
474
491
|
h = e;
|
|
475
492
|
let n = l(t.indexes);
|
|
@@ -495,7 +512,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
495
512
|
if (d) return;
|
|
496
513
|
v = e;
|
|
497
514
|
let t = T(e);
|
|
498
|
-
h < 0 ? h = t : (t >= Math.max(h *
|
|
515
|
+
h < 0 ? h = t : (t >= Math.max(h * M, h + N) || t >= i && h < i) && (g = !0), E(e), S();
|
|
499
516
|
},
|
|
500
517
|
prioritize: (e) => {
|
|
501
518
|
d || (m = e);
|
|
@@ -503,14 +520,14 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
503
520
|
destroy: () => {
|
|
504
521
|
d = !0, f.length = 0;
|
|
505
522
|
for (let e of u) URL.revokeObjectURL(e.url);
|
|
506
|
-
u = [],
|
|
523
|
+
u = [], A(c);
|
|
507
524
|
}
|
|
508
525
|
};
|
|
509
526
|
} catch (e) {
|
|
510
|
-
throw
|
|
527
|
+
throw A(c), e;
|
|
511
528
|
}
|
|
512
|
-
},
|
|
513
|
-
let { video: t, subtitles: n } = e, r = e.mode ??
|
|
529
|
+
}, F = 1280, ce = 1920, le = 250, ue = 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, z = () => I() ? L() ? "window" : R() ? "burn-in" : null : null, de = (e) => {
|
|
530
|
+
let { video: t, subtitles: n } = e, r = e.mode ?? z() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? ce : 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) => {
|
|
514
531
|
r.drawImage(t, 0, 0, e.width, e.height);
|
|
515
532
|
let i = n.querySelector("canvas");
|
|
516
533
|
i && i.width > 0 && i.height > 0 && r.drawImage(i, 0, 0, e.width, e.height);
|
|
@@ -539,39 +556,39 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
539
556
|
l = !0;
|
|
540
557
|
let { mirror: e } = a;
|
|
541
558
|
h(), e.play().then(() => new Promise((e) => {
|
|
542
|
-
setTimeout(e,
|
|
559
|
+
setTimeout(e, ue);
|
|
543
560
|
})).catch(() => {}).finally(() => {
|
|
544
561
|
e.pause(), setTimeout(() => {
|
|
545
562
|
l = !1;
|
|
546
|
-
},
|
|
563
|
+
}, ue);
|
|
547
564
|
});
|
|
548
565
|
}, E = [["play", () => {
|
|
549
566
|
t.play().catch(() => {});
|
|
550
|
-
}], ["pause", () => t.pause()]],
|
|
567
|
+
}], ["pause", () => t.pause()]], ee = (e) => {
|
|
551
568
|
if ("mediaSession" in navigator) for (let [t, n] of E) try {
|
|
552
569
|
navigator.mediaSession.setActionHandler(t, e ? n : null);
|
|
553
570
|
} catch {}
|
|
554
|
-
},
|
|
555
|
-
if (y(),
|
|
571
|
+
}, D = () => {
|
|
572
|
+
if (y(), ee(!1), t.removeEventListener("play", C), t.removeEventListener("pause", w), t.removeEventListener("seeked", T), !a) {
|
|
556
573
|
f?.();
|
|
557
574
|
return;
|
|
558
575
|
}
|
|
559
576
|
let { stream: e, mirror: n } = a;
|
|
560
|
-
n.removeEventListener("play", x), n.removeEventListener("pause", S), n.removeEventListener("leavepictureinpicture",
|
|
577
|
+
n.removeEventListener("play", x), n.removeEventListener("pause", S), n.removeEventListener("leavepictureinpicture", O);
|
|
561
578
|
for (let t of e.getTracks()) t.stop();
|
|
562
579
|
n.remove(), a = void 0, f?.();
|
|
563
|
-
},
|
|
580
|
+
}, O = () => D(), k = (e) => {
|
|
564
581
|
let r = t.style.opacity, i = n.style.display;
|
|
565
582
|
e.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;z-index:1", t.style.opacity = "0", n.style.display = "none", f = () => {
|
|
566
583
|
f = void 0, t.style.opacity = r, n.style.display = i;
|
|
567
584
|
};
|
|
568
|
-
},
|
|
585
|
+
}, A = async () => {
|
|
569
586
|
let n = Math.min(t.videoWidth || i, i), o = Math.round(n * ((t.videoHeight || 9) / (t.videoWidth || 16))), c = document.createElement("canvas");
|
|
570
587
|
c.width = n, c.height = o;
|
|
571
588
|
let l = c.getContext("2d");
|
|
572
589
|
if (!l) throw Error("the picture in picture composite has no 2d context");
|
|
573
590
|
let u = document.createElement("video");
|
|
574
|
-
u.muted = !0, u.playsInline = !0, r === "burn-in" ?
|
|
591
|
+
u.muted = !0, u.playsInline = !0, r === "burn-in" ? k(u) : u.style.cssText = "position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-1px;top:-1px", m(c, l);
|
|
575
592
|
let f = {
|
|
576
593
|
composite: c,
|
|
577
594
|
context: l,
|
|
@@ -580,7 +597,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
580
597
|
};
|
|
581
598
|
if (a = f, u.srcObject = f.stream, (e.container ?? t.parentElement ?? document.body).appendChild(u), v(), s = setInterval(() => {
|
|
582
599
|
t.paused && h();
|
|
583
|
-
},
|
|
600
|
+
}, le), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
|
|
584
601
|
let t = () => {
|
|
585
602
|
clearTimeout(n), e();
|
|
586
603
|
}, n = setTimeout(t, 1e3);
|
|
@@ -589,7 +606,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
589
606
|
u.remove();
|
|
590
607
|
return;
|
|
591
608
|
}
|
|
592
|
-
if (u.addEventListener("play", x), u.addEventListener("pause", S), u.addEventListener("leavepictureinpicture",
|
|
609
|
+
if (u.addEventListener("play", x), u.addEventListener("pause", S), u.addEventListener("leavepictureinpicture", O), t.addEventListener("play", C), t.addEventListener("pause", w), t.addEventListener("seeked", T), ee(!0), b(), r === "burn-in") {
|
|
593
610
|
t.paused && u.pause(), e.onBurnedInChange?.(!0);
|
|
594
611
|
return;
|
|
595
612
|
}
|
|
@@ -602,7 +619,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
602
619
|
try {
|
|
603
620
|
if (p()) {
|
|
604
621
|
if (r === "burn-in") {
|
|
605
|
-
|
|
622
|
+
D(), e.onBurnedInChange?.(!1);
|
|
606
623
|
return;
|
|
607
624
|
}
|
|
608
625
|
await document.exitPictureInPicture().catch(() => {});
|
|
@@ -610,9 +627,9 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
610
627
|
}
|
|
611
628
|
r !== "burn-in" && document.pictureInPictureElement && await document.exitPictureInPicture().catch(() => {});
|
|
612
629
|
try {
|
|
613
|
-
await
|
|
630
|
+
await A();
|
|
614
631
|
} catch (t) {
|
|
615
|
-
console.warn("subtitle compositing for picture in picture failed", t),
|
|
632
|
+
console.warn("subtitle compositing for picture in picture failed", t), D(), r === "burn-in" && e.onBurnedInChange?.(!1);
|
|
616
633
|
}
|
|
617
634
|
} finally {
|
|
618
635
|
u = !1;
|
|
@@ -621,9 +638,9 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
621
638
|
},
|
|
622
639
|
mode: r,
|
|
623
640
|
destroy: () => {
|
|
624
|
-
d = !0, r !== "burn-in" && p() && document.exitPictureInPicture().catch(() => {}),
|
|
641
|
+
d = !0, r !== "burn-in" && p() && document.exitPictureInPicture().catch(() => {}), D(), r === "burn-in" && e.onBurnedInChange?.(!1);
|
|
625
642
|
}
|
|
626
643
|
};
|
|
627
644
|
};
|
|
628
645
|
//#endregion
|
|
629
|
-
export { g as a,
|
|
646
|
+
export { g as a, A as c, i as d, o as f, k as i, s as l, z as n, _ as o, se as r, te as s, de 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, MediaChapter, MediaIndex, PictureInPictureController, PlaybackController, PlaybackOptions, SubtitleStream, ThumbnailImage, } from './engine';
|
|
13
|
+
export type { AudioStream, MediaChapter, MediaIndex, PictureInPictureController, PlaybackController, PlaybackOptions, PlaybackOutput, SubtitleStream, ThumbnailImage, } from './engine';
|
|
14
14
|
export { startPlayback, createThumbnailGenerator, createSubtitleRenderer, createPictureInPicture, SUBTITLES_OFF, } from './engine';
|