@bendyline/squisq-editor-react 2.4.5 → 2.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/{chunk-PEI5EBEN.js → chunk-4F7SN6WU.js} +1 -1
- package/dist/{chunk-7OKWPWEQ.js → chunk-55DQFHK5.js} +184 -48
- package/dist/{chunk-V6Z7GG55.js → chunk-IQFPX2CE.js} +1 -1
- package/dist/{chunk-WLJ623UZ.js → chunk-K2WJYVS4.js} +7 -6
- package/dist/{chunk-PDCKJCOS.js → chunk-OWOKKZAU.js} +43 -11
- package/dist/{chunk-WDX6UDL5.js → chunk-YPRX32GY.js} +1 -1
- package/dist/{chunk-PKGBWNUQ.js → chunk-ZSQN6IO7.js} +1061 -141
- package/dist/index.d.ts +7 -9
- package/dist/index.js +21 -7
- package/dist/json-editor/index.js +2 -2
- package/dist/recorder/index.d.ts +2 -2
- package/dist/recorder/index.js +17 -3
- package/dist/{recorder-C0Tkyu5W.d.ts → recorder-CnOKlYY3.d.ts} +96 -202
- package/dist/shell/index.js +5 -5
- package/dist/teleprompter/index.d.ts +2 -2
- package/dist/teleprompter/index.js +2 -2
- package/dist/useNarrationStage-Bk3mdW75.d.ts +560 -0
- package/package.json +4 -4
- package/dist/useNarrationStage-Bqo18PBw.d.ts +0 -313
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { SyntheticEvent } from 'react';
|
|
2
|
+
import { Doc, MediaProvider } from '@bendyline/squisq/schemas';
|
|
3
|
+
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
4
|
+
import { NarrationScript, VadConfig, NarrationTrace, NarrationAlignment } from '@bendyline/squisq/narration';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format probing for MediaRecorder.
|
|
8
|
+
*
|
|
9
|
+
* Different browsers expose different container/codec combinations. Chrome
|
|
10
|
+
* and Firefox produce WebM (VP8/VP9 + Opus); Safari produces MP4 (H.264 +
|
|
11
|
+
* AAC). We probe at runtime via `MediaRecorder.isTypeSupported()` and pick
|
|
12
|
+
* the best supported option, falling back to whatever the browser hands
|
|
13
|
+
* back when no probe succeeds.
|
|
14
|
+
*/
|
|
15
|
+
/** What the recorded stream is intended to capture. */
|
|
16
|
+
type CaptureKind = 'audio' | 'video';
|
|
17
|
+
/** A probed format choice — what to pass to `MediaRecorder` and where to write it. */
|
|
18
|
+
interface ResolvedFormat {
|
|
19
|
+
/** MIME type to pass to `new MediaRecorder(stream, { mimeType })`. Empty string means "let the browser pick". */
|
|
20
|
+
mimeType: string;
|
|
21
|
+
/** File extension to use when writing to the container, including the leading dot. */
|
|
22
|
+
extension: string;
|
|
23
|
+
/** Container directory inside the `ContentContainer` (no trailing slash). */
|
|
24
|
+
directory: 'audio' | 'video';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the format the recorder will use for a given capture kind. If a
|
|
28
|
+
* `preferred` MIME type is supported, it wins; otherwise we fall through
|
|
29
|
+
* the priority list. When nothing matches (extremely old browser), we
|
|
30
|
+
* return an empty `mimeType` — `MediaRecorder` will pick a default and we
|
|
31
|
+
* tag the file with `.webm` as a best guess.
|
|
32
|
+
*/
|
|
33
|
+
declare function resolveFormat(kind: CaptureKind, preferred?: string): ResolvedFormat;
|
|
34
|
+
/**
|
|
35
|
+
* `MediaRecorder` support probe. Returns false when running in a
|
|
36
|
+
* non-browser environment (e.g. SSR) or on a browser that doesn't
|
|
37
|
+
* implement the API at all.
|
|
38
|
+
*/
|
|
39
|
+
declare function supportsMediaRecorder(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* `getUserMedia` support probe (for mic / camera capture).
|
|
42
|
+
*/
|
|
43
|
+
declare function supportsUserMedia(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* `getDisplayMedia` support probe (for screen capture). Browsers may
|
|
46
|
+
* implement `mediaDevices` without `getDisplayMedia` (Firefox on Android
|
|
47
|
+
* being the long-standing example), so this is its own probe.
|
|
48
|
+
*/
|
|
49
|
+
declare function supportsDisplayMedia(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Build a default filename for a recording. `basename` is a hint
|
|
52
|
+
* (e.g. user-typed name); when omitted, a sortable timestamp is used so
|
|
53
|
+
* concurrent recordings don't collide. General recorder callers can supply a
|
|
54
|
+
* source-aware `seed`; dedicated narration callers omit it and retain the
|
|
55
|
+
* historical `narration-*` default.
|
|
56
|
+
*/
|
|
57
|
+
type RecordingFilenameSeed = 'audio' | 'camera' | 'camera-audio' | 'screen' | 'screen-audio';
|
|
58
|
+
declare function buildFilename(kind: CaptureKind, extension: string, basename?: string, seed?: RecordingFilenameSeed): string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* useMediaRecorder
|
|
62
|
+
*
|
|
63
|
+
* React wrapper around `MediaRecorder` that handles stream acquisition,
|
|
64
|
+
* the recorder lifecycle, and produces a single `Blob` on stop. Selects
|
|
65
|
+
* a browser-supported MIME type via {@link resolveFormat}.
|
|
66
|
+
*
|
|
67
|
+
* Mirrors the shape of `useVideoExport` in `@bendyline/squisq-video-react`
|
|
68
|
+
* (request → start → stop → blob), inverted for capture rather than
|
|
69
|
+
* export.
|
|
70
|
+
*
|
|
71
|
+
* The `'screen+camera'` source is the one exception to "one stream, one
|
|
72
|
+
* blob": it drives TWO `MediaRecorder`s in lockstep (screen + system audio
|
|
73
|
+
* on the primary lane, camera + mic on a secondary lane), because a single
|
|
74
|
+
* recorder can only hold one video track. The secondary lane surfaces as
|
|
75
|
+
* {@link UseMediaRecorderResult.camera}; every other source leaves it null.
|
|
76
|
+
* The two lanes' start skew is measured (`cameraOffsetSec`) so the composed
|
|
77
|
+
* playback can line the presenter bubble up with the screen.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Which capture source to use. `screen+mic` mixes the microphone into the
|
|
82
|
+
* screen stream (one file); `screen+camera` records screen and camera as two
|
|
83
|
+
* separate files in lockstep.
|
|
84
|
+
*/
|
|
85
|
+
type RecorderSource = 'mic' | 'camera' | 'screen' | 'screen+mic' | 'screen+camera';
|
|
86
|
+
type RecorderAudioBitrateMode = 'constant' | 'variable';
|
|
87
|
+
/**
|
|
88
|
+
* MediaRecorder options standardized after the DOM library fields currently
|
|
89
|
+
* shipped by TypeScript. They are passed through only when explicitly set.
|
|
90
|
+
*/
|
|
91
|
+
interface RecorderExtendedMediaOptions {
|
|
92
|
+
audioBitrateMode?: RecorderAudioBitrateMode;
|
|
93
|
+
videoKeyFrameIntervalDuration?: number;
|
|
94
|
+
videoKeyFrameIntervalCount?: number;
|
|
95
|
+
}
|
|
96
|
+
/** Discriminated state describing what the recorder is currently doing. */
|
|
97
|
+
type RecorderState = 'idle' | 'requesting' | 'ready' | 'recording' | 'stopping' | 'stopped' | 'error';
|
|
98
|
+
/**
|
|
99
|
+
* The camera companion lane, present only for `source === 'screen+camera'`
|
|
100
|
+
* (null for every other source). Its `blob` is null until the take stops.
|
|
101
|
+
*/
|
|
102
|
+
interface RecorderCameraLane {
|
|
103
|
+
/** Camera stream (video + mic when requested); inactive after stop, null after teardown. */
|
|
104
|
+
stream: MediaStream | null;
|
|
105
|
+
/** Final camera `Blob` after `stop()` resolves, or null while recording. */
|
|
106
|
+
blob: Blob | null;
|
|
107
|
+
/** MIME type of the camera lane (shares the video format with the screen lane). */
|
|
108
|
+
mimeType: string | null;
|
|
109
|
+
/** File extension matching `mimeType` (e.g. `.webm`). */
|
|
110
|
+
extension: string | null;
|
|
111
|
+
}
|
|
112
|
+
interface UseMediaRecorderOptions {
|
|
113
|
+
/** Which capture pipeline to use (default: `'mic'`). */
|
|
114
|
+
source?: RecorderSource;
|
|
115
|
+
/**
|
|
116
|
+
* Preferred MIME type override. When the browser supports it, this
|
|
117
|
+
* wins over the default candidate list. When unset (or unsupported),
|
|
118
|
+
* the hook probes a built-in priority list.
|
|
119
|
+
*/
|
|
120
|
+
mimeType?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Video track constraints for camera sources. Also used for screen capture
|
|
123
|
+
* when `screenVideoConstraints` is omitted, preserving the original API.
|
|
124
|
+
*/
|
|
125
|
+
videoConstraints?: MediaTrackConstraints | boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Display-video constraints for screen sources. Keeping this independent
|
|
128
|
+
* lets a dual screen+camera take request different dimensions/aspect ratios
|
|
129
|
+
* for each lane.
|
|
130
|
+
*/
|
|
131
|
+
screenVideoConstraints?: MediaTrackConstraints | boolean;
|
|
132
|
+
/** Audio-track constraints specific to getDisplayMedia system audio. */
|
|
133
|
+
screenAudioConstraints?: MediaTrackConstraints;
|
|
134
|
+
/** Audio track constraints for mic / camera / screen+mic sources. */
|
|
135
|
+
audioConstraints?: MediaTrackConstraints | boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Bits-per-second hint passed to `MediaRecorder`. Most browsers cap to
|
|
138
|
+
* reasonable defaults internally; leaving this undefined is usually
|
|
139
|
+
* fine.
|
|
140
|
+
*/
|
|
141
|
+
bitsPerSecond?: number;
|
|
142
|
+
/** Audio bitrate hint passed to `MediaRecorder`. */
|
|
143
|
+
audioBitsPerSecond?: number;
|
|
144
|
+
/** Video bitrate hint passed to `MediaRecorder`. */
|
|
145
|
+
videoBitsPerSecond?: number;
|
|
146
|
+
/** Constant/variable audio encoder preference, when supported. */
|
|
147
|
+
audioBitrateMode?: RecorderAudioBitrateMode;
|
|
148
|
+
/** Requested milliseconds between video keyframes. Mutually exclusive with count. */
|
|
149
|
+
videoKeyFrameIntervalDuration?: number;
|
|
150
|
+
/** Requested frames between video keyframes. Mutually exclusive with duration. */
|
|
151
|
+
videoKeyFrameIntervalCount?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Whether to capture system (tab/monitor) audio. Browser support is limited
|
|
154
|
+
* (desktop Chromium only); when unsupported the resulting stream simply omits
|
|
155
|
+
* it. How it is obtained depends on the source:
|
|
156
|
+
* - `'screen'` / `'screen+mic'` / `'screen+camera'` — folded into the screen
|
|
157
|
+
* lane's `getDisplayMedia` (rides the SCREEN file for the dual source).
|
|
158
|
+
* - `'mic'` / `'camera'` — captured via a SEPARATE `getDisplayMedia` whose
|
|
159
|
+
* video is discarded, then mixed into the mic/camera file. The browser still
|
|
160
|
+
* shows the screen/tab picker (audio-only display capture isn't allowed).
|
|
161
|
+
*/
|
|
162
|
+
systemAudio?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* For `source === 'camera'`, whether to include the microphone track.
|
|
165
|
+
* Defaults to `true` (camera + mic). Set `false` to capture silent
|
|
166
|
+
* video. Ignored for `'mic'`/`'screen'`/`'screen+mic'`, whose mic
|
|
167
|
+
* handling is encoded in the source itself. For `'screen+camera'` this
|
|
168
|
+
* gates the microphone on the CAMERA lane (the screen lane never carries
|
|
169
|
+
* the mic — system audio rides it instead).
|
|
170
|
+
*/
|
|
171
|
+
includeMicrophone?: boolean;
|
|
172
|
+
}
|
|
173
|
+
interface UseMediaRecorderResult {
|
|
174
|
+
/** Current recorder state. */
|
|
175
|
+
state: RecorderState;
|
|
176
|
+
/** `MediaStream` acquired by `request()`; live during preview/recording and
|
|
177
|
+
* inactive after stop. For `'screen+camera'` this is the SCREEN stream (see
|
|
178
|
+
* `camera` for the other). */
|
|
179
|
+
stream: MediaStream | null;
|
|
180
|
+
/** Final `Blob` after `stop()` resolves, or `null` while recording. For
|
|
181
|
+
* `'screen+camera'` this is the SCREEN file. */
|
|
182
|
+
blob: Blob | null;
|
|
183
|
+
/** MIME type the recorder actually used (after `request()`). */
|
|
184
|
+
mimeType: string | null;
|
|
185
|
+
/** File extension matching `mimeType` (e.g. `.webm`). */
|
|
186
|
+
extension: string | null;
|
|
187
|
+
/** Suggested container directory (`'audio'` for mic, `'video'` for camera/screen). */
|
|
188
|
+
directory: 'audio' | 'video' | null;
|
|
189
|
+
/** Milliseconds elapsed since `start()` was called. Updates ~10× per second while recording. */
|
|
190
|
+
durationMs: number;
|
|
191
|
+
/** Most recent error, if any. */
|
|
192
|
+
error: Error | null;
|
|
193
|
+
/**
|
|
194
|
+
* The camera companion lane for `source === 'screen+camera'`, else null.
|
|
195
|
+
* Its `blob` lands together with the primary `blob` when `stop()` resolves.
|
|
196
|
+
*/
|
|
197
|
+
camera: RecorderCameraLane | null;
|
|
198
|
+
/**
|
|
199
|
+
* Camera onstart minus screen onstart, in seconds (positive = camera
|
|
200
|
+
* started later). Null until both lanes have reported `onstart`, and for
|
|
201
|
+
* every non-dual source.
|
|
202
|
+
*/
|
|
203
|
+
cameraOffsetSec: number | null;
|
|
204
|
+
/**
|
|
205
|
+
* Acquire the stream and prepare a `MediaRecorder`. After this resolves
|
|
206
|
+
* the hook is in `'ready'` state and a `<video>`/`<audio>` element can
|
|
207
|
+
* preview `stream`. Call `start()` to begin recording.
|
|
208
|
+
*/
|
|
209
|
+
request: () => Promise<void>;
|
|
210
|
+
/** Start recording. Must be called from `'ready'`. */
|
|
211
|
+
start: () => void;
|
|
212
|
+
/**
|
|
213
|
+
* Stop recording and resolve with the resulting `Blob`. Safe to call
|
|
214
|
+
* from `'recording'`; a no-op from any other state (resolves with the
|
|
215
|
+
* existing `blob`, or `null`). Once the take has flushed, all capture
|
|
216
|
+
* tracks are stopped so browser sharing / camera / microphone indicators
|
|
217
|
+
* do not remain active during review.
|
|
218
|
+
*/
|
|
219
|
+
stop: () => Promise<Blob | null>;
|
|
220
|
+
/**
|
|
221
|
+
* Tear everything down — stops the recorder if running, releases all
|
|
222
|
+
* tracks, disposes the AudioContext mixer (if any), and returns to
|
|
223
|
+
* `'idle'`. Always safe to call.
|
|
224
|
+
*/
|
|
225
|
+
cancel: () => void;
|
|
226
|
+
/** Clear the current take. A new permission request may be needed before re-recording. */
|
|
227
|
+
reset: () => void;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Returns the kind of capture that the given source produces. Exposed
|
|
231
|
+
* separately from {@link useMediaRecorder} so non-React callers
|
|
232
|
+
* (e.g. headless tests) can resolve a format up front.
|
|
233
|
+
*/
|
|
234
|
+
declare function getCaptureKind(source: RecorderSource): CaptureKind;
|
|
235
|
+
declare function useMediaRecorder(options?: UseMediaRecorderOptions): UseMediaRecorderResult;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Mic capture + PCM transport for the teleprompter.
|
|
239
|
+
*
|
|
240
|
+
* getUserMedia → AudioContext → AudioWorklet tap → subscriber callbacks
|
|
241
|
+
* with 1024-sample Float32Array hops on the main thread. The DSP itself
|
|
242
|
+
* lives in core (`@bendyline/squisq/narration`) — this hook only moves
|
|
243
|
+
* samples. Falls back to a ScriptProcessorNode when `audioWorklet` is
|
|
244
|
+
* unavailable (same push model, so pacing still isn't tied to rAF).
|
|
245
|
+
*/
|
|
246
|
+
type MicAnalysisStatus = 'idle' | 'starting' | 'live' | 'error';
|
|
247
|
+
type PcmHopListener = (pcm: Float32Array, audioTimeSec: number) => void;
|
|
248
|
+
interface MicAnalysisHandle {
|
|
249
|
+
status: MicAnalysisStatus;
|
|
250
|
+
error: Error | null;
|
|
251
|
+
/** The live mic stream (feeds the narration recorder too), or null. */
|
|
252
|
+
stream: MediaStream | null;
|
|
253
|
+
/** AudioContext sample rate once live. */
|
|
254
|
+
sampleRate: number | null;
|
|
255
|
+
/** Known audio inputs (labels appear after the first grant). */
|
|
256
|
+
devices: MediaDeviceInfo[];
|
|
257
|
+
/**
|
|
258
|
+
* Start (or restart) capture. Resolves with the live stream — callers
|
|
259
|
+
* that need it immediately must use the return value, not the `stream`
|
|
260
|
+
* state field, which only updates on the NEXT render (stale-closure
|
|
261
|
+
* hazard right after an await). Null on failure/supersession.
|
|
262
|
+
*/
|
|
263
|
+
start: (deviceId: string | null) => Promise<MediaStream | null>;
|
|
264
|
+
stop: () => void;
|
|
265
|
+
/** Subscribe to PCM hops; returns an unsubscribe. */
|
|
266
|
+
subscribeHop: (listener: PcmHopListener) => () => void;
|
|
267
|
+
}
|
|
268
|
+
declare function useMicAnalysis(constraints?: MediaTrackConstraints): MicAnalysisHandle;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Shared types for the Narrate (teleprompter) display mode.
|
|
272
|
+
*/
|
|
273
|
+
interface TeleprompterPrefs {
|
|
274
|
+
/** Prompter type size in px (28–96). */
|
|
275
|
+
fontSizePx: number;
|
|
276
|
+
/** Beam-splitter mirror flip. */
|
|
277
|
+
mirrored: boolean;
|
|
278
|
+
/** Base speaking rate in words per minute (80–260). */
|
|
279
|
+
baseWpm: number;
|
|
280
|
+
/** Voice-adaptive pacing on/off; off = constant-rate manual mode. */
|
|
281
|
+
voiceTracking: boolean;
|
|
282
|
+
/** VAD sensitivity 0–1 (0.5 = engine defaults). */
|
|
283
|
+
vadSensitivity: number;
|
|
284
|
+
/** Countdown before the prompter starts rolling. */
|
|
285
|
+
countdownSec: 0 | 3 | 5 | 10;
|
|
286
|
+
/** Eye-line chevrons + focus band. */
|
|
287
|
+
lineGuide: boolean;
|
|
288
|
+
/** Preferred mic device id (null = system default). */
|
|
289
|
+
micDeviceId: string | null;
|
|
290
|
+
}
|
|
291
|
+
declare const DEFAULT_TELEPROMPTER_PREFS: TeleprompterPrefs;
|
|
292
|
+
declare const TELEPROMPTER_PREF_LIMITS: Readonly<{
|
|
293
|
+
fontSizePx: {
|
|
294
|
+
min: number;
|
|
295
|
+
max: number;
|
|
296
|
+
};
|
|
297
|
+
baseWpm: {
|
|
298
|
+
min: number;
|
|
299
|
+
max: number;
|
|
300
|
+
};
|
|
301
|
+
vadSensitivity: {
|
|
302
|
+
min: number;
|
|
303
|
+
max: number;
|
|
304
|
+
};
|
|
305
|
+
micDeviceIdMaxLength: 1024;
|
|
306
|
+
}>;
|
|
307
|
+
/**
|
|
308
|
+
* Validate persisted or externally supplied preferences and return a complete,
|
|
309
|
+
* bounded value. Unknown keys are ignored; invalid fields retain `fallback`.
|
|
310
|
+
*/
|
|
311
|
+
declare function normalizeTeleprompterPrefs(value: unknown, fallback?: TeleprompterPrefs): TeleprompterPrefs;
|
|
312
|
+
type PrompterTransport = 'stopped' | 'countdown' | 'rolling' | 'paused' | 'finished';
|
|
313
|
+
/** Floating-surface tier, best first. */
|
|
314
|
+
type FloatTier = 'document-pip' | 'video-pip' | 'popup' | 'docked';
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The teleprompter controller — single source of truth, always in the
|
|
318
|
+
* MAIN window (floats are render targets only).
|
|
319
|
+
*
|
|
320
|
+
* Loop-ownership rule: **the audio worklet owns time; the main window
|
|
321
|
+
* owns state; the visible surface owns pixels.** Voice-mode position
|
|
322
|
+
* advances on worklet PCM hops (immune to rAF/timer throttling while
|
|
323
|
+
* the browser is occluded by recording software); manual constant-rate
|
|
324
|
+
* mode uses rAF and is documented as best-effort under occlusion. React
|
|
325
|
+
* state publishes at ~15 Hz; per-hop subscribers (`subscribeTick`)
|
|
326
|
+
* exist for the video-PiP canvas pump.
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
interface TeleprompterKeyEvent {
|
|
330
|
+
readonly defaultPrevented: boolean;
|
|
331
|
+
readonly key: string;
|
|
332
|
+
readonly repeat: boolean;
|
|
333
|
+
readonly target: EventTarget | null;
|
|
334
|
+
preventDefault: () => void;
|
|
335
|
+
}
|
|
336
|
+
interface TeleprompterController {
|
|
337
|
+
script: NarrationScript | null;
|
|
338
|
+
transport: PrompterTransport;
|
|
339
|
+
countdownRemaining: number | null;
|
|
340
|
+
/** Fractional word position (published ~15 Hz). */
|
|
341
|
+
wordPos: number;
|
|
342
|
+
/** Smoothed mic level 0–1 for the meter. */
|
|
343
|
+
micLevel: number;
|
|
344
|
+
/** VAD flag for the meter tint. */
|
|
345
|
+
voiceActive: boolean;
|
|
346
|
+
mic: MicAnalysisHandle;
|
|
347
|
+
prefs: TeleprompterPrefs;
|
|
348
|
+
setPrefs: (patch: Partial<TeleprompterPrefs>) => void;
|
|
349
|
+
play: () => void;
|
|
350
|
+
pause: () => void;
|
|
351
|
+
restart: () => void;
|
|
352
|
+
/** Move by spoken words and re-anchor voice tracking. */
|
|
353
|
+
nudge: (deltaWords: number) => void;
|
|
354
|
+
/** Jump to an absolute token index (e.g. click a block marker). */
|
|
355
|
+
seekToToken: (tokenIndex: number) => void;
|
|
356
|
+
/** Per-analysis-tick subscription (video-PiP pump). Not throttled. */
|
|
357
|
+
subscribeTick: (cb: (wordPos: number) => void) => () => void;
|
|
358
|
+
handleKeyDown: (event: TeleprompterKeyEvent) => void;
|
|
359
|
+
}
|
|
360
|
+
/** Map the 0–1 sensitivity pref onto VAD thresholds (0.5 = engine defaults). */
|
|
361
|
+
declare function vadConfigForSensitivity(sensitivity: number): Partial<VadConfig>;
|
|
362
|
+
declare function useTeleprompter(opts: {
|
|
363
|
+
doc: Doc | null;
|
|
364
|
+
micConstraints?: MediaTrackConstraints;
|
|
365
|
+
}): TeleprompterController;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Floating-teleprompter window manager — framework-free.
|
|
369
|
+
*
|
|
370
|
+
* Capability ladder, best first:
|
|
371
|
+
* 1. `document-pip` — Document Picture-in-Picture (Chromium 116+,
|
|
372
|
+
* Firefox 151+): a true always-on-top window hosting live DOM; the
|
|
373
|
+
* React surface portals into it.
|
|
374
|
+
* 2. `video-pip` — canvas → `captureStream(0)` → `<video>` →
|
|
375
|
+
* `requestPictureInPicture()` (Safari's only always-on-top path;
|
|
376
|
+
* `webkitSetPresentationMode` fallback). Read-only: the main
|
|
377
|
+
* window draws frames and calls `requestFrame()` on analysis
|
|
378
|
+
* ticks — never rAF, which throttles under occlusion.
|
|
379
|
+
* 3. `popup` — `window.open` (positionable, not always-on-top).
|
|
380
|
+
* 4. `docked` — no float.
|
|
381
|
+
*
|
|
382
|
+
* Every tier is feature-detected at open time and falls through to the
|
|
383
|
+
* next on ANY failure. All interactive state stays in the main window;
|
|
384
|
+
* floats are render targets only.
|
|
385
|
+
*/
|
|
386
|
+
|
|
387
|
+
interface FloatOpenOptions {
|
|
388
|
+
width: number;
|
|
389
|
+
height: number;
|
|
390
|
+
/** Try this tier first; the ladder continues below it on failure. */
|
|
391
|
+
preferredTier?: FloatTier;
|
|
392
|
+
title: string;
|
|
393
|
+
}
|
|
394
|
+
interface CanvasSink {
|
|
395
|
+
canvas: HTMLCanvasElement;
|
|
396
|
+
width: number;
|
|
397
|
+
height: number;
|
|
398
|
+
/** Push the freshly drawn canvas frame into the PiP video. */
|
|
399
|
+
requestFrame: () => void;
|
|
400
|
+
}
|
|
401
|
+
type FloatEvent = 'closed' | 'tierchange';
|
|
402
|
+
interface FloatingWindowManager {
|
|
403
|
+
readonly tier: FloatTier;
|
|
404
|
+
readonly isOpen: boolean;
|
|
405
|
+
/** Resolves with the tier that actually opened ('docked' if none could). */
|
|
406
|
+
open(opts: FloatOpenOptions): Promise<FloatTier>;
|
|
407
|
+
/** Idempotent; restores docked and emits 'closed'. */
|
|
408
|
+
close(): void;
|
|
409
|
+
/** Portal container for 'document-pip' | 'popup'; null otherwise. */
|
|
410
|
+
getPortalTarget(): HTMLElement | null;
|
|
411
|
+
/** Canvas sink for 'video-pip'; null otherwise. */
|
|
412
|
+
getCanvasSink(): CanvasSink | null;
|
|
413
|
+
on(event: FloatEvent, cb: (tier: FloatTier) => void): () => void;
|
|
414
|
+
/** Tear everything down (unmount); like close() but silent-safe. */
|
|
415
|
+
dispose(): void;
|
|
416
|
+
}
|
|
417
|
+
/** Feature-detect the available float tiers, best first. */
|
|
418
|
+
declare function detectFloatTiers(): FloatTier[];
|
|
419
|
+
declare function createFloatingWindowManager(deps: {
|
|
420
|
+
styleCss: string;
|
|
421
|
+
}): FloatingWindowManager;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* React binding over {@link createFloatingWindowManager}: exposes the
|
|
425
|
+
* current tier + portal target as state and guarantees the float closes
|
|
426
|
+
* when the owning view unmounts (mode switch, Use-tab exit, shell
|
|
427
|
+
* teardown).
|
|
428
|
+
*/
|
|
429
|
+
|
|
430
|
+
interface FloatingWindowHandle {
|
|
431
|
+
tier: FloatTier;
|
|
432
|
+
isOpen: boolean;
|
|
433
|
+
/** Non-docked tiers this browser supports, best first. */
|
|
434
|
+
supportedTiers: FloatTier[];
|
|
435
|
+
portalTarget: HTMLElement | null;
|
|
436
|
+
canvasSink: CanvasSink | null;
|
|
437
|
+
open: (preferredTier?: FloatTier) => Promise<void>;
|
|
438
|
+
close: () => void;
|
|
439
|
+
}
|
|
440
|
+
declare function useFloatingWindow(styleCss: string): FloatingWindowHandle;
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Narration recorder for the teleprompter — an in-place (no modal)
|
|
444
|
+
* capture flow.
|
|
445
|
+
*
|
|
446
|
+
* Audio records THE MIC ANALYSIS STREAM (what paces the prompter is
|
|
447
|
+
* exactly what lands in the take); the optional camera is a separate
|
|
448
|
+
* video-only capture whose start skew vs the audio recorder is measured
|
|
449
|
+
* and persisted (`cameraOffsetSec`) — the audio file is the doc clock,
|
|
450
|
+
* so camera skew never affects narration timing. While recording, a
|
|
451
|
+
* sparse live trace of the prompter position is sampled; after stop,
|
|
452
|
+
* the take is decoded and run through core's offline aligner
|
|
453
|
+
* (`alignNarration`) to produce word/block timestamps for the sidecar.
|
|
454
|
+
* Decode/alignment failure degrades gracefully: saving still works,
|
|
455
|
+
* just without re-timing.
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
type NarrationRecorderState = 'idle' | 'starting' | 'recording' | 'processing' | 'review' | 'saving' | 'error';
|
|
459
|
+
type NarrationMediaRecorderOptions = MediaRecorderOptions & RecorderExtendedMediaOptions;
|
|
460
|
+
interface NarrationTake {
|
|
461
|
+
audioBlob: Blob;
|
|
462
|
+
audioMime: string;
|
|
463
|
+
audioExt: string;
|
|
464
|
+
cameraBlob: Blob | null;
|
|
465
|
+
cameraMime: string | null;
|
|
466
|
+
cameraExt: string | null;
|
|
467
|
+
durationSec: number;
|
|
468
|
+
cameraOffsetSec: number | undefined;
|
|
469
|
+
trace: NarrationTrace;
|
|
470
|
+
alignment: NarrationAlignment | null;
|
|
471
|
+
script: NarrationScript;
|
|
472
|
+
}
|
|
473
|
+
interface UseNarrationRecorderOptions {
|
|
474
|
+
mic: MicAnalysisHandle;
|
|
475
|
+
getScript: () => NarrationScript | null;
|
|
476
|
+
/** Live prompter position, sampled into the trace while recording. */
|
|
477
|
+
getWordPos: () => number;
|
|
478
|
+
getMicDeviceId: () => string | null;
|
|
479
|
+
/** Constraints for the optional, video-only camera companion. */
|
|
480
|
+
cameraConstraints?: MediaTrackConstraints | boolean;
|
|
481
|
+
/** MediaRecorder hints for the primary narration audio file. */
|
|
482
|
+
audioRecorderOptions?: NarrationMediaRecorderOptions;
|
|
483
|
+
/** MediaRecorder hints for the camera companion file. */
|
|
484
|
+
cameraRecorderOptions?: NarrationMediaRecorderOptions;
|
|
485
|
+
/** Fired when capture actually starts (View starts the prompter). */
|
|
486
|
+
onRecordingStart?: () => void;
|
|
487
|
+
onRecordingStop?: () => void;
|
|
488
|
+
}
|
|
489
|
+
interface NarrationRecorderController {
|
|
490
|
+
state: NarrationRecorderState;
|
|
491
|
+
error: Error | null;
|
|
492
|
+
withCamera: boolean;
|
|
493
|
+
setWithCamera: (on: boolean) => void;
|
|
494
|
+
/** Live camera stream for the self-view while recording. */
|
|
495
|
+
cameraStream: MediaStream | null;
|
|
496
|
+
take: NarrationTake | null;
|
|
497
|
+
start: () => Promise<void>;
|
|
498
|
+
stop: () => Promise<void>;
|
|
499
|
+
retake: () => void;
|
|
500
|
+
discard: () => void;
|
|
501
|
+
/** Transition into/out of 'saving'; the View owns the actual I/O. */
|
|
502
|
+
beginSave: () => void;
|
|
503
|
+
finishSave: (ok: boolean, error?: Error) => void;
|
|
504
|
+
}
|
|
505
|
+
declare function useNarrationRecorder(options: UseNarrationRecorderOptions): NarrationRecorderController;
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* useNarrationStage — the state/orchestration half of the narration stage,
|
|
509
|
+
* extracted from TeleprompterView so other hosts (the Record media dialog)
|
|
510
|
+
* can mount the same prompter + recorder + save pipeline.
|
|
511
|
+
*
|
|
512
|
+
* Owns the teleprompter controller (single source of truth in the main
|
|
513
|
+
* window), the floating-window handle, the narration recorder, and the
|
|
514
|
+
* retry-idempotent save flow. Pair with <NarrationStage> for the DOM.
|
|
515
|
+
*
|
|
516
|
+
* Recording is prop-gated: pass `recording` deps (media provider + markdown
|
|
517
|
+
* writers) to enable capture; without them this is a pure prompter with zero
|
|
518
|
+
* capture code paths.
|
|
519
|
+
*/
|
|
520
|
+
|
|
521
|
+
/** Editor plumbing the recording flow needs; omit for prompter-only use. */
|
|
522
|
+
interface TeleprompterRecordingDeps {
|
|
523
|
+
mediaProvider: MediaProvider;
|
|
524
|
+
container: ContentContainer | null;
|
|
525
|
+
markdownSource: string;
|
|
526
|
+
setMarkdownSource: (next: string) => void;
|
|
527
|
+
bumpMediaRevision: () => void;
|
|
528
|
+
}
|
|
529
|
+
interface UseNarrationStageOptions {
|
|
530
|
+
doc: Doc | null;
|
|
531
|
+
/** Recording deps; null/omitted disables the Record affordance. */
|
|
532
|
+
recording?: TeleprompterRecordingDeps | null;
|
|
533
|
+
/** Optional user-chosen filename base threaded into the save plan. */
|
|
534
|
+
getAudioBasename?: () => string | undefined;
|
|
535
|
+
/** Microphone constraints shared by voice analysis and audio recording. */
|
|
536
|
+
micConstraints?: MediaTrackConstraints;
|
|
537
|
+
/** Constraints for the optional narration camera lane. */
|
|
538
|
+
cameraConstraints?: MediaTrackConstraints;
|
|
539
|
+
/** MediaRecorder hints for the narration audio file. */
|
|
540
|
+
audioRecorderOptions?: NarrationMediaRecorderOptions;
|
|
541
|
+
/** MediaRecorder hints for the optional narration camera file. */
|
|
542
|
+
cameraRecorderOptions?: NarrationMediaRecorderOptions;
|
|
543
|
+
}
|
|
544
|
+
interface NarrationStageHandle {
|
|
545
|
+
controller: TeleprompterController;
|
|
546
|
+
float: FloatingWindowHandle;
|
|
547
|
+
recorder: NarrationRecorderController;
|
|
548
|
+
/** Pass-through so the stage component can gate its record slot. */
|
|
549
|
+
recording: TeleprompterRecordingDeps | null;
|
|
550
|
+
saveNotice: string | null;
|
|
551
|
+
dismissSaveNotice: () => void;
|
|
552
|
+
handleSave: () => Promise<void>;
|
|
553
|
+
handleRetake: () => void;
|
|
554
|
+
handleDiscard: () => void;
|
|
555
|
+
reviewAudioUrl: string | null;
|
|
556
|
+
handleReviewTimeUpdate: (event: SyntheticEvent<HTMLAudioElement>) => void;
|
|
557
|
+
}
|
|
558
|
+
declare function useNarrationStage(opts: UseNarrationStageOptions): NarrationStageHandle;
|
|
559
|
+
|
|
560
|
+
export { useMediaRecorder as A, useMicAnalysis as B, type CanvasSink as C, DEFAULT_TELEPROMPTER_PREFS as D, useNarrationRecorder as E, type FloatOpenOptions as F, useNarrationStage as G, useTeleprompter as H, vadConfigForSensitivity as I, TELEPROMPTER_PREF_LIMITS as J, normalizeTeleprompterPrefs as K, type MicAnalysisHandle as M, type NarrationRecorderController as N, type PrompterTransport as P, type RecorderAudioBitrateMode as R, type TeleprompterController as T, type UseMediaRecorderOptions as U, type CaptureKind as a, type FloatTier as b, type FloatingWindowHandle as c, type FloatingWindowManager as d, type MicAnalysisStatus as e, type NarrationRecorderState as f, type NarrationStageHandle as g, type NarrationTake as h, type RecorderCameraLane as i, type RecorderExtendedMediaOptions as j, type RecorderSource as k, type RecorderState as l, type ResolvedFormat as m, type TeleprompterPrefs as n, type TeleprompterRecordingDeps as o, type UseMediaRecorderResult as p, type UseNarrationStageOptions as q, buildFilename as r, createFloatingWindowManager as s, detectFloatTiers as t, getCaptureKind as u, resolveFormat as v, supportsDisplayMedia as w, supportsMediaRecorder as x, supportsUserMedia as y, useFloatingWindow as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-editor-react",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7",
|
|
4
4
|
"description": "React editor shell with raw/WYSIWYG/preview modes for Squisq documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@bendyline/squisq": "2.4.
|
|
88
|
-
"@bendyline/squisq-formats": "2.3.
|
|
89
|
-
"@bendyline/squisq-react": "2.4.
|
|
87
|
+
"@bendyline/squisq": "2.4.4",
|
|
88
|
+
"@bendyline/squisq-formats": "2.3.9",
|
|
89
|
+
"@bendyline/squisq-react": "2.4.7",
|
|
90
90
|
"@fortawesome/fontawesome-free": "7.2.0",
|
|
91
91
|
"@tiptap/core": "2.27.2",
|
|
92
92
|
"@tiptap/extension-heading": "2.27.2",
|