avbridge 2.3.0 → 2.6.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/dist/{chunk-6UUT4BEA.cjs → chunk-2IJ66NTD.cjs} +13 -20
  3. package/dist/chunk-2IJ66NTD.cjs.map +1 -0
  4. package/dist/{chunk-XKPSTC34.cjs → chunk-2XW2O3YI.cjs} +5 -20
  5. package/dist/chunk-2XW2O3YI.cjs.map +1 -0
  6. package/dist/chunk-5KVLE6YI.js +167 -0
  7. package/dist/chunk-5KVLE6YI.js.map +1 -0
  8. package/dist/{chunk-7RGG6ME7.cjs → chunk-6SOFJV44.cjs} +422 -688
  9. package/dist/chunk-6SOFJV44.cjs.map +1 -0
  10. package/dist/{chunk-2PGRFCWB.js → chunk-CPJLFFCC.js} +8 -18
  11. package/dist/chunk-CPJLFFCC.js.map +1 -0
  12. package/dist/chunk-CPZ7PXAM.cjs +240 -0
  13. package/dist/chunk-CPZ7PXAM.cjs.map +1 -0
  14. package/dist/{chunk-QQXBPW72.js → chunk-E76AMWI4.js} +4 -18
  15. package/dist/chunk-E76AMWI4.js.map +1 -0
  16. package/dist/chunk-LUFA47FP.js +19 -0
  17. package/dist/chunk-LUFA47FP.js.map +1 -0
  18. package/dist/{chunk-NV7ILLWH.js → chunk-OGYHFY6K.js} +404 -665
  19. package/dist/chunk-OGYHFY6K.js.map +1 -0
  20. package/dist/chunk-Q2VUO52Z.cjs +374 -0
  21. package/dist/chunk-Q2VUO52Z.cjs.map +1 -0
  22. package/dist/chunk-QDJLQR53.cjs +22 -0
  23. package/dist/chunk-QDJLQR53.cjs.map +1 -0
  24. package/dist/chunk-S4WAZC2T.cjs +173 -0
  25. package/dist/chunk-S4WAZC2T.cjs.map +1 -0
  26. package/dist/chunk-SMH6IOP2.js +368 -0
  27. package/dist/chunk-SMH6IOP2.js.map +1 -0
  28. package/dist/chunk-SR3MPV4D.js +237 -0
  29. package/dist/chunk-SR3MPV4D.js.map +1 -0
  30. package/dist/chunk-X2K3GIWE.js +235 -0
  31. package/dist/chunk-X2K3GIWE.js.map +1 -0
  32. package/dist/chunk-ZCUXHW55.cjs +242 -0
  33. package/dist/chunk-ZCUXHW55.cjs.map +1 -0
  34. package/dist/element-browser.js +883 -492
  35. package/dist/element-browser.js.map +1 -1
  36. package/dist/element.cjs +88 -6
  37. package/dist/element.cjs.map +1 -1
  38. package/dist/element.d.cts +51 -1
  39. package/dist/element.d.ts +51 -1
  40. package/dist/element.js +87 -5
  41. package/dist/element.js.map +1 -1
  42. package/dist/index.cjs +523 -393
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +2 -2
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +494 -366
  47. package/dist/index.js.map +1 -1
  48. package/dist/libav-demux-H2GS46GH.cjs +27 -0
  49. package/dist/libav-demux-H2GS46GH.cjs.map +1 -0
  50. package/dist/libav-demux-OWZ4T2YW.js +6 -0
  51. package/dist/libav-demux-OWZ4T2YW.js.map +1 -0
  52. package/dist/{libav-import-GST2AMPL.cjs → libav-import-2ZVKV2E7.cjs} +2 -2
  53. package/dist/{libav-import-GST2AMPL.cjs.map → libav-import-2ZVKV2E7.cjs.map} +1 -1
  54. package/dist/{libav-import-2JURFHEW.js → libav-import-6MGLCXVQ.js} +2 -2
  55. package/dist/{libav-import-2JURFHEW.js.map → libav-import-6MGLCXVQ.js.map} +1 -1
  56. package/dist/{player-B6WB74RD.d.ts → player-DGXeCNfD.d.cts} +41 -1
  57. package/dist/{player-B6WB74RD.d.cts → player-DGXeCNfD.d.ts} +41 -1
  58. package/dist/player.cjs +731 -472
  59. package/dist/player.cjs.map +1 -1
  60. package/dist/player.d.cts +229 -120
  61. package/dist/player.d.ts +229 -120
  62. package/dist/player.js +710 -451
  63. package/dist/player.js.map +1 -1
  64. package/dist/remux-OBSMIENG.cjs +35 -0
  65. package/dist/remux-OBSMIENG.cjs.map +1 -0
  66. package/dist/remux-WBYIZBBX.js +10 -0
  67. package/dist/remux-WBYIZBBX.js.map +1 -0
  68. package/dist/source-4TZ6KMNV.js +4 -0
  69. package/dist/{source-F656KYYV.js.map → source-4TZ6KMNV.js.map} +1 -1
  70. package/dist/source-7YLO6E7X.cjs +29 -0
  71. package/dist/{source-73CAH6HW.cjs.map → source-7YLO6E7X.cjs.map} +1 -1
  72. package/dist/source-MTX5ELUZ.js +4 -0
  73. package/dist/{source-QJR3OHTW.js.map → source-MTX5ELUZ.js.map} +1 -1
  74. package/dist/source-VFLXLOCN.cjs +29 -0
  75. package/dist/{source-VB74JQ7Z.cjs.map → source-VFLXLOCN.cjs.map} +1 -1
  76. package/dist/subtitles-4T74JRGT.js +4 -0
  77. package/dist/subtitles-4T74JRGT.js.map +1 -0
  78. package/dist/subtitles-QUH4LPI4.cjs +29 -0
  79. package/dist/subtitles-QUH4LPI4.cjs.map +1 -0
  80. package/package.json +1 -1
  81. package/src/convert/remux.ts +1 -35
  82. package/src/convert/transcode-libav.ts +691 -0
  83. package/src/convert/transcode.ts +12 -4
  84. package/src/element/avbridge-player.ts +100 -0
  85. package/src/element/avbridge-video.ts +140 -3
  86. package/src/element/player-styles.ts +12 -0
  87. package/src/errors.ts +6 -0
  88. package/src/player.ts +15 -16
  89. package/src/strategies/fallback/decoder.ts +96 -173
  90. package/src/strategies/fallback/index.ts +46 -2
  91. package/src/strategies/fallback/libav-import.ts +9 -1
  92. package/src/strategies/fallback/video-renderer.ts +107 -0
  93. package/src/strategies/hybrid/decoder.ts +88 -180
  94. package/src/strategies/hybrid/index.ts +35 -2
  95. package/src/strategies/native.ts +6 -3
  96. package/src/strategies/remux/index.ts +14 -2
  97. package/src/strategies/remux/pipeline.ts +72 -12
  98. package/src/subtitles/render.ts +8 -0
  99. package/src/types.ts +32 -0
  100. package/src/util/libav-demux.ts +405 -0
  101. package/src/util/time-ranges.ts +40 -0
  102. package/dist/chunk-2PGRFCWB.js.map +0 -1
  103. package/dist/chunk-6UUT4BEA.cjs.map +0 -1
  104. package/dist/chunk-7RGG6ME7.cjs.map +0 -1
  105. package/dist/chunk-NV7ILLWH.js.map +0 -1
  106. package/dist/chunk-QQXBPW72.js.map +0 -1
  107. package/dist/chunk-XKPSTC34.cjs.map +0 -1
  108. package/dist/source-73CAH6HW.cjs +0 -28
  109. package/dist/source-F656KYYV.js +0 -3
  110. package/dist/source-QJR3OHTW.js +0 -3
  111. package/dist/source-VB74JQ7Z.cjs +0 -28
@@ -1,241 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunk6UUT4BEA_cjs = require('./chunk-6UUT4BEA.cjs');
3
+ var chunkS4WAZC2T_cjs = require('./chunk-S4WAZC2T.cjs');
4
+ var chunkZCUXHW55_cjs = require('./chunk-ZCUXHW55.cjs');
5
+ var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
6
+ var chunkCPZ7PXAM_cjs = require('./chunk-CPZ7PXAM.cjs');
4
7
  var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
5
8
  var chunkF3LQJKXK_cjs = require('./chunk-F3LQJKXK.cjs');
6
9
 
