@bendyline/squisq-editor-react 2.11.16 → 2.11.18

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-ZDWZRVAY.js → chunk-2XXBDECJ.js} +1 -1
  2. package/dist/{chunk-DNB32SU2.js → chunk-45NQUR62.js} +1105 -130
  3. package/dist/{chunk-34FRFDQ6.js → chunk-CGENKHWY.js} +58 -102
  4. package/dist/chunk-DNEVUOBZ.js +14 -0
  5. package/dist/chunk-DNJ6HOES.js +93 -0
  6. package/dist/{chunk-EN5BYZOE.js → chunk-DPAMJV6A.js} +3 -2
  7. package/dist/{chunk-5TYCTE33.js → chunk-GIXXD2O3.js} +2189 -1834
  8. package/dist/{chunk-ENDRPV6T.js → chunk-H7YTL6SL.js} +1 -1
  9. package/dist/chunk-LRZJD5YD.js +107 -0
  10. package/dist/{chunk-4D7QMRTZ.js → chunk-U6NVT3BY.js} +466 -540
  11. package/dist/{chunk-WDQFWD7Z.js → chunk-UBFNLWCN.js} +2 -2
  12. package/dist/{chunk-27WOOJXR.js → chunk-WKZLBGPX.js} +1 -1
  13. package/dist/dictationSession-PN3OT5LJ.js +521 -0
  14. package/dist/index.d.ts +36 -21
  15. package/dist/index.js +54 -22
  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-C6ArYQBa.d.ts → recorder-CRsw0eC7.d.ts} +6 -5
  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-Cb0P_ln8.d.ts} +170 -5
  24. package/dist/speech/index.d.ts +126 -0
  25. package/dist/speech/index.js +10 -0
  26. package/dist/styles/index.css +162 -29
  27. package/dist/teleprompter/index.d.ts +13 -5
  28. package/dist/teleprompter/index.js +3 -2
  29. package/dist/{useNarrationStage-DopDr_P5.d.ts → useNarrationStage-vKtk6H-V.d.ts} +7 -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-CGENKHWY.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-34FRFDQ6.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";
@@ -1818,8 +1729,9 @@ function isExpandedPanel(mode) {
1818
1729
  return mode !== "none";
1819
1730
  }
1820
1731
  function panelModeAfterToggle(current, target, checked) {
1821
- if (checked) return target;
1822
- return current === target ? "none" : current;
1732
+ const narration = target === "narration" ? checked : current === "narration" || current === "both";
1733
+ const slides = target === "slides" ? checked : current === "slides" || current === "both";
1734
+ return narration ? slides ? "both" : "narration" : slides ? "slides" : "none";
1823
1735
  }
