@bendyline/squisq-video-react 2.0.2 → 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.
Files changed (44) hide show
  1. package/COPYING.GPL-2.0.txt +339 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +39 -0
  4. package/README.md +34 -7
  5. package/dist/chunk-KQG6DZ7G.js +549 -0
  6. package/dist/chunk-VI5AIVOQ.js +1951 -0
  7. package/dist/chunk-VILZP3GL.js +856 -0
  8. package/dist/chunk-YZN7D4IC.js +300 -0
  9. package/dist/components/index.d.ts +90 -0
  10. package/dist/components/index.js +11 -0
  11. package/dist/encoder/index.d.ts +95 -0
  12. package/dist/encoder/index.js +13 -0
  13. package/dist/hooks/index.d.ts +32 -0
  14. package/dist/hooks/index.js +10 -0
  15. package/dist/index.d.ts +8 -277
  16. package/dist/index.js +12 -1503
  17. package/dist/useVideoExport-NtqZVgaz.d.ts +115 -0
  18. package/dist/workers/encode.worker.js +37 -20
  19. package/dist/workers/ffmpeg.class-worker.js +0 -1
  20. package/package.json +26 -8
  21. package/dist/chunk-6DKLHXX3.js +0 -47
  22. package/dist/chunk-6DKLHXX3.js.map +0 -1
  23. package/dist/index.js.map +0 -1
  24. package/dist/workers/encode.worker.js.map +0 -1
  25. package/dist/workers/ffmpeg.class-worker.js.map +0 -1
  26. package/src/VideoExportButton.tsx +0 -87
  27. package/src/VideoExportModal.tsx +0 -553
  28. package/src/__tests__/audioTrack.test.ts +0 -108
  29. package/src/__tests__/gifTranscode.test.ts +0 -118
  30. package/src/__tests__/mp4MuxAudio.test.ts +0 -72
  31. package/src/__tests__/useVideoExportGif.test.ts +0 -155
  32. package/src/__tests__/videoExportProps.test.tsx +0 -148
  33. package/src/__tests__/workerEncoder.test.ts +0 -143
  34. package/src/audioTrack.ts +0 -332
  35. package/src/gifTranscode.ts +0 -75
  36. package/src/hooks/useFrameCapture.ts +0 -268
  37. package/src/hooks/useVideoExport.ts +0 -647
  38. package/src/index.ts +0 -40
  39. package/src/mainThreadEncoder.ts +0 -158
  40. package/src/mp4Mux.ts +0 -123
  41. package/src/workerEncoder.ts +0 -175
  42. package/src/workers/encode.worker.ts +0 -439
  43. package/src/workers/ffmpeg.class-worker.ts +0 -11
  44. package/src/workers/workerTypes.ts +0 -89
