@bendyline/squisq-video-react 2.1.0 → 2.2.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/NOTICE.md +23 -33
- package/dist/chunk-KQG6DZ7G.js +549 -0
- package/dist/chunk-VI5AIVOQ.js +1951 -0
- package/dist/chunk-VILZP3GL.js +856 -0
- package/dist/chunk-YZN7D4IC.js +300 -0
- package/dist/components/index.d.ts +90 -0
- package/dist/components/index.js +11 -0
- package/dist/encoder/index.d.ts +95 -0
- package/dist/encoder/index.js +13 -0
- package/dist/hooks/index.d.ts +32 -0
- package/dist/hooks/index.js +10 -0
- package/dist/index.d.ts +8 -315
- package/dist/index.js +12 -1664
- package/dist/useVideoExport-NtqZVgaz.d.ts +115 -0
- package/dist/workers/encode.worker.js +1 -1
- package/package.json +22 -7
- package/dist/chunk-2PGWBGAT.js +0 -46
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { MediaProvider, Doc } from '@bendyline/squisq/schemas';
|
|
2
|
+
import { ResourcePolicy } from '@bendyline/squisq/markdown';
|
|
3
|
+
import { VideoQuality, VideoOrientation, FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
|
|
4
|
+
import { CaptionMode } from '@bendyline/squisq-react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* useVideoExport — Main orchestration hook for browser video export.
|
|
8
|
+
*
|
|
9
|
+
* Coordinates frame capture (hidden iframe + html2canvas) with
|
|
10
|
+
* main-thread WebCodecs encoding via mp4-muxer. Manages the full
|
|
11
|
+
* lifecycle: prepare → capture + encode → download.
|
|
12
|
+
*
|
|
13
|
+
* Encoding runs on the main thread because frame capture via html2canvas
|
|
14
|
+
* (~100-200ms per frame) is the bottleneck, not encoding (~1ms per frame
|
|
15
|
+
* with hardware-accelerated WebCodecs). Worker offloading would add
|
|
16
|
+
* complexity with minimal benefit.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* const { state, progress, phase, startExport, cancel, downloadUrl } = useVideoExport();
|
|
20
|
+
* <button onClick={() => startExport(doc, options)}>Export</button>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
type VideoExportState = 'idle' | 'preparing' | 'capturing' | 'encoding' | 'complete' | 'error';
|
|
24
|
+
/** Browser export container format. */
|
|
25
|
+
type VideoOutputFormat = 'mp4' | 'gif';
|
|
26
|
+
type VideoAudioPolicy = 'require' | 'best-effort' | 'omit';
|
|
27
|
+
interface VideoExportConfig {
|
|
28
|
+
/** Output container (default: 'mp4') */
|
|
29
|
+
outputFormat?: VideoOutputFormat;
|
|
30
|
+
/**
|
|
31
|
+
* How authored MP4 audio is handled. `require` (default) fails before
|
|
32
|
+
* capture when audio cannot be loaded/decoded and fails the export if later
|
|
33
|
+
* encoding or muxing fails. `best-effort` explicitly permits video-only
|
|
34
|
+
* degradation; `omit` intentionally skips audio.
|
|
35
|
+
*/
|
|
36
|
+
audioPolicy?: VideoAudioPolicy;
|
|
37
|
+
/** Render authored animations and slide transitions (default: true for MP4, false for GIF). */
|
|
38
|
+
animationsEnabled?: boolean;
|
|
39
|
+
/** Encoding quality preset (default: 'normal') */
|
|
40
|
+
quality?: VideoQuality;
|
|
41
|
+
/** Frames per second (default: 30) */
|
|
42
|
+
fps?: number;
|
|
43
|
+
/** Viewport orientation (default: 'landscape') */
|
|
44
|
+
orientation?: VideoOrientation;
|
|
45
|
+
/** Explicit output width. GIF defaults to 960 landscape / 540 portrait. */
|
|
46
|
+
width?: number;
|
|
47
|
+
/** Explicit output height. GIF defaults to 540 landscape / 960 portrait. */
|
|
48
|
+
height?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Map of relative image paths to binary data.
|
|
51
|
+
* Used to embed images into the render HTML.
|
|
52
|
+
*/
|
|
53
|
+
images?: Map<string, ArrayBuffer>;
|
|
54
|
+
/**
|
|
55
|
+
* Map of audio segment names to binary data.
|
|
56
|
+
* Used to embed audio into the render HTML.
|
|
57
|
+
*/
|
|
58
|
+
audio?: Map<string, ArrayBuffer>;
|
|
59
|
+
/** MediaProvider to resolve media URLs (alternative to passing images directly) */
|
|
60
|
+
mediaProvider?: MediaProvider;
|
|
61
|
+
/** Policy and byte/time limits for MediaProvider URLs. */
|
|
62
|
+
resourcePolicy?: ResourcePolicy;
|
|
63
|
+
/** Caption mode for the exported video (default: 'off') */
|
|
64
|
+
captionMode?: CaptionMode;
|
|
65
|
+
/** Player IIFE bundle (unused in browser export, kept for CLI/Playwright path) */
|
|
66
|
+
playerScript?: string;
|
|
67
|
+
/** Optional self-hosted ffmpeg.wasm core URLs for fallback/offline/CSP use. */
|
|
68
|
+
ffmpegWasm?: FfmpegWasmLoadConfig;
|
|
69
|
+
}
|
|
70
|
+
interface VideoExportResult {
|
|
71
|
+
/** Current export state */
|
|
72
|
+
state: VideoExportState;
|
|
73
|
+
/** 0–100 progress percentage */
|
|
74
|
+
progress: number;
|
|
75
|
+
/** Human-readable description of the current phase */
|
|
76
|
+
phase: string;
|
|
77
|
+
/** Video duration detected from the doc (seconds) */
|
|
78
|
+
duration: number;
|
|
79
|
+
/** Effective output format for the current or most recent export. */
|
|
80
|
+
outputFormat: VideoOutputFormat;
|
|
81
|
+
/** Encoder backend ('webcodecs' when WebCodecs H.264 active, 'ffmpeg-wasm' when worker fallback active, null when idle) */
|
|
82
|
+
backend: 'webcodecs' | 'ffmpeg-wasm' | null;
|
|
83
|
+
/** Blob download URL (populated when state === 'complete') */
|
|
84
|
+
downloadUrl: string | null;
|
|
85
|
+
/** File size in bytes (populated when state === 'complete') */
|
|
86
|
+
fileSize: number;
|
|
87
|
+
/**
|
|
88
|
+
* Whether an audio track was muxed into the exported MP4 (populated when
|
|
89
|
+
* state === 'complete'). False when the doc had no audio or when audio was
|
|
90
|
+
* skipped/degraded — see {@link audioSkippedReason}.
|
|
91
|
+
*/
|
|
92
|
+
audioIncluded: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Why audio is absent, when `audioIncluded` is false. `null` means the doc
|
|
95
|
+
* simply had no audio (not a failure); a non-null string explains a genuine
|
|
96
|
+
* capability shortfall or runtime failure. Audio problems never fail the
|
|
97
|
+
* export — the video always completes.
|
|
98
|
+
*/
|
|
99
|
+
audioSkippedReason: string | null;
|
|
100
|
+
/** Error message (populated when state === 'error') */
|
|
101
|
+
error: string | null;
|
|
102
|
+
/** Seconds elapsed since export started */
|
|
103
|
+
elapsed: number;
|
|
104
|
+
/** Estimated seconds remaining (0 when idle or complete) */
|
|
105
|
+
estimatedRemaining: number;
|
|
106
|
+
/** Start a new export */
|
|
107
|
+
startExport: (doc: Doc, config: VideoExportConfig) => Promise<void>;
|
|
108
|
+
/** Cancel an in-progress export */
|
|
109
|
+
cancel: () => void;
|
|
110
|
+
/** Reset state back to idle (e.g., after complete or error) */
|
|
111
|
+
reset: () => void;
|
|
112
|
+
}
|
|
113
|
+
declare function useVideoExport(): VideoExportResult;
|
|
114
|
+
|
|
115
|
+
export { type VideoAudioPolicy as V, type VideoExportConfig as a, type VideoExportResult as b, type VideoExportState as c, type VideoOutputFormat as d, useVideoExport as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-video-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "React components for browser-based MP4 and animated-GIF export of Squisq documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.14.0"
|
|
28
|
+
},
|
|
26
29
|
"main": "./dist/index.js",
|
|
27
30
|
"types": "./dist/index.d.ts",
|
|
28
31
|
"exports": {
|
|
@@ -30,6 +33,18 @@
|
|
|
30
33
|
"types": "./dist/index.d.ts",
|
|
31
34
|
"import": "./dist/index.js",
|
|
32
35
|
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./components": {
|
|
38
|
+
"types": "./dist/components/index.d.ts",
|
|
39
|
+
"import": "./dist/components/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./hooks": {
|
|
42
|
+
"types": "./dist/hooks/index.d.ts",
|
|
43
|
+
"import": "./dist/hooks/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./encoder": {
|
|
46
|
+
"types": "./dist/encoder/index.d.ts",
|
|
47
|
+
"import": "./dist/encoder/index.js"
|
|
33
48
|
}
|
|
34
49
|
},
|
|
35
50
|
"files": [
|
|
@@ -40,7 +55,7 @@
|
|
|
40
55
|
"COPYING.GPL-2.0.txt"
|
|
41
56
|
],
|
|
42
57
|
"scripts": {
|
|
43
|
-
"build": "tsup",
|
|
58
|
+
"build": "tsup && node ../../scripts/remove-empty-chunks.mjs dist",
|
|
44
59
|
"dev": "concurrently -n js,dts -c blue,gray -r \"tsup --watch --no-dts --no-clean\" \"tsup --watch --dts-only --no-clean\"",
|
|
45
60
|
"typecheck": "tsc --noEmit"
|
|
46
61
|
},
|
|
@@ -49,20 +64,20 @@
|
|
|
49
64
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
50
65
|
},
|
|
51
66
|
"dependencies": {
|
|
52
|
-
"@bendyline/squisq": "2.
|
|
53
|
-
"@bendyline/squisq-video": "2.
|
|
54
|
-
"@bendyline/squisq-react": "2.
|
|
67
|
+
"@bendyline/squisq": "2.3.0",
|
|
68
|
+
"@bendyline/squisq-video": "2.2.0",
|
|
69
|
+
"@bendyline/squisq-react": "2.3.0",
|
|
55
70
|
"@ffmpeg/core": "0.12.9",
|
|
56
71
|
"@ffmpeg/ffmpeg": "0.12.15",
|
|
57
72
|
"@ffmpeg/util": "0.12.2",
|
|
58
|
-
"html2canvas": "1.4.1"
|
|
59
|
-
"mp4-muxer": "5.2.2"
|
|
73
|
+
"html2canvas": "1.4.1"
|
|
60
74
|
},
|
|
61
75
|
"devDependencies": {
|
|
62
76
|
"@types/react": "18.3.28",
|
|
63
77
|
"@types/react-dom": "18.3.7",
|
|
64
78
|
"react": "18.3.1",
|
|
65
79
|
"react-dom": "18.3.1",
|
|
80
|
+
"mp4-muxer": "5.2.2",
|
|
66
81
|
"tsup": "8.5.1",
|
|
67
82
|
"typescript": "5.9.3"
|
|
68
83
|
},
|
package/dist/chunk-2PGWBGAT.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
};
|