@bendyline/squisq-editor-react 2.11.18 → 2.11.20
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/dist/chunk-2MP7QE7O.js +15 -0
- package/dist/{chunk-H7YTL6SL.js → chunk-DJS5I5NO.js} +1 -1
- package/dist/{chunk-DPAMJV6A.js → chunk-HSWWEWT4.js} +31 -2
- package/dist/{chunk-45NQUR62.js → chunk-JI74KA6C.js} +197 -126
- package/dist/chunk-K56KR27T.js +141 -0
- package/dist/{chunk-WKZLBGPX.js → chunk-LR6Q2MK4.js} +1 -1
- package/dist/{chunk-GIXXD2O3.js → chunk-NL4JP55H.js} +3453 -2606
- package/dist/{chunk-2XXBDECJ.js → chunk-OCQEWWHI.js} +1 -1
- package/dist/{chunk-U6NVT3BY.js → chunk-UFK47ZAI.js} +42 -22
- package/dist/{chunk-UBFNLWCN.js → chunk-V2OLZOYJ.js} +2 -2
- package/dist/{chunk-CGENKHWY.js → chunk-ZS6G2P57.js} +27 -3
- package/dist/deviceSettings-BSkIQncC.d.ts +83 -0
- package/dist/{dictationSession-PN3OT5LJ.js → dictationSession-SLDNYIZ2.js} +3 -133
- package/dist/index.d.ts +200 -18
- package/dist/index.js +33 -10
- package/dist/json-editor/index.js +3 -3
- package/dist/recorder/index.d.ts +4 -2
- package/dist/recorder/index.js +3 -3
- package/dist/{recorder-CRsw0eC7.d.ts → recorder-Civyq5SU.d.ts} +4 -82
- package/dist/review/index.js +2 -2
- package/dist/screenRegion-Ci_RsCzG.d.ts +8 -0
- package/dist/shell/index.d.ts +2 -2
- package/dist/shell/index.js +11 -9
- package/dist/{shell-Cb0P_ln8.d.ts → shell-DTefmCBG.d.ts} +40 -3
- package/dist/speech/index.d.ts +35 -118
- package/dist/speech/index.js +7 -1
- package/dist/speechAudioWav-WKIQWGIJ.js +13 -0
- package/dist/styles/index.css +113 -3
- package/dist/teleprompter/index.d.ts +3 -2
- package/dist/teleprompter/index.js +2 -2
- package/dist/types-Bi4ZVKvN.d.ts +126 -0
- package/dist/{useNarrationStage-vKtk6H-V.d.ts → useNarrationStage-DlighI1z.d.ts} +17 -9
- package/package.json +6 -6
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
timingPathFor,
|
|
21
21
|
useNarrationStage,
|
|
22
22
|
useStreamPreview
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-ZS6G2P57.js";
|
|
24
24
|
import {
|
|
25
25
|
buildFilename,
|
|
26
26
|
requestMicStream,
|
|
@@ -2612,6 +2612,7 @@ function RecorderModal({
|
|
|
2612
2612
|
const narrationRecording = narrationOn && Boolean(narration?.recording);
|
|
2613
2613
|
const expanded = isExpandedPanel(panelMode);
|
|
2614
2614
|
const [narrationRequesting, setNarrationRequesting] = useState3(false);
|
|
2615
|
+
const [countingIn, setCountingIn] = useState3(false);
|
|
2615
2616
|
const [narrationPreview, setNarrationPreview] = useState3(null);
|
|
2616
2617
|
const [deviceSettings, setDeviceSettings] = useState3(() => ({
|
|
2617
2618
|
...DEFAULT_RECORDER_DEVICE_SETTINGS,
|
|
@@ -2927,17 +2928,27 @@ function RecorderModal({
|
|
|
2927
2928
|
} catch {
|
|
2928
2929
|
}
|
|
2929
2930
|
}, [recorder]);
|
|
2930
|
-
const handleStart = useCallback4(() => {
|
|
2931
|
+
const handleStart = useCallback4(async () => {
|
|
2931
2932
|
setSaveError(null);
|
|
2932
2933
|
dualSaveProgressRef.current = {};
|
|
2934
|
+
if (narrationOn) {
|
|
2935
|
+
setCountingIn(true);
|
|
2936
|
+
const rolled = await stageRef.current.controller.countIn();
|
|
2937
|
+
setCountingIn(false);
|
|
2938
|
+
if (!rolled) return;
|
|
2939
|
+
}
|
|
2933
2940
|
recorder.start();
|
|
2934
2941
|
const blockId = slideDeckRef.current[slideIndexRef.current]?.blockId;
|
|
2935
2942
|
setAdvanceLog(blockId ? recordSlideShown(EMPTY_ADVANCE_LOG, blockId, 0) : EMPTY_ADVANCE_LOG);
|
|
2936
|
-
}, [recorder]);
|
|
2943
|
+
}, [narrationOn, recorder]);
|
|
2937
2944
|
const handleStop = useCallback4(async () => {
|
|
2938
2945
|
setSaveError(null);
|
|
2946
|
+
if (countingIn) {
|
|
2947
|
+
stageRef.current.controller.pause();
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2939
2950
|
await recorder.stop();
|
|
2940
|
-
}, [recorder]);
|
|
2951
|
+
}, [countingIn, recorder]);
|
|
2941
2952
|
const writeSlideTimingSidecar = useCallback4(
|
|
2942
2953
|
async (mediaRelativePath, durationSec) => {
|
|
2943
2954
|
const doc = slides?.doc;
|
|
@@ -3130,8 +3141,8 @@ function RecorderModal({
|
|
|
3130
3141
|
);
|
|
3131
3142
|
const isAudioOnly = recordedMediaKind(source, recorder.stream, recorder.mimeType) === "audio";
|
|
3132
3143
|
const showPreview = recorder.state !== "idle" && recorder.state !== "error";
|
|
3133
|
-
const canRecord = recorder.state === "ready";
|
|
3134
|
-
const canStop = recorder.state === "recording";
|
|
3144
|
+
const canRecord = recorder.state === "ready" && !countingIn;
|
|
3145
|
+
const canStop = recorder.state === "recording" || countingIn;
|
|
3135
3146
|
const canSave = recorder.state === "stopped" && recorder.blob !== null;
|
|
3136
3147
|
const isBusy = recorder.state === "requesting" || recorder.state === "stopping" || isSaving;
|
|
3137
3148
|
const slidesTimingCapture = slidesOn && slidesCaptureTimings;
|
|
@@ -3185,7 +3196,7 @@ function RecorderModal({
|
|
|
3185
3196
|
sourceText
|
|
3186
3197
|
]);
|
|
3187
3198
|
const slideCoverageWarning = slideCoverage ? unshownSlidesWarning(slideCoverage.total, slideCoverage.unshown) : null;
|
|
3188
|
-
const togglesLocked = recorder.state === "recording" || recorder.state === "requesting" || recorder.state === "stopping" || canSave;
|
|
3199
|
+
const togglesLocked = countingIn || recorder.state === "recording" || recorder.state === "requesting" || recorder.state === "stopping" || canSave;
|
|
3189
3200
|
const toggleLockReason = canSave ? "Save or discard this recording before changing sources" : void 0;
|
|
3190
3201
|
const systemAudioHasCompanion = micOn || cameraOn || screenOn;
|
|
3191
3202
|
const simpleToggleProps = (key) => {
|
|
@@ -3680,24 +3691,33 @@ function RecorderModal({
|
|
|
3680
3691
|
children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
|
|
3681
3692
|
}
|
|
3682
3693
|
),
|
|
3683
|
-
canRecord && /* @__PURE__ */ jsxs5(
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3694
|
+
canRecord && /* @__PURE__ */ jsxs5(
|
|
3695
|
+
"button",
|
|
3696
|
+
{
|
|
3697
|
+
type: "button",
|
|
3698
|
+
style: btnRecord,
|
|
3699
|
+
onClick: () => void handleStart(),
|
|
3700
|
+
disabled: isBusy,
|
|
3701
|
+
children: [
|
|
3702
|
+
/* @__PURE__ */ jsx6(
|
|
3691
3703
|
"span",
|
|
3692
3704
|
{
|
|
3693
|
-
className: "squisq-recorder-record-dot
|
|
3694
|
-
style:
|
|
3705
|
+
className: "squisq-recorder-record-dot",
|
|
3706
|
+
style: recordDotFrameStyle,
|
|
3707
|
+
"aria-hidden": "true",
|
|
3708
|
+
children: /* @__PURE__ */ jsx6(
|
|
3709
|
+
"span",
|
|
3710
|
+
{
|
|
3711
|
+
className: "squisq-recorder-record-dot-center",
|
|
3712
|
+
style: recordDotStyle
|
|
3713
|
+
}
|
|
3714
|
+
)
|
|
3695
3715
|
}
|
|
3696
|
-
)
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3716
|
+
),
|
|
3717
|
+
"Record"
|
|
3718
|
+
]
|
|
3719
|
+
}
|
|
3720
|
+
),
|
|
3701
3721
|
canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
|
|
3702
3722
|
canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3703
3723
|
/* @__PURE__ */ jsx6(
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MarkdownEscape
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OCQEWWHI.js";
|
|
4
4
|
import {
|
|
5
5
|
markdownToTiptap,
|
|
6
6
|
tiptapToMarkdown
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HSWWEWT4.js";
|
|
8
8
|
|
|
9
9
|
// src/jsonEditor/JsonEditorContext.tsx
|
|
10
10
|
import { createContext, useContext, useEffect, useMemo } from "react";
|
|
@@ -429,6 +429,7 @@ function useTeleprompter(opts) {
|
|
|
429
429
|
const levelRef = useRef2(0);
|
|
430
430
|
const voiceRef = useRef2(false);
|
|
431
431
|
const countdownTimerRef = useRef2(null);
|
|
432
|
+
const countInWaitersRef = useRef2([]);
|
|
432
433
|
scriptRef.current = script;
|
|
433
434
|
prefsRef.current = prefs;
|
|
434
435
|
transportRef.current = transport;
|
|
@@ -453,13 +454,19 @@ function useTeleprompter(opts) {
|
|
|
453
454
|
const notifyTick = useCallback2(() => {
|
|
454
455
|
for (const cb of tickSubsRef.current) cb(wordPosRef.current);
|
|
455
456
|
}, []);
|
|
457
|
+
const settleCountIn = useCallback2((rolled) => {
|
|
458
|
+
const waiters = countInWaitersRef.current;
|
|
459
|
+
countInWaitersRef.current = [];
|
|
460
|
+
for (const resolve of waiters) resolve(rolled);
|
|
461
|
+
}, []);
|
|
456
462
|
const clearCountdown = useCallback2(() => {
|
|
457
463
|
if (countdownTimerRef.current !== null) {
|
|
458
464
|
clearInterval(countdownTimerRef.current);
|
|
459
465
|
countdownTimerRef.current = null;
|
|
460
466
|
}
|
|
461
467
|
setCountdownRemaining(null);
|
|
462
|
-
|
|
468
|
+
settleCountIn(false);
|
|
469
|
+
}, [settleCountIn]);
|
|
463
470
|
const finish = useCallback2(() => {
|
|
464
471
|
setTransport("finished");
|
|
465
472
|
transportRef.current = "finished";
|
|
@@ -533,13 +540,14 @@ function useTeleprompter(opts) {
|
|
|
533
540
|
return () => cancelAnimationFrame(raf);
|
|
534
541
|
}, [transport, mic.status, finish, notifyTick, publish]);
|
|
535
542
|
const beginRolling = useCallback2(() => {
|
|
543
|
+
settleCountIn(true);
|
|
536
544
|
clearCountdown();
|
|
537
545
|
if (sessionRef.current) {
|
|
538
546
|
sessionRef.current = reanchorSession(sessionRef.current, wordPosRef.current);
|
|
539
547
|
}
|
|
540
548
|
setTransport("rolling");
|
|
541
549
|
transportRef.current = "rolling";
|
|
542
|
-
}, [clearCountdown]);
|
|
550
|
+
}, [clearCountdown, settleCountIn]);
|
|
543
551
|
const play = useCallback2(() => {
|
|
544
552
|
if (transportRef.current === "rolling" || transportRef.current === "countdown") return;
|
|
545
553
|
if (transportRef.current === "finished") {
|
|
@@ -564,6 +572,14 @@ function useTeleprompter(opts) {
|
|
|
564
572
|
beginRolling();
|
|
565
573
|
}
|
|
566
574
|
}, [beginRolling, mic, publish]);
|
|
575
|
+
const countIn = useCallback2(() => {
|
|
576
|
+
if (transportRef.current === "rolling") return Promise.resolve(true);
|
|
577
|
+
const rolled = new Promise((resolve) => {
|
|
578
|
+
countInWaitersRef.current.push(resolve);
|
|
579
|
+
});
|
|
580
|
+
play();
|
|
581
|
+
return rolled;
|
|
582
|
+
}, [play]);
|
|
567
583
|
const pause = useCallback2(() => {
|
|
568
584
|
if (transportRef.current === "countdown") {
|
|
569
585
|
clearCountdown();
|
|
@@ -699,6 +715,7 @@ function useTeleprompter(opts) {
|
|
|
699
715
|
prefs,
|
|
700
716
|
setPrefs,
|
|
701
717
|
play,
|
|
718
|
+
countIn,
|
|
702
719
|
pause,
|
|
703
720
|
restart,
|
|
704
721
|
nudge,
|
|
@@ -1450,6 +1467,11 @@ function useNarrationRecorder(options) {
|
|
|
1450
1467
|
cameraMime = videoFormat.mimeType;
|
|
1451
1468
|
cameraExt = videoFormat.extension;
|
|
1452
1469
|
}
|
|
1470
|
+
if (opts.countIn) {
|
|
1471
|
+
setCameraStream(camera);
|
|
1472
|
+
if (!await opts.countIn()) throw new StartAborted();
|
|
1473
|
+
if (superseded()) throw new StartAborted();
|
|
1474
|
+
}
|
|
1453
1475
|
const capture = {
|
|
1454
1476
|
audioRecorder,
|
|
1455
1477
|
audioChunks: [],
|
|
@@ -1527,6 +1549,7 @@ function useNarrationRecorder(options) {
|
|
|
1527
1549
|
if (!capture) {
|
|
1528
1550
|
if (startingRef.current) {
|
|
1529
1551
|
cancelPendingStart();
|
|
1552
|
+
optionsRef.current.onRecordingStop?.();
|
|
1530
1553
|
setState("idle");
|
|
1531
1554
|
}
|
|
1532
1555
|
return;
|
|
@@ -1826,7 +1849,8 @@ function useNarrationStage(opts) {
|
|
|
1826
1849
|
maxRecordingBytes: opts.maxRecordingBytes,
|
|
1827
1850
|
audioRecorderOptions: opts.audioRecorderOptions,
|
|
1828
1851
|
cameraRecorderOptions: opts.cameraRecorderOptions,
|
|
1829
|
-
|
|
1852
|
+
// The take starts when the countdown ends, with the prompter already rolling.
|
|
1853
|
+
countIn: () => controllerRef.current.countIn(),
|
|
1830
1854
|
onRecordingStop: () => controllerRef.current.pause()
|
|
1831
1855
|
});
|
|
1832
1856
|
const recorderRef = useRef5(recorder);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { S as ScreenCaptureRegion } from './screenRegion-Ci_RsCzG.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User-configurable recorder device/encoding settings.
|
|
5
|
+
*
|
|
6
|
+
* Values stay serializable so the modal can include them in its capture key:
|
|
7
|
+
* changing a setting releases any prepared preview before a new stream is
|
|
8
|
+
* requested. Numeric and string track values are expressed as either `ideal`
|
|
9
|
+
* (best effort) or `exact` (fail when unavailable) constraints. Display
|
|
10
|
+
* capture is the exception: the screen-capture standard rejects exact
|
|
11
|
+
* constraints during its user picker, so those values always remain ideal.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type RecorderConstraintMode = 'ideal' | 'exact';
|
|
15
|
+
interface RecorderAudioDeviceSettings {
|
|
16
|
+
deviceId: string;
|
|
17
|
+
groupId?: string;
|
|
18
|
+
autoGainControl?: boolean;
|
|
19
|
+
channelCount?: number;
|
|
20
|
+
echoCancellation?: boolean;
|
|
21
|
+
latency?: number;
|
|
22
|
+
noiseSuppression?: boolean;
|
|
23
|
+
sampleRate?: number;
|
|
24
|
+
sampleSize?: number;
|
|
25
|
+
}
|
|
26
|
+
interface RecorderCameraDeviceSettings {
|
|
27
|
+
deviceId: string;
|
|
28
|
+
groupId?: string;
|
|
29
|
+
aspectRatio?: number;
|
|
30
|
+
backgroundBlur?: boolean;
|
|
31
|
+
facingMode?: string;
|
|
32
|
+
frameRate?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
resizeMode?: string;
|
|
35
|
+
width?: number;
|
|
36
|
+
}
|
|
37
|
+
interface RecorderScreenDeviceSettings {
|
|
38
|
+
/** Crop in captured pixels, relative to the chosen surface (not the desktop). */
|
|
39
|
+
crop?: ScreenCaptureRegion;
|
|
40
|
+
aspectRatio?: number;
|
|
41
|
+
cursor?: string;
|
|
42
|
+
displaySurface?: string;
|
|
43
|
+
frameRate?: number;
|
|
44
|
+
height?: number;
|
|
45
|
+
logicalSurface?: boolean;
|
|
46
|
+
resizeMode?: string;
|
|
47
|
+
width?: number;
|
|
48
|
+
}
|
|
49
|
+
interface RecorderScreenAudioSettings {
|
|
50
|
+
restrictOwnAudio?: boolean;
|
|
51
|
+
suppressLocalAudioPlayback?: boolean;
|
|
52
|
+
}
|
|
53
|
+
interface RecorderEncodingSettings {
|
|
54
|
+
audioMimeType: string;
|
|
55
|
+
videoMimeType: string;
|
|
56
|
+
bitsPerSecond?: number;
|
|
57
|
+
audioBitsPerSecond?: number;
|
|
58
|
+
videoBitsPerSecond?: number;
|
|
59
|
+
audioBitrateMode?: 'constant' | 'variable';
|
|
60
|
+
videoKeyFrameIntervalDuration?: number;
|
|
61
|
+
videoKeyFrameIntervalCount?: number;
|
|
62
|
+
}
|
|
63
|
+
interface RecorderDeviceSettings {
|
|
64
|
+
constraintMode: RecorderConstraintMode;
|
|
65
|
+
audio: RecorderAudioDeviceSettings;
|
|
66
|
+
camera: RecorderCameraDeviceSettings;
|
|
67
|
+
screen: RecorderScreenDeviceSettings;
|
|
68
|
+
screenAudio: RecorderScreenAudioSettings;
|
|
69
|
+
encoding: RecorderEncodingSettings;
|
|
70
|
+
}
|
|
71
|
+
declare const DEFAULT_RECORDER_DEVICE_SETTINGS: RecorderDeviceSettings;
|
|
72
|
+
/** Build microphone constraints from the advanced settings form. */
|
|
73
|
+
declare function buildRecorderAudioConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
|
|
74
|
+
/** Build camera constraints from the advanced settings form. */
|
|
75
|
+
declare function buildRecorderCameraConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
|
|
76
|
+
/** Build display-capture constraints independently from camera constraints. */
|
|
77
|
+
declare function buildRecorderScreenConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
|
|
78
|
+
/** Build the audio-track constraints specific to getDisplayMedia. */
|
|
79
|
+
declare function buildRecorderScreenAudioConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
|
|
80
|
+
/** Convert the form's kbps values to the bps values MediaRecorder expects. */
|
|
81
|
+
declare function recorderBitsPerSecond(value: number | undefined): number | undefined;
|
|
82
|
+
|
|
83
|
+
export { DEFAULT_RECORDER_DEVICE_SETTINGS as D, type RecorderDeviceSettings as R, type RecorderAudioDeviceSettings as a, type RecorderCameraDeviceSettings as b, type RecorderConstraintMode as c, type RecorderEncodingSettings as d, type RecorderScreenAudioSettings as e, type RecorderScreenDeviceSettings as f, buildRecorderAudioConstraints as g, buildRecorderCameraConstraints as h, buildRecorderScreenAudioConstraints as i, buildRecorderScreenConstraints as j, recorderBitsPerSecond as r };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
microphoneTakeAsWav
|
|
3
|
+
} from "./chunk-K56KR27T.js";
|
|
4
|
+
import "./chunk-DNEVUOBZ.js";
|
|
4
5
|
import {
|
|
5
6
|
DEFAULT_RECORDER_DEVICE_SETTINGS,
|
|
6
7
|
buildRecorderAudioConstraints
|
|
@@ -320,137 +321,6 @@ function extendRecognitionPrompt(previous, transcript) {
|
|
|
320
321
|
return (firstWhitespace >= 0 ? tail.slice(firstWhitespace + 1) : tail).trim();
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
// src/speech/speechAudioWav.ts
|
|
324
|
-
async function microphoneTakeAsWav(blob) {
|
|
325
|
-
const encoded = await blobBytes(blob);
|
|
326
|
-
let audio;
|
|
327
|
-
try {
|
|
328
|
-
audio = await decodeAt16k(encoded);
|
|
329
|
-
} catch (caught) {
|
|
330
|
-
const detail = caught instanceof Error && caught.message ? ` (${caught.message})` : "";
|
|
331
|
-
throw new Error(`This recording could not be prepared for speech recognition${detail}`);
|
|
332
|
-
}
|
|
333
|
-
const channels = Array.from(
|
|
334
|
-
{ length: audio.numberOfChannels },
|
|
335
|
-
(_, index) => audio.getChannelData(index)
|
|
336
|
-
);
|
|
337
|
-
const mono = downmixToMono(channels);
|
|
338
|
-
const samples = audio.sampleRate === SPEECH_INPUT_SAMPLE_RATE ? mono : resampleLinear(mono, audio.sampleRate, SPEECH_INPUT_SAMPLE_RATE);
|
|
339
|
-
return encodeMonoPcm16Wav([samples], SPEECH_INPUT_SAMPLE_RATE).buffer;
|
|
340
|
-
}
|
|
341
|
-
function blobBytes(blob) {
|
|
342
|
-
if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
|
|
343
|
-
return new Promise((resolve, reject) => {
|
|
344
|
-
const reader = new FileReader();
|
|
345
|
-
reader.onload = () => {
|
|
346
|
-
const result = reader.result;
|
|
347
|
-
if (result instanceof ArrayBuffer) resolve(result);
|
|
348
|
-
else reject(new Error("The recording could not be read."));
|
|
349
|
-
};
|
|
350
|
-
reader.onerror = () => reject(reader.error ?? new Error("The recording could not be read."));
|
|
351
|
-
reader.readAsArrayBuffer(blob);
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
async function decodeAt16k(encoded) {
|
|
355
|
-
if (typeof OfflineAudioContext !== "undefined") {
|
|
356
|
-
let offline = null;
|
|
357
|
-
try {
|
|
358
|
-
offline = new OfflineAudioContext(1, 1, SPEECH_INPUT_SAMPLE_RATE);
|
|
359
|
-
} catch {
|
|
360
|
-
offline = null;
|
|
361
|
-
}
|
|
362
|
-
if (offline) return offline.decodeAudioData(encoded);
|
|
363
|
-
}
|
|
364
|
-
if (typeof AudioContext === "undefined") {
|
|
365
|
-
throw new Error("Web Audio is unavailable");
|
|
366
|
-
}
|
|
367
|
-
const context = new AudioContext();
|
|
368
|
-
try {
|
|
369
|
-
return await context.decodeAudioData(encoded);
|
|
370
|
-
} finally {
|
|
371
|
-
await context.close().catch(() => void 0);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
function downmixToMono(channels) {
|
|
375
|
-
const sampleCount = channels[0]?.length ?? 0;
|
|
376
|
-
if (channels.length === 0 || sampleCount === 0) {
|
|
377
|
-
throw new Error("The microphone recording was empty.");
|
|
378
|
-
}
|
|
379
|
-
if (channels.length === 1) return channels[0];
|
|
380
|
-
if (channels.some((channel) => channel.length !== sampleCount)) {
|
|
381
|
-
throw new Error("The microphone recording channels have different lengths.");
|
|
382
|
-
}
|
|
383
|
-
const mono = new Float32Array(sampleCount);
|
|
384
|
-
for (let index = 0; index < sampleCount; index += 1) {
|
|
385
|
-
let sum = 0;
|
|
386
|
-
for (const channel of channels) sum += channel[index] ?? 0;
|
|
387
|
-
mono[index] = sum / channels.length;
|
|
388
|
-
}
|
|
389
|
-
return mono;
|
|
390
|
-
}
|
|
391
|
-
function resampleLinear(samples, fromRate, toRate) {
|
|
392
|
-
if (!Number.isFinite(fromRate) || fromRate <= 0 || !Number.isFinite(toRate) || toRate <= 0) {
|
|
393
|
-
throw new Error("The microphone recording has an invalid sample rate.");
|
|
394
|
-
}
|
|
395
|
-
if (fromRate === toRate) return samples;
|
|
396
|
-
const outputLength = Math.max(1, Math.round(samples.length * toRate / fromRate));
|
|
397
|
-
const output = new Float32Array(outputLength);
|
|
398
|
-
const step = fromRate / toRate;
|
|
399
|
-
for (let index = 0; index < outputLength; index += 1) {
|
|
400
|
-
const position = index * step;
|
|
401
|
-
const left = Math.floor(position);
|
|
402
|
-
const right = Math.min(left + 1, samples.length - 1);
|
|
403
|
-
const fraction = position - left;
|
|
404
|
-
const a = samples[Math.min(left, samples.length - 1)] ?? 0;
|
|
405
|
-
const b = samples[right] ?? 0;
|
|
406
|
-
output[index] = a + (b - a) * fraction;
|
|
407
|
-
}
|
|
408
|
-
return output;
|
|
409
|
-
}
|
|
410
|
-
function encodeMonoPcm16Wav(channels, sampleRate) {
|
|
411
|
-
const sampleCount = channels[0]?.length ?? 0;
|
|
412
|
-
if (channels.length === 0 || sampleCount === 0) {
|
|
413
|
-
throw new Error("The microphone recording was empty.");
|
|
414
|
-
}
|
|
415
|
-
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
416
|
-
throw new Error("The microphone recording has an invalid sample rate.");
|
|
417
|
-
}
|
|
418
|
-
if (channels.some((channel) => channel.length !== sampleCount)) {
|
|
419
|
-
throw new Error("The microphone recording channels have different lengths.");
|
|
420
|
-
}
|
|
421
|
-
const rate = Math.round(sampleRate);
|
|
422
|
-
const bytesPerSample = 2;
|
|
423
|
-
const dataBytes = sampleCount * bytesPerSample;
|
|
424
|
-
const buffer = new ArrayBuffer(44 + dataBytes);
|
|
425
|
-
const view = new DataView(buffer);
|
|
426
|
-
writeAscii(view, 0, "RIFF");
|
|
427
|
-
view.setUint32(4, 36 + dataBytes, true);
|
|
428
|
-
writeAscii(view, 8, "WAVE");
|
|
429
|
-
writeAscii(view, 12, "fmt ");
|
|
430
|
-
view.setUint32(16, 16, true);
|
|
431
|
-
view.setUint16(20, 1, true);
|
|
432
|
-
view.setUint16(22, 1, true);
|
|
433
|
-
view.setUint32(24, rate, true);
|
|
434
|
-
view.setUint32(28, rate * bytesPerSample, true);
|
|
435
|
-
view.setUint16(32, bytesPerSample, true);
|
|
436
|
-
view.setUint16(34, 16, true);
|
|
437
|
-
writeAscii(view, 36, "data");
|
|
438
|
-
view.setUint32(40, dataBytes, true);
|
|
439
|
-
for (let index = 0; index < sampleCount; index += 1) {
|
|
440
|
-
let mono = 0;
|
|
441
|
-
for (const channel of channels) mono += channel[index] ?? 0;
|
|
442
|
-
mono = Math.max(-1, Math.min(1, mono / channels.length));
|
|
443
|
-
const pcm = mono < 0 ? Math.round(mono * 32768) : Math.round(mono * 32767);
|
|
444
|
-
view.setInt16(44 + index * bytesPerSample, pcm, true);
|
|
445
|
-
}
|
|
446
|
-
return new Uint8Array(buffer);
|
|
447
|
-
}
|
|
448
|
-
function writeAscii(view, offset, value) {
|
|
449
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
450
|
-
view.setUint8(offset + index, value.charCodeAt(index));
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
324
|
// src/speech/dictationSession.ts
|
|
455
325
|
function dictationAudioConstraints(settings = DEFAULT_RECORDER_DEVICE_SETTINGS) {
|
|
456
326
|
return {
|