7
- // src/probe/mediabunny.ts
8
- async function probeWithMediabunny(source, sniffedContainer) {
9
- const mb = await import('mediabunny');
10
- const input = new mb.Input({
11
- source: await buildMediabunnySource(mb, source),
12
- formats: mb.ALL_FORMATS
13
- });
14
- const allTracks = await input.getTracks();
15
- const duration = await safeNumber(() => input.computeDuration());
16
- const videoTracks = [];
17
- const audioTracks = [];
18
- for (const track of allTracks) {
19
- if (track.isVideoTrack()) {
20
- const codecParam = await safe(() => track.getCodecParameterString());
21
- videoTracks.push({
22
- id: track.id,
23
- codec: mediabunnyVideoToAvbridge(track.codec),
24
- width: track.displayWidth ?? track.codedWidth ?? 0,
25
- height: track.displayHeight ?? track.codedHeight ?? 0,
26
- codecString: codecParam ?? void 0
27
- });
28
- } else if (track.isAudioTrack()) {
29
- const codecParam = await safe(() => track.getCodecParameterString());
30
- audioTracks.push({
31
- id: track.id,
32
- codec: mediabunnyAudioToAvbridge(track.codec),
33
- channels: track.numberOfChannels ?? 0,
34
- sampleRate: track.sampleRate ?? 0,
35
- language: track.languageCode,
36
- codecString: codecParam ?? void 0
37
- });
38
- }
39
- }
40
- const format = await safe(() => input.getFormat());
41
- const container = resolveContainer(format?.name, sniffedContainer);
42
- return {
43
- source: source.original,
44
- name: source.name,
45
- byteLength: source.byteLength,
46
- container,
47
- videoTracks,
48
- audioTracks,
49
- subtitleTracks: [],
50
- probedBy: "mediabunny",
51
- duration
52
- };
53
- }
54
- async function buildMediabunnySource(mb, source) {
55
- if (source.kind === "url") {
56
- return new mb.UrlSource(source.url);
57
- }
58
- return new mb.BlobSource(source.blob);
59
- }
60
- async function buildMediabunnySourceFromInput(mb, source) {
61
- if (typeof source === "string") return new mb.UrlSource(source);
62
- if (source instanceof URL) return new mb.UrlSource(source.toString());
63
- if (source instanceof Blob) return new mb.BlobSource(source);
64
- if (source instanceof ArrayBuffer) return new mb.BlobSource(new Blob([source]));
65
- if (source instanceof Uint8Array) return new mb.BlobSource(new Blob([source]));
66
- throw new TypeError("unsupported source type for mediabunny");
67
- }
68
- function resolveContainer(formatName, sniffed) {
69
- const name = (formatName ?? "").toLowerCase();
70
- if (name.includes("matroska") || name.includes("mkv")) return "mkv";
71
- if (name.includes("webm")) return "webm";
72
- if (name.includes("mp4") || name.includes("isom")) return "mp4";
73
- if (name.includes("mov") || name.includes("quicktime")) return "mov";
74
- if (name.includes("ogg")) return "ogg";
75
- if (name.includes("wav")) return "wav";
76
- if (name.includes("flac")) return "flac";
77
- if (name.includes("mp3")) return "mp3";
78
- if (name.includes("adts") || name.includes("aac")) return "adts";
79
- if (name.includes("mpegts") || name.includes("mpeg-ts") || name.includes("transport")) return "mpegts";
80
- return sniffed;
81
- }
82
- function mediabunnyVideoToAvbridge(c) {
83
- switch (c) {
84
- case "avc":
85
- return "h264";
86
- case "hevc":
87
- return "h265";
88
- case "vp8":
89
- return "vp8";
90
- case "vp9":
91
- return "vp9";
92
- case "av1":
93
- return "av1";
94
- default:
95
- return c ? c : "unknown";
96
- }
97
- }
98
- function avbridgeVideoToMediabunny(c) {
99
- switch (c) {
100
- case "h264":
101
- return "avc";
102
- case "h265":
103
- return "hevc";
104
- case "vp8":
105
- return "vp8";
106
- case "vp9":
107
- return "vp9";
108
- case "av1":
109
- return "av1";
110
- default:
111
- return null;
112
- }
113
- }
114
- function mediabunnyAudioToAvbridge(c) {
115
- switch (c) {
116
- case "aac":
117
- return "aac";
118
- case "mp3":
119
- return "mp3";
120
- case "opus":
121
- return "opus";
122
- case "vorbis":
123
- return "vorbis";
124
- case "flac":
125
- return "flac";
126
- case "ac3":
127
- return "ac3";
128
- case "eac3":
129
- return "eac3";
130
- default:
131
- return c ? c : "unknown";
132
- }
133
- }
134
- function avbridgeAudioToMediabunny(c) {
135
- switch (c) {
136
- case "aac":
137
- return "aac";
138
- case "mp3":
139
- return "mp3";
140
- case "opus":
141
- return "opus";
142
- case "vorbis":
143
- return "vorbis";
144
- case "flac":
145
- return "flac";
146
- case "ac3":
147
- return "ac3";
148
- case "eac3":
149
- return "eac3";
150
- default:
151
- return null;
152
- }
153
- }
154
- async function safeNumber(fn) {
155
- try {
156
- const v = await fn();
157
- return typeof v === "number" && Number.isFinite(v) ? v : void 0;
158
- } catch {
159
- return void 0;
160
- }
161
- }
162
- async function safe(fn) {
163
- try {
164
- return await fn();
165
- } catch {
166
- return void 0;
167
- }
168
- }
169
-
170
- // src/probe/index.ts
171
- var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
172
- "mp4",
173
- "mov",
174
- "mkv",
175
- "webm",
176
- "ogg",
177
- "wav",
178
- "mp3",
179
- "flac",
180
- "adts",
181
- "mpegts"
182
- ]);
183
- async function probe(source, transport) {
184
- const normalized = await chunk6UUT4BEA_cjs.normalizeSource(source, transport);
185
- const sniffed = await chunk6UUT4BEA_cjs.sniffNormalizedSource(normalized);
186
- if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
187
- try {
188
- const result = await probeWithMediabunny(normalized, sniffed);
189
- const hasUnknownCodec = result.videoTracks.some((t) => t.codec === "unknown") || result.audioTracks.some((t) => t.codec === "unknown");
190
- if (hasUnknownCodec) {
191
- try {
192
- const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
193
- return await probeWithLibav(normalized, sniffed);
194
- } catch {
195
- return result;
196
- }
197
- }
198
- return result;
199
- } catch (mediabunnyErr) {
200
- console.warn(
201
- `[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,
202
- mediabunnyErr.message
203
- );
204
- try {
205
- const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
206
- return await probeWithLibav(normalized, sniffed);
207
- } catch (libavErr) {
208
- const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
209
- const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);
210
- throw new chunk6UUT4BEA_cjs.AvbridgeError(
211
- chunk6UUT4BEA_cjs.ERR_PROBE_FAILED,
212
- `Failed to probe ${sniffed.toUpperCase()} file. mediabunny: ${mbMsg}. libav: ${lvMsg}.`,
213
- "The file may be corrupt, truncated, or in an unsupported format. Enable AVBRIDGE_DEBUG for detailed logs."
214
- );
215
- }
216
- }
217
- }
218
- try {
219
- const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
220
- return await probeWithLibav(normalized, sniffed);
221
- } catch (err) {
222
- const inner = err instanceof Error ? err.message : String(err);
223
- console.error("[avbridge] libav probe failed for", sniffed, "file:", err);
224
- if (sniffed === "unknown") {
225
- throw new chunk6UUT4BEA_cjs.AvbridgeError(
226
- chunk6UUT4BEA_cjs.ERR_PROBE_UNKNOWN_CONTAINER,
227
- `Unable to probe source: container format could not be identified. libav fallback: ${inner || "(no details)"}`,
228
- "The file may be corrupt or in a format avbridge doesn't recognize. Check the file plays in VLC or ffprobe."
229
- );
230
- }
231
- throw new chunk6UUT4BEA_cjs.AvbridgeError(
232
- chunk6UUT4BEA_cjs.ERR_LIBAV_NOT_REACHABLE,
233
- `${sniffed.toUpperCase()} files require libav.js, which failed to load: ${inner || "(no details)"}`,
234
- "Install @libav.js/variant-webcodecs, or check that AVBRIDGE_LIBAV_BASE points to the correct path."
235
- );
236
- }
237
- }
238
-
239
10
  // src/util/codec-strings.ts
240
11
  function videoCodecString(track) {
241
12
  if (track.codecString) return track.codecString;
@@ -485,36 +256,6 @@ function isRiskyNative(video) {
485
256
  return false;
486
257
  }
487
258
 
488
- // src/subtitles/srt.ts
489
- function srtToVtt(srt) {
490
- if (srt.charCodeAt(0) === 65279) srt = srt.slice(1);
491
- const normalized = srt.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
492
- const blocks = normalized.split(/\n{2,}/);
493
- const out = ["WEBVTT", ""];
494
- for (const block of blocks) {
495
- const lines = block.split("\n");
496
- if (lines.length > 0 && /^\d+$/.test(lines[0].trim())) {
497
- lines.shift();
498
- }
499
- if (lines.length === 0) continue;
500
- const timing = lines.shift();
501
- const vttTiming = convertTiming(timing);
502
- if (!vttTiming) continue;
503
- out.push(vttTiming);
504
- for (const l of lines) out.push(l);
505
- out.push("");
506
- }
507
- return out.join("\n");
508
- }
509
- function convertTiming(line) {
510
- const m = /^(\d{1,2}):(\d{2}):(\d{2})[,.](\d{1,3})\s*-->\s*(\d{1,2}):(\d{2}):(\d{2})[,.](\d{1,3})(.*)$/.exec(
511
- line.trim()
512
- );
513
- if (!m) return null;
514
- const fmt = (h, mm, s, ms) => `${h.padStart(2, "0")}:${mm}:${s}.${ms.padEnd(3, "0").slice(0, 3)}`;
515
- return `${fmt(m[1], m[2], m[3], m[4])} --> ${fmt(m[5], m[6], m[7], m[8])}${m[9] ?? ""}`;
516
- }
517
-
518
259
  // src/events.ts
519
260
  var TypedEmitter = class {
520
261
  listeners = {};
@@ -720,7 +461,7 @@ async function createNativeSession(context, video) {
720
461
  },
721
462
  async setAudioTrack(id) {
722
463
  const tracks = video.audioTracks;
723
- if (!tracks) return;
464
+ if (!tracks || tracks.length === 0) return;
724
465
  for (let i = 0; i < tracks.length; i++) {
725
466
  tracks[i].enabled = tracks[i].id === String(id) || i === id;
726
467
  }
@@ -772,15 +513,15 @@ var MseSink = class {
772
513
  constructor(options) {
773
514
  this.options = options;
774
515
  if (typeof MediaSource === "undefined") {
775
- throw new chunk6UUT4BEA_cjs.AvbridgeError(
776
- chunk6UUT4BEA_cjs.ERR_MSE_NOT_SUPPORTED,
516
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
517
+ chunk2IJ66NTD_cjs.ERR_MSE_NOT_SUPPORTED,
777
518
  "MediaSource Extensions (MSE) are not supported in this environment.",
778
519
  "MSE is required for the remux strategy. Use a browser that supports MSE, or try the fallback strategy."
779
520
  );
780
521
  }
781
522
  if (!MediaSource.isTypeSupported(options.mime)) {
782
- throw new chunk6UUT4BEA_cjs.AvbridgeError(
783
- chunk6UUT4BEA_cjs.ERR_MSE_CODEC_NOT_SUPPORTED,
523
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
524
+ chunk2IJ66NTD_cjs.ERR_MSE_CODEC_NOT_SUPPORTED,
784
525
  `This browser's MSE does not support "${options.mime}".`,
785
526
  "The codec combination can't be played via remux in this browser. The player will try the next strategy automatically."
786
527
  );
@@ -968,30 +709,49 @@ var MseSink = class {
968
709
  async function createRemuxPipeline(ctx, video) {
969
710
  const mb = await import('mediabunny');
970
711
  const videoTrackInfo = ctx.videoTracks[0];
971
- const audioTrackInfo = ctx.audioTracks[0];
972
712
  if (!videoTrackInfo) throw new Error("remux: source has no video track");
973
- const mbVideoCodec = avbridgeVideoToMediabunny(videoTrackInfo.codec);
713
+ const mbVideoCodec = chunkZCUXHW55_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec);
974
714
  if (!mbVideoCodec) {
975
715
  throw new Error(`remux: video codec "${videoTrackInfo.codec}" is not supported by mediabunny output`);
976
716
  }
977
- const mbAudioCodec = audioTrackInfo ? avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
978
717
  const input = new mb.Input({
979
- source: await buildMediabunnySourceFromInput(mb, ctx.source),
718
+ source: await chunkZCUXHW55_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
980
719
  formats: mb.ALL_FORMATS
981
720
  });
982
721
  const allTracks = await input.getTracks();
983
722
  const inputVideo = allTracks.find((t) => t.id === videoTrackInfo.id && t.isVideoTrack());
984
- const inputAudio = audioTrackInfo ? allTracks.find((t) => t.id === audioTrackInfo.id && t.isAudioTrack()) : null;
985
723
  if (!inputVideo || !inputVideo.isVideoTrack()) {
986
724
  throw new Error("remux: video track not found in input");
987
725
  }
988
- if (audioTrackInfo && (!inputAudio || !inputAudio.isAudioTrack())) {
989
- throw new Error("remux: audio track not found in input");
990
- }
991
726
  const videoConfig = await inputVideo.getDecoderConfig();
992
- const audioConfig = inputAudio && inputAudio.isAudioTrack() ? await inputAudio.getDecoderConfig() : null;
993
727
  const videoSink = new mb.EncodedPacketSink(inputVideo);
994
- const audioSink = inputAudio?.isAudioTrack() ? new mb.EncodedPacketSink(inputAudio) : null;
728
+ let selectedAudioTrackId = ctx.audioTracks[0]?.id ?? null;
729
+ let inputAudio = null;
730
+ let mbAudioCodec = null;
731
+ let audioSink = null;
732
+ let audioConfig = null;
733
+ async function rebuildAudio() {
734
+ if (selectedAudioTrackId == null) {
735
+ inputAudio = null;
736
+ mbAudioCodec = null;
737
+ audioSink = null;
738
+ audioConfig = null;
739
+ return;
740
+ }
741
+ const trackInfo = ctx.audioTracks.find((t) => t.id === selectedAudioTrackId);
742
+ if (!trackInfo) {
743
+ throw new Error(`remux: no audio track with id ${selectedAudioTrackId}`);
744
+ }
745
+ const newInput = allTracks.find((t) => t.id === trackInfo.id && t.isAudioTrack());
746
+ if (!newInput || !newInput.isAudioTrack()) {
747
+ throw new Error("remux: audio track not found in input");
748
+ }
749
+ inputAudio = newInput;
750
+ mbAudioCodec = chunkZCUXHW55_cjs.avbridgeAudioToMediabunny(trackInfo.codec);
751
+ audioSink = new mb.EncodedPacketSink(newInput);
752
+ audioConfig = await newInput.getDecoderConfig();
753
+ }
754
+ await rebuildAudio();
995
755
  let sink = null;
996
756
  const stats = { videoPackets: 0, audioPackets: 0, bytesWritten: 0, fragments: 0 };
997
757
  let destroyed = false;
@@ -1116,6 +876,30 @@ async function createRemuxPipeline(ctx, video) {
1116
876
  pendingAutoPlay = autoPlay;
1117
877
  if (sink) sink.setPlayOnSeek(autoPlay);
1118
878
  },
879
+ async setAudioTrack(trackId, time, autoPlay) {
880
+ if (selectedAudioTrackId === trackId) return;
881
+ if (!ctx.audioTracks.some((t) => t.id === trackId)) {
882
+ console.warn("[avbridge] remux: setAudioTrack \u2014 unknown track id", trackId);
883
+ return;
884
+ }
885
+ pumpToken++;
886
+ selectedAudioTrackId = trackId;
887
+ await rebuildAudio().catch((err) => {
888
+ console.warn("[avbridge] remux: rebuildAudio failed:", err.message);
889
+ });
890
+ if (sink) {
891
+ try {
892
+ sink.destroy();
893
+ } catch {
894
+ }
895
+ sink = null;
896
+ }
897
+ pendingAutoPlay = autoPlay;
898
+ pendingStartTime = time;
899
+ pumpLoop(++pumpToken, time).catch((err) => {
900
+ console.error("[avbridge] remux pipeline setAudioTrack pump failed:", err);
901
+ });
902
+ },
1119
903
  async destroy() {
1120
904
  destroyed = true;
1121
905
  pumpToken++;
@@ -1175,7 +959,19 @@ async function createRemuxSession(context, video) {
1175
959
  const wasPlaying = !video.paused;
1176
960
  await pipeline.seek(time, wasPlaying || wantPlay);
1177
961
  },
1178
- async setAudioTrack(_id) {
962
+ async setAudioTrack(id) {
963
+ if (!context.audioTracks.some((t) => t.id === id)) {
964
+ console.warn("[avbridge] remux: setAudioTrack \u2014 unknown track id", id);
965
+ return;
966
+ }
967
+ const wasPlaying = !video.paused;
968
+ const time = video.currentTime || 0;
969
+ if (!started) {
970
+ started = true;
971
+ await pipeline.setAudioTrack(id, time, wantPlay || wasPlaying);
972
+ return;
973
+ }
974
+ await pipeline.setAudioTrack(id, time, wasPlaying || wantPlay);
1179
975
  },
1180
976
  async setSubtitleTrack(id) {
1181
977
  const tracks = video.textTracks;
@@ -1228,6 +1024,9 @@ var VideoRenderer = class {
1228
1024
  document.body.appendChild(this.canvas);
1229
1025
  }
1230
1026
  target.style.visibility = "hidden";
1027
+ const overlayParent = parent instanceof HTMLElement ? parent : document.body;
1028
+ this.subtitleOverlay = new chunkS4WAZC2T_cjs.SubtitleOverlay(overlayParent);
1029
+ this.watchTextTracks(target);
1231
1030
  const ctx = this.canvas.getContext("2d");
1232
1031
  if (!ctx) throw new Error("video renderer: failed to acquire 2D context");
1233
1032
  this.ctx = ctx;
@@ -1253,6 +1052,15 @@ var VideoRenderer = class {
1253
1052
  ticksWaiting = 0;
1254
1053
  /** Cumulative count of ticks where PTS mode painted a frame. */
1255
1054
  ticksPainted = 0;
1055
+ /**
1056
+ * Subtitle overlay div attached to the stage wrapper alongside the
1057
+ * canvas. Created lazily when subtitle tracks are attached via the
1058
+ * target's `<track>` children. Canvas strategies (hybrid, fallback)
1059
+ * hide the <video>, so we can't rely on the browser's native cue
1060
+ * rendering; we read TextTrack.cues and render into this overlay.
1061
+ */
1062
+ subtitleOverlay = null;
1063
+ subtitleTrack = null;
1256
1064
  /**
1257
1065
  * Calibration offset (microseconds) between video PTS and audio clock.
1258
1066
  * Video PTS and AudioContext.currentTime can drift ~0.1% relative to
@@ -1296,9 +1104,80 @@ var VideoRenderer = class {
1296
1104
  this.framesDroppedOverflow++;
1297
1105
  }
1298
1106
  }
1107
+ /**
1108
+ * Watch the target <video>'s textTracks list. When a track is added,
1109
+ * grab it and start polling cues on each render tick. Existing tracks
1110
+ * (if any) are picked up immediately.
1111
+ */
1112
+ watchTextTracks(target) {
1113
+ const pick = () => {
1114
+ if (this.subtitleTrack) return;
1115
+ const tracks = target.textTracks;
1116
+ if (isDebug()) {
1117
+ console.log(`[avbridge:subs] watchTextTracks pick() \u2014 ${tracks.length} tracks`);
1118
+ }
1119
+ for (let i = 0; i < tracks.length; i++) {
1120
+ const t = tracks[i];
1121
+ if (isDebug()) {
1122
+ console.log(`[avbridge:subs] track ${i}: kind=${t.kind} mode=${t.mode} cues=${t.cues?.length ?? 0}`);
1123
+ }
1124
+ if (t.kind === "subtitles" || t.kind === "captions") {
1125
+ this.subtitleTrack = t;
1126
+ t.mode = "hidden";
1127
+ if (isDebug()) {
1128
+ console.log(`[avbridge:subs] picked track, mode=hidden`);
1129
+ }
1130
+ const trackEl = target.querySelector(`track[srclang="${t.language}"]`);
1131
+ if (trackEl) {
1132
+ trackEl.addEventListener("load", () => {
1133
+ if (isDebug()) {
1134
+ console.log(`[avbridge:subs] track element loaded, cues=${t.cues?.length ?? 0}`);
1135
+ }
1136
+ });
1137
+ trackEl.addEventListener("error", (ev) => {
1138
+ console.warn(`[avbridge:subs] track element error:`, ev);
1139
+ });
1140
+ }
1141
+ break;
1142
+ }
1143
+ }
1144
+ };
1145
+ pick();
1146
+ if (typeof target.textTracks.addEventListener === "function") {
1147
+ target.textTracks.addEventListener("addtrack", (e) => {
1148
+ if (isDebug()) {
1149
+ console.log("[avbridge:subs] addtrack event fired");
1150
+ }
1151
+ pick();
1152
+ });
1153
+ }
1154
+ }
1155
+ _loggedCues = false;
1156
+ /** Find the active cue (if any) for the given media time. */
1157
+ updateSubtitles() {
1158
+ if (!this.subtitleOverlay || !this.subtitleTrack) return;
1159
+ const cues = this.subtitleTrack.cues;
1160
+ if (!cues || cues.length === 0) return;
1161
+ if (isDebug() && !this._loggedCues) {
1162
+ this._loggedCues = true;
1163
+ console.log(`[avbridge:subs] cues available: ${cues.length}, first start=${cues[0].startTime}, last end=${cues[cues.length - 1].endTime}`);
1164
+ }
1165
+ const t = this.clock.now();
1166
+ let activeText = "";
1167
+ for (let i = 0; i < cues.length; i++) {
1168
+ const c = cues[i];
1169
+ if (t >= c.startTime && t <= c.endTime) {
1170
+ const vttCue = c;
1171
+ activeText = vttCue.text ?? "";
1172
+ break;
1173
+ }
1174
+ }
1175
+ this.subtitleOverlay.setText(activeText.replace(/<[^>]+>/g, ""));
1176
+ }
1299
1177
  tick() {
1300
1178
  if (this.destroyed) return;
1301
1179
  this.rafHandle = requestAnimationFrame(this.tick);
1180
+ this.updateSubtitles();
1302
1181
  if (this.queue.length === 0) return;
1303
1182
  const playing = this.clock.isPlaying();
1304
1183
  if (!playing) {
@@ -1427,6 +1306,11 @@ var VideoRenderer = class {
1427
1306
  this.destroyed = true;
1428
1307
  if (this.rafHandle != null) cancelAnimationFrame(this.rafHandle);
1429
1308
  this.flush();
1309
+ if (this.subtitleOverlay) {
1310
+ this.subtitleOverlay.destroy();
1311
+ this.subtitleOverlay = null;
1312
+ }
1313
+ this.subtitleTrack = null;
1430
1314
  this.canvas.remove();
1431
1315
  this.target.style.visibility = "";
1432
1316
  }
@@ -1676,7 +1560,8 @@ async function startHybridDecoder(opts) {
1676
1560
  const readPkt = await libav.av_packet_alloc();
1677
1561
  const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
1678
1562
  const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
1679
- const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
1563
+ const firstAudioTrackId = opts.context.audioTracks[0]?.id;
1564
+ let audioStream = (firstAudioTrackId != null ? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === firstAudioTrackId) : void 0) ?? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
1680
1565
  if (!videoStream && !audioStream) {
1681
1566
  throw new Error("hybrid decoder: file has no decodable streams");
1682
1567
  }
@@ -1830,7 +1715,7 @@ async function startHybridDecoder(opts) {
1830
1715
  const processed = await applyBSF(videoPackets);
1831
1716
  for (const pkt of processed) {
1832
1717
  if (myToken !== pumpToken || destroyed) return;
1833
- sanitizePacketTimestamp(pkt, () => {
1718
+ chunkCPZ7PXAM_cjs.sanitizePacketTimestamp(pkt, () => {
1834
1719
  const ts = syntheticVideoUs;
1835
1720
  syntheticVideoUs += videoFrameStepUs;
1836
1721
  return ts;
@@ -1909,7 +1794,7 @@ async function startHybridDecoder(opts) {
1909
1794
  const frames = allFrames;
1910
1795
  for (const f of frames) {
1911
1796
  if (myToken !== pumpToken || destroyed) return;
1912
- sanitizeFrameTimestamp(
1797
+ chunkCPZ7PXAM_cjs.sanitizeFrameTimestamp(
1913
1798
  f,
1914
1799
  () => {
1915
1800
  const ts = syntheticAudioUs;
@@ -1920,7 +1805,7 @@ async function startHybridDecoder(opts) {
1920
1805
  },
1921
1806
  audioTimeBase
1922
1807
  );
1923
- const samples = libavFrameToInterleavedFloat32(f);
1808
+ const samples = chunkCPZ7PXAM_cjs.libavFrameToInterleavedFloat32(f);
1924
1809
  if (samples) {
1925
1810
  opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
1926
1811
  audioFramesDecoded++;
@@ -1972,6 +1857,71 @@ async function startHybridDecoder(opts) {
1972
1857
  } catch {
1973
1858
  }
1974
1859
  },
1860
+ async setAudioTrack(trackId, timeSec) {
1861
+ if (audioStream && audioStream.index === trackId) return;
1862
+ const newStream = streams.find(
1863
+ (s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === trackId
1864
+ );
1865
+ if (!newStream) {
1866
+ console.warn("[avbridge] hybrid: setAudioTrack \u2014 no stream with id", trackId);
1867
+ return;
1868
+ }
1869
+ const newToken = ++pumpToken;
1870
+ if (pumpRunning) {
1871
+ try {
1872
+ await pumpRunning;
1873
+ } catch {
1874
+ }
1875
+ }
1876
+ if (destroyed) return;
1877
+ if (audioDec) {
1878
+ try {
1879
+ await libav.ff_free_decoder?.(audioDec.c, audioDec.pkt, audioDec.frame);
1880
+ } catch {
1881
+ }
1882
+ audioDec = null;
1883
+ }
1884
+ try {
1885
+ const [, c, pkt, frame] = await libav.ff_init_decoder(newStream.codec_id, {
1886
+ codecpar: newStream.codecpar
1887
+ });
1888
+ audioDec = { c, pkt, frame };
1889
+ audioTimeBase = newStream.time_base_num && newStream.time_base_den ? [newStream.time_base_num, newStream.time_base_den] : void 0;
1890
+ } catch (err) {
1891
+ console.warn(
1892
+ "[avbridge] hybrid: setAudioTrack init failed \u2014 switching to no-audio:",
1893
+ err.message
1894
+ );
1895
+ audioDec = null;
1896
+ opts.audio.setNoAudio();
1897
+ }
1898
+ audioStream = newStream;
1899
+ try {
1900
+ const tsUs = Math.floor(timeSec * 1e6);
1901
+ const [tsLo, tsHi] = libav.f64toi64 ? libav.f64toi64(tsUs) : [tsUs | 0, Math.floor(tsUs / 4294967296)];
1902
+ await libav.av_seek_frame(
1903
+ fmt_ctx,
1904
+ -1,
1905
+ tsLo,
1906
+ tsHi,
1907
+ libav.AVSEEK_FLAG_BACKWARD ?? 0
1908
+ );
1909
+ } catch (err) {
1910
+ console.warn("[avbridge] hybrid: setAudioTrack seek failed:", err);
1911
+ }
1912
+ try {
1913
+ if (videoDecoder && videoDecoder.state === "configured") {
1914
+ await videoDecoder.flush();
1915
+ }
1916
+ } catch {
1917
+ }
1918
+ await flushBSF();
1919
+ syntheticVideoUs = Math.round(timeSec * 1e6);
1920
+ syntheticAudioUs = Math.round(timeSec * 1e6);
1921
+ pumpRunning = pumpLoop(newToken).catch(
1922
+ (err) => console.error("[avbridge] hybrid pump failed (post-setAudioTrack):", err)
1923
+ );
1924
+ },
1975
1925
  async seek(timeSec) {
1976
1926
  const newToken = ++pumpToken;
1977
1927
  if (pumpRunning) {
@@ -2029,161 +1979,9 @@ async function startHybridDecoder(opts) {
2029
1979
  }
2030
1980
  };
2031
1981
  }
2032
- function sanitizePacketTimestamp(pkt, nextUs, fallbackTimeBase) {
2033
- const lo = pkt.pts ?? 0;
2034
- const hi = pkt.ptshi ?? 0;
2035
- const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
2036
- if (isInvalid) {
2037
- const us2 = nextUs();
2038
- pkt.pts = us2;
2039
- pkt.ptshi = 0;
2040
- pkt.time_base_num = 1;
2041
- pkt.time_base_den = 1e6;
2042
- return;
2043
- }
2044
- const tb = fallbackTimeBase ?? [1, 1e6];
2045
- const pts64 = hi * 4294967296 + lo;
2046
- const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
2047
- if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
2048
- pkt.pts = us;
2049
- pkt.ptshi = us < 0 ? -1 : 0;
2050
- pkt.time_base_num = 1;
2051
- pkt.time_base_den = 1e6;
2052
- return;
2053
- }
2054
- const fallback = nextUs();
2055
- pkt.pts = fallback;
2056
- pkt.ptshi = 0;
2057
- pkt.time_base_num = 1;
2058
- pkt.time_base_den = 1e6;
2059
- }
2060
- function sanitizeFrameTimestamp(frame, nextUs, fallbackTimeBase) {
2061
- const lo = frame.pts ?? 0;
2062
- const hi = frame.ptshi ?? 0;
2063
- const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
2064
- if (isInvalid) {
2065
- const us2 = nextUs();
2066
- frame.pts = us2;
2067
- frame.ptshi = 0;
2068
- return;
2069
- }
2070
- const tb = fallbackTimeBase ?? [1, 1e6];
2071
- const pts64 = hi * 4294967296 + lo;
2072
- const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
2073
- if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
2074
- frame.pts = us;
2075
- frame.ptshi = us < 0 ? -1 : 0;
2076
- return;
2077
- }
2078
- const fallback = nextUs();
2079
- frame.pts = fallback;
2080
- frame.ptshi = 0;
2081
- }
2082
- var AV_SAMPLE_FMT_U8 = 0;
2083
- var AV_SAMPLE_FMT_S16 = 1;
2084
- var AV_SAMPLE_FMT_S32 = 2;
2085
- var AV_SAMPLE_FMT_FLT = 3;
2086
- var AV_SAMPLE_FMT_U8P = 5;
2087
- var AV_SAMPLE_FMT_S16P = 6;
2088
- var AV_SAMPLE_FMT_S32P = 7;
2089
- var AV_SAMPLE_FMT_FLTP = 8;
2090
- function libavFrameToInterleavedFloat32(frame) {
2091
- const channels = frame.channels ?? frame.ch_layout_nb_channels ?? 1;
2092
- const sampleRate = frame.sample_rate ?? 44100;
2093
- const nbSamples = frame.nb_samples ?? 0;
2094
- if (nbSamples === 0) return null;
2095
- const out = new Float32Array(nbSamples * channels);
2096
- switch (frame.format) {
2097
- case AV_SAMPLE_FMT_FLTP: {
2098
- const planes = ensurePlanes(frame.data, channels);
2099
- for (let ch = 0; ch < channels; ch++) {
2100
- const plane = asFloat32(planes[ch]);
2101
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i];
2102
- }
2103
- return { data: out, channels, sampleRate };
2104
- }
2105
- case AV_SAMPLE_FMT_FLT: {
2106
- const flat = asFloat32(frame.data);
2107
- for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i];
2108
- return { data: out, channels, sampleRate };
2109
- }
2110
- case AV_SAMPLE_FMT_S16P: {
2111
- const planes = ensurePlanes(frame.data, channels);
2112
- for (let ch = 0; ch < channels; ch++) {
2113
- const plane = asInt16(planes[ch]);
2114
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 32768;
2115
- }
2116
- return { data: out, channels, sampleRate };
2117
- }
2118
- case AV_SAMPLE_FMT_S16: {
2119
- const flat = asInt16(frame.data);
2120
- for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 32768;
2121
- return { data: out, channels, sampleRate };
2122
- }
2123
- case AV_SAMPLE_FMT_S32P: {
2124
- const planes = ensurePlanes(frame.data, channels);
2125
- for (let ch = 0; ch < channels; ch++) {
2126
- const plane = asInt32(planes[ch]);
2127
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 2147483648;
2128
- }
2129
- return { data: out, channels, sampleRate };
2130
- }
2131
- case AV_SAMPLE_FMT_S32: {
2132
- const flat = asInt32(frame.data);
2133
- for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 2147483648;
2134
- return { data: out, channels, sampleRate };
2135
- }
2136
- case AV_SAMPLE_FMT_U8P: {
2137
- const planes = ensurePlanes(frame.data, channels);
2138
- for (let ch = 0; ch < channels; ch++) {
2139
- const plane = asUint8(planes[ch]);
2140
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = (plane[i] - 128) / 128;
2141
- }
2142
- return { data: out, channels, sampleRate };
2143
- }
2144
- case AV_SAMPLE_FMT_U8: {
2145
- const flat = asUint8(frame.data);
2146
- for (let i = 0; i < nbSamples * channels; i++) out[i] = (flat[i] - 128) / 128;
2147
- return { data: out, channels, sampleRate };
2148
- }
2149
- default:
2150
- return null;
2151
- }
2152
- }
2153
- function ensurePlanes(data, channels) {
2154
- if (Array.isArray(data)) return data;
2155
- const arr = data;
2156
- const len = arr.length;
2157
- const perChannel = Math.floor(len / channels);
2158
- const planes = [];
2159
- for (let ch = 0; ch < channels; ch++) {
2160
- planes.push(arr.subarray ? arr.subarray(ch * perChannel, (ch + 1) * perChannel) : arr);
2161
- }
2162
- return planes;
2163
- }
2164
- function asFloat32(x) {
2165
- if (x instanceof Float32Array) return x;
2166
- const ta = x;
2167
- return new Float32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
2168
- }
2169
- function asInt16(x) {
2170
- if (x instanceof Int16Array) return x;
2171
- const ta = x;
2172
- return new Int16Array(ta.buffer, ta.byteOffset, ta.byteLength / 2);
2173
- }
2174
- function asInt32(x) {
2175
- if (x instanceof Int32Array) return x;
2176
- const ta = x;
2177
- return new Int32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
2178
- }
2179
- function asUint8(x) {
2180
- if (x instanceof Uint8Array) return x;
2181
- const ta = x;
2182
- return new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
2183
- }
2184
1982
  async function loadBridge() {
2185
1983
  try {
2186
- const wrapper = await import('./libav-import-GST2AMPL.cjs');
1984
+ const wrapper = await import('./libav-import-2ZVKV2E7.cjs');
2187
1985
  return wrapper.libavBridge;
2188
1986
  } catch (err) {
2189
1987
  throw new Error(
@@ -2192,12 +1990,41 @@ async function loadBridge() {
2192
1990
  }
2193
1991
  }
2194
1992
 
1993
+ // src/util/time-ranges.ts
1994
+ function makeTimeRanges(ranges) {
1995
+ const frozen = ranges.slice();
1996
+ const impl = {
1997
+ get length() {
1998
+ return frozen.length;
1999
+ },
2000
+ start(index) {
2001
+ if (index < 0 || index >= frozen.length) {
2002
+ throw new DOMException(
2003
+ `TimeRanges.start: index ${index} out of range (length=${frozen.length})`,
2004
+ "IndexSizeError"
2005
+ );
2006
+ }
2007
+ return frozen[index][0];
2008
+ },
2009
+ end(index) {
2010
+ if (index < 0 || index >= frozen.length) {
2011
+ throw new DOMException(
2012
+ `TimeRanges.end: index ${index} out of range (length=${frozen.length})`,
2013
+ "IndexSizeError"
2014
+ );
2015
+ }
2016
+ return frozen[index][1];
2017
+ }
2018
+ };
2019
+ return impl;
2020
+ }
2021
+
2195
2022
  // src/strategies/hybrid/index.ts
2196
2023
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
2197
2024
  var READY_TIMEOUT_SECONDS = 10;
2198
2025
  async function createHybridSession(ctx, target, transport) {
2199
- const { normalizeSource: normalizeSource2 } = await import('./source-73CAH6HW.cjs');
2200
- const source = await normalizeSource2(ctx.source);
2026
+ const { normalizeSource } = await import('./source-VFLXLOCN.cjs');
2027
+ const source = await normalizeSource(ctx.source);
2201
2028
  const fps = ctx.videoTracks[0]?.fps ?? 30;
2202
2029
  const audio = new AudioOutput();
2203
2030
  const renderer = new VideoRenderer(target, audio, fps);
@@ -2249,6 +2076,18 @@ async function createHybridSession(ctx, target, transport) {
2249
2076
  get: () => ctx.duration ?? NaN
2250
2077
  });
2251
2078
  }
2079
+ Object.defineProperty(target, "readyState", {
2080
+ configurable: true,
2081
+ get: () => {
2082
+ if (!renderer.hasFrames()) return 0;
2083
+ if (!audio.isPlaying() && audio.bufferAhead() <= 0 && !audio.isNoAudio()) return 1;
2084
+ return 2;
2085
+ }
2086
+ });
2087
+ Object.defineProperty(target, "seekable", {
2088
+ configurable: true,
2089
+ get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2090
+ });
2252
2091
  async function waitForBuffer() {
2253
2092
  const start = performance.now();
2254
2093
  while (true) {
@@ -2293,7 +2132,24 @@ async function createHybridSession(ctx, target, transport) {
2293
2132
  async seek(time) {
2294
2133
  await doSeek(time);
2295
2134
  },
2296
- async setAudioTrack(_id) {
2135
+ async setAudioTrack(id) {
2136
+ if (!ctx.audioTracks.some((t) => t.id === id)) {
2137
+ console.warn("[avbridge] hybrid: setAudioTrack \u2014 unknown track id", id);
2138
+ return;
2139
+ }
2140
+ const wasPlaying = audio.isPlaying();
2141
+ const currentTime = audio.now();
2142
+ await audio.pause().catch(() => {
2143
+ });
2144
+ await handles.setAudioTrack(id, currentTime).catch(
2145
+ (err) => console.warn("[avbridge] hybrid: handles.setAudioTrack failed:", err)
2146
+ );
2147
+ await audio.reset(currentTime);
2148
+ renderer.flush();
2149
+ if (wasPlaying) {
2150
+ await waitForBuffer();
2151
+ await audio.start();
2152
+ }
2297
2153
  },
2298
2154
  async setSubtitleTrack(_id) {
2299
2155
  },
@@ -2313,6 +2169,8 @@ async function createHybridSession(ctx, target, transport) {
2313
2169
  delete target.paused;
2314
2170
  delete target.volume;
2315
2171
  delete target.muted;
2172
+ delete target.readyState;
2173
+ delete target.seekable;
2316
2174
  } catch {
2317
2175
  }
2318
2176
  },
@@ -2332,7 +2190,8 @@ async function startDecoder(opts) {
2332
2190
  const readPkt = await libav.av_packet_alloc();
2333
2191
  const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
2334
2192
  const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
2335
- const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
2193
+ const firstAudioTrackId = opts.context.audioTracks[0]?.id;
2194
+ let audioStream = (firstAudioTrackId != null ? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === firstAudioTrackId) : void 0) ?? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
2336
2195
  if (!videoStream && !audioStream) {
2337
2196
  throw new Error("fallback decoder: file has no decodable streams");
2338
2197
  }
@@ -2548,7 +2407,7 @@ async function startDecoder(opts) {
2548
2407
  if (myToken !== pumpToken || destroyed) return;
2549
2408
  for (const f of frames) {
2550
2409
  if (myToken !== pumpToken || destroyed) return;
2551
- const bridgeOpts = sanitizeFrameTimestamp2(
2410
+ chunkCPZ7PXAM_cjs.sanitizeFrameTimestamp(
2552
2411
  f,
2553
2412
  () => {
2554
2413
  const ts = syntheticVideoUs;
@@ -2558,7 +2417,7 @@ async function startDecoder(opts) {
2558
2417
  videoTimeBase
2559
2418
  );
2560
2419
  try {
2561
- const vf = bridge.laFrameToVideoFrame(f, bridgeOpts);
2420
+ const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
2562
2421
  opts.renderer.enqueue(vf);
2563
2422
  videoFramesDecoded++;
2564
2423
  } catch (err) {
@@ -2586,7 +2445,7 @@ async function startDecoder(opts) {
2586
2445
  if (myToken !== pumpToken || destroyed) return;
2587
2446
  for (const f of frames) {
2588
2447
  if (myToken !== pumpToken || destroyed) return;
2589
- sanitizeFrameTimestamp2(
2448
+ chunkCPZ7PXAM_cjs.sanitizeFrameTimestamp(
2590
2449
  f,
2591
2450
  () => {
2592
2451
  const ts = syntheticAudioUs;
@@ -2597,7 +2456,7 @@ async function startDecoder(opts) {
2597
2456
  },
2598
2457
  audioTimeBase
2599
2458
  );
2600
- const samples = libavFrameToInterleavedFloat322(f);
2459
+ const samples = chunkCPZ7PXAM_cjs.libavFrameToInterleavedFloat32(f);
2601
2460
  if (samples) {
2602
2461
  opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
2603
2462
  audioFramesDecoded++;
@@ -2645,6 +2504,69 @@ async function startDecoder(opts) {
2645
2504
  } catch {
2646
2505
  }
2647
2506
  },
2507
+ async setAudioTrack(trackId, timeSec) {
2508
+ if (audioStream && audioStream.index === trackId) return;
2509
+ const newStream = streams.find(
2510
+ (s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === trackId
2511
+ );
2512
+ if (!newStream) {
2513
+ console.warn("[avbridge] fallback: setAudioTrack \u2014 no stream with id", trackId);
2514
+ return;
2515
+ }
2516
+ const newToken = ++pumpToken;
2517
+ if (pumpRunning) {
2518
+ try {
2519
+ await pumpRunning;
2520
+ } catch {
2521
+ }
2522
+ }
2523
+ if (destroyed) return;
2524
+ if (audioDec) {
2525
+ try {
2526
+ await libav.ff_free_decoder?.(audioDec.c, audioDec.pkt, audioDec.frame);
2527
+ } catch {
2528
+ }
2529
+ audioDec = null;
2530
+ }
2531
+ try {
2532
+ const [, c, pkt, frame] = await libav.ff_init_decoder(newStream.codec_id, {
2533
+ codecpar: newStream.codecpar
2534
+ });
2535
+ audioDec = { c, pkt, frame };
2536
+ audioTimeBase = newStream.time_base_num && newStream.time_base_den ? [newStream.time_base_num, newStream.time_base_den] : void 0;
2537
+ } catch (err) {
2538
+ console.warn(
2539
+ "[avbridge] fallback: setAudioTrack init failed \u2014 falling back to no-audio mode:",
2540
+ err.message
2541
+ );
2542
+ audioDec = null;
2543
+ opts.audio.setNoAudio();
2544
+ }
2545
+ audioStream = newStream;
2546
+ try {
2547
+ const tsUs = Math.floor(timeSec * 1e6);
2548
+ const [tsLo, tsHi] = libav.f64toi64 ? libav.f64toi64(tsUs) : [tsUs | 0, Math.floor(tsUs / 4294967296)];
2549
+ await libav.av_seek_frame(
2550
+ fmt_ctx,
2551
+ -1,
2552
+ tsLo,
2553
+ tsHi,
2554
+ libav.AVSEEK_FLAG_BACKWARD ?? 0
2555
+ );
2556
+ } catch (err) {
2557
+ console.warn("[avbridge] fallback: setAudioTrack seek failed:", err);
2558
+ }
2559
+ try {
2560
+ if (videoDec) await libav.avcodec_flush_buffers?.(videoDec.c);
2561
+ } catch {
2562
+ }
2563
+ await flushBSF();
2564
+ syntheticVideoUs = Math.round(timeSec * 1e6);
2565
+ syntheticAudioUs = Math.round(timeSec * 1e6);
2566
+ pumpRunning = pumpLoop(newToken).catch(
2567
+ (err) => console.error("[avbridge] fallback pump failed (post-setAudioTrack):", err)
2568
+ );
2569
+ },
2648
2570
  async seek(timeSec) {
2649
2571
  const newToken = ++pumpToken;
2650
2572
  if (pumpRunning) {
@@ -2701,138 +2623,9 @@ async function startDecoder(opts) {
2701
2623
  }
2702
2624
  };
2703
2625
  }
2704
- function sanitizeFrameTimestamp2(frame, nextUs, fallbackTimeBase) {
2705
- const lo = frame.pts ?? 0;
2706
- const hi = frame.ptshi ?? 0;
2707
- const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
2708
- if (isInvalid) {
2709
- const us2 = nextUs();
2710
- frame.pts = us2;
2711
- frame.ptshi = 0;
2712
- return { timeBase: [1, 1e6] };
2713
- }
2714
- const tb = fallbackTimeBase ?? [1, 1e6];
2715
- const pts64 = hi * 4294967296 + lo;
2716
- const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
2717
- if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
2718
- frame.pts = us;
2719
- frame.ptshi = us < 0 ? -1 : 0;
2720
- return { timeBase: [1, 1e6] };
2721
- }
2722
- const fallback = nextUs();
2723
- frame.pts = fallback;
2724
- frame.ptshi = 0;
2725
- return { timeBase: [1, 1e6] };
2726
- }
2727
- var AV_SAMPLE_FMT_U82 = 0;
2728
- var AV_SAMPLE_FMT_S162 = 1;
2729
- var AV_SAMPLE_FMT_S322 = 2;
2730
- var AV_SAMPLE_FMT_FLT2 = 3;
2731
- var AV_SAMPLE_FMT_U8P2 = 5;
2732
- var AV_SAMPLE_FMT_S16P2 = 6;
2733
- var AV_SAMPLE_FMT_S32P2 = 7;
2734
- var AV_SAMPLE_FMT_FLTP2 = 8;
2735
- function libavFrameToInterleavedFloat322(frame) {
2736
- const channels = frame.channels ?? frame.ch_layout_nb_channels ?? 1;
2737
- const sampleRate = frame.sample_rate ?? 44100;
2738
- const nbSamples = frame.nb_samples ?? 0;
2739
- if (nbSamples === 0) return null;
2740
- const out = new Float32Array(nbSamples * channels);
2741
- switch (frame.format) {
2742
- case AV_SAMPLE_FMT_FLTP2: {
2743
- const planes = ensurePlanes2(frame.data, channels);
2744
- for (let ch = 0; ch < channels; ch++) {
2745
- const plane = asFloat322(planes[ch]);
2746
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i];
2747
- }
2748
- return { data: out, channels, sampleRate };
2749
- }
2750
- case AV_SAMPLE_FMT_FLT2: {
2751
- const flat = asFloat322(frame.data);
2752
- for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i];
2753
- return { data: out, channels, sampleRate };
2754
- }
2755
- case AV_SAMPLE_FMT_S16P2: {
2756
- const planes = ensurePlanes2(frame.data, channels);
2757
- for (let ch = 0; ch < channels; ch++) {
2758
- const plane = asInt162(planes[ch]);
2759
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 32768;
2760
- }
2761
- return { data: out, channels, sampleRate };
2762
- }
2763
- case AV_SAMPLE_FMT_S162: {
2764
- const flat = asInt162(frame.data);
2765
- for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 32768;
2766
- return { data: out, channels, sampleRate };
2767
- }
2768
- case AV_SAMPLE_FMT_S32P2: {
2769
- const planes = ensurePlanes2(frame.data, channels);
2770
- for (let ch = 0; ch < channels; ch++) {
2771
- const plane = asInt322(planes[ch]);
2772
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 2147483648;
2773
- }
2774
- return { data: out, channels, sampleRate };
2775
- }
2776
- case AV_SAMPLE_FMT_S322: {
2777
- const flat = asInt322(frame.data);
2778
- for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 2147483648;
2779
- return { data: out, channels, sampleRate };
2780
- }
2781
- case AV_SAMPLE_FMT_U8P2: {
2782
- const planes = ensurePlanes2(frame.data, channels);
2783
- for (let ch = 0; ch < channels; ch++) {
2784
- const plane = asUint82(planes[ch]);
2785
- for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = (plane[i] - 128) / 128;
2786
- }
2787
- return { data: out, channels, sampleRate };
2788
- }
2789
- case AV_SAMPLE_FMT_U82: {
2790
- const flat = asUint82(frame.data);
2791
- for (let i = 0; i < nbSamples * channels; i++) out[i] = (flat[i] - 128) / 128;
2792
- return { data: out, channels, sampleRate };
2793
- }
2794
- default:
2795
- if (!globalThis.__avbridgeLoggedSampleFmt) {
2796
- globalThis.__avbridgeLoggedSampleFmt = frame.format;
2797
- console.warn(`[avbridge] unsupported audio sample format from libav: ${frame.format}`);
2798
- }
2799
- return null;
2800
- }
2801
- }
2802
- function ensurePlanes2(data, channels) {
2803
- if (Array.isArray(data)) return data;
2804
- const arr = data;
2805
- const len = arr.length;
2806
- const perChannel = Math.floor(len / channels);
2807
- const planes = [];
2808
- for (let ch = 0; ch < channels; ch++) {
2809
- planes.push(arr.subarray ? arr.subarray(ch * perChannel, (ch + 1) * perChannel) : arr);
2810
- }
2811
- return planes;
2812
- }
2813
- function asFloat322(x) {
2814
- if (x instanceof Float32Array) return x;
2815
- const ta = x;
2816
- return new Float32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
2817
- }
2818
- function asInt162(x) {
2819
- if (x instanceof Int16Array) return x;
2820
- const ta = x;
2821
- return new Int16Array(ta.buffer, ta.byteOffset, ta.byteLength / 2);
2822
- }
2823
- function asInt322(x) {
2824
- if (x instanceof Int32Array) return x;
2825
- const ta = x;
2826
- return new Int32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
2827
- }
2828
- function asUint82(x) {
2829
- if (x instanceof Uint8Array) return x;
2830
- const ta = x;
2831
- return new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
2832
- }
2833
2626
  async function loadBridge2() {
2834
2627
  try {
2835
- const wrapper = await import('./libav-import-GST2AMPL.cjs');
2628
+ const wrapper = await import('./libav-import-2ZVKV2E7.cjs');
2836
2629
  return wrapper.libavBridge;
2837
2630
  } catch (err) {
2838
2631
  throw new Error(
@@ -2845,8 +2638,8 @@ async function loadBridge2() {
2845
2638
  var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
2846
2639
  var READY_TIMEOUT_SECONDS2 = 3;
2847
2640
  async function createFallbackSession(ctx, target, transport) {
2848
- const { normalizeSource: normalizeSource2 } = await import('./source-73CAH6HW.cjs');
2849
- const source = await normalizeSource2(ctx.source);
2641
+ const { normalizeSource } = await import('./source-VFLXLOCN.cjs');
2642
+ const source = await normalizeSource(ctx.source);
2850
2643
  const fps = ctx.videoTracks[0]?.fps ?? 30;
2851
2644
  const audio = new AudioOutput();
2852
2645
  const renderer = new VideoRenderer(target, audio, fps);
@@ -2898,6 +2691,18 @@ async function createFallbackSession(ctx, target, transport) {
2898
2691
  get: () => ctx.duration ?? NaN
2899
2692
  });
2900
2693
  }
2694
+ Object.defineProperty(target, "readyState", {
2695
+ configurable: true,
2696
+ get: () => {
2697
+ if (!renderer.hasFrames()) return 0;
2698
+ if (!audio.isPlaying() && audio.bufferAhead() <= 0 && !audio.isNoAudio()) return 1;
2699
+ return 2;
2700
+ }
2701
+ });
2702
+ Object.defineProperty(target, "seekable", {
2703
+ configurable: true,
2704
+ get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2705
+ });
2901
2706
  async function waitForBuffer() {
2902
2707
  const start = performance.now();
2903
2708
  let firstFrameAtMs = 0;
@@ -2966,7 +2771,24 @@ async function createFallbackSession(ctx, target, transport) {
2966
2771
  async seek(time) {
2967
2772
  await doSeek(time);
2968
2773
  },
2969
- async setAudioTrack(_id) {
2774
+ async setAudioTrack(id) {
2775
+ if (!ctx.audioTracks.some((t) => t.id === id)) {
2776
+ console.warn("[avbridge] fallback: setAudioTrack \u2014 unknown track id", id);
2777
+ return;
2778
+ }
2779
+ const wasPlaying = audio.isPlaying();
2780
+ const currentTime = audio.now();
2781
+ await audio.pause().catch(() => {
2782
+ });
2783
+ await handles.setAudioTrack(id, currentTime).catch(
2784
+ (err) => console.warn("[avbridge] fallback: handles.setAudioTrack failed:", err)
2785
+ );
2786
+ await audio.reset(currentTime);
2787
+ renderer.flush();
2788
+ if (wasPlaying) {
2789
+ await waitForBuffer();
2790
+ await audio.start();
2791
+ }
2970
2792
  },
2971
2793
  async setSubtitleTrack(_id) {
2972
2794
  },
@@ -2983,6 +2805,8 @@ async function createFallbackSession(ctx, target, transport) {
2983
2805
  delete target.paused;
2984
2806
  delete target.volume;
2985
2807
  delete target.muted;
2808
+ delete target.readyState;
2809
+ delete target.seekable;
2986
2810
  } catch {
2987
2811
  }
2988
2812
  },
@@ -3020,89 +2844,6 @@ function registerBuiltins(registry) {
3020
2844
  registry.register(fallbackPlugin);
3021
2845
  }
3022
2846
 
3023
- // src/subtitles/vtt.ts
3024
- function isVtt(text) {
3025
- const trimmed = text.replace(/^\ufeff/, "").trimStart();
3026
- return trimmed.startsWith("WEBVTT");
3027
- }
3028
-
3029
- // src/subtitles/index.ts
3030
- async function discoverSidecars(file, directory) {
3031
- const baseName = file.name.replace(/\.[^.]+$/, "");
3032
- const found = [];
3033
- for await (const [name, handle] of directory) {
3034
- if (handle.kind !== "file") continue;
3035
- if (!name.startsWith(baseName)) continue;
3036
- const lower = name.toLowerCase();
3037
- let format = null;
3038
- if (lower.endsWith(".srt")) format = "srt";
3039
- else if (lower.endsWith(".vtt")) format = "vtt";
3040
- if (!format) continue;
3041
- const sidecarFile = await handle.getFile();
3042
- const url = URL.createObjectURL(sidecarFile);
3043
- const langMatch = name.slice(baseName.length).match(/[._-]([a-z]{2,3})(?:[._-]|\.)/i);
3044
- found.push({
3045
- url,
3046
- format,
3047
- language: langMatch?.[1]
3048
- });
3049
- }
3050
- return found;
3051
- }
3052
- var SubtitleResourceBag = class {
3053
- urls = /* @__PURE__ */ new Set();
3054
- /** Track an externally-created blob URL (e.g. from `discoverSidecars`). */
3055
- track(url) {
3056
- this.urls.add(url);
3057
- }
3058
- /** Convenience: create a blob URL and track it in one call. */
3059
- createObjectURL(blob) {
3060
- const url = URL.createObjectURL(blob);
3061
- this.urls.add(url);
3062
- return url;
3063
- }
3064
- /** Revoke every tracked URL. Idempotent — safe to call multiple times. */
3065
- revokeAll() {
3066
- for (const u of this.urls) URL.revokeObjectURL(u);
3067
- this.urls.clear();
3068
- }
3069
- };
3070
- async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
3071
- const doFetch = chunk6UUT4BEA_cjs.fetchWith(transport);
3072
- for (const t of Array.from(video.querySelectorAll("track[data-avbridge]"))) {
3073
- t.remove();
3074
- }
3075
- for (const t of tracks) {
3076
- if (!t.sidecarUrl) continue;
3077
- try {
3078
- let url = t.sidecarUrl;
3079
- if (t.format === "srt") {
3080
- const res = await doFetch(t.sidecarUrl, transport?.requestInit);
3081
- const text = await res.text();
3082
- const vtt = srtToVtt(text);
3083
- const blob = new Blob([vtt], { type: "text/vtt" });
3084
- url = bag ? bag.createObjectURL(blob) : URL.createObjectURL(blob);
3085
- } else if (t.format === "vtt") {
3086
- const res = await doFetch(t.sidecarUrl, transport?.requestInit);
3087
- const text = await res.text();
3088
- if (!isVtt(text)) {
3089
- console.warn("[avbridge] subtitle missing WEBVTT header:", t.sidecarUrl);
3090
- }
3091
- }
3092
- const trackEl = document.createElement("track");
3093
- trackEl.kind = "subtitles";
3094
- trackEl.src = url;
3095
- trackEl.srclang = t.language ?? "und";
3096
- trackEl.label = t.language ?? `Subtitle ${t.id}`;
3097
- trackEl.dataset.avbridge = "true";
3098
- video.appendChild(trackEl);
3099
- } catch (err) {
3100
- const e = err instanceof Error ? err : new Error(String(err));
3101
- onError?.(e, t);
3102
- }
3103
- }
3104
- }
3105
-
3106
2847
  // src/player.ts
3107
2848
  var UnifiedPlayer = class _UnifiedPlayer {
3108
2849
  /**
@@ -3146,7 +2887,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
3146
2887
  switchingPromise = Promise.resolve();
3147
2888
  // Owns blob URLs created during sidecar discovery + SRT->VTT conversion.
3148
2889
  // Revoked at destroy() so repeated source swaps don't leak.
3149
- subtitleResources = new SubtitleResourceBag();
2890
+ subtitleResources = new chunkS4WAZC2T_cjs.SubtitleResourceBag();
3150
2891
  // Transport config extracted from CreatePlayerOptions. Threaded to probe,
3151
2892
  // subtitle fetches, and strategy session creators. Not stored on MediaContext
3152
2893
  // because it's runtime config, not media analysis.
@@ -3174,7 +2915,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
3174
2915
  const bootstrapStart = performance.now();
3175
2916
  try {
3176
2917
  chunkG4APZMCP_cjs.dbg.info("bootstrap", "start");
3177
- const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => probe(this.options.source, this.transport));
2918
+ const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => chunkZCUXHW55_cjs.probe(this.options.source, this.transport));
3178
2919
  chunkG4APZMCP_cjs.dbg.info(
3179
2920
  "probe",
3180
2921
  `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
@@ -3192,7 +2933,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
3192
2933
  }
3193
2934
  }
3194
2935
  if (this.options.directory && this.options.source instanceof File) {
3195
- const found = await discoverSidecars(this.options.source, this.options.directory);
2936
+ const found = await chunkS4WAZC2T_cjs.discoverSidecars(this.options.source, this.options.directory);
3196
2937
  for (const s of found) {
3197
2938
  this.subtitleResources.track(s.url);
3198
2939
  ctx.subtitleTracks.push({
@@ -3215,17 +2956,15 @@ var UnifiedPlayer = class _UnifiedPlayer {
3215
2956
  reason: decision.reason
3216
2957
  });
3217
2958
  await this.startSession(decision.strategy, decision.reason);
3218
- if (this.session.strategy !== "fallback" && this.session.strategy !== "hybrid") {
3219
- await attachSubtitleTracks(
3220
- this.options.target,
3221
- ctx.subtitleTracks,
3222
- this.subtitleResources,
3223
- (err, track) => {
3224
- console.warn(`[avbridge] subtitle ${track.id} failed: ${err.message}`);
3225
- },
3226
- this.transport
3227
- );
3228
- }
2959
+ await chunkS4WAZC2T_cjs.attachSubtitleTracks(
2960
+ this.options.target,
2961
+ ctx.subtitleTracks,
2962
+ this.subtitleResources,
2963
+ (err, track) => {
2964
+ console.warn(`[avbridge] subtitle ${track.id} failed: ${err.message}`);
2965
+ },
2966
+ this.transport
2967
+ );
3229
2968
  this.emitter.emitSticky("tracks", {
3230
2969
  video: ctx.videoTracks,
3231
2970
  audio: ctx.audioTracks,
@@ -3360,8 +3099,8 @@ var UnifiedPlayer = class _UnifiedPlayer {
3360
3099
  }
3361
3100
  return;
3362
3101
  }
3363
- this.emitter.emit("error", new chunk6UUT4BEA_cjs.AvbridgeError(
3364
- chunk6UUT4BEA_cjs.ERR_ALL_STRATEGIES_EXHAUSTED,
3102
+ this.emitter.emit("error", new chunk2IJ66NTD_cjs.AvbridgeError(
3103
+ chunk2IJ66NTD_cjs.ERR_ALL_STRATEGIES_EXHAUSTED,
3365
3104
  `All playback strategies failed: ${errors.join("; ")}`,
3366
3105
  "This file may require a codec or container that isn't available in this browser. Try the fallback strategy or check browser codec support."
3367
3106
  ));
@@ -3415,7 +3154,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
3415
3154
  // ── Public: manual strategy switch ────────────────────────────────────
3416
3155
  /** Manually switch to a different playback strategy. Preserves current position and play/pause state. Concurrent calls are serialized. */
3417
3156
  async setStrategy(strategy, reason) {
3418
- if (!this.mediaContext) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3157
+ if (!this.mediaContext) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3419
3158
  if (this.session?.strategy === strategy) return;
3420
3159
  this.switchingPromise = this.switchingPromise.then(
3421
3160
  () => this.doSetStrategy(strategy, reason)
@@ -3478,7 +3217,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
3478
3217
  }
3479
3218
  /** Begin or resume playback. Throws if the player is not ready. */
3480
3219
  async play() {
3481
- if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3220
+ if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3482
3221
  this.userIntent = "play";
3483
3222
  this.autoPausedForVisibility = false;
3484
3223
  await this.session.play();
@@ -3517,17 +3256,17 @@ var UnifiedPlayer = class _UnifiedPlayer {
3517
3256
  }
3518
3257
  /** Seek to the given time in seconds. Throws if the player is not ready. */
3519
3258
  async seek(time) {
3520
- if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3259
+ if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3521
3260
  await this.session.seek(time);
3522
3261
  }
3523
3262
  /** Switch the active audio track by track ID. Throws if the player is not ready. */
3524
3263
  async setAudioTrack(id) {
3525
- if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3264
+ if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3526
3265
  await this.session.setAudioTrack(id);
3527
3266
  }
3528
3267
  /** Switch the active subtitle track by track ID, or pass `null` to disable subtitles. */
3529
3268
  async setSubtitleTrack(id) {
3530
- if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3269
+ if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
3531
3270
  await this.session.setSubtitleTrack(id);
3532
3271
  }
3533
3272
  /** Return a snapshot of current diagnostics: container, codecs, strategy, runtime stats, and strategy history. */
@@ -3625,12 +3364,7 @@ exports.FALLBACK_VIDEO_CODECS = FALLBACK_VIDEO_CODECS;
3625
3364
  exports.NATIVE_AUDIO_CODECS = NATIVE_AUDIO_CODECS;
3626
3365
  exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
3627
3366
  exports.UnifiedPlayer = UnifiedPlayer;
3628
- exports.avbridgeAudioToMediabunny = avbridgeAudioToMediabunny;
3629
- exports.avbridgeVideoToMediabunny = avbridgeVideoToMediabunny;
3630
- exports.buildMediabunnySourceFromInput = buildMediabunnySourceFromInput;
3631
3367
  exports.classifyContext = classifyContext;
3632
3368
  exports.createPlayer = createPlayer;
3633
- exports.probe = probe;
3634
- exports.srtToVtt = srtToVtt;
3635
- //# sourceMappingURL=chunk-7RGG6ME7.cjs.map
3636
- //# sourceMappingURL=chunk-7RGG6ME7.cjs.map
3369
+ //# sourceMappingURL=chunk-6SOFJV44.cjs.map
3370
+ //# sourceMappingURL=chunk-6SOFJV44.cjs.map