1824
1736
  function clampSlideIndex(index, count) {
1825
1737
  if (count <= 0) return 0;
@@ -2695,8 +2607,9 @@ function RecorderModal({
2695
2607
  const [cameraPlaybackUrl, setCameraPlaybackUrl] = useState3(null);
2696
2608
  const [playbackPositionMs, setPlaybackPositionMs] = useState3(0);
2697
2609
  const [panelMode, setPanelMode] = useState3("none");
2698
- const narrationOn = panelMode === "narration";
2699
- const slidesOn = panelMode === "slides";
2610
+ const narrationOn = panelMode === "narration" || panelMode === "both";
2611
+ const slidesOn = panelMode === "slides" || panelMode === "both";
2612
+ const narrationRecording = narrationOn && Boolean(narration?.recording);
2700
2613
  const expanded = isExpandedPanel(panelMode);
2701
2614
  const [narrationRequesting, setNarrationRequesting] = useState3(false);
2702
2615
  const [narrationPreview, setNarrationPreview] = useState3(null);
@@ -2757,13 +2670,16 @@ function RecorderModal({
2757
2670
  systemAudio: canIncludeSystemAudio ? includeSystemAudio : false
2758
2671
  });
2759
2672
  const filenameSeed = recordingFilenameSeed(source, recorder.stream, micOn || includeSystemAudio);
2760
- const narrationAvailable = Boolean(narration && narration.recording);
2673
+ const narrationAvailable = Boolean(narration?.doc);
2761
2674
  const basenameRef = useRef2(basename);
2762
2675
  basenameRef.current = basename;
2763
2676
  const stage = useNarrationStage({
2764
2677
  doc: narration?.doc ?? null,
2765
2678
  maxRecordingBytes,
2766
2679
  recording: narration?.recording ?? null,
2680
+ // A prompter-only stage borrows the take's audio. It never requests or
2681
+ // stops capture tracks; the recorder remains their sole owner.
2682
+ analysisStream: narration?.recording ? void 0 : narrationOn && recorder.state !== "stopped" ? isDual ? recorder.camera?.stream ?? null : recorder.stream : null,
2767
2683
  getAudioBasename: () => basenameRef.current.trim() || void 0,
2768
2684
  micConstraints: audioConstraints,
2769
2685
  cameraConstraints,
@@ -2818,35 +2734,41 @@ function RecorderModal({
2818
2734
  useEffect4(() => {
2819
2735
  setAdvanceLog(EMPTY_ADVANCE_LOG);
2820
2736
  setSlideIndex(0);
2821
- }, [panelMode, slideDeckKey]);
2737
+ }, [slidesOn, slideDeckKey]);
2738
+ useEffect4(() => {
2739
+ if (!narrationOn || narrationRecording) return;
2740
+ const controller = stageRef.current.controller;
2741
+ if (recorder.state === "recording") controller.play();
2742
+ else controller.pause();
2743
+ }, [narrationOn, narrationRecording, recorder.state]);
2822
2744
  const handleDeviceSettingsChange = useCallback4(
2823
2745
  (next) => {
2824
2746
  setDeviceSettings(next);
2825
- if (narrationOn) {
2747
+ if (narrationRecording) {
2826
2748
  stageRef.current.controller.setPrefs({
2827
2749
  micDeviceId: next.audio.deviceId || null
2828
2750
  });
2829
2751
  }
2830
2752
  },
2831
- [narrationOn]
2753
+ [narrationRecording]
2832
2754
  );
2833
2755
  const narrationMicDeviceId = stage.controller.prefs.micDeviceId ?? "";
2834
2756
  useEffect4(() => {
2835
- if (!narrationOn) return;
2757
+ if (!narrationRecording) return;
2836
2758
  setDeviceSettings(
2837
2759
  (current) => current.audio.deviceId === narrationMicDeviceId ? current : {
2838
2760
  ...current,
2839
2761
  audio: { ...current.audio, deviceId: narrationMicDeviceId }
2840
2762
  }
2841
2763
  );
2842
- }, [narrationMicDeviceId, narrationOn]);
2764
+ }, [narrationMicDeviceId, narrationRecording]);
2843
2765
  const prevNarrationOnRef = useRef2(narrationOn);
2844
2766
  useEffect4(() => {
2845
2767
  const was = prevNarrationOnRef.current;
2846
2768
  prevNarrationOnRef.current = narrationOn;
2847
2769
  if (was === narrationOn) return;
2848
2770
  if (narrationOn) {
2849
- recorder.cancel();
2771
+ if (narrationRecording) recorder.cancel();
2850
2772
  } else {
2851
2773
  const s = stageRef.current;
2852
2774
  s.controller.pause();
@@ -2855,7 +2777,7 @@ function RecorderModal({
2855
2777
  }
2856
2778
  if (s.float.isOpen) s.float.close();
2857
2779
  }
2858
- }, [narrationOn, recorder]);
2780
+ }, [narrationOn, narrationRecording, recorder]);
2859
2781
  useEffect4(() => {
2860
2782
  if (!narrationOn) return;
2861
2783
  const onKey = (event) => {
@@ -2867,7 +2789,7 @@ function RecorderModal({
2867
2789
  return () => document.removeEventListener("keydown", onKey, true);
2868
2790
  }, [narrationOn]);
2869
2791
  const narrationMicLive = stage.controller.mic.status === "live";
2870
- const narrationPreviewWanted = narrationOn && stage.recorder.withCamera && narrationMicLive && stage.recorder.cameraStream === null && stage.recorder.state !== "processing" && stage.recorder.state !== "review" && stage.recorder.state !== "saving";
2792
+ const narrationPreviewWanted = narrationRecording && stage.recorder.withCamera && narrationMicLive && stage.recorder.cameraStream === null && stage.recorder.state !== "processing" && stage.recorder.state !== "review" && stage.recorder.state !== "saving";
2871
2793
  const narrationPreviewRef = useRef2(null);
2872
2794
  useEffect4(() => {
2873
2795
  const existing = narrationPreviewRef.current;
@@ -2916,20 +2838,20 @@ function RecorderModal({
2916
2838
  const handleNarrationRecord = useCallback4(() => {
2917
2839
  void stageRef.current.recorder.start();
2918
2840
  }, []);
2919
- const narrationCameraStream = narrationOn ? stage.recorder.cameraStream ?? narrationPreview : null;
2920
- const activePrimaryStream = narrationOn ? stage.controller.mic.stream : recorder.stream;
2921
- const activeCameraStream = narrationOn ? narrationCameraStream : recorder.camera?.stream ?? null;
2841
+ const narrationCameraStream = narrationRecording ? stage.recorder.cameraStream ?? narrationPreview : null;
2842
+ const activePrimaryStream = narrationRecording ? stage.controller.mic.stream : recorder.stream;
2843
+ const activeCameraStream = narrationRecording ? narrationCameraStream : recorder.camera?.stream ?? null;
2922
2844
  const { devices: mediaDevices, refresh: refreshMediaDevices } = useMediaDevices();
2923
2845
  useEffect4(() => {
2924
2846
  if (activePrimaryStream || activeCameraStream) refreshMediaDevices();
2925
2847
  }, [activeCameraStream, activePrimaryStream, refreshMediaDevices]);
2926
2848
  useStreamPreview(
2927
2849
  previewRef,
2928
- narrationOn ? narrationCameraStream : recorder.state === "stopped" ? null : recorder.stream
2850
+ narrationRecording ? narrationCameraStream : recorder.state === "stopped" ? null : recorder.stream
2929
2851
  );
2930
2852
  useStreamPreview(
2931
2853
  cameraPreviewRef,
2932
- !narrationOn && isDual && recorder.state !== "stopped" ? recorder.camera?.stream ?? null : null
2854
+ !narrationRecording && isDual && recorder.state !== "stopped" ? recorder.camera?.stream ?? null : null
2933
2855
  );
2934
2856
  useEffect4(() => {
2935
2857
  setPlaybackPositionMs(0);
@@ -2967,7 +2889,7 @@ function RecorderModal({
2967
2889
  }
2968
2890
  }, [captureKey, recorder]);
2969
2891
  const handleClose = useCallback4(() => {
2970
- if (!narrationOn) {
2892
+ if (!narrationRecording) {
2971
2893
  if (isSaving || recorder.state === "stopping" || recorder.state === "requesting") return;
2972
2894
  if ((recorder.blob || recorder.camera?.blob || recorder.state === "recording") && !window.confirm(
2973
2895
  "Discard this recording? Download or save it before closing to keep a copy."
@@ -2975,7 +2897,7 @@ function RecorderModal({
2975
2897
  return;
2976
2898
  }
2977
2899
  const s = stageRef.current;
2978
- if (closeNeedsConfirm(narrationOn, s.recorder.state, s.recorder.take !== null)) {
2900
+ if (closeNeedsConfirm(narrationRecording, s.recorder.state, s.recorder.take !== null)) {
2979
2901
  if (!window.confirm("Discard the current narration take?")) return;
2980
2902
  s.handleDiscard();
2981
2903
  }
@@ -2987,7 +2909,7 @@ function RecorderModal({
2987
2909
  }
2988
2910
  recorder.cancel();
2989
2911
  onClose();
2990
- }, [narrationOn, recorder, onClose, isSaving]);
2912
+ }, [narrationOn, narrationRecording, recorder, onClose, isSaving]);
2991
2913
  useModalDialog({
2992
2914
  rootRef: overlayRef,
2993
2915
  dialogRef,
@@ -3197,7 +3119,8 @@ function RecorderModal({
3197
3119
  dualSaveProgressRef.current = {};
3198
3120
  setAdvanceLog(EMPTY_ADVANCE_LOG);
3199
3121
  recorder.reset();
3200
- }, [recorder]);
3122
+ if (narrationOn) stageRef.current.controller.restart();
3123
+ }, [narrationOn, recorder]);
3201
3124
  const handlePlaybackTimeUpdate = useCallback4(
3202
3125
  (media) => {
3203
3126
  const currentMs = Number.isFinite(media.currentTime) ? media.currentTime * 1e3 : 0;
@@ -3234,7 +3157,7 @@ function RecorderModal({
3234
3157
  );
3235
3158
  const timingDownloadFilename = `${downloadFilename}.timing.json`;
3236
3159
  useEffect4(() => {
3237
- if (narrationOn || !canSave) {
3160
+ if (narrationRecording || !canSave) {
3238
3161
  setTimingDownloadUrl(null);
3239
3162
  return;
3240
3163
  }
@@ -3250,7 +3173,7 @@ function RecorderModal({
3250
3173
  setTimingDownloadUrl(url);
3251
3174
  return () => URL.revokeObjectURL(url);
3252
3175
  }, [
3253
- narrationOn,
3176
+ narrationRecording,
3254
3177
  canSave,
3255
3178
  recorder.durationMs,
3256
3179
  slidesOn,
@@ -3293,9 +3216,9 @@ function RecorderModal({
3293
3216
  recorder.blob !== null || recorder.camera?.blob != null,
3294
3217
  stage.recorder.state
3295
3218
  );
3296
- const deviceSettingsLocked = narrationOn ? !narrationRecorderIdle : togglesLocked;
3297
- const microphoneEnabled = narrationOn ? true : micOn;
3298
- const cameraEnabled = narrationOn ? stage.recorder.withCamera : cameraOn;
3219
+ const deviceSettingsLocked = narrationRecording ? !narrationRecorderIdle : togglesLocked;
3220
+ const microphoneEnabled = narrationRecording ? true : micOn;
3221
+ const cameraEnabled = narrationRecording ? stage.recorder.withCamera : cameraOn;
3299
3222
  const narrationToggleFor = (key) => {
3300
3223
  switch (key) {
3301
3224
  case "mic":
@@ -3359,442 +3282,451 @@ function RecorderModal({
3359
3282
  children: [
3360
3283
  /* @__PURE__ */ jsx6("h2", { id: headingId, style: titleStyle, children: title }),
3361
3284
  /* @__PURE__ */ jsxs5("div", { style: expanded ? bodyRowStyle : void 0, children: [
3362
- /* @__PURE__ */ jsxs5("div", { style: expanded ? leftColStyle : void 0, children: [
3363
- /* @__PURE__ */ jsx6("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
3364
- groupIndex > 0 && /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: groupDividerStyle }),
3365
- /* @__PURE__ */ jsx6("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
3366
- if (t.key === "systemAudio" && !canIncludeSystemAudio) return null;
3367
- const props = narrationOn ? narrationToggleFor(t.key) : simpleToggleProps(t.key);
3368
- return /* @__PURE__ */ jsx6(
3369
- "button",
3370
- {
3371
- type: "button",
3372
- "aria-pressed": props.active,
3373
- style: props.active ? toggleActive : toggleBase,
3374
- onClick: props.onClick,
3375
- disabled: props.disabled,
3376
- title: props.title,
3377
- children: t.label
3378
- },
3379
- t.key
3380
- );
3381
- }) })
3382
- ] }, group.label)) }),
3383
- /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: narrationOn ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
3384
- narrationAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3385
- /* @__PURE__ */ jsx6(
3386
- "input",
3387
- {
3388
- type: "checkbox",
3389
- style: { accentColor: "var(--squisq-recorder-accent)" },
3390
- checked: narrationOn,
3391
- onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "narration", e.target.checked)),
3392
- disabled: narrationToggleDisabled,
3393
- title: narrationToggleDisabled ? narrationOn ? "Finish or discard the narration take first" : "Save or discard this recording before switching modes" : void 0
3394
- }
3395
- ),
3396
- "Show narration mode"
3397
- ] }),
3398
- slidesAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3399
- /* @__PURE__ */ jsx6(
3400
- "input",
3401
- {
3402
- type: "checkbox",
3403
- style: { accentColor: "var(--squisq-recorder-accent)" },
3404
- checked: slidesOn,
3405
- onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "slides", e.target.checked)),
3406
- disabled: narrationToggleDisabled,
3407
- title: narrationToggleDisabled ? "Save or discard this recording before switching modes" : void 0
3408
- }
3409
- ),
3410
- "Show slides mode"
3411
- ] }),
3412
- /* @__PURE__ */ jsx6(
3413
- RecorderDeviceQuickPicks,
3414
- {
3415
- devices: mediaDevices,
3416
- microphoneEnabled,
3417
- cameraEnabled,
3418
- audioDeviceId: deviceSettings.audio.deviceId,
3419
- cameraDeviceId: deviceSettings.camera.deviceId,
3420
- onAudioDeviceChange: (deviceId) => handleDeviceSettingsChange({
3421
- ...deviceSettings,
3422
- audio: { ...deviceSettings.audio, deviceId }
3423
- }),
3424
- onCameraDeviceChange: (deviceId) => handleDeviceSettingsChange({
3425
- ...deviceSettings,
3426
- camera: { ...deviceSettings.camera, deviceId }
3427
- }),
3428
- disabled: deviceSettingsLocked
3429
- }
3430
- ),
3431
- !narrationOn && screenOn && /* @__PURE__ */ jsx6(
3432
- RecorderScreenSettings,
3433
- {
3434
- value: deviceSettings.screen,
3435
- onChange: (screen) => handleDeviceSettingsChange({ ...deviceSettings, screen }),
3436
- disabled: deviceSettingsLocked,
3437
- hasPreview: recorder.state === "ready",
3438
- onChoose: () => {
3439
- recorder.cancel();
3440
- void handleRequest();
3441
- }
3442
- }
3443
- ),
3444
- /* @__PURE__ */ jsx6(
3445
- RecorderDeviceSettingsPanel,
3446
- {
3447
- value: deviceSettings,
3448
- onChange: handleDeviceSettingsChange,
3449
- disabled: deviceSettingsLocked,
3450
- microphoneEnabled,
3451
- cameraEnabled,
3452
- screenEnabled: narrationOn ? false : screenOn,
3453
- systemAudioEnabled: !narrationOn && includeSystemAudio,
3454
- separateAudioRecorder: narrationOn,
3455
- primaryStream: activePrimaryStream,
3456
- cameraStream: activeCameraStream
3457
- }
3458
- ),
3459
- !narrationOn && recorder.error && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: recorder.error.message }),
3460
- !narrationOn && saveError && /* @__PURE__ */ jsxs5("div", { role: "alert", style: errorStyle, children: [
3461
- saveError,
3462
- /* @__PURE__ */ jsx6("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
3463
- ] }),
3464
- /* @__PURE__ */ jsxs5("p", { style: summaryStyle, children: [
3465
- "Recording size:",
3466
- " ",
3467
- formatRecordingBytes(
3468
- narrationOn ? stage.recorder.recordedBytes : recorder.recordedBytes
3469
- ),
3470
- ". Automatically stops at",
3471
- " ",
3472
- formatRecordingBytes(
3473
- narrationOn ? stage.recorder.maxRecordingBytes : recorder.maxRecordingBytes
3474
- ),
3475
- " ",
3476
- "total. Final encoding may add more data."
3477
- ] }),
3478
- !narrationOn && recorder.limitReached && /* @__PURE__ */ jsx6("p", { role: "status", style: recordingStatusStyle, children: "Recording stopped at the size limit. Your complete recording is retained; download a copy or save it to the document." }),
3479
- narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
3480
- narrationOn && narrationCameraStream && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6(
3481
- "video",
3482
- {
3483
- ref: previewRef,
3484
- autoPlay: true,
3485
- muted: true,
3486
- playsInline: true,
3487
- style: { width: "100%", height: "100%", objectFit: "contain" }
3488
- }
3489
- ) }),
3490
- narrationOn && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
3491
- narrationOn && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
3492
- narrationOn && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
3493
- /* @__PURE__ */ jsx6(
3494
- "div",
3495
- {
3496
- "aria-hidden": "true",
3497
- style: {
3498
- ...meterLevelStyle,
3499
- width: `${Math.round(Math.min(1, Math.max(0, stage.controller.micLevel)) * 100)}%`
3500
- }
3501
- }
3502
- ),
3503
- /* @__PURE__ */ jsx6("span", { style: { position: "relative" }, children: stage.recorder.state === "recording" ? "\u25CF Recording narration" : narrationMicLive ? stage.controller.voiceActive ? "Voice detected" : "Microphone ready" : "Click Start preview to check your mic." })
3504
- ] }),
3505
- !narrationOn && !showPreview && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: screenOn ? "Choose a screen or window to preview what will be recorded." : "Click Start preview to prepare a recording." }) }),
3506
- !narrationOn && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
3507
- /* @__PURE__ */ jsx6(
3508
- "video",
3509
- {
3510
- ref: previewRef,
3511
- autoPlay: true,
3512
- muted: true,
3513
- playsInline: true,
3514
- style: { width: "100%", height: "100%", objectFit: "contain" }
3515
- }
3516
- ),
3517
- isDual && /* @__PURE__ */ jsx6(
3518
- "video",
3519
- {
3520
- ref: cameraPreviewRef,
3521
- autoPlay: true,
3522
- muted: true,
3523
- playsInline: true,
3524
- "aria-label": "Camera preview",
3525
- style: {
3526
- position: "absolute",
3527
- right: "3%",
3528
- bottom: "6%",
3529
- width: "22%",
3530
- aspectRatio: "16 / 9",
3531
- objectFit: "cover",
3532
- background: "#000",
3533
- border: "1px solid rgba(255,255,255,0.6)"
3534
- }
3535
- }
3536
- )
3537
- ] }),
3538
- !narrationOn && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3539
- "\u25CF Recording ",
3540
- formatDurationMs(recorder.durationMs)
3541
- ] }) : /* @__PURE__ */ jsx6(Fragment3, { children: "Microphone ready" }) }),
3542
- !narrationOn && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
3543
- /* @__PURE__ */ jsx6(
3544
- "video",
3545
- {
3546
- src: playbackUrl,
3547
- controls: true,
3548
- playsInline: true,
3549
- onTimeUpdate: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3550
- onSeeking: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3551
- onEnded: () => setPlaybackPositionMs(recorder.durationMs),
3552
- style: { width: "100%", height: "100%", objectFit: "contain" }
3553
- }
3554
- ),
3555
- /* @__PURE__ */ jsxs5(
3556
- "div",
3557
- {
3558
- role: "timer",
3559
- "aria-label": `Playback time: ${formatDurationMs(playbackPositionMs)} of ${formatDurationMs(recorder.durationMs)}`,
3560
- style: playbackTimeStyle,
3561
- children: [
3562
- formatDurationMs(playbackPositionMs),
3563
- " / ",
3564
- formatDurationMs(recorder.durationMs)
3565
- ]
3566
- }
3567
- )
3568
- ] }),
3569
- !narrationOn && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3570
- /* @__PURE__ */ jsx6("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
3571
- /* @__PURE__ */ jsx6(
3572
- "video",
3573
- {
3574
- src: cameraPlaybackUrl,
3575
- controls: true,
3576
- playsInline: true,
3577
- "aria-label": "Camera recording",
3578
- style: {
3579
- width: "48%",
3580
- display: "block",
3581
- marginLeft: "auto",
3582
- background: "#000"
3583
- }
3584
- }
3585
- )
3586
- ] }),
3587
- !narrationOn && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3588
- /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
3589
- "\u2713 Recorded ",
3590
- formatDurationMs(recorder.durationMs)
3591
- ] }),
3592
- /* @__PURE__ */ jsx6("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
3593
- ] }),
3594
- !narrationOn && canSave && playbackUrl && /* @__PURE__ */ jsxs5(
3595
- "div",
3596
- {
3597
- style: {
3598
- ...buttonRowStyle,
3599
- flexWrap: "wrap",
3600
- justifyContent: "flex-start",
3601
- marginBottom: 12
3602
- },
3603
- children: [
3604
- /* @__PURE__ */ jsx6("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
3605
- isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx6(
3606
- "a",
3285
+ /* @__PURE__ */ jsxs5(
3286
+ "div",
3287
+ {
3288
+ style: expanded ? {
3289
+ ...leftColStyle,
3290
+ ...panelMode === "both" ? { flex: "0 0 min(280px, 24vw)" } : {}
3291
+ } : void 0,
3292
+ children: [
3293
+ /* @__PURE__ */ jsx6("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
3294
+ groupIndex > 0 && /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: groupDividerStyle }),
3295
+ /* @__PURE__ */ jsx6("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
3296
+ if (t.key === "systemAudio" && !canIncludeSystemAudio) return null;
3297
+ const props = narrationRecording ? narrationToggleFor(t.key) : simpleToggleProps(t.key);
3298
+ return /* @__PURE__ */ jsx6(
3299
+ "button",
3300
+ {
3301
+ type: "button",
3302
+ "aria-pressed": props.active,
3303
+ style: props.active ? toggleActive : toggleBase,
3304
+ onClick: props.onClick,
3305
+ disabled: props.disabled,
3306
+ title: props.title,
3307
+ children: t.label
3308
+ },
3309
+ t.key
3310
+ );
3311
+ }) })
3312
+ ] }, group.label)) }),
3313
+ /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: narrationRecording ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
3314
+ narrationAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3315
+ /* @__PURE__ */ jsx6(
3316
+ "input",
3607
3317
  {
3608
- href: cameraPlaybackUrl,
3609
- download: buildFilename(
3610
- "video",
3611
- recorder.camera?.extension ?? ".webm",
3612
- basename.trim() ? `${basename.trim()}-camera` : void 0,
3613
- micOn ? "camera-audio" : "camera"
3614
- ),
3615
- style: btnSecondary,
3616
- children: "Download camera recording"
3318
+ type: "checkbox",
3319
+ style: { accentColor: "var(--squisq-recorder-accent)" },
3320
+ checked: narrationOn,
3321
+ onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "narration", e.target.checked)),
3322
+ disabled: narrationToggleDisabled,
3323
+ title: narrationToggleDisabled ? narrationRecording ? "Finish or discard the narration take first" : "Save or discard this recording before switching modes" : void 0
3617
3324
  }
