@bendyline/squisq-editor-react 2.11.17 → 2.11.19

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 (30) hide show
  1. package/dist/{chunk-US5DALTZ.js → chunk-5EB3BGPE.js} +2419 -2025
  2. package/dist/{chunk-QZWFSSDE.js → chunk-DJS5I5NO.js} +1 -1
  3. package/dist/chunk-DNEVUOBZ.js +14 -0
  4. package/dist/chunk-DNJ6HOES.js +93 -0
  5. package/dist/{chunk-DPAMJV6A.js → chunk-HSWWEWT4.js} +31 -2
  6. package/dist/{chunk-EJKGXWNK.js → chunk-LR6Q2MK4.js} +1 -1
  7. package/dist/chunk-LRZJD5YD.js +107 -0
  8. package/dist/{chunk-2XXBDECJ.js → chunk-OCQEWWHI.js} +1 -1
  9. package/dist/{chunk-HTO5MTTJ.js → chunk-TZX7RKZV.js} +1107 -130
  10. package/dist/{chunk-C6QVA6TO.js → chunk-UFK47ZAI.js} +58 -133
  11. package/dist/{chunk-UBFNLWCN.js → chunk-V2OLZOYJ.js} +2 -2
  12. package/dist/{chunk-XZVMKR5Q.js → chunk-ZS6G2P57.js} +41 -96
  13. package/dist/dictationSession-PN3OT5LJ.js +521 -0
  14. package/dist/index.d.ts +30 -11
  15. package/dist/index.js +53 -21
  16. package/dist/json-editor/index.js +3 -3
  17. package/dist/recorder/index.d.ts +2 -2
  18. package/dist/recorder/index.js +17 -13
  19. package/dist/{recorder-CRsw0eC7.d.ts → recorder-DKN3vZPi.d.ts} +1 -1
  20. package/dist/review/index.js +3 -2
  21. package/dist/shell/index.d.ts +2 -1
  22. package/dist/shell/index.js +10 -7
  23. package/dist/{shell-X0EoQbbN.d.ts → shell-Cje1A3zN.d.ts} +176 -5
  24. package/dist/speech/index.d.ts +126 -0
  25. package/dist/speech/index.js +10 -0
  26. package/dist/styles/index.css +142 -3
  27. package/dist/teleprompter/index.d.ts +13 -5
  28. package/dist/teleprompter/index.js +3 -2
  29. package/dist/{useNarrationStage-vKtk6H-V.d.ts → useNarrationStage-CB8dynUg.d.ts} +15 -1
  30. package/package.json +10 -6
@@ -1,25 +1,35 @@
1
+ import {
2
+ DEFAULT_RECORDER_DEVICE_SETTINGS,
3
+ buildRecorderAudioConstraints,
4
+ buildRecorderCameraConstraints,
5
+ buildRecorderScreenAudioConstraints,
6
+ buildRecorderScreenConstraints,
7
+ recorderBitsPerSecond
8
+ } from "./chunk-LRZJD5YD.js";
1
9
  import {
2
10
  Icon
3
11
  } from "./chunk-GS7QWYFT.js";
4
12
  import {
5
13
  NarrationStage,
6
- buildFilename,
7
14
  buildTimingJson,
8
15
  encodeNarrationTimingJson,
9
16
  encodeTimingJson,
10
17
  formatRecordingBytes,
11
18
  requestCameraStream,
19
+ resolveMaxRecordingBytes,
20
+ timingPathFor,
21
+ useNarrationStage,
22
+ useStreamPreview
23
+ } from "./chunk-ZS6G2P57.js";
24
+ import {
25
+ buildFilename,
12
26
  requestMicStream,
13
27
  resolveFormat,
14
- resolveMaxRecordingBytes,
15
28
  supportsDisplayMedia,
16
29
  supportsMediaRecorder,
17
30
  supportsSystemAudioCapture,
18
- supportsUserMedia,
19
- timingPathFor,
20
- useNarrationStage,
21
- useStreamPreview
22
- } from "./chunk-XZVMKR5Q.js";
31
+ supportsUserMedia
32
+ } from "./chunk-DNJ6HOES.js";
23
33
 
