@banou/media-player 0.10.16 → 0.10.18
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 +4 -0
- package/dist/engine/index.js +1 -1
- package/dist/engine/source-buffer.d.ts +1 -1
- package/dist/{engine-C9vSEJYW.js → engine-C4Gj_4zs.js} +136 -136
- package/dist/index.js +119 -103
- package/dist/react/components/chrome.d.ts +2 -1
- package/dist/react/components/control-bar.d.ts +5 -1
- package/dist/react/video-player.d.ts +7 -0
- package/package.json +1 -1
- package/src/lib/engine/playback.ts +4 -2
- package/src/lib/engine/source-buffer.ts +6 -3
- package/src/lib/react/components/chrome.tsx +8 -2
- package/src/lib/react/components/control-bar.tsx +24 -5
- package/src/lib/react/video-player.tsx +9 -0
package/README.md
CHANGED
|
@@ -158,6 +158,10 @@ an offer to skip an opening or ending, volume on a log curve, mute, playback spe
|
|
|
158
158
|
selection, subtitle track selection, picture in picture, fullscreen, and keyboard shortcuts. Nothing
|
|
159
159
|
is persisted: volume, speed and track choices start at their defaults every load.
|
|
160
160
|
|
|
161
|
+
The shortcuts (Space or `k`, `f`, `m`, the arrow keys) listen on the whole page. Pass `shortcuts="player"` for a
|
|
162
|
+
player among other content: a key then acts only from inside the player (a click on the picture puts the focus
|
|
163
|
+
there) or while it is fullscreen, and Space and the arrows keep scrolling the page everywhere else.
|
|
164
|
+
|
|
161
165
|
### Chapters
|
|
162
166
|
|
|
163
167
|
Chapters come from the container, and the seekbar draws them as segments with a break at each
|
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-C4Gj_4zs.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 };
|
|
@@ -8,5 +8,5 @@ export declare const updateSourceBuffer: (sourceBuffer: SourceBuffer, mediaSourc
|
|
|
8
8
|
appendBuffer: (buffer: ArrayBuffer | Uint8Array) => Promise<void>;
|
|
9
9
|
unbufferRange: (start: number, end: number) => Promise<void>;
|
|
10
10
|
updateTimestampOffset: (timestampOffset: number) => Promise<void>;
|
|
11
|
-
endOfStream: () => Promise<void>;
|
|
11
|
+
endOfStream: (allowed?: () => boolean) => Promise<void>;
|
|
12
12
|
};
|
|
@@ -26,8 +26,8 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
26
26
|
updateTimestampOffset: (t) => r(async () => {
|
|
27
27
|
e.timestampOffset = t;
|
|
28
28
|
}),
|
|
29
|
-
endOfStream: () => r(async () => {
|
|
30
|
-
if (
|
|
29
|
+
endOfStream: (n = () => !0) => r(async () => {
|
|
30
|
+
if (t.readyState === "open" && !e.updating && n()) try {
|
|
31
31
|
t.endOfStream();
|
|
32
32
|
} catch {}
|
|
33
33
|
})
|
|
@@ -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, D = 12, O = 200, k = 250, A = 25e5, j = (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
|
+
}, ee = 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 = A, audioStreamIndex: _, onReady: ee, onError: te, onRecovered: ne, onSeek: re, onSubtitleStreams: ie, onAudioStreams: ae } = t, M = 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
|
+
}), N = [], P = !1, F = () => {
|
|
192
|
+
if (!P) {
|
|
193
|
+
P = !0;
|
|
194
|
+
for (let e of [...N].reverse()) try {
|
|
195
195
|
e();
|
|
196
196
|
} catch {}
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
-
|
|
199
|
+
N.push(() => j(M));
|
|
200
200
|
try {
|
|
201
|
-
let e = await
|
|
202
|
-
|
|
201
|
+
let e = await M.init(), t = e.audioStreams ?? [], a = t.some((e) => e.streamIndex === _) ? _ : t[0]?.streamIndex ?? -1;
|
|
202
|
+
ae?.(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: ie
|
|
211
211
|
});
|
|
212
|
-
if (
|
|
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
|
-
|
|
215
|
+
let m = new MediaSource(), A = URL.createObjectURL(m);
|
|
216
|
+
N.push(() => URL.revokeObjectURL(A)), n.src = A, 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 j = 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,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:
|
|
241
|
-
await
|
|
242
|
-
let
|
|
240
|
+
}), { appendBuffer: I, unbufferRange: oe, updateTimestampOffset: se, endOfStream: ce } = o(j, m);
|
|
241
|
+
await I(e.data), e.subtitles?.length && s.pushFragments(e.subtitles), ee?.();
|
|
242
|
+
let L = !1, R = !1, z = 0, B, le = () => B ?? n.currentTime, V = !1, ue = !1, de = 0, H = 0, U = null, W = 0, G = 0, K = !1, fe = 0, q = !0, J = (e, t, n = !1) => {
|
|
243
243
|
let r = e?.message === "Cancelled";
|
|
244
|
-
t && r || (console.error(e), (!
|
|
245
|
-
},
|
|
246
|
-
if (
|
|
247
|
-
let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(
|
|
244
|
+
t && r || (console.error(e), (!K || n) && (K = !0, te?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
|
|
245
|
+
}, pe = async (e = !1) => {
|
|
246
|
+
if (z > 0 && !e) return;
|
|
247
|
+
let t = n.currentTime, r = e ? x : v, a = e ? S : y, o = i(j), s = (e, t) => (q = !1, oe(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
|
-
},
|
|
251
|
-
let e = i(
|
|
250
|
+
}, me = () => {
|
|
251
|
+
let e = i(j);
|
|
252
252
|
if (!e.length) return !0;
|
|
253
|
-
let t =
|
|
254
|
-
return
|
|
255
|
-
},
|
|
256
|
-
let t =
|
|
257
|
-
return e >=
|
|
258
|
-
},
|
|
259
|
-
let e = i(
|
|
260
|
-
e.length && (
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
let t = ++
|
|
253
|
+
let t = le(), n = e.find((e) => e.start <= t + T && t < e.end);
|
|
254
|
+
return ye() && _e(t) && (!n || n.start <= t) || n && n.end >= t + y ? !1 : !n || n.end < t + b;
|
|
255
|
+
}, he = (e) => i(j).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 >= G - .1 || !!t && t.end >= G - .1;
|
|
258
|
+
}, ve = () => {
|
|
259
|
+
let e = i(j).map((e) => e.end);
|
|
260
|
+
e.length && (G = Math.max(G, ...e)), V && (ue = !0);
|
|
261
|
+
}, ye = () => V || ue && (i(j).at(-1)?.end ?? 0) >= G - .1, Y = async (e) => {
|
|
262
|
+
R = !0, U = null, W = 0, fe = e, q = !0;
|
|
263
|
+
let t = ++H;
|
|
264
264
|
try {
|
|
265
|
-
let { data: n, pts: r, subtitles: i, finished: a } = await
|
|
266
|
-
if (
|
|
267
|
-
n.byteLength && (await
|
|
265
|
+
let { data: n, pts: r, subtitles: i, finished: a } = await M.seek(e);
|
|
266
|
+
if (P || t !== H || (a && (V = !0), i?.length && s.pushFragments(i), await se(r), P || t !== H)) return;
|
|
267
|
+
n.byteLength && (await I(n), ve());
|
|
268
268
|
} catch (e) {
|
|
269
|
-
t ===
|
|
269
|
+
t === H && (q = !1), J(e, P || t !== H);
|
|
270
270
|
} finally {
|
|
271
|
-
t ===
|
|
271
|
+
t === H && (R = !1);
|
|
272
272
|
}
|
|
273
|
-
},
|
|
274
|
-
let e =
|
|
273
|
+
}, be = async () => {
|
|
274
|
+
let e = U;
|
|
275
275
|
if (e) try {
|
|
276
|
-
await
|
|
276
|
+
await I(e), ve(), U === e && (U = null, W = 0);
|
|
277
277
|
} catch (t) {
|
|
278
|
-
if (
|
|
278
|
+
if (U !== e) return;
|
|
279
279
|
if (t?.name === "QuotaExceededError") {
|
|
280
|
-
let t = await
|
|
281
|
-
if (
|
|
280
|
+
let t = await pe(!0).then(() => !0, () => !1);
|
|
281
|
+
if (U !== e) return;
|
|
282
282
|
if (t) {
|
|
283
|
-
|
|
283
|
+
V = !1, await Y(ge()?.end ?? n.currentTime);
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
-
if (
|
|
288
|
-
throw
|
|
287
|
+
if (W += 1, W < C) return;
|
|
288
|
+
throw U = null, t;
|
|
289
289
|
}
|
|
290
|
-
},
|
|
291
|
-
if (
|
|
292
|
-
let e = ++
|
|
293
|
-
|
|
290
|
+
}, xe = async () => {
|
|
291
|
+
if (L || R || $ !== void 0 || P || !U && (V || !me())) return;
|
|
292
|
+
let e = ++de, t = H, n = () => P || e !== de || t !== H;
|
|
293
|
+
L = !0;
|
|
294
294
|
try {
|
|
295
|
-
if (!
|
|
296
|
-
let { data: e, subtitles: t, finished: r } = await
|
|
297
|
-
if (n() || (r && (
|
|
298
|
-
|
|
295
|
+
if (!U) {
|
|
296
|
+
let { data: e, subtitles: t, finished: r } = await M.read();
|
|
297
|
+
if (n() || (r && (V = !0), t?.length && s.pushFragments(t), !e.byteLength)) return;
|
|
298
|
+
U = e, W = 0;
|
|
299
299
|
}
|
|
300
|
-
await
|
|
300
|
+
await be(), !n() && K && (K = !1, ne?.());
|
|
301
301
|
} catch (e) {
|
|
302
|
-
|
|
302
|
+
J(e, n());
|
|
303
303
|
} finally {
|
|
304
|
-
e ===
|
|
304
|
+
e === de && (L = !1);
|
|
305
305
|
}
|
|
306
|
-
},
|
|
307
|
-
let t =
|
|
306
|
+
}, Se = (e) => e >= fe && Ce(e) && we(e), Ce = (e) => i(j).some((t) => t.start <= e && e < t.end), X = (e) => {
|
|
307
|
+
let t = he(e);
|
|
308
308
|
return t ? t.end - e : 0;
|
|
309
|
-
},
|
|
310
|
-
if (!
|
|
311
|
-
|
|
309
|
+
}, we = (e) => X(e) >= E || ye() && _e(e), Te = async (e) => {
|
|
310
|
+
if (!P && !(Ce(e) && we(e))) {
|
|
311
|
+
V = !1, z++, B = e;
|
|
312
312
|
try {
|
|
313
|
-
await
|
|
314
|
-
let t =
|
|
315
|
-
for (let n = 0; n <
|
|
316
|
-
let t =
|
|
317
|
-
if (await
|
|
313
|
+
await Y(e);
|
|
314
|
+
let t = H;
|
|
315
|
+
for (let n = 0; n < D && !(P || t !== H || V || X(e) >= E); n++) {
|
|
316
|
+
let t = X(e);
|
|
317
|
+
if (await xe(), X(e) <= t) break;
|
|
318
318
|
}
|
|
319
319
|
} finally {
|
|
320
|
-
|
|
320
|
+
z--, B === e && (B = void 0);
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
-
},
|
|
324
|
-
let t = performance.now(), n = t -
|
|
325
|
-
if (n >=
|
|
326
|
-
|
|
323
|
+
}, Ee = 0, Z = null, Q, De = (e) => {
|
|
324
|
+
let t = performance.now(), n = t - Ee;
|
|
325
|
+
if (n >= O) {
|
|
326
|
+
Ee = t, Z = null, re?.(e);
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
let e =
|
|
332
|
-
|
|
333
|
-
},
|
|
329
|
+
Z = e, !Q && (Q = setTimeout(() => {
|
|
330
|
+
Q = void 0;
|
|
331
|
+
let e = Z;
|
|
332
|
+
Z = null, !(e === null || P) && (Ee = performance.now(), re?.(e));
|
|
333
|
+
}, O - n));
|
|
334
334
|
};
|
|
335
|
-
|
|
336
|
-
Z && clearTimeout(Z);
|
|
337
|
-
});
|
|
338
|
-
let ke = 0, Q;
|
|
339
|
-
M.push(() => {
|
|
335
|
+
N.push(() => {
|
|
340
336
|
Q && clearTimeout(Q);
|
|
341
337
|
});
|
|
342
|
-
let
|
|
338
|
+
let Oe = 0, $;
|
|
339
|
+
N.push(() => {
|
|
340
|
+
$ && clearTimeout($);
|
|
341
|
+
});
|
|
342
|
+
let ke = () => {
|
|
343
343
|
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 (
|
|
348
|
-
|
|
344
|
+
r > 0 && De(Math.min(Math.max(t / r, 0), 1));
|
|
345
|
+
let i = performance.now(), a = i - Oe < k;
|
|
346
|
+
if (Oe = i, !Se(t) && !(q && (R || X(t) > 0) && Math.abs(t - fe) < .001)) {
|
|
347
|
+
if (V = !1, $ && clearTimeout($), !a) {
|
|
348
|
+
$ = void 0, Y(t);
|
|
349
349
|
return;
|
|
350
350
|
}
|
|
351
|
-
|
|
352
|
-
if (
|
|
351
|
+
$ = setTimeout(() => {
|
|
352
|
+
if ($ = void 0, P) return;
|
|
353
353
|
let e = n.currentTime;
|
|
354
|
-
|
|
355
|
-
},
|
|
354
|
+
Se(e) || (V = !1, Y(e));
|
|
355
|
+
}, k);
|
|
356
356
|
}
|
|
357
357
|
};
|
|
358
|
-
n.addEventListener("seeking",
|
|
359
|
-
let
|
|
360
|
-
if (
|
|
361
|
-
|
|
358
|
+
n.addEventListener("seeking", ke), N.push(() => n.removeEventListener("seeking", ke));
|
|
359
|
+
let Ae = !1, je = () => {
|
|
360
|
+
if (Ae) return;
|
|
361
|
+
Ae = !0;
|
|
362
362
|
let e = n.error;
|
|
363
|
-
|
|
363
|
+
J(new g(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
|
|
364
364
|
};
|
|
365
|
-
n.addEventListener("error", je),
|
|
365
|
+
n.addEventListener("error", je), N.push(() => n.removeEventListener("error", je));
|
|
366
366
|
let Me = setInterval(() => {
|
|
367
|
-
|
|
367
|
+
Ae || n.error || (pe().catch(() => {}), xe().catch((e) => J(e, !1)), ye() && !U && _e() && m.readyState === "open" && ce(() => !R).catch(() => {}));
|
|
368
368
|
}, 100);
|
|
369
|
-
return
|
|
370
|
-
destroy:
|
|
371
|
-
prepareSeek:
|
|
369
|
+
return N.push(() => clearInterval(Me)), {
|
|
370
|
+
destroy: F,
|
|
371
|
+
prepareSeek: Te,
|
|
372
372
|
videoElement: n,
|
|
373
373
|
selectSubtitleStream: (e) => s.selectStream(e),
|
|
374
374
|
indexes: e.indexes ?? [],
|
|
@@ -378,10 +378,10 @@ 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
|
|
381
|
+
throw F(), e;
|
|
382
382
|
}
|
|
383
|
-
},
|
|
384
|
-
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s =
|
|
383
|
+
}, te = 5, ne = 500, re = 320, ie = 1e6, ae = 3, M = 1e4, N = 1.5, P = 4e6, F = 3e4, I = async (e) => {
|
|
384
|
+
let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = re } = e, c = await t({
|
|
385
385
|
publicPath: n,
|
|
386
386
|
workerUrl: r,
|
|
387
387
|
workerOptions: { type: "module" },
|
|
@@ -389,7 +389,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
389
389
|
read: a
|
|
390
390
|
});
|
|
391
391
|
try {
|
|
392
|
-
let t = await c.init(), n = t.duration, r = Math.max(e.interval ??
|
|
392
|
+
let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? te, n / ne), a = [], l = (e) => {
|
|
393
393
|
let t = [];
|
|
394
394
|
for (let [a, o] of e.entries()) {
|
|
395
395
|
let s = t.at(-1);
|
|
@@ -397,7 +397,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
397
397
|
timestamp: o.timestamp,
|
|
398
398
|
endTime: n,
|
|
399
399
|
startByte: o.pos,
|
|
400
|
-
endByte: Math.min((e[a + 1]?.pos ?? i) +
|
|
400
|
+
endByte: Math.min((e[a + 1]?.pos ?? i) + ie, i),
|
|
401
401
|
done: !1,
|
|
402
402
|
attempts: 0
|
|
403
403
|
});
|
|
@@ -436,7 +436,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
436
436
|
}), o(e);
|
|
437
437
|
}, x = async (e) => {
|
|
438
438
|
if (d) return;
|
|
439
|
-
let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")),
|
|
439
|
+
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))));
|
|
440
440
|
r.getContext("2d").drawImage(n, 0, 0, r.width, r.height), n.close();
|
|
441
441
|
let i = await r.convertToBlob({
|
|
442
442
|
type: "image/webp",
|
|
@@ -460,7 +460,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
460
460
|
if (!f.length) return;
|
|
461
461
|
let e = f.splice(y(), 1)[0];
|
|
462
462
|
p = !0, x(e).catch(() => {
|
|
463
|
-
e.attempts += 1, e.done = e.attempts >=
|
|
463
|
+
e.attempts += 1, e.done = e.attempts >= ae;
|
|
464
464
|
}).finally(() => {
|
|
465
465
|
p = !1, S();
|
|
466
466
|
});
|
|
@@ -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")),
|
|
472
|
+
let t = await Promise.race([c.init(), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), F))]);
|
|
473
473
|
if (d) return;
|
|
474
474
|
h = e;
|
|
475
475
|
let n = l(t.indexes);
|
|
@@ -495,7 +495,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
495
495
|
if (d) return;
|
|
496
496
|
v = e;
|
|
497
497
|
let t = T(e);
|
|
498
|
-
h < 0 ? h = t : (t >= Math.max(h *
|
|
498
|
+
h < 0 ? h = t : (t >= Math.max(h * N, h + P) || t >= i && h < i) && (g = !0), E(e), S();
|
|
499
499
|
},
|
|
500
500
|
prioritize: (e) => {
|
|
501
501
|
d || (m = e);
|
|
@@ -503,14 +503,14 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
503
503
|
destroy: () => {
|
|
504
504
|
d = !0, f.length = 0;
|
|
505
505
|
for (let e of u) URL.revokeObjectURL(e.url);
|
|
506
|
-
u = [],
|
|
506
|
+
u = [], j(c);
|
|
507
507
|
}
|
|
508
508
|
};
|
|
509
509
|
} catch (e) {
|
|
510
|
-
throw
|
|
510
|
+
throw j(c), e;
|
|
511
511
|
}
|
|
512
|
-
},
|
|
513
|
-
let { video: t, subtitles: n } = e, r = e.mode ??
|
|
512
|
+
}, oe = 1280, se = 1920, ce = 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, le = () => R() ? z() ? "window" : B() ? "burn-in" : null : null, V = (e) => {
|
|
513
|
+
let { video: t, subtitles: n } = e, r = e.mode ?? le() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? se : oe), 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,39 +539,39 @@ 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,
|
|
542
|
+
setTimeout(e, L);
|
|
543
543
|
})).catch(() => {}).finally(() => {
|
|
544
544
|
e.pause(), setTimeout(() => {
|
|
545
545
|
l = !1;
|
|
546
|
-
},
|
|
546
|
+
}, L);
|
|
547
547
|
});
|
|
548
548
|
}, E = [["play", () => {
|
|
549
549
|
t.play().catch(() => {});
|
|
550
|
-
}], ["pause", () => t.pause()]],
|
|
550
|
+
}], ["pause", () => t.pause()]], D = (e) => {
|
|
551
551
|
if ("mediaSession" in navigator) for (let [t, n] of E) try {
|
|
552
552
|
navigator.mediaSession.setActionHandler(t, e ? n : null);
|
|
553
553
|
} catch {}
|
|
554
|
-
},
|
|
555
|
-
if (y(),
|
|
554
|
+
}, O = () => {
|
|
555
|
+
if (y(), D(!1), t.removeEventListener("play", C), t.removeEventListener("pause", w), t.removeEventListener("seeked", T), !a) {
|
|
556
556
|
f?.();
|
|
557
557
|
return;
|
|
558
558
|
}
|
|
559
559
|
let { stream: e, mirror: n } = a;
|
|
560
|
-
n.removeEventListener("play", x), n.removeEventListener("pause", S), n.removeEventListener("leavepictureinpicture",
|
|
560
|
+
n.removeEventListener("play", x), n.removeEventListener("pause", S), n.removeEventListener("leavepictureinpicture", k);
|
|
561
561
|
for (let t of e.getTracks()) t.stop();
|
|
562
562
|
n.remove(), a = void 0, f?.();
|
|
563
|
-
},
|
|
563
|
+
}, k = () => O(), A = (e) => {
|
|
564
564
|
let r = t.style.opacity, i = n.style.display;
|
|
565
565
|
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
566
|
f = void 0, t.style.opacity = r, n.style.display = i;
|
|
567
567
|
};
|
|
568
|
-
},
|
|
568
|
+
}, j = async () => {
|
|
569
569
|
let n = Math.min(t.videoWidth || i, i), o = Math.round(n * ((t.videoHeight || 9) / (t.videoWidth || 16))), c = document.createElement("canvas");
|
|
570
570
|
c.width = n, c.height = o;
|
|
571
571
|
let l = c.getContext("2d");
|
|
572
572
|
if (!l) throw Error("the picture in picture composite has no 2d context");
|
|
573
573
|
let u = document.createElement("video");
|
|
574
|
-
u.muted = !0, u.playsInline = !0, r === "burn-in" ?
|
|
574
|
+
u.muted = !0, u.playsInline = !0, r === "burn-in" ? A(u) : u.style.cssText = "position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-1px;top:-1px", m(c, l);
|
|
575
575
|
let f = {
|
|
576
576
|
composite: c,
|
|
577
577
|
context: l,
|
|
@@ -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
|
-
},
|
|
583
|
+
}, ce), 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);
|
|
@@ -589,7 +589,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
589
589
|
u.remove();
|
|
590
590
|
return;
|
|
591
591
|
}
|
|
592
|
-
if (u.addEventListener("play", x), u.addEventListener("pause", S), u.addEventListener("leavepictureinpicture",
|
|
592
|
+
if (u.addEventListener("play", x), u.addEventListener("pause", S), u.addEventListener("leavepictureinpicture", k), t.addEventListener("play", C), t.addEventListener("pause", w), t.addEventListener("seeked", T), D(!0), b(), r === "burn-in") {
|
|
593
593
|
t.paused && u.pause(), e.onBurnedInChange?.(!0);
|
|
594
594
|
return;
|
|
595
595
|
}
|
|
@@ -602,7 +602,7 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
602
602
|
try {
|
|
603
603
|
if (p()) {
|
|
604
604
|
if (r === "burn-in") {
|
|
605
|
-
|
|
605
|
+
O(), e.onBurnedInChange?.(!1);
|
|
606
606
|
return;
|
|
607
607
|
}
|
|
608
608
|
await document.exitPictureInPicture().catch(() => {});
|
|
@@ -610,9 +610,9 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
610
610
|
}
|
|
611
611
|
r !== "burn-in" && document.pictureInPictureElement && await document.exitPictureInPicture().catch(() => {});
|
|
612
612
|
try {
|
|
613
|
-
await
|
|
613
|
+
await j();
|
|
614
614
|
} catch (t) {
|
|
615
|
-
console.warn("subtitle compositing for picture in picture failed", t),
|
|
615
|
+
console.warn("subtitle compositing for picture in picture failed", t), O(), r === "burn-in" && e.onBurnedInChange?.(!1);
|
|
616
616
|
}
|
|
617
617
|
} finally {
|
|
618
618
|
u = !1;
|
|
@@ -621,9 +621,9 @@ var i = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
|
|
|
621
621
|
},
|
|
622
622
|
mode: r,
|
|
623
623
|
destroy: () => {
|
|
624
|
-
d = !0, r !== "burn-in" && p() && document.exitPictureInPicture().catch(() => {}),
|
|
624
|
+
d = !0, r !== "burn-in" && p() && document.exitPictureInPicture().catch(() => {}), O(), r === "burn-in" && e.onBurnedInChange?.(!1);
|
|
625
625
|
}
|
|
626
626
|
};
|
|
627
627
|
};
|
|
628
628
|
//#endregion
|
|
629
|
-
export { g as a,
|
|
629
|
+
export { g as a, j as c, i as d, o as f, A as i, s as l, le as n, _ as o, I as r, ee as s, V 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-
|
|
1
|
+
import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-C4Gj_4zs.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";
|
|
@@ -2252,82 +2252,90 @@ ${gt}
|
|
|
2252
2252
|
}
|
|
2253
2253
|
}
|
|
2254
2254
|
}
|
|
2255
|
-
`, Vt = () => {
|
|
2256
|
-
let
|
|
2257
|
-
let r = Ne(
|
|
2258
|
-
|
|
2259
|
-
}, [
|
|
2255
|
+
`, Vt = ({ shortcuts: e = "window", root: t }) => {
|
|
2256
|
+
let n = G(), r = G((e) => e.paused), i = G((e) => e.currentTime), a = G((e) => e.duration), o = G((e) => e.seekingTo), s = G((e) => e.fullscreen), c = G((e) => e.hideUI), l = G((e) => e.togglePictureInPicture), f = G((e) => e.pictureInPictureMode), h = G((e) => e.burnedInSubtitles), g = G((e) => e.passThroughPictureInPicture), _ = G((e) => e.subtitleTracks), v = G((e) => e.requestSeek), [y, b] = m(null), x = p(null), S = p(void 0), C = f === "burn-in", w = _.length > 0, D = a > 0 && i === a, O = u(({ direction: e, stepSize: t }) => {
|
|
2257
|
+
let r = Ne(n.volume), i = e === "up" ? t : -t, a = Math.max(0, Math.min(1, r + i)), o = n.muted;
|
|
2258
|
+
n.setVolume(Me(a)), o && !n.muted && n.toggleMuted();
|
|
2259
|
+
}, [n]), k = p(!1);
|
|
2260
2260
|
d(() => {
|
|
2261
|
-
let
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
let
|
|
2265
|
-
if (
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2261
|
+
let r = (e) => v ? v(e) : n.seek(e), i = (e) => e.composedPath()[0], a = (e) => {
|
|
2262
|
+
k.current = !!t.current?.contains(i(e) ?? null);
|
|
2263
|
+
}, o = (a) => {
|
|
2264
|
+
let o = i(a);
|
|
2265
|
+
if (o?.isContentEditable || /^(input|textarea|select)$/i.test(o?.tagName ?? "")) return;
|
|
2266
|
+
let s = t.current?.contains(o ?? null) || o === document.body && k.current;
|
|
2267
|
+
if (e === "player" && !n.fullscreen && !s) return;
|
|
2268
|
+
let c = !0;
|
|
2269
|
+
if (a.key === "f") n.toggleFullscreen();
|
|
2270
|
+
else if (a.key === "k") n.togglePaused();
|
|
2271
|
+
else if (a.key === " ") n.togglePaused();
|
|
2272
|
+
else if (a.key === "m") n.toggleMuted();
|
|
2273
|
+
else if (a.key === "ArrowUp") O({
|
|
2270
2274
|
direction: "up",
|
|
2271
2275
|
stepSize: It
|
|
2272
2276
|
});
|
|
2273
|
-
else if (
|
|
2277
|
+
else if (a.key === "ArrowDown") O({
|
|
2274
2278
|
direction: "down",
|
|
2275
2279
|
stepSize: It
|
|
2276
2280
|
});
|
|
2277
|
-
else if (
|
|
2278
|
-
if (!
|
|
2279
|
-
|
|
2280
|
-
} else
|
|
2281
|
-
|
|
2281
|
+
else if (a.key === "ArrowRight") {
|
|
2282
|
+
if (!n.duration) return;
|
|
2283
|
+
r(Math.min(n.currentTime + Lt, n.duration));
|
|
2284
|
+
} else a.key === "ArrowLeft" ? r(Math.max(n.currentTime - Lt, 0)) : c = !1;
|
|
2285
|
+
c && a.preventDefault();
|
|
2286
|
+
};
|
|
2287
|
+
return window.addEventListener("keydown", o), window.addEventListener("pointerdown", a, !0), window.addEventListener("focusin", a, !0), () => {
|
|
2288
|
+
window.removeEventListener("keydown", o), window.removeEventListener("pointerdown", a, !0), window.removeEventListener("focusin", a, !0);
|
|
2282
2289
|
};
|
|
2283
|
-
return window.addEventListener("keydown", n), () => window.removeEventListener("keydown", n);
|
|
2284
2290
|
}, [
|
|
2291
|
+
n,
|
|
2292
|
+
O,
|
|
2293
|
+
v,
|
|
2285
2294
|
e,
|
|
2286
|
-
|
|
2287
|
-
g
|
|
2295
|
+
t
|
|
2288
2296
|
]);
|
|
2289
|
-
let
|
|
2290
|
-
e.preventDefault(), e.deltaY < 0 ?
|
|
2297
|
+
let A = u((e) => {
|
|
2298
|
+
e.preventDefault(), e.deltaY < 0 ? O({
|
|
2291
2299
|
direction: "up",
|
|
2292
2300
|
stepSize: It
|
|
2293
|
-
}) :
|
|
2301
|
+
}) : O({
|
|
2294
2302
|
direction: "down",
|
|
2295
2303
|
stepSize: It
|
|
2296
2304
|
});
|
|
2297
|
-
}, [
|
|
2305
|
+
}, [O]);
|
|
2298
2306
|
d(() => {
|
|
2299
|
-
if (
|
|
2300
|
-
}, [
|
|
2301
|
-
let
|
|
2302
|
-
if (!
|
|
2307
|
+
if (y) return y.addEventListener("wheel", A, { passive: !1 }), () => y.removeEventListener("wheel", A);
|
|
2308
|
+
}, [y, A]);
|
|
2309
|
+
let j = (e) => {
|
|
2310
|
+
if (!g || g.active || g.armed || e.pointerType === "touch") return;
|
|
2303
2311
|
let { left: t, top: n, width: r, height: i } = e.currentTarget.getBoundingClientRect();
|
|
2304
|
-
|
|
2312
|
+
g.arm({
|
|
2305
2313
|
left: t,
|
|
2306
2314
|
top: n,
|
|
2307
2315
|
width: r,
|
|
2308
2316
|
height: i
|
|
2309
2317
|
});
|
|
2310
|
-
},
|
|
2318
|
+
}, M = !!g?.armed;
|
|
2311
2319
|
return d(() => {
|
|
2312
|
-
if (!
|
|
2313
|
-
clearInterval(
|
|
2320
|
+
if (!M) return;
|
|
2321
|
+
clearInterval(S.current);
|
|
2314
2322
|
let e = Infinity, t = setInterval(() => {
|
|
2315
2323
|
if (Date.now() > e) {
|
|
2316
2324
|
clearInterval(t);
|
|
2317
2325
|
return;
|
|
2318
2326
|
}
|
|
2319
|
-
let n =
|
|
2327
|
+
let n = x.current, r = document.activeElement;
|
|
2320
2328
|
n && r?.tagName === "IFRAME" && r.closest(".video")?.parentElement?.contains(n) && n.focus({ preventScroll: !0 });
|
|
2321
2329
|
}, Rt);
|
|
2322
|
-
return
|
|
2330
|
+
return S.current = t, () => {
|
|
2323
2331
|
e = Date.now() + zt;
|
|
2324
2332
|
};
|
|
2325
|
-
}, [
|
|
2333
|
+
}, [M]), d(() => () => clearInterval(S.current), []), /* @__PURE__ */ E("div", {
|
|
2326
2334
|
css: Bt,
|
|
2327
|
-
style: { ...
|
|
2335
|
+
style: { ...c ? {
|
|
2328
2336
|
opacity: "0",
|
|
2329
2337
|
pointerEvents: "none"
|
|
2330
|
-
} :
|
|
2338
|
+
} : M ? { pointerEvents: "none" } : {} },
|
|
2331
2339
|
children: [/* @__PURE__ */ T(dt, {}), /* @__PURE__ */ E("div", {
|
|
2332
2340
|
className: "actions",
|
|
2333
2341
|
children: [/* @__PURE__ */ E("div", {
|
|
@@ -2339,16 +2347,16 @@ ${gt}
|
|
|
2339
2347
|
text: /* @__PURE__ */ T("button", {
|
|
2340
2348
|
className: "play",
|
|
2341
2349
|
type: "button",
|
|
2342
|
-
onClick: () =>
|
|
2343
|
-
"aria-label":
|
|
2344
|
-
children: T(
|
|
2350
|
+
onClick: () => n.togglePaused(),
|
|
2351
|
+
"aria-label": D ? "Replay" : r ? "Play" : "Pause",
|
|
2352
|
+
children: T(D ? L : r ? I : F, {})
|
|
2345
2353
|
}),
|
|
2346
|
-
toolTipText: /* @__PURE__ */ T("span", { children:
|
|
2354
|
+
toolTipText: /* @__PURE__ */ T("span", { children: D ? "Replay (k)" : r ? "Play (k)" : "Pause (k)" })
|
|
2347
2355
|
}),
|
|
2348
|
-
/* @__PURE__ */ T(Ft, { ref:
|
|
2356
|
+
/* @__PURE__ */ T(Ft, { ref: b }),
|
|
2349
2357
|
/* @__PURE__ */ T("div", {
|
|
2350
2358
|
className: "time",
|
|
2351
|
-
children: Fe(
|
|
2359
|
+
children: Fe(o ?? i, a)
|
|
2352
2360
|
})
|
|
2353
2361
|
]
|
|
2354
2362
|
}), /* @__PURE__ */ E("div", {
|
|
@@ -2357,40 +2365,40 @@ ${gt}
|
|
|
2357
2365
|
/* @__PURE__ */ T(wt, {}),
|
|
2358
2366
|
/* @__PURE__ */ T(At, {}),
|
|
2359
2367
|
/* @__PURE__ */ T(Ot, {}),
|
|
2360
|
-
|
|
2368
|
+
g ? /* @__PURE__ */ T($, {
|
|
2361
2369
|
id: "picture-in-picture",
|
|
2362
2370
|
tooltipPlace: "top-end",
|
|
2363
2371
|
text: /* @__PURE__ */ T("button", {
|
|
2364
|
-
ref:
|
|
2365
|
-
className:
|
|
2372
|
+
ref: x,
|
|
2373
|
+
className: g.armed ? "picture-in-picture armed" : "picture-in-picture",
|
|
2366
2374
|
type: "button",
|
|
2367
|
-
onPointerEnter:
|
|
2368
|
-
onPointerMove:
|
|
2369
|
-
onClick:
|
|
2375
|
+
onPointerEnter: j,
|
|
2376
|
+
onPointerMove: j,
|
|
2377
|
+
onClick: g.active ? g.exit : void 0,
|
|
2370
2378
|
"aria-label": "Picture in picture",
|
|
2371
|
-
"aria-pressed":
|
|
2379
|
+
"aria-pressed": g.active,
|
|
2372
2380
|
children: /* @__PURE__ */ T("img", {
|
|
2373
2381
|
src: ft,
|
|
2374
2382
|
alt: ""
|
|
2375
2383
|
})
|
|
2376
2384
|
}),
|
|
2377
|
-
toolTipText: /* @__PURE__ */ T("span", { children:
|
|
2378
|
-
}) :
|
|
2385
|
+
toolTipText: /* @__PURE__ */ T("span", { children: g.active ? "Exit picture in picture" : "Picture in picture" })
|
|
2386
|
+
}) : l ? /* @__PURE__ */ T($, {
|
|
2379
2387
|
id: "picture-in-picture",
|
|
2380
2388
|
tooltipPlace: "top-end",
|
|
2381
2389
|
text: /* @__PURE__ */ T("button", {
|
|
2382
2390
|
className: "picture-in-picture",
|
|
2383
2391
|
type: "button",
|
|
2384
|
-
onClick:
|
|
2385
|
-
disabled:
|
|
2386
|
-
"aria-label":
|
|
2387
|
-
"aria-pressed":
|
|
2388
|
-
children:
|
|
2392
|
+
onClick: l,
|
|
2393
|
+
disabled: C && !w,
|
|
2394
|
+
"aria-label": C ? "Put the subtitles in the video" : "Picture in picture",
|
|
2395
|
+
"aria-pressed": C ? h : void 0,
|
|
2396
|
+
children: C ? T(h ? mt : ht, {}) : /* @__PURE__ */ T("img", {
|
|
2389
2397
|
src: ft,
|
|
2390
2398
|
alt: ""
|
|
2391
2399
|
})
|
|
2392
2400
|
}),
|
|
2393
|
-
toolTipText: /* @__PURE__ */ T("span", { children:
|
|
2401
|
+
toolTipText: /* @__PURE__ */ T("span", { children: C ? w ? h ? "Subtitles are in the picture" : "Put the subtitles in the picture" : "This file has no subtitles" : "Picture in picture" })
|
|
2394
2402
|
}) : null,
|
|
2395
2403
|
/* @__PURE__ */ T($, {
|
|
2396
2404
|
id: "full-screen",
|
|
@@ -2398,12 +2406,12 @@ ${gt}
|
|
|
2398
2406
|
text: /* @__PURE__ */ T("button", {
|
|
2399
2407
|
className: "full-screen",
|
|
2400
2408
|
type: "button",
|
|
2401
|
-
onClick: () =>
|
|
2409
|
+
onClick: () => n.toggleFullscreen(),
|
|
2402
2410
|
"aria-label": "Full screen",
|
|
2403
|
-
"aria-pressed":
|
|
2404
|
-
children: T(
|
|
2411
|
+
"aria-pressed": s,
|
|
2412
|
+
children: T(s ? P : N, {})
|
|
2405
2413
|
}),
|
|
2406
|
-
toolTipText: /* @__PURE__ */ T("span", { children:
|
|
2414
|
+
toolTipText: /* @__PURE__ */ T("span", { children: s ? "Exit full screen (f)" : "Full screen (f)" })
|
|
2407
2415
|
})
|
|
2408
2416
|
]
|
|
2409
2417
|
})]
|
|
@@ -2594,6 +2602,9 @@ ${gt}
|
|
|
2594
2602
|
align-items: center;
|
|
2595
2603
|
height: 100%;
|
|
2596
2604
|
width: 100%;
|
|
2605
|
+
/* focusable with shortcuts='player', and the first key after a click on the picture would otherwise
|
|
2606
|
+
ring the whole player as :focus-visible */
|
|
2607
|
+
outline: none;
|
|
2597
2608
|
|
|
2598
2609
|
& > div:not(:last-of-type) {
|
|
2599
2610
|
position: absolute;
|
|
@@ -2646,85 +2657,89 @@ ${gt}
|
|
|
2646
2657
|
key: r,
|
|
2647
2658
|
item: e
|
|
2648
2659
|
}];
|
|
2649
|
-
}), nn = ({ ref: e, onVideoRef: t, onSubtitleRef: n, overlay: r, controls: i,
|
|
2650
|
-
let
|
|
2660
|
+
}), nn = ({ ref: e, onVideoRef: t, onSubtitleRef: n, overlay: r, controls: i, shortcuts: a, children: o }) => {
|
|
2661
|
+
let s = G(), c = G((e) => e.hideUI), l = G((e) => e.setHideUI), u = G((e) => e.hasMedia), f = G((e) => e.passThroughPictureInPicture), m = p(void 0), h = p("mouse"), g = p({
|
|
2651
2662
|
x: -1,
|
|
2652
2663
|
y: -1
|
|
2653
|
-
}),
|
|
2654
|
-
d(() => () => clearTimeout(
|
|
2655
|
-
|
|
2656
|
-
}, [
|
|
2657
|
-
let
|
|
2658
|
-
|
|
2659
|
-
},
|
|
2660
|
-
|
|
2661
|
-
}, y = () => {
|
|
2662
|
-
if (o.passThroughPictureInPicture?.armed) return !0;
|
|
2663
|
-
if (m.current !== "mouse") return !1;
|
|
2664
|
-
let { x: e, y: t } = h.current;
|
|
2665
|
-
if (!g.current || e < 0) return !1;
|
|
2666
|
-
let n = document.elementFromPoint(e, t);
|
|
2667
|
-
return !!n && g.current.contains(n) && !n.closest(".video");
|
|
2664
|
+
}), _ = p(null);
|
|
2665
|
+
d(() => () => clearTimeout(m.current), []), d(() => {
|
|
2666
|
+
u || (clearTimeout(m.current), l(!1));
|
|
2667
|
+
}, [u, l]);
|
|
2668
|
+
let v = () => {
|
|
2669
|
+
u && l(!0);
|
|
2670
|
+
}, y = (t) => {
|
|
2671
|
+
_.current = t, typeof e == "function" ? e(t) : e && (e.current = t);
|
|
2668
2672
|
}, b = () => {
|
|
2669
|
-
if (
|
|
2670
|
-
|
|
2673
|
+
if (s.passThroughPictureInPicture?.armed) return !0;
|
|
2674
|
+
if (h.current !== "mouse") return !1;
|
|
2675
|
+
let { x: e, y: t } = g.current;
|
|
2676
|
+
if (!_.current || e < 0) return !1;
|
|
2677
|
+
let n = document.elementFromPoint(e, t);
|
|
2678
|
+
return !!n && _.current.contains(n) && !n.closest(".video");
|
|
2679
|
+
}, x = () => {
|
|
2680
|
+
if (b()) {
|
|
2681
|
+
m.current = setTimeout(x, Zt);
|
|
2671
2682
|
return;
|
|
2672
2683
|
}
|
|
2673
|
-
|
|
2674
|
-
},
|
|
2675
|
-
|
|
2684
|
+
v();
|
|
2685
|
+
}, S = () => {
|
|
2686
|
+
l(!1), clearTimeout(m.current), m.current = setTimeout(x, Zt);
|
|
2676
2687
|
};
|
|
2677
2688
|
return /* @__PURE__ */ E("div", {
|
|
2678
2689
|
css: en,
|
|
2679
|
-
ref:
|
|
2690
|
+
ref: y,
|
|
2680
2691
|
onPointerMove: (e) => {
|
|
2681
|
-
|
|
2692
|
+
h.current = e.pointerType, e.pointerType === "mouse" && (g.current = {
|
|
2682
2693
|
x: e.clientX,
|
|
2683
2694
|
y: e.clientY
|
|
2684
|
-
},
|
|
2695
|
+
}, S());
|
|
2685
2696
|
},
|
|
2686
2697
|
onPointerDown: (e) => {
|
|
2687
|
-
|
|
2698
|
+
h.current = e.pointerType;
|
|
2688
2699
|
},
|
|
2689
2700
|
onMouseOut: (e) => {
|
|
2690
2701
|
let t = e.relatedTarget;
|
|
2691
2702
|
if (t && e.currentTarget.contains(t)) return;
|
|
2692
2703
|
let { left: n, right: r, top: i, bottom: a } = e.currentTarget.getBoundingClientRect();
|
|
2693
|
-
e.clientX >= n && e.clientX < r && e.clientY >= i && e.clientY < a || (clearTimeout(
|
|
2704
|
+
e.clientX >= n && e.clientX < r && e.clientY >= i && e.clientY < a || (clearTimeout(m.current), v());
|
|
2694
2705
|
},
|
|
2695
|
-
className:
|
|
2706
|
+
className: c ? "hide" : "",
|
|
2707
|
+
tabIndex: a === "player" ? -1 : void 0,
|
|
2696
2708
|
children: [
|
|
2697
2709
|
/* @__PURE__ */ T(ke, { onSubtitleRef: n }),
|
|
2698
2710
|
tn(r).map(({ key: e, item: t }) => /* @__PURE__ */ T("div", {
|
|
2699
2711
|
css: Qt,
|
|
2700
|
-
style: { ...
|
|
2712
|
+
style: { ...c ? {
|
|
2701
2713
|
opacity: "0",
|
|
2702
2714
|
visibility: "hidden",
|
|
2703
2715
|
pointerEvents: "none"
|
|
2704
2716
|
} : {} },
|
|
2705
2717
|
children: t
|
|
2706
2718
|
}, e)),
|
|
2707
|
-
i === !1 ? null : /* @__PURE__ */ T(Vt, {
|
|
2719
|
+
i === !1 ? null : /* @__PURE__ */ T(Vt, {
|
|
2720
|
+
shortcuts: a,
|
|
2721
|
+
root: _
|
|
2722
|
+
}),
|
|
2708
2723
|
/* @__PURE__ */ T(Wt, {}),
|
|
2709
2724
|
/* @__PURE__ */ T(Xt, {}),
|
|
2710
|
-
|
|
2725
|
+
f?.armed && _.current ? /* @__PURE__ */ T("div", {
|
|
2711
2726
|
className: "pass-through",
|
|
2712
|
-
style: { clipPath: $t(
|
|
2713
|
-
onPointerOver:
|
|
2727
|
+
style: { clipPath: $t(f.armed, _.current.getBoundingClientRect()) },
|
|
2728
|
+
onPointerOver: f.disarm
|
|
2714
2729
|
}) : null,
|
|
2715
2730
|
/* @__PURE__ */ E("div", {
|
|
2716
2731
|
className: "video",
|
|
2717
2732
|
onClick: () => {
|
|
2718
|
-
if (
|
|
2719
|
-
|
|
2733
|
+
if (h.current === "mouse") {
|
|
2734
|
+
s.togglePaused();
|
|
2720
2735
|
return;
|
|
2721
2736
|
}
|
|
2722
|
-
|
|
2737
|
+
c ? S() : (clearTimeout(m.current), v());
|
|
2723
2738
|
},
|
|
2724
2739
|
children: [t ? /* @__PURE__ */ T("video", {
|
|
2725
2740
|
ref: t,
|
|
2726
2741
|
playsInline: !0
|
|
2727
|
-
}) : null,
|
|
2742
|
+
}) : null, o]
|
|
2728
2743
|
})
|
|
2729
2744
|
]
|
|
2730
2745
|
});
|
|
@@ -2839,6 +2854,7 @@ ${gt}
|
|
|
2839
2854
|
onSubtitleRef: w,
|
|
2840
2855
|
overlay: e.overlay,
|
|
2841
2856
|
controls: e.controls,
|
|
2857
|
+
shortcuts: e.shortcuts,
|
|
2842
2858
|
children: t
|
|
2843
2859
|
});
|
|
2844
2860
|
}, an = g`
|
|
@@ -8,7 +8,8 @@ export type ChromeProps = {
|
|
|
8
8
|
overlay?: ReactNode;
|
|
9
9
|
/** False draws no control bar at all, leaving the picture, the title and the overlay. */
|
|
10
10
|
controls?: boolean;
|
|
11
|
+
shortcuts?: 'window' | 'player';
|
|
11
12
|
children?: ReactNode;
|
|
12
13
|
};
|
|
13
|
-
export declare const Chrome: ({ ref, onVideoRef, onSubtitleRef, overlay, controls, children }: ChromeProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare const Chrome: ({ ref, onVideoRef, onSubtitleRef, overlay, controls, shortcuts, children }: ChromeProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
14
15
|
export default Chrome;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
export declare const ControlBar: ({ shortcuts, root }: {
|
|
3
|
+
shortcuts?: 'window' | 'player';
|
|
4
|
+
root: RefObject<HTMLDivElement | null>;
|
|
5
|
+
}) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
2
6
|
export default ControlBar;
|
|
@@ -59,6 +59,13 @@ type CommonOptions = {
|
|
|
59
59
|
* store's `attach` and does nothing at all until a media arrives.
|
|
60
60
|
*/
|
|
61
61
|
controls?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Where the keyboard shortcuts listen. `'window'`, the default, takes keys from the whole page, which suits a
|
|
64
|
+
* page that is only the player. `'player'` takes a key only when the focus is inside the player (a click on the
|
|
65
|
+
* picture puts it there), or nothing has the focus and the last click or focus was in the player, or the player
|
|
66
|
+
* is fullscreen, so Space and the arrow keys keep scrolling the page and pressing its buttons everywhere else.
|
|
67
|
+
*/
|
|
68
|
+
shortcuts?: 'window' | 'player';
|
|
62
69
|
/**
|
|
63
70
|
* The app's own content over the video: a download readout, a badge, a logo, anything the player
|
|
64
71
|
* itself has no opinion about.
|
package/package.json
CHANGED
|
@@ -709,8 +709,10 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
|
|
|
709
709
|
if (elementFailed || videoElement.error) return
|
|
710
710
|
evict().catch(() => {})
|
|
711
711
|
pump().catch((error) => reportError(error, false))
|
|
712
|
-
//
|
|
713
|
-
|
|
712
|
+
// Every remove() puts the MediaSource back to 'open', so the end has to be re-armed, though not while a remuxer
|
|
713
|
+
// seek runs: queued behind a remove() and run after a move had started one, it came before that seek's append over
|
|
714
|
+
// the end, and Firefox 151 then sometimes waited at the end for good (4 of 1060 end moves, measured 2026-10-12)
|
|
715
|
+
if (endInBuffer() && !pending && atEnd() && mediaSource.readyState === 'open') endOfStream(() => !seeking).catch(() => {})
|
|
714
716
|
}, 100)
|
|
715
717
|
teardown.push(() => clearInterval(interval))
|
|
716
718
|
|
|
@@ -50,10 +50,13 @@ export const updateSourceBuffer = (sourceBuffer: SourceBuffer, mediaSource: Medi
|
|
|
50
50
|
const updateTimestampOffset = (timestampOffset: number) =>
|
|
51
51
|
enqueue(async () => { sourceBuffer.timestampOffset = timestampOffset })
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* endOfStream throws mid-update and is undone by the next remove(), so the caller re-arms it.
|
|
55
|
+
* `allowed` is asked when the call reaches the front of the queue, not when it is queued.
|
|
56
|
+
*/
|
|
57
|
+
const endOfStream = (allowed = () => true) =>
|
|
55
58
|
enqueue(async () => {
|
|
56
|
-
if (mediaSource.readyState !== 'open' || sourceBuffer.updating) return
|
|
59
|
+
if (mediaSource.readyState !== 'open' || sourceBuffer.updating || !allowed()) return
|
|
57
60
|
try { mediaSource.endOfStream() } catch {}
|
|
58
61
|
})
|
|
59
62
|
|
|
@@ -52,6 +52,9 @@ const style = css`
|
|
|
52
52
|
align-items: center;
|
|
53
53
|
height: 100%;
|
|
54
54
|
width: 100%;
|
|
55
|
+
/* focusable with shortcuts='player', and the first key after a click on the picture would otherwise
|
|
56
|
+
ring the whole player as :focus-visible */
|
|
57
|
+
outline: none;
|
|
55
58
|
|
|
56
59
|
& > div:not(:last-of-type) {
|
|
57
60
|
position: absolute;
|
|
@@ -129,10 +132,11 @@ export type ChromeProps = {
|
|
|
129
132
|
overlay?: ReactNode
|
|
130
133
|
/** False draws no control bar at all, leaving the picture, the title and the overlay. */
|
|
131
134
|
controls?: boolean
|
|
135
|
+
shortcuts?: 'window' | 'player'
|
|
132
136
|
children?: ReactNode
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
export const Chrome = ({ ref, onVideoRef, onSubtitleRef, overlay, controls, children }: ChromeProps) => {
|
|
139
|
+
export const Chrome = ({ ref, onVideoRef, onSubtitleRef, overlay, controls, shortcuts, children }: ChromeProps) => {
|
|
136
140
|
const player = usePlayer()
|
|
137
141
|
const hideUI = usePlayer((state) => state.hideUI)
|
|
138
142
|
const setHideUI = usePlayer((state) => state.setHideUI)
|
|
@@ -264,6 +268,8 @@ export const Chrome = ({ ref, onVideoRef, onSubtitleRef, overlay, controls, chil
|
|
|
264
268
|
onPointerDown={onPointerDown}
|
|
265
269
|
onMouseOut={onMouseOut}
|
|
266
270
|
className={hideUI ? 'hide' : ''}
|
|
271
|
+
// focusable, so a click on the picture puts the focus, and with it the shortcuts, inside the player
|
|
272
|
+
tabIndex={shortcuts === 'player' ? -1 : undefined}
|
|
267
273
|
>
|
|
268
274
|
<Overlay onSubtitleRef={onSubtitleRef} />
|
|
269
275
|
{overlayItems(overlay).map(({ key, item }) => (
|
|
@@ -275,7 +281,7 @@ export const Chrome = ({ ref, onVideoRef, onSubtitleRef, overlay, controls, chil
|
|
|
275
281
|
{item}
|
|
276
282
|
</div>
|
|
277
283
|
))}
|
|
278
|
-
{controls === false ? null : <ControlBar />}
|
|
284
|
+
{controls === false ? null : <ControlBar shortcuts={shortcuts} root={root} />}
|
|
279
285
|
{/* Not tied to `hideUI`: it says what to do next, and it is on screen for nine seconds. */}
|
|
280
286
|
<BurnInHint />
|
|
281
287
|
<SkipChapter />
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference types="@emotion/react/types/css-prop" />
|
|
2
|
+
import type { RefObject } from 'react'
|
|
3
|
+
|
|
2
4
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
5
|
import { css } from '@emotion/react'
|
|
4
6
|
import { Maximize, Minimize, Pause, Play, RotateCcw } from 'lucide-react'
|
|
@@ -157,7 +159,9 @@ const style = css`
|
|
|
157
159
|
}
|
|
158
160
|
`
|
|
159
161
|
|
|
160
|
-
export const ControlBar = (
|
|
162
|
+
export const ControlBar = (
|
|
163
|
+
{ shortcuts = 'window', root }: { shortcuts?: 'window' | 'player', root: RefObject<HTMLDivElement | null> },
|
|
164
|
+
) => {
|
|
161
165
|
const player = usePlayer()
|
|
162
166
|
const paused = usePlayer((state) => state.paused)
|
|
163
167
|
const currentTime = usePlayer((state) => state.currentTime)
|
|
@@ -198,12 +202,21 @@ export const ControlBar = () => {
|
|
|
198
202
|
if (wasMuted && !player.muted) player.toggleMuted()
|
|
199
203
|
}, [player])
|
|
200
204
|
|
|
205
|
+
// whether the last press or focus on the page landed in the player: a focused control that goes away (Skip
|
|
206
|
+
// Opening, once pressed) leaves the focus on the body, and the shortcuts stay with the player through that
|
|
207
|
+
const engaged = useRef(false)
|
|
208
|
+
|
|
201
209
|
useEffect(() => {
|
|
202
210
|
const seek = (time: number) => requestSeek ? requestSeek(time) : player.seek(time)
|
|
211
|
+
// the path, not ev.target, which a shadow root around the player retargets to its host
|
|
212
|
+
const targetOf = (ev: Event) => ev.composedPath()[0] as HTMLElement | undefined
|
|
213
|
+
const engage = (ev: Event) => { engaged.current = !!root.current?.contains(targetOf(ev) ?? null) }
|
|
203
214
|
const eventListener = (ev: KeyboardEvent) => {
|
|
204
|
-
// on the window, so typing into a consumer's own input
|
|
205
|
-
const target = ev
|
|
215
|
+
// on the window, so typing into a consumer's own input opts out
|
|
216
|
+
const target = targetOf(ev)
|
|
206
217
|
if (target?.isContentEditable || /^(input|textarea|select)$/i.test(target?.tagName ?? '')) return
|
|
218
|
+
const fromPlayer = root.current?.contains(target ?? null) || (target === document.body && engaged.current)
|
|
219
|
+
if (shortcuts === 'player' && !player.fullscreen && !fromPlayer) return
|
|
207
220
|
|
|
208
221
|
let shouldPreventDefault = true
|
|
209
222
|
// space would otherwise reach the browser's own shortcut
|
|
@@ -233,8 +246,14 @@ export const ControlBar = () => {
|
|
|
233
246
|
}
|
|
234
247
|
}
|
|
235
248
|
window.addEventListener('keydown', eventListener)
|
|
236
|
-
|
|
237
|
-
|
|
249
|
+
window.addEventListener('pointerdown', engage, true)
|
|
250
|
+
window.addEventListener('focusin', engage, true)
|
|
251
|
+
return () => {
|
|
252
|
+
window.removeEventListener('keydown', eventListener)
|
|
253
|
+
window.removeEventListener('pointerdown', engage, true)
|
|
254
|
+
window.removeEventListener('focusin', engage, true)
|
|
255
|
+
}
|
|
256
|
+
}, [player, modifyVolume, requestSeek, shortcuts, root])
|
|
238
257
|
|
|
239
258
|
const handleWheel = useCallback((e: WheelEvent) => {
|
|
240
259
|
e.preventDefault()
|
|
@@ -73,6 +73,14 @@ type CommonOptions = {
|
|
|
73
73
|
*/
|
|
74
74
|
controls?: boolean
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Where the keyboard shortcuts listen. `'window'`, the default, takes keys from the whole page, which suits a
|
|
78
|
+
* page that is only the player. `'player'` takes a key only when the focus is inside the player (a click on the
|
|
79
|
+
* picture puts it there), or nothing has the focus and the last click or focus was in the player, or the player
|
|
80
|
+
* is fullscreen, so Space and the arrow keys keep scrolling the page and pressing its buttons everywhere else.
|
|
81
|
+
*/
|
|
82
|
+
shortcuts?: 'window' | 'player'
|
|
83
|
+
|
|
76
84
|
/**
|
|
77
85
|
* The app's own content over the video: a download readout, a badge, a logo, anything the player
|
|
78
86
|
* itself has no opinion about.
|
|
@@ -411,6 +419,7 @@ const PlayerRoot = ({ options, children }: { options: MediaPlayerOptions, childr
|
|
|
411
419
|
onSubtitleRef={setSubtitleLayer}
|
|
412
420
|
overlay={options.overlay}
|
|
413
421
|
controls={options.controls}
|
|
422
|
+
shortcuts={options.shortcuts}
|
|
414
423
|
>
|
|
415
424
|
{children}
|
|
416
425
|
</Chrome>
|