3618
3325
  ),
3619
- timingDownloadUrl && /* @__PURE__ */ jsx6(
3620
- "a",
3326
+ "Show narration mode"
3327
+ ] }),
3328
+ slidesAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3329
+ /* @__PURE__ */ jsx6(
3330
+ "input",
3621
3331
  {
3622
- href: timingDownloadUrl,
3623
- download: timingDownloadFilename,
3624
- style: btnSecondary,
3625
- children: "Download recording timings"
3332
+ type: "checkbox",
3333
+ style: { accentColor: "var(--squisq-recorder-accent)" },
3334
+ checked: slidesOn,
3335
+ onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "slides", e.target.checked)),
3336
+ disabled: narrationToggleDisabled,
3337
+ title: narrationToggleDisabled ? "Save or discard this recording before switching modes" : void 0
3626
3338
  }
3627
- )
3628
- ]
3629
- }
3630
- ),
3631
- !narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3632
- /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
3633
- /* @__PURE__ */ jsx6(
3634
- "textarea",
3635
- {
3636
- id: "recorder-source-text",
3637
- style: textareaStyle,
3638
- placeholder: "Type the text you're going to read aloud.",
3639
- value: sourceText,
3640
- onChange: (e) => setSourceText(e.target.value),
3641
- disabled: recorder.state === "recording"
3642
- }
3643
- )
3644
- ] }),
3645
- /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
3646
- /* @__PURE__ */ jsx6(
3647
- "input",
3648
- {
3649
- id: "recorder-basename",
3650
- type: "text",
3651
- style: inputStyle2,
3652
- placeholder: narrationOn ? "narration" : isDual ? "screen + camera" : filenameSeed,
3653
- value: basename,
3654
- onChange: (e) => setBasename(e.target.value),
3655
- disabled: narrationOn ? !narrationRecorderIdle : recorder.state === "recording"
3656
- }
3657
- ),
3658
- !narrationOn && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: recordingStatusStyle, children: [
3659
- "\u25CF Recording ",
3660
- formatDurationMs(recorder.durationMs)
3661
- ] }),
3662
- showTimingCheckbox && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3663
- /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3339
+ ),
3340
+ "Show slides mode"
3341
+ ] }),
3664
3342
  /* @__PURE__ */ jsx6(
3665
- "input",
3343
+ RecorderDeviceQuickPicks,
3666
3344
  {
3667
- type: "checkbox",
3668
- style: { accentColor: "var(--squisq-recorder-accent)" },
3669
- checked: applySlideTimings,
3670
- onChange: (e) => setApplySlideTimings(e.target.checked),
3671
- disabled: isSaving
3345
+ devices: mediaDevices,
3346
+ microphoneEnabled,
3347
+ cameraEnabled,
3348
+ audioDeviceId: deviceSettings.audio.deviceId,
3349
+ cameraDeviceId: deviceSettings.camera.deviceId,
3350
+ onAudioDeviceChange: (deviceId) => handleDeviceSettingsChange({
3351
+ ...deviceSettings,
3352
+ audio: { ...deviceSettings.audio, deviceId }
3353
+ }),
3354
+ onCameraDeviceChange: (deviceId) => handleDeviceSettingsChange({
3355
+ ...deviceSettings,
3356
+ camera: { ...deviceSettings.camera, deviceId }
3357
+ }),
3358
+ disabled: deviceSettingsLocked
3672
3359
  }
3673
3360
  ),