24
34
  // src/recorder/sources/screenRegion.ts
25
35
  function validateScreenCaptureRegion(region, width, height) {
@@ -1707,105 +1717,6 @@ function RecorderDeviceQuickPicks({
1707
1717
  ] });
1708
1718
  }
1709
1719
 
1710
- // src/recorder/deviceSettings.ts
1711
- var DEFAULT_RECORDER_DEVICE_SETTINGS = {
1712
- constraintMode: "ideal",
1713
- audio: { deviceId: "" },
1714
- camera: { deviceId: "" },
1715
- screen: {},
1716
- screenAudio: {},
1717
- encoding: {
1718
- audioMimeType: "",
1719
- videoMimeType: ""
1720
- }
1721
- };
1722
- function preferredNumber(value, mode) {
1723
- if (value === void 0 || !Number.isFinite(value) || value <= 0) return void 0;
1724
- return { [mode]: value };
1725
- }
1726
- function preferredInteger(value, mode) {
1727
- if (value === void 0 || !Number.isFinite(value) || value <= 0) return void 0;
1728
- return { [mode]: Math.round(value) };
1729
- }
1730
- function preferredString(value, mode) {
1731
- const trimmed = value?.trim();
1732
- return trimmed ? { [mode]: trimmed } : void 0;
1733
- }
1734
- function buildRecorderAudioConstraints(settings) {
1735
- const { audio, constraintMode } = settings;
1736
- const result = {};
1737
- if (audio.deviceId) result.deviceId = { exact: audio.deviceId };
1738
- const groupId = preferredString(audio.groupId, constraintMode);
1739
- if (groupId) result.groupId = groupId;
1740
- if (audio.autoGainControl !== void 0) result.autoGainControl = audio.autoGainControl;
1741
- if (audio.echoCancellation !== void 0) result.echoCancellation = audio.echoCancellation;
1742
- if (audio.noiseSuppression !== void 0) result.noiseSuppression = audio.noiseSuppression;
1743
- const channelCount = preferredInteger(audio.channelCount, constraintMode);
1744
- const latency = preferredNumber(audio.latency, constraintMode);
1745
- const sampleRate = preferredInteger(audio.sampleRate, constraintMode);
1746
- const sampleSize = preferredInteger(audio.sampleSize, constraintMode);
1747
- if (channelCount) result.channelCount = channelCount;
1748
- if (latency) result.latency = latency;
1749
- if (sampleRate) result.sampleRate = sampleRate;
1750
- if (sampleSize) result.sampleSize = sampleSize;
1751
- return result;
1752
- }
1753
- function buildRecorderCameraConstraints(settings) {
1754
- const { camera, constraintMode } = settings;
1755
- const result = {};
1756
- if (camera.deviceId) result.deviceId = { exact: camera.deviceId };
1757
- const groupId = preferredString(camera.groupId, constraintMode);
1758
- if (groupId) result.groupId = groupId;
1759
- if (camera.backgroundBlur !== void 0) result.backgroundBlur = camera.backgroundBlur;
1760
- const aspectRatio = preferredNumber(camera.aspectRatio, constraintMode);
1761
- const facingMode = preferredString(camera.facingMode, constraintMode);
1762
- const frameRate = preferredNumber(camera.frameRate, constraintMode);
1763
- const height = preferredInteger(camera.height, constraintMode);
1764
- const resizeMode = preferredString(camera.resizeMode, constraintMode);
1765
- const width = preferredInteger(camera.width, constraintMode);
1766
- if (aspectRatio) result.aspectRatio = aspectRatio;
1767
- if (facingMode) result.facingMode = facingMode;
1768
- if (frameRate) result.frameRate = frameRate;
1769
- if (height) result.height = height;
1770
- if (resizeMode) result.resizeMode = resizeMode;
1771
- if (width) result.width = width;
1772
- return result;
1773
- }
1774
- function buildRecorderScreenConstraints(settings) {
1775
- const { screen } = settings;
1776
- const result = {};
1777
- const aspectRatio = preferredNumber(screen.aspectRatio, "ideal");
1778
- const cursor = preferredString(screen.cursor, "ideal");
1779
- const displaySurface = preferredString(screen.displaySurface, "ideal");
1780
- const frameRate = preferredNumber(screen.frameRate, "ideal");
1781
- const height = preferredInteger(screen.height, "ideal");
1782
- const resizeMode = preferredString(screen.resizeMode, "ideal");
1783
- const width = preferredInteger(screen.width, "ideal");
1784
- if (screen.logicalSurface !== void 0) result.logicalSurface = screen.logicalSurface;
1785
- if (aspectRatio) result.aspectRatio = aspectRatio;
1786
- if (cursor) result.cursor = cursor;
1787
- if (displaySurface) result.displaySurface = displaySurface;
1788
- if (frameRate) result.frameRate = frameRate;
1789
- if (height) result.height = height;
1790
- if (resizeMode) result.resizeMode = resizeMode;
1791
- if (width) result.width = width;
1792
- return result;
1793
- }
1794
- function buildRecorderScreenAudioConstraints(settings) {
1795
- const result = {};
1796
- if (settings.screenAudio.restrictOwnAudio !== void 0) {
1797
- result.restrictOwnAudio = settings.screenAudio.restrictOwnAudio;
1798
- }
1799
- if (settings.screenAudio.suppressLocalAudioPlayback !== void 0) {
1800
- result.suppressLocalAudioPlayback = settings.screenAudio.suppressLocalAudioPlayback;
1801
- }
1802
- return result;
1803
- }
1804
- function recorderBitsPerSecond(value) {
1805
- if (value === void 0 || !Number.isFinite(value) || value <= 0) return void 0;
1806
- return Math.round(value * 1e3);
1807
- }
1808
-
1809
1720
  // src/recorder/recordedMediaKind.ts
