@bendyline/squisq-video-react 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -5
- package/dist/chunk-6DKLHXX3.js +47 -0
- package/dist/chunk-6DKLHXX3.js.map +1 -0
- package/dist/index.d.ts +120 -38
- package/dist/index.js +320 -25
- package/dist/index.js.map +1 -1
- package/dist/workers/encode.worker.js +3 -20
- package/dist/workers/encode.worker.js.map +1 -1
- package/package.json +8 -5
- package/src/VideoExportButton.tsx +14 -2
- package/src/VideoExportModal.tsx +37 -7
- package/src/__tests__/audioTrack.test.ts +108 -0
- package/src/__tests__/mp4MuxAudio.test.ts +72 -0
- package/src/__tests__/videoExportProps.test.tsx +73 -0
- package/src/audioTrack.ts +323 -0
- package/src/hooks/useFrameCapture.ts +1 -1
- package/src/hooks/useVideoExport.ts +187 -6
- package/src/index.ts +5 -2
- package/src/mainThreadEncoder.ts +26 -13
- package/src/mp4Mux.ts +77 -3
- package/src/workers/encode.worker.ts +6 -21
- package/dist/chunk-LZYWP4HB.js +0 -28
- package/dist/chunk-LZYWP4HB.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @bendyline/squisq-video-react
|
|
2
2
|
|
|
3
|
-
React components and hooks for exporting Squisq documents to MP4 video directly in the browser. Uses WebCodecs for hardware-accelerated H.264 encoding with html2canvas for frame capture.
|
|
3
|
+
React components and hooks for exporting Squisq documents to MP4 video directly in the browser. Uses WebCodecs for hardware-accelerated H.264 encoding (with an ffmpeg.wasm worker fallback) and html2canvas for frame capture. As of v1.5 the exported MP4 also carries an **audio** track (narration + timed media).
|
|
4
4
|
|
|
5
5
|
Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
|
|
6
6
|
|
|
@@ -27,6 +27,11 @@ function App() {
|
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
**v1.5:** `playerScript` is now **optional** — the browser export captures frames
|
|
31
|
+
from a live in-page `DocPlayer`, so the standalone bundle is only needed for
|
|
32
|
+
CLI/Playwright-style pipelines. A new `defaultConfig?: Partial<VideoExportConfig>`
|
|
33
|
+
prop seeds the modal's initial quality/fps/orientation/caption selections.
|
|
34
|
+
|
|
30
35
|
### Full Export Modal
|
|
31
36
|
|
|
32
37
|
```tsx
|
|
@@ -85,10 +90,13 @@ function CustomExport({ doc, images, audio }) {
|
|
|
85
90
|
const {
|
|
86
91
|
state, // 'idle' | 'preparing' | 'capturing' | 'encoding' | 'complete' | 'error'
|
|
87
92
|
progress, // 0–100
|
|
93
|
+
backend, // 'webcodecs' | 'ffmpeg-wasm' | null
|
|
88
94
|
elapsed,
|
|
89
95
|
estimatedRemaining,
|
|
90
96
|
downloadUrl,
|
|
91
97
|
fileSize,
|
|
98
|
+
audioIncluded, // whether an audio track was muxed in
|
|
99
|
+
audioSkippedReason, // null when the doc had no audio; a string explains a shortfall
|
|
92
100
|
error,
|
|
93
101
|
startExport,
|
|
94
102
|
cancel,
|
|
@@ -97,7 +105,7 @@ function CustomExport({ doc, images, audio }) {
|
|
|
97
105
|
|
|
98
106
|
return (
|
|
99
107
|
<div>
|
|
100
|
-
<button onClick={() => startExport(
|
|
108
|
+
<button onClick={() => startExport(doc, { images, audio, quality: 'normal', fps: 30 })}>
|
|
101
109
|
Export
|
|
102
110
|
</button>
|
|
103
111
|
{state === 'capturing' && <p>Progress: {progress}%</p>}
|
|
@@ -113,16 +121,34 @@ function CustomExport({ doc, images, audio }) {
|
|
|
113
121
|
|
|
114
122
|
## Browser Requirements
|
|
115
123
|
|
|
116
|
-
WebCodecs H.264 encoding requires Chrome 94+ or Edge 94+.
|
|
124
|
+
WebCodecs H.264 encoding requires Chrome 94+ or Edge 94+. When WebCodecs H.264
|
|
125
|
+
is unavailable, the export automatically falls back to an ffmpeg.wasm worker —
|
|
126
|
+
which requires `SharedArrayBuffer` (i.e. Cross-Origin-Isolation headers on the
|
|
127
|
+
host page). Use `supportsWebCodecs()` to probe at runtime:
|
|
117
128
|
|
|
118
129
|
```ts
|
|
119
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
supportsWebCodecs,
|
|
132
|
+
supportsWebCodecsH264,
|
|
133
|
+
supportsWebCodecsAac,
|
|
134
|
+
} from '@bendyline/squisq-video-react';
|
|
120
135
|
|
|
121
136
|
if (!supportsWebCodecs()) {
|
|
122
|
-
//
|
|
137
|
+
// ffmpeg.wasm fallback will be used (needs Cross-Origin-Isolation)
|
|
123
138
|
}
|
|
124
139
|
```
|
|
125
140
|
|
|
141
|
+
**Audio tiers.** The audio track is muxed via WebCodecs AAC when available
|
|
142
|
+
(`supportsWebCodecsAac()`); otherwise it is skipped and the export reports
|
|
143
|
+
`audioIncluded: false` with an `audioSkippedReason`. Audio problems never fail
|
|
144
|
+
the export — the video always completes. `supportsWebCodecsH264(config)` probes a
|
|
145
|
+
specific encoder configuration; `EncoderConfig` is also exported.
|
|
146
|
+
|
|
147
|
+
## Full API Reference
|
|
148
|
+
|
|
149
|
+
See [docs/API.md](https://github.com/bendyline/squisq/blob/main/docs/API.md)
|
|
150
|
+
for complete prop tables, `VideoExportConfig`, and the encoder utilities.
|
|
151
|
+
|
|
126
152
|
## Related Packages
|
|
127
153
|
|
|
128
154
|
| Package | Description |
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/mp4Mux.ts
|
|
2
|
+
import { Muxer, ArrayBufferTarget } from "mp4-muxer";
|
|
3
|
+
function createMp4Muxer(options) {
|
|
4
|
+
const target = new ArrayBufferTarget();
|
|
5
|
+
const muxer = new Muxer({
|
|
6
|
+
target,
|
|
7
|
+
video: {
|
|
8
|
+
codec: "avc",
|
|
9
|
+
width: options.width,
|
|
10
|
+
height: options.height
|
|
11
|
+
},
|
|
12
|
+
// Only declare the audio track when requested so the video-only
|
|
13
|
+
// configuration stays byte-identical to the historical output.
|
|
14
|
+
...options.audio ? {
|
|
15
|
+
audio: {
|
|
16
|
+
codec: "aac",
|
|
17
|
+
numberOfChannels: options.audio.numberOfChannels,
|
|
18
|
+
sampleRate: options.audio.sampleRate
|
|
19
|
+
}
|
|
20
|
+
} : {},
|
|
21
|
+
fastStart: "in-memory"
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
hasAudioTrack: options.audio !== void 0,
|
|
25
|
+
addVideoChunk(chunk, meta) {
|
|
26
|
+
muxer.addVideoChunk(chunk, meta);
|
|
27
|
+
},
|
|
28
|
+
addVideoChunkRaw(data, type, timestampMicros, durationMicros, meta) {
|
|
29
|
+
muxer.addVideoChunkRaw(data, type, timestampMicros, durationMicros, meta);
|
|
30
|
+
},
|
|
31
|
+
addAudioChunk(chunk, meta) {
|
|
32
|
+
muxer.addAudioChunk(chunk, meta);
|
|
33
|
+
},
|
|
34
|
+
addAudioChunkRaw(data, type, timestampMicros, durationMicros, meta) {
|
|
35
|
+
muxer.addAudioChunkRaw(data, type, timestampMicros, durationMicros, meta);
|
|
36
|
+
},
|
|
37
|
+
finalize() {
|
|
38
|
+
muxer.finalize();
|
|
39
|
+
return target.buffer;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
createMp4Muxer
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=chunk-6DKLHXX3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mp4Mux.ts"],"sourcesContent":["/**\n * mp4Mux — Thin wrapper around mp4-muxer for WebCodecs encoding.\n *\n * Creates a Muxer instance configured for H.264 video (and, optionally, an\n * AAC audio track), accumulates encoded chunks, and produces a final MP4\n * ArrayBuffer. When no `audio` option is supplied the muxer configuration is\n * byte-identical to the video-only path.\n */\n\nimport { Muxer, ArrayBufferTarget } from 'mp4-muxer';\n\nexport interface Mp4MuxerOptions {\n width: number;\n height: number;\n fps: number;\n /**\n * When present, declares an AAC audio track alongside the video track.\n * Feed encoded audio via {@link Mp4MuxerHandle.addAudioChunk} (from a\n * WebCodecs `AudioEncoder`) or {@link Mp4MuxerHandle.addAudioChunkRaw}.\n */\n audio?: {\n numberOfChannels: number;\n sampleRate: number;\n };\n}\n\nexport interface Mp4MuxerHandle {\n /** Add an encoded video chunk to the muxer. */\n addVideoChunk(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): void;\n /**\n * Add a raw video sample (bytes + timing) without an `EncodedVideoChunk`.\n * Useful where no `VideoEncoder` instance is available (e.g. Node tests).\n */\n addVideoChunkRaw(\n data: Uint8Array,\n type: 'key' | 'delta',\n timestampMicros: number,\n durationMicros: number,\n meta?: EncodedVideoChunkMetadata,\n ): void;\n /** Add an encoded audio chunk (from a WebCodecs `AudioEncoder`). */\n addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;\n /**\n * Add a raw audio sample (bytes + timing) without an `EncodedAudioChunk`.\n * Useful where no `AudioEncoder` instance is available (e.g. Node tests).\n */\n addAudioChunkRaw(\n data: Uint8Array,\n type: 'key' | 'delta',\n timestampMicros: number,\n durationMicros: number,\n meta?: EncodedAudioChunkMetadata,\n ): void;\n /** Whether this muxer was created with an audio track. */\n readonly hasAudioTrack: boolean;\n /** Finalize and return the MP4 as an ArrayBuffer. */\n finalize(): ArrayBuffer;\n}\n\n/**\n * Create an MP4 muxer configured for H.264 video, plus an optional AAC track.\n */\nexport function createMp4Muxer(options: Mp4MuxerOptions): Mp4MuxerHandle {\n const target = new ArrayBufferTarget();\n\n const muxer = new Muxer({\n target,\n video: {\n codec: 'avc',\n width: options.width,\n height: options.height,\n },\n // Only declare the audio track when requested so the video-only\n // configuration stays byte-identical to the historical output.\n ...(options.audio\n ? {\n audio: {\n codec: 'aac' as const,\n numberOfChannels: options.audio.numberOfChannels,\n sampleRate: options.audio.sampleRate,\n },\n }\n : {}),\n fastStart: 'in-memory',\n });\n\n return {\n hasAudioTrack: options.audio !== undefined,\n\n addVideoChunk(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) {\n muxer.addVideoChunk(chunk, meta);\n },\n\n addVideoChunkRaw(\n data: Uint8Array,\n type: 'key' | 'delta',\n timestampMicros: number,\n durationMicros: number,\n meta?: EncodedVideoChunkMetadata,\n ) {\n muxer.addVideoChunkRaw(data, type, timestampMicros, durationMicros, meta);\n },\n\n addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) {\n muxer.addAudioChunk(chunk, meta);\n },\n\n addAudioChunkRaw(\n data: Uint8Array,\n type: 'key' | 'delta',\n timestampMicros: number,\n durationMicros: number,\n meta?: EncodedAudioChunkMetadata,\n ) {\n muxer.addAudioChunkRaw(data, type, timestampMicros, durationMicros, meta);\n },\n\n finalize(): ArrayBuffer {\n muxer.finalize();\n return target.buffer;\n },\n };\n}\n"],"mappings":";AASA,SAAS,OAAO,yBAAyB;AAqDlC,SAAS,eAAe,SAA0C;AACvE,QAAM,SAAS,IAAI,kBAAkB;AAErC,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB;AAAA,IACA,OAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,IAClB;AAAA;AAAA;AAAA,IAGA,GAAI,QAAQ,QACR;AAAA,MACE,OAAO;AAAA,QACL,OAAO;AAAA,QACP,kBAAkB,QAAQ,MAAM;AAAA,QAChC,YAAY,QAAQ,MAAM;AAAA,MAC5B;AAAA,IACF,IACA,CAAC;AAAA,IACL,WAAW;AAAA,EACb,CAAC;AAED,SAAO;AAAA,IACL,eAAe,QAAQ,UAAU;AAAA,IAEjC,cAAc,OAA0B,MAAkC;AACxE,YAAM,cAAc,OAAO,IAAI;AAAA,IACjC;AAAA,IAEA,iBACE,MACA,MACA,iBACA,gBACA,MACA;AACA,YAAM,iBAAiB,MAAM,MAAM,iBAAiB,gBAAgB,IAAI;AAAA,IAC1E;AAAA,IAEA,cAAc,OAA0B,MAAkC;AACxE,YAAM,cAAc,OAAO,IAAI;AAAA,IACjC;AAAA,IAEA,iBACE,MACA,MACA,iBACA,gBACA,MACA;AACA,YAAM,iBAAiB,MAAM,MAAM,iBAAiB,gBAAgB,IAAI;AAAA,IAC1E;AAAA,IAEA,WAAwB;AACtB,YAAM,SAAS;AACf,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,44 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { MediaProvider, Doc } from '@bendyline/squisq/schemas';
|
|
3
3
|
import { VideoQuality, VideoOrientation, RenderHtmlOptions } from '@bendyline/squisq-video';
|
|
4
4
|
import { CaptionMode } from '@bendyline/squisq-react';
|
|
5
5
|
|
|
6
|
-
interface VideoExportModalProps {
|
|
7
|
-
/** The document to export */
|
|
8
|
-
doc: Doc;
|
|
9
|
-
/** Player IIFE bundle source */
|
|
10
|
-
playerScript: string;
|
|
11
|
-
/** Optional media provider for resolving images/audio */
|
|
12
|
-
mediaProvider?: MediaProvider;
|
|
13
|
-
/** Pre-collected images map (alternative to mediaProvider) */
|
|
14
|
-
images?: Map<string, ArrayBuffer>;
|
|
15
|
-
/** Pre-collected audio map */
|
|
16
|
-
audio?: Map<string, ArrayBuffer>;
|
|
17
|
-
/** Called when the modal should close */
|
|
18
|
-
onClose: () => void;
|
|
19
|
-
}
|
|
20
|
-
declare function VideoExportModal({ doc, playerScript, mediaProvider, images, audio, onClose, }: VideoExportModalProps): react_jsx_runtime.JSX.Element;
|
|
21
|
-
|
|
22
|
-
interface VideoExportButtonProps {
|
|
23
|
-
/** The document to export */
|
|
24
|
-
doc: Doc;
|
|
25
|
-
/** Player IIFE bundle source */
|
|
26
|
-
playerScript: string;
|
|
27
|
-
/** Optional media provider for resolving images/audio */
|
|
28
|
-
mediaProvider?: MediaProvider;
|
|
29
|
-
/** Pre-collected images map */
|
|
30
|
-
images?: Map<string, ArrayBuffer>;
|
|
31
|
-
/** Pre-collected audio map */
|
|
32
|
-
audio?: Map<string, ArrayBuffer>;
|
|
33
|
-
/** Button label (default: "Export Video") */
|
|
34
|
-
label?: string;
|
|
35
|
-
/** Additional inline styles for the button */
|
|
36
|
-
style?: React.CSSProperties;
|
|
37
|
-
/** Whether the button is disabled */
|
|
38
|
-
disabled?: boolean;
|
|
39
|
-
}
|
|
40
|
-
declare function VideoExportButton({ doc, playerScript, mediaProvider, images, audio, label, style, disabled, }: VideoExportButtonProps): react_jsx_runtime.JSX.Element;
|
|
41
|
-
|
|
42
6
|
/**
|
|
43
7
|
* useVideoExport — Main orchestration hook for browser video export.
|
|
44
8
|
*
|
|
@@ -96,6 +60,19 @@ interface VideoExportResult {
|
|
|
96
60
|
downloadUrl: string | null;
|
|
97
61
|
/** File size in bytes (populated when state === 'complete') */
|
|
98
62
|
fileSize: number;
|
|
63
|
+
/**
|
|
64
|
+
* Whether an audio track was muxed into the exported MP4 (populated when
|
|
65
|
+
* state === 'complete'). False when the doc had no audio or when audio was
|
|
66
|
+
* skipped/degraded — see {@link audioSkippedReason}.
|
|
67
|
+
*/
|
|
68
|
+
audioIncluded: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Why audio is absent, when `audioIncluded` is false. `null` means the doc
|
|
71
|
+
* simply had no audio (not a failure); a non-null string explains a genuine
|
|
72
|
+
* capability shortfall or runtime failure. Audio problems never fail the
|
|
73
|
+
* export — the video always completes.
|
|
74
|
+
*/
|
|
75
|
+
audioSkippedReason: string | null;
|
|
99
76
|
/** Error message (populated when state === 'error') */
|
|
100
77
|
error: string | null;
|
|
101
78
|
/** Seconds elapsed since export started */
|
|
@@ -111,6 +88,63 @@ interface VideoExportResult {
|
|
|
111
88
|
}
|
|
112
89
|
declare function useVideoExport(): VideoExportResult;
|
|
113
90
|
|
|
91
|
+
interface VideoExportModalProps {
|
|
92
|
+
/** The document to export */
|
|
93
|
+
doc: Doc;
|
|
94
|
+
/**
|
|
95
|
+
* Player IIFE bundle source. Unused by the browser export path (frames
|
|
96
|
+
* are captured from a live in-page DocPlayer); only forwarded for
|
|
97
|
+
* CLI/Playwright-style pipelines that render standalone HTML.
|
|
98
|
+
*/
|
|
99
|
+
playerScript?: string;
|
|
100
|
+
/** Optional media provider for resolving images/audio */
|
|
101
|
+
mediaProvider?: MediaProvider;
|
|
102
|
+
/** Pre-collected images map (alternative to mediaProvider) */
|
|
103
|
+
images?: Map<string, ArrayBuffer>;
|
|
104
|
+
/** Pre-collected audio map */
|
|
105
|
+
audio?: Map<string, ArrayBuffer>;
|
|
106
|
+
/**
|
|
107
|
+
* Seeds the modal's initial quality/fps/orientation/captionMode selections
|
|
108
|
+
* and is merged (as a base) into the config passed to the export hook, so a
|
|
109
|
+
* host can share one config shape with `useVideoExport`. The individual
|
|
110
|
+
* `images`/`audio`/`mediaProvider`/`playerScript` props still take
|
|
111
|
+
* precedence over any matching key here.
|
|
112
|
+
*/
|
|
113
|
+
defaultConfig?: Partial<VideoExportConfig>;
|
|
114
|
+
/** Called when the modal should close */
|
|
115
|
+
onClose: () => void;
|
|
116
|
+
}
|
|
117
|
+
declare function VideoExportModal({ doc, playerScript, mediaProvider, images, audio, defaultConfig, onClose, }: VideoExportModalProps): react_jsx_runtime.JSX.Element;
|
|
118
|
+
|
|
119
|
+
interface VideoExportButtonProps {
|
|
120
|
+
/** The document to export */
|
|
121
|
+
doc: Doc;
|
|
122
|
+
/**
|
|
123
|
+
* Player IIFE bundle source. Unused by the browser export path (frames
|
|
124
|
+
* are captured from a live in-page DocPlayer); only forwarded for
|
|
125
|
+
* CLI/Playwright-style pipelines that render standalone HTML.
|
|
126
|
+
*/
|
|
127
|
+
playerScript?: string;
|
|
128
|
+
/** Optional media provider for resolving images/audio */
|
|
129
|
+
mediaProvider?: MediaProvider;
|
|
130
|
+
/** Pre-collected images map */
|
|
131
|
+
images?: Map<string, ArrayBuffer>;
|
|
132
|
+
/** Pre-collected audio map */
|
|
133
|
+
audio?: Map<string, ArrayBuffer>;
|
|
134
|
+
/**
|
|
135
|
+
* Seeds the modal's initial export settings and is merged as a base into the
|
|
136
|
+
* config passed to the export hook. Forwarded to {@link VideoExportModal}.
|
|
137
|
+
*/
|
|
138
|
+
defaultConfig?: Partial<VideoExportConfig>;
|
|
139
|
+
/** Button label (default: "Export Video") */
|
|
140
|
+
label?: string;
|
|
141
|
+
/** Additional inline styles for the button */
|
|
142
|
+
style?: React.CSSProperties;
|
|
143
|
+
/** Whether the button is disabled */
|
|
144
|
+
disabled?: boolean;
|
|
145
|
+
}
|
|
146
|
+
declare function VideoExportButton({ doc, playerScript, mediaProvider, images, audio, defaultConfig, label, style, disabled, }: VideoExportButtonProps): react_jsx_runtime.JSX.Element;
|
|
147
|
+
|
|
114
148
|
/**
|
|
115
149
|
* useFrameCapture — Hidden div + html2canvas frame capture.
|
|
116
150
|
*
|
|
@@ -153,10 +187,25 @@ interface EncoderConfig {
|
|
|
153
187
|
height: number;
|
|
154
188
|
fps: number;
|
|
155
189
|
quality: 'draft' | 'normal' | 'high';
|
|
190
|
+
/**
|
|
191
|
+
* When present, the underlying muxer is configured with an AAC audio track
|
|
192
|
+
* and {@link MainThreadEncoder.addAudioChunk} becomes usable. Absent → the
|
|
193
|
+
* encoder produces a video-only MP4 exactly as before.
|
|
194
|
+
*/
|
|
195
|
+
audio?: {
|
|
196
|
+
numberOfChannels: number;
|
|
197
|
+
sampleRate: number;
|
|
198
|
+
};
|
|
156
199
|
}
|
|
157
200
|
interface MainThreadEncoder {
|
|
158
201
|
/** Encode a single frame. The bitmap is closed after encoding. */
|
|
159
202
|
encodeFrame(bitmap: ImageBitmap, frameIndex: number): void;
|
|
203
|
+
/**
|
|
204
|
+
* Hand an encoded audio chunk (from a WebCodecs `AudioEncoder`) to the muxer.
|
|
205
|
+
* Only valid when the encoder was created with an `audio` config; otherwise a
|
|
206
|
+
* no-op. Must be called before {@link finalize}.
|
|
207
|
+
*/
|
|
208
|
+
addAudioChunk?(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;
|
|
160
209
|
/** Flush pending frames and finalize the MP4. Returns the MP4 ArrayBuffer. */
|
|
161
210
|
finalize(): Promise<ArrayBuffer>;
|
|
162
211
|
/** Close the encoder without producing output (e.g., on cancel). */
|
|
@@ -166,6 +215,13 @@ interface MainThreadEncoder {
|
|
|
166
215
|
* Check whether the browser supports WebCodecs video encoding.
|
|
167
216
|
*/
|
|
168
217
|
declare function supportsWebCodecs(): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Probe whether the WebCodecs encoder actually supports the H.264 profile
|
|
220
|
+
* we use. The `VideoEncoder` global can exist while the specific codec is
|
|
221
|
+
* unavailable — this is the case on Linux Chromium, which ships without
|
|
222
|
+
* the proprietary H.264 encoder.
|
|
223
|
+
*/
|
|
224
|
+
declare function supportsWebCodecsH264(config: EncoderConfig): Promise<boolean>;
|
|
169
225
|
/**
|
|
170
226
|
* Create a main-thread WebCodecs encoder.
|
|
171
227
|
*
|
|
@@ -173,4 +229,30 @@ declare function supportsWebCodecs(): boolean;
|
|
|
173
229
|
*/
|
|
174
230
|
declare function createEncoder(config: EncoderConfig): MainThreadEncoder;
|
|
175
231
|
|
|
176
|
-
|
|
232
|
+
/**
|
|
233
|
+
* audioTrack — In-browser audio rendering + AAC encoding for MP4 export.
|
|
234
|
+
*
|
|
235
|
+
* Replaces the CLI's ffmpeg `adelay`/`atrim`/`amix` graph with browser-native
|
|
236
|
+
* primitives:
|
|
237
|
+
*
|
|
238
|
+
* - {@link supportsWebCodecsAac} probes whether the browser can encode AAC
|
|
239
|
+
* (WebCodecs `AudioEncoder`).
|
|
240
|
+
* - {@link renderAudioTimeline} mixes an {@link AudioTimelineClip} list into a
|
|
241
|
+
* single `AudioBuffer` using an `OfflineAudioContext` (decode → schedule →
|
|
242
|
+
* render).
|
|
243
|
+
* - {@link encodeAacTrack} feeds that buffer through a WebCodecs
|
|
244
|
+
* `AudioEncoder` and hands the chunks to an mp4-muxer audio track (tier 1).
|
|
245
|
+
* - {@link audioBufferToWav} + {@link muxAudioWithFfmpegWasm} cover the
|
|
246
|
+
* ffmpeg.wasm fallback (tier 2).
|
|
247
|
+
* - {@link selectAudioTier} is the pure capability→tier decision shared with
|
|
248
|
+
* `useVideoExport` (and unit-tested at the module boundary).
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Whether the browser can encode AAC (`mp4a.40.2`) via WebCodecs.
|
|
253
|
+
* Returns false when `AudioEncoder` is undefined (e.g. Node/jsdom, Safari,
|
|
254
|
+
* older Chromium) or the config is unsupported.
|
|
255
|
+
*/
|
|
256
|
+
declare function supportsWebCodecsAac(sampleRate?: number, channels?: number): Promise<boolean>;
|
|
257
|
+
|
|
258
|
+
export { type EncoderConfig, type FrameCaptureHandle, type MainThreadEncoder, VideoExportButton, type VideoExportButtonProps, type VideoExportConfig, VideoExportModal, type VideoExportModalProps, type VideoExportResult, type VideoExportState, createEncoder, supportsWebCodecs, supportsWebCodecsAac, supportsWebCodecsH264, useFrameCapture, useVideoExport };
|