3674
- SLIDE_TIMING_CHECKBOX_LABEL
3675
- ] }),
3676
- applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: slideCoverageWarning })
3677
- ] }),
3678
- /* @__PURE__ */ jsxs5("div", { style: buttonRowStyle, children: [
3679
- /* @__PURE__ */ jsx6(
3680
- "button",
3681
- {
3682
- type: "button",
3683
- style: btnSecondary,
3684
- onClick: handleClose,
3685
- disabled: !narrationOn && isBusy,
3686
- children: "Close"
3687
- }
3688
- ),
3689
- narrationOn && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3690
- narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx6(
3691
- "button",
3361
+ !narrationRecording && screenOn && /* @__PURE__ */ jsx6(
3362
+ RecorderScreenSettings,
3363
+ {
3364
+ value: deviceSettings.screen,
3365
+ onChange: (screen) => handleDeviceSettingsChange({ ...deviceSettings, screen }),
3366
+ disabled: deviceSettingsLocked,
3367
+ hasPreview: recorder.state === "ready",
3368
+ onChoose: () => {
3369
+ recorder.cancel();
3370
+ void handleRequest();
3371
+ }
3372
+ }
3373
+ ),
3374
+ /* @__PURE__ */ jsx6(
3375
+ RecorderDeviceSettingsPanel,
3692
3376
  {
3693
- type: "button",
3694
- style: btnPrimary,
3695
- onClick: () => void handleNarrationPreview(),
3696
- disabled: narrationRequesting,
3697
- children: narrationRequesting ? "Requesting\u2026" : "Start preview"
3377
+ value: deviceSettings,
3378
+ onChange: handleDeviceSettingsChange,
3379
+ disabled: deviceSettingsLocked,
3380
+ microphoneEnabled,
3381
+ cameraEnabled,
3382
+ screenEnabled: narrationRecording ? false : screenOn,
3383
+ systemAudioEnabled: !narrationRecording && includeSystemAudio,
3384
+ separateAudioRecorder: narrationRecording,
3385
+ primaryStream: activePrimaryStream,
3386
+ cameraStream: activeCameraStream
3698
3387
  }
3699
3388
  ),
3700
- narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
3389
+ !narrationRecording && recorder.error && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: recorder.error.message }),
3390
+ !narrationRecording && saveError && /* @__PURE__ */ jsxs5("div", { role: "alert", style: errorStyle, children: [
3391
+ saveError,
3392
+ /* @__PURE__ */ jsx6("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
3393
+ ] }),
3394
+ /* @__PURE__ */ jsxs5("p", { style: summaryStyle, children: [
3395
+ "Recording size:",
3396
+ " ",
3397
+ formatRecordingBytes(
3398
+ narrationRecording ? stage.recorder.recordedBytes : recorder.recordedBytes
3399
+ ),
3400
+ ". Automatically stops at",
3401
+ " ",
3402
+ formatRecordingBytes(
3403
+ narrationRecording ? stage.recorder.maxRecordingBytes : recorder.maxRecordingBytes
3404
+ ),
3405
+ " ",
3406
+ "total. Final encoding may add more data."
3407
+ ] }),
3408
+ !narrationRecording && recorder.limitReached && /* @__PURE__ */ jsx6("p", { role: "status", style: recordingStatusStyle, children: "Recording stopped at the size limit. Your complete recording is retained; download a copy or save it to the document." }),
3409
+ narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
3410
+ narrationRecording && narrationCameraStream && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6(
3411
+ "video",
3412
+ {
3413
+ ref: previewRef,
3414
+ autoPlay: true,
3415
+ muted: true,
3416
+ playsInline: true,
3417
+ style: { width: "100%", height: "100%", objectFit: "contain" }
3418
+ }
3419
+ ) }),
3420
+ narrationRecording && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
3421
+ narrationRecording && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
3422
+ narrationRecording && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
3701
3423
  /* @__PURE__ */ jsx6(
3702
- "span",
3424
+ "div",
3703
3425
  {
3704
- className: "squisq-recorder-record-dot",
3705
- style: recordDotFrameStyle,
3706
3426
  "aria-hidden": "true",
3707
- children: /* @__PURE__ */ jsx6(
3708
- "span",
3709
- {
3710
- className: "squisq-recorder-record-dot-center",
3711
- style: recordDotStyle
3712
- }
3713
- )
3427
+ style: {
3428
+ ...meterLevelStyle,
3429
+ width: `${Math.round(Math.min(1, Math.max(0, stage.controller.micLevel)) * 100)}%`
3430
+ }
3714
3431
  }
3715
3432
  ),
3716
- "Record"
3433
+ /* @__PURE__ */ jsx6("span", { style: { position: "relative" }, children: stage.recorder.state === "recording" ? "\u25CF Recording narration" : narrationMicLive ? stage.controller.voiceActive ? "Voice detected" : "Microphone ready" : "Click Start preview to check your mic." })
3717
3434
  ] }),
