avbridge 2.2.0 → 2.2.1
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 +45 -0
- package/README.md +98 -71
- package/dist/{chunk-C5VA5U5O.js → chunk-DMWARSEF.js} +76 -21
- package/dist/chunk-DMWARSEF.js.map +1 -0
- package/dist/{chunk-OE66B34H.cjs → chunk-UF2N5L63.cjs} +76 -21
- package/dist/chunk-UF2N5L63.cjs.map +1 -0
- package/dist/element-browser.js +74 -19
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +2 -2
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +1 -1
- package/dist/index.cjs +14 -14
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{player-DUyvltvy.d.cts → player-U2NPmFvA.d.cts} +1 -0
- package/dist/{player-DUyvltvy.d.ts → player-U2NPmFvA.d.ts} +1 -0
- package/package.json +1 -1
- package/src/player.ts +24 -16
- package/src/strategies/fallback/index.ts +8 -0
- package/src/strategies/fallback/video-renderer.ts +5 -1
- package/src/strategies/hybrid/index.ts +9 -1
- package/src/strategies/remux/index.ts +13 -1
- package/src/strategies/remux/pipeline.ts +6 -0
- package/dist/chunk-C5VA5U5O.js.map +0 -1
- package/dist/chunk-OE66B34H.cjs.map +0 -1
package/dist/element.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkUF2N5L63_cjs = require('./chunk-UF2N5L63.cjs');
|
|
4
4
|
require('./chunk-HZLQNKFN.cjs');
|
|
5
5
|
require('./chunk-G4APZMCP.cjs');
|
|
6
6
|
require('./chunk-NZU7W256.cjs');
|
|
@@ -226,7 +226,7 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
|
|
|
226
226
|
this._dispatch("loadstart", {});
|
|
227
227
|
let player;
|
|
228
228
|
try {
|
|
229
|
-
player = await
|
|
229
|
+
player = await chunkUF2N5L63_cjs.createPlayer({
|
|
230
230
|
source,
|
|
231
231
|
target: this._videoEl,
|
|
232
232
|
// Honor the consumer's preferred initial strategy. "auto" means
|
package/dist/element.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-
|
|
1
|
+
import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-U2NPmFvA.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `<avbridge-video>` — `HTMLMediaElement`-compatible primitive backed by the
|
package/dist/element.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-
|
|
1
|
+
import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-U2NPmFvA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `<avbridge-video>` — `HTMLMediaElement`-compatible primitive backed by the
|
package/dist/element.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkUF2N5L63_cjs = require('./chunk-UF2N5L63.cjs');
|
|
4
4
|
var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
|
|
5
5
|
var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
|
|
6
6
|
require('./chunk-G4APZMCP.cjs');
|
|
@@ -21,7 +21,7 @@ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
|
21
21
|
async function remux(source, options = {}) {
|
|
22
22
|
const outputFormat = options.outputFormat ?? "mp4";
|
|
23
23
|
options.signal?.throwIfAborted();
|
|
24
|
-
const ctx = await
|
|
24
|
+
const ctx = await chunkUF2N5L63_cjs.probe(source);
|
|
25
25
|
options.signal?.throwIfAborted();
|
|
26
26
|
validateRemuxEligibility(ctx, options.strict ?? false);
|
|
27
27
|
if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
|
|
@@ -33,7 +33,7 @@ function validateRemuxEligibility(ctx, strict) {
|
|
|
33
33
|
const video = ctx.videoTracks[0];
|
|
34
34
|
const audio = ctx.audioTracks[0];
|
|
35
35
|
if (video) {
|
|
36
|
-
const mbCodec =
|
|
36
|
+
const mbCodec = chunkUF2N5L63_cjs.avbridgeVideoToMediabunny(video.codec);
|
|
37
37
|
if (!mbCodec) {
|
|
38
38
|
throw new Error(
|
|
39
39
|
`Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
@@ -41,7 +41,7 @@ function validateRemuxEligibility(ctx, strict) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
if (audio) {
|
|
44
|
-
const mbCodec =
|
|
44
|
+
const mbCodec = chunkUF2N5L63_cjs.avbridgeAudioToMediabunny(audio.codec);
|
|
45
45
|
if (!mbCodec) {
|
|
46
46
|
throw new Error(
|
|
47
47
|
`Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
@@ -60,7 +60,7 @@ function validateRemuxEligibility(ctx, strict) {
|
|
|
60
60
|
async function remuxViaMediAbunny(ctx, outputFormat, options) {
|
|
61
61
|
const mb = await import('mediabunny');
|
|
62
62
|
const input = new mb.Input({
|
|
63
|
-
source: await
|
|
63
|
+
source: await chunkUF2N5L63_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
|
|
64
64
|
formats: mb.ALL_FORMATS
|
|
65
65
|
});
|
|
66
66
|
const target = new mb.BufferTarget();
|
|
@@ -146,8 +146,8 @@ async function doLibavRemux(libav, filename, ctx, outputFormat, options) {
|
|
|
146
146
|
const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
|
|
147
147
|
const videoTrackInfo = ctx.videoTracks[0];
|
|
148
148
|
const audioTrackInfo = ctx.audioTracks[0];
|
|
149
|
-
const mbVideoCodec = videoTrackInfo ?
|
|
150
|
-
const mbAudioCodec = audioTrackInfo ?
|
|
149
|
+
const mbVideoCodec = videoTrackInfo ? chunkUF2N5L63_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
|
|
150
|
+
const mbAudioCodec = audioTrackInfo ? chunkUF2N5L63_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
|
|
151
151
|
const target = new mb.BufferTarget();
|
|
152
152
|
const output = new mb.Output({
|
|
153
153
|
format: createOutputFormat(mb, outputFormat),
|
|
@@ -358,7 +358,7 @@ async function transcode(source, options = {}) {
|
|
|
358
358
|
const quality = options.quality ?? "medium";
|
|
359
359
|
validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
|
|
360
360
|
options.signal?.throwIfAborted();
|
|
361
|
-
const ctx = await
|
|
361
|
+
const ctx = await chunkUF2N5L63_cjs.probe(source);
|
|
362
362
|
options.signal?.throwIfAborted();
|
|
363
363
|
if (!MEDIABUNNY_CONTAINERS2.has(ctx.container)) {
|
|
364
364
|
throw new Error(
|
|
@@ -370,7 +370,7 @@ async function transcode(source, options = {}) {
|
|
|
370
370
|
async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
|
|
371
371
|
const mb = await import('mediabunny');
|
|
372
372
|
const input = new mb.Input({
|
|
373
|
-
source: await
|
|
373
|
+
source: await chunkUF2N5L63_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
|
|
374
374
|
formats: mb.ALL_FORMATS
|
|
375
375
|
});
|
|
376
376
|
const target = new mb.BufferTarget();
|
|
@@ -552,23 +552,23 @@ function qualityToMediabunny(mb, quality) {
|
|
|
552
552
|
|
|
553
553
|
Object.defineProperty(exports, "UnifiedPlayer", {
|
|
554
554
|
enumerable: true,
|
|
555
|
-
get: function () { return
|
|
555
|
+
get: function () { return chunkUF2N5L63_cjs.UnifiedPlayer; }
|
|
556
556
|
});
|
|
557
557
|
Object.defineProperty(exports, "classify", {
|
|
558
558
|
enumerable: true,
|
|
559
|
-
get: function () { return
|
|
559
|
+
get: function () { return chunkUF2N5L63_cjs.classifyContext; }
|
|
560
560
|
});
|
|
561
561
|
Object.defineProperty(exports, "createPlayer", {
|
|
562
562
|
enumerable: true,
|
|
563
|
-
get: function () { return
|
|
563
|
+
get: function () { return chunkUF2N5L63_cjs.createPlayer; }
|
|
564
564
|
});
|
|
565
565
|
Object.defineProperty(exports, "probe", {
|
|
566
566
|
enumerable: true,
|
|
567
|
-
get: function () { return
|
|
567
|
+
get: function () { return chunkUF2N5L63_cjs.probe; }
|
|
568
568
|
});
|
|
569
569
|
Object.defineProperty(exports, "srtToVtt", {
|
|
570
570
|
enumerable: true,
|
|
571
|
-
get: function () { return
|
|
571
|
+
get: function () { return chunkUF2N5L63_cjs.srtToVtt; }
|
|
572
572
|
});
|
|
573
573
|
exports.remux = remux;
|
|
574
574
|
exports.transcode = transcode;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-
|
|
2
|
-
export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-
|
|
1
|
+
import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-U2NPmFvA.cjs';
|
|
2
|
+
export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-U2NPmFvA.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Pure classification — no I/O, no async. Test-friendly.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-
|
|
2
|
-
export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-
|
|
1
|
+
import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-U2NPmFvA.js';
|
|
2
|
+
export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-U2NPmFvA.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Pure classification — no I/O, no async. Test-friendly.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-
|
|
2
|
-
export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-
|
|
1
|
+
import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-DMWARSEF.js';
|
|
2
|
+
export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-DMWARSEF.js';
|
|
3
3
|
import { normalizeSource } from './chunk-ILKDNBSE.js';
|
|
4
4
|
import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
|
|
5
5
|
import './chunk-5DMTJVIU.js';
|
package/package.json
CHANGED
package/src/player.ts
CHANGED
|
@@ -34,6 +34,11 @@ export class UnifiedPlayer {
|
|
|
34
34
|
private lastProgressPosition = -1;
|
|
35
35
|
private errorListener: (() => void) | null = null;
|
|
36
36
|
|
|
37
|
+
// Bound so we can removeEventListener in destroy(); without this the
|
|
38
|
+
// listener outlives the player and accumulates on elements that swap
|
|
39
|
+
// source (e.g. <avbridge-video>).
|
|
40
|
+
private endedListener: (() => void) | null = null;
|
|
41
|
+
|
|
37
42
|
// Serializes escalation / setStrategy calls
|
|
38
43
|
private switchingPromise: Promise<void> = Promise.resolve();
|
|
39
44
|
|
|
@@ -121,12 +126,10 @@ export class UnifiedPlayer {
|
|
|
121
126
|
// Try the primary strategy, falling through the chain on failure
|
|
122
127
|
await this.startSession(decision.strategy, decision.reason);
|
|
123
128
|
|
|
124
|
-
// Apply subtitles for non-canvas strategies.
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// load-bearing for playback. (Promoting this to a typed `subtitleerror`
|
|
129
|
-
// event is a nice-to-have follow-up.)
|
|
129
|
+
// Apply subtitles for non-canvas strategies. Per-track failures are
|
|
130
|
+
// caught inside attachSubtitleTracks and logged via console.warn —
|
|
131
|
+
// subtitles are not load-bearing, so a bad sidecar must not break
|
|
132
|
+
// bootstrap.
|
|
130
133
|
if (this.session!.strategy !== "fallback" && this.session!.strategy !== "hybrid") {
|
|
131
134
|
await attachSubtitleTracks(
|
|
132
135
|
this.options.target,
|
|
@@ -146,7 +149,8 @@ export class UnifiedPlayer {
|
|
|
146
149
|
});
|
|
147
150
|
|
|
148
151
|
this.startTimeupdateLoop();
|
|
149
|
-
this.
|
|
152
|
+
this.endedListener = () => this.emitter.emit("ended", undefined);
|
|
153
|
+
this.options.target.addEventListener("ended", this.endedListener);
|
|
150
154
|
this.emitter.emitSticky("ready", undefined);
|
|
151
155
|
const bootstrapElapsed = performance.now() - bootstrapStart;
|
|
152
156
|
dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
|
|
@@ -492,6 +496,10 @@ export class UnifiedPlayer {
|
|
|
492
496
|
this.timeupdateInterval = null;
|
|
493
497
|
}
|
|
494
498
|
this.clearSupervisor();
|
|
499
|
+
if (this.endedListener) {
|
|
500
|
+
this.options.target.removeEventListener("ended", this.endedListener);
|
|
501
|
+
this.endedListener = null;
|
|
502
|
+
}
|
|
495
503
|
if (this.session) {
|
|
496
504
|
await this.session.destroy();
|
|
497
505
|
this.session = null;
|
|
@@ -508,14 +516,12 @@ export async function createPlayer(options: CreatePlayerOptions): Promise<Unifie
|
|
|
508
516
|
}
|
|
509
517
|
|
|
510
518
|
/**
|
|
511
|
-
* Build a synthetic classification
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
* any downstream logic that trusted `strategyClass` could make the wrong
|
|
518
|
-
* decision. We now derive the class from the picked strategy.
|
|
519
|
+
* Build a synthetic classification for an explicit `initialStrategy` override.
|
|
520
|
+
* The `class` is derived from the chosen strategy so diagnostics and any
|
|
521
|
+
* downstream consumer of `strategyClass` see the real strategy. The fallback
|
|
522
|
+
* chain is inherited from the natural classification but must never contain
|
|
523
|
+
* `initial` itself — otherwise `startSession` would retry the strategy that
|
|
524
|
+
* just failed before escalating.
|
|
519
525
|
*
|
|
520
526
|
* @internal — exported for unit tests; not part of the public API.
|
|
521
527
|
*/
|
|
@@ -525,11 +531,13 @@ export function buildInitialDecision(
|
|
|
525
531
|
): Classification {
|
|
526
532
|
const natural = classify(ctx);
|
|
527
533
|
const cls = strategyToClass(initial, natural);
|
|
534
|
+
const inherited = natural.fallbackChain ?? defaultFallbackChain(initial);
|
|
535
|
+
const fallbackChain = inherited.filter((s) => s !== initial);
|
|
528
536
|
return {
|
|
529
537
|
class: cls,
|
|
530
538
|
strategy: initial,
|
|
531
539
|
reason: `initial strategy "${initial}" requested via options.initialStrategy`,
|
|
532
|
-
fallbackChain
|
|
540
|
+
fallbackChain,
|
|
533
541
|
};
|
|
534
542
|
}
|
|
535
543
|
|
|
@@ -93,6 +93,13 @@ export async function createFallbackSession(
|
|
|
93
93
|
void doSeek(v);
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
|
+
// Mirror `paused` from the audio clock — the underlying <video> never
|
|
97
|
+
// has its own src, so its native `paused` is always true and would
|
|
98
|
+
// mislead doSetStrategy's wasPlaying capture on a backend switch.
|
|
99
|
+
Object.defineProperty(target, "paused", {
|
|
100
|
+
configurable: true,
|
|
101
|
+
get: () => !audio.isPlaying(),
|
|
102
|
+
});
|
|
96
103
|
// Mirror duration so the demo's controls can use target.duration too.
|
|
97
104
|
if (ctx.duration && Number.isFinite(ctx.duration)) {
|
|
98
105
|
Object.defineProperty(target, "duration", {
|
|
@@ -244,6 +251,7 @@ export async function createFallbackSession(
|
|
|
244
251
|
try {
|
|
245
252
|
delete (target as unknown as Record<string, unknown>).currentTime;
|
|
246
253
|
delete (target as unknown as Record<string, unknown>).duration;
|
|
254
|
+
delete (target as unknown as Record<string, unknown>).paused;
|
|
247
255
|
} catch { /* ignore */ }
|
|
248
256
|
},
|
|
249
257
|
|
|
@@ -51,8 +51,12 @@ export class VideoRenderer {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
this.canvas = document.createElement("canvas");
|
|
54
|
+
// object-fit:contain letterboxes the canvas bitmap (sized to
|
|
55
|
+
// frame.displayWidth × displayHeight in paint()) inside the stage so
|
|
56
|
+
// portrait / non-stage-aspect content isn't stretched. Canvas is a
|
|
57
|
+
// replaced element, so object-fit applies.
|
|
54
58
|
this.canvas.style.cssText =
|
|
55
|
-
"position:absolute;left:0;top:0;width:100%;height:100%;background:black;";
|
|
59
|
+
"position:absolute;left:0;top:0;width:100%;height:100%;background:black;object-fit:contain;";
|
|
56
60
|
|
|
57
61
|
// Attach the canvas next to the video. When the video lives inside an
|
|
58
62
|
// `<avbridge-video>` shadow root, `target.parentElement` is the
|
|
@@ -45,12 +45,19 @@ export async function createHybridSession(
|
|
|
45
45
|
throw err;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Patch <video> element for the unified player layer
|
|
48
|
+
// Patch <video> element for the unified player layer. `paused` is
|
|
49
|
+
// mirrored from the audio clock so callers that inspect target.paused
|
|
50
|
+
// (notably doSetStrategy capturing wasPlaying) see the real play state
|
|
51
|
+
// — the underlying <video> never has its own src and stays paused.
|
|
49
52
|
Object.defineProperty(target, "currentTime", {
|
|
50
53
|
configurable: true,
|
|
51
54
|
get: () => audio.now(),
|
|
52
55
|
set: (v: number) => { void doSeek(v); },
|
|
53
56
|
});
|
|
57
|
+
Object.defineProperty(target, "paused", {
|
|
58
|
+
configurable: true,
|
|
59
|
+
get: () => !audio.isPlaying(),
|
|
60
|
+
});
|
|
54
61
|
if (ctx.duration && Number.isFinite(ctx.duration)) {
|
|
55
62
|
Object.defineProperty(target, "duration", {
|
|
56
63
|
configurable: true,
|
|
@@ -129,6 +136,7 @@ export async function createHybridSession(
|
|
|
129
136
|
try {
|
|
130
137
|
delete (target as unknown as Record<string, unknown>).currentTime;
|
|
131
138
|
delete (target as unknown as Record<string, unknown>).duration;
|
|
139
|
+
delete (target as unknown as Record<string, unknown>).paused;
|
|
132
140
|
} catch { /* ignore */ }
|
|
133
141
|
},
|
|
134
142
|
|
|
@@ -36,7 +36,19 @@ export async function createRemuxSession(
|
|
|
36
36
|
await pipeline.start(video.currentTime || 0, true);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
// seek() may have already started the pump with autoPlay=false
|
|
40
|
+
// (strategy-switch flow calls seek before play). Flip the pipeline's
|
|
41
|
+
// pending autoPlay so the MseSink fires video.play() once buffered
|
|
42
|
+
// data lands, and also attempt an immediate video.play() in case the
|
|
43
|
+
// sink is already wired up. The immediate call can reject when
|
|
44
|
+
// video.src hasn't been set yet — that's fine, the deferred path will
|
|
45
|
+
// catch it.
|
|
46
|
+
pipeline.setAutoPlay(true);
|
|
47
|
+
try {
|
|
48
|
+
await video.play();
|
|
49
|
+
} catch {
|
|
50
|
+
/* sink not ready yet; setAutoPlay will handle playback on first buffered write */
|
|
51
|
+
}
|
|
40
52
|
},
|
|
41
53
|
pause() {
|
|
42
54
|
wantPlay = false;
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
export interface RemuxPipeline {
|
|
25
25
|
start(fromTime?: number, autoPlay?: boolean): Promise<void>;
|
|
26
26
|
seek(time: number, autoPlay?: boolean): Promise<void>;
|
|
27
|
+
/** Update the autoplay intent mid-flight — used when play() arrives after seek() but before the MseSink has been constructed. */
|
|
28
|
+
setAutoPlay(autoPlay: boolean): void;
|
|
27
29
|
destroy(): Promise<void>;
|
|
28
30
|
stats(): Record<string, unknown>;
|
|
29
31
|
}
|
|
@@ -248,6 +250,10 @@ export async function createRemuxPipeline(
|
|
|
248
250
|
console.error("[avbridge] remux pipeline reseek failed:", err);
|
|
249
251
|
});
|
|
250
252
|
},
|
|
253
|
+
setAutoPlay(autoPlay) {
|
|
254
|
+
pendingAutoPlay = autoPlay;
|
|
255
|
+
if (sink) sink.setPlayOnSeek(autoPlay);
|
|
256
|
+
},
|
|
251
257
|
async destroy() {
|
|
252
258
|
destroyed = true;
|
|
253
259
|
pumpToken++;
|