avbridge 2.9.0 → 2.11.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 (50) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/{chunk-EY6DZEDT.cjs → chunk-37UOSAVI.cjs} +55 -10
  3. package/dist/chunk-37UOSAVI.cjs.map +1 -0
  4. package/dist/{chunk-5KVLE6YI.js → chunk-EDDWAN2L.js} +3 -2
  5. package/dist/chunk-EDDWAN2L.js.map +1 -0
  6. package/dist/{chunk-SN4WZE24.js → chunk-IHNHHEA2.js} +51 -6
  7. package/dist/chunk-IHNHHEA2.js.map +1 -0
  8. package/dist/{chunk-S4WAZC2T.cjs → chunk-WRKO6Q42.cjs} +3 -2
  9. package/dist/chunk-WRKO6Q42.cjs.map +1 -0
  10. package/dist/element-browser.js +63 -4
  11. package/dist/element-browser.js.map +1 -1
  12. package/dist/element.cjs +18 -5
  13. package/dist/element.cjs.map +1 -1
  14. package/dist/element.d.cts +1 -1
  15. package/dist/element.d.ts +1 -1
  16. package/dist/element.js +17 -4
  17. package/dist/element.js.map +1 -1
  18. package/dist/index.cjs +10 -10
  19. package/dist/index.d.cts +2 -2
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +2 -2
  22. package/dist/{player-DEcidWk6.d.cts → player-DDdNVFDv.d.cts} +23 -1
  23. package/dist/{player-DEcidWk6.d.ts → player-DDdNVFDv.d.ts} +23 -1
  24. package/dist/player.cjs +329 -109
  25. package/dist/player.cjs.map +1 -1
  26. package/dist/player.d.cts +42 -0
  27. package/dist/player.d.ts +42 -0
  28. package/dist/player.js +325 -105
  29. package/dist/player.js.map +1 -1
  30. package/dist/subtitles-5H24MEBJ.js +4 -0
  31. package/dist/{subtitles-4T74JRGT.js.map → subtitles-5H24MEBJ.js.map} +1 -1
  32. package/dist/subtitles-HMVGWTU2.cjs +29 -0
  33. package/dist/{subtitles-QUH4LPI4.cjs.map → subtitles-HMVGWTU2.cjs.map} +1 -1
  34. package/package.json +1 -1
  35. package/src/element/avbridge-player.ts +235 -78
  36. package/src/element/avbridge-subtitles.ts +273 -0
  37. package/src/element/avbridge-video.ts +21 -1
  38. package/src/element/player-styles.ts +85 -35
  39. package/src/index.ts +1 -0
  40. package/src/strategies/fallback/audio-output.ts +39 -4
  41. package/src/strategies/fallback/index.ts +12 -0
  42. package/src/strategies/hybrid/index.ts +9 -0
  43. package/src/subtitles/index.ts +2 -0
  44. package/src/types.ts +25 -0
  45. package/dist/chunk-5KVLE6YI.js.map +0 -1
  46. package/dist/chunk-EY6DZEDT.cjs.map +0 -1
  47. package/dist/chunk-S4WAZC2T.cjs.map +0 -1
  48. package/dist/chunk-SN4WZE24.js.map +0 -1
  49. package/dist/subtitles-4T74JRGT.js +0 -4
  50. package/dist/subtitles-QUH4LPI4.cjs +0 -29
@@ -133,6 +133,7 @@ async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
133
133
  }