3718
- (stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx6(
3719
- "button",
3720
- {
3721
- type: "button",
3722
- style: btnDanger,
3723
- onClick: () => void stage.recorder.stop(),
3724
- children: "Stop"
3725
- }
3726
- ),
3727
- stage.recorder.state === "processing" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
3728
- stage.recorder.state === "saving" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
3729
- stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3730
- /* @__PURE__ */ jsx6("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
3435
+ !narrationRecording && !showPreview && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: screenOn ? "Choose a screen or window to preview what will be recorded." : "Click Start preview to prepare a recording." }) }),
3436
+ !narrationRecording && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
3731
3437
  /* @__PURE__ */ jsx6(
3732
- "button",
3438
+ "video",
3733
3439
  {
3734
- type: "button",
3735
- style: btnPrimary,
3736
- onClick: () => void stage.handleSave(),
3737
- children: "Save to document"
3440
+ ref: previewRef,
3441
+ autoPlay: true,
3442
+ muted: true,
3443
+ playsInline: true,
3444
+ style: { width: "100%", height: "100%", objectFit: "contain" }
3445
+ }
3446
+ ),
3447
+ isDual && /* @__PURE__ */ jsx6(
3448
+ "video",
3449
+ {
3450
+ ref: cameraPreviewRef,
3451
+ autoPlay: true,
3452
+ muted: true,
3453
+ playsInline: true,
3454
+ "aria-label": "Camera preview",
3455
+ style: {
3456
+ position: "absolute",
3457
+ right: "3%",
3458
+ bottom: "6%",
3459
+ width: "22%",
3460
+ aspectRatio: "16 / 9",
3461
+ objectFit: "cover",
3462
+ background: "#000",
3463
+ border: "1px solid rgba(255,255,255,0.6)"
3464
+ }
3738
3465
  }
3739
3466
  )
