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.
- package/CHANGELOG.md +114 -0
- package/dist/{chunk-6UUT4BEA.cjs → chunk-2IJ66NTD.cjs} +13 -20
- package/dist/chunk-2IJ66NTD.cjs.map +1 -0
- package/dist/{chunk-XKPSTC34.cjs → chunk-2XW2O3YI.cjs} +5 -20
- package/dist/chunk-2XW2O3YI.cjs.map +1 -0
- package/dist/chunk-5KVLE6YI.js +167 -0
- package/dist/chunk-5KVLE6YI.js.map +1 -0
- package/dist/{chunk-7RGG6ME7.cjs → chunk-6SOFJV44.cjs} +422 -688
- package/dist/chunk-6SOFJV44.cjs.map +1 -0
- package/dist/{chunk-2PGRFCWB.js → chunk-CPJLFFCC.js} +8 -18
- package/dist/chunk-CPJLFFCC.js.map +1 -0
- package/dist/chunk-CPZ7PXAM.cjs +240 -0
- package/dist/chunk-CPZ7PXAM.cjs.map +1 -0
- package/dist/{chunk-QQXBPW72.js → chunk-E76AMWI4.js} +4 -18
- package/dist/chunk-E76AMWI4.js.map +1 -0
- package/dist/chunk-LUFA47FP.js +19 -0
- package/dist/chunk-LUFA47FP.js.map +1 -0
- package/dist/{chunk-NV7ILLWH.js → chunk-OGYHFY6K.js} +404 -665
- package/dist/chunk-OGYHFY6K.js.map +1 -0
- package/dist/chunk-Q2VUO52Z.cjs +374 -0
- package/dist/chunk-Q2VUO52Z.cjs.map +1 -0
- package/dist/chunk-QDJLQR53.cjs +22 -0
- package/dist/chunk-QDJLQR53.cjs.map +1 -0
- package/dist/chunk-S4WAZC2T.cjs +173 -0
- package/dist/chunk-S4WAZC2T.cjs.map +1 -0
- package/dist/chunk-SMH6IOP2.js +368 -0
- package/dist/chunk-SMH6IOP2.js.map +1 -0
- package/dist/chunk-SR3MPV4D.js +237 -0
- package/dist/chunk-SR3MPV4D.js.map +1 -0
- package/dist/chunk-X2K3GIWE.js +235 -0
- package/dist/chunk-X2K3GIWE.js.map +1 -0
- package/dist/chunk-ZCUXHW55.cjs +242 -0
- package/dist/chunk-ZCUXHW55.cjs.map +1 -0
- package/dist/element-browser.js +883 -492
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +88 -6
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +51 -1
- package/dist/element.d.ts +51 -1
- package/dist/element.js +87 -5
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +523 -393
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +494 -366
- package/dist/index.js.map +1 -1
- package/dist/libav-demux-H2GS46GH.cjs +27 -0
- package/dist/libav-demux-H2GS46GH.cjs.map +1 -0
- package/dist/libav-demux-OWZ4T2YW.js +6 -0
- package/dist/libav-demux-OWZ4T2YW.js.map +1 -0
- package/dist/{libav-import-GST2AMPL.cjs → libav-import-2ZVKV2E7.cjs} +2 -2
- package/dist/{libav-import-GST2AMPL.cjs.map → libav-import-2ZVKV2E7.cjs.map} +1 -1
- package/dist/{libav-import-2JURFHEW.js → libav-import-6MGLCXVQ.js} +2 -2
- package/dist/{libav-import-2JURFHEW.js.map → libav-import-6MGLCXVQ.js.map} +1 -1
- package/dist/{player-B6WB74RD.d.ts → player-DGXeCNfD.d.cts} +41 -1
- package/dist/{player-B6WB74RD.d.cts → player-DGXeCNfD.d.ts} +41 -1
- package/dist/player.cjs +731 -472
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +229 -120
- package/dist/player.d.ts +229 -120
- package/dist/player.js +710 -451
- package/dist/player.js.map +1 -1
- package/dist/remux-OBSMIENG.cjs +35 -0
- package/dist/remux-OBSMIENG.cjs.map +1 -0
- package/dist/remux-WBYIZBBX.js +10 -0
- package/dist/remux-WBYIZBBX.js.map +1 -0
- package/dist/source-4TZ6KMNV.js +4 -0
- package/dist/{source-F656KYYV.js.map → source-4TZ6KMNV.js.map} +1 -1
- package/dist/source-7YLO6E7X.cjs +29 -0
- package/dist/{source-73CAH6HW.cjs.map → source-7YLO6E7X.cjs.map} +1 -1
- package/dist/source-MTX5ELUZ.js +4 -0
- package/dist/{source-QJR3OHTW.js.map → source-MTX5ELUZ.js.map} +1 -1
- package/dist/source-VFLXLOCN.cjs +29 -0
- package/dist/{source-VB74JQ7Z.cjs.map → source-VFLXLOCN.cjs.map} +1 -1
- package/dist/subtitles-4T74JRGT.js +4 -0
- package/dist/subtitles-4T74JRGT.js.map +1 -0
- package/dist/subtitles-QUH4LPI4.cjs +29 -0
- package/dist/subtitles-QUH4LPI4.cjs.map +1 -0
- package/package.json +1 -1
- package/src/convert/remux.ts +1 -35
- package/src/convert/transcode-libav.ts +691 -0
- package/src/convert/transcode.ts +12 -4
- package/src/element/avbridge-player.ts +100 -0
- package/src/element/avbridge-video.ts +140 -3
- package/src/element/player-styles.ts +12 -0
- package/src/errors.ts +6 -0
- package/src/player.ts +15 -16
- package/src/strategies/fallback/decoder.ts +96 -173
- package/src/strategies/fallback/index.ts +46 -2
- package/src/strategies/fallback/libav-import.ts +9 -1
- package/src/strategies/fallback/video-renderer.ts +107 -0
- package/src/strategies/hybrid/decoder.ts +88 -180
- package/src/strategies/hybrid/index.ts +35 -2
- package/src/strategies/native.ts +6 -3
- package/src/strategies/remux/index.ts +14 -2
- package/src/strategies/remux/pipeline.ts +72 -12
- package/src/subtitles/render.ts +8 -0
- package/src/types.ts +32 -0
- package/src/util/libav-demux.ts +405 -0
- package/src/util/time-ranges.ts +40 -0
- package/dist/chunk-2PGRFCWB.js.map +0 -1
- package/dist/chunk-6UUT4BEA.cjs.map +0 -1
- package/dist/chunk-7RGG6ME7.cjs.map +0 -1
- package/dist/chunk-NV7ILLWH.js.map +0 -1
- package/dist/chunk-QQXBPW72.js.map +0 -1
- package/dist/chunk-XKPSTC34.cjs.map +0 -1
- package/dist/source-73CAH6HW.cjs +0 -28
- package/dist/source-F656KYYV.js +0 -3
- package/dist/source-QJR3OHTW.js +0 -3
- package/dist/source-VB74JQ7Z.cjs +0 -28
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/subtitles/srt.ts","../src/subtitles/vtt.ts","../src/subtitles/render.ts","../src/subtitles/index.ts"],"names":["fetchWith"],"mappings":"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","file":"chunk-S4WAZC2T.cjs","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 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"]}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-SR3MPV4D.js';
|
|
2
|
+
import { normalizeSource } from './chunk-CPJLFFCC.js';
|
|
3
|
+
import { sanitizePacketTimestamp } from './chunk-X2K3GIWE.js';
|
|
4
|
+
import { prepareLibavInput } from './chunk-DCSOQH2N.js';
|
|
5
|
+
|
|
6
|
+
function isAnnexB(bytes) {
|
|
7
|
+
if (bytes.length < 3) return false;
|
|
8
|
+
if (bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 1) return true;
|
|
9
|
+
if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 0 && bytes[3] === 1) return true;
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
function* iterateAnnexBNalus(bytes) {
|
|
13
|
+
const length = bytes.length;
|
|
14
|
+
let i = 0;
|
|
15
|
+
let nalStart = -1;
|
|
16
|
+
while (i < length) {
|
|
17
|
+
let scLen = 0;
|
|
18
|
+
if (i + 3 < length && bytes[i] === 0 && bytes[i + 1] === 0 && bytes[i + 2] === 0 && bytes[i + 3] === 1) {
|
|
19
|
+
scLen = 4;
|
|
20
|
+
} else if (i + 2 < length && bytes[i] === 0 && bytes[i + 1] === 0 && bytes[i + 2] === 1) {
|
|
21
|
+
scLen = 3;
|
|
22
|
+
}
|
|
23
|
+
if (scLen > 0) {
|
|
24
|
+
if (nalStart >= 0) {
|
|
25
|
+
yield bytes.subarray(nalStart, i);
|
|
26
|
+
}
|
|
27
|
+
nalStart = i + scLen;
|
|
28
|
+
i += scLen;
|
|
29
|
+
} else {
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (nalStart >= 0 && nalStart < length) {
|
|
34
|
+
yield bytes.subarray(nalStart, length);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function annexBToAvcc(annexB) {
|
|
38
|
+
const nalus = [];
|
|
39
|
+
let total = 0;
|
|
40
|
+
for (const nal of iterateAnnexBNalus(annexB)) {
|
|
41
|
+
nalus.push(nal);
|
|
42
|
+
total += 4 + nal.length;
|
|
43
|
+
}
|
|
44
|
+
const out = new Uint8Array(total);
|
|
45
|
+
let off = 0;
|
|
46
|
+
for (const nal of nalus) {
|
|
47
|
+
const len = nal.length;
|
|
48
|
+
out[off++] = len >>> 24 & 255;
|
|
49
|
+
out[off++] = len >>> 16 & 255;
|
|
50
|
+
out[off++] = len >>> 8 & 255;
|
|
51
|
+
out[off++] = len & 255;
|
|
52
|
+
out.set(nal, off);
|
|
53
|
+
off += len;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/convert/remux.ts
|
|
59
|
+
var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
60
|
+
"mp4",
|
|
61
|
+
"mov",
|
|
62
|
+
"mkv",
|
|
63
|
+
"webm",
|
|
64
|
+
"ogg",
|
|
65
|
+
"wav",
|
|
66
|
+
"mp3",
|
|
67
|
+
"flac",
|
|
68
|
+
"adts"
|
|
69
|
+
]);
|
|
70
|
+
async function remux(source, options = {}) {
|
|
71
|
+
const outputFormat = options.outputFormat ?? "mp4";
|
|
72
|
+
options.signal?.throwIfAborted();
|
|
73
|
+
const ctx = await probe(source);
|
|
74
|
+
options.signal?.throwIfAborted();
|
|
75
|
+
validateRemuxEligibility(ctx, options.strict ?? false);
|
|
76
|
+
if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
|
|
77
|
+
return remuxViaMediAbunny(ctx, outputFormat, options);
|
|
78
|
+
}
|
|
79
|
+
return remuxViaLibav(ctx, outputFormat, options);
|
|
80
|
+
}
|
|
81
|
+
function validateRemuxEligibility(ctx, strict) {
|
|
82
|
+
const video = ctx.videoTracks[0];
|
|
83
|
+
const audio = ctx.audioTracks[0];
|
|
84
|
+
if (video) {
|
|
85
|
+
const mbCodec = avbridgeVideoToMediabunny(video.codec);
|
|
86
|
+
if (!mbCodec) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (audio) {
|
|
93
|
+
const mbCodec = avbridgeAudioToMediabunny(audio.codec);
|
|
94
|
+
if (!mbCodec) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (strict && video?.codec === "h264" && audio?.codec === "mp3") {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Cannot remux in strict mode: H.264 + MP3 is a best-effort combination that may produce playback issues in some browsers. Set strict: false to allow, or use transcode() to re-encode audio to AAC.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!video && !audio) {
|
|
106
|
+
throw new Error("Cannot remux: source has no video or audio tracks.");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function remuxViaMediAbunny(ctx, outputFormat, options) {
|
|
110
|
+
const mb = await import('mediabunny');
|
|
111
|
+
const input = new mb.Input({
|
|
112
|
+
source: await buildMediabunnySourceFromInput(mb, ctx.source),
|
|
113
|
+
formats: mb.ALL_FORMATS
|
|
114
|
+
});
|
|
115
|
+
const target = new mb.BufferTarget();
|
|
116
|
+
const output = new mb.Output({
|
|
117
|
+
format: createOutputFormat(mb, outputFormat),
|
|
118
|
+
target
|
|
119
|
+
});
|
|
120
|
+
const conversion = await mb.Conversion.init({
|
|
121
|
+
input,
|
|
122
|
+
output,
|
|
123
|
+
showWarnings: false
|
|
124
|
+
});
|
|
125
|
+
if (!conversion.isValid) {
|
|
126
|
+
const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
|
|
127
|
+
throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);
|
|
128
|
+
}
|
|
129
|
+
if (options.onProgress) {
|
|
130
|
+
const onProgress = options.onProgress;
|
|
131
|
+
conversion.onProgress = (p) => {
|
|
132
|
+
onProgress({ percent: p * 100, bytesWritten: 0 });
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
let abortHandler;
|
|
136
|
+
if (options.signal) {
|
|
137
|
+
options.signal.throwIfAborted();
|
|
138
|
+
abortHandler = () => void conversion.cancel();
|
|
139
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
await conversion.execute();
|
|
143
|
+
} finally {
|
|
144
|
+
if (abortHandler && options.signal) {
|
|
145
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (!target.buffer) {
|
|
149
|
+
throw new Error("Remux failed: mediabunny produced no output buffer.");
|
|
150
|
+
}
|
|
151
|
+
const mimeType = mimeForFormat(outputFormat);
|
|
152
|
+
const blob = new Blob([target.buffer], { type: mimeType });
|
|
153
|
+
const filename = generateFilename(ctx.name, outputFormat);
|
|
154
|
+
options.onProgress?.({ percent: 100, bytesWritten: blob.size });
|
|
155
|
+
return {
|
|
156
|
+
blob,
|
|
157
|
+
mimeType,
|
|
158
|
+
container: outputFormat,
|
|
159
|
+
videoCodec: ctx.videoTracks[0]?.codec,
|
|
160
|
+
audioCodec: ctx.audioTracks[0]?.codec,
|
|
161
|
+
duration: ctx.duration,
|
|
162
|
+
filename
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function remuxViaLibav(ctx, outputFormat, options) {
|
|
166
|
+
let loadLibav;
|
|
167
|
+
let pickLibavVariant;
|
|
168
|
+
try {
|
|
169
|
+
const loader = await import('./libav-loader-27RDIN2I.js');
|
|
170
|
+
const routing = await import('./variant-routing-434STYAB.js');
|
|
171
|
+
loadLibav = loader.loadLibav;
|
|
172
|
+
pickLibavVariant = routing.pickLibavVariant;
|
|
173
|
+
} catch {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Cannot remux ${ctx.container.toUpperCase()} source: libav.js is not available. Install @libav.js/variant-webcodecs and libavjs-webcodecs-bridge, or build the custom avbridge variant with scripts/build-libav.sh.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const variant = pickLibavVariant(ctx);
|
|
179
|
+
const libav = await loadLibav(variant);
|
|
180
|
+
const normalized = await normalizeSource(ctx.source);
|
|
181
|
+
const filename = ctx.name ?? `remux-input-${Date.now()}`;
|
|
182
|
+
const handle = await prepareLibavInput(libav, filename, normalized);
|
|
183
|
+
try {
|
|
184
|
+
return await doLibavRemux(libav, filename, ctx, outputFormat, options);
|
|
185
|
+
} finally {
|
|
186
|
+
await handle.detach().catch(() => {
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function doLibavRemux(libav, filename, ctx, outputFormat, options) {
|
|
191
|
+
const mb = await import('mediabunny');
|
|
192
|
+
const readPkt = await libav.av_packet_alloc();
|
|
193
|
+
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename);
|
|
194
|
+
const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
|
|
195
|
+
const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
|
|
196
|
+
const videoTrackInfo = ctx.videoTracks[0];
|
|
197
|
+
const audioTrackInfo = ctx.audioTracks[0];
|
|
198
|
+
const mbVideoCodec = videoTrackInfo ? avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
|
|
199
|
+
const mbAudioCodec = audioTrackInfo ? avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
|
|
200
|
+
const target = new mb.BufferTarget();
|
|
201
|
+
const output = new mb.Output({
|
|
202
|
+
format: createOutputFormat(mb, outputFormat),
|
|
203
|
+
target
|
|
204
|
+
});
|
|
205
|
+
let videoSource = null;
|
|
206
|
+
let audioSource = null;
|
|
207
|
+
if (mbVideoCodec && videoStream) {
|
|
208
|
+
videoSource = new mb.EncodedVideoPacketSource(mbVideoCodec);
|
|
209
|
+
output.addVideoTrack(videoSource);
|
|
210
|
+
}
|
|
211
|
+
if (mbAudioCodec && audioStream) {
|
|
212
|
+
audioSource = new mb.EncodedAudioPacketSource(mbAudioCodec);
|
|
213
|
+
output.addAudioTrack(audioSource);
|
|
214
|
+
}
|
|
215
|
+
await output.start();
|
|
216
|
+
const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
|
|
217
|
+
const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
|
|
218
|
+
let syntheticVideoUs = 0;
|
|
219
|
+
let syntheticAudioUs = 0;
|
|
220
|
+
const videoTimeBase = videoStream?.time_base_num && videoStream?.time_base_den ? [videoStream.time_base_num, videoStream.time_base_den] : void 0;
|
|
221
|
+
const audioTimeBase = audioStream?.time_base_num && audioStream?.time_base_den ? [audioStream.time_base_num, audioStream.time_base_den] : void 0;
|
|
222
|
+
let totalPackets = 0;
|
|
223
|
+
const durationUs = ctx.duration ? ctx.duration * 1e6 : 0;
|
|
224
|
+
let firstVideoMeta = true;
|
|
225
|
+
let firstAudioMeta = true;
|
|
226
|
+
while (true) {
|
|
227
|
+
options.signal?.throwIfAborted();
|
|
228
|
+
let readErr;
|
|
229
|
+
let packets;
|
|
230
|
+
try {
|
|
231
|
+
[readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
|
|
232
|
+
limit: 64 * 1024
|
|
233
|
+
});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
throw new Error(`libav demux failed: ${err.message}`);
|
|
236
|
+
}
|
|
237
|
+
const videoPackets = videoStream ? packets[videoStream.index] ?? [] : [];
|
|
238
|
+
const audioPackets = audioStream ? packets[audioStream.index] ?? [] : [];
|
|
239
|
+
if (videoSource) {
|
|
240
|
+
for (const pkt of videoPackets) {
|
|
241
|
+
sanitizePacketTimestamp(pkt, () => {
|
|
242
|
+
const ts = syntheticVideoUs;
|
|
243
|
+
syntheticVideoUs += videoFrameStepUs;
|
|
244
|
+
return ts;
|
|
245
|
+
}, videoTimeBase);
|
|
246
|
+
if (videoTrackInfo && (videoTrackInfo.codec === "h264" || videoTrackInfo.codec === "h265") && isAnnexB(pkt.data)) {
|
|
247
|
+
pkt.data = annexBToAvcc(pkt.data);
|
|
248
|
+
}
|
|
249
|
+
const mbPacket = libavPacketToMediAbunny(mb, pkt);
|
|
250
|
+
await videoSource.add(
|
|
251
|
+
mbPacket,
|
|
252
|
+
firstVideoMeta ? { decoderConfig: buildVideoDecoderConfig(videoTrackInfo) } : void 0
|
|
253
|
+
);
|
|
254
|
+
firstVideoMeta = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (audioSource) {
|
|
258
|
+
for (const pkt of audioPackets) {
|
|
259
|
+
sanitizePacketTimestamp(pkt, () => {
|
|
260
|
+
const ts = syntheticAudioUs;
|
|
261
|
+
const sampleRate = audioTrackInfo?.sampleRate ?? 44100;
|
|
262
|
+
syntheticAudioUs += Math.round(1024 * 1e6 / sampleRate);
|
|
263
|
+
return ts;
|
|
264
|
+
}, audioTimeBase);
|
|
265
|
+
const mbPacket = libavPacketToMediAbunny(mb, pkt);
|
|
266
|
+
await audioSource.add(
|
|
267
|
+
mbPacket,
|
|
268
|
+
firstAudioMeta ? { decoderConfig: buildAudioDecoderConfig(audioTrackInfo) } : void 0
|
|
269
|
+
);
|
|
270
|
+
firstAudioMeta = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
totalPackets += videoPackets.length + audioPackets.length;
|
|
274
|
+
if (options.onProgress && durationUs > 0) {
|
|
275
|
+
const lastVideoTs = videoPackets.length > 0 ? videoPackets[videoPackets.length - 1].pts ?? 0 : 0;
|
|
276
|
+
const lastAudioTs = audioPackets.length > 0 ? audioPackets[audioPackets.length - 1].pts ?? 0 : 0;
|
|
277
|
+
const currentUs = Math.max(lastVideoTs, lastAudioTs);
|
|
278
|
+
const percent = Math.min(99, currentUs / durationUs * 100);
|
|
279
|
+
options.onProgress({ percent, bytesWritten: 0 });
|
|
280
|
+
}
|
|
281
|
+
if (readErr === libav.AVERROR_EOF) break;
|
|
282
|
+
if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {
|
|
283
|
+
console.warn("[avbridge] remux: ff_read_frame_multi returned", readErr);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
await output.finalize();
|
|
288
|
+
try {
|
|
289
|
+
await libav.av_packet_free?.(readPkt);
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
await libav.avformat_close_input_js(fmt_ctx);
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
if (!target.buffer) {
|
|
297
|
+
throw new Error("Remux failed: mediabunny produced no output buffer.");
|
|
298
|
+
}
|
|
299
|
+
const mimeType = mimeForFormat(outputFormat);
|
|
300
|
+
const blob = new Blob([target.buffer], { type: mimeType });
|
|
301
|
+
const outputFilename = generateFilename(ctx.name, outputFormat);
|
|
302
|
+
options.onProgress?.({ percent: 100, bytesWritten: blob.size });
|
|
303
|
+
return {
|
|
304
|
+
blob,
|
|
305
|
+
mimeType,
|
|
306
|
+
container: outputFormat,
|
|
307
|
+
videoCodec: videoTrackInfo?.codec,
|
|
308
|
+
audioCodec: audioTrackInfo?.codec,
|
|
309
|
+
duration: ctx.duration,
|
|
310
|
+
filename: outputFilename
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function createOutputFormat(mb, format) {
|
|
314
|
+
switch (format) {
|
|
315
|
+
case "mp4":
|
|
316
|
+
return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
|
|
317
|
+
case "webm":
|
|
318
|
+
return new mb.WebMOutputFormat();
|
|
319
|
+
case "mkv":
|
|
320
|
+
return new mb.MkvOutputFormat();
|
|
321
|
+
default:
|
|
322
|
+
return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function mimeForFormat(format) {
|
|
326
|
+
switch (format) {
|
|
327
|
+
case "mp4":
|
|
328
|
+
return "video/mp4";
|
|
329
|
+
case "webm":
|
|
330
|
+
return "video/webm";
|
|
331
|
+
case "mkv":
|
|
332
|
+
return "video/x-matroska";
|
|
333
|
+
default:
|
|
334
|
+
return "application/octet-stream";
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function generateFilename(originalName, format) {
|
|
338
|
+
const ext = format === "mkv" ? "mkv" : format;
|
|
339
|
+
if (!originalName) return `output.${ext}`;
|
|
340
|
+
const base = originalName.replace(/\.[^.]+$/, "");
|
|
341
|
+
return `${base}.${ext}`;
|
|
342
|
+
}
|
|
343
|
+
var _seqCounter = 0;
|
|
344
|
+
function libavPacketToMediAbunny(mb, pkt) {
|
|
345
|
+
const KEY_FRAME_FLAG = 1;
|
|
346
|
+
const timestampSec = (pkt.pts ?? 0) / 1e6;
|
|
347
|
+
const durationSec = (pkt.duration ?? 0) / 1e6;
|
|
348
|
+
const type = pkt.flags & KEY_FRAME_FLAG ? "key" : "delta";
|
|
349
|
+
return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);
|
|
350
|
+
}
|
|
351
|
+
function buildVideoDecoderConfig(track) {
|
|
352
|
+
return {
|
|
353
|
+
codec: track.codecString ?? track.codec,
|
|
354
|
+
codedWidth: track.width,
|
|
355
|
+
codedHeight: track.height
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function buildAudioDecoderConfig(track) {
|
|
359
|
+
return {
|
|
360
|
+
codec: track.codecString ?? track.codec,
|
|
361
|
+
numberOfChannels: track.channels,
|
|
362
|
+
sampleRate: track.sampleRate
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export { createOutputFormat, generateFilename, mimeForFormat, remux, validateRemuxEligibility };
|
|
367
|
+
//# sourceMappingURL=chunk-SMH6IOP2.js.map
|
|
368
|
+
//# sourceMappingURL=chunk-SMH6IOP2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/strategies/remux/annexb.ts","../src/convert/remux.ts"],"names":[],"mappings":"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","file":"chunk-SMH6IOP2.js","sourcesContent":["/**\n * H.264/HEVC bitstream conversion helpers.\n *\n * Demuxers from MP4-family containers (mediabunny) hand us packets in **AVCC**\n * format: each NAL unit prefixed with a 4-byte big-endian length.\n *\n * Demuxers from elementary-stream/AVI/TS hand us **Annex B**: NAL units\n * separated by `00 00 00 01` (or `00 00 01`) start codes.\n *\n * MSE expects AVCC inside fragmented MP4. So when the source side emits Annex\n * B, we need to convert before muxing. Going the other way (AVCC → Annex B) is\n * useful for feeding `VideoDecoder` configured with `description` omitted.\n */\n\nconst START_CODE_4 = new Uint8Array([0, 0, 0, 1]);\n\n/** True if the bytes look like Annex B (start with `00 00 00 01` or `00 00 01`). */\nexport function isAnnexB(bytes: Uint8Array): boolean {\n if (bytes.length < 3) return false;\n if (bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 1) return true;\n if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 0 && bytes[3] === 1) return true;\n return false;\n}\n\n/**\n * Walk an Annex B byte stream and yield each NAL unit (without start code).\n * This is the standard byte-by-byte scan; no SIMD tricks because the typical\n * frame is small.\n */\nexport function* iterateAnnexBNalus(bytes: Uint8Array): Generator<Uint8Array> {\n const length = bytes.length;\n let i = 0;\n let nalStart = -1;\n\n while (i < length) {\n // Look for start code at position i\n let scLen = 0;\n if (i + 3 < length && bytes[i] === 0 && bytes[i + 1] === 0 && bytes[i + 2] === 0 && bytes[i + 3] === 1) {\n scLen = 4;\n } else if (i + 2 < length && bytes[i] === 0 && bytes[i + 1] === 0 && bytes[i + 2] === 1) {\n scLen = 3;\n }\n\n if (scLen > 0) {\n if (nalStart >= 0) {\n yield bytes.subarray(nalStart, i);\n }\n nalStart = i + scLen;\n i += scLen;\n } else {\n i += 1;\n }\n }\n\n if (nalStart >= 0 && nalStart < length) {\n yield bytes.subarray(nalStart, length);\n }\n}\n\n/**\n * Convert an Annex B byte stream to AVCC. Each NALU is prefixed with its\n * 4-byte big-endian length.\n */\nexport function annexBToAvcc(annexB: Uint8Array): Uint8Array {\n const nalus: Uint8Array[] = [];\n let total = 0;\n for (const nal of iterateAnnexBNalus(annexB)) {\n nalus.push(nal);\n total += 4 + nal.length;\n }\n const out = new Uint8Array(total);\n let off = 0;\n for (const nal of nalus) {\n const len = nal.length;\n out[off++] = (len >>> 24) & 0xff;\n out[off++] = (len >>> 16) & 0xff;\n out[off++] = (len >>> 8) & 0xff;\n out[off++] = len & 0xff;\n out.set(nal, off);\n off += len;\n }\n return out;\n}\n\n/**\n * Convert AVCC (4-byte length-prefixed) NALUs to Annex B. Each NALU is\n * prefixed with `00 00 00 01`.\n */\nexport function avccToAnnexB(avcc: Uint8Array): Uint8Array {\n const out: Uint8Array[] = [];\n let total = 0;\n let i = 0;\n while (i + 4 <= avcc.length) {\n const len =\n (avcc[i] << 24) | (avcc[i + 1] << 16) | (avcc[i + 2] << 8) | avcc[i + 3];\n i += 4;\n if (i + len > avcc.length) {\n throw new Error(`avccToAnnexB: NAL length ${len} overflows buffer at offset ${i}`);\n }\n out.push(START_CODE_4);\n out.push(avcc.subarray(i, i + len));\n total += 4 + len;\n i += len;\n }\n const merged = new Uint8Array(total);\n let off = 0;\n for (const chunk of out) {\n merged.set(chunk, off);\n off += chunk.length;\n }\n return merged;\n}\n","/**\n * Standalone remux function: repackage media into a modern container without\n * re-encoding. Input can be any format avbridge can probe; output is a\n * finalized downloadable file (MP4, WebM, or MKV).\n *\n * Two internal paths:\n * - **Path A** (mediabunny-readable containers): wraps mediabunny's Conversion\n * class for MP4/MKV/WebM/OGG/MOV/WAV/MP3/FLAC/ADTS sources.\n * - **Path B** (AVI/ASF/FLV): libav.js demux + mediabunny mux via manual\n * packet pump. Lazy-loads libav.js — zero cost if unused.\n */\n\nimport { probe } from \"../probe/index.js\";\nimport { isAnnexB, annexBToAvcc } from \"../strategies/remux/annexb.js\";\nimport {\n avbridgeVideoToMediabunny,\n avbridgeAudioToMediabunny,\n buildMediabunnySourceFromInput,\n} from \"../probe/mediabunny.js\";\nimport { normalizeSource } from \"../util/source.js\";\nimport { sanitizePacketTimestamp } from \"../util/libav-demux.js\";\nimport { prepareLibavInput, type LibavInputHandle } from \"../util/libav-http-reader.js\";\nimport type {\n MediaInput,\n MediaContext,\n ConvertOptions,\n ConvertResult,\n OutputFormat,\n} from \"../types.js\";\n\n/** Containers mediabunny can read (and therefore use Conversion for). */\nconst MEDIABUNNY_CONTAINERS = new Set([\n \"mp4\", \"mov\", \"mkv\", \"webm\", \"ogg\", \"wav\", \"mp3\", \"flac\", \"adts\",\n]);\n\n/**\n * Remux a media source into a modern container format without re-encoding.\n *\n * @throws When the source codecs cannot be remuxed (e.g. WMV3 — use `transcode()` instead).\n * @throws When an AVI/ASF/FLV source is provided but libav.js is not installed.\n */\nexport async function remux(\n source: MediaInput,\n options: ConvertOptions = {},\n): Promise<ConvertResult> {\n const outputFormat = options.outputFormat ?? \"mp4\";\n options.signal?.throwIfAborted();\n\n // Probe the source\n const ctx = await probe(source);\n options.signal?.throwIfAborted();\n\n // Validate remux eligibility: all codecs must map to mediabunny output codecs\n validateRemuxEligibility(ctx, options.strict ?? false);\n\n // Route to the appropriate path\n if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {\n return remuxViaMediAbunny(ctx, outputFormat, options);\n }\n return remuxViaLibav(ctx, outputFormat, options);\n}\n\n// ── Eligibility validation ──────────────────────────────────────────────────\n\n/** @internal Exported for testing. */\nexport function validateRemuxEligibility(ctx: MediaContext, strict: boolean): void {\n const video = ctx.videoTracks[0];\n const audio = ctx.audioTracks[0];\n\n if (video) {\n const mbCodec = avbridgeVideoToMediabunny(video.codec);\n if (!mbCodec) {\n throw new Error(\n `Cannot remux: video codec \"${video.codec}\" is not supported for remuxing. ` +\n `Use transcode() to re-encode to a modern codec.`,\n );\n }\n }\n\n if (audio) {\n const mbCodec = avbridgeAudioToMediabunny(audio.codec);\n if (!mbCodec) {\n throw new Error(\n `Cannot remux: audio codec \"${audio.codec}\" is not supported for remuxing. ` +\n `Use transcode() to re-encode to a modern codec.`,\n );\n }\n }\n\n if (strict && video?.codec === \"h264\" && audio?.codec === \"mp3\") {\n throw new Error(\n `Cannot remux in strict mode: H.264 + MP3 is a best-effort combination ` +\n `that may produce playback issues in some browsers. ` +\n `Set strict: false to allow, or use transcode() to re-encode audio to AAC.`,\n );\n }\n\n if (!video && !audio) {\n throw new Error(\"Cannot remux: source has no video or audio tracks.\");\n }\n}\n\n// ── Path A: mediabunny Conversion ───────────────────────────────────────────\n\nasync function remuxViaMediAbunny(\n ctx: MediaContext,\n outputFormat: OutputFormat,\n options: ConvertOptions,\n): Promise<ConvertResult> {\n const mb = await import(\"mediabunny\");\n\n const input = new mb.Input({\n source: await buildMediabunnySourceFromInput(mb, ctx.source),\n formats: mb.ALL_FORMATS,\n });\n\n const target = new mb.BufferTarget();\n const output = new mb.Output({\n format: createOutputFormat(mb, outputFormat),\n target,\n });\n\n const conversion = await mb.Conversion.init({\n input,\n output,\n showWarnings: false,\n });\n\n if (!conversion.isValid) {\n const reasons = conversion.discardedTracks\n .map((d) => `${d.track.type} track discarded: ${d.reason}`)\n .join(\"; \");\n throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);\n }\n\n // Wire progress\n if (options.onProgress) {\n const onProgress = options.onProgress;\n conversion.onProgress = (p) => {\n onProgress({ percent: p * 100, bytesWritten: 0 });\n };\n }\n\n // Wire cancellation\n let abortHandler: (() => void) | undefined;\n if (options.signal) {\n options.signal.throwIfAborted();\n abortHandler = () => void conversion.cancel();\n options.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n await conversion.execute();\n } finally {\n if (abortHandler && options.signal) {\n options.signal.removeEventListener(\"abort\", abortHandler);\n }\n }\n\n if (!target.buffer) {\n throw new Error(\"Remux failed: mediabunny produced no output buffer.\");\n }\n\n const mimeType = mimeForFormat(outputFormat);\n const blob = new Blob([target.buffer], { type: mimeType });\n const filename = generateFilename(ctx.name, outputFormat);\n\n options.onProgress?.({ percent: 100, bytesWritten: blob.size });\n\n return {\n blob,\n mimeType,\n container: outputFormat,\n videoCodec: ctx.videoTracks[0]?.codec,\n audioCodec: ctx.audioTracks[0]?.codec,\n duration: ctx.duration,\n filename,\n };\n}\n\n// ── Path B: libav.js demux + mediabunny mux (AVI/ASF/FLV) ──────────────────\n\nasync function remuxViaLibav(\n ctx: MediaContext,\n outputFormat: OutputFormat,\n options: ConvertOptions,\n): Promise<ConvertResult> {\n // Lazy-load libav\n let loadLibav: typeof import(\"../strategies/fallback/libav-loader.js\").loadLibav;\n let pickLibavVariant: typeof import(\"../strategies/fallback/variant-routing.js\").pickLibavVariant;\n try {\n const loader = await import(\"../strategies/fallback/libav-loader.js\");\n const routing = await import(\"../strategies/fallback/variant-routing.js\");\n loadLibav = loader.loadLibav;\n pickLibavVariant = routing.pickLibavVariant;\n } catch {\n throw new Error(\n `Cannot remux ${ctx.container.toUpperCase()} source: libav.js is not available. ` +\n `Install @libav.js/variant-webcodecs and libavjs-webcodecs-bridge, ` +\n `or build the custom avbridge variant with scripts/build-libav.sh.`,\n );\n }\n\n const variant = pickLibavVariant(ctx);\n const libav = await loadLibav(variant) as unknown as LibavRuntime;\n\n // For Blob/File inputs, libav reads from an in-memory readahead file.\n // For URL inputs, libav demuxes via HTTP Range requests through the\n // block reader — no full download.\n const normalized = await normalizeSource(ctx.source);\n const filename = ctx.name ?? `remux-input-${Date.now()}`;\n const handle: LibavInputHandle = await prepareLibavInput(libav as unknown as Parameters<typeof prepareLibavInput>[0], filename, normalized);\n\n try {\n return await doLibavRemux(libav, filename, ctx, outputFormat, options);\n } finally {\n await handle.detach().catch(() => {});\n }\n}\n\nasync function doLibavRemux(\n libav: LibavRuntime,\n filename: string,\n ctx: MediaContext,\n outputFormat: OutputFormat,\n options: ConvertOptions,\n): Promise<ConvertResult> {\n const mb = await import(\"mediabunny\");\n\n const readPkt = await libav.av_packet_alloc();\n const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename);\n const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;\n const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;\n\n // Map codecs to mediabunny output\n const videoTrackInfo = ctx.videoTracks[0];\n const audioTrackInfo = ctx.audioTracks[0];\n const mbVideoCodec = videoTrackInfo ? avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;\n const mbAudioCodec = audioTrackInfo ? avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;\n\n // Set up mediabunny output with BufferTarget\n const target = new mb.BufferTarget();\n const output = new mb.Output({\n format: createOutputFormat(mb, outputFormat),\n target,\n });\n\n let videoSource: InstanceType<typeof mb.EncodedVideoPacketSource> | null = null;\n let audioSource: InstanceType<typeof mb.EncodedAudioPacketSource> | null = null;\n\n if (mbVideoCodec && videoStream) {\n videoSource = new mb.EncodedVideoPacketSource(mbVideoCodec);\n output.addVideoTrack(videoSource);\n }\n if (mbAudioCodec && audioStream) {\n type AudioCodecArg = ConstructorParameters<typeof mb.EncodedAudioPacketSource>[0];\n audioSource = new mb.EncodedAudioPacketSource(mbAudioCodec as AudioCodecArg);\n output.addAudioTrack(audioSource);\n }\n\n await output.start();\n\n // Timestamp tracking for synthetic timestamps\n const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;\n const videoFrameStepUs = Math.max(1, Math.round(1_000_000 / videoFps));\n let syntheticVideoUs = 0;\n let syntheticAudioUs = 0;\n\n const videoTimeBase: [number, number] | undefined =\n videoStream?.time_base_num && videoStream?.time_base_den\n ? [videoStream.time_base_num, videoStream.time_base_den]\n : undefined;\n const audioTimeBase: [number, number] | undefined =\n audioStream?.time_base_num && audioStream?.time_base_den\n ? [audioStream.time_base_num, audioStream.time_base_den]\n : undefined;\n\n let totalPackets = 0;\n const durationUs = ctx.duration ? ctx.duration * 1_000_000 : 0;\n let firstVideoMeta = true;\n let firstAudioMeta = true;\n\n // Pump loop: read packets from libav, feed to mediabunny output\n while (true) {\n options.signal?.throwIfAborted();\n\n let readErr: number;\n let packets: Record<number, LibavPacket[]>;\n try {\n [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {\n limit: 64 * 1024,\n });\n } catch (err) {\n throw new Error(`libav demux failed: ${(err as Error).message}`);\n }\n\n const videoPackets = videoStream ? packets[videoStream.index] ?? [] : [];\n const audioPackets = audioStream ? packets[audioStream.index] ?? [] : [];\n\n // Feed video packets\n if (videoSource) {\n for (const pkt of videoPackets) {\n sanitizePacketTimestamp(pkt, () => {\n const ts = syntheticVideoUs;\n syntheticVideoUs += videoFrameStepUs;\n return ts;\n }, videoTimeBase);\n\n // libav demuxes AVI/ASF/FLV H.264 as Annex B (start-code framed),\n // but mediabunny's fMP4 muxer expects AVCC (length-prefixed). Convert\n // on the fly. The check is cheap: isAnnexB reads 4 bytes at the head.\n if (videoTrackInfo && (videoTrackInfo.codec === \"h264\" || videoTrackInfo.codec === \"h265\") && isAnnexB(pkt.data)) {\n pkt.data = annexBToAvcc(pkt.data);\n }\n\n const mbPacket = libavPacketToMediAbunny(mb, pkt);\n await videoSource.add(\n mbPacket,\n firstVideoMeta ? { decoderConfig: buildVideoDecoderConfig(videoTrackInfo!) } : undefined,\n );\n firstVideoMeta = false;\n }\n }\n\n // Feed audio packets\n if (audioSource) {\n for (const pkt of audioPackets) {\n sanitizePacketTimestamp(pkt, () => {\n const ts = syntheticAudioUs;\n const sampleRate = audioTrackInfo?.sampleRate ?? 44100;\n syntheticAudioUs += Math.round(1024 * 1_000_000 / sampleRate);\n return ts;\n }, audioTimeBase);\n\n const mbPacket = libavPacketToMediAbunny(mb, pkt);\n await audioSource.add(\n mbPacket,\n firstAudioMeta ? { decoderConfig: buildAudioDecoderConfig(audioTrackInfo!) } : undefined,\n );\n firstAudioMeta = false;\n }\n }\n\n totalPackets += videoPackets.length + audioPackets.length;\n\n // Report progress\n if (options.onProgress && durationUs > 0) {\n const lastVideoTs = videoPackets.length > 0 ? videoPackets[videoPackets.length - 1].pts ?? 0 : 0;\n const lastAudioTs = audioPackets.length > 0 ? audioPackets[audioPackets.length - 1].pts ?? 0 : 0;\n const currentUs = Math.max(lastVideoTs, lastAudioTs);\n const percent = Math.min(99, (currentUs / durationUs) * 100);\n options.onProgress({ percent, bytesWritten: 0 });\n }\n\n if (readErr === libav.AVERROR_EOF) break;\n if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {\n console.warn(\"[avbridge] remux: ff_read_frame_multi returned\", readErr);\n break;\n }\n }\n\n await output.finalize();\n\n // Cleanup libav resources\n try { await libav.av_packet_free?.(readPkt); } catch { /* ignore */ }\n try { await libav.avformat_close_input_js(fmt_ctx); } catch { /* ignore */ }\n\n if (!target.buffer) {\n throw new Error(\"Remux failed: mediabunny produced no output buffer.\");\n }\n\n const mimeType = mimeForFormat(outputFormat);\n const blob = new Blob([target.buffer], { type: mimeType });\n const outputFilename = generateFilename(ctx.name, outputFormat);\n\n options.onProgress?.({ percent: 100, bytesWritten: blob.size });\n\n return {\n blob,\n mimeType,\n container: outputFormat,\n videoCodec: videoTrackInfo?.codec,\n audioCodec: audioTrackInfo?.codec,\n duration: ctx.duration,\n filename: outputFilename,\n };\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\n/** @internal Exported for use by transcode(). */\nexport function createOutputFormat(\n mb: typeof import(\"mediabunny\"),\n format: OutputFormat,\n) {\n switch (format) {\n case \"mp4\": return new mb.Mp4OutputFormat({ fastStart: \"in-memory\" });\n case \"webm\": return new mb.WebMOutputFormat();\n case \"mkv\": return new mb.MkvOutputFormat();\n default: return new mb.Mp4OutputFormat({ fastStart: \"in-memory\" });\n }\n}\n\n/** @internal Exported for testing. */\nexport function mimeForFormat(format: OutputFormat): string {\n switch (format) {\n case \"mp4\": return \"video/mp4\";\n case \"webm\": return \"video/webm\";\n case \"mkv\": return \"video/x-matroska\";\n default: return \"application/octet-stream\";\n }\n}\n\n/** @internal Exported for testing. */\nexport function generateFilename(originalName: string | undefined, format: OutputFormat): string {\n const ext = format === \"mkv\" ? \"mkv\" : format;\n if (!originalName) return `output.${ext}`;\n const base = originalName.replace(/\\.[^.]+$/, \"\");\n return `${base}.${ext}`;\n}\n\n/** Sequence counter for decode-order numbering in mediabunny packets. */\nlet _seqCounter = 0;\n\n/**\n * Convert a libav packet to a mediabunny EncodedPacket.\n * Timestamps from libav are in microseconds (after sanitization); mediabunny wants seconds.\n */\nfunction libavPacketToMediAbunny(\n mb: typeof import(\"mediabunny\"),\n pkt: LibavPacket,\n): InstanceType<typeof mb.EncodedPacket> {\n const KEY_FRAME_FLAG = 0x0001;\n const timestampSec = (pkt.pts ?? 0) / 1_000_000;\n const durationSec = (pkt.duration ?? 0) / 1_000_000;\n const type = (pkt.flags & KEY_FRAME_FLAG) ? \"key\" as const : \"delta\" as const;\n return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);\n}\n\nfunction buildVideoDecoderConfig(track: { codec: string; width: number; height: number; codecString?: string }) {\n return {\n codec: track.codecString ?? track.codec,\n codedWidth: track.width,\n codedHeight: track.height,\n };\n}\n\nfunction buildAudioDecoderConfig(track: { codec: string; channels: number; sampleRate: number; codecString?: string }) {\n return {\n codec: track.codecString ?? track.codec,\n numberOfChannels: track.channels,\n sampleRate: track.sampleRate,\n };\n}\n\n// ── Structural types ────────────────────────────────────────────────────────\n\ninterface LibavPacket {\n data: Uint8Array;\n pts: number;\n ptshi?: number;\n duration?: number;\n durationhi?: number;\n flags: number;\n stream_index: number;\n time_base_num?: number;\n time_base_den?: number;\n}\n\ninterface LibavStream {\n index: number;\n codec_type: number;\n codec_id: number;\n codecpar: number;\n time_base_num?: number;\n time_base_den?: number;\n}\n\ninterface LibavRuntime {\n AVMEDIA_TYPE_VIDEO: number;\n AVMEDIA_TYPE_AUDIO: number;\n AVERROR_EOF: number;\n EAGAIN: number;\n\n mkreadaheadfile(name: string, blob: Blob): Promise<void>;\n unlinkreadaheadfile(name: string): Promise<void>;\n ff_init_demuxer_file(name: string): Promise<[number, LibavStream[]]>;\n ff_read_frame_multi(\n fmt_ctx: number,\n pkt: number,\n opts?: { limit?: number },\n ): Promise<[number, Record<number, LibavPacket[]>]>;\n av_packet_alloc(): Promise<number>;\n av_packet_free?(pkt: number): Promise<void>;\n avformat_close_input_js(ctx: number): Promise<void>;\n}\n"]}
|