@@ -0,0 +1,549 @@
1
+ import {
2
+ useVideoExport
3
+ } from "./chunk-VILZP3GL.js";
4
+
5
+ // src/VideoExportModal.tsx
6
+ import { useState, useCallback, useId, useRef } from "react";
7
+ import { useModalDialog } from "@bendyline/squisq-react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ function formatDuration(seconds) {
10
+ if (seconds < 60) return `${seconds}s`;
11
+ const m = Math.floor(seconds / 60);
12
+ const s = seconds % 60;
13
+ return `${m}m ${s}s`;
14
+ }
15
+ function encoderLabel(outputFormat, backend) {
16
+ if (outputFormat === "gif") {
17
+ return backend === "webcodecs" ? "WebCodecs (H.264) \u2192 ffmpeg.wasm (GIF)" : "ffmpeg.wasm (H.264 \u2192 GIF)";
18
+ }
19
+ return backend === "webcodecs" ? "WebCodecs (H.264)" : "ffmpeg.wasm (H.264)";
20
+ }
21
+ var VIDEO_EXPORT_PALETTES = {
22
+ light: {
23
+ overlay: "rgba(0, 0, 0, 0.5)",
24
+ surface: "#FFFDF7",
25
+ control: "#ffffff",
26
+ border: "#c9b98a",
27
+ text: "#4a3c1f",
28
+ heading: "#2d2310",
29
+ label: "#5a4a2a",
30
+ muted: "#8a7a5a",
31
+ secondary: "#E8DFC6",
32
+ primary: "#8B6914",
33
+ primaryBorder: "#7a5c10",
34
+ primaryText: "#ffffff",
35
+ success: "#2d6a10",
36
+ danger: "#a03020"
37
+ },
38
+ dark: {
39
+ overlay: "rgba(2, 6, 23, 0.72)",
40
+ surface: "#111827",
41
+ control: "#0f172a",
42
+ border: "#475569",
43
+ text: "#e5e7eb",
44
+ heading: "#f8fafc",
45
+ label: "#cbd5e1",
46
+ muted: "#94a3b8",
47
+ secondary: "#1e293b",
48
+ primary: "#9a7416",
49
+ primaryBorder: "#d1a73b",
50
+ primaryText: "#ffffff",
51
+ success: "#86efac",
52
+ danger: "#fca5a5"
53
+ }
54
+ };
55
+ var overlayStyle = {
56
+ position: "fixed",
57
+ inset: 0,
58
+ display: "flex",
59
+ alignItems: "center",
60
+ justifyContent: "center",
61
+ zIndex: 1e4
62
+ };
63
+ var modalStyle = {
64
+ borderRadius: 0,
65
+ padding: "24px 28px",
66
+ minWidth: 380,
67
+ maxWidth: 480,
68
+ boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
69
+ fontFamily: "system-ui, -apple-system, sans-serif"
70
+ };
71
+ var titleStyle = {
72
+ margin: "0 0 16px 0",
73
+ fontSize: 18,
74
+ fontWeight: 600
75
+ };
76
+ var labelStyle = {
77
+ display: "block",
78
+ fontSize: 13,
79
+ fontWeight: 500,
80
+ marginBottom: 4
81
+ };
82
+ var selectStyle = {
83
+ width: "100%",
84
+ padding: "6px 8px",
85
+ fontSize: 13,
86
+ fontFamily: "inherit",
87
+ borderRadius: 0,
88
+ marginBottom: 12
89
+ };
90
+ var btnPrimary = {
91
+ padding: "8px 20px",
92
+ fontSize: 14,
93
+ fontFamily: "inherit",
94
+ fontWeight: 500,
95
+ cursor: "pointer",
96
+ borderRadius: 0
97
+ };
98
+ var btnSecondary = {
99
+ padding: "8px 20px",
100
+ fontSize: 14,
101
+ fontFamily: "inherit",
102
+ fontWeight: 500,
103
+ cursor: "pointer",
104
+ borderRadius: 0
105
+ };
106
+ var progressBarOuterStyle = {
107
+ width: "100%",
108
+ height: 8,
109
+ borderRadius: 0,
110
+ overflow: "hidden",
111
+ marginBottom: 8
112
+ };
113
+ var footerStyle = {
114
+ display: "flex",
115
+ justifyContent: "flex-end",
116
+ gap: 8,
117
+ marginTop: 20
118
+ };
119
+ function VideoExportModal({
120
+ doc,
121
+ playerScript,
122
+ mediaProvider,
123
+ images,
124
+ audio,
125
+ defaultConfig,
126
+ colorScheme = "light",
127
+ uiPalette,
128
+ onClose
129
+ }) {
130
+ const overlayRef = useRef(null);
131
+ const dialogRef = useRef(null);
132
+ const titleId = useId();
133
+ const initialOutputFormat = defaultConfig?.outputFormat ?? "mp4";
134
+ const [outputFormat, setOutputFormat] = useState(initialOutputFormat);
135
+ const [quality, setQuality] = useState(defaultConfig?.quality ?? "normal");
136
+ const [fps, setFps] = useState(defaultConfig?.fps ?? (initialOutputFormat === "gif" ? 10 : 24));
137
+ const [orientation, setOrientation] = useState(
138
+ defaultConfig?.orientation ?? "landscape"
139
+ );
140
+ const [captionMode, setCaptionMode] = useState(defaultConfig?.captionMode ?? "off");
141
+ const [animationsEnabled, setAnimationsEnabled] = useState(
142
+ defaultConfig?.animationsEnabled ?? initialOutputFormat === "mp4"
143
+ );
144
+ const [audioPolicy, setAudioPolicy] = useState(
145
+ defaultConfig?.audioPolicy ?? "require"
146
+ );
147
+ const palette = {
148
+ ...VIDEO_EXPORT_PALETTES[colorScheme],
149
+ ...uiPalette
150
+ };
151
+ const themedModalStyle = {
152
+ ...modalStyle,
153
+ background: palette.surface,
154
+ border: `1px solid ${palette.border}`,
155
+ color: palette.text,
156
+ colorScheme
157
+ };
158
+ const themedTitleStyle = { ...titleStyle, color: palette.heading };
159
+ const themedLabelStyle = { ...labelStyle, color: palette.label };
160
+ const themedSelectStyle = {
161
+ ...selectStyle,
162
+ border: `1px solid ${palette.border}`,
163
+ background: palette.control,
164
+ color: palette.text,
165
+ colorScheme
166
+ };
167
+ const themedPrimaryButtonStyle = {
168
+ ...btnPrimary,
169
+ background: palette.primary,
170
+ border: `1px solid ${palette.primaryBorder}`,
171
+ color: palette.primaryText
172
+ };
173
+ const themedSecondaryButtonStyle = {
174
+ ...btnSecondary,
175
+ background: palette.secondary,
176
+ color: palette.text,
177
+ border: `1px solid ${palette.border}`
178
+ };
179
+ const exportHook = useVideoExport();
180
+ const {
181
+ state,
182
+ progress,
183
+ backend,
184
+ outputFormat: completedOutputFormat,
185
+ downloadUrl,
186
+ fileSize,
187
+ audioIncluded,
188
+ audioSkippedReason,
189
+ error,
190
+ elapsed,
191
+ estimatedRemaining,
192
+ startExport,
193
+ cancel: cancelExport,
194
+ reset: resetExport
195
+ } = exportHook;
196
+ const handleOutputFormatChange = useCallback((next) => {
197
+ setOutputFormat(next);
198
+ if (next === "gif") {
199
+ setFps(10);
200
+ setAnimationsEnabled(false);
201
+ } else {
202
+ setFps(24);
203
+ setAnimationsEnabled(true);
204
+ }
205
+ }, []);
206
+ const handleExport = useCallback(async () => {
207
+ const config = {
208
+ // defaultConfig is the base; explicit props/selections win over it.
209
+ ...defaultConfig,
210
+ outputFormat,
211
+ animationsEnabled,
212
+ quality,
213
+ fps,
214
+ orientation,
215
+ captionMode,
216
+ audioPolicy,
217
+ images,
218
+ audio,
219
+ mediaProvider,
220
+ // Only thread the bundle through when the host actually supplied one.
221
+ ...playerScript !== void 0 ? { playerScript } : {}
222
+ };
223
+ await startExport(doc, config);
224
+ }, [
225
+ doc,
226
+ outputFormat,
227
+ animationsEnabled,
228
+ quality,
229
+ fps,
230
+ orientation,
231
+ captionMode,
232
+ audioPolicy,
233
+ images,
234
+ audio,
235
+ mediaProvider,
236
+ playerScript,
237
+ defaultConfig,
238
+ startExport
239
+ ]);
240
+ const handleDownload = useCallback(() => {
241
+ if (!downloadUrl) return;
242
+ const a = document.createElement("a");
243
+ a.href = downloadUrl;
244
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
245
+ a.download = `document-${ts}.${completedOutputFormat}`;
246
+ document.body.appendChild(a);
247
+ a.click();
248
+ document.body.removeChild(a);
249
+ }, [downloadUrl, completedOutputFormat]);
250
+ const handleClose = useCallback(() => {
251
+ if (state === "capturing" || state === "encoding" || state === "preparing") {
252
+ cancelExport();
253
+ }
254
+ resetExport();
255
+ onClose();
256
+ }, [state, cancelExport, resetExport, onClose]);
257
+ const isExporting = state === "preparing" || state === "capturing" || state === "encoding";
258
+ useModalDialog({ rootRef: overlayRef, dialogRef, onClose: handleClose });
259
+ return /* @__PURE__ */ jsx(
260
+ "div",
261
+ {
262
+ ref: overlayRef,
263
+ style: { ...overlayStyle, background: palette.overlay },
264
+ "data-color-scheme": colorScheme,
265
+ onClick: handleClose,
266
+ children: /* @__PURE__ */ jsxs(
267
+ "div",
268
+ {
269
+ ref: dialogRef,
270
+ role: "dialog",
271
+ "aria-modal": "true",
272
+ "aria-labelledby": titleId,
273
+ tabIndex: -1,
274
+ style: themedModalStyle,
275
+ "data-squisq-video-export-modal": true,
276
+ onClick: (e) => e.stopPropagation(),
277
+ children: [
278
+ /* @__PURE__ */ jsx("h2", { id: titleId, style: themedTitleStyle, children: outputFormat === "gif" ? "Export Animated GIF" : "Export Video" }),
279
+ state === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
280
+ /* @__PURE__ */ jsxs("div", { children: [
281
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Format" }),
282
+ /* @__PURE__ */ jsxs(
283
+ "select",
284
+ {
285
+ "aria-label": "Format",
286
+ style: themedSelectStyle,
287
+ value: outputFormat,
288
+ onChange: (e) => handleOutputFormatChange(e.target.value),
289
+ children: [
290
+ /* @__PURE__ */ jsx("option", { value: "mp4", children: "MP4 video" }),
291
+ /* @__PURE__ */ jsx("option", { value: "gif", children: "Animated GIF" })
292
+ ]
293
+ }
294
+ )
295
+ ] }),
296
+ /* @__PURE__ */ jsxs("div", { children: [
297
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Quality" }),
298
+ /* @__PURE__ */ jsxs(
299
+ "select",
300
+ {
301
+ "aria-label": "Quality",
302
+ style: themedSelectStyle,
303
+ value: quality,
304
+ onChange: (e) => setQuality(e.target.value),
305
+ children: [
306
+ /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft \u2014 fast, lower quality" }),
307
+ /* @__PURE__ */ jsx("option", { value: "normal", children: "Normal \u2014 balanced" }),
308
+ /* @__PURE__ */ jsx("option", { value: "high", children: "High \u2014 best quality, slower" })
309
+ ]
310
+ }
311
+ )
312
+ ] }),
313
+ /* @__PURE__ */ jsxs("div", { children: [
314
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Frame Rate" }),
315
+ /* @__PURE__ */ jsxs(
316
+ "select",
317
+ {
318
+ "aria-label": "Frame Rate",
319
+ style: themedSelectStyle,
320
+ value: fps,
321
+ onChange: (e) => setFps(Number(e.target.value)),
322
+ children: [
323
+ /* @__PURE__ */ jsx("option", { value: 10, children: "10 fps \u2014 recommended for GIF" }),
324
+ /* @__PURE__ */ jsx("option", { value: 15, children: "15 fps \u2014 fast export" }),
325
+ /* @__PURE__ */ jsx("option", { value: 24, children: "24 fps \u2014 cinematic" }),
326
+ /* @__PURE__ */ jsx("option", { value: 30, children: "30 fps \u2014 smooth" })
327
+ ]
328
+ }
329
+ )
330
+ ] }),
331
+ /* @__PURE__ */ jsxs("div", { children: [
332
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Orientation" }),
333
+ /* @__PURE__ */ jsxs(
334
+ "select",
335
+ {
336
+ "aria-label": "Orientation",
337
+ style: themedSelectStyle,
338
+ value: orientation,
339
+ onChange: (e) => setOrientation(e.target.value),
340
+ children: [
341
+ /* @__PURE__ */ jsxs("option", { value: "landscape", children: [
342
+ "Landscape (",
343
+ outputFormat === "gif" ? "960 \xD7 540" : "1920 \xD7 1080",
344
+ ")"
345
+ ] }),
346
+ /* @__PURE__ */ jsxs("option", { value: "portrait", children: [
347
+ "Portrait (",
348
+ outputFormat === "gif" ? "540 \xD7 960" : "1080 \xD7 1920",
349
+ ")"
350
+ ] })
351
+ ]
352
+ }
353
+ )
354
+ ] }),
355
+ /* @__PURE__ */ jsxs("div", { children: [
356
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Captions" }),
357
+ /* @__PURE__ */ jsxs(
358
+ "select",
359
+ {
360
+ "aria-label": "Captions",
361
+ style: themedSelectStyle,
362
+ value: captionMode,
363
+ onChange: (e) => setCaptionMode(e.target.value),
364
+ children: [
365
+ /* @__PURE__ */ jsx("option", { value: "off", children: "None" }),
366
+ /* @__PURE__ */ jsx("option", { value: "standard", children: "Standard (top bar)" }),
367
+ /* @__PURE__ */ jsx("option", { value: "social", children: "Social media (large words)" })
368
+ ]
369
+ }
370
+ )
371
+ ] }),
372
+ outputFormat === "mp4" && /* @__PURE__ */ jsxs("div", { children: [
373
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Audio" }),
374
+ /* @__PURE__ */ jsxs(
375
+ "select",
376
+ {
377
+ "aria-label": "Audio handling",
378
+ style: themedSelectStyle,
379
+ value: audioPolicy,
380
+ onChange: (e) => setAudioPolicy(e.target.value),
381
+ children: [
382
+ /* @__PURE__ */ jsx("option", { value: "require", children: "Require document audio" }),
383
+ /* @__PURE__ */ jsx("option", { value: "best-effort", children: "Best effort \u2014 allow video-only fallback" }),
384
+ /* @__PURE__ */ jsx("option", { value: "omit", children: "Omit audio intentionally" })
385
+ ]
386
+ }
387
+ )
388
+ ] }),
389
+ /* @__PURE__ */ jsxs("div", { children: [
390
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Animations & transitions" }),
391
+ /* @__PURE__ */ jsxs(
392
+ "select",
393
+ {
394
+ "aria-label": "Animations and transitions",
395
+ style: themedSelectStyle,
396
+ value: animationsEnabled ? "enabled" : "disabled",
397
+ onChange: (e) => setAnimationsEnabled(e.target.value === "enabled"),
398
+ children: [
399
+ /* @__PURE__ */ jsx("option", { value: "enabled", children: "Enabled" }),
400
+ /* @__PURE__ */ jsx("option", { value: "disabled", children: "Disabled \u2014 smaller files" })
401
+ ]
402
+ }
403
+ ),
404
+ outputFormat === "gif" && animationsEnabled && /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "-6px 0 12px" }, children: "Disabling motion is recommended for much smaller GIFs." })
405
+ ] }),
406
+ /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
407
+ /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Cancel" }),
408
+ /* @__PURE__ */ jsx("button", { style: themedPrimaryButtonStyle, onClick: handleExport, children: outputFormat === "gif" ? "Export GIF" : "Export Video" })
409
+ ] })
410
+ ] }),
411
+ isExporting && /* @__PURE__ */ jsxs(Fragment, { children: [
412
+ backend && /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 8px 0" }, children: [
413
+ "Encoder: ",
414
+ encoderLabel(completedOutputFormat, backend)
415
+ ] }),
416
+ /* @__PURE__ */ jsx(
417
+ "div",
418
+ {
419
+ role: "progressbar",
420
+ "aria-label": "Video export progress",
421
+ "aria-valuemin": 0,
422
+ "aria-valuemax": 100,
423
+ "aria-valuenow": progress,
424
+ style: { ...progressBarOuterStyle, background: palette.secondary },
425
+ "data-squisq-video-export-progress-track": true,
426
+ children: /* @__PURE__ */ jsx(
427
+ "div",
428
+ {
429
+ style: {
430
+ width: `${progress}%`,
431
+ height: "100%",
432
+ background: palette.primary,
433
+ transition: "width 0.3s ease"
434
+ },
435
+ "data-squisq-video-export-progress": true
436
+ }
437
+ )
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, margin: "0 0 4px 0" }, children: [
441
+ progress,
442
+ "% complete"
443
+ ] }),
444
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: 0 }, children: [
445
+ formatDuration(elapsed),
446
+ " elapsed",
447
+ estimatedRemaining > 0 && ` \xB7 ~${formatDuration(estimatedRemaining)} remaining`
448
+ ] }),
449
+ /* @__PURE__ */ jsx("div", { style: footerStyle, children: /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: cancelExport, children: "Cancel" }) })
450
+ ] }),
451
+ state === "complete" && /* @__PURE__ */ jsxs(Fragment, { children: [
452
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 14, margin: "0 0 8px 0", color: palette.success }, children: "Export complete!" }),
453
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, color: palette.label, margin: "0 0 4px 0" }, children: [
454
+ "File size: ",
455
+ (fileSize / (1024 * 1024)).toFixed(1),
456
+ " MB"
457
+ ] }),
458
+ completedOutputFormat === "gif" ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 4px 0" }, children: "Animated GIF does not include audio." }) : audioIncluded ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.success, margin: "0 0 4px 0" }, children: "Audio included \u2713" }) : /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 4px 0" }, children: [
459
+ "Video only",
460
+ audioSkippedReason ? ` \u2014 ${audioSkippedReason}` : ""
461
+ ] }),
462
+ backend && /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 12px 0" }, children: [
463
+ "Encoded with ",
464
+ encoderLabel(completedOutputFormat, backend)
465
+ ] }),
466
+ /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
467
+ /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Close" }),
468
+ /* @__PURE__ */ jsxs("button", { style: themedPrimaryButtonStyle, onClick: handleDownload, children: [
469
+ "Download ",
470
+ completedOutputFormat.toUpperCase()
471
+ ] })
472
+ ] })
473
+ ] }),
474
+ state === "error" && /* @__PURE__ */ jsxs(Fragment, { children: [
475
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 14, margin: "0 0 8px 0", color: palette.danger }, children: "Export failed" }),
476
+ /* @__PURE__ */ jsx(
477
+ "p",
478
+ {
479
+ style: {
480
+ fontSize: 13,
481
+ color: palette.label,
482
+ margin: "0 0 12px 0",
483
+ wordBreak: "break-word"
484
+ },
485
+ children: error
486
+ }
487
+ ),
488
+ /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
489
+ /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Close" }),
490
+ /* @__PURE__ */ jsxs("button", { style: themedPrimaryButtonStyle, onClick: handleExport, children: [
491
+ "Retry ",
492
+ outputFormat === "gif" ? "GIF" : "video"
493
+ ] })
494
+ ] })
495
+ ] })
496
+ ]
497
+ }
498
+ )
499
+ }
500
+ );
501
+ }
502
+
503
+ // src/VideoExportButton.tsx
504
+ import { useState as useState2, useCallback as useCallback2 } from "react";
505
+ import { createPortal } from "react-dom";
506
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
507
+ function VideoExportButton({
508
+ doc,
509
+ playerScript,
510
+ mediaProvider,
511
+ images,
512
+ audio,
513
+ defaultConfig,
514
+ colorScheme,
515
+ uiPalette,
516
+ label,
517
+ style,
518
+ disabled
519
+ }) {
520
+ const [showModal, setShowModal] = useState2(false);
521
+ const resolvedLabel = label ?? (defaultConfig?.outputFormat === "gif" ? "Export GIF" : "Export Video");
522
+ const handleOpen = useCallback2(() => setShowModal(true), []);
523
+ const handleClose = useCallback2(() => setShowModal(false), []);
524
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
525
+ /* @__PURE__ */ jsx2("button", { onClick: handleOpen, disabled, style, children: resolvedLabel }),
526
+ showModal && createPortal(
527
+ /* @__PURE__ */ jsx2(
528
+ VideoExportModal,
529
+ {
530
+ doc,
531
+ playerScript,
532
+ mediaProvider,
533
+ images,
534
+ audio,
535
+ defaultConfig,
536
+ colorScheme,
537
+ uiPalette,
538
+ onClose: handleClose
539
+ }
540
+ ),
541
+ document.body
542
+ )
543
+ ] });
544
+ }
545
+
546
+ export {
547
+ VideoExportModal,
548
+ VideoExportButton
549
+ };