3740
- ] })
3741
- ] }),
3742
- !narrationOn && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3743
- (recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx6(
3744
- "button",
3745
- {
3746
- type: "button",
3747
- style: btnPrimary,
3748
- onClick: handleRequest,
3749
- disabled: isBusy || !canCapture,
3750
- children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3751
- }
3752
- ),
3753
- canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3467
+ ] }),
3468
+ !narrationRecording && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3469
+ "\u25CF Recording ",
3470
+ formatDurationMs(recorder.durationMs)
3471
+ ] }) : /* @__PURE__ */ jsx6(Fragment3, { children: "Microphone ready" }) }),
3472
+ !narrationRecording && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
3754
3473
  /* @__PURE__ */ jsx6(
3755
- "span",
3474
+ "video",
3756
3475
  {
3757
- className: "squisq-recorder-record-dot",
3758
- style: recordDotFrameStyle,
3759
- "aria-hidden": "true",
3760
- children: /* @__PURE__ */ jsx6(
3761
- "span",
3476
+ src: playbackUrl,
3477
+ controls: true,
3478
+ playsInline: true,
3479
+ onTimeUpdate: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3480
+ onSeeking: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3481
+ onEnded: () => setPlaybackPositionMs(recorder.durationMs),
3482
+ style: { width: "100%", height: "100%", objectFit: "contain" }
3483
+ }
3484
+ ),
3485
+ /* @__PURE__ */ jsxs5(
3486
+ "div",
3487
+ {
3488
+ role: "timer",
3489
+ "aria-label": `Playback time: ${formatDurationMs(playbackPositionMs)} of ${formatDurationMs(recorder.durationMs)}`,
3490
+ style: playbackTimeStyle,
3491
+ children: [
3492
+ formatDurationMs(playbackPositionMs),
3493
+ " / ",
3494
+ formatDurationMs(recorder.durationMs)
3495
+ ]
3496
+ }
3497
+ )
3498
+ ] }),
3499
+ !narrationRecording && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3500
+ /* @__PURE__ */ jsx6("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
3501
+ /* @__PURE__ */ jsx6(
3502
+ "video",
3503
+ {
3504
+ src: cameraPlaybackUrl,
3505
+ controls: true,
3506
+ playsInline: true,
3507
+ "aria-label": "Camera recording",
3508
+ style: {
3509
+ width: "48%",
3510
+ display: "block",
3511
+ marginLeft: "auto",
3512
+ background: "#000"
3513
+ }
3514
+ }
3515
+ )
3516
+ ] }),
3517
+ !narrationRecording && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3518
+ /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
3519
+ "\u2713 Recorded ",
3520
+ formatDurationMs(recorder.durationMs)
3521
+ ] }),
3522
+ /* @__PURE__ */ jsx6("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
3523
+ ] }),
3524
+ !narrationRecording && canSave && playbackUrl && /* @__PURE__ */ jsxs5(
3525
+ "div",
3526
+ {
3527
+ style: {
3528
+ ...buttonRowStyle,
3529
+ flexWrap: "wrap",
3530
+ justifyContent: "flex-start",
3531
+ marginBottom: 12
3532
+ },
3533
+ children: [
3534
+ /* @__PURE__ */ jsx6("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
3535
+ isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx6(
3536
+ "a",
3762
3537
  {
3763
- className: "squisq-recorder-record-dot-center",
3764
- style: recordDotStyle
3538
+ href: cameraPlaybackUrl,
3539
+ download: buildFilename(
3540
+ "video",
3541
+ recorder.camera?.extension ?? ".webm",
3542
+ basename.trim() ? `${basename.trim()}-camera` : void 0,
3543
+ micOn ? "camera-audio" : "camera"
3544
+ ),
3545
+ style: btnSecondary,
3546
+ children: "Download camera recording"
3547
+ }
3548
+ ),
3549
+ timingDownloadUrl && /* @__PURE__ */ jsx6(
3550
+ "a",
3551
+ {
3552
+ href: timingDownloadUrl,
3553
+ download: timingDownloadFilename,
3554
+ style: btnSecondary,
3555
+ children: "Download recording timings"
3765
3556
  }
3766
3557
  )
3558
+ ]
3559
+ }
3560
+ ),
3561
+ !narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3562
+ /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
3563
+ /* @__PURE__ */ jsx6(
3564
+ "textarea",
3565
+ {
3566
+ id: "recorder-source-text",
3567
+ style: textareaStyle,
3568
+ placeholder: "Type the text you're going to read aloud.",
3569
+ value: sourceText,
3570
+ onChange: (e) => setSourceText(e.target.value),
3571
+ disabled: recorder.state === "recording"
3767
3572
  }
3768
- ),
3769
- "Record"
3573
+ )
3574
+ ] }),
3575
+ /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
3576
+ /* @__PURE__ */ jsx6(
3577
+ "input",
3578
+ {
3579
+ id: "recorder-basename",
3580
+ type: "text",
3581
+ style: inputStyle2,
3582
+ placeholder: narrationRecording ? "narration" : isDual ? "screen + camera" : filenameSeed,
3583
+ value: basename,
3584
+ onChange: (e) => setBasename(e.target.value),
3585
+ disabled: narrationRecording ? !narrationRecorderIdle : recorder.state === "recording"
3586
+ }
3587
+ ),
3588
+ !narrationRecording && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: recordingStatusStyle, children: [
3589
+ "\u25CF Recording ",
3590
+ formatDurationMs(recorder.durationMs)
3591
+ ] }),
3592
+ showTimingCheckbox && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3593
+ /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3594
+ /* @__PURE__ */ jsx6(
3595
+ "input",
3596
+ {
3597
+ type: "checkbox",
3598
+ style: { accentColor: "var(--squisq-recorder-accent)" },
3599
+ checked: applySlideTimings,
3600
+ onChange: (e) => setApplySlideTimings(e.target.checked),
3601
+ disabled: isSaving
3602
+ }
3603
+ ),
3604
+ SLIDE_TIMING_CHECKBOX_LABEL
3605
+ ] }),
3606
+ applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: slideCoverageWarning })
3770
3607
  ] }),
