@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,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) {
@@ -178,7 +101,9 @@ async function registerPcmWorklet(ctx) {
178
101
 
179
102
  // src/teleprompter/useMicAnalysis.ts
180
103
  import { useCallback, useEffect as useEffect2, useRef, useState } from "react";
181
- function useMicAnalysis(constraints) {
104
+ function useMicAnalysis(constraints, externalStream) {
105
+ const externalStreamRef = useRef(externalStream);
106
+ externalStreamRef.current = externalStream;
182
107
  const [status, setStatus] = useState("idle");
183
108
  const [error, setError] = useState(null);
184
109
  const [devices, setDevices] = useState([]);
@@ -220,7 +145,7 @@ function useMicAnalysis(constraints) {
220
145
  graph.sink?.disconnect();
221
146
  } catch {
222
147
  }
223
- for (const track of graph.stream.getTracks()) track.stop();
148
+ if (graph.ownsStream) for (const track of graph.stream.getTracks()) track.stop();
224
149
  void graph.context.close().catch(() => void 0);
225
150
  }, []);
226
151
  const stop = useCallback(() => {
@@ -238,17 +163,26 @@ function useMicAnalysis(constraints) {
238
163
  teardown();
239
164
  setStatus("starting");
240
165
  setError(null);
166
+ const ownsStream = externalStreamRef.current === void 0;
167
+ let acquiredStream = null;
168
+ let acquiredContext = null;
241
169
  try {
170
+ if (!ownsStream && !externalStreamRef.current?.getAudioTracks().length) {
171
+ setStatus("idle");
172
+ return null;
173
+ }
242
174
  const baseConstraints = constraintsRef.current;
243
- const micStream = await requestMicStream({
175
+ const micStream = externalStreamRef.current ?? await requestMicStream({
244
176
  ...baseConstraints ?? {},
245
177
  ...deviceId ? { deviceId: { exact: deviceId } } : {}
246
178
  });
179
+ acquiredStream = micStream;
247
180
  if (generation !== generationRef.current) {
248
- for (const track of micStream.getTracks()) track.stop();
181
+ if (ownsStream) for (const track of micStream.getTracks()) track.stop();
249
182
  return null;
250
183
  }
251
184
  const context = new AudioContext();
185
+ acquiredContext = context;
252
186
  if (context.state === "suspended") {
253
187
  await context.resume().catch(() => void 0);
254
188
  }
@@ -297,17 +231,19 @@ function useMicAnalysis(constraints) {
297
231
  node = processor;
298
232
  }
299
233
  if (generation !== generationRef.current) {
300
- for (const track of micStream.getTracks()) track.stop();
234
+ if (ownsStream) for (const track of micStream.getTracks()) track.stop();
301
235
  void context.close().catch(() => void 0);
302
236
  return null;
303
237
  }
304
- graphRef.current = { stream: micStream, context, source, node, sink };
238
+ graphRef.current = { stream: micStream, ownsStream, context, source, node, sink };
305
239
  setStream(micStream);
306
240
  setSampleRate(context.sampleRate);
307
241
  setStatus("live");
308
242
  void refreshDevices();
309
243
  return micStream;
310
244
  } catch (err) {
245
+ if (ownsStream) for (const track of acquiredStream?.getTracks() ?? []) track.stop();
246
+ if (acquiredContext) void acquiredContext.close().catch(() => void 0);
311
247
  if (generation !== generationRef.current) return null;
312
248
  setError(err instanceof Error ? err : new Error(String(err)));
313
249
  setStatus("error");
@@ -316,6 +252,12 @@ function useMicAnalysis(constraints) {
316
252
  },
317
253
  [refreshDevices, teardown]
318
254
  );
255
+ useEffect2(() => {
256
+ if (externalStream === void 0) return;
257
+ if (externalStream?.getAudioTracks().length) void start(null);
258
+ else stop();
259
+ return stop;
260
+ }, [externalStream, start, stop]);
319
261
  const subscribeHop = useCallback((listener) => {
320
262
  listenersRef.current.add(listener);
321
263
  return () => {
@@ -333,7 +275,17 @@ function useMicAnalysis(constraints) {
333
275
  teardown();
334
276
  };
335
277
  }, [teardown]);
336
- return { status, error, stream, sampleRate, devices, start, stop, subscribeHop };
278
+ return {
279
+ status,
280
+ error,
281
+ stream,
282
+ sampleRate,
283
+ devices,
284
+ sourceManaged: externalStream !== void 0,
285
+ start,
286
+ stop,
287
+ subscribeHop
288
+ };
337
289
  }
338
290
 
339
291
  // src/teleprompter/types.ts
@@ -465,7 +417,7 @@ function useTeleprompter(opts) {
465
417
  const [transport, setTransport] = useState2("stopped");
466
418
  const [countdownRemaining, setCountdownRemaining] = useState2(null);
467
419
  const [view, setView] = useState2({ wordPos: 0, micLevel: 0, voiceActive: false });
468
- const mic = useMicAnalysis(opts.micConstraints);
420
+ const mic = useMicAnalysis(opts.micConstraints, opts.analysisStream);
469
421
  const scriptRef = useRef2(script);
470
422
  const prefsRef = useRef2(prefs);
471
423
  const transportRef = useRef2(transport);
@@ -1764,11 +1716,16 @@ function insertNarrationPreamble(source, audioPath, cameraPath) {
1764
1716
 
1765
1717
  // src/teleprompter/recording/narrationSave.ts
1766
1718
  import {
1767
- buildNarrationTimingJson
1719
+ buildNarrationTimingJson,
1720
+ resolveNarrationTimingGenerator
1768
1721
  } from "@bendyline/squisq/narration";
1769
1722
  function buildNarrationSavePlan(args) {
1723
+ const timingOptions = {
1724
+ ...args.baseWpm !== void 0 ? { baseWpm: args.baseWpm } : {},
1725
+ ...args.generator ? { generator: args.generator } : {}
1726
+ };
1770
1727
  const sidecarPayload = args.alignment ? buildNarrationTimingJson(args.script, args.alignment, args.durationSec, {
1771
- baseWpm: args.baseWpm,
1728
+ ...timingOptions,
1772
1729
  ...args.cameraOffsetSec !== void 0 ? { cameraOffsetSec: args.cameraOffsetSec } : {}
1773
1730
  }) : {
1774
1731
  version: 3,
@@ -1776,7 +1733,7 @@ function buildNarrationSavePlan(args) {
1776
1733
  duration: args.durationSec,
1777
1734
  bookmarks: [],
1778
1735
  blocks: [],
1779
- generator: { name: "squisq-teleprompter", method: "dsp-align", baseWpm: args.baseWpm }
1736
+ generator: resolveNarrationTimingGenerator(timingOptions)
1780
1737
  };
1781
1738
  return {
1782
1739
  audioRelativeName: `audio/${buildFilename("audio", args.audioExt, args.audioBasename)}`,
@@ -1851,7 +1808,11 @@ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMem
1851
1808
  import { wordIndexAtTime } from "@bendyline/squisq/narration";
1852
1809
  function useNarrationStage(opts) {
1853
1810
  const { doc, recording = null, getAudioBasename } = opts;
1854
- const controller = useTeleprompter({ doc, micConstraints: opts.micConstraints });
1811
+ const controller = useTeleprompter({
1812
+ doc,
1813
+ micConstraints: opts.micConstraints,
1814
+ analysisStream: opts.analysisStream
1815
+ });
1855
1816
  const float = useFloatingWindow(TELEPROMPTER_CSS);
1856
1817
  const controllerRef = useRef5(controller);
1857
1818
  controllerRef.current = controller;
@@ -2325,6 +2286,8 @@ function TeleprompterControls({
2325
2286
  {
2326
2287
  "aria-label": "Microphone",
2327
2288
  value: prefs.micDeviceId ?? "",
2289
+ disabled: mic.sourceManaged,
2290
+ title: mic.sourceManaged ? "Microphone selected in recording settings" : void 0,
2328
2291
  onChange: (e) => setPrefs({ micDeviceId: e.target.value || null }),
2329
2292
  children: [
2330
2293
  /* @__PURE__ */ jsx2("option", { value: "", children: "Default mic" }),
@@ -2839,13 +2802,6 @@ function NarrationStage(props) {
2839
2802
  export {
2840
2803
  resolveMaxRecordingBytes,
2841
2804
  formatRecordingBytes,
2842
- resolveFormat,
2843
- supportsMediaRecorder,
2844
- supportsUserMedia,
2845
- supportsDisplayMedia,
2846
- supportsSystemAudioCapture,
2847
- buildFilename,
2848
- requestMicStream,
2849
2805
  requestCameraStream,
2850
2806
  useStreamPreview,
2851
2807
  buildTimingJson,
@@ -0,0 +1,14 @@
1
+ // src/speech/types.ts
2
+ var SPEECH_INPUT_SAMPLE_RATE = 16e3;
3
+ function resolveSpeechInputProvider(capability) {
4
+ return typeof capability === "function" ? capability() : capability;
5
+ }
6
+ function isSpeechInputProviderFactory(capability) {
7
+ return typeof capability === "function";
8
+ }
9
+
10
+ export {
11
+ SPEECH_INPUT_SAMPLE_RATE,
12
+ resolveSpeechInputProvider,
13
+ isSpeechInputProviderFactory
14
+ };
@@ -0,0 +1,93 @@
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
+ }
84
+
85
+ export {
86
+ resolveFormat,
87
+ supportsMediaRecorder,
88
+ supportsUserMedia,
89
+ supportsDisplayMedia,
90
+ supportsSystemAudioCapture,
91
+ buildFilename,
92
+ requestMicStream
93
+ };
@@ -588,7 +588,7 @@ function tiptapToMarkdown(html) {
588
588
  const imgMatch = remaining.match(/^<img\b([^>]*)>/);
589
589
  if (imgMatch) {
590
590
  const attrs = imgMatch[1] ?? "";
591
- const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1];
591
+ const src = /(?:^|\s)(?:data-squisq-image-)?src="([^"]*)"/i.exec(attrs)?.[1];
592
592
  if (src) {
593
593
  const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? "";
594
594
  const serialized = serializeImage(src, alt, attrs);
@@ -903,6 +903,7 @@ function serializeImage(src, alt, attrs) {
903
903
  const width = /\bwidth="([^"]*)"/i.exec(attrs)?.[1];
904
904
  const height = /\bheight="([^"]*)"/i.exec(attrs)?.[1];
905
905
  const title = /\btitle="([^"]*)"/i.exec(attrs)?.[1];
906
+ if (title?.startsWith("squisq-upload:")) return "";
906
907
  if (!width && !height) {
907
908
  return `![${alt}](${escapedDestination(src)})`;
908
909
  }
@@ -1188,7 +1189,7 @@ function htmlToInline(html) {
1188
1189
  return `[${text}](${escapedDestination(href)}${titlePart})`;
1189
1190
  });
1190
1191
  result = result.replace(RE_IMG_TAG, (match, attrs) => {
1191
- const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1];
1192
+ const src = /(?:^|\s)(?:data-squisq-image-)?src="([^"]*)"/i.exec(attrs)?.[1];
1192
1193
  if (!src) return match;
1193
1194
  const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? "";
1194
1195
  const serialized = serializeImage(src, alt, attrs);