@bendyline/squisq-video-react 2.2.5 → 2.2.7

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.
@@ -172,15 +172,27 @@ async function renderAudioTimeline(clips, buffers, totalDurationSec, sampleRate
172
172
  const length = Math.max(1, Math.ceil(totalDurationSec * sampleRate));
173
173
  const ctx = new Ctor(channels, length, sampleRate);
174
174
  const decoded = /* @__PURE__ */ new Map();
175
+ const decodeFailures = [];
175
176
  for (const [src, data] of buffers) {
176
177
  try {
177
178
  decoded.set(src, await ctx.decodeAudioData(data.slice(0)));
178
179
  } catch {
180
+ decodeFailures.push(src);
179
181
  }
180
182
  }
183
+ const requiredFailures = decodeFailures.filter(
184
+ (src) => clips.some((clip) => clip.src === src && clip.sourceKind !== "video")
185
+ );
186
+ if (requiredFailures.length > 0) {
187
+ throw new Error(`No decodable audio track was found in: ${requiredFailures.join(", ")}`);
188
+ }
189
+ let scheduledNodes = 0;
181
190
  for (const clip of clips) {
182
191
  const buffer = decoded.get(clip.src);
183
- if (!buffer) continue;
192
+ if (!buffer) {
193
+ if (clip.sourceKind === "video") continue;
194
+ throw new Error(`Audio source was not decoded: ${clip.src}`);
195
+ }
184
196
  const node = ctx.createBufferSource();
185
197
  node.buffer = buffer;
186
198
  node.connect(ctx.destination);
@@ -188,7 +200,9 @@ async function renderAudioTimeline(clips, buffers, totalDurationSec, sampleRate
188
200
  const offset = Math.max(0, clip.sourceInSec);
189
201
  const duration = Math.max(0, clip.durationSec);
190
202
  node.start(when, offset, duration);
203
+ scheduledNodes++;
191
204
  }
205
+ if (scheduledNodes === 0) return null;
192
206
  return ctx.startRendering();
193
207
  }
194
208
  async function encodeAacTrack(audioBuffer, sink, bitrate) {
@@ -10,7 +10,7 @@ import {
10
10
  supportsWebCodecs,
11
11
  supportsWebCodecsAac,
12
12
  supportsWebCodecsH264
13
- } from "./chunk-ZQJBUX75.js";
13
+ } from "./chunk-2XACUF6E.js";
14
14
 
15
15
  // src/hooks/useFrameCapture.ts
16
16
  import { createElement } from "react";
@@ -1168,6 +1168,9 @@ function useVideoExport(options = {}) {
1168
1168
  totalAudioDur,
1169
1169
  EXPORT_AUDIO_SAMPLE_RATE
1170
1170
  );
1171
+ if (!renderedAudio) {
1172
+ audioReasonLocal = "No included video source contained a decodable audio track.";
1173
+ }
1171
1174
  }
1172
1175
  } catch (audioErr) {
1173
1176
  renderedAudio = null;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useVideoExport
3
- } from "./chunk-NZYFSB2T.js";
3
+ } from "./chunk-NX34XGLL.js";
4
4
 
5
5
  // src/VideoExportModal.tsx
6
6
  import { useState, useCallback, useId, useRef } from "react";