3771
- canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3772
- canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3608
+ /* @__PURE__ */ jsxs5("div", { style: buttonRowStyle, children: [
3773
3609
  /* @__PURE__ */ jsx6(
3774
3610
  "button",
3775
3611
  {
3776
3612
  type: "button",
3777
3613
  style: btnSecondary,
3778
- onClick: handleDiscard,
3779
- disabled: isBusy,
3780
- children: "Discard & re-record"
3614
+ onClick: handleClose,
3615
+ disabled: !narrationRecording && isBusy,
3616
+ children: "Close"
3781
3617
  }
3782
3618
  ),
3783
- /* @__PURE__ */ jsx6(
3784
- "button",
3785
- {
3786
- type: "button",
3787
- style: btnPrimary,
3788
- onClick: handleSave,
3789
- disabled: isBusy,
3790
- children: isSaving ? "Saving\u2026" : "Save to document"
3791
- }
3792
- )
3619
+ narrationRecording && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3620
+ narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx6(
3621
+ "button",
3622
+ {
3623
+ type: "button",
3624
+ style: btnPrimary,
3625
+ onClick: () => void handleNarrationPreview(),
3626
+ disabled: narrationRequesting,
3627
+ children: narrationRequesting ? "Requesting\u2026" : "Start preview"
3628
+ }
3629
+ ),
3630
+ narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
3631
+ /* @__PURE__ */ jsx6(
3632
+ "span",
3633
+ {
3634
+ className: "squisq-recorder-record-dot",
3635
+ style: recordDotFrameStyle,
3636
+ "aria-hidden": "true",
3637
+ children: /* @__PURE__ */ jsx6(
3638
+ "span",
3639
+ {
3640
+ className: "squisq-recorder-record-dot-center",
3641
+ style: recordDotStyle
3642
+ }
3643
+ )
3644
+ }
3645
+ ),
3646
+ "Record"
3647
+ ] }),
3648
+ (stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx6(
3649
+ "button",
3650
+ {
3651
+ type: "button",
3652
+ style: btnDanger,
3653
+ onClick: () => void stage.recorder.stop(),
3654
+ children: "Stop"
3655
+ }
3656
+ ),
3657
+ stage.recorder.state === "processing" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
3658
+ stage.recorder.state === "saving" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
3659
+ stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3660
+ /* @__PURE__ */ jsx6("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
3661
+ /* @__PURE__ */ jsx6(
3662
+ "button",
3663
+ {
3664
+ type: "button",
3665
+ style: btnPrimary,
3666
+ onClick: () => void stage.handleSave(),
3667
+ children: "Save to document"
3668
+ }
3669
+ )
3670
+ ] })
3671
+ ] }),
3672
+ !narrationRecording && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3673
+ (recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx6(
3674
+ "button",
3675
+ {
3676
+ type: "button",
3677
+ style: btnPrimary,
3678
+ onClick: handleRequest,
3679
+ disabled: isBusy || !canCapture,
3680
+ children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3681
+ }
3682
+ ),
3683
+ canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3684
+ /* @__PURE__ */ jsx6(
3685
+ "span",
3686
+ {
3687
+ className: "squisq-recorder-record-dot",
3688
+ style: recordDotFrameStyle,
3689
+ "aria-hidden": "true",
3690
+ children: /* @__PURE__ */ jsx6(
3691
+ "span",
3692
+ {
3693
+ className: "squisq-recorder-record-dot-center",
3694
+ style: recordDotStyle
3695
+ }
3696
+ )
3697
+ }
3698
+ ),
3699
+ "Record"
3700
+ ] }),
3701
+ canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3702
+ canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3703
+ /* @__PURE__ */ jsx6(
3704
+ "button",
3705
+ {
3706
+ type: "button",
3707
+ style: btnSecondary,
3708
+ onClick: handleDiscard,
3709
+ disabled: isBusy,
3710
+ children: "Discard & re-record"
3711
+ }
3712
+ ),
3713
+ /* @__PURE__ */ jsx6(
3714
+ "button",
3715
+ {
3716
+ type: "button",
3717
+ style: btnPrimary,
3718
+ onClick: handleSave,
3719
+ disabled: isBusy,
3720
+ children: isSaving ? "Saving\u2026" : "Save to document"
3721
+ }
3722
+ )
3723
+ ] })
3724
+ ] })
3793
3725
  ] })