134
134
  for (const t of tracks) {
135
135
  if (!t.sidecarUrl) continue;
136
+ console.log(`[avbridge:subs] attaching track id=${t.id} format=${t.format} url=${t.sidecarUrl.slice(0, 60)}`);
136
137
  try {
137
138
  let url = t.sidecarUrl;
138
139
  if (t.format === "srt") {
@@ -163,5 +164,5 @@ async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
163
164
  }
164
165
 
165
166
  export { SubtitleOverlay, SubtitleResourceBag, attachSubtitleTracks, discoverSidecars, srtToVtt };
166
- //# sourceMappingURL=chunk-5KVLE6YI.js.map
167
- //# sourceMappingURL=chunk-5KVLE6YI.js.map
167
+ //# sourceMappingURL=chunk-EDDWAN2L.js.map
168
+ //# sourceMappingURL=chunk-EDDWAN2L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/subtitles/srt.ts","../src/subtitles/vtt.ts","../src/subtitles/render.ts","../src/subtitles/index.ts"],"names":[],"mappings":"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","file":"chunk-EDDWAN2L.js","sourcesContent":["/**\n * SRT → WebVTT converter.\n *\n * SRT cues:\n *\n * 1\n * 00:00:20,000 --> 00:00:24,400\n * Subtitle text, possibly multiple lines.\n *\n * WebVTT cues:\n *\n * WEBVTT\n *\n * 00:00:20.000 --> 00:00:24.400\n * Subtitle text, possibly multiple lines.\n *\n * The differences in v1 are:\n * - leading `WEBVTT` magic line\n * - `,` → `.` for milliseconds\n * - cue index lines are stripped (WebVTT allows them but SRT-style ints can\n * confuse some parsers; we drop them)\n * - BOM is stripped\n */\nexport function srtToVtt(srt: string): string {\n // Strip BOM\n if (srt.charCodeAt(0) === 0xfeff) srt = srt.slice(1);\n // Normalize line endings\n const normalized = srt.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").trim();\n\n const blocks = normalized.split(/\\n{2,}/);\n const out: string[] = [\"WEBVTT\", \"\"];\n\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n // Drop the leading numeric index, if present.\n if (lines.length > 0 && /^\\d+$/.test(lines[0].trim())) {\n lines.shift();\n }\n if (lines.length === 0) continue;\n\n const timing = lines.shift()!;\n const vttTiming = convertTiming(timing);\n if (!vttTiming) continue; // skip malformed cue\n\n out.push(vttTiming);\n for (const l of lines) out.push(l);\n out.push(\"\");\n }\n\n return out.join(\"\\n\");\n}\n\nfunction convertTiming(line: string): string | null {\n // SRT: HH:MM:SS,mmm --> HH:MM:SS,mmm (optional cue settings after)\n 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(\n line.trim(),\n );\n if (!m) return null;\n const fmt = (h: string, mm: string, s: string, ms: string) =>\n `${h.padStart(2, \"0\")}:${mm}:${s}.${ms.padEnd(3, \"0\").slice(0, 3)}`;\n return `${fmt(m[1], m[2], m[3], m[4])} --> ${fmt(m[5], m[6], m[7], m[8])}${m[9] ?? \"\"}`;\n}\n","/** Light validation for incoming VTT — we do not parse cues, just confirm header. */\nexport function isVtt(text: string): boolean {\n const trimmed = text.replace(/^\\ufeff/, \"\").trimStart();\n return trimmed.startsWith(\"WEBVTT\");\n}\n","/**\n * Custom subtitle overlay for the fallback strategy. We don't have a `<video>`\n * with text tracks here, so we render cues into a positioned div ourselves.\n *\n * v1 only handles plain-text WebVTT cues with `HH:MM:SS.mmm` timing. Cue\n * settings, voice tags, and styling are ignored.\n */\n\ninterface Cue {\n start: number;\n end: number;\n text: string;\n}\n\nexport class SubtitleOverlay {\n private el: HTMLDivElement;\n private cues: Cue[] = [];\n\n constructor(parent: HTMLElement) {\n this.el = document.createElement(\"div\");\n this.el.style.cssText =\n \"position:absolute;left:0;right:0;bottom:8%;text-align:center;color:white;text-shadow:0 0 4px black;font-family:sans-serif;font-size:1.4em;pointer-events:none;\";\n parent.appendChild(this.el);\n }\n\n loadVtt(text: string): void {\n this.cues = parseVtt(text);\n }\n\n update(currentTime: number): void {\n const active = this.cues.find((c) => currentTime >= c.start && currentTime <= c.end);\n this.el.textContent = active?.text ?? \"\";\n }\n\n /** Set the currently-displayed text directly (bypasses loadVtt/update). */\n setText(text: string): void {\n // Only touch the DOM if it actually changed — rAF tick runs 60Hz.\n if (this.el.textContent !== text) {\n this.el.textContent = text;\n }\n }\n\n destroy(): void {\n this.el.remove();\n this.cues = [];\n }\n}\n\nfunction parseVtt(text: string): Cue[] {\n const cues: Cue[] = [];\n const blocks = text.replace(/\\r\\n/g, \"\\n\").split(/\\n{2,}/);\n for (const block of blocks) {\n const lines = block.split(\"\\n\").filter(Boolean);\n if (lines.length === 0 || lines[0] === \"WEBVTT\") continue;\n const timingIdx = lines.findIndex((l) => l.includes(\"-->\"));\n if (timingIdx < 0) continue;\n const m = /(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})\\s*-->\\s*(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})/.exec(\n lines[timingIdx],\n );\n if (!m) continue;\n const t = (h: string, mm: string, s: string, ms: string) =>\n Number(h) * 3600 + Number(mm) * 60 + Number(s) + Number(ms) / 1000;\n cues.push({\n start: t(m[1], m[2], m[3], m[4]),\n end: t(m[5], m[6], m[7], m[8]),\n text: lines.slice(timingIdx + 1).join(\"\\n\"),\n });\n }\n return cues;\n}\n","import type { SubtitleTrackInfo, TransportConfig } from \"../types.js\";\nimport { fetchWith } from \"../util/transport.js\";\nimport { srtToVtt } from \"./srt.js\";\nimport { isVtt } from \"./vtt.js\";\n\nexport { srtToVtt } from \"./srt.js\";\nexport { SubtitleOverlay } from \"./render.js\";\n\n/**\n * Discover sidecar `.srt` / `.vtt` files next to the source. Requires the\n * caller to pass a `FileSystemDirectoryHandle` (e.g. via the File System\n * Access API). Without that handle we can't enumerate sibling files.\n *\n * The returned `url` fields are blob URLs created via `URL.createObjectURL`.\n * They must be revoked by the caller (e.g. via `revokeSubtitleResources()`)\n * when the player tears down or the source changes — otherwise repeated\n * source swaps in a single-page app will leak.\n */\nexport interface DiscoveredSidecar {\n url: string;\n format: \"srt\" | \"vtt\";\n language?: string;\n}\n\nexport async function discoverSidecars(\n file: File,\n directory: FileSystemDirectoryHandle,\n): Promise<DiscoveredSidecar[]> {\n const baseName = file.name.replace(/\\.[^.]+$/, \"\");\n const found: DiscoveredSidecar[] = [];\n\n // Walk the directory and look for `${baseName}*.srt` / `*.vtt`.\n for await (const [name, handle] of (directory as unknown as AsyncIterable<[string, FileSystemHandle]>)) {\n if (handle.kind !== \"file\") continue;\n if (!name.startsWith(baseName)) continue;\n const lower = name.toLowerCase();\n let format: \"srt\" | \"vtt\" | null = null;\n if (lower.endsWith(\".srt\")) format = \"srt\";\n else if (lower.endsWith(\".vtt\")) format = \"vtt\";\n if (!format) continue;\n\n const sidecarFile = await (handle as FileSystemFileHandle).getFile();\n const url = URL.createObjectURL(sidecarFile);\n\n // Try to extract a language tag (eg. movie.en.srt → \"en\").\n const langMatch = name.slice(baseName.length).match(/[._-]([a-z]{2,3})(?:[._-]|\\.)/i);\n found.push({\n url,\n format,\n language: langMatch?.[1],\n });\n }\n\n return found;\n}\n\n/**\n * Owns every blob URL created during sidecar discovery and SRT→VTT\n * conversion for a single player session. Revoking the bag releases all of\n * them in one shot at teardown.\n */\nexport class SubtitleResourceBag {\n private urls = new Set<string>();\n\n /** Track an externally-created blob URL (e.g. from `discoverSidecars`). */\n track(url: string): void {\n this.urls.add(url);\n }\n\n /** Convenience: create a blob URL and track it in one call. */\n createObjectURL(blob: Blob): string {\n const url = URL.createObjectURL(blob);\n this.urls.add(url);\n return url;\n }\n\n /** Revoke every tracked URL. Idempotent — safe to call multiple times. */\n revokeAll(): void {\n for (const u of this.urls) URL.revokeObjectURL(u);\n this.urls.clear();\n }\n}\n\n/**\n * Attach `<track>` elements for each subtitle to the player's `<video>`. SRT\n * sources are converted to VTT first via blob URLs because `<track>` only\n * accepts WebVTT.\n *\n * Pass a {@link SubtitleResourceBag} so the player can revoke the generated\n * blob URLs at teardown. Without one, every SRT subtitle leaks a blob URL\n * per attach.\n *\n * Errors during fetch/parse are caught per-track and reported via the\n * `onError` callback (if provided) so a single bad subtitle doesn't break\n * bootstrap. Subtitles are *not* load-bearing for playback.\n */\nexport async function attachSubtitleTracks(\n video: HTMLVideoElement,\n tracks: SubtitleTrackInfo[],\n bag?: SubtitleResourceBag,\n onError?: (err: Error, track: SubtitleTrackInfo) => void,\n transport?: TransportConfig,\n): Promise<void> {\n const doFetch = fetchWith(transport);\n\n // Clear existing dynamically-attached tracks.\n for (const t of Array.from(video.querySelectorAll(\"track[data-avbridge]\"))) {\n t.remove();\n }\n\n for (const t of tracks) {\n if (!t.sidecarUrl) continue;\n // eslint-disable-next-line no-console\n console.log(`[avbridge:subs] attaching track id=${t.id} format=${t.format} url=${t.sidecarUrl.slice(0, 60)}`);\n try {\n let url = t.sidecarUrl;\n if (t.format === \"srt\") {\n const res = await doFetch(t.sidecarUrl, transport?.requestInit);\n const text = await res.text();\n const vtt = srtToVtt(text);\n const blob = new Blob([vtt], { type: \"text/vtt\" });\n url = bag ? bag.createObjectURL(blob) : URL.createObjectURL(blob);\n } else if (t.format === \"vtt\") {\n // Validate quickly so a malformed file fails loudly here.\n const res = await doFetch(t.sidecarUrl, transport?.requestInit);\n const text = await res.text();\n if (!isVtt(text)) {\n // eslint-disable-next-line no-console\n console.warn(\"[avbridge] subtitle missing WEBVTT header:\", t.sidecarUrl);\n }\n }\n const trackEl = document.createElement(\"track\");\n trackEl.kind = \"subtitles\";\n trackEl.src = url;\n trackEl.srclang = t.language ?? \"und\";\n trackEl.label = t.language ?? `Subtitle ${t.id}`;\n trackEl.dataset.avbridge = \"true\";\n video.appendChild(trackEl);\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n onError?.(e, t);\n }\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-5KVLE6YI.js';
1
+ import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-EDDWAN2L.js';
2
2
  import { probe, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, avbridgeAudioToMediabunny } from './chunk-GYIJU44C.js';
3
3
  import { AvbridgeError, ERR_ALL_STRATEGIES_EXHAUSTED, ERR_PLAYER_NOT_READY, ERR_MSE_NOT_SUPPORTED, ERR_MSE_CODEC_NOT_SUPPORTED } from './chunk-CPJLFFCC.js';
4
4
  import { packetPtsSec, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 } from './chunk-2NSOOMXW.js';
@@ -1374,6 +1374,10 @@ var AudioOutput = class {
1374
1374
  _volume = 1;
1375
1375
  /** User-set muted flag. When true, gain is forced to 0. */
1376
1376
  _muted = false;
1377
+ /** Playback rate. Scales the media clock and each AudioBufferSourceNode's
1378
+ * playbackRate so audio pitches up/down accordingly (same as native
1379
+ * <video>.playbackRate). Default 1. */
1380
+ _rate = 1;
1377
1381
  constructor() {
1378
1382
  this.ctx = new AudioContext();
1379
1383
  this.gain = this.ctx.createGain();
@@ -1395,6 +1399,20 @@ var AudioOutput = class {
1395
1399
  getMuted() {
1396
1400
  return this._muted;
1397
1401
  }
1402
+ /** Set playback rate. Scales the media clock and pitches audio output
1403
+ * (same as native <video>.playbackRate — speed without pitch correction).
1404
+ * Rebases the anchor so the clock transition is seamless. */
1405
+ setPlaybackRate(rate) {
1406
+ if (rate === this._rate) return;
1407
+ const t = this.now();
1408
+ this.mediaTimeOfAnchor = t;
1409
+ this.ctxTimeAtAnchor = this.ctx.currentTime;
1410
+ this.wallAnchorMs = performance.now();
1411
+ this._rate = rate;
1412
+ }
1413
+ getPlaybackRate() {
1414
+ return this._rate;
1415
+ }
1398
1416
  applyGain() {
1399
1417
  const target = this._muted ? 0 : this._volume;
1400
1418
  try {
@@ -1415,12 +1433,12 @@ var AudioOutput = class {
1415
1433
  now() {
1416
1434
  if (this.noAudio) {
1417
1435
  if (this.state === "playing") {
1418
- return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3;
1436
+ return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3 * this._rate;
1419
1437
  }
1420
1438
  return this.mediaTimeOfAnchor;
1421
1439
  }
1422
1440
  if (this.state === "playing") {
1423
- return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor);
1441
+ return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor) * this._rate;
1424
1442
  }
1425
1443
  return this.mediaTimeOfAnchor;
1426
1444
  }
@@ -1472,7 +1490,8 @@ var AudioOutput = class {
1472
1490
  const node = this.ctx.createBufferSource();
1473
1491
  node.buffer = buffer;
1474
1492
  node.connect(this.gain);
1475
- let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
1493
+ if (this._rate !== 1) node.playbackRate.value = this._rate;
1494
+ let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor) / this._rate;
1476
1495
  if (ctxStart < this.ctx.currentTime) {
1477
1496
  this.ctxTimeAtAnchor = this.ctx.currentTime;
1478
1497
  this.mediaTimeOfAnchor = this.mediaTimeOfNext;
@@ -1499,6 +1518,10 @@ var AudioOutput = class {
1499
1518
  if (this.ctx.state === "suspended") {
1500
1519
  await this.ctx.resume();
1501
1520
  }
1521
+ try {
1522
+ this.gain.connect(this.ctx.destination);
1523
+ } catch {
1524
+ }
1502
1525
  if (this.state === "paused") {
1503
1526
  this.ctxTimeAtAnchor = this.ctx.currentTime;
1504
1527
  this.state = "playing";
@@ -1525,6 +1548,10 @@ var AudioOutput = class {
1525
1548
  this.mediaTimeOfAnchor = this.now();
1526
1549
  this.state = "paused";
1527
1550
  if (this.noAudio) return;
1551
+ try {
1552
+ this.gain.disconnect();
1553
+ } catch {
1554
+ }
1528
1555
  if (this.ctx.state === "running") {
1529
1556
  await this.ctx.suspend();
1530
1557
  }
@@ -2120,6 +2147,14 @@ async function createHybridSession(ctx, target, transport) {
2120
2147
  get: () => ctx.duration ?? NaN
2121
2148
  });
2122
2149
  }
2150
+ Object.defineProperty(target, "playbackRate", {
2151
+ configurable: true,
2152
+ get: () => audio.getPlaybackRate(),
2153
+ set: (v) => {
2154
+ audio.setPlaybackRate(v);
2155
+ target.dispatchEvent(new Event("ratechange"));
2156
+ }
2157
+ });
2123
2158
  Object.defineProperty(target, "readyState", {
2124
2159
  configurable: true,
2125
2160
  get: () => {
@@ -2230,6 +2265,7 @@ async function createHybridSession(ctx, target, transport) {
2230
2265
  delete target.muted;
2231
2266
  delete target.readyState;
2232
2267
  delete target.seekable;
2268
+ delete target.playbackRate;
2233
2269
  } catch {
2234
2270
  }
2235
2271
  },
@@ -2765,6 +2801,14 @@ async function createFallbackSession(ctx, target, transport) {
2765
2801
  get: () => ctx.duration ?? NaN
2766
2802
  });
2767
2803
  }
2804
+ Object.defineProperty(target, "playbackRate", {
2805
+ configurable: true,
2806
+ get: () => audio.getPlaybackRate(),
2807
+ set: (v) => {
2808
+ audio.setPlaybackRate(v);
2809
+ target.dispatchEvent(new Event("ratechange"));
2810
+ }
2811
+ });
2768
2812
  Object.defineProperty(target, "readyState", {
2769
2813
  configurable: true,
2770
2814
  get: () => {
@@ -2896,6 +2940,7 @@ async function createFallbackSession(ctx, target, transport) {
2896
2940
  delete target.muted;
2897
2941
  delete target.readyState;
2898
2942
  delete target.seekable;
2943
+ delete target.playbackRate;
2899
2944
  } catch {
2900
2945
  }
2901
2946
  },
@@ -3498,5 +3543,5 @@ function defaultFallbackChain(strategy) {
3498
3543
  }
3499
3544
 
3500
3545
  export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext, createPlayer };
3501
- //# sourceMappingURL=chunk-SN4WZE24.js.map
3502
- //# sourceMappingURL=chunk-SN4WZE24.js.map
3546
+ //# sourceMappingURL=chunk-IHNHHEA2.js.map
3547
+ //# sourceMappingURL=chunk-IHNHHEA2.js.map