@@ -179,9 +179,7 @@ function VideoExportModal({
179
179
  const [animationsEnabled, setAnimationsEnabled] = useState(
180
180
  defaultConfig?.animationsEnabled ?? initialOutputFormat === "mp4"
181
181
  );
182
- const [audioPolicy, setAudioPolicy] = useState(
183
- defaultConfig?.audioPolicy ?? "require"
184
- );
182
+ const [includeAudio, setIncludeAudio] = useState(defaultConfig?.audioPolicy !== "omit");
185
183
  const palette = {
186
184
  ...VIDEO_EXPORT_PALETTES[colorScheme],
187
185
  ...uiPalette
@@ -268,7 +266,7 @@ function VideoExportModal({
268
266
  fps,
269
267
  orientation,
270
268
  captionMode,
271
- audioPolicy,
269
+ audioPolicy: includeAudio ? "require" : "omit",
272
270
  images,
273
271
  audio,
274
272
  mediaProvider,
@@ -284,7 +282,7 @@ function VideoExportModal({
284
282
  fps,
285
283
  orientation,
286
284
  captionMode,
287
- audioPolicy,
285
+ includeAudio,
288
286
  images,
289
287
  audio,
290
288
  mediaProvider,
@@ -443,16 +441,28 @@ function VideoExportModal({
443
441
  outputFormat === "mp4" && /* @__PURE__ */ jsxs("div", { children: [
444
442
  /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Audio" }),
445
443
  /* @__PURE__ */ jsxs(
446
- "select",
444
+ "label",
447
445
  {
448
- "aria-label": "Audio handling",
449
- style: themedSelectStyle,
450
- value: audioPolicy,
451
- onChange: (e) => setAudioPolicy(e.target.value),
446
+ style: {
447
+ display: "flex",
448
+ alignItems: "center",
449
+ gap: 8,
450
+ marginBottom: 12,
451
+ fontSize: 14,
452
+ color: palette.text,
453
+ cursor: "pointer"
454
+ },
452
455
  children: [
453
- /* @__PURE__ */ jsx("option", { value: "require", children: "Require document audio" }),
454
- /* @__PURE__ */ jsx("option", { value: "best-effort", children: "Best effort \u2014 allow video-only fallback" }),
455
- /* @__PURE__ */ jsx("option", { value: "omit", children: "Omit audio intentionally" })
456
+ /* @__PURE__ */ jsx(
457
+ "input",
458
+ {
459
+ type: "checkbox",
460
+ "aria-label": "Include audio",
461
+ checked: includeAudio,
462
+ onChange: (e) => setIncludeAudio(e.target.checked)
463
+ }
464
+ ),
465
+ "Include audio"
456
466
  ]
457
467
  }
458
468
  )
@@ -575,7 +585,7 @@ function VideoExportModal({
575
585
  (fileSize / (1024 * 1024)).toFixed(1),
576
586
  " MB"
577
587
  ] }),
578
- 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: [
588
+ completedOutputFormat === "gif" ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 4px 0" }, children: "Animated GIF does not include audio." }) : !includeAudio ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 4px 0" }, children: "Audio excluded" }) : 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: [
579
589
  "Video only",
580
590
  audioSkippedReason ? ` \u2014 ${audioSkippedReason}` : ""
581
591
  ] }),
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Doc, MediaProvider } from '@bendyline/squisq/schemas';
3
- import { a as VideoExportConfig } from '../useVideoExport-Okwrf8gg.js';
3
+ import { a as VideoExportConfig } from '../useVideoExport-DKpdXZ0o.js';
4
4
  import '@bendyline/squisq/markdown';
5
5
  import '@bendyline/squisq-video';
6
6
  import '@bendyline/squisq-react';
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  VideoExportButton,
3
3
  VideoExportModal
4
- } from "../chunk-U2TST3MN.js";
5
- import "../chunk-NZYFSB2T.js";
6
- import "../chunk-ZQJBUX75.js";
4
+ } from "../chunk-SO656KT7.js";
5
+ import "../chunk-NX34XGLL.js";
6
+ import "../chunk-2XACUF6E.js";
7
7
  import "../chunk-MEPETH5V.js";
8
8
  export {
9
9
  VideoExportButton,
@@ -3,7 +3,7 @@ import {
3
3
  supportsWebCodecs,
4
4
  supportsWebCodecsAac,
5
5
  supportsWebCodecsH264
6
- } from "../chunk-ZQJBUX75.js";
6
+ } from "../chunk-2XACUF6E.js";
7
7
  import "../chunk-MEPETH5V.js";
8
8
  export {
9
9
  createEncoder,
@@ -1,4 +1,4 @@
1
- export { D as DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS, R as ResolvedVideoExportCover, U as UseVideoExportOptions, V as VideoAudioPolicy, a as VideoExportConfig, b as VideoExportFramePreview, c as VideoExportResult, d as VideoExportState, e as VideoOutputFormat, r as resolveVideoExportCover, u as useVideoExport } from '../useVideoExport-Okwrf8gg.js';
1
+ export { D as DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS, R as ResolvedVideoExportCover, U as UseVideoExportOptions, V as VideoAudioPolicy, a as VideoExportConfig, b as VideoExportFramePreview, c as VideoExportResult, d as VideoExportState, e as VideoOutputFormat, r as resolveVideoExportCover, u as useVideoExport } from '../useVideoExport-DKpdXZ0o.js';
2
2
  import { Doc } from '@bendyline/squisq/schemas';
3
3
  import { RenderHtmlOptions } from '@bendyline/squisq-video';
4
4
  import { CaptionMode } from '@bendyline/squisq-react';
@@ -3,8 +3,8 @@ import {
3
3
  resolveVideoExportCover,
4
4
  useFrameCapture,
5
5
  useVideoExport
6
- } from "../chunk-NZYFSB2T.js";
7
- import "../chunk-ZQJBUX75.js";
6
+ } from "../chunk-NX34XGLL.js";
7
+ import "../chunk-2XACUF6E.js";
8
8
  import "../chunk-MEPETH5V.js";
9
9
  export {
10
10
  DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { VideoExportButton, VideoExportButtonProps, VideoExportModal, VideoExportModalProps, VideoExportPalette } from './components/index.js';
2
- export { D as DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS, R as ResolvedVideoExportCover, U as UseVideoExportOptions, V as VideoAudioPolicy, a as VideoExportConfig, b as VideoExportFramePreview, c as VideoExportResult, d as VideoExportState, e as VideoOutputFormat, r as resolveVideoExportCover, u as useVideoExport } from './useVideoExport-Okwrf8gg.js';
2
+ export { D as DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS, R as ResolvedVideoExportCover, U as UseVideoExportOptions, V as VideoAudioPolicy, a as VideoExportConfig, b as VideoExportFramePreview, c as VideoExportResult, d as VideoExportState, e as VideoOutputFormat, r as resolveVideoExportCover, u as useVideoExport } from './useVideoExport-DKpdXZ0o.js';
3
3
  export { FrameCaptureHandle, FrameCaptureOptions, FrameCaptureRenderOptions, useFrameCapture } from './hooks/index.js';
4
4
  export { E as EncoderConfig, a as EncoderFrameSource, M as MainThreadEncoder, c as createEncoder, s as supportsWebCodecs, b as supportsWebCodecsH264 } from './mainThreadEncoder-BgcFyYvO.js';
5
5
  export { FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
package/dist/index.js CHANGED
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  VideoExportButton,
3
3
  VideoExportModal
4
- } from "./chunk-U2TST3MN.js";
4
+ } from "./chunk-SO656KT7.js";
5
5
  import {
6
6
  DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS,
7
7
  resolveVideoExportCover,
8
8
  useFrameCapture,
9
9
  useVideoExport
10
- } from "./chunk-NZYFSB2T.js";
10
+ } from "./chunk-NX34XGLL.js";
11
11
  import {
12
12
  createEncoder,
13
13
  supportsWebCodecs,
14
14
  supportsWebCodecsAac,
15
15
  supportsWebCodecsH264
16
- } from "./chunk-ZQJBUX75.js";
16
+ } from "./chunk-2XACUF6E.js";
17
17
  import "./chunk-MEPETH5V.js";
18
18
  export {
19
19
  DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS,
@@ -119,8 +119,8 @@ interface VideoExportResult {
119
119
  /**
120
120
  * Why audio is absent, when `audioIncluded` is false. `null` means the doc
121
121
  * simply had no audio (not a failure); a non-null string explains a genuine
122
- * capability shortfall or runtime failure. Audio problems never fail the
123
- * export the video always completes.
122
+ * capability shortfall or runtime failure. The default `require` policy
123
+ * fails the export instead of returning a misleading video-only success.
124
124
  */
125
125
  audioSkippedReason: string | null;
126
126
  /** Error message (populated when state === 'error') */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video-react",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "React components for browser-based MP4 and animated-GIF export of Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -65,9 +65,9 @@
65
65
  "react-dom": "^18.0.0 || ^19.0.0"
66
66
  },
67
67
  "dependencies": {
68
- "@bendyline/squisq": "2.4.1",
69
- "@bendyline/squisq-video": "2.2.5",
70
- "@bendyline/squisq-react": "2.4.1",
68
+ "@bendyline/squisq": "2.4.3",
69
+ "@bendyline/squisq-video": "2.2.7",
70
+ "@bendyline/squisq-react": "2.4.3",
71
71
  "@ffmpeg/core": "0.12.9",
72
72
  "@ffmpeg/ffmpeg": "0.12.15",
73
73
  "@ffmpeg/util": "0.12.2",