3794
- ] })
3795
- ] })
3796
- ] }),
3797
- narrationOn && narration && /* @__PURE__ */ jsx6("div", { style: rightColStyle, children: /* @__PURE__ */ jsx6(
3726
+ ]
3727
+ }
3728
+ ),
3729
+ narrationOn && narration && /* @__PURE__ */ jsx6("div", { style: { ...rightColStyle, flex: "1 1 0" }, children: /* @__PURE__ */ jsx6(
3798
3730
  NarrationStage,
3799
3731
  {
3800
3732
  stage,
@@ -3806,7 +3738,7 @@ function RecorderModal({
3806
3738
  showReviewActions: false
3807
3739
  }
3808
3740
  ) }),
3809
- slidesOn && /* @__PURE__ */ jsx6("div", { style: rightColStyle, children: /* @__PURE__ */ jsx6(
3741
+ slidesOn && /* @__PURE__ */ jsx6("div", { style: { ...rightColStyle, flex: "1 1 0" }, children: /* @__PURE__ */ jsx6(
3810
3742
  RecorderSlidesPanel,
3811
3743
  {
3812
3744
  slides: slideDeck,
@@ -3815,7 +3747,7 @@ function RecorderModal({
3815
3747
  viewport: slidesViewport,
3816
3748
  basePath: slides?.basePath,
3817
3749
  mediaProvider: slides?.mediaProvider ?? mediaProvider,
3818
- recording: recorder.state === "recording",
3750
+ recording: narrationRecording ? stage.recorder.state === "recording" : recorder.state === "recording",
3819
3751
  shownBlockIds
3820
3752
  }
3821
3753
  ) })
@@ -3901,12 +3833,6 @@ export {
3901
3833
  useMediaDevices,
3902
3834
  RecorderDeviceSettingsPanel,
3903
3835
  RecorderDeviceQuickPicks,
3904
- DEFAULT_RECORDER_DEVICE_SETTINGS,
3905
- buildRecorderAudioConstraints,
3906
- buildRecorderCameraConstraints,
3907
- buildRecorderScreenConstraints,
3908
- buildRecorderScreenAudioConstraints,
3909
- recorderBitsPerSecond,
3910
3836
  recordedMediaKind,
3911
3837
  isExpandedPanel,
3912
3838
  panelModeAfterToggle,