avbridge 2.10.0 → 2.12.0
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/CHANGELOG.md +69 -0
- package/dist/{avi-B5CQYB7L.cjs → avi-EQE6AR75.cjs} +4 -4
- package/dist/{avi-2ILLBNPQ.cjs.map → avi-EQE6AR75.cjs.map} +1 -1
- package/dist/{avi-RWWPN2PR.js → avi-NNHH4AAA.js} +3 -3
- package/dist/{avi-JXU4GQL2.js.map → avi-NNHH4AAA.js.map} +1 -1
- package/dist/{avi-JXU4GQL2.js → avi-S7EY54YA.js} +3 -3
- package/dist/{avi-RWWPN2PR.js.map → avi-S7EY54YA.js.map} +1 -1
- package/dist/{avi-2ILLBNPQ.cjs → avi-Y3N325WZ.cjs} +4 -4
- package/dist/{avi-B5CQYB7L.cjs.map → avi-Y3N325WZ.cjs.map} +1 -1
- package/dist/{chunk-GYIJU44C.js → chunk-2LNXMGT6.js} +5 -5
- package/dist/{chunk-GYIJU44C.js.map → chunk-2LNXMGT6.js.map} +1 -1
- package/dist/{chunk-DCSOQH2N.js → chunk-3AI5WFFN.js} +40 -16
- package/dist/chunk-3AI5WFFN.js.map +1 -0
- package/dist/{chunk-2NSOOMXW.js → chunk-3YKWU4FM.js} +3 -3
- package/dist/{chunk-2NSOOMXW.js.map → chunk-3YKWU4FM.js.map} +1 -1
- package/dist/{chunk-CL6UEUQF.js → chunk-5Y5BTB5D.js} +5 -5
- package/dist/{chunk-CL6UEUQF.js.map → chunk-5Y5BTB5D.js.map} +1 -1
- package/dist/{chunk-NQULEIA3.cjs → chunk-7EF4VTUS.cjs} +36 -28
- package/dist/chunk-7EF4VTUS.cjs.map +1 -0
- package/dist/{chunk-5KVLE6YI.js → chunk-EDDWAN2L.js} +3 -2
- package/dist/chunk-EDDWAN2L.js.map +1 -0
- package/dist/{chunk-OTFS7DC4.cjs → chunk-GJBNLPGI.cjs} +14 -14
- package/dist/{chunk-OTFS7DC4.cjs.map → chunk-GJBNLPGI.cjs.map} +1 -1
- package/dist/{chunk-BYGZN4Z5.cjs → chunk-HBHSUGNI.cjs} +5 -5
- package/dist/{chunk-BYGZN4Z5.cjs.map → chunk-HBHSUGNI.cjs.map} +1 -1
- package/dist/{chunk-L7A3ECI2.cjs → chunk-HZUVMXBN.cjs} +4 -4
- package/dist/{chunk-L7A3ECI2.cjs.map → chunk-HZUVMXBN.cjs.map} +1 -1
- package/dist/{chunk-S4WAZC2T.cjs → chunk-WRKO6Q42.cjs} +3 -2
- package/dist/chunk-WRKO6Q42.cjs.map +1 -0
- package/dist/{chunk-Z33SBWL5.cjs → chunk-YPZFGJV3.cjs} +40 -16
- package/dist/chunk-YPZFGJV3.cjs.map +1 -0
- package/dist/{chunk-3GKM5DFM.js → chunk-Z26PXRUY.js} +18 -10
- package/dist/chunk-Z26PXRUY.js.map +1 -0
- package/dist/element-browser.js +65 -19
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +21 -8
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +20 -7
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +23 -23
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +10 -10
- package/dist/{libav-demux-3N5Y3VQA.cjs → libav-demux-575OYCT2.cjs} +9 -9
- package/dist/{libav-demux-3N5Y3VQA.cjs.map → libav-demux-575OYCT2.cjs.map} +1 -1
- package/dist/{libav-demux-JXD4OTLM.js → libav-demux-SXZDLC7W.js} +4 -4
- package/dist/{libav-demux-JXD4OTLM.js.map → libav-demux-SXZDLC7W.js.map} +1 -1
- package/dist/libav-http-reader-2S5HAHW4.js +3 -0
- package/dist/{libav-http-reader-WXG3Z7AI.js.map → libav-http-reader-2S5HAHW4.js.map} +1 -1
- package/dist/libav-http-reader-Q356EO2K.cjs +16 -0
- package/dist/{libav-http-reader-AZLE7YFS.cjs.map → libav-http-reader-Q356EO2K.cjs.map} +1 -1
- package/dist/{player-DDdNVFDv.d.cts → player-bQ6n4hVp.d.cts} +15 -0
- package/dist/{player-DDdNVFDv.d.ts → player-bQ6n4hVp.d.ts} +15 -0
- package/dist/player.cjs +166 -33
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +36 -0
- package/dist/player.d.ts +36 -0
- package/dist/player.js +162 -29
- package/dist/player.js.map +1 -1
- package/dist/remux-7TA4FKTY.js +10 -0
- package/dist/{remux-56V7LDAD.js.map → remux-7TA4FKTY.js.map} +1 -1
- package/dist/remux-VPKCLHHM.cjs +35 -0
- package/dist/{remux-KUS5GIL6.cjs.map → remux-VPKCLHHM.cjs.map} +1 -1
- package/dist/subtitles-5H24MEBJ.js +4 -0
- package/dist/{subtitles-4T74JRGT.js.map → subtitles-5H24MEBJ.js.map} +1 -1
- package/dist/subtitles-HMVGWTU2.cjs +29 -0
- package/dist/{subtitles-QUH4LPI4.cjs.map → subtitles-HMVGWTU2.cjs.map} +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +128 -18
- package/src/element/avbridge-subtitles.ts +273 -0
- package/src/element/avbridge-video.ts +21 -1
- package/src/element/player-styles.ts +13 -1
- package/src/player.ts +3 -3
- package/src/strategies/fallback/audio-output.ts +10 -0
- package/src/subtitles/index.ts +2 -0
- package/src/types.ts +15 -0
- package/src/util/libav-http-reader.ts +58 -19
- package/dist/chunk-3GKM5DFM.js.map +0 -1
- package/dist/chunk-5KVLE6YI.js.map +0 -1
- package/dist/chunk-DCSOQH2N.js.map +0 -1
- package/dist/chunk-NQULEIA3.cjs.map +0 -1
- package/dist/chunk-S4WAZC2T.cjs.map +0 -1
- package/dist/chunk-Z33SBWL5.cjs.map +0 -1
- package/dist/libav-http-reader-AZLE7YFS.cjs +0 -16
- package/dist/libav-http-reader-WXG3Z7AI.js +0 -3
- package/dist/remux-56V7LDAD.js +0 -10
- package/dist/remux-KUS5GIL6.cjs +0 -35
- package/dist/subtitles-4T74JRGT.js +0 -4
- package/dist/subtitles-QUH4LPI4.cjs +0 -29
package/dist/player.d.cts
CHANGED
|
@@ -209,6 +209,14 @@ interface CreatePlayerOptions {
|
|
|
209
209
|
* for interceptors, logging, or environments without a global fetch.
|
|
210
210
|
*/
|
|
211
211
|
fetchFn?: FetchFn;
|
|
212
|
+
/**
|
|
213
|
+
* Byte budget for the libav HTTP reader's LRU cache of fetched ranges.
|
|
214
|
+
* Defaults to 8 MB. Set to `0` to disable caching. Raise this when the
|
|
215
|
+
* app plays seek-heavy legacy-container media from URLs — hot regions
|
|
216
|
+
* (header/moov, tail index, current window) stay resident instead of
|
|
217
|
+
* being re-fetched on every bounce.
|
|
218
|
+
*/
|
|
219
|
+
cacheBytes?: number;
|
|
212
220
|
}
|
|
213
221
|
/** Signature-compatible with `globalThis.fetch`. */
|
|
214
222
|
type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
@@ -216,6 +224,13 @@ type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Respons
|
|
|
216
224
|
interface TransportConfig {
|
|
217
225
|
requestInit?: RequestInit;
|
|
218
226
|
fetchFn?: FetchFn;
|
|
227
|
+
/**
|
|
228
|
+
* Byte budget for the libav HTTP reader's LRU cache of fetched ranges.
|
|
229
|
+
* Defaults to 8 MB. Set to `0` to disable caching entirely. Higher
|
|
230
|
+
* values help seek-heavy network playback keep hot regions
|
|
231
|
+
* (header/moov, tail index, current read) resident.
|
|
232
|
+
*/
|
|
233
|
+
cacheBytes?: number;
|
|
219
234
|
}
|
|
220
235
|
/** Events emitted by {@link UnifiedPlayer}. Strongly typed. */
|
|
221
236
|
interface PlayerEventMap {
|
|
@@ -345,6 +360,8 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
345
360
|
private _state;
|
|
346
361
|
private _controlsTimer;
|
|
347
362
|
private _settingsOpen;
|
|
363
|
+
private _activeAudioTrackId;
|
|
364
|
+
private _activeSubtitleTrackId;
|
|
348
365
|
private _userSeeking;
|
|
349
366
|
private _holdTimer;
|
|
350
367
|
private _holdSpeedActive;
|
|
@@ -369,11 +386,21 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
369
386
|
private _onSeekCommit;
|
|
370
387
|
/** Linear click-to-time mapping across the full track width (no edge clamping). */
|
|
371
388
|
private _timeFromSeekPointer;
|
|
389
|
+
/** Seekbar width below which drag-to-scrub seeks in real-time (vs
|
|
390
|
+
* preview-only). On narrow bars precise positioning is hard, so
|
|
391
|
+
* immediate video feedback is more useful than a time tooltip. */
|
|
392
|
+
private static readonly SCRUB_WIDTH_THRESHOLD;
|
|
372
393
|
private _onSeekPointerDown;
|
|
373
394
|
private _onSeekHover;
|
|
374
395
|
private _updateSeekTooltip;
|
|
375
396
|
private _updateSeekVisuals;
|
|
376
397
|
private _updateTime;
|
|
398
|
+
/**
|
|
399
|
+
* Render every buffered range as its own segment so gaps (common on MSE
|
|
400
|
+
* after seeks) are visible. Not gated by `_userSeeking` — ranges should
|
|
401
|
+
* keep updating while the user scrubs, and runs cheaply on `progress`.
|
|
402
|
+
*/
|
|
403
|
+
private _updateBuffered;
|
|
377
404
|
private _toggleMute;
|
|
378
405
|
private _updateVolume;
|
|
379
406
|
private _toggleSettings;
|
|
@@ -399,8 +426,14 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
399
426
|
showControls(durationMs?: number): void;
|
|
400
427
|
private _showControls;
|
|
401
428
|
private _scheduleHide;
|
|
429
|
+
/** Read the controls-timeout attribute. 0 or negative = never hide.
|
|
430
|
+
* Unset = default 3000ms. */
|
|
431
|
+
private _getControlsTimeout;
|
|
402
432
|
/** Track whether the last interaction was touch so click handler can skip. */
|
|
403
433
|
private _lastPointerTypeWasTouch;
|
|
434
|
+
/** True for ~50ms after a touch double-tap was handled, so the
|
|
435
|
+
* synthetic dblclick from the browser doesn't also fire fullscreen. */
|
|
436
|
+
private _touchDoubleTapConsumed;
|
|
404
437
|
/** True if the event's composed path passes through consumer-slotted
|
|
405
438
|
* content (toolbar or content-overlay). Slotted content lives in the
|
|
406
439
|
* light DOM so `.closest(".avp-toolbar-top")` on the event target won't
|
|
@@ -412,6 +445,9 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
412
445
|
private _onPointerUp;
|
|
413
446
|
private _cancelHold;
|
|
414
447
|
private _doDoubleTap;
|
|
448
|
+
/** Duration of one frame in seconds, derived from diagnostics fps or
|
|
449
|
+
* a 30fps default. Used for frame-step shortcuts (`,` / `.`). */
|
|
450
|
+
private _frameDuration;
|
|
415
451
|
private _onKeydown;
|
|
416
452
|
private _clearTimers;
|
|
417
453
|
get src(): string;
|
package/dist/player.d.ts
CHANGED
|
@@ -209,6 +209,14 @@ interface CreatePlayerOptions {
|
|
|
209
209
|
* for interceptors, logging, or environments without a global fetch.
|
|
210
210
|
*/
|
|
211
211
|
fetchFn?: FetchFn;
|
|
212
|
+
/**
|
|
213
|
+
* Byte budget for the libav HTTP reader's LRU cache of fetched ranges.
|
|
214
|
+
* Defaults to 8 MB. Set to `0` to disable caching. Raise this when the
|
|
215
|
+
* app plays seek-heavy legacy-container media from URLs — hot regions
|
|
216
|
+
* (header/moov, tail index, current window) stay resident instead of
|
|
217
|
+
* being re-fetched on every bounce.
|
|
218
|
+
*/
|
|
219
|
+
cacheBytes?: number;
|
|
212
220
|
}
|
|
213
221
|
/** Signature-compatible with `globalThis.fetch`. */
|
|
214
222
|
type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
@@ -216,6 +224,13 @@ type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Respons
|
|
|
216
224
|
interface TransportConfig {
|
|
217
225
|
requestInit?: RequestInit;
|
|
218
226
|
fetchFn?: FetchFn;
|
|
227
|
+
/**
|
|
228
|
+
* Byte budget for the libav HTTP reader's LRU cache of fetched ranges.
|
|
229
|
+
* Defaults to 8 MB. Set to `0` to disable caching entirely. Higher
|
|
230
|
+
* values help seek-heavy network playback keep hot regions
|
|
231
|
+
* (header/moov, tail index, current read) resident.
|
|
232
|
+
*/
|
|
233
|
+
cacheBytes?: number;
|
|
219
234
|
}
|
|
220
235
|
/** Events emitted by {@link UnifiedPlayer}. Strongly typed. */
|
|
221
236
|
interface PlayerEventMap {
|
|
@@ -345,6 +360,8 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
345
360
|
private _state;
|
|
346
361
|
private _controlsTimer;
|
|
347
362
|
private _settingsOpen;
|
|
363
|
+
private _activeAudioTrackId;
|
|
364
|
+
private _activeSubtitleTrackId;
|
|
348
365
|
private _userSeeking;
|
|
349
366
|
private _holdTimer;
|
|
350
367
|
private _holdSpeedActive;
|
|
@@ -369,11 +386,21 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
369
386
|
private _onSeekCommit;
|
|
370
387
|
/** Linear click-to-time mapping across the full track width (no edge clamping). */
|
|
371
388
|
private _timeFromSeekPointer;
|
|
389
|
+
/** Seekbar width below which drag-to-scrub seeks in real-time (vs
|
|
390
|
+
* preview-only). On narrow bars precise positioning is hard, so
|
|
391
|
+
* immediate video feedback is more useful than a time tooltip. */
|
|
392
|
+
private static readonly SCRUB_WIDTH_THRESHOLD;
|
|
372
393
|
private _onSeekPointerDown;
|
|
373
394
|
private _onSeekHover;
|
|
374
395
|
private _updateSeekTooltip;
|
|
375
396
|
private _updateSeekVisuals;
|
|
376
397
|
private _updateTime;
|
|
398
|
+
/**
|
|
399
|
+
* Render every buffered range as its own segment so gaps (common on MSE
|
|
400
|
+
* after seeks) are visible. Not gated by `_userSeeking` — ranges should
|
|
401
|
+
* keep updating while the user scrubs, and runs cheaply on `progress`.
|
|
402
|
+
*/
|
|
403
|
+
private _updateBuffered;
|
|
377
404
|
private _toggleMute;
|
|
378
405
|
private _updateVolume;
|
|
379
406
|
private _toggleSettings;
|
|
@@ -399,8 +426,14 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
399
426
|
showControls(durationMs?: number): void;
|
|
400
427
|
private _showControls;
|
|
401
428
|
private _scheduleHide;
|
|
429
|
+
/** Read the controls-timeout attribute. 0 or negative = never hide.
|
|
430
|
+
* Unset = default 3000ms. */
|
|
431
|
+
private _getControlsTimeout;
|
|
402
432
|
/** Track whether the last interaction was touch so click handler can skip. */
|
|
403
433
|
private _lastPointerTypeWasTouch;
|
|
434
|
+
/** True for ~50ms after a touch double-tap was handled, so the
|
|
435
|
+
* synthetic dblclick from the browser doesn't also fire fullscreen. */
|
|
436
|
+
private _touchDoubleTapConsumed;
|
|
404
437
|
/** True if the event's composed path passes through consumer-slotted
|
|
405
438
|
* content (toolbar or content-overlay). Slotted content lives in the
|
|
406
439
|
* light DOM so `.closest(".avp-toolbar-top")` on the event target won't
|
|
@@ -412,6 +445,9 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
412
445
|
private _onPointerUp;
|
|
413
446
|
private _cancelHold;
|
|
414
447
|
private _doDoubleTap;
|
|
448
|
+
/** Duration of one frame in seconds, derived from diagnostics fps or
|
|
449
|
+
* a 30fps default. Used for frame-step shortcuts (`,` / `.`). */
|
|
450
|
+
private _frameDuration;
|
|
415
451
|
private _onKeydown;
|
|
416
452
|
private _clearTimers;
|
|
417
453
|
get src(): string;
|
package/dist/player.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AvbridgeError, ERR_ALL_STRATEGIES_EXHAUSTED, ERR_PLAYER_NOT_READY, normalizeSource, sniffNormalizedSource, ERR_PROBE_FAILED, ERR_PROBE_UNKNOWN_CONTAINER, ERR_LIBAV_NOT_REACHABLE, ERR_MSE_NOT_SUPPORTED, ERR_MSE_CODEC_NOT_SUPPORTED } from './chunk-E76AMWI4.js';
|
|
2
2
|
import { dbg, loadLibav } from './chunk-IAYKFGFG.js';
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-
|
|
3
|
+
import './chunk-3AI5WFFN.js';
|
|
4
|
+
import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-EDDWAN2L.js';
|
|
5
5
|
import './chunk-LUFA47FP.js';
|
|
6
6
|
|
|
7
7
|
// src/events.ts
|
|
@@ -237,7 +237,7 @@ async function probe(source, transport) {
|
|
|
237
237
|
const hasUnknownCodec = result.videoTracks.some((t) => t.codec === "unknown") || result.audioTracks.some((t) => t.codec === "unknown");
|
|
238
238
|
if (hasUnknownCodec) {
|
|
239
239
|
try {
|
|
240
|
-
const { probeWithLibav } = await import('./avi-
|
|
240
|
+
const { probeWithLibav } = await import('./avi-S7EY54YA.js');
|
|
241
241
|
return await probeWithLibav(normalized, sniffed);
|
|
242
242
|
} catch {
|
|
243
243
|
return result;
|
|
@@ -250,7 +250,7 @@ async function probe(source, transport) {
|
|
|
250
250
|
mediabunnyErr.message
|
|
251
251
|
);
|
|
252
252
|
try {
|
|
253
|
-
const { probeWithLibav } = await import('./avi-
|
|
253
|
+
const { probeWithLibav } = await import('./avi-S7EY54YA.js');
|
|
254
254
|
return await probeWithLibav(normalized, sniffed);
|
|
255
255
|
} catch (libavErr) {
|
|
256
256
|
const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
|
|
@@ -264,7 +264,7 @@ async function probe(source, transport) {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
try {
|
|
267
|
-
const { probeWithLibav } = await import('./avi-
|
|
267
|
+
const { probeWithLibav } = await import('./avi-S7EY54YA.js');
|
|
268
268
|
return await probeWithLibav(normalized, sniffed);
|
|
269
269
|
} catch (err) {
|
|
270
270
|
const inner = err instanceof Error ? err.message : String(err);
|
|
@@ -1749,6 +1749,10 @@ var AudioOutput = class {
|
|
|
1749
1749
|
if (this.ctx.state === "suspended") {
|
|
1750
1750
|
await this.ctx.resume();
|
|
1751
1751
|
}
|
|
1752
|
+
try {
|
|
1753
|
+
this.gain.connect(this.ctx.destination);
|
|
1754
|
+
} catch {
|
|
1755
|
+
}
|
|
1752
1756
|
if (this.state === "paused") {
|
|
1753
1757
|
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1754
1758
|
this.state = "playing";
|
|
@@ -1775,6 +1779,10 @@ var AudioOutput = class {
|
|
|
1775
1779
|
this.mediaTimeOfAnchor = this.now();
|
|
1776
1780
|
this.state = "paused";
|
|
1777
1781
|
if (this.noAudio) return;
|
|
1782
|
+
try {
|
|
1783
|
+
this.gain.disconnect();
|
|
1784
|
+
} catch {
|
|
1785
|
+
}
|
|
1778
1786
|
if (this.ctx.state === "running") {
|
|
1779
1787
|
await this.ctx.suspend();
|
|
1780
1788
|
}
|
|
@@ -2013,7 +2021,7 @@ async function startHybridDecoder(opts) {
|
|
|
2013
2021
|
const variant = pickLibavVariant(opts.context);
|
|
2014
2022
|
const libav = await loadLibav(variant);
|
|
2015
2023
|
const bridge = await loadBridge();
|
|
2016
|
-
const { prepareLibavInput } = await import('./libav-http-reader-
|
|
2024
|
+
const { prepareLibavInput } = await import('./libav-http-reader-2S5HAHW4.js');
|
|
2017
2025
|
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
|
|
2018
2026
|
const readPkt = await libav.av_packet_alloc();
|
|
2019
2027
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
@@ -2683,7 +2691,7 @@ async function startDecoder(opts) {
|
|
|
2683
2691
|
const variant = "avbridge";
|
|
2684
2692
|
const libav = await loadLibav(variant);
|
|
2685
2693
|
const bridge = await loadBridge2();
|
|
2686
|
-
const { prepareLibavInput } = await import('./libav-http-reader-
|
|
2694
|
+
const { prepareLibavInput } = await import('./libav-http-reader-2S5HAHW4.js');
|
|
2687
2695
|
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
|
|
2688
2696
|
const readPkt = await libav.av_packet_alloc();
|
|
2689
2697
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
@@ -3412,9 +3420,9 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3412
3420
|
constructor(options, registry) {
|
|
3413
3421
|
this.options = options;
|
|
3414
3422
|
this.registry = registry;
|
|
3415
|
-
const { requestInit, fetchFn } = options;
|
|
3416
|
-
if (requestInit || fetchFn) {
|
|
3417
|
-
this.transport = { requestInit, fetchFn };
|
|
3423
|
+
const { requestInit, fetchFn, cacheBytes } = options;
|
|
3424
|
+
if (requestInit || fetchFn || cacheBytes !== void 0) {
|
|
3425
|
+
this.transport = { requestInit, fetchFn, cacheBytes };
|
|
3418
3426
|
}
|
|
3419
3427
|
}
|
|
3420
3428
|
options;
|
|
@@ -4565,7 +4573,7 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
|
|
|
4565
4573
|
* strategies pick up the new track via their textTracks watcher.
|
|
4566
4574
|
*/
|
|
4567
4575
|
async addSubtitle(subtitle) {
|
|
4568
|
-
const { attachSubtitleTracks: attachSubtitleTracks2 } = await import('./subtitles-
|
|
4576
|
+
const { attachSubtitleTracks: attachSubtitleTracks2 } = await import('./subtitles-5H24MEBJ.js');
|
|
4569
4577
|
const format = subtitle.format ?? (subtitle.url.endsWith(".srt") ? "srt" : "vtt");
|
|
4570
4578
|
const track = {
|
|
4571
4579
|
id: this._subtitleTracks.length,
|
|
@@ -4574,14 +4582,27 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
|
|
|
4574
4582
|
sidecarUrl: subtitle.url
|
|
4575
4583
|
};
|
|
4576
4584
|
this._subtitleTracks.push(track);
|
|
4585
|
+
console.log(`[avbridge:subs] addSubtitle id=${track.id} format=${format} lang=${subtitle.language ?? "?"}`);
|
|
4577
4586
|
await attachSubtitleTracks2(
|
|
4578
4587
|
this._videoEl,
|
|
4579
4588
|
this._subtitleTracks,
|
|
4580
4589
|
void 0,
|
|
4581
4590
|
(err, t) => {
|
|
4582
|
-
console.warn(`[avbridge] subtitle ${t.id} failed: ${err.message}`);
|
|
4591
|
+
console.warn(`[avbridge:subs] subtitle ${t.id} failed: ${err.message}`);
|
|
4583
4592
|
}
|
|
4584
4593
|
);
|
|
4594
|
+
const textTracks = this._videoEl.textTracks;
|
|
4595
|
+
for (let i = 0; i < textTracks.length; i++) {
|
|
4596
|
+
if (textTracks[i].label === (subtitle.language ?? `Subtitle ${track.id}`)) {
|
|
4597
|
+
textTracks[i].mode = "showing";
|
|
4598
|
+
console.log(`[avbridge:subs] enabled textTrack[${i}] mode=showing`);
|
|
4599
|
+
break;
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
this._dispatch("trackschange", {
|
|
4603
|
+
audioTracks: this._audioTracks,
|
|
4604
|
+
subtitleTracks: this.subtitleTracks
|
|
4605
|
+
});
|
|
4585
4606
|
}
|
|
4586
4607
|
/**
|
|
4587
4608
|
* Disable the automatic `screen.orientation.lock()` that runs on
|
|
@@ -5012,6 +5033,12 @@ var PLAYER_STYLES = (
|
|
|
5012
5033
|
display: flex;
|
|
5013
5034
|
align-items: center;
|
|
5014
5035
|
cursor: pointer;
|
|
5036
|
+
/* Claim all touch gestures on the seek bar. Without this, Android
|
|
5037
|
+
* browsers (Chrome, Samsung Internet) treat horizontal drags as
|
|
5038
|
+
* scroll candidates and cancel pointermove once the gesture
|
|
5039
|
+
* resolves, breaking scrub. touch-action must be set in CSS \u2014
|
|
5040
|
+
* preventDefault() on pointerdown is too late. */
|
|
5041
|
+
touch-action: none;
|
|
5015
5042
|
}
|
|
5016
5043
|
|
|
5017
5044
|
.avp-seek-track {
|
|
@@ -5029,7 +5056,13 @@ var PLAYER_STYLES = (
|
|
|
5029
5056
|
|
|
5030
5057
|
.avp-seek-buffered {
|
|
5031
5058
|
position: absolute;
|
|
5032
|
-
|
|
5059
|
+
inset: 0;
|
|
5060
|
+
pointer-events: none;
|
|
5061
|
+
}
|
|
5062
|
+
|
|
5063
|
+
.avp-seek-buffered-range {
|
|
5064
|
+
position: absolute;
|
|
5065
|
+
top: 0;
|
|
5033
5066
|
height: 100%;
|
|
5034
5067
|
background: rgba(255, 255, 255, 0.35);
|
|
5035
5068
|
border-radius: inherit;
|
|
@@ -5395,7 +5428,7 @@ function formatTime(sec) {
|
|
|
5395
5428
|
return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
5396
5429
|
}
|
|
5397
5430
|
var PLAYBACK_SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
5398
|
-
var
|
|
5431
|
+
var DEFAULT_CONTROLS_HIDE_MS = 3e3;
|
|
5399
5432
|
var FORWARDED_EVENTS = [
|
|
5400
5433
|
"ready",
|
|
5401
5434
|
"error",
|
|
@@ -5438,7 +5471,7 @@ var PROXY_ATTRIBUTES = [
|
|
|
5438
5471
|
];
|
|
5439
5472
|
var PLAYER_ATTRIBUTES = ["show-fit"];
|
|
5440
5473
|
var FIT_MODES = ["contain", "cover", "fill"];
|
|
5441
|
-
var AvbridgePlayerElement = class extends HTMLElement {
|
|
5474
|
+
var AvbridgePlayerElement = class _AvbridgePlayerElement extends HTMLElement {
|
|
5442
5475
|
static observedAttributes = [...PROXY_ATTRIBUTES, ...PLAYER_ATTRIBUTES];
|
|
5443
5476
|
// ── Internal DOM refs ──────────────────────────────────────────────────
|
|
5444
5477
|
_video;
|
|
@@ -5466,6 +5499,8 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5466
5499
|
_state = "idle";
|
|
5467
5500
|
_controlsTimer = null;
|
|
5468
5501
|
_settingsOpen = false;
|
|
5502
|
+
_activeAudioTrackId = null;
|
|
5503
|
+
_activeSubtitleTrackId = null;
|
|
5469
5504
|
_userSeeking = false;
|
|
5470
5505
|
_holdTimer = null;
|
|
5471
5506
|
_holdSpeedActive = false;
|
|
@@ -5587,6 +5622,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5587
5622
|
on(this._video, "ended", () => this._setState("ended"));
|
|
5588
5623
|
on(this._video, "error", () => this._setState("error"));
|
|
5589
5624
|
on(this._video, "timeupdate", () => this._updateTime());
|
|
5625
|
+
on(this._video, "progress", () => this._updateBuffered());
|
|
5590
5626
|
on(this._video, "volumechange", () => this._updateVolume());
|
|
5591
5627
|
on(this._video, "trackschange", () => this._buildSettingsMenu());
|
|
5592
5628
|
on(this._video, "durationchange", () => {
|
|
@@ -5729,6 +5765,10 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5729
5765
|
const frac = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
5730
5766
|
return frac * (this._video.duration || 0);
|
|
5731
5767
|
}
|
|
5768
|
+
/** Seekbar width below which drag-to-scrub seeks in real-time (vs
|
|
5769
|
+
* preview-only). On narrow bars precise positioning is hard, so
|
|
5770
|
+
* immediate video feedback is more useful than a time tooltip. */
|
|
5771
|
+
static SCRUB_WIDTH_THRESHOLD = 400;
|
|
5732
5772
|
_onSeekPointerDown(e) {
|
|
5733
5773
|
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
5734
5774
|
e.preventDefault();
|
|
@@ -5736,15 +5776,26 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5736
5776
|
const seekBar = this.shadowRoot.querySelector(".avp-seek");
|
|
5737
5777
|
seekBar.setPointerCapture(e.pointerId);
|
|
5738
5778
|
seekBar.setAttribute("data-seeking", "");
|
|
5779
|
+
const scrubMode = seekBar.getBoundingClientRect().width < _AvbridgePlayerElement.SCRUB_WIDTH_THRESHOLD;
|
|
5780
|
+
let lastScrubCommit = 0;
|
|
5739
5781
|
const initial = this._timeFromSeekPointer(e.clientX);
|
|
5740
5782
|
this._seekInput.value = String(initial);
|
|
5741
5783
|
this._onSeekInput();
|
|
5742
5784
|
this._updateSeekTooltip(e.clientX);
|
|
5785
|
+
if (scrubMode) this._onSeekCommit();
|
|
5743
5786
|
const onMove = (ev) => {
|
|
5744
5787
|
const t = this._timeFromSeekPointer(ev.clientX);
|
|
5745
5788
|
this._seekInput.value = String(t);
|
|
5746
5789
|
this._onSeekInput();
|
|
5747
5790
|
this._updateSeekTooltip(ev.clientX);
|
|
5791
|
+
if (scrubMode) {
|
|
5792
|
+
const now = performance.now();
|
|
5793
|
+
if (now - lastScrubCommit > 250) {
|
|
5794
|
+
lastScrubCommit = now;
|
|
5795
|
+
this._onSeekCommit();
|
|
5796
|
+
this._userSeeking = true;
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
5748
5799
|
};
|
|
5749
5800
|
const onUp = (ev) => {
|
|
5750
5801
|
const t = this._timeFromSeekPointer(ev.clientX);
|
|
@@ -5788,13 +5839,45 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5788
5839
|
this._seekInput.value = String(t);
|
|
5789
5840
|
this._updateSeekVisuals(t);
|
|
5790
5841
|
this._timeDisplay.textContent = `${formatTime(t)} / ${formatTime(d)}`;
|
|
5842
|
+
this._updateBuffered();
|
|
5843
|
+
}
|
|
5844
|
+
/**
|
|
5845
|
+
* Render every buffered range as its own segment so gaps (common on MSE
|
|
5846
|
+
* after seeks) are visible. Not gated by `_userSeeking` — ranges should
|
|
5847
|
+
* keep updating while the user scrubs, and runs cheaply on `progress`.
|
|
5848
|
+
*/
|
|
5849
|
+
_updateBuffered() {
|
|
5850
|
+
const d = this._video.duration;
|
|
5851
|
+
if (!(d > 0)) return;
|
|
5852
|
+
let buf;
|
|
5791
5853
|
try {
|
|
5792
|
-
|
|
5793
|
-
if (buf && buf.length > 0 && d > 0) {
|
|
5794
|
-
const end = buf.end(buf.length - 1);
|
|
5795
|
-
this._seekBuffered.style.width = `${end / d * 100}%`;
|
|
5796
|
-
}
|
|
5854
|
+
buf = this._video.buffered;
|
|
5797
5855
|
} catch {
|
|
5856
|
+
return;
|
|
5857
|
+
}
|
|
5858
|
+
const count = buf ? buf.length : 0;
|
|
5859
|
+
const host = this._seekBuffered;
|
|
5860
|
+
while (host.childElementCount > count) host.lastElementChild.remove();
|
|
5861
|
+
while (host.childElementCount < count) {
|
|
5862
|
+
const seg = document.createElement("div");
|
|
5863
|
+
seg.className = "avp-seek-buffered-range";
|
|
5864
|
+
host.appendChild(seg);
|
|
5865
|
+
}
|
|
5866
|
+
for (let i = 0; i < count; i++) {
|
|
5867
|
+
let start;
|
|
5868
|
+
let end;
|
|
5869
|
+
try {
|
|
5870
|
+
start = buf.start(i);
|
|
5871
|
+
end = buf.end(i);
|
|
5872
|
+
} catch {
|
|
5873
|
+
continue;
|
|
5874
|
+
}
|
|
5875
|
+
const s = Math.max(0, start);
|
|
5876
|
+
const e = Math.min(d, end);
|
|
5877
|
+
if (e <= s) continue;
|
|
5878
|
+
const seg = host.children[i];
|
|
5879
|
+
seg.style.left = `${s / d * 100}%`;
|
|
5880
|
+
seg.style.width = `${(e - s) / d * 100}%`;
|
|
5798
5881
|
}
|
|
5799
5882
|
}
|
|
5800
5883
|
// ── Controls: volume ───────────────────────────────────────────────────
|
|
@@ -5845,19 +5928,27 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5845
5928
|
sections.push(selectRow("Speed", speedValue, speedOpts, `data-action="speed"`));
|
|
5846
5929
|
const audios = this._video.audioTracks ?? [];
|
|
5847
5930
|
if (audios.length > 1) {
|
|
5931
|
+
const activeAudioId = this._activeAudioTrackId ?? audios[0]?.id;
|
|
5932
|
+
const activeAudio = audios.find((t) => t.id === activeAudioId) ?? audios[0];
|
|
5933
|
+
const audioValue = activeAudio?.language ?? `Track ${activeAudio?.id ?? 1}`;
|
|
5848
5934
|
let audioOpts = "";
|
|
5849
5935
|
for (const t of audios) {
|
|
5850
|
-
|
|
5936
|
+
const sel = t.id === activeAudioId ? " selected" : "";
|
|
5937
|
+
audioOpts += `<option value="${t.id}"${sel}>${t.language ?? `Track ${t.id}`}</option>`;
|
|
5851
5938
|
}
|
|
5852
|
-
sections.push(selectRow("Audio",
|
|
5939
|
+
sections.push(selectRow("Audio", audioValue, audioOpts, `data-action="audio"`));
|
|
5853
5940
|
}
|
|
5854
5941
|
const subs = this._video.subtitleTracks ?? [];
|
|
5855
5942
|
if (subs.length > 0) {
|
|
5856
|
-
|
|
5943
|
+
const activeSubId = this._activeSubtitleTrackId;
|
|
5944
|
+
const activeSub = activeSubId != null ? subs.find((t) => t.id === activeSubId) : null;
|
|
5945
|
+
const subValue = activeSub ? activeSub.language ?? `Track ${activeSub.id}` : "Off";
|
|
5946
|
+
let subOpts = `<option value="-1"${activeSubId == null ? " selected" : ""}>Off</option>`;
|
|
5857
5947
|
for (const t of subs) {
|
|
5858
|
-
|
|
5948
|
+
const sel = t.id === activeSubId ? " selected" : "";
|
|
5949
|
+
subOpts += `<option value="${t.id}"${sel}>${t.language ?? `Track ${t.id}`}</option>`;
|
|
5859
5950
|
}
|
|
5860
|
-
sections.push(selectRow("Subtitles",
|
|
5951
|
+
sections.push(selectRow("Subtitles", subValue, subOpts, `data-action="subtitle"`));
|
|
5861
5952
|
}
|
|
5862
5953
|
if (this.hasAttribute("show-fit")) {
|
|
5863
5954
|
const currentFit = this._video.fit ?? "contain";
|
|
@@ -5897,11 +5988,15 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5897
5988
|
this._video.playbackRate = Number(val);
|
|
5898
5989
|
break;
|
|
5899
5990
|
case "audio":
|
|
5991
|
+
this._activeAudioTrackId = Number(val);
|
|
5900
5992
|
void this._video.setAudioTrack(Number(val));
|
|
5901
5993
|
break;
|
|
5902
|
-
case "subtitle":
|
|
5903
|
-
|
|
5994
|
+
case "subtitle": {
|
|
5995
|
+
const subId = Number(val);
|
|
5996
|
+
this._activeSubtitleTrackId = subId >= 0 ? subId : null;
|
|
5997
|
+
void this._video.setSubtitleTrack(subId >= 0 ? subId : null);
|
|
5904
5998
|
break;
|
|
5999
|
+
}
|
|
5905
6000
|
case "fit":
|
|
5906
6001
|
this.setAttribute("fit", val);
|
|
5907
6002
|
break;
|
|
@@ -5996,16 +6091,26 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5996
6091
|
_showControls() {
|
|
5997
6092
|
this.showControls();
|
|
5998
6093
|
}
|
|
5999
|
-
_scheduleHide(durationMs
|
|
6094
|
+
_scheduleHide(durationMs) {
|
|
6095
|
+
const ms = durationMs ?? this._getControlsTimeout();
|
|
6000
6096
|
if (this._controlsTimer) clearTimeout(this._controlsTimer);
|
|
6001
6097
|
if (this._state !== "playing" && this._state !== "buffering") return;
|
|
6002
6098
|
if (this._settingsOpen) return;
|
|
6099
|
+
if (ms <= 0) return;
|
|
6003
6100
|
this._controlsTimer = setTimeout(() => {
|
|
6004
6101
|
if (this._state === "playing") {
|
|
6005
6102
|
this.setAttribute("data-controls-hidden", "");
|
|
6006
6103
|
this._toolbarTop.setAttribute("data-visible", "false");
|
|
6007
6104
|
}
|
|
6008
|
-
},
|
|
6105
|
+
}, ms);
|
|
6106
|
+
}
|
|
6107
|
+
/** Read the controls-timeout attribute. 0 or negative = never hide.
|
|
6108
|
+
* Unset = default 3000ms. */
|
|
6109
|
+
_getControlsTimeout() {
|
|
6110
|
+
const attr = this.getAttribute("controls-timeout");
|
|
6111
|
+
if (attr == null) return DEFAULT_CONTROLS_HIDE_MS;
|
|
6112
|
+
const n = Number(attr);
|
|
6113
|
+
return Number.isFinite(n) ? n : DEFAULT_CONTROLS_HIDE_MS;
|
|
6009
6114
|
}
|
|
6010
6115
|
// Strategy is visible in Stats for Nerds, no badge in controls bar.
|
|
6011
6116
|
// ── Click / tap handling (YouTube delayed-tap pattern) ──────────────────
|
|
@@ -6017,6 +6122,9 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
6017
6122
|
// it's treated as a double-click and the single-click action is cancelled.
|
|
6018
6123
|
/** Track whether the last interaction was touch so click handler can skip. */
|
|
6019
6124
|
_lastPointerTypeWasTouch = false;
|
|
6125
|
+
/** True for ~50ms after a touch double-tap was handled, so the
|
|
6126
|
+
* synthetic dblclick from the browser doesn't also fire fullscreen. */
|
|
6127
|
+
_touchDoubleTapConsumed = false;
|
|
6020
6128
|
/** True if the event's composed path passes through consumer-slotted
|
|
6021
6129
|
* content (toolbar or content-overlay). Slotted content lives in the
|
|
6022
6130
|
* light DOM so `.closest(".avp-toolbar-top")` on the event target won't
|
|
@@ -6050,6 +6158,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
6050
6158
|
_onContainerDblClick(e) {
|
|
6051
6159
|
if (e.target.closest?.(".avp-controls, .avp-settings")) return;
|
|
6052
6160
|
if (this._isSlottedContentEvent(e)) return;
|
|
6161
|
+
if (this._touchDoubleTapConsumed) return;
|
|
6053
6162
|
if (this._tapTimer) {
|
|
6054
6163
|
clearTimeout(this._tapTimer);
|
|
6055
6164
|
this._tapTimer = null;
|
|
@@ -6091,6 +6200,10 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
6091
6200
|
} else {
|
|
6092
6201
|
this._toggleFullscreen();
|
|
6093
6202
|
}
|
|
6203
|
+
this._touchDoubleTapConsumed = true;
|
|
6204
|
+
setTimeout(() => {
|
|
6205
|
+
this._touchDoubleTapConsumed = false;
|
|
6206
|
+
}, 100);
|
|
6094
6207
|
this._lastTapTime = 0;
|
|
6095
6208
|
return;
|
|
6096
6209
|
}
|
|
@@ -6124,6 +6237,13 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
6124
6237
|
this._video.currentTime = Math.max(0, this._video.currentTime + delta);
|
|
6125
6238
|
}
|
|
6126
6239
|
// ── Keyboard shortcuts ─────────────────────────────────────────────────
|
|
6240
|
+
/** Duration of one frame in seconds, derived from diagnostics fps or
|
|
6241
|
+
* a 30fps default. Used for frame-step shortcuts (`,` / `.`). */
|
|
6242
|
+
_frameDuration() {
|
|
6243
|
+
const diag = this._video.getDiagnostics();
|
|
6244
|
+
const fps = diag?.fps && diag.fps > 0 ? diag.fps : 30;
|
|
6245
|
+
return 1 / fps;
|
|
6246
|
+
}
|
|
6127
6247
|
_onKeydown(e) {
|
|
6128
6248
|
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
|
6129
6249
|
switch (e.key) {
|
|
@@ -6168,6 +6288,19 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
6168
6288
|
this._video.playbackRate = Math.max(0.25, this._video.playbackRate - 0.25);
|
|
6169
6289
|
this._buildSettingsMenu();
|
|
6170
6290
|
break;
|
|
6291
|
+
case ",":
|
|
6292
|
+
e.preventDefault();
|
|
6293
|
+
if (!this._video.paused) this._video.pause();
|
|
6294
|
+
this._video.currentTime = Math.max(0, this._video.currentTime - this._frameDuration());
|
|
6295
|
+
break;
|
|
6296
|
+
case ".":
|
|
6297
|
+
e.preventDefault();
|
|
6298
|
+
if (!this._video.paused) this._video.pause();
|
|
6299
|
+
this._video.currentTime = Math.min(
|
|
6300
|
+
this._video.duration || 0,
|
|
6301
|
+
this._video.currentTime + this._frameDuration()
|
|
6302
|
+
);
|
|
6303
|
+
break;
|
|
6171
6304
|
case "Escape":
|
|
6172
6305
|
if (this._settingsOpen) {
|
|
6173
6306
|
e.preventDefault();
|