@twick/browser-render 0.15.6 → 0.15.8

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 CHANGED
@@ -46,19 +46,21 @@ a.click();
46
46
  import { useBrowserRenderer } from '@twick/browser-render';
47
47
 
48
48
  function VideoRenderer() {
49
- const { render, progress, isRendering, videoBlob, download } = useBrowserRenderer({
50
- width: 1920,
51
- height: 1080,
52
- fps: 30,
53
- autoDownload: false
54
- });
49
+ const { render, progress, isRendering, videoBlob, download, error, reset } =
50
+ useBrowserRenderer({
51
+ width: 720,
52
+ height: 1280,
53
+ fps: 30,
54
+ includeAudio: true, // enable audio rendering + FFmpeg mux
55
+ autoDownload: true, // auto-download final MP4
56
+ });
55
57
 
56
58
  const handleRender = async () => {
57
59
  await render({
58
60
  input: {
59
- properties: { width: 1920, height: 1080, fps: 30 },
60
- tracks: [/* ... */]
61
- }
61
+ properties: { width: 720, height: 1280, fps: 30 },
62
+ tracks: [/* ... */],
63
+ },
62
64
  });
63
65
  };
64
66
 
@@ -69,6 +71,12 @@ function VideoRenderer() {
69
71
  </button>
70
72
  {isRendering && <progress value={progress} max={1} />}
71
73
  {videoBlob && <button onClick={download}>Download</button>}
74
+ {error && (
75
+ <div>
76
+ <p>{error.message}</p>
77
+ <button onClick={reset}>Clear error</button>
78
+ </div>
79
+ )}
72
80
  </div>
73
81
  );
74
82
  }
