@bendyline/squisq-editor-react 2.11.15 → 2.11.17
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 +9 -1
- package/dist/{chunk-ZDWZRVAY.js → chunk-2XXBDECJ.js} +1 -1
- package/dist/{chunk-BJ3TXAIQ.js → chunk-C6QVA6TO.js} +731 -448
- package/dist/{chunk-EN5BYZOE.js → chunk-DPAMJV6A.js} +3 -2
- package/dist/{chunk-DN6XFOY2.js → chunk-EJKGXWNK.js} +1 -1
- package/dist/{chunk-DNB32SU2.js → chunk-HTO5MTTJ.js} +1 -1
- package/dist/{chunk-ENDRPV6T.js → chunk-QZWFSSDE.js} +1 -1
- package/dist/{chunk-WDQFWD7Z.js → chunk-UBFNLWCN.js} +2 -2
- package/dist/{chunk-7B46MJNW.js → chunk-US5DALTZ.js} +871 -692
- package/dist/{chunk-34FRFDQ6.js → chunk-XZVMKR5Q.js} +44 -9
- package/dist/index.d.ts +9 -17
- package/dist/index.js +10 -10
- package/dist/json-editor/index.js +3 -3
- package/dist/recorder/index.d.ts +2 -2
- package/dist/recorder/index.js +3 -3
- package/dist/{recorder-CC3VW9Z7.d.ts → recorder-CRsw0eC7.d.ts} +11 -5
- package/dist/review/index.js +2 -2
- package/dist/shell/index.js +7 -7
- package/dist/styles/index.css +37 -29
- package/dist/teleprompter/index.d.ts +2 -2
- package/dist/teleprompter/index.js +2 -2
- package/dist/{useNarrationStage-D3Sgcf0L.d.ts → useNarrationStage-vKtk6H-V.d.ts} +17 -2
- package/package.json +6 -6
|
@@ -19,7 +19,115 @@ import {
|
|
|
19
19
|
timingPathFor,
|
|
20
20
|
useNarrationStage,
|
|
21
21
|
useStreamPreview
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-XZVMKR5Q.js";
|
|
23
|
+
|
|
24
|
+
// src/recorder/sources/screenRegion.ts
|
|
25
|
+
function validateScreenCaptureRegion(region, width, height) {
|
|
26
|
+
const { x, y, width: w, height: h } = region;
|
|
27
|
+
if (![x, y, w, h].every((value) => Number.isSafeInteger(value) && value >= 0 && value <= 16384) || w === 0 || h === 0 || w * h > 16777216) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Enter a valid recording region: non-negative left/top and positive whole-pixel dimensions (up to 16 megapixels)."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (width !== void 0 && x + w > width || height !== void 0 && y + h > height) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`The recording region is outside the selected surface (${width} \xD7 ${height} pixels). Adjust the coordinates or choose another surface.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function cropScreenStream(source, region) {
|
|
39
|
+
validateScreenCaptureRegion(region);
|
|
40
|
+
const [sourceTrack] = source.getVideoTracks();
|
|
41
|
+
if (!sourceTrack) throw new Error("The selected surface has no video track.");
|
|
42
|
+
const video = document.createElement("video");
|
|
43
|
+
video.muted = true;
|
|
44
|
+
video.playsInline = true;
|
|
45
|
+
const canvas = document.createElement("canvas");
|
|
46
|
+
const context = canvas.getContext("2d");
|
|
47
|
+
if (!context || typeof canvas.captureStream !== "function")
|
|
48
|
+
throw new Error("Recording a region is not supported in this browser.");
|
|
49
|
+
canvas.width = region.width;
|
|
50
|
+
canvas.height = region.height;
|
|
51
|
+
let timer;
|
|
52
|
+
let output;
|
|
53
|
+
let disposed = false;
|
|
54
|
+
const dispose = () => {
|
|
55
|
+
if (disposed) return;
|
|
56
|
+
disposed = true;
|
|
57
|
+
clearInterval(timer);
|
|
58
|
+
sourceTrack.removeEventListener("ended", ended);
|
|
59
|
+
video.pause();
|
|
60
|
+
video.srcObject = null;
|
|
61
|
+
output?.getVideoTracks().forEach((track) => track.stop());
|
|
62
|
+
source.getTracks().forEach((track) => track.stop());
|
|
63
|
+
};
|
|
64
|
+
const ended = () => {
|
|
65
|
+
const track = output?.getVideoTracks()[0];
|
|
66
|
+
dispose();
|
|
67
|
+
track?.dispatchEvent(new Event("ended"));
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
await new Promise((resolve, reject) => {
|
|
71
|
+
const cleanup = () => {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
video.removeEventListener("loadeddata", ready);
|
|
74
|
+
video.removeEventListener("error", failed);
|
|
75
|
+
sourceTrack.removeEventListener("ended", failed);
|
|
76
|
+
};
|
|
77
|
+
const ready = () => {
|
|
78
|
+
cleanup();
|
|
79
|
+
resolve();
|
|
80
|
+
};
|
|
81
|
+
const failed = () => {
|
|
82
|
+
cleanup();
|
|
83
|
+
reject(new Error("Could not preview the selected screen. Choose it again."));
|
|
84
|
+
};
|
|
85
|
+
const timeout = setTimeout(failed, 1e4);
|
|
86
|
+
video.addEventListener("loadeddata", ready, { once: true });
|
|
87
|
+
video.addEventListener("error", failed, { once: true });
|
|
88
|
+
sourceTrack.addEventListener("ended", failed, { once: true });
|
|
89
|
+
try {
|
|
90
|
+
video.srcObject = source;
|
|
91
|
+
void video.play().catch(failed);
|
|
92
|
+
} catch {
|
|
93
|
+
failed();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (sourceTrack.readyState === "ended")
|
|
97
|
+
throw new Error("Screen sharing ended. Choose a surface again.");
|
|
98
|
+
validateScreenCaptureRegion(region, video.videoWidth, video.videoHeight);
|
|
99
|
+
const draw = () => {
|
|
100
|
+
if (region.x + region.width > video.videoWidth || region.y + region.height > video.videoHeight || sourceTrack.readyState === "ended") {
|
|
101
|
+
ended();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
context.drawImage(
|
|
106
|
+
video,
|
|
107
|
+
region.x,
|
|
108
|
+
region.y,
|
|
109
|
+
region.width,
|
|
110
|
+
region.height,
|
|
111
|
+
0,
|
|
112
|
+
0,
|
|
113
|
+
region.width,
|
|
114
|
+
region.height
|
|
115
|
+
);
|
|
116
|
+
} catch {
|
|
117
|
+
ended();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
draw();
|
|
121
|
+
output = canvas.captureStream(30);
|
|
122
|
+
const stream = new MediaStream([...output.getVideoTracks(), ...source.getAudioTracks()]);
|
|
123
|
+
sourceTrack.addEventListener("ended", ended, { once: true });
|
|
124
|
+
timer = setInterval(draw, 1e3 / 30);
|
|
125
|
+
return { stream, dispose };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
dispose();
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
23
131
|
|
|
24
132
|
// src/recorder/sources/screenStream.ts
|
|
25
133
|
function mixAudioTracks(streams) {
|
|
@@ -38,6 +146,25 @@ function mixAudioTracks(streams) {
|
|
|
38
146
|
return { track: mixed, context: ctx };
|
|
39
147
|
}
|
|
40
148
|
async function requestScreenStream(options) {
|
|
149
|
+
if (options?.crop) validateScreenCaptureRegion(options.crop);
|
|
150
|
+
const handle = await requestFullScreenStream(options);
|
|
151
|
+
if (!options?.crop) return handle;
|
|
152
|
+
try {
|
|
153
|
+
const cropped = await cropScreenStream(handle.stream, options.crop);
|
|
154
|
+
return {
|
|
155
|
+
stream: cropped.stream,
|
|
156
|
+
dispose: () => {
|
|
157
|
+
cropped.dispose();
|
|
158
|
+
handle.dispose();
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
handle.stream.getTracks().forEach((track) => track.stop());
|
|
163
|
+
handle.dispose();
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function requestFullScreenStream(options) {
|
|
41
168
|
if (!supportsDisplayMedia()) {
|
|
42
169
|
throw new Error("navigator.mediaDevices.getDisplayMedia is not available in this environment.");
|
|
43
170
|
}
|
|
@@ -198,6 +325,7 @@ async function acquireStream(source, opts) {
|
|
|
198
325
|
case "screen+mic": {
|
|
199
326
|
const handle = await requestScreenStream({
|
|
200
327
|
video: opts.screenVideoConstraints ?? opts.videoConstraints ?? true,
|
|
328
|
+
crop: opts.screenRegion,
|
|
201
329
|
systemAudio: opts.systemAudio ?? false,
|
|
202
330
|
systemAudioConstraints: opts.screenAudioConstraints,
|
|
203
331
|
includeMicrophone: source === "screen+mic",
|
|
@@ -209,6 +337,7 @@ async function acquireStream(source, opts) {
|
|
|
209
337
|
}
|
|
210
338
|
async function acquireDualStreams(opts, isStale) {
|
|
211
339
|
const screen = await requestScreenStream({
|
|
340
|
+
crop: opts.screenRegion,
|
|
212
341
|
video: opts.screenVideoConstraints ?? opts.videoConstraints ?? true,
|
|
213
342
|
systemAudio: opts.systemAudio ?? false,
|
|
214
343
|
systemAudioConstraints: opts.screenAudioConstraints,
|
|
@@ -583,6 +712,15 @@ function useMediaRecorder(options = {}) {
|
|
|
583
712
|
};
|
|
584
713
|
recorderRef.current = recorder;
|
|
585
714
|
disposeStreamRef.current = dispose;
|
|
715
|
+
if (source === "screen" || source === "screen+mic") {
|
|
716
|
+
const [screenTrack] = nextStream.getVideoTracks();
|
|
717
|
+
if (screenTrack)
|
|
718
|
+
screenTrack.onended = () => {
|
|
719
|
+
if (lifecycle !== lifecycleRef.current) return;
|
|
720
|
+
if (stateRef.current === "recording") void stopFnRef.current?.();
|
|
721
|
+
else if (stateRef.current === "ready") cancelFnRef.current?.();
|
|
722
|
+
};
|
|
723
|
+
}
|
|
586
724
|
setStream(nextStream);
|
|
587
725
|
setFormat(resolved);
|
|
588
726
|
setBlob(null);
|
|
@@ -1253,24 +1391,6 @@ function RecorderDeviceSettingsPanel({
|
|
|
1253
1391
|
/* @__PURE__ */ jsx("legend", { style: legendStyle, children: screenEnabled ? "Screen capture" : "System audio capture" }),
|
|
1254
1392
|
/* @__PURE__ */ jsxs("div", { style: gridStyle, children: [
|
|
1255
1393
|
screenEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1256
|
-
supports("displaySurface") && /* @__PURE__ */ jsxs("label", { style: { ...fieldStyle, gridColumn: "1 / -1" }, children: [
|
|
1257
|
-
"Preferred surface",
|
|
1258
|
-
/* @__PURE__ */ jsxs(
|
|
1259
|
-
"select",
|
|
1260
|
-
{
|
|
1261
|
-
value: value.screen.displaySurface ?? "",
|
|
1262
|
-
disabled,
|
|
1263
|
-
style: inputStyle,
|
|
1264
|
-
onChange: (event) => patchScreen({ displaySurface: event.target.value || void 0 }),
|
|
1265
|
-
children: [
|
|
1266
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Browser picker default" }),
|
|
1267
|
-
/* @__PURE__ */ jsx("option", { value: "monitor", children: "Entire monitor" }),
|
|
1268
|
-
/* @__PURE__ */ jsx("option", { value: "window", children: "Application window" }),
|
|
1269
|
-
/* @__PURE__ */ jsx("option", { value: "browser", children: "Browser tab" })
|
|
1270
|
-
]
|
|
1271
|
-
}
|
|
1272
|
-
)
|
|
1273
|
-
] }),
|
|
1274
1394
|
supports("cursor") && /* @__PURE__ */ jsxs("label", { style: fieldStyle, children: [
|
|
1275
1395
|
"Mouse cursor",
|
|
1276
1396
|
/* @__PURE__ */ jsxs(
|
|
@@ -1698,8 +1818,9 @@ function isExpandedPanel(mode) {
|
|
|
1698
1818
|
return mode !== "none";
|
|
1699
1819
|
}
|
|
1700
1820
|
function panelModeAfterToggle(current, target, checked) {
|
|
1701
|
-
|
|
1702
|
-
|
|
1821
|
+
const narration = target === "narration" ? checked : current === "narration" || current === "both";
|
|
1822
|
+
const slides = target === "slides" ? checked : current === "slides" || current === "both";
|
|
1823
|
+
return narration ? slides ? "both" : "narration" : slides ? "slides" : "none";
|
|
1703
1824
|
}
|
|
1704
1825
|
function clampSlideIndex(index, count) {
|
|
1705
1826
|
if (count <= 0) return 0;
|
|
@@ -2059,6 +2180,134 @@ import {
|
|
|
2059
2180
|
recordSlideShown
|
|
2060
2181
|
} from "@bendyline/squisq/narration";
|
|
2061
2182
|
|
|
2183
|
+
// src/recorder/RecorderScreenSettings.tsx
|
|
2184
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2185
|
+
var field = { display: "flex", flexDirection: "column", gap: 4, minWidth: 0 };
|
|
2186
|
+
var input = {
|
|
2187
|
+
width: "100%",
|
|
2188
|
+
boxSizing: "border-box",
|
|
2189
|
+
padding: "6px 8px",
|
|
2190
|
+
font: "inherit",
|
|
2191
|
+
color: "var(--squisq-recorder-text)",
|
|
2192
|
+
background: "var(--squisq-recorder-input)",
|
|
2193
|
+
border: "1px solid var(--squisq-recorder-border)"
|
|
2194
|
+
};
|
|
2195
|
+
function RecorderScreenSettings({
|
|
2196
|
+
value,
|
|
2197
|
+
onChange,
|
|
2198
|
+
disabled,
|
|
2199
|
+
onChoose,
|
|
2200
|
+
hasPreview
|
|
2201
|
+
}) {
|
|
2202
|
+
const crop = value.crop;
|
|
2203
|
+
return /* @__PURE__ */ jsxs4(
|
|
2204
|
+
"fieldset",
|
|
2205
|
+
{
|
|
2206
|
+
disabled,
|
|
2207
|
+
style: {
|
|
2208
|
+
margin: "0 0 12px",
|
|
2209
|
+
padding: 12,
|
|
2210
|
+
border: "1px solid var(--squisq-recorder-border)",
|
|
2211
|
+
fontSize: 13
|
|
2212
|
+
},
|
|
2213
|
+
children: [
|
|
2214
|
+
/* @__PURE__ */ jsx5("legend", { children: "What to record" }),
|
|
2215
|
+
/* @__PURE__ */ jsxs4(
|
|
2216
|
+
"div",
|
|
2217
|
+
{
|
|
2218
|
+
style: {
|
|
2219
|
+
display: "grid",
|
|
2220
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
2221
|
+
gap: 10
|
|
2222
|
+
},
|
|
2223
|
+
children: [
|
|
2224
|
+
/* @__PURE__ */ jsxs4("label", { style: field, children: [
|
|
2225
|
+
"Preferred surface",
|
|
2226
|
+
/* @__PURE__ */ jsxs4(
|
|
2227
|
+
"select",
|
|
2228
|
+
{
|
|
2229
|
+
style: input,
|
|
2230
|
+
value: value.displaySurface ?? "",
|
|
2231
|
+
onChange: (event) => onChange({ ...value, displaySurface: event.target.value || void 0 }),
|
|
2232
|
+
children: [
|
|
2233
|
+
/* @__PURE__ */ jsx5("option", { value: "", children: "Any available surface" }),
|
|
2234
|
+
/* @__PURE__ */ jsx5("option", { value: "monitor", children: "Entire screen" }),
|
|
2235
|
+
/* @__PURE__ */ jsx5("option", { value: "window", children: "Application window" }),
|
|
2236
|
+
/* @__PURE__ */ jsx5("option", { value: "browser", children: "Browser tab (when available)" })
|
|
2237
|
+
]
|
|
2238
|
+
}
|
|
2239
|
+
)
|
|
2240
|
+
] }),
|
|
2241
|
+
/* @__PURE__ */ jsxs4("label", { style: field, children: [
|
|
2242
|
+
"Capture area",
|
|
2243
|
+
/* @__PURE__ */ jsxs4(
|
|
2244
|
+
"select",
|
|
2245
|
+
{
|
|
2246
|
+
style: input,
|
|
2247
|
+
value: value.crop ? "region" : "full",
|
|
2248
|
+
onChange: (event) => onChange({
|
|
2249
|
+
...value,
|
|
2250
|
+
crop: event.target.value === "region" ? { x: 0, y: 0, width: 1280, height: 720 } : void 0
|
|
2251
|
+
}),
|
|
2252
|
+
children: [
|
|
2253
|
+
/* @__PURE__ */ jsx5("option", { value: "full", children: "Whole selected surface" }),
|
|
2254
|
+
/* @__PURE__ */ jsx5("option", { value: "region", children: "Region by coordinates" })
|
|
2255
|
+
]
|
|
2256
|
+
}
|
|
2257
|
+
)
|
|
2258
|
+
] }),
|
|
2259
|
+
crop && /* @__PURE__ */ jsx5(
|
|
2260
|
+
"div",
|
|
2261
|
+
{
|
|
2262
|
+
style: {
|
|
2263
|
+
gridColumn: "1 / -1",
|
|
2264
|
+
display: "grid",
|
|
2265
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
2266
|
+
gap: 10
|
|
2267
|
+
},
|
|
2268
|
+
children: ["x", "y", "width", "height"].map((key) => /* @__PURE__ */ jsxs4("label", { style: field, children: [
|
|
2269
|
+
{
|
|
2270
|
+
x: "Left (px)",
|
|
2271
|
+
y: "Top (px)",
|
|
2272
|
+
width: "Region width (px)",
|
|
2273
|
+
height: "Region height (px)"
|
|
2274
|
+
}[key],
|
|
2275
|
+
/* @__PURE__ */ jsx5(
|
|
2276
|
+
"input",
|
|
2277
|
+
{
|
|
2278
|
+
style: input,
|
|
2279
|
+
type: "number",
|
|
2280
|
+
min: key === "x" || key === "y" ? 0 : 1,
|
|
2281
|
+
max: 16384,
|
|
2282
|
+
step: 1,
|
|
2283
|
+
value: Number.isFinite(crop[key]) ? crop[key] : "",
|
|
2284
|
+
onChange: (event) => onChange({ ...value, crop: { ...crop, [key]: event.target.valueAsNumber } })
|
|
2285
|
+
}
|
|
2286
|
+
)
|
|
2287
|
+
] }, key))
|
|
2288
|
+
}
|
|
2289
|
+
)
|
|
2290
|
+
]
|
|
2291
|
+
}
|
|
2292
|
+
),
|
|
2293
|
+
/* @__PURE__ */ jsxs4("p", { style: { margin: "8px 0 0", color: "var(--squisq-recorder-muted)" }, children: [
|
|
2294
|
+
"Choose a screen, window or tab in the picker, then check the preview. Available choices depend on your browser or app.",
|
|
2295
|
+
value.crop && " Coordinates start at the selected surface\u2019s top-left, in captured pixels. Only this region is saved; audio is unchanged."
|
|
2296
|
+
] }),
|
|
2297
|
+
/* @__PURE__ */ jsx5(
|
|
2298
|
+
"button",
|
|
2299
|
+
{
|
|
2300
|
+
type: "button",
|
|
2301
|
+
style: { ...input, marginTop: 10, width: "auto", cursor: "pointer" },
|
|
2302
|
+
onClick: onChoose,
|
|
2303
|
+
children: hasPreview ? "Change screen or window" : "Choose screen or window"
|
|
2304
|
+
}
|
|
2305
|
+
)
|
|
2306
|
+
]
|
|
2307
|
+
}
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2062
2311
|
// src/modal/useModalDialog.ts
|
|
2063
2312
|
import { useModalDialog } from "@bendyline/squisq-react";
|
|
2064
2313
|
|
|
@@ -2085,7 +2334,7 @@ function narrationCaptureSummary(withCamera) {
|
|
|
2085
2334
|
}
|
|
2086
2335
|
|
|
2087
2336
|
// src/recorder/RecorderModal.tsx
|
|
2088
|
-
import { Fragment as Fragment3, jsx as
|
|
2337
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2089
2338
|
var overlayStyle = {
|
|
2090
2339
|
position: "fixed",
|
|
2091
2340
|
inset: 0,
|
|
@@ -2447,8 +2696,9 @@ function RecorderModal({
|
|
|
2447
2696
|
const [cameraPlaybackUrl, setCameraPlaybackUrl] = useState3(null);
|
|
2448
2697
|
const [playbackPositionMs, setPlaybackPositionMs] = useState3(0);
|
|
2449
2698
|
const [panelMode, setPanelMode] = useState3("none");
|
|
2450
|
-
const narrationOn = panelMode === "narration";
|
|
2451
|
-
const slidesOn = panelMode === "slides";
|
|
2699
|
+
const narrationOn = panelMode === "narration" || panelMode === "both";
|
|
2700
|
+
const slidesOn = panelMode === "slides" || panelMode === "both";
|
|
2701
|
+
const narrationRecording = narrationOn && Boolean(narration?.recording);
|
|
2452
2702
|
const expanded = isExpandedPanel(panelMode);
|
|
2453
2703
|
const [narrationRequesting, setNarrationRequesting] = useState3(false);
|
|
2454
2704
|
const [narrationPreview, setNarrationPreview] = useState3(null);
|
|
@@ -2494,6 +2744,7 @@ function RecorderModal({
|
|
|
2494
2744
|
audioConstraints,
|
|
2495
2745
|
videoConstraints: cameraConstraints,
|
|
2496
2746
|
screenVideoConstraints: screenConstraints,
|
|
2747
|
+
screenRegion: deviceSettings.screen.crop,
|
|
2497
2748
|
screenAudioConstraints,
|
|
2498
2749
|
mimeType: (source === "mic" ? encoding.audioMimeType : encoding.videoMimeType).trim() || void 0,
|
|
2499
2750
|
bitsPerSecond: recorderBitsPerSecond(encoding.bitsPerSecond),
|
|
@@ -2508,13 +2759,16 @@ function RecorderModal({
|
|
|
2508
2759
|
systemAudio: canIncludeSystemAudio ? includeSystemAudio : false
|
|
2509
2760
|
});
|
|
2510
2761
|
const filenameSeed = recordingFilenameSeed(source, recorder.stream, micOn || includeSystemAudio);
|
|
2511
|
-
const narrationAvailable = Boolean(narration
|
|
2762
|
+
const narrationAvailable = Boolean(narration?.doc);
|
|
2512
2763
|
const basenameRef = useRef2(basename);
|
|
2513
2764
|
basenameRef.current = basename;
|
|
2514
2765
|
const stage = useNarrationStage({
|
|
2515
2766
|
doc: narration?.doc ?? null,
|
|
2516
2767
|
maxRecordingBytes,
|
|
2517
2768
|
recording: narration?.recording ?? null,
|
|
2769
|
+
// A prompter-only stage borrows the take's audio. It never requests or
|
|
2770
|
+
// stops capture tracks; the recorder remains their sole owner.
|
|
2771
|
+
analysisStream: narration?.recording ? void 0 : narrationOn && recorder.state !== "stopped" ? isDual ? recorder.camera?.stream ?? null : recorder.stream : null,
|
|
2518
2772
|
getAudioBasename: () => basenameRef.current.trim() || void 0,
|
|
2519
2773
|
micConstraints: audioConstraints,
|
|
2520
2774
|
cameraConstraints,
|
|
@@ -2569,35 +2823,41 @@ function RecorderModal({
|
|
|
2569
2823
|
useEffect4(() => {
|
|
2570
2824
|
setAdvanceLog(EMPTY_ADVANCE_LOG);
|
|
2571
2825
|
setSlideIndex(0);
|
|
2572
|
-
}, [
|
|
2826
|
+
}, [slidesOn, slideDeckKey]);
|
|
2827
|
+
useEffect4(() => {
|
|
2828
|
+
if (!narrationOn || narrationRecording) return;
|
|
2829
|
+
const controller = stageRef.current.controller;
|
|
2830
|
+
if (recorder.state === "recording") controller.play();
|
|
2831
|
+
else controller.pause();
|
|
2832
|
+
}, [narrationOn, narrationRecording, recorder.state]);
|
|
2573
2833
|
const handleDeviceSettingsChange = useCallback4(
|
|
2574
2834
|
(next) => {
|
|
2575
2835
|
setDeviceSettings(next);
|
|
2576
|
-
if (
|
|
2836
|
+
if (narrationRecording) {
|
|
2577
2837
|
stageRef.current.controller.setPrefs({
|
|
2578
2838
|
micDeviceId: next.audio.deviceId || null
|
|
2579
2839
|
});
|
|
2580
2840
|
}
|
|
2581
2841
|
},
|
|
2582
|
-
[
|
|
2842
|
+
[narrationRecording]
|
|
2583
2843
|
);
|
|
2584
2844
|
const narrationMicDeviceId = stage.controller.prefs.micDeviceId ?? "";
|
|
2585
2845
|
useEffect4(() => {
|
|
2586
|
-
if (!
|
|
2846
|
+
if (!narrationRecording) return;
|
|
2587
2847
|
setDeviceSettings(
|
|
2588
2848
|
(current) => current.audio.deviceId === narrationMicDeviceId ? current : {
|
|
2589
2849
|
...current,
|
|
2590
2850
|
audio: { ...current.audio, deviceId: narrationMicDeviceId }
|
|
2591
2851
|
}
|
|
2592
2852
|
);
|
|
2593
|
-
}, [narrationMicDeviceId,
|
|
2853
|
+
}, [narrationMicDeviceId, narrationRecording]);
|
|
2594
2854
|
const prevNarrationOnRef = useRef2(narrationOn);
|
|
2595
2855
|
useEffect4(() => {
|
|
2596
2856
|
const was = prevNarrationOnRef.current;
|
|
2597
2857
|
prevNarrationOnRef.current = narrationOn;
|
|
2598
2858
|
if (was === narrationOn) return;
|
|
2599
2859
|
if (narrationOn) {
|
|
2600
|
-
recorder.cancel();
|
|
2860
|
+
if (narrationRecording) recorder.cancel();
|
|
2601
2861
|
} else {
|
|
2602
2862
|
const s = stageRef.current;
|
|
2603
2863
|
s.controller.pause();
|
|
@@ -2606,7 +2866,7 @@ function RecorderModal({
|
|
|
2606
2866
|
}
|
|
2607
2867
|
if (s.float.isOpen) s.float.close();
|
|
2608
2868
|
}
|
|
2609
|
-
}, [narrationOn, recorder]);
|
|
2869
|
+
}, [narrationOn, narrationRecording, recorder]);
|
|
2610
2870
|
useEffect4(() => {
|
|
2611
2871
|
if (!narrationOn) return;
|
|
2612
2872
|
const onKey = (event) => {
|
|
@@ -2618,7 +2878,7 @@ function RecorderModal({
|
|
|
2618
2878
|
return () => document.removeEventListener("keydown", onKey, true);
|
|
2619
2879
|
}, [narrationOn]);
|
|
2620
2880
|
const narrationMicLive = stage.controller.mic.status === "live";
|
|
2621
|
-
const narrationPreviewWanted =
|
|
2881
|
+
const narrationPreviewWanted = narrationRecording && stage.recorder.withCamera && narrationMicLive && stage.recorder.cameraStream === null && stage.recorder.state !== "processing" && stage.recorder.state !== "review" && stage.recorder.state !== "saving";
|
|
2622
2882
|
const narrationPreviewRef = useRef2(null);
|
|
2623
2883
|
useEffect4(() => {
|
|
2624
2884
|
const existing = narrationPreviewRef.current;
|
|
@@ -2667,20 +2927,20 @@ function RecorderModal({
|
|
|
2667
2927
|
const handleNarrationRecord = useCallback4(() => {
|
|
2668
2928
|
void stageRef.current.recorder.start();
|
|
2669
2929
|
}, []);
|
|
2670
|
-
const narrationCameraStream =
|
|
2671
|
-
const activePrimaryStream =
|
|
2672
|
-
const activeCameraStream =
|
|
2930
|
+
const narrationCameraStream = narrationRecording ? stage.recorder.cameraStream ?? narrationPreview : null;
|
|
2931
|
+
const activePrimaryStream = narrationRecording ? stage.controller.mic.stream : recorder.stream;
|
|
2932
|
+
const activeCameraStream = narrationRecording ? narrationCameraStream : recorder.camera?.stream ?? null;
|
|
2673
2933
|
const { devices: mediaDevices, refresh: refreshMediaDevices } = useMediaDevices();
|
|
2674
2934
|
useEffect4(() => {
|
|
2675
2935
|
if (activePrimaryStream || activeCameraStream) refreshMediaDevices();
|
|
2676
2936
|
}, [activeCameraStream, activePrimaryStream, refreshMediaDevices]);
|
|
2677
2937
|
useStreamPreview(
|
|
2678
2938
|
previewRef,
|
|
2679
|
-
|
|
2939
|
+
narrationRecording ? narrationCameraStream : recorder.state === "stopped" ? null : recorder.stream
|
|
2680
2940
|
);
|
|
2681
2941
|
useStreamPreview(
|
|
2682
2942
|
cameraPreviewRef,
|
|
2683
|
-
!
|
|
2943
|
+
!narrationRecording && isDual && recorder.state !== "stopped" ? recorder.camera?.stream ?? null : null
|
|
2684
2944
|
);
|
|
2685
2945
|
useEffect4(() => {
|
|
2686
2946
|
setPlaybackPositionMs(0);
|
|
@@ -2718,7 +2978,7 @@ function RecorderModal({
|
|
|
2718
2978
|
}
|
|
2719
2979
|
}, [captureKey, recorder]);
|
|
2720
2980
|
const handleClose = useCallback4(() => {
|
|
2721
|
-
if (!
|
|
2981
|
+
if (!narrationRecording) {
|
|
2722
2982
|
if (isSaving || recorder.state === "stopping" || recorder.state === "requesting") return;
|
|
2723
2983
|
if ((recorder.blob || recorder.camera?.blob || recorder.state === "recording") && !window.confirm(
|
|
2724
2984
|
"Discard this recording? Download or save it before closing to keep a copy."
|
|
@@ -2726,7 +2986,7 @@ function RecorderModal({
|
|
|
2726
2986
|
return;
|
|
2727
2987
|
}
|
|
2728
2988
|
const s = stageRef.current;
|
|
2729
|
-
if (closeNeedsConfirm(
|
|
2989
|
+
if (closeNeedsConfirm(narrationRecording, s.recorder.state, s.recorder.take !== null)) {
|
|
2730
2990
|
if (!window.confirm("Discard the current narration take?")) return;
|
|
2731
2991
|
s.handleDiscard();
|
|
2732
2992
|
}
|
|
@@ -2738,7 +2998,7 @@ function RecorderModal({
|
|
|
2738
2998
|
}
|
|
2739
2999
|
recorder.cancel();
|
|
2740
3000
|
onClose();
|
|
2741
|
-
}, [narrationOn, recorder, onClose, isSaving]);
|
|
3001
|
+
}, [narrationOn, narrationRecording, recorder, onClose, isSaving]);
|
|
2742
3002
|
useModalDialog({
|
|
2743
3003
|
rootRef: overlayRef,
|
|
2744
3004
|
dialogRef,
|
|
@@ -2948,7 +3208,8 @@ function RecorderModal({
|
|
|
2948
3208
|
dualSaveProgressRef.current = {};
|
|
2949
3209
|
setAdvanceLog(EMPTY_ADVANCE_LOG);
|
|
2950
3210
|
recorder.reset();
|
|
2951
|
-
|
|
3211
|
+
if (narrationOn) stageRef.current.controller.restart();
|
|
3212
|
+
}, [narrationOn, recorder]);
|
|
2952
3213
|
const handlePlaybackTimeUpdate = useCallback4(
|
|
2953
3214
|
(media) => {
|
|
2954
3215
|
const currentMs = Number.isFinite(media.currentTime) ? media.currentTime * 1e3 : 0;
|
|
@@ -2985,7 +3246,7 @@ function RecorderModal({
|
|
|
2985
3246
|
);
|
|
2986
3247
|
const timingDownloadFilename = `${downloadFilename}.timing.json`;
|
|
2987
3248
|
useEffect4(() => {
|
|
2988
|
-
if (
|
|
3249
|
+
if (narrationRecording || !canSave) {
|
|
2989
3250
|
setTimingDownloadUrl(null);
|
|
2990
3251
|
return;
|
|
2991
3252
|
}
|
|
@@ -3001,7 +3262,7 @@ function RecorderModal({
|
|
|
3001
3262
|
setTimingDownloadUrl(url);
|
|
3002
3263
|
return () => URL.revokeObjectURL(url);
|
|
3003
3264
|
}, [
|
|
3004
|
-
|
|
3265
|
+
narrationRecording,
|
|
3005
3266
|
canSave,
|
|
3006
3267
|
recorder.durationMs,
|
|
3007
3268
|
slidesOn,
|
|
@@ -3044,9 +3305,9 @@ function RecorderModal({
|
|
|
3044
3305
|
recorder.blob !== null || recorder.camera?.blob != null,
|
|
3045
3306
|
stage.recorder.state
|
|
3046
3307
|
);
|
|
3047
|
-
const deviceSettingsLocked =
|
|
3048
|
-
const microphoneEnabled =
|
|
3049
|
-
const cameraEnabled =
|
|
3308
|
+
const deviceSettingsLocked = narrationRecording ? !narrationRecorderIdle : togglesLocked;
|
|
3309
|
+
const microphoneEnabled = narrationRecording ? true : micOn;
|
|
3310
|
+
const cameraEnabled = narrationRecording ? stage.recorder.withCamera : cameraOn;
|
|
3050
3311
|
const narrationToggleFor = (key) => {
|
|
3051
3312
|
switch (key) {
|
|
3052
3313
|
case "mic":
|
|
@@ -3082,14 +3343,14 @@ function RecorderModal({
|
|
|
3082
3343
|
};
|
|
3083
3344
|
}
|
|
3084
3345
|
};
|
|
3085
|
-
return /* @__PURE__ */
|
|
3346
|
+
return /* @__PURE__ */ jsx6(
|
|
3086
3347
|
"div",
|
|
3087
3348
|
{
|
|
3088
3349
|
ref: overlayRef,
|
|
3089
3350
|
className: "squisq-editor-shell squisq-recorder-overlay",
|
|
3090
3351
|
"data-theme": colorScheme,
|
|
3091
3352
|
style: { ...overlayStyle, ...recorderThemeStyle(colorScheme) },
|
|
3092
|
-
children: /* @__PURE__ */
|
|
3353
|
+
children: /* @__PURE__ */ jsxs5(
|
|
3093
3354
|
"div",
|
|
3094
3355
|
{
|
|
3095
3356
|
ref: dialogRef,
|
|
@@ -3108,431 +3369,453 @@ function RecorderModal({
|
|
|
3108
3369
|
"aria-labelledby": headingId,
|
|
3109
3370
|
tabIndex: -1,
|
|
3110
3371
|
children: [
|
|
3111
|
-
/* @__PURE__ */
|
|
3112
|
-
/* @__PURE__ */
|
|
3113
|
-
/* @__PURE__ */
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
}
|
|
3146
|
-
),
|
|
3147
|
-
"Show narration mode"
|
|
3148
|
-
] }),
|
|
3149
|
-
slidesAvailable && /* @__PURE__ */ jsxs4("label", { style: checkboxRowStyle, children: [
|
|
3150
|
-
/* @__PURE__ */ jsx5(
|
|
3151
|
-
"input",
|
|
3152
|
-
{
|
|
3153
|
-
type: "checkbox",
|
|
3154
|
-
style: { accentColor: "var(--squisq-recorder-accent)" },
|
|
3155
|
-
checked: slidesOn,
|
|
3156
|
-
onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "slides", e.target.checked)),
|
|
3157
|
-
disabled: narrationToggleDisabled,
|
|
3158
|
-
title: narrationToggleDisabled ? "Save or discard this recording before switching modes" : void 0
|
|
3159
|
-
}
|
|
3160
|
-
),
|
|
3161
|
-
"Show slides mode"
|
|
3162
|
-
] }),
|
|
3163
|
-
/* @__PURE__ */ jsx5(
|
|
3164
|
-
RecorderDeviceQuickPicks,
|
|
3165
|
-
{
|
|
3166
|
-
devices: mediaDevices,
|
|
3167
|
-
microphoneEnabled,
|
|
3168
|
-
cameraEnabled,
|
|
3169
|
-
audioDeviceId: deviceSettings.audio.deviceId,
|
|
3170
|
-
cameraDeviceId: deviceSettings.camera.deviceId,
|
|
3171
|
-
onAudioDeviceChange: (deviceId) => handleDeviceSettingsChange({
|
|
3172
|
-
...deviceSettings,
|
|
3173
|
-
audio: { ...deviceSettings.audio, deviceId }
|
|
3174
|
-
}),
|
|
3175
|
-
onCameraDeviceChange: (deviceId) => handleDeviceSettingsChange({
|
|
3176
|
-
...deviceSettings,
|
|
3177
|
-
camera: { ...deviceSettings.camera, deviceId }
|
|
3178
|
-
}),
|
|
3179
|
-
disabled: deviceSettingsLocked
|
|
3180
|
-
}
|
|
3181
|
-
),
|
|
3182
|
-
/* @__PURE__ */ jsx5(
|
|
3183
|
-
RecorderDeviceSettingsPanel,
|
|
3184
|
-
{
|
|
3185
|
-
value: deviceSettings,
|
|
3186
|
-
onChange: handleDeviceSettingsChange,
|
|
3187
|
-
disabled: deviceSettingsLocked,
|
|
3188
|
-
microphoneEnabled,
|
|
3189
|
-
cameraEnabled,
|
|
3190
|
-
screenEnabled: narrationOn ? false : screenOn,
|
|
3191
|
-
systemAudioEnabled: !narrationOn && includeSystemAudio,
|
|
3192
|
-
separateAudioRecorder: narrationOn,
|
|
3193
|
-
primaryStream: activePrimaryStream,
|
|
3194
|
-
cameraStream: activeCameraStream
|
|
3195
|
-
}
|
|
3196
|
-
),
|
|
3197
|
-
!narrationOn && recorder.error && /* @__PURE__ */ jsx5("div", { style: errorStyle, children: recorder.error.message }),
|
|
3198
|
-
!narrationOn && saveError && /* @__PURE__ */ jsxs4("div", { role: "alert", style: errorStyle, children: [
|
|
3199
|
-
saveError,
|
|
3200
|
-
/* @__PURE__ */ jsx5("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
|
|
3201
|
-
] }),
|
|
3202
|
-
/* @__PURE__ */ jsxs4("p", { style: summaryStyle, children: [
|
|
3203
|
-
"Recording size:",
|
|
3204
|
-
" ",
|
|
3205
|
-
formatRecordingBytes(
|
|
3206
|
-
narrationOn ? stage.recorder.recordedBytes : recorder.recordedBytes
|
|
3207
|
-
),
|
|
3208
|
-
". Automatically stops at",
|
|
3209
|
-
" ",
|
|
3210
|
-
formatRecordingBytes(
|
|
3211
|
-
narrationOn ? stage.recorder.maxRecordingBytes : recorder.maxRecordingBytes
|
|
3212
|
-
),
|
|
3213
|
-
" ",
|
|
3214
|
-
"total. Final encoding may add more data."
|
|
3215
|
-
] }),
|
|
3216
|
-
!narrationOn && recorder.limitReached && /* @__PURE__ */ jsx5("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." }),
|
|
3217
|
-
narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx5("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
|
|
3218
|
-
narrationOn && narrationCameraStream && /* @__PURE__ */ jsx5("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx5(
|
|
3219
|
-
"video",
|
|
3220
|
-
{
|
|
3221
|
-
ref: previewRef,
|
|
3222
|
-
autoPlay: true,
|
|
3223
|
-
muted: true,
|
|
3224
|
-
playsInline: true,
|
|
3225
|
-
style: { width: "100%", height: "100%", objectFit: "contain" }
|
|
3226
|
-
}
|
|
3227
|
-
) }),
|
|
3228
|
-
narrationOn && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx5("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
|
|
3229
|
-
narrationOn && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx5("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx5("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
|
|
3230
|
-
narrationOn && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs4("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
|
|
3231
|
-
/* @__PURE__ */ jsx5(
|
|
3232
|
-
"div",
|
|
3233
|
-
{
|
|
3234
|
-
"aria-hidden": "true",
|
|
3235
|
-
style: {
|
|
3236
|
-
...meterLevelStyle,
|
|
3237
|
-
width: `${Math.round(Math.min(1, Math.max(0, stage.controller.micLevel)) * 100)}%`
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
|
-
),
|
|
3241
|
-
/* @__PURE__ */ jsx5("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." })
|
|
3242
|
-
] }),
|
|
3243
|
-
!narrationOn && !showPreview && /* @__PURE__ */ jsx5("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx5("span", { children: "Click Start Preview to start a recording." }) }),
|
|
3244
|
-
!narrationOn && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
|
|
3245
|
-
/* @__PURE__ */ jsx5(
|
|
3246
|
-
"video",
|
|
3247
|
-
{
|
|
3248
|
-
ref: previewRef,
|
|
3249
|
-
autoPlay: true,
|
|
3250
|
-
muted: true,
|
|
3251
|
-
playsInline: true,
|
|
3252
|
-
style: { width: "100%", height: "100%", objectFit: "contain" }
|
|
3253
|
-
}
|
|
3254
|
-
),
|
|
3255
|
-
isDual && /* @__PURE__ */ jsx5(
|
|
3256
|
-
"video",
|
|
3257
|
-
{
|
|
3258
|
-
ref: cameraPreviewRef,
|
|
3259
|
-
autoPlay: true,
|
|
3260
|
-
muted: true,
|
|
3261
|
-
playsInline: true,
|
|
3262
|
-
"aria-label": "Camera preview",
|
|
3263
|
-
style: {
|
|
3264
|
-
position: "absolute",
|
|
3265
|
-
right: "3%",
|
|
3266
|
-
bottom: "6%",
|
|
3267
|
-
width: "22%",
|
|
3268
|
-
aspectRatio: "16 / 9",
|
|
3269
|
-
objectFit: "cover",
|
|
3270
|
-
background: "#000",
|
|
3271
|
-
border: "1px solid rgba(255,255,255,0.6)"
|
|
3272
|
-
}
|
|
3273
|
-
}
|
|
3274
|
-
)
|
|
3275
|
-
] }),
|
|
3276
|
-
!narrationOn && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx5("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
3277
|
-
"\u25CF Recording ",
|
|
3278
|
-
formatDurationMs(recorder.durationMs)
|
|
3279
|
-
] }) : /* @__PURE__ */ jsx5(Fragment3, { children: "Microphone ready" }) }),
|
|
3280
|
-
!narrationOn && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
|
|
3281
|
-
/* @__PURE__ */ jsx5(
|
|
3282
|
-
"video",
|
|
3283
|
-
{
|
|
3284
|
-
src: playbackUrl,
|
|
3285
|
-
controls: true,
|
|
3286
|
-
playsInline: true,
|
|
3287
|
-
onTimeUpdate: (event) => handlePlaybackTimeUpdate(event.currentTarget),
|
|
3288
|
-
onSeeking: (event) => handlePlaybackTimeUpdate(event.currentTarget),
|
|
3289
|
-
onEnded: () => setPlaybackPositionMs(recorder.durationMs),
|
|
3290
|
-
style: { width: "100%", height: "100%", objectFit: "contain" }
|
|
3291
|
-
}
|
|
3292
|
-
),
|
|
3293
|
-
/* @__PURE__ */ jsxs4(
|
|
3294
|
-
"div",
|
|
3295
|
-
{
|
|
3296
|
-
role: "timer",
|
|
3297
|
-
"aria-label": `Playback time: ${formatDurationMs(playbackPositionMs)} of ${formatDurationMs(recorder.durationMs)}`,
|
|
3298
|
-
style: playbackTimeStyle,
|
|
3299
|
-
children: [
|
|
3300
|
-
formatDurationMs(playbackPositionMs),
|
|
3301
|
-
" / ",
|
|
3302
|
-
formatDurationMs(recorder.durationMs)
|
|
3303
|
-
]
|
|
3304
|
-
}
|
|
3305
|
-
)
|
|
3306
|
-
] }),
|
|
3307
|
-
!narrationOn && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs4("div", { style: { marginBottom: 12 }, children: [
|
|
3308
|
-
/* @__PURE__ */ jsx5("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
|
|
3309
|
-
/* @__PURE__ */ jsx5(
|
|
3310
|
-
"video",
|
|
3311
|
-
{
|
|
3312
|
-
src: cameraPlaybackUrl,
|
|
3313
|
-
controls: true,
|
|
3314
|
-
playsInline: true,
|
|
3315
|
-
"aria-label": "Camera recording",
|
|
3316
|
-
style: {
|
|
3317
|
-
width: "48%",
|
|
3318
|
-
display: "block",
|
|
3319
|
-
marginLeft: "auto",
|
|
3320
|
-
background: "#000"
|
|
3321
|
-
}
|
|
3322
|
-
}
|
|
3323
|
-
)
|
|
3324
|
-
] }),
|
|
3325
|
-
!narrationOn && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: { marginBottom: 12 }, children: [
|
|
3326
|
-
/* @__PURE__ */ jsxs4("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
|
|
3327
|
-
"\u2713 Recorded ",
|
|
3328
|
-
formatDurationMs(recorder.durationMs)
|
|
3329
|
-
] }),
|
|
3330
|
-
/* @__PURE__ */ jsx5("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
|
|
3331
|
-
] }),
|
|
3332
|
-
!narrationOn && canSave && playbackUrl && /* @__PURE__ */ jsxs4(
|
|
3333
|
-
"div",
|
|
3334
|
-
{
|
|
3335
|
-
style: {
|
|
3336
|
-
...buttonRowStyle,
|
|
3337
|
-
flexWrap: "wrap",
|
|
3338
|
-
justifyContent: "flex-start",
|
|
3339
|
-
marginBottom: 12
|
|
3340
|
-
},
|
|
3341
|
-
children: [
|
|
3342
|
-
/* @__PURE__ */ jsx5("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
|
|
3343
|
-
isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx5(
|
|
3344
|
-
"a",
|
|
3372
|
+
/* @__PURE__ */ jsx6("h2", { id: headingId, style: titleStyle, children: title }),
|
|
3373
|
+
/* @__PURE__ */ jsxs5("div", { style: expanded ? bodyRowStyle : void 0, children: [
|
|
3374
|
+
/* @__PURE__ */ jsxs5(
|
|
3375
|
+
"div",
|
|
3376
|
+
{
|
|
3377
|
+
style: expanded ? {
|
|
3378
|
+
...leftColStyle,
|
|
3379
|
+
...panelMode === "both" ? { flex: "0 0 min(280px, 24vw)" } : {}
|
|
3380
|
+
} : void 0,
|
|
3381
|
+
children: [
|
|
3382
|
+
/* @__PURE__ */ jsx6("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
3383
|
+
groupIndex > 0 && /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: groupDividerStyle }),
|
|
3384
|
+
/* @__PURE__ */ jsx6("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
|
|
3385
|
+
if (t.key === "systemAudio" && !canIncludeSystemAudio) return null;
|
|
3386
|
+
const props = narrationRecording ? narrationToggleFor(t.key) : simpleToggleProps(t.key);
|
|
3387
|
+
return /* @__PURE__ */ jsx6(
|
|
3388
|
+
"button",
|
|
3389
|
+
{
|
|
3390
|
+
type: "button",
|
|
3391
|
+
"aria-pressed": props.active,
|
|
3392
|
+
style: props.active ? toggleActive : toggleBase,
|
|
3393
|
+
onClick: props.onClick,
|
|
3394
|
+
disabled: props.disabled,
|
|
3395
|
+
title: props.title,
|
|
3396
|
+
children: t.label
|
|
3397
|
+
},
|
|
3398
|
+
t.key
|
|
3399
|
+
);
|
|
3400
|
+
}) })
|
|
3401
|
+
] }, group.label)) }),
|
|
3402
|
+
/* @__PURE__ */ jsx6("p", { style: summaryStyle, children: narrationRecording ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
|
|
3403
|
+
narrationAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
|
|
3404
|
+
/* @__PURE__ */ jsx6(
|
|
3405
|
+
"input",
|
|
3345
3406
|
{
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
),
|
|
3353
|
-
style: btnSecondary,
|
|
3354
|
-
children: "Download camera recording"
|
|
3407
|
+
type: "checkbox",
|
|
3408
|
+
style: { accentColor: "var(--squisq-recorder-accent)" },
|
|
3409
|
+
checked: narrationOn,
|
|
3410
|
+
onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "narration", e.target.checked)),
|
|
3411
|
+
disabled: narrationToggleDisabled,
|
|
3412
|
+
title: narrationToggleDisabled ? narrationRecording ? "Finish or discard the narration take first" : "Save or discard this recording before switching modes" : void 0
|
|
3355
3413
|
}
|
|
3356
3414
|
),
|
|
3357
|
-
|
|
3358
|
-
|
|
3415
|
+
"Show narration mode"
|
|
3416
|
+
] }),
|
|
3417
|
+
slidesAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
|
|
3418
|
+
/* @__PURE__ */ jsx6(
|
|
3419
|
+
"input",
|
|
3359
3420
|
{
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3421
|
+
type: "checkbox",
|
|
3422
|
+
style: { accentColor: "var(--squisq-recorder-accent)" },
|
|
3423
|
+
checked: slidesOn,
|
|
3424
|
+
onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "slides", e.target.checked)),
|
|
3425
|
+
disabled: narrationToggleDisabled,
|
|
3426
|
+
title: narrationToggleDisabled ? "Save or discard this recording before switching modes" : void 0
|
|
3364
3427
|
}
|
|
3365
|
-
)
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
/* @__PURE__ */ jsx5("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
|
|
3371
|
-
/* @__PURE__ */ jsx5(
|
|
3372
|
-
"textarea",
|
|
3373
|
-
{
|
|
3374
|
-
id: "recorder-source-text",
|
|
3375
|
-
style: textareaStyle,
|
|
3376
|
-
placeholder: "Type the text you're going to read aloud.",
|
|
3377
|
-
value: sourceText,
|
|
3378
|
-
onChange: (e) => setSourceText(e.target.value),
|
|
3379
|
-
disabled: recorder.state === "recording"
|
|
3380
|
-
}
|
|
3381
|
-
)
|
|
3382
|
-
] }),
|
|
3383
|
-
/* @__PURE__ */ jsx5("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
|
|
3384
|
-
/* @__PURE__ */ jsx5(
|
|
3385
|
-
"input",
|
|
3386
|
-
{
|
|
3387
|
-
id: "recorder-basename",
|
|
3388
|
-
type: "text",
|
|
3389
|
-
style: inputStyle2,
|
|
3390
|
-
placeholder: narrationOn ? "narration" : isDual ? "screen + camera" : filenameSeed,
|
|
3391
|
-
value: basename,
|
|
3392
|
-
onChange: (e) => setBasename(e.target.value),
|
|
3393
|
-
disabled: narrationOn ? !narrationRecorderIdle : recorder.state === "recording"
|
|
3394
|
-
}
|
|
3395
|
-
),
|
|
3396
|
-
!narrationOn && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: recordingStatusStyle, children: [
|
|
3397
|
-
"\u25CF Recording ",
|
|
3398
|
-
formatDurationMs(recorder.durationMs)
|
|
3399
|
-
] }),
|
|
3400
|
-
showTimingCheckbox && /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
3401
|
-
/* @__PURE__ */ jsxs4("label", { style: checkboxRowStyle, children: [
|
|
3402
|
-
/* @__PURE__ */ jsx5(
|
|
3403
|
-
"input",
|
|
3428
|
+
),
|
|
3429
|
+
"Show slides mode"
|
|
3430
|
+
] }),
|
|
3431
|
+
/* @__PURE__ */ jsx6(
|
|
3432
|
+
RecorderDeviceQuickPicks,
|
|
3404
3433
|
{
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3434
|
+
devices: mediaDevices,
|
|
3435
|
+
microphoneEnabled,
|
|
3436
|
+
cameraEnabled,
|
|
3437
|
+
audioDeviceId: deviceSettings.audio.deviceId,
|
|
3438
|
+
cameraDeviceId: deviceSettings.camera.deviceId,
|
|
3439
|
+
onAudioDeviceChange: (deviceId) => handleDeviceSettingsChange({
|
|
3440
|
+
...deviceSettings,
|
|
3441
|
+
audio: { ...deviceSettings.audio, deviceId }
|
|
3442
|
+
}),
|
|
3443
|
+
onCameraDeviceChange: (deviceId) => handleDeviceSettingsChange({
|
|
3444
|
+
...deviceSettings,
|
|
3445
|
+
camera: { ...deviceSettings.camera, deviceId }
|
|
3446
|
+
}),
|
|
3447
|
+
disabled: deviceSettingsLocked
|
|
3410
3448
|
}
|
|
3411
3449
|
),
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
narrationOn && /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
3428
|
-
narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx5(
|
|
3429
|
-
"button",
|
|
3450
|
+
!narrationRecording && screenOn && /* @__PURE__ */ jsx6(
|
|
3451
|
+
RecorderScreenSettings,
|
|
3452
|
+
{
|
|
3453
|
+
value: deviceSettings.screen,
|
|
3454
|
+
onChange: (screen) => handleDeviceSettingsChange({ ...deviceSettings, screen }),
|
|
3455
|
+
disabled: deviceSettingsLocked,
|
|
3456
|
+
hasPreview: recorder.state === "ready",
|
|
3457
|
+
onChoose: () => {
|
|
3458
|
+
recorder.cancel();
|
|
3459
|
+
void handleRequest();
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
),
|
|
3463
|
+
/* @__PURE__ */ jsx6(
|
|
3464
|
+
RecorderDeviceSettingsPanel,
|
|
3430
3465
|
{
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3466
|
+
value: deviceSettings,
|
|
3467
|
+
onChange: handleDeviceSettingsChange,
|
|
3468
|
+
disabled: deviceSettingsLocked,
|
|
3469
|
+
microphoneEnabled,
|
|
3470
|
+
cameraEnabled,
|
|
3471
|
+
screenEnabled: narrationRecording ? false : screenOn,
|
|
3472
|
+
systemAudioEnabled: !narrationRecording && includeSystemAudio,
|
|
3473
|
+
separateAudioRecorder: narrationRecording,
|
|
3474
|
+
primaryStream: activePrimaryStream,
|
|
3475
|
+
cameraStream: activeCameraStream
|
|
3436
3476
|
}
|
|
3437
3477
|
),
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3478
|
+
!narrationRecording && recorder.error && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: recorder.error.message }),
|
|
3479
|
+
!narrationRecording && saveError && /* @__PURE__ */ jsxs5("div", { role: "alert", style: errorStyle, children: [
|
|
3480
|
+
saveError,
|
|
3481
|
+
/* @__PURE__ */ jsx6("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
|
|
3482
|
+
] }),
|
|
3483
|
+
/* @__PURE__ */ jsxs5("p", { style: summaryStyle, children: [
|
|
3484
|
+
"Recording size:",
|
|
3485
|
+
" ",
|
|
3486
|
+
formatRecordingBytes(
|
|
3487
|
+
narrationRecording ? stage.recorder.recordedBytes : recorder.recordedBytes
|
|
3488
|
+
),
|
|
3489
|
+
". Automatically stops at",
|
|
3490
|
+
" ",
|
|
3491
|
+
formatRecordingBytes(
|
|
3492
|
+
narrationRecording ? stage.recorder.maxRecordingBytes : recorder.maxRecordingBytes
|
|
3493
|
+
),
|
|
3494
|
+
" ",
|
|
3495
|
+
"total. Final encoding may add more data."
|
|
3496
|
+
] }),
|
|
3497
|
+
!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." }),
|
|
3498
|
+
narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
|
|
3499
|
+
narrationRecording && narrationCameraStream && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6(
|
|
3500
|
+
"video",
|
|
3501
|
+
{
|
|
3502
|
+
ref: previewRef,
|
|
3503
|
+
autoPlay: true,
|
|
3504
|
+
muted: true,
|
|
3505
|
+
playsInline: true,
|
|
3506
|
+
style: { width: "100%", height: "100%", objectFit: "contain" }
|
|
3507
|
+
}
|
|
3508
|
+
) }),
|
|
3509
|
+
narrationRecording && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
|
|
3510
|
+
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." }) }),
|
|
3511
|
+
narrationRecording && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
|
|
3512
|
+
/* @__PURE__ */ jsx6(
|
|
3513
|
+
"div",
|
|
3441
3514
|
{
|
|
3442
|
-
className: "squisq-recorder-record-dot",
|
|
3443
|
-
style: recordDotFrameStyle,
|
|
3444
3515
|
"aria-hidden": "true",
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
{
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3516
|
+
style: {
|
|
3517
|
+
...meterLevelStyle,
|
|
3518
|
+
width: `${Math.round(Math.min(1, Math.max(0, stage.controller.micLevel)) * 100)}%`
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
),
|
|
3522
|
+
/* @__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." })
|
|
3523
|
+
] }),
|
|
3524
|
+
!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." }) }),
|
|
3525
|
+
!narrationRecording && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
|
|
3526
|
+
/* @__PURE__ */ jsx6(
|
|
3527
|
+
"video",
|
|
3528
|
+
{
|
|
3529
|
+
ref: previewRef,
|
|
3530
|
+
autoPlay: true,
|
|
3531
|
+
muted: true,
|
|
3532
|
+
playsInline: true,
|
|
3533
|
+
style: { width: "100%", height: "100%", objectFit: "contain" }
|
|
3452
3534
|
}
|
|
3453
3535
|
),
|
|
3454
|
-
|
|
3536
|
+
isDual && /* @__PURE__ */ jsx6(
|
|
3537
|
+
"video",
|
|
3538
|
+
{
|
|
3539
|
+
ref: cameraPreviewRef,
|
|
3540
|
+
autoPlay: true,
|
|
3541
|
+
muted: true,
|
|
3542
|
+
playsInline: true,
|
|
3543
|
+
"aria-label": "Camera preview",
|
|
3544
|
+
style: {
|
|
3545
|
+
position: "absolute",
|
|
3546
|
+
right: "3%",
|
|
3547
|
+
bottom: "6%",
|
|
3548
|
+
width: "22%",
|
|
3549
|
+
aspectRatio: "16 / 9",
|
|
3550
|
+
objectFit: "cover",
|
|
3551
|
+
background: "#000",
|
|
3552
|
+
border: "1px solid rgba(255,255,255,0.6)"
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
)
|
|
3455
3556
|
] }),
|
|
3456
|
-
|
|
3457
|
-
"
|
|
3557
|
+
!narrationRecording && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3558
|
+
"\u25CF Recording ",
|
|
3559
|
+
formatDurationMs(recorder.durationMs)
|
|
3560
|
+
] }) : /* @__PURE__ */ jsx6(Fragment3, { children: "Microphone ready" }) }),
|
|
3561
|
+
!narrationRecording && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
|
|
3562
|
+
/* @__PURE__ */ jsx6(
|
|
3563
|
+
"video",
|
|
3564
|
+
{
|
|
3565
|
+
src: playbackUrl,
|
|
3566
|
+
controls: true,
|
|
3567
|
+
playsInline: true,
|
|
3568
|
+
onTimeUpdate: (event) => handlePlaybackTimeUpdate(event.currentTarget),
|
|
3569
|
+
onSeeking: (event) => handlePlaybackTimeUpdate(event.currentTarget),
|
|
3570
|
+
onEnded: () => setPlaybackPositionMs(recorder.durationMs),
|
|
3571
|
+
style: { width: "100%", height: "100%", objectFit: "contain" }
|
|
3572
|
+
}
|
|
3573
|
+
),
|
|
3574
|
+
/* @__PURE__ */ jsxs5(
|
|
3575
|
+
"div",
|
|
3576
|
+
{
|
|
3577
|
+
role: "timer",
|
|
3578
|
+
"aria-label": `Playback time: ${formatDurationMs(playbackPositionMs)} of ${formatDurationMs(recorder.durationMs)}`,
|
|
3579
|
+
style: playbackTimeStyle,
|
|
3580
|
+
children: [
|
|
3581
|
+
formatDurationMs(playbackPositionMs),
|
|
3582
|
+
" / ",
|
|
3583
|
+
formatDurationMs(recorder.durationMs)
|
|
3584
|
+
]
|
|
3585
|
+
}
|
|
3586
|
+
)
|
|
3587
|
+
] }),
|
|
3588
|
+
!narrationRecording && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
|
|
3589
|
+
/* @__PURE__ */ jsx6("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
|
|
3590
|
+
/* @__PURE__ */ jsx6(
|
|
3591
|
+
"video",
|
|
3592
|
+
{
|
|
3593
|
+
src: cameraPlaybackUrl,
|
|
3594
|
+
controls: true,
|
|
3595
|
+
playsInline: true,
|
|
3596
|
+
"aria-label": "Camera recording",
|
|
3597
|
+
style: {
|
|
3598
|
+
width: "48%",
|
|
3599
|
+
display: "block",
|
|
3600
|
+
marginLeft: "auto",
|
|
3601
|
+
background: "#000"
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
)
|
|
3605
|
+
] }),
|
|
3606
|
+
!narrationRecording && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
|
|
3607
|
+
/* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
|
|
3608
|
+
"\u2713 Recorded ",
|
|
3609
|
+
formatDurationMs(recorder.durationMs)
|
|
3610
|
+
] }),
|
|
3611
|
+
/* @__PURE__ */ jsx6("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
|
|
3612
|
+
] }),
|
|
3613
|
+
!narrationRecording && canSave && playbackUrl && /* @__PURE__ */ jsxs5(
|
|
3614
|
+
"div",
|
|
3458
3615
|
{
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3616
|
+
style: {
|
|
3617
|
+
...buttonRowStyle,
|
|
3618
|
+
flexWrap: "wrap",
|
|
3619
|
+
justifyContent: "flex-start",
|
|
3620
|
+
marginBottom: 12
|
|
3621
|
+
},
|
|
3622
|
+
children: [
|
|
3623
|
+
/* @__PURE__ */ jsx6("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
|
|
3624
|
+
isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx6(
|
|
3625
|
+
"a",
|
|
3626
|
+
{
|
|
3627
|
+
href: cameraPlaybackUrl,
|
|
3628
|
+
download: buildFilename(
|
|
3629
|
+
"video",
|
|
3630
|
+
recorder.camera?.extension ?? ".webm",
|
|
3631
|
+
basename.trim() ? `${basename.trim()}-camera` : void 0,
|
|
3632
|
+
micOn ? "camera-audio" : "camera"
|
|
3633
|
+
),
|
|
3634
|
+
style: btnSecondary,
|
|
3635
|
+
children: "Download camera recording"
|
|
3636
|
+
}
|
|
3637
|
+
),
|
|
3638
|
+
timingDownloadUrl && /* @__PURE__ */ jsx6(
|
|
3639
|
+
"a",
|
|
3640
|
+
{
|
|
3641
|
+
href: timingDownloadUrl,
|
|
3642
|
+
download: timingDownloadFilename,
|
|
3643
|
+
style: btnSecondary,
|
|
3644
|
+
children: "Download recording timings"
|
|
3645
|
+
}
|
|
3646
|
+
)
|
|
3647
|
+
]
|
|
3463
3648
|
}
|
|
3464
3649
|
),
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
/* @__PURE__ */ jsx5(
|
|
3470
|
-
"button",
|
|
3650
|
+
!narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3651
|
+
/* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
|
|
3652
|
+
/* @__PURE__ */ jsx6(
|
|
3653
|
+
"textarea",
|
|
3471
3654
|
{
|
|
3472
|
-
|
|
3473
|
-
style:
|
|
3474
|
-
|
|
3475
|
-
|
|
3655
|
+
id: "recorder-source-text",
|
|
3656
|
+
style: textareaStyle,
|
|
3657
|
+
placeholder: "Type the text you're going to read aloud.",
|
|
3658
|
+
value: sourceText,
|
|
3659
|
+
onChange: (e) => setSourceText(e.target.value),
|
|
3660
|
+
disabled: recorder.state === "recording"
|
|
3476
3661
|
}
|
|
3477
3662
|
)
|
|
3478
|
-
] })
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
"button",
|
|
3663
|
+
] }),
|
|
3664
|
+
/* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
|
|
3665
|
+
/* @__PURE__ */ jsx6(
|
|
3666
|
+
"input",
|
|
3483
3667
|
{
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3668
|
+
id: "recorder-basename",
|
|
3669
|
+
type: "text",
|
|
3670
|
+
style: inputStyle2,
|
|
3671
|
+
placeholder: narrationRecording ? "narration" : isDual ? "screen + camera" : filenameSeed,
|
|
3672
|
+
value: basename,
|
|
3673
|
+
onChange: (e) => setBasename(e.target.value),
|
|
3674
|
+
disabled: narrationRecording ? !narrationRecorderIdle : recorder.state === "recording"
|
|
3489
3675
|
}
|
|
3490
3676
|
),
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3677
|
+
!narrationRecording && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: recordingStatusStyle, children: [
|
|
3678
|
+
"\u25CF Recording ",
|
|
3679
|
+
formatDurationMs(recorder.durationMs)
|
|
3680
|
+
] }),
|
|
3681
|
+
showTimingCheckbox && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3682
|
+
/* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
|
|
3683
|
+
/* @__PURE__ */ jsx6(
|
|
3684
|
+
"input",
|
|
3685
|
+
{
|
|
3686
|
+
type: "checkbox",
|
|
3687
|
+
style: { accentColor: "var(--squisq-recorder-accent)" },
|
|
3688
|
+
checked: applySlideTimings,
|
|
3689
|
+
onChange: (e) => setApplySlideTimings(e.target.checked),
|
|
3690
|
+
disabled: isSaving
|
|
3691
|
+
}
|
|
3692
|
+
),
|
|
3693
|
+
SLIDE_TIMING_CHECKBOX_LABEL
|
|
3694
|
+
] }),
|
|
3695
|
+
applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: slideCoverageWarning })
|
|
3508
3696
|
] }),
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
/* @__PURE__ */ jsx5(
|
|
3697
|
+
/* @__PURE__ */ jsxs5("div", { style: buttonRowStyle, children: [
|
|
3698
|
+
/* @__PURE__ */ jsx6(
|
|
3512
3699
|
"button",
|
|
3513
3700
|
{
|
|
3514
3701
|
type: "button",
|
|
3515
3702
|
style: btnSecondary,
|
|
3516
|
-
onClick:
|
|
3517
|
-
disabled: isBusy,
|
|
3518
|
-
children: "
|
|
3703
|
+
onClick: handleClose,
|
|
3704
|
+
disabled: !narrationRecording && isBusy,
|
|
3705
|
+
children: "Close"
|
|
3519
3706
|
}
|
|
3520
3707
|
),
|
|
3521
|
-
/* @__PURE__ */
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3708
|
+
narrationRecording && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3709
|
+
narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx6(
|
|
3710
|
+
"button",
|
|
3711
|
+
{
|
|
3712
|
+
type: "button",
|
|
3713
|
+
style: btnPrimary,
|
|
3714
|
+
onClick: () => void handleNarrationPreview(),
|
|
3715
|
+
disabled: narrationRequesting,
|
|
3716
|
+
children: narrationRequesting ? "Requesting\u2026" : "Start preview"
|
|
3717
|
+
}
|
|
3718
|
+
),
|
|
3719
|
+
narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
|
|
3720
|
+
/* @__PURE__ */ jsx6(
|
|
3721
|
+
"span",
|
|
3722
|
+
{
|
|
3723
|
+
className: "squisq-recorder-record-dot",
|
|
3724
|
+
style: recordDotFrameStyle,
|
|
3725
|
+
"aria-hidden": "true",
|
|
3726
|
+
children: /* @__PURE__ */ jsx6(
|
|
3727
|
+
"span",
|
|
3728
|
+
{
|
|
3729
|
+
className: "squisq-recorder-record-dot-center",
|
|
3730
|
+
style: recordDotStyle
|
|
3731
|
+
}
|
|
3732
|
+
)
|
|
3733
|
+
}
|
|
3734
|
+
),
|
|
3735
|
+
"Record"
|
|
3736
|
+
] }),
|
|
3737
|
+
(stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx6(
|
|
3738
|
+
"button",
|
|
3739
|
+
{
|
|
3740
|
+
type: "button",
|
|
3741
|
+
style: btnDanger,
|
|
3742
|
+
onClick: () => void stage.recorder.stop(),
|
|
3743
|
+
children: "Stop"
|
|
3744
|
+
}
|
|
3745
|
+
),
|
|
3746
|
+
stage.recorder.state === "processing" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
|
|
3747
|
+
stage.recorder.state === "saving" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
|
|
3748
|
+
stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3749
|
+
/* @__PURE__ */ jsx6("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
|
|
3750
|
+
/* @__PURE__ */ jsx6(
|
|
3751
|
+
"button",
|
|
3752
|
+
{
|
|
3753
|
+
type: "button",
|
|
3754
|
+
style: btnPrimary,
|
|
3755
|
+
onClick: () => void stage.handleSave(),
|
|
3756
|
+
children: "Save to document"
|
|
3757
|
+
}
|
|
3758
|
+
)
|
|
3759
|
+
] })
|
|
3760
|
+
] }),
|
|
3761
|
+
!narrationRecording && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3762
|
+
(recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx6(
|
|
3763
|
+
"button",
|
|
3764
|
+
{
|
|
3765
|
+
type: "button",
|
|
3766
|
+
style: btnPrimary,
|
|
3767
|
+
onClick: handleRequest,
|
|
3768
|
+
disabled: isBusy || !canCapture,
|
|
3769
|
+
children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
|
|
3770
|
+
}
|
|
3771
|
+
),
|
|
3772
|
+
canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
|
|
3773
|
+
/* @__PURE__ */ jsx6(
|
|
3774
|
+
"span",
|
|
3775
|
+
{
|
|
3776
|
+
className: "squisq-recorder-record-dot",
|
|
3777
|
+
style: recordDotFrameStyle,
|
|
3778
|
+
"aria-hidden": "true",
|
|
3779
|
+
children: /* @__PURE__ */ jsx6(
|
|
3780
|
+
"span",
|
|
3781
|
+
{
|
|
3782
|
+
className: "squisq-recorder-record-dot-center",
|
|
3783
|
+
style: recordDotStyle
|
|
3784
|
+
}
|
|
3785
|
+
)
|
|
3786
|
+
}
|
|
3787
|
+
),
|
|
3788
|
+
"Record"
|
|
3789
|
+
] }),
|
|
3790
|
+
canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
|
|
3791
|
+
canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3792
|
+
/* @__PURE__ */ jsx6(
|
|
3793
|
+
"button",
|
|
3794
|
+
{
|
|
3795
|
+
type: "button",
|
|
3796
|
+
style: btnSecondary,
|
|
3797
|
+
onClick: handleDiscard,
|
|
3798
|
+
disabled: isBusy,
|
|
3799
|
+
children: "Discard & re-record"
|
|
3800
|
+
}
|
|
3801
|
+
),
|
|
3802
|
+
/* @__PURE__ */ jsx6(
|
|
3803
|
+
"button",
|
|
3804
|
+
{
|
|
3805
|
+
type: "button",
|
|
3806
|
+
style: btnPrimary,
|
|
3807
|
+
onClick: handleSave,
|
|
3808
|
+
disabled: isBusy,
|
|
3809
|
+
children: isSaving ? "Saving\u2026" : "Save to document"
|
|
3810
|
+
}
|
|
3811
|
+
)
|
|
3812
|
+
] })
|
|
3813
|
+
] })
|
|
3531
3814
|
] })
|
|
3532
|
-
]
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
narrationOn && narration && /* @__PURE__ */
|
|
3815
|
+
]
|
|
3816
|
+
}
|
|
3817
|
+
),
|
|
3818
|
+
narrationOn && narration && /* @__PURE__ */ jsx6("div", { style: { ...rightColStyle, flex: "1 1 0" }, children: /* @__PURE__ */ jsx6(
|
|
3536
3819
|
NarrationStage,
|
|
3537
3820
|
{
|
|
3538
3821
|
stage,
|
|
@@ -3544,7 +3827,7 @@ function RecorderModal({
|
|
|
3544
3827
|
showReviewActions: false
|
|
3545
3828
|
}
|
|
3546
3829
|
) }),
|
|
3547
|
-
slidesOn && /* @__PURE__ */
|
|
3830
|
+
slidesOn && /* @__PURE__ */ jsx6("div", { style: { ...rightColStyle, flex: "1 1 0" }, children: /* @__PURE__ */ jsx6(
|
|
3548
3831
|
RecorderSlidesPanel,
|
|
3549
3832
|
{
|
|
3550
3833
|
slides: slideDeck,
|
|
@@ -3553,7 +3836,7 @@ function RecorderModal({
|
|
|
3553
3836
|
viewport: slidesViewport,
|
|
3554
3837
|
basePath: slides?.basePath,
|
|
3555
3838
|
mediaProvider: slides?.mediaProvider ?? mediaProvider,
|
|
3556
|
-
recording: recorder.state === "recording",
|
|
3839
|
+
recording: narrationRecording ? stage.recorder.state === "recording" : recorder.state === "recording",
|
|
3557
3840
|
shownBlockIds
|
|
3558
3841
|
}
|
|
3559
3842
|
) })
|
|
@@ -3568,7 +3851,7 @@ function RecorderModal({
|
|
|
3568
3851
|
// src/recorder/RecorderPanel.tsx
|
|
3569
3852
|
import { useCallback as useCallback5, useState as useState4 } from "react";
|
|
3570
3853
|
import { createPortal } from "react-dom";
|
|
3571
|
-
import { Fragment as Fragment4, jsx as
|
|
3854
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3572
3855
|
function RecorderPanel({
|
|
3573
3856
|
mediaProvider,
|
|
3574
3857
|
container = null,
|
|
@@ -3594,8 +3877,8 @@ function RecorderPanel({
|
|
|
3594
3877
|
[controlledOpen, onOpenChange]
|
|
3595
3878
|
);
|
|
3596
3879
|
const handleClose = useCallback5(() => setOpen(false), [setOpen]);
|
|
3597
|
-
return /* @__PURE__ */
|
|
3598
|
-
showTrigger && /* @__PURE__ */
|
|
3880
|
+
return /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3881
|
+
showTrigger && /* @__PURE__ */ jsx7(
|
|
3599
3882
|
"button",
|
|
3600
3883
|
{
|
|
3601
3884
|
type: "button",
|
|
@@ -3604,11 +3887,11 @@ function RecorderPanel({
|
|
|
3604
3887
|
"aria-label": tooltip,
|
|
3605
3888
|
"aria-expanded": open,
|
|
3606
3889
|
onClick: () => setOpen(!open),
|
|
3607
|
-
children: /* @__PURE__ */
|
|
3890
|
+
children: /* @__PURE__ */ jsx7(Icon, { icon: "fa-solid fa-microphone" })
|
|
3608
3891
|
}
|
|
3609
3892
|
),
|
|
3610
3893
|
open && typeof document !== "undefined" && createPortal(
|
|
3611
|
-
/* @__PURE__ */
|
|
3894
|
+
/* @__PURE__ */ jsx7(
|
|
3612
3895
|
RecorderModal,
|
|
3613
3896
|
{
|
|
3614
3897
|
mediaProvider,
|