1810
1721
  function recordedMediaKind(source, stream, mimeType) {
1811
1722
  if (stream) return stream.getVideoTracks().length > 0 ? "video" : "audio";
@@ -2701,6 +2612,7 @@ function RecorderModal({
2701
2612
  const narrationRecording = narrationOn && Boolean(narration?.recording);
2702
2613
  const expanded = isExpandedPanel(panelMode);
2703
2614
  const [narrationRequesting, setNarrationRequesting] = useState3(false);
2615
+ const [countingIn, setCountingIn] = useState3(false);
2704
2616
  const [narrationPreview, setNarrationPreview] = useState3(null);
2705
2617
  const [deviceSettings, setDeviceSettings] = useState3(() => ({
2706
2618
  ...DEFAULT_RECORDER_DEVICE_SETTINGS,
@@ -3016,17 +2928,27 @@ function RecorderModal({
3016
2928
  } catch {
3017
2929
  }
3018
2930
  }, [recorder]);
3019
- const handleStart = useCallback4(() => {
2931
+ const handleStart = useCallback4(async () => {
3020
2932
  setSaveError(null);
3021
2933
  dualSaveProgressRef.current = {};
2934
+ if (narrationOn) {
2935
+ setCountingIn(true);
2936
+ const rolled = await stageRef.current.controller.countIn();
2937
+ setCountingIn(false);
2938
+ if (!rolled) return;
2939
+ }
3022
2940
  recorder.start();
3023
2941
  const blockId = slideDeckRef.current[slideIndexRef.current]?.blockId;
3024
2942
  setAdvanceLog(blockId ? recordSlideShown(EMPTY_ADVANCE_LOG, blockId, 0) : EMPTY_ADVANCE_LOG);
3025
- }, [recorder]);
2943
+ }, [narrationOn, recorder]);
3026
2944
  const handleStop = useCallback4(async () => {
3027
2945
  setSaveError(null);
2946
+ if (countingIn) {
2947
+ stageRef.current.controller.pause();
2948
+ return;
2949
+ }
3028
2950
  await recorder.stop();
3029
- }, [recorder]);
2951
+ }, [countingIn, recorder]);
3030
2952
  const writeSlideTimingSidecar = useCallback4(
3031
2953
  async (mediaRelativePath, durationSec) => {
3032
2954
  const doc = slides?.doc;
@@ -3219,8 +3141,8 @@ function RecorderModal({
3219
3141
  );
3220
3142
  const isAudioOnly = recordedMediaKind(source, recorder.stream, recorder.mimeType) === "audio";
3221
3143
  const showPreview = recorder.state !== "idle" && recorder.state !== "error";
3222
- const canRecord = recorder.state === "ready";
3223
- const canStop = recorder.state === "recording";
3144
+ const canRecord = recorder.state === "ready" && !countingIn;
3145
+ const canStop = recorder.state === "recording" || countingIn;
3224
3146
  const canSave = recorder.state === "stopped" && recorder.blob !== null;
3225
3147
  const isBusy = recorder.state === "requesting" || recorder.state === "stopping" || isSaving;
3226
3148
  const slidesTimingCapture = slidesOn && slidesCaptureTimings;
@@ -3274,7 +3196,7 @@ function RecorderModal({
3274
3196
  sourceText
3275
3197
  ]);
3276
3198
  const slideCoverageWarning = slideCoverage ? unshownSlidesWarning(slideCoverage.total, slideCoverage.unshown) : null;
3277
- const togglesLocked = recorder.state === "recording" || recorder.state === "requesting" || recorder.state === "stopping" || canSave;
3199
+ const togglesLocked = countingIn || recorder.state === "recording" || recorder.state === "requesting" || recorder.state === "stopping" || canSave;
3278
3200
  const toggleLockReason = canSave ? "Save or discard this recording before changing sources" : void 0;
3279
3201
  const systemAudioHasCompanion = micOn || cameraOn || screenOn;
3280
3202
  const simpleToggleProps = (key) => {
@@ -3769,24 +3691,33 @@ function RecorderModal({
3769
3691
  children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3770
3692
  }
3771
3693
  ),
3772
- canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3773
- /* @__PURE__ */ jsx6(
3774
- "span",
3775
- {
3776
- className: "squisq-recorder-record-dot",
3777
- style: recordDotFrameStyle,
3778
- "aria-hidden": "true",
3779
- children: /* @__PURE__ */ jsx6(
3694
+ canRecord && /* @__PURE__ */ jsxs5(
3695
+ "button",
3696
+ {
3697
+ type: "button",
3698
+ style: btnRecord,
3699
+ onClick: () => void handleStart(),
3700
+ disabled: isBusy,
3701
+ children: [
3702
+ /* @__PURE__ */ jsx6(
3780
3703
  "span",
3781
3704
  {
3782
- className: "squisq-recorder-record-dot-center",
3783
- style: recordDotStyle
3705
+ className: "squisq-recorder-record-dot",
3706
+ style: recordDotFrameStyle,
3707
+ "aria-hidden": "true",
3708
+ children: /* @__PURE__ */ jsx6(
3709
+ "span",
3710
+ {
3711
+ className: "squisq-recorder-record-dot-center",
3712
+ style: recordDotStyle
3713
+ }
3714
+ )
3784
3715
  }
3785
- )
3786
- }
3787
- ),
3788
- "Record"
3789
- ] }),
3716
+ ),
3717
+ "Record"
3718
+ ]
3719
+ }
3720
+ ),
3790
3721
  canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3791
3722
  canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3792
3723
  /* @__PURE__ */ jsx6(
@@ -3922,12 +3853,6 @@ export {
3922
3853
  useMediaDevices,
3923
3854
  RecorderDeviceSettingsPanel,
3924
3855
  RecorderDeviceQuickPicks,
3925
- DEFAULT_RECORDER_DEVICE_SETTINGS,
3926
- buildRecorderAudioConstraints,
3927
- buildRecorderCameraConstraints,
3928
- buildRecorderScreenConstraints,
3929
- buildRecorderScreenAudioConstraints,
3930
- recorderBitsPerSecond,
3931
3856
  recordedMediaKind,
3932
3857
  isExpandedPanel,
3933
3858
  panelModeAfterToggle,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MarkdownEscape
3
- } from "./chunk-2XXBDECJ.js";
3
+ } from "./chunk-OCQEWWHI.js";
4
4
  import {
5
5
  markdownToTiptap,
6
6
  tiptapToMarkdown
7
- } from "./chunk-DPAMJV6A.js";
7
+ } from "./chunk-HSWWEWT4.js";
8
8
 
9
9
  // src/jsonEditor/JsonEditorContext.tsx
10
10
  import { createContext, useContext, useEffect, useMemo } from "react";
@@ -1,86 +1,9 @@
1
- // src/recorder/formats.ts
2
- var AUDIO_CANDIDATES = [
3
- "audio/webm;codecs=opus",
4
- "audio/webm",
5
- "audio/mp4;codecs=mp4a.40.2",
6
- "audio/mp4",
7
- "audio/ogg;codecs=opus"
8
- ];
9
- var VIDEO_CANDIDATES = [
10
- "video/webm;codecs=vp9,opus",
11
- "video/webm;codecs=vp8,opus",
12
- "video/webm",
13
- "video/mp4;codecs=avc1.42E01E,mp4a.40.2",
14
- "video/mp4"
15
- ];
16
- function extensionForMime(mimeType) {
17
- const m = mimeType.toLowerCase();
18
- if (m.startsWith("audio/webm")) return ".webm";
19
- if (m.startsWith("audio/ogg")) return ".ogg";
20
- if (m.startsWith("audio/mp4")) return ".m4a";
21
- if (m.startsWith("audio/mpeg")) return ".mp3";
22
- if (m.startsWith("audio/wav")) return ".wav";
23
- if (m.startsWith("video/webm")) return ".webm";
24
- if (m.startsWith("video/mp4")) return ".mp4";
25
- return ".bin";
26
- }
27
- function probeMimeType(candidates) {
28
- if (typeof MediaRecorder === "undefined") return null;
29
- for (const candidate of candidates) {
30
- try {
31
- if (MediaRecorder.isTypeSupported(candidate)) return candidate;
32
- } catch {
33
- }
34
- }
35
- return null;
36
- }
37
- function resolveFormat(kind, preferred) {
38
- const candidates = kind === "audio" ? AUDIO_CANDIDATES : VIDEO_CANDIDATES;
39
- const probed = (preferred && probeMimeType([preferred])) ?? probeMimeType(candidates) ?? "";
40
- const directory = kind === "audio" ? "audio" : "video";
41
- const extension = probed ? extensionForMime(probed) : ".webm";
42
- return { mimeType: probed, extension, directory };
43
- }
44
- function supportsMediaRecorder() {
45
- return typeof MediaRecorder !== "undefined";
46
- }
47
- function supportsUserMedia() {
48
- return typeof navigator !== "undefined" && typeof navigator.mediaDevices !== "undefined" && typeof navigator.mediaDevices.getUserMedia === "function";
49
- }
50
- function supportsDisplayMedia() {
51
- return typeof navigator !== "undefined" && typeof navigator.mediaDevices !== "undefined" && typeof navigator.mediaDevices.getDisplayMedia === "function";
52
- }
53
- function supportsSystemAudioCapture() {
54
- if (!supportsDisplayMedia()) return false;
55
- const nav = navigator;
56
- const uaData = nav.userAgentData;
57
- if (uaData?.mobile === true) return false;
58
- if (uaData?.brands?.length) {
59
- return uaData.brands.some(({ brand }) => /chromium/i.test(brand));
60
- }
61
- const userAgent = nav.userAgent ?? "";
62
- if (/Android|Mobile|iPhone|iPad|iPod/i.test(userAgent)) return false;
63
- return /(?:Chrome|Chromium|Edg|OPR)\//.test(userAgent);
64
- }
65
- function buildFilename(kind, extension, basename, seed) {
66
- const safe = basename ? basename.trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-") : "";
67
- if (safe) return `${safe}${extension}`;
68
- const now = /* @__PURE__ */ new Date();
69
- const stamp = now.getFullYear().toString().padStart(4, "0") + (now.getMonth() + 1).toString().padStart(2, "0") + now.getDate().toString().padStart(2, "0") + "-" + now.getHours().toString().padStart(2, "0") + now.getMinutes().toString().padStart(2, "0") + now.getSeconds().toString().padStart(2, "0");
70
- const prefix = seed ?? (kind === "audio" ? "narration" : "recording");
71
- return `${prefix}-${stamp}${extension}`;
72
- }
73
-
74
- // src/recorder/sources/micStream.ts
75
- async function requestMicStream(constraints) {
76
- if (!supportsUserMedia()) {
77
- throw new Error("navigator.mediaDevices.getUserMedia is not available in this environment.");
78
- }
79
- return navigator.mediaDevices.getUserMedia({
80
- audio: constraints ?? true,
81
- video: false
82
- });
83
- }
1
+ import {
2
+ buildFilename,
3
+ requestMicStream,
4
+ resolveFormat,
5
+ supportsUserMedia
6
+ } from "./chunk-DNJ6HOES.js";
84
7
 
85
8
  // src/recorder/sources/cameraStream.ts
86
9
  async function requestCameraStream(options) {
@@ -506,6 +429,7 @@ function useTeleprompter(opts) {
506
429
  const levelRef = useRef2(0);
507
430
  const voiceRef = useRef2(false);
508
431
  const countdownTimerRef = useRef2(null);
432
+ const countInWaitersRef = useRef2([]);
509
433
  scriptRef.current = script;
510
434
  prefsRef.current = prefs;
511
435
  transportRef.current = transport;
@@ -530,13 +454,19 @@ function useTeleprompter(opts) {
530
454
  const notifyTick = useCallback2(() => {
531
455
  for (const cb of tickSubsRef.current) cb(wordPosRef.current);
532
456
  }, []);
457
+ const settleCountIn = useCallback2((rolled) => {
458
+ const waiters = countInWaitersRef.current;
459
+ countInWaitersRef.current = [];
460
+ for (const resolve of waiters) resolve(rolled);
461
+ }, []);
533
462
  const clearCountdown = useCallback2(() => {
534
463
  if (countdownTimerRef.current !== null) {
535
464
  clearInterval(countdownTimerRef.current);
536
465
  countdownTimerRef.current = null;
537
466
  }
538
467
  setCountdownRemaining(null);
539
- }, []);
468
+ settleCountIn(false);
469
+ }, [settleCountIn]);
540
470
  const finish = useCallback2(() => {
541
471
  setTransport("finished");
542
472
  transportRef.current = "finished";
@@ -610,13 +540,14 @@ function useTeleprompter(opts) {
610
540
  return () => cancelAnimationFrame(raf);
611
541
  }, [transport, mic.status, finish, notifyTick, publish]);
612
542
  const beginRolling = useCallback2(() => {
543
+ settleCountIn(true);
613
544
  clearCountdown();
614
545
  if (sessionRef.current) {
615
546
  sessionRef.current = reanchorSession(sessionRef.current, wordPosRef.current);
616
547
  }
617
548
  setTransport("rolling");
618
549
  transportRef.current = "rolling";
619
- }, [clearCountdown]);
550
+ }, [clearCountdown, settleCountIn]);
620
551
  const play = useCallback2(() => {
621
552
  if (transportRef.current === "rolling" || transportRef.current === "countdown") return;
622
553
  if (transportRef.current === "finished") {
@@ -641,6 +572,14 @@ function useTeleprompter(opts) {
641
572
  beginRolling();
642
573
  }
643
574
  }, [beginRolling, mic, publish]);
575
+ const countIn = useCallback2(() => {
576
+ if (transportRef.current === "rolling") return Promise.resolve(true);
577
+ const rolled = new Promise((resolve) => {
578
+ countInWaitersRef.current.push(resolve);
579
+ });
580
+ play();
581
+ return rolled;
582
+ }, [play]);
644
583
  const pause = useCallback2(() => {
645
584
  if (transportRef.current === "countdown") {
646
585
  clearCountdown();
@@ -776,6 +715,7 @@ function useTeleprompter(opts) {
776
715
  prefs,
777
716
  setPrefs,
778
717
  play,
718
+ countIn,
779
719
  pause,
780
720
  restart,
781
721
  nudge,
@@ -1527,6 +1467,11 @@ function useNarrationRecorder(options) {
1527
1467
  cameraMime = videoFormat.mimeType;
1528
1468
  cameraExt = videoFormat.extension;
1529
1469
  }
1470
+ if (opts.countIn) {
1471
+ setCameraStream(camera);
1472
+ if (!await opts.countIn()) throw new StartAborted();
1473
+ if (superseded()) throw new StartAborted();
1474
+ }
1530
1475
  const capture = {
1531
1476
  audioRecorder,
1532
1477
  audioChunks: [],
@@ -1604,6 +1549,7 @@ function useNarrationRecorder(options) {
1604
1549
  if (!capture) {
1605
1550
  if (startingRef.current) {
1606
1551
  cancelPendingStart();
1552
+ optionsRef.current.onRecordingStop?.();
1607
1553
  setState("idle");
1608
1554
  }
1609
1555
  return;
@@ -1793,11 +1739,16 @@ function insertNarrationPreamble(source, audioPath, cameraPath) {
1793
1739
 
1794
1740
  // src/teleprompter/recording/narrationSave.ts
1795
1741
  import {
1796
- buildNarrationTimingJson
1742
+ buildNarrationTimingJson,
1743
+ resolveNarrationTimingGenerator
1797
1744
  } from "@bendyline/squisq/narration";
1798
1745
  function buildNarrationSavePlan(args) {
1746
+ const timingOptions = {
1747
+ ...args.baseWpm !== void 0 ? { baseWpm: args.baseWpm } : {},
1748
+ ...args.generator ? { generator: args.generator } : {}
1749
+ };
1799
1750
  const sidecarPayload = args.alignment ? buildNarrationTimingJson(args.script, args.alignment, args.durationSec, {
1800
- baseWpm: args.baseWpm,
1751
+ ...timingOptions,
1801
1752
  ...args.cameraOffsetSec !== void 0 ? { cameraOffsetSec: args.cameraOffsetSec } : {}
1802
1753
  }) : {
1803
1754
  version: 3,
@@ -1805,7 +1756,7 @@ function buildNarrationSavePlan(args) {
1805
1756
  duration: args.durationSec,
1806
1757
  bookmarks: [],
1807
1758
  blocks: [],
1808
- generator: { name: "squisq-teleprompter", method: "dsp-align", baseWpm: args.baseWpm }
1759
+ generator: resolveNarrationTimingGenerator(timingOptions)
1809
1760
  };
1810
1761
  return {
1811
1762
  audioRelativeName: `audio/${buildFilename("audio", args.audioExt, args.audioBasename)}`,
@@ -1898,7 +1849,8 @@ function useNarrationStage(opts) {
1898
1849
  maxRecordingBytes: opts.maxRecordingBytes,
1899
1850
  audioRecorderOptions: opts.audioRecorderOptions,
1900
1851
  cameraRecorderOptions: opts.cameraRecorderOptions,
1901
- onRecordingStart: () => controllerRef.current.play(),
1852
+ // The take starts when the countdown ends, with the prompter already rolling.
1853
+ countIn: () => controllerRef.current.countIn(),
1902
1854
  onRecordingStop: () => controllerRef.current.pause()
1903
1855
  });
1904
1856
  const recorderRef = useRef5(recorder);
@@ -2874,13 +2826,6 @@ function NarrationStage(props) {
2874
2826
  export {
2875
2827
  resolveMaxRecordingBytes,
2876
2828
  formatRecordingBytes,
2877
- resolveFormat,
2878
- supportsMediaRecorder,
2879
- supportsUserMedia,
2880
- supportsDisplayMedia,
2881
- supportsSystemAudioCapture,
2882
- buildFilename,
2883
- requestMicStream,
2884
2829
  requestCameraStream,
2885
2830
  useStreamPreview,
2886
2831
  buildTimingJson,