@@ -92,6 +100,9 @@ function VideoRenderer() {
92
100
  fps?: number; // Frames per second (default: 30)
93
101
  quality?: 'low' | 'medium' | 'high';
94
102
  range?: [number, number]; // [start, end] in seconds
103
+ includeAudio?: boolean; // Enable audio rendering and muxing (default: false)
104
+ downloadAudioSeparately?: boolean; // Also download audio.wav when muxing fails
105
+ onAudioReady?: (audioBlob: Blob) => void; // Callback when raw audio is ready
95
106
  onProgress?: (progress: number) => void;
96
107
  onComplete?: (videoBlob: Blob) => void;
97
108
  onError?: (error: Error) => void;
@@ -112,15 +123,16 @@ const videoBlob = await renderTwickVideoInBrowser({
112
123
 
113
124
  **Note**: String paths only work in Node.js. In the browser, you must import and pass the Project object directly.
114
125
 
115
- ## WASM Setup
126
+ ## WASM and public assets
116
127
 
117
- Copy the WASM file to your public directory:
128
+ The package ships `public/` with `mp4-wasm.wasm` and `audio-worker.js`. Copy them into your app's public directory so they are served:
118
129
 
119
130
  ```bash
120
- cp node_modules/mp4-wasm/dist/mp4-wasm.wasm public/
131
+ cp node_modules/@twick/browser-render/public/mp4-wasm.wasm public/
132
+ cp node_modules/@twick/browser-render/public/audio-worker.js public/
121
133
  ```
122
134
 
123
- Or configure Vite to serve it:
135
+ Alternatively, configure your bundler (e.g. Vite) to serve WASM:
124
136
 
125
137
  ```typescript
126
138
  // vite.config.ts
@@ -129,23 +141,49 @@ export default defineConfig({
129
141
  });
130
142
  ```
131
143
 
144
+ ### FFmpeg audio/video muxing (optional)
145
+
146
+ When `settings.includeAudio` is enabled, `@twick/browser-render` will render audio in the browser and (by default) try to mux it into the final MP4 using `@ffmpeg/ffmpeg`.
147
+ To match the setup used in `@twick/examples`, you should:
148
+
149
+ 1. Install the FFmpeg packages:
150
+
151
+ ```bash
152
+ npm install @ffmpeg/ffmpeg @ffmpeg/util @ffmpeg/core
153
+ ```
154
+
155
+ 2. Expose the FFmpeg core files from your app’s `public` folder so they are available at:
156
+
157
+ ```text
158
+ /public/ffmpeg/ffmpeg-core.js
159
+ /public/ffmpeg/ffmpeg-core.wasm
160
+ ```
161
+
162
+ For example, you can copy them from `node_modules/@ffmpeg/core/dist`:
163
+
164
+ ```bash
165
+ mkdir -p public/ffmpeg
166
+ cp node_modules/@ffmpeg/core/dist/ffmpeg-core.js public/ffmpeg/
167
+ cp node_modules/@ffmpeg/core/dist/ffmpeg-core.wasm public/ffmpeg/
168
+ ```
169
+
170
+ In the browser, the muxer loads these files from `${window.location.origin}/ffmpeg`, so the URLs must match that structure.
171
+ If FFmpeg cannot be loaded, the renderer will fall back to returning a video-only file and (optionally) downloading `audio.wav` separately when `downloadAudioSeparately` is true.
172
+
132
173
  ## Limitations
133
174
 
134
- - **Audio**: Audio processing is not yet implemented. Only video encoding is supported.
175
+ - **Audio**: Audio rendering and FFmpeg-based muxing run entirely in the browser and are still considered experimental. If FFmpeg assets are not available, only video will be muxed and audio may be downloaded as a separate file.
135
176
  - **Browser Support**: Requires WebCodecs API (Chrome 94+, Edge 94+)
136
177
 
137
- ## API Comparison
178
+ ## License
138
179
 
139
- This package follows the same API as the server renderer (`@twick/renderer`):
180
+ This package is licensed under the **Sustainable Use License (SUL) Version 1.0**.
140
181
 
141
- | Feature | Server | Browser |
142
- |---------|--------|---------|
143
- | Duration Calculation | `renderer.getNumberOfFrames()` | Same |
144
- | Playback State | `PlaybackState.Rendering` | ✅ Same |
145
- | Frame Progression | `playback.progress()` | ✅ Same |
146
- | Variables Assignment | `project.variables = {...}` | ✅ Same |
147
- | Audio Support | ✅ FFmpeg | ❌ Not yet |
182
+ - Free for use in commercial and non-commercial apps
183
+ - Can be modified and self-hosted
184
+ - Cannot be sold, rebranded, or distributed as a standalone SDK
148
185
 
149
- ## License
186
+ For commercial licensing inquiries, contact: contact@kifferai.com
187
+
188
+ For full license terms, see the main LICENSE.md file in the project root.
150
189
 
151
- MIT
@@ -0,0 +1,225 @@
1
+ import { Project } from '@twick/core';
2
+
3
+ /**
4
+ * Browser rendering configuration
5
+ */
6
+ interface BrowserRenderConfig {
7
+ /**
8
+ * Custom Project object
9
+ * If not provided, defaults to @twick/visualizer project
10
+ *
11
+ * Note: Must be an imported Project object, not a string path.
12
+ * String paths only work in Node.js environments (server renderer).
13
+ *
14
+ * Example:
15
+ * ```typescript
16
+ * import myProject from './my-custom-project';
17
+ *
18
+ * await renderTwickVideoInBrowser({
19
+ * projectFile: myProject,
20
+ * variables: { input: {...} }
21
+ * });
22
+ * ```
23
+ */
24
+ projectFile?: Project;
25
+ /** Input variables containing project configuration */
26
+ variables: {
27
+ input: any;
28
+ playerId?: string;
29
+ [key: string]: any;
30
+ };
31
+ /** Render settings */
32
+ settings?: {
33
+ width?: number;
34
+ height?: number;
35
+ fps?: number;
36
+ quality?: 'low' | 'medium' | 'high';
37
+ range?: [number, number];
38
+ includeAudio?: boolean;
39
+ downloadAudioSeparately?: boolean;
40
+ onAudioReady?: (audioBlob: Blob) => void;
41
+ onProgress?: (progress: number) => void;
42
+ onComplete?: (videoBlob: Blob) => void;
43
+ onError?: (error: Error) => void;
44
+ };
45
+ }
46
+ /**
47
+ * Renders a Twick video directly in the browser without requiring a server.
48
+ * Uses WebCodecs API for encoding video frames into MP4 format.
49
+ *
50
+ * This function uses the same signature as the server renderer for consistency.
51
+ *
52
+ * @param config - Configuration object containing variables and settings
53
+ * @param config.projectFile - Optional project file path or Project object (defaults to visualizer project)
54
+ * @param config.variables - Variables containing input configuration (tracks, elements, etc.)
55
+ * @param config.settings - Optional render settings (width, height, fps, etc.)
56
+ * @returns Promise resolving to a Blob containing the rendered video
57
+ *
58
+ * @example
59
+ * ```js
60
+ * import { renderTwickVideoInBrowser } from '@twick/browser-render';
61
+ *
62
+ * // Using default visualizer project
63
+ * const videoBlob = await renderTwickVideoInBrowser({
64
+ * variables: {
65
+ * input: {
66
+ * properties: { width: 1920, height: 1080, fps: 30 },
67
+ * tracks: [
68
+ * // Your tracks configuration
69
+ * ]
70
+ * }
71
+ * },
72
+ * settings: {
73
+ * width: 1920,
74
+ * height: 1080,
75
+ * fps: 30,
76
+ * quality: 'high',
77
+ * onProgress: (progress) => console.log(`Rendering: ${progress * 100}%`),
78
+ * }
79
+ * });
80
+ *
81
+ * // Using custom project
82
+ * import myProject from './my-custom-project';
83
+ * const videoBlob = await renderTwickVideoInBrowser({
84
+ * projectFile: myProject, // Must be an imported Project object
85
+ * variables: { input: {...} },
86
+ * settings: {...}
87
+ * });
88
+ *
89
+ * // Download the video
90
+ * const url = URL.createObjectURL(videoBlob);
91
+ * const a = document.createElement('a');
92
+ * a.href = url;
93
+ * a.download = 'video.mp4';
94
+ * a.click();
95
+ * URL.revokeObjectURL(url);
96
+ * ```
97
+ */
98
+ declare const renderTwickVideoInBrowser: (config: BrowserRenderConfig) => Promise<Blob>;
99
+ /**
100
+ * Helper function to download a video blob as a file
101
+ *
102
+ * @param videoBlob - The video blob to download
103
+ * @param filename - The desired filename (default: 'video.mp4')
104
+ *
105
+ * @example
106
+ * ```js
107
+ * const blob = await renderTwickVideoInBrowser(projectData);
108
+ * downloadVideoBlob(blob, 'my-video.mp4');
109
+ * ```
110
+ */
111
+ declare const downloadVideoBlob: (videoBlob: Blob, filename?: string) => void;
112
+
113
+ interface UseBrowserRendererOptions {
114
+ /**
115
+ * Custom Project object
116
+ * If not provided, defaults to @twick/visualizer project
117
+ *
118
+ * Note: Must be an imported Project object, not a string path.
119
+ * String paths only work in Node.js (server renderer).
120
+ *
121
+ * Example:
122
+ * ```typescript
123
+ * import myProject from './my-custom-project';
124
+ * useBrowserRenderer({ projectFile: myProject })
125
+ * ```
126
+ */
127
+ projectFile?: any;
128
+ /** Video width in pixels */
129
+ width?: number;
130
+ /** Video height in pixels */
131
+ height?: number;
132
+ /** Frames per second */
133
+ fps?: number;
134
+ /** Render quality */
135
+ quality?: 'low' | 'medium' | 'high';
136
+ /** Time range to render [start, end] in seconds */
137
+ range?: [number, number];
138
+ /** Include audio in rendered video (experimental) */
139
+ includeAudio?: boolean;
140
+ /** Download audio separately as WAV file */
141
+ downloadAudioSeparately?: boolean;
142
+ /** Callback when audio is ready */
143
+ onAudioReady?: (audioBlob: Blob) => void;
144
+ /** Automatically download the video when rendering completes */
145
+ autoDownload?: boolean;
146
+ /** Default filename for downloads */
147
+ downloadFilename?: string;
148
+ }
149
+ interface UseBrowserRendererReturn {
150
+ /** Start rendering the video */
151
+ render: (variables: BrowserRenderConfig['variables']) => Promise<Blob | null>;
152
+ /** Current rendering progress (0-1) */
153
+ progress: number;
154
+ /** Whether rendering is in progress */
155
+ isRendering: boolean;
156
+ /** Error if rendering failed */
157
+ error: Error | null;
158
+ /** The rendered video blob (available after rendering completes) */
159
+ videoBlob: Blob | null;
160
+ /** Download the rendered video */
161
+ download: (filename?: string) => void;
162
+ /** Reset the renderer state */
163
+ reset: () => void;
164
+ }
165
+ /**
166
+ * React hook for rendering Twick videos in the browser
167
+ *
168
+ * Uses the same pattern as the server renderer for consistency.
169
+ *
170
+ * @param options - Rendering options
171
+ * @returns Renderer state and control functions
172
+ *
173
+ * @example
174
+ * ```tsx
175
+ * import { useBrowserRenderer } from '@twick/browser-render';
176
+ *
177
+ * // Using default visualizer project
178
+ * function MyComponent() {
179
+ * const { render, progress, isRendering, videoBlob, download } = useBrowserRenderer({
180
+ * width: 1920,
181
+ * height: 1080,
182
+ * fps: 30,
183
+ * autoDownload: true,
184
+ * });
185
+ *
186
+ * const handleRender = async () => {
187
+ * await render({
188
+ * input: {
189
+ * properties: { width: 1920, height: 1080, fps: 30 },
190
+ * tracks: [
191
+ * // Your tracks configuration
192
+ * ]
193
+ * }
194
+ * });
195
+ * };
196
+ *
197
+ * return (
198
+ * <div>
199
+ * <button onClick={handleRender} disabled={isRendering}>
200
+ * {isRendering ? `Rendering... ${(progress * 100).toFixed(0)}%` : 'Render Video'}
201
+ * </button>
202
+ * {videoBlob && !autoDownload && (
203
+ * <button onClick={() => download('my-video.mp4')}>Download</button>
204
+ * )}
205
+ * </div>
206
+ * );
207
+ * }
208
+ *
209
+ * // Using custom project (must import it first)
210
+ * import myProject from './my-project';
211
+ *
212
+ * function CustomProjectComponent() {
213
+ * const { render } = useBrowserRenderer({
214
+ * projectFile: myProject, // Pass the imported project object
215
+ * width: 1920,
216
+ * height: 1080,
217
+ * });
218
+ *
219
+ * // ... rest of component
220
+ * }
221
+ * ```
222
+ */
223
+ declare const useBrowserRenderer: (options?: UseBrowserRendererOptions) => UseBrowserRendererReturn;
224
+
225
+ export { type BrowserRenderConfig, type UseBrowserRendererOptions, type UseBrowserRendererReturn, renderTwickVideoInBrowser as default, downloadVideoBlob, renderTwickVideoInBrowser, useBrowserRenderer };
@@ -0,0 +1,225 @@
1
+ import { Project } from '@twick/core';
2
+
3
+ /**
4
+ * Browser rendering configuration
5
+ */
6
+ interface BrowserRenderConfig {
7
+ /**
8
+ * Custom Project object
9
+ * If not provided, defaults to @twick/visualizer project
10
+ *
11
+ * Note: Must be an imported Project object, not a string path.
12
+ * String paths only work in Node.js environments (server renderer).
13
+ *
14
+ * Example:
15
+ * ```typescript
16
+ * import myProject from './my-custom-project';
17
+ *
18
+ * await renderTwickVideoInBrowser({
19
+ * projectFile: myProject,
20
+ * variables: { input: {...} }
21
+ * });
22
+ * ```
23
+ */
24
+ projectFile?: Project;
25
+ /** Input variables containing project configuration */
26
+ variables: {
27
+ input: any;
28
+ playerId?: string;
29
+ [key: string]: any;
30
+ };
31
+ /** Render settings */
32
+ settings?: {
33
+ width?: number;
34
+ height?: number;
35
+ fps?: number;
36
+ quality?: 'low' | 'medium' | 'high';
37
+ range?: [number, number];
38
+ includeAudio?: boolean;
39
+ downloadAudioSeparately?: boolean;
40
+ onAudioReady?: (audioBlob: Blob) => void;
41
+ onProgress?: (progress: number) => void;
42
+ onComplete?: (videoBlob: Blob) => void;
43
+ onError?: (error: Error) => void;
44
+ };
45
+ }
46
+ /**
47
+ * Renders a Twick video directly in the browser without requiring a server.
48
+ * Uses WebCodecs API for encoding video frames into MP4 format.
49
+ *
50
+ * This function uses the same signature as the server renderer for consistency.
51
+ *
52
+ * @param config - Configuration object containing variables and settings
53
+ * @param config.projectFile - Optional project file path or Project object (defaults to visualizer project)
54
+ * @param config.variables - Variables containing input configuration (tracks, elements, etc.)
55
+ * @param config.settings - Optional render settings (width, height, fps, etc.)
56
+ * @returns Promise resolving to a Blob containing the rendered video
57
+ *
58
+ * @example
59
+ * ```js
60
+ * import { renderTwickVideoInBrowser } from '@twick/browser-render';
61
+ *
62
+ * // Using default visualizer project
63
+ * const videoBlob = await renderTwickVideoInBrowser({
64
+ * variables: {
65
+ * input: {
66
+ * properties: { width: 1920, height: 1080, fps: 30 },
67
+ * tracks: [
68
+ * // Your tracks configuration
69
+ * ]
70
+ * }
71
+ * },
72
+ * settings: {
73
+ * width: 1920,
74
+ * height: 1080,
75
+ * fps: 30,
76
+ * quality: 'high',
77
+ * onProgress: (progress) => console.log(`Rendering: ${progress * 100}%`),
78
+ * }
79
+ * });
80
+ *
81
+ * // Using custom project
82
+ * import myProject from './my-custom-project';
83
+ * const videoBlob = await renderTwickVideoInBrowser({
84
+ * projectFile: myProject, // Must be an imported Project object
85
+ * variables: { input: {...} },
86
+ * settings: {...}
87
+ * });
88
+ *
89
+ * // Download the video
90
+ * const url = URL.createObjectURL(videoBlob);
91
+ * const a = document.createElement('a');
92
+ * a.href = url;
93
+ * a.download = 'video.mp4';
94
+ * a.click();
95
+ * URL.revokeObjectURL(url);
96
+ * ```
97
+ */
98
+ declare const renderTwickVideoInBrowser: (config: BrowserRenderConfig) => Promise<Blob>;
99
+ /**
100
+ * Helper function to download a video blob as a file
101
+ *
102
+ * @param videoBlob - The video blob to download
103
+ * @param filename - The desired filename (default: 'video.mp4')
104
+ *
105
+ * @example
106
+ * ```js
107
+ * const blob = await renderTwickVideoInBrowser(projectData);
108
+ * downloadVideoBlob(blob, 'my-video.mp4');
109
+ * ```
110
+ */
111
+ declare const downloadVideoBlob: (videoBlob: Blob, filename?: string) => void;
112
+
113
+ interface UseBrowserRendererOptions {
114
+ /**
115
+ * Custom Project object
116
+ * If not provided, defaults to @twick/visualizer project
117
+ *
118
+ * Note: Must be an imported Project object, not a string path.
119
+ * String paths only work in Node.js (server renderer).
120
+ *
121
+ * Example:
122
+ * ```typescript
123
+ * import myProject from './my-custom-project';
124
+ * useBrowserRenderer({ projectFile: myProject })
125
+ * ```
126
+ */
127
+ projectFile?: any;
128
+ /** Video width in pixels */
129
+ width?: number;
130
+ /** Video height in pixels */
131
+ height?: number;
132
+ /** Frames per second */
133
+ fps?: number;
134
+ /** Render quality */
135
+ quality?: 'low' | 'medium' | 'high';
136
+ /** Time range to render [start, end] in seconds */
137
+ range?: [number, number];
138
+ /** Include audio in rendered video (experimental) */
139
+ includeAudio?: boolean;
140
+ /** Download audio separately as WAV file */
141
+ downloadAudioSeparately?: boolean;
142
+ /** Callback when audio is ready */
143
+ onAudioReady?: (audioBlob: Blob) => void;
144
+ /** Automatically download the video when rendering completes */
145
+ autoDownload?: boolean;
146
+ /** Default filename for downloads */
147
+ downloadFilename?: string;
148
+ }
149
+ interface UseBrowserRendererReturn {
150
+ /** Start rendering the video */
151
+ render: (variables: BrowserRenderConfig['variables']) => Promise<Blob | null>;
152
+ /** Current rendering progress (0-1) */
153
+ progress: number;
154
+ /** Whether rendering is in progress */
155
+ isRendering: boolean;
156
+ /** Error if rendering failed */
157
+ error: Error | null;
158
+ /** The rendered video blob (available after rendering completes) */
159
+ videoBlob: Blob | null;
160
+ /** Download the rendered video */
161
+ download: (filename?: string) => void;
162
+ /** Reset the renderer state */
163
+ reset: () => void;
164
+ }
165
+ /**
166
+ * React hook for rendering Twick videos in the browser
167
+ *
168
+ * Uses the same pattern as the server renderer for consistency.
169
+ *
170
+ * @param options - Rendering options
171
+ * @returns Renderer state and control functions
172
+ *
173
+ * @example
174
+ * ```tsx
175
+ * import { useBrowserRenderer } from '@twick/browser-render';
176
+ *
177
+ * // Using default visualizer project
178
+ * function MyComponent() {
179
+ * const { render, progress, isRendering, videoBlob, download } = useBrowserRenderer({
180
+ * width: 1920,
181
+ * height: 1080,
182
+ * fps: 30,
183
+ * autoDownload: true,
184
+ * });
185
+ *
186
+ * const handleRender = async () => {
187
+ * await render({
188
+ * input: {
189
+ * properties: { width: 1920, height: 1080, fps: 30 },
190
+ * tracks: [
191
+ * // Your tracks configuration
192
+ * ]
193
+ * }
194
+ * });
195
+ * };
196
+ *
197
+ * return (
198
+ * <div>
199
+ * <button onClick={handleRender} disabled={isRendering}>
200
+ * {isRendering ? `Rendering... ${(progress * 100).toFixed(0)}%` : 'Render Video'}
201
+ * </button>
202
+ * {videoBlob && !autoDownload && (
203
+ * <button onClick={() => download('my-video.mp4')}>Download</button>
204
+ * )}
205
+ * </div>
206
+ * );
207
+ * }
208
+ *
209
+ * // Using custom project (must import it first)
210
+ * import myProject from './my-project';
211
+ *
212
+ * function CustomProjectComponent() {
213
+ * const { render } = useBrowserRenderer({
214
+ * projectFile: myProject, // Pass the imported project object
215
+ * width: 1920,
216
+ * height: 1080,
217
+ * });
218
+ *
219
+ * // ... rest of component
220
+ * }
221
+ * ```
222
+ */
223
+ declare const useBrowserRenderer: (options?: UseBrowserRendererOptions) => UseBrowserRendererReturn;
224
+
225
+ export { type BrowserRenderConfig, type UseBrowserRendererOptions, type UseBrowserRendererReturn, renderTwickVideoInBrowser as default, downloadVideoBlob, renderTwickVideoInBrowser, useBrowserRenderer };