@ttt-productions/file-input 0.11.18 → 0.11.19

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.
@@ -1 +1 @@
1
- {"version":3,"file":"record-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"AA+EA,wBAAgB,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,CACV,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,OAAO,GAAG,OAAO,KAC5B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AASD,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,QAAgB,EAChB,SAAiB,EACjB,UAAU,EACV,cAAc,GACf,EAAE,iBAAiB,+BA4iBnB"}
1
+ {"version":3,"file":"record-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"AA6FA,wBAAgB,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,CACV,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,OAAO,GAAG,OAAO,KAC5B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AASD,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,QAAgB,EAChB,SAAiB,EACjB,UAAU,EACV,cAAc,GACf,EAAE,iBAAiB,+BAwmBnB"}
@@ -13,6 +13,17 @@ const RECORDER_MIME_CANDIDATES = {
13
13
  audio: ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'],
14
14
  video: ['video/webm;codecs=vp9,opus', 'video/webm', 'video/mp4'],
15
15
  };
16
+ // TEMPORARY: the record_dialog_* console lines diagnose the Firefox recording failure, and come out
17
+ // as soon as it is diagnosed and fixed.
18
+ function logRecordDialog(event, data = {}) {
19
+ if (typeof console === 'undefined' || typeof console.debug !== 'function')
20
+ return;
21
+ console.debug(`record_dialog_${event} ${JSON.stringify(data)}`);
22
+ }
23
+ // TEMPORARY: part of the record_dialog_* diagnostics.
24
+ function errorFields(e) {
25
+ return e instanceof Error ? { name: e.name, message: e.message } : { thrown: String(e) };
26
+ }
16
27
  function stopIfRecording(recorder) {
17
28
  if (!recorder || recorder.state !== 'recording')
18
29
  return;
@@ -72,7 +83,7 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
72
83
  const [elapsedMs, setElapsedMs] = useState(0);
73
84
  const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);
74
85
  const [error, setError] = useState(null);
75
- const [acquiring, setAcquiring] = useState(false);
86
+ const [streamStatus, setStreamStatus] = useState('acquiring');
76
87
  const mediaRecorderRef = useRef(null);
77
88
  const recordStreamRef = useRef(null);
78
89
  const liveStreamKindRef = useRef(null);
@@ -194,25 +205,38 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
194
205
  const wantVideo = kind === 'video';
195
206
  const facingMode = cameraFacingMode ?? 'user';
196
207
  const gen = acquireGenRef.current;
208
+ // TEMPORARY diagnostic
209
+ logRecordDialog('stream_request', { kind, gen });
197
210
  const stream = await navigator.mediaDevices.getUserMedia({
198
211
  audio: true,
199
212
  video: wantVideo ? { facingMode } : false,
200
213
  });
201
214
  if (gen !== acquireGenRef.current) {
215
+ // TEMPORARY diagnostic
216
+ logRecordDialog('stream_discarded', { kind, gen, currentGen: acquireGenRef.current });
202
217
  stream.getTracks().forEach((t) => t.stop());
203
218
  return null;
204
219
  }
220
+ // TEMPORARY diagnostic
221
+ logRecordDialog('stream_ready', {
222
+ kind,
223
+ gen,
224
+ tracks: stream.getTracks().map((t) => ({ kind: t.kind, readyState: t.readyState })),
225
+ });
205
226
  recordStreamRef.current = stream;
206
227
  liveStreamKindRef.current = kind;
207
228
  setError(null);
229
+ setStreamStatus('ready');
208
230
  if (wantVideo && livePreviewRef.current) {
209
231
  livePreviewRef.current.srcObject = stream;
210
232
  livePreviewRef.current.muted = true;
211
233
  try {
212
234
  await livePreviewRef.current.play();
213
235
  }
214
- catch {
236
+ catch (e) {
215
237
  // A browser that blocks preview playback rejects; recording does not depend on it.
238
+ // TEMPORARY diagnostic
239
+ logRecordDialog('preview_play_failed', errorFields(e));
216
240
  }
217
241
  }
218
242
  else if (!wantVideo) {
@@ -223,25 +247,34 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
223
247
  const startLivePreview = useCallback(async (kind) => {
224
248
  teardownCapture();
225
249
  const req = ++previewReqRef.current;
226
- setAcquiring(true);
250
+ setStreamStatus('acquiring');
227
251
  try {
228
252
  await acquireStream(kind);
229
253
  }
230
254
  catch (e) {
255
+ // TEMPORARY diagnostic
256
+ logRecordDialog('stream_failed', {
257
+ kind,
258
+ req,
259
+ currentReq: previewReqRef.current,
260
+ ...errorFields(e),
261
+ });
262
+ if (req !== previewReqRef.current)
263
+ return;
231
264
  setError(e instanceof Error ? e.message : 'Camera/microphone access denied.');
232
- }
233
- finally {
234
- if (req === previewReqRef.current)
235
- setAcquiring(false);
265
+ setStreamStatus('failed');
236
266
  }
237
267
  }, [teardownCapture, acquireStream]);
238
268
  useEffect(() => {
239
269
  if (open) {
270
+ // TEMPORARY diagnostic
271
+ logRecordDialog('open', { initialKind });
240
272
  void startLivePreview(initialKind);
241
273
  }
242
274
  return () => {
243
275
  teardownCapture();
244
276
  revokeLastRecordUrl();
277
+ setStreamStatus('acquiring');
245
278
  };
246
279
  }, [open, initialKind, startLivePreview, teardownCapture, revokeLastRecordUrl]);
247
280
  const startRecording = useCallback(async (kind) => {
@@ -251,17 +284,29 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
251
284
  setRecordKind(kind);
252
285
  const wantVideo = kind === 'video';
253
286
  let stream = recordStreamRef.current;
287
+ // TEMPORARY diagnostic
288
+ logRecordDialog('start_branch', {
289
+ kind,
290
+ branch: !stream || liveStreamKindRef.current !== kind ? 'reacquire' : 'reuse',
291
+ liveKind: liveStreamKindRef.current,
292
+ trackStates: stream?.getTracks().map((t) => t.readyState) ?? [],
293
+ });
254
294
  if (!stream || liveStreamKindRef.current !== kind) {
255
295
  try {
256
296
  stream = await acquireStream(kind);
257
297
  }
258
298
  catch (e) {
299
+ // TEMPORARY diagnostic
300
+ logRecordDialog('start_stream_failed', { kind, ...errorFields(e) });
259
301
  setError(e instanceof Error ? e.message : 'Camera/microphone access denied.');
260
302
  return;
261
303
  }
262
304
  }
263
- if (!stream)
305
+ if (!stream) {
306
+ // TEMPORARY diagnostic
307
+ logRecordDialog('start_no_stream', { kind });
264
308
  return;
309
+ }
265
310
  const preferredMimeType = pickRecorderMimeType(kind);
266
311
  let mr;
267
312
  try {
@@ -269,16 +314,26 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
269
314
  ? new MediaRecorder(stream, { mimeType: preferredMimeType })
270
315
  : new MediaRecorder(stream);
271
316
  }
272
- catch {
317
+ catch (e) {
318
+ // TEMPORARY diagnostic
319
+ logRecordDialog('recorder_create_fallback', { preferredMimeType, ...errorFields(e) });
273
320
  mr = new MediaRecorder(stream);
274
321
  }
322
+ // TEMPORARY diagnostic
323
+ logRecordDialog('recorder_created', { preferredMimeType, mimeType: mr.mimeType });
275
324
  mediaRecorderRef.current = mr;
276
325
  recordChunksRef.current = [];
277
326
  mr.ondataavailable = (e) => {
278
327
  if (e.data && e.data.size > 0)
279
328
  recordChunksRef.current.push(e.data);
280
329
  };
330
+ mr.onerror = (e) => {
331
+ // TEMPORARY diagnostic
332
+ logRecordDialog('recorder_error', { type: e.type });
333
+ };
281
334
  mr.onstop = () => {
335
+ // TEMPORARY diagnostic
336
+ logRecordDialog('recorder_stopped', { chunks: recordChunksRef.current.length });
282
337
  const blob = new Blob(recordChunksRef.current, {
283
338
  type: mr.mimeType || preferredMimeType || (wantVideo ? 'video/webm' : 'audio/webm'),
284
339
  });
@@ -290,7 +345,16 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
290
345
  setRecorderState('preview');
291
346
  teardownCapture();
292
347
  };
293
- mr.start();
348
+ try {
349
+ mr.start();
350
+ }
351
+ catch (e) {
352
+ // TEMPORARY diagnostic
353
+ logRecordDialog('recorder_start_failed', errorFields(e));
354
+ throw e;
355
+ }
356
+ // TEMPORARY diagnostic
357
+ logRecordDialog('recorder_started', { state: mr.state });
294
358
  setRecorderState('recording');
295
359
  startElapsedTimer();
296
360
  if (maxRecordDurationSec && maxRecordDurationSec > 0) {
@@ -371,12 +435,17 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
371
435
  const isRecording = recorderState === 'recording';
372
436
  const isPreview = recorderState === 'preview';
373
437
  const isIdle = recorderState === 'idle';
438
+ const acquiring = streamStatus === 'acquiring';
374
439
  const elapsedLabel = maxRecordDurationSec
375
440
  ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`
376
441
  : formatDuration(elapsedMs);
377
442
  return (_jsxs(_Fragment, { children: [_jsx(Dialog, { open: open, onOpenChange: handleDialogOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Record" }), _jsxs(DialogDescription, { children: [isIdle && 'Choose audio or video, then press Start.', isRecording && 'Recording…', isPreview && 'Review your recording. Save to upload, or Re-record to try again.'] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [canRecVideo && (_jsx(Button, { type: "button", variant: recordKind === 'video' ? 'default' : 'secondary', "aria-pressed": recordKind === 'video', onClick: () => handleSelectKind('video'), disabled: !isIdle || acquiring, icon: _jsx(Video, { className: "icon-xs" }), children: "Video" })), canRecAudio && (_jsx(Button, { type: "button", variant: recordKind === 'audio' ? 'default' : 'secondary', "aria-pressed": recordKind === 'audio', onClick: () => handleSelectKind('audio'), disabled: !isIdle || acquiring, icon: _jsx(Mic, { className: "icon-xs" }), children: "Audio" })), canPhoto && onRequestPhoto && (_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
378
443
  onRequestPhoto();
379
444
  onOpenChange(false);
380
- }, disabled: !isIdle, icon: _jsx(Camera, { className: "icon-xs" }), children: "Photo" }))] }), recordKind === 'video' && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video", children: [_jsx("video", { ref: livePreviewRef, className: "w-full h-full object-contain", playsInline: true, muted: true }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting camera" }) }))] })), recordKind === 'audio' && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md bg-muted p-2 text-primary", children: [_jsx("canvas", { ref: waveformCanvasRef, width: 480, height: 80, className: "w-full h-20", "aria-label": "Audio waveform" }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting microphone" }) }))] })), error && (_jsx("p", { className: "text-sm text-destructive", role: "alert", children: error })), isRecording && (_jsxs("div", { className: "flex items-center gap-2 text-sm", "aria-live": "polite", children: [_jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse", "aria-label": "Recording" }), _jsx("span", { className: "tabular-nums", children: elapsedLabel })] })), isPreview && recordPreviewUrl && (_jsx("div", { className: "mt-2", children: _jsx(MediaPreview, { url: recordPreviewUrl, type: recordKind, controls: true, priority: true, className: "w-full rounded-md" }) })), _jsxs(DialogFooter, { className: "flex-row justify-between gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => handleDialogOpenChange(false), disabled: save.pending, icon: _jsx(X, { className: "icon-xs" }), children: "Close" }), isIdle && (_jsx(Button, { type: "button", variant: "default", onClick: () => void start.run(recordKind), disabled: disabled || isLoading || acquiring, pending: start.pending, icon: recordKind === 'video' ? (_jsx(Video, { className: "icon-xs" })) : (_jsx(Mic, { className: "icon-xs" })), children: "Start" })), isRecording && (_jsx(Button, { type: "button", variant: "default", onClick: stopRecording, children: "Stop" })), isPreview && (_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: handleReRecord, disabled: save.pending, icon: _jsx(RotateCcw, { className: "icon-xs" }), children: "Re-record" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void save.run(), disabled: disabled || isLoading, pending: save.pending, icon: _jsx(Check, { className: "icon-xs" }), children: "Save" })] }))] })] }) }), _jsx(AlertDialog, { open: discardConfirmOpen, onOpenChange: setDiscardConfirmOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Discard recording?" }), _jsx(AlertDialogDescription, { children: "You have a recorded clip that hasn't been saved. Closing now will discard it." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Keep recording" }), _jsx(AlertDialogAction, { onClick: closeDialogImmediately, children: "Discard" })] })] }) })] }));
445
+ }, disabled: !isIdle, icon: _jsx(Camera, { className: "icon-xs" }), children: "Photo" }))] }), recordKind === 'video' && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video", children: [_jsx("video", { ref: livePreviewRef, className: "w-full h-full object-contain", playsInline: true, muted: true }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting camera" }) }))] })), recordKind === 'audio' && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md bg-muted p-2 text-primary", children: [_jsx("canvas", { ref: waveformCanvasRef, width: 480, height: 80, className: "w-full h-20", "aria-label": "Audio waveform" }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting microphone" }) }))] })), error && (_jsx("p", { className: "text-sm text-destructive", role: "alert", children: error })), isRecording && (_jsxs("div", { className: "flex items-center gap-2 text-sm", "aria-live": "polite", children: [_jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse", "aria-label": "Recording" }), _jsx("span", { className: "tabular-nums", children: elapsedLabel })] })), isPreview && recordPreviewUrl && (_jsx("div", { className: "mt-2", children: _jsx(MediaPreview, { url: recordPreviewUrl, type: recordKind, controls: true, priority: true, className: "w-full rounded-md" }) })), _jsxs(DialogFooter, { className: "flex-row justify-between gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => handleDialogOpenChange(false), disabled: save.pending, icon: _jsx(X, { className: "icon-xs" }), children: "Close" }), isIdle && (_jsx(Button, { type: "button", variant: "default", onClick: () => {
446
+ // TEMPORARY diagnostic
447
+ logRecordDialog('start_click', { kind: recordKind, streamStatus });
448
+ void start.run(recordKind);
449
+ }, disabled: disabled || isLoading || acquiring, pending: start.pending, icon: recordKind === 'video' ? (_jsx(Video, { className: "icon-xs" })) : (_jsx(Mic, { className: "icon-xs" })), children: "Start" })), isRecording && (_jsx(Button, { type: "button", variant: "default", onClick: stopRecording, children: "Stop" })), isPreview && (_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: handleReRecord, disabled: save.pending, icon: _jsx(RotateCcw, { className: "icon-xs" }), children: "Re-record" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void save.run(), disabled: disabled || isLoading, pending: save.pending, icon: _jsx(Check, { className: "icon-xs" }), children: "Save" })] }))] })] }) }), _jsx(AlertDialog, { open: discardConfirmOpen, onOpenChange: setDiscardConfirmOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Discard recording?" }), _jsx(AlertDialogDescription, { children: "You have a recorded clip that hasn't been saved. Closing now will discard it." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Keep recording" }), _jsx(AlertDialogAction, { onClick: closeDialogImmediately, children: "Discard" })] })] }) })] }));
381
450
  }
382
451
  //# sourceMappingURL=record-dialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"record-dialog.js","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { Camera, Video, Mic, X, RotateCcw, Check } from 'lucide-react';\nimport { startWaveformLoop } from '@ttt-productions/media-viewer';\nimport { MediaPreview } from '@ttt-productions/media-viewer/react';\nimport { ensureFileWithContentType } from '../../lib/infer-content-type.js';\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Spinner,\n useAsyncAction,\n} from '@ttt-productions/ui-core/react';\nimport { releaseObjectUrl } from '../../lib/validate-media-duration.js';\n\ntype RecorderState = 'idle' | 'recording' | 'preview';\n\n// An explicit supported container keeps mr.mimeType deterministic; Safari has no webm and falls\n// through to mp4.\nconst RECORDER_MIME_CANDIDATES: Record<'audio' | 'video', readonly string[]> = {\n audio: ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'],\n video: ['video/webm;codecs=vp9,opus', 'video/webm', 'video/mp4'],\n};\n\nfunction stopIfRecording(recorder: MediaRecorder | null): void {\n if (!recorder || recorder.state !== 'recording') return;\n try {\n recorder.stop();\n } catch {\n // A recorder that stopped between the state check and this call throws; it has stopped.\n }\n}\n\nfunction pickRecorderMimeType(kind: 'audio' | 'video'): string | undefined {\n if (typeof MediaRecorder === 'undefined' || typeof MediaRecorder.isTypeSupported !== 'function') {\n return undefined;\n }\n for (const candidate of RECORDER_MIME_CANDIDATES[kind]) {\n try {\n if (MediaRecorder.isTypeSupported(candidate)) return candidate;\n } catch {\n // A browser that cannot parse a candidate throws; it counts as unsupported.\n }\n }\n return undefined;\n}\n\n// mp4 is not in the ambiguous-extension map, so an audio recording is named .m4a whatever\n// container type the recorder reports.\nfunction recordingFileName(mime: string, kind: 'audio' | 'video'): string {\n const base = mime.split(';')[0].trim().toLowerCase();\n const ext =\n base === 'audio/mp4' || base === 'video/mp4'\n ? kind === 'audio'\n ? 'm4a'\n : 'mp4'\n : base === 'audio/ogg' || base === 'video/ogg'\n ? 'ogg'\n : 'webm';\n return `recording.${ext}`;\n}\n\n// A MediaRecorder blob runs a little longer than start() to stop() (muxer flush, frame-end\n// duration, late timers) and both duration gates are exact, so recording stops before the cap.\nconst AUTO_STOP_SAFETY_MARGIN_MS = 750;\n\nexport function autoStopDelayMs(maxRecordDurationSec: number): number {\n const capMs = Math.floor(maxRecordDurationSec * 1000);\n return Math.max(capMs - AUTO_STOP_SAFETY_MARGIN_MS, Math.ceil(capMs / 2));\n}\n\ninterface RecordDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n initialKind: 'audio' | 'video';\n canPhoto: boolean;\n canRecVideo: boolean;\n canRecAudio: boolean;\n cameraFacingMode?: 'user' | 'environment';\n maxRecordDurationSec?: number;\n disabled?: boolean;\n isLoading?: boolean;\n onRecorded: (\n file: File,\n previewUrl: string,\n recordedKind: 'audio' | 'video',\n ) => void | Promise<void>;\n onRequestPhoto?: () => void;\n}\n\nfunction formatDuration(ms: number): string {\n const totalSec = Math.max(0, Math.floor(ms / 1000));\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n}\n\nexport function RecordDialog({\n open,\n onOpenChange,\n initialKind,\n canPhoto,\n canRecVideo,\n canRecAudio,\n cameraFacingMode,\n maxRecordDurationSec,\n disabled = false,\n isLoading = false,\n onRecorded,\n onRequestPhoto,\n}: RecordDialogProps) {\n const [recordKind, setRecordKind] = useState<'audio' | 'video'>(initialKind);\n const [recorderState, setRecorderState] = useState<RecorderState>('idle');\n const [recordPreviewUrl, setRecordPreviewUrl] = useState<string | null>(null);\n const [recordedFile, setRecordedFile] = useState<File | null>(null);\n const [elapsedMs, setElapsedMs] = useState(0);\n const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [acquiring, setAcquiring] = useState(false);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const recordStreamRef = useRef<MediaStream | null>(null);\n const liveStreamKindRef = useRef<'audio' | 'video' | null>(null);\n const recordChunksRef = useRef<BlobPart[]>([]);\n const lastRecordUrlRef = useRef<string | null>(null);\n const livePreviewRef = useRef<HTMLVideoElement | null>(null);\n const autoStopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // An acquisition that resolves after a teardown belongs to nobody and is never attached.\n const acquireGenRef = useRef(0);\n const previewReqRef = useRef(0);\n\n const startedAtRef = useRef<number>(0);\n const elapsedIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const audioContextRef = useRef<AudioContext | null>(null);\n const audioAnalyserRef = useRef<AnalyserNode | null>(null);\n const audioSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const waveformStopRef = useRef<(() => void) | null>(null);\n const waveformCanvasRef = useRef<HTMLCanvasElement | null>(null);\n\n useEffect(() => {\n if (open) {\n setRecordKind(initialKind);\n setRecorderState('idle');\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setDiscardConfirmOpen(false);\n setError(null);\n }\n }, [open, initialKind]);\n\n const revokeLastRecordUrl = useCallback(() => {\n if (!lastRecordUrlRef.current) return;\n releaseObjectUrl(lastRecordUrlRef.current);\n lastRecordUrlRef.current = null;\n }, []);\n\n const makeRecordUrl = useCallback(\n (blob: Blob) => {\n revokeLastRecordUrl();\n const url = URL.createObjectURL(blob);\n lastRecordUrlRef.current = url;\n return url;\n },\n [revokeLastRecordUrl],\n );\n\n const stopElapsedTimer = useCallback(() => {\n if (elapsedIntervalRef.current) {\n clearInterval(elapsedIntervalRef.current);\n elapsedIntervalRef.current = null;\n }\n }, []);\n\n const startElapsedTimer = useCallback(() => {\n stopElapsedTimer();\n startedAtRef.current = Date.now();\n setElapsedMs(0);\n elapsedIntervalRef.current = setInterval(() => {\n setElapsedMs(Date.now() - startedAtRef.current);\n }, 100);\n }, [stopElapsedTimer]);\n\n const stopWaveform = useCallback(() => {\n waveformStopRef.current?.();\n waveformStopRef.current = null;\n try {\n audioSourceRef.current?.disconnect();\n } catch {\n // A node that is already disconnected throws, and there is nothing left to tear down.\n }\n audioSourceRef.current = null;\n audioAnalyserRef.current = null;\n const ctx = audioContextRef.current;\n audioContextRef.current = null;\n if (ctx && ctx.state !== 'closed') {\n // Closing a context the browser is already closing rejects; the context is released either way.\n ctx.close().catch(() => {});\n }\n }, []);\n\n const startWaveform = useCallback(\n (stream: MediaStream) => {\n stopWaveform();\n try {\n // lib.dom does not declare Safari's prefixed webkitAudioContext.\n const prefixed = window as Window & { webkitAudioContext?: typeof AudioContext };\n const AudioCtx: typeof AudioContext | undefined =\n window.AudioContext || prefixed.webkitAudioContext;\n if (!AudioCtx) return;\n\n const ctx = new AudioCtx();\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n source.connect(analyser);\n\n audioContextRef.current = ctx;\n audioSourceRef.current = source;\n audioAnalyserRef.current = analyser;\n\n waveformStopRef.current = startWaveformLoop({\n analyser,\n getCanvas: () => waveformCanvasRef.current,\n mode: 'line',\n });\n } catch {\n // The waveform is decoration: a browser that refuses an audio graph records without one.\n }\n },\n [stopWaveform],\n );\n\n const teardownCapture = useCallback(() => {\n acquireGenRef.current += 1;\n if (autoStopTimeoutRef.current) {\n clearTimeout(autoStopTimeoutRef.current);\n autoStopTimeoutRef.current = null;\n }\n mediaRecorderRef.current = null;\n\n const s = recordStreamRef.current;\n recordStreamRef.current = null;\n liveStreamKindRef.current = null;\n if (s) s.getTracks().forEach((t) => t.stop());\n\n if (livePreviewRef.current) {\n livePreviewRef.current.srcObject = null;\n }\n\n recordChunksRef.current = [];\n\n stopElapsedTimer();\n stopWaveform();\n }, [stopElapsedTimer, stopWaveform]);\n\n const acquireStream = useCallback(\n async (kind: 'audio' | 'video') => {\n const wantVideo = kind === 'video';\n const facingMode = cameraFacingMode ?? 'user';\n const gen = acquireGenRef.current;\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: wantVideo ? { facingMode } : false,\n });\n if (gen !== acquireGenRef.current) {\n stream.getTracks().forEach((t) => t.stop());\n return null;\n }\n recordStreamRef.current = stream;\n liveStreamKindRef.current = kind;\n setError(null);\n if (wantVideo && livePreviewRef.current) {\n livePreviewRef.current.srcObject = stream;\n livePreviewRef.current.muted = true;\n try {\n await livePreviewRef.current.play();\n } catch {\n // A browser that blocks preview playback rejects; recording does not depend on it.\n }\n } else if (!wantVideo) {\n startWaveform(stream);\n }\n return stream;\n },\n [cameraFacingMode, startWaveform],\n );\n\n const startLivePreview = useCallback(\n async (kind: 'audio' | 'video') => {\n teardownCapture();\n const req = ++previewReqRef.current;\n setAcquiring(true);\n try {\n await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Camera/microphone access denied.');\n } finally {\n if (req === previewReqRef.current) setAcquiring(false);\n }\n },\n [teardownCapture, acquireStream],\n );\n\n useEffect(() => {\n if (open) {\n void startLivePreview(initialKind);\n }\n return () => {\n teardownCapture();\n revokeLastRecordUrl();\n };\n }, [open, initialKind, startLivePreview, teardownCapture, revokeLastRecordUrl]);\n\n const startRecording = useCallback(\n async (kind: 'audio' | 'video') => {\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setRecordKind(kind);\n\n const wantVideo = kind === 'video';\n\n let stream = recordStreamRef.current;\n if (!stream || liveStreamKindRef.current !== kind) {\n try {\n stream = await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Camera/microphone access denied.');\n return;\n }\n }\n if (!stream) return;\n\n const preferredMimeType = pickRecorderMimeType(kind);\n let mr: MediaRecorder;\n try {\n mr = preferredMimeType\n ? new MediaRecorder(stream, { mimeType: preferredMimeType })\n : new MediaRecorder(stream);\n } catch {\n mr = new MediaRecorder(stream);\n }\n mediaRecorderRef.current = mr;\n recordChunksRef.current = [];\n\n mr.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) recordChunksRef.current.push(e.data);\n };\n\n mr.onstop = () => {\n const blob = new Blob(recordChunksRef.current, {\n type: mr.mimeType || preferredMimeType || (wantVideo ? 'video/webm' : 'audio/webm'),\n });\n\n const rawFile = new File([blob], recordingFileName(blob.type, kind), { type: blob.type });\n const file = ensureFileWithContentType(rawFile, wantVideo ? 'video' : 'audio');\n const url = makeRecordUrl(file);\n\n setRecordedFile(file);\n setRecordPreviewUrl(url);\n setRecorderState('preview');\n\n teardownCapture();\n };\n\n mr.start();\n setRecorderState('recording');\n startElapsedTimer();\n\n if (maxRecordDurationSec && maxRecordDurationSec > 0) {\n autoStopTimeoutRef.current = setTimeout(\n () => stopIfRecording(mediaRecorderRef.current),\n autoStopDelayMs(maxRecordDurationSec),\n );\n }\n },\n [\n acquireStream,\n maxRecordDurationSec,\n makeRecordUrl,\n revokeLastRecordUrl,\n teardownCapture,\n startElapsedTimer,\n ],\n );\n\n const stopRecording = useCallback(() => {\n const mr = mediaRecorderRef.current;\n if (mr && mr.state === 'recording') {\n try {\n mr.stop();\n } catch {\n teardownCapture();\n setRecorderState('idle');\n }\n } else {\n teardownCapture();\n setRecorderState('idle');\n }\n }, [teardownCapture]);\n\n const handleReRecord = useCallback(() => {\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setElapsedMs(0);\n setError(null);\n setRecorderState('idle');\n void startLivePreview(recordKind);\n }, [revokeLastRecordUrl, startLivePreview, recordKind]);\n\n const handleSelectKind = useCallback(\n (kind: 'audio' | 'video') => {\n setRecordKind(kind);\n setError(null);\n void startLivePreview(kind);\n },\n [startLivePreview],\n );\n\n const reportError = useCallback((e: unknown) => {\n setError(e instanceof Error ? e.message : 'Something went wrong with the recording.');\n }, []);\n\n const start = useAsyncAction((kind: 'audio' | 'video') => startRecording(kind), {\n onError: reportError,\n });\n\n const save = useAsyncAction(\n async () => {\n if (!recordedFile || !recordPreviewUrl) return;\n await onRecorded(recordedFile, recordPreviewUrl, recordKind);\n onOpenChange(false);\n },\n { onError: reportError },\n );\n\n const closeDialogImmediately = useCallback(() => {\n stopIfRecording(mediaRecorderRef.current);\n teardownCapture();\n revokeLastRecordUrl();\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setRecorderState('idle');\n setDiscardConfirmOpen(false);\n onOpenChange(false);\n }, [teardownCapture, revokeLastRecordUrl, onOpenChange]);\n\n const handleDialogOpenChange = useCallback(\n (v: boolean) => {\n if (v) return;\n if (save.pending) return;\n if (recorderState === 'preview') {\n setDiscardConfirmOpen(true);\n return;\n }\n closeDialogImmediately();\n },\n [recorderState, save.pending, closeDialogImmediately],\n );\n\n const isRecording = recorderState === 'recording';\n const isPreview = recorderState === 'preview';\n const isIdle = recorderState === 'idle';\n\n const elapsedLabel = maxRecordDurationSec\n ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`\n : formatDuration(elapsedMs);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Record</DialogTitle>\n <DialogDescription>\n {isIdle && 'Choose audio or video, then press Start.'}\n {isRecording && 'Recording…'}\n {isPreview && 'Review your recording. Save to upload, or Re-record to try again.'}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"flex flex-wrap gap-2\">\n {canRecVideo && (\n <Button\n type=\"button\"\n variant={recordKind === 'video' ? 'default' : 'secondary'}\n aria-pressed={recordKind === 'video'}\n onClick={() => handleSelectKind('video')}\n disabled={!isIdle || acquiring}\n icon={<Video className=\"icon-xs\" />}\n >\n Video\n </Button>\n )}\n {canRecAudio && (\n <Button\n type=\"button\"\n variant={recordKind === 'audio' ? 'default' : 'secondary'}\n aria-pressed={recordKind === 'audio'}\n onClick={() => handleSelectKind('audio')}\n disabled={!isIdle || acquiring}\n icon={<Mic className=\"icon-xs\" />}\n >\n Audio\n </Button>\n )}\n {canPhoto && onRequestPhoto && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n onRequestPhoto();\n onOpenChange(false);\n }}\n disabled={!isIdle}\n icon={<Camera className=\"icon-xs\" />}\n >\n Photo\n </Button>\n )}\n </div>\n\n {recordKind === 'video' && !isPreview && (\n <div className=\"relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video\">\n <video\n ref={livePreviewRef}\n className=\"w-full h-full object-contain\"\n playsInline\n muted\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting camera\" />\n </div>\n )}\n </div>\n )}\n\n {recordKind === 'audio' && !isPreview && (\n <div className=\"relative mt-2 rounded-md bg-muted p-2 text-primary\">\n <canvas\n ref={waveformCanvasRef}\n width={480}\n height={80}\n className=\"w-full h-20\"\n aria-label=\"Audio waveform\"\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting microphone\" />\n </div>\n )}\n </div>\n )}\n\n {error && (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {error}\n </p>\n )}\n\n {isRecording && (\n <div className=\"flex items-center gap-2 text-sm\" aria-live=\"polite\">\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse\"\n aria-label=\"Recording\"\n />\n <span className=\"tabular-nums\">{elapsedLabel}</span>\n </div>\n )}\n\n {isPreview && recordPreviewUrl && (\n <div className=\"mt-2\">\n <MediaPreview\n url={recordPreviewUrl}\n type={recordKind}\n controls\n priority\n className=\"w-full rounded-md\"\n />\n </div>\n )}\n\n <DialogFooter className=\"flex-row justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleDialogOpenChange(false)}\n disabled={save.pending}\n icon={<X className=\"icon-xs\" />}\n >\n Close\n </Button>\n\n {isIdle && (\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void start.run(recordKind)}\n disabled={disabled || isLoading || acquiring}\n pending={start.pending}\n icon={\n recordKind === 'video' ? (\n <Video className=\"icon-xs\" />\n ) : (\n <Mic className=\"icon-xs\" />\n )\n }\n >\n Start\n </Button>\n )}\n\n {isRecording && (\n <Button type=\"button\" variant=\"default\" onClick={stopRecording}>\n Stop\n </Button>\n )}\n\n {isPreview && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={handleReRecord}\n disabled={save.pending}\n icon={<RotateCcw className=\"icon-xs\" />}\n >\n Re-record\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void save.run()}\n disabled={disabled || isLoading}\n pending={save.pending}\n icon={<Check className=\"icon-xs\" />}\n >\n Save\n </Button>\n </div>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n <AlertDialog open={discardConfirmOpen} onOpenChange={setDiscardConfirmOpen}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Discard recording?</AlertDialogTitle>\n <AlertDialogDescription>\n You have a recorded clip that hasn't been saved. Closing now will discard it.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep recording</AlertDialogCancel>\n <AlertDialogAction onClick={closeDialogImmediately}>Discard</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </>\n );\n}\n"]}
1
+ {"version":3,"file":"record-dialog.js","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { Camera, Video, Mic, X, RotateCcw, Check } from 'lucide-react';\nimport { startWaveformLoop } from '@ttt-productions/media-viewer';\nimport { MediaPreview } from '@ttt-productions/media-viewer/react';\nimport { ensureFileWithContentType } from '../../lib/infer-content-type.js';\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Spinner,\n useAsyncAction,\n} from '@ttt-productions/ui-core/react';\nimport { releaseObjectUrl } from '../../lib/validate-media-duration.js';\n\ntype RecorderState = 'idle' | 'recording' | 'preview';\n\ntype StreamStatus = 'acquiring' | 'ready' | 'failed';\n\n// An explicit supported container keeps mr.mimeType deterministic; Safari has no webm and falls\n// through to mp4.\nconst RECORDER_MIME_CANDIDATES: Record<'audio' | 'video', readonly string[]> = {\n audio: ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'],\n video: ['video/webm;codecs=vp9,opus', 'video/webm', 'video/mp4'],\n};\n\n// TEMPORARY: the record_dialog_* console lines diagnose the Firefox recording failure, and come out\n// as soon as it is diagnosed and fixed.\nfunction logRecordDialog(event: string, data: Record<string, unknown> = {}): void {\n if (typeof console === 'undefined' || typeof console.debug !== 'function') return;\n console.debug(`record_dialog_${event} ${JSON.stringify(data)}`);\n}\n\n// TEMPORARY: part of the record_dialog_* diagnostics.\nfunction errorFields(e: unknown): Record<string, unknown> {\n return e instanceof Error ? { name: e.name, message: e.message } : { thrown: String(e) };\n}\n\nfunction stopIfRecording(recorder: MediaRecorder | null): void {\n if (!recorder || recorder.state !== 'recording') return;\n try {\n recorder.stop();\n } catch {\n // A recorder that stopped between the state check and this call throws; it has stopped.\n }\n}\n\nfunction pickRecorderMimeType(kind: 'audio' | 'video'): string | undefined {\n if (typeof MediaRecorder === 'undefined' || typeof MediaRecorder.isTypeSupported !== 'function') {\n return undefined;\n }\n for (const candidate of RECORDER_MIME_CANDIDATES[kind]) {\n try {\n if (MediaRecorder.isTypeSupported(candidate)) return candidate;\n } catch {\n // A browser that cannot parse a candidate throws; it counts as unsupported.\n }\n }\n return undefined;\n}\n\n// mp4 is not in the ambiguous-extension map, so an audio recording is named .m4a whatever\n// container type the recorder reports.\nfunction recordingFileName(mime: string, kind: 'audio' | 'video'): string {\n const base = mime.split(';')[0].trim().toLowerCase();\n const ext =\n base === 'audio/mp4' || base === 'video/mp4'\n ? kind === 'audio'\n ? 'm4a'\n : 'mp4'\n : base === 'audio/ogg' || base === 'video/ogg'\n ? 'ogg'\n : 'webm';\n return `recording.${ext}`;\n}\n\n// A MediaRecorder blob runs a little longer than start() to stop() (muxer flush, frame-end\n// duration, late timers) and both duration gates are exact, so recording stops before the cap.\nconst AUTO_STOP_SAFETY_MARGIN_MS = 750;\n\nexport function autoStopDelayMs(maxRecordDurationSec: number): number {\n const capMs = Math.floor(maxRecordDurationSec * 1000);\n return Math.max(capMs - AUTO_STOP_SAFETY_MARGIN_MS, Math.ceil(capMs / 2));\n}\n\ninterface RecordDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n initialKind: 'audio' | 'video';\n canPhoto: boolean;\n canRecVideo: boolean;\n canRecAudio: boolean;\n cameraFacingMode?: 'user' | 'environment';\n maxRecordDurationSec?: number;\n disabled?: boolean;\n isLoading?: boolean;\n onRecorded: (\n file: File,\n previewUrl: string,\n recordedKind: 'audio' | 'video',\n ) => void | Promise<void>;\n onRequestPhoto?: () => void;\n}\n\nfunction formatDuration(ms: number): string {\n const totalSec = Math.max(0, Math.floor(ms / 1000));\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n}\n\nexport function RecordDialog({\n open,\n onOpenChange,\n initialKind,\n canPhoto,\n canRecVideo,\n canRecAudio,\n cameraFacingMode,\n maxRecordDurationSec,\n disabled = false,\n isLoading = false,\n onRecorded,\n onRequestPhoto,\n}: RecordDialogProps) {\n const [recordKind, setRecordKind] = useState<'audio' | 'video'>(initialKind);\n const [recorderState, setRecorderState] = useState<RecorderState>('idle');\n const [recordPreviewUrl, setRecordPreviewUrl] = useState<string | null>(null);\n const [recordedFile, setRecordedFile] = useState<File | null>(null);\n const [elapsedMs, setElapsedMs] = useState(0);\n const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [streamStatus, setStreamStatus] = useState<StreamStatus>('acquiring');\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const recordStreamRef = useRef<MediaStream | null>(null);\n const liveStreamKindRef = useRef<'audio' | 'video' | null>(null);\n const recordChunksRef = useRef<BlobPart[]>([]);\n const lastRecordUrlRef = useRef<string | null>(null);\n const livePreviewRef = useRef<HTMLVideoElement | null>(null);\n const autoStopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // An acquisition that resolves after a teardown belongs to nobody and is never attached.\n const acquireGenRef = useRef(0);\n const previewReqRef = useRef(0);\n\n const startedAtRef = useRef<number>(0);\n const elapsedIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const audioContextRef = useRef<AudioContext | null>(null);\n const audioAnalyserRef = useRef<AnalyserNode | null>(null);\n const audioSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const waveformStopRef = useRef<(() => void) | null>(null);\n const waveformCanvasRef = useRef<HTMLCanvasElement | null>(null);\n\n useEffect(() => {\n if (open) {\n setRecordKind(initialKind);\n setRecorderState('idle');\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setDiscardConfirmOpen(false);\n setError(null);\n }\n }, [open, initialKind]);\n\n const revokeLastRecordUrl = useCallback(() => {\n if (!lastRecordUrlRef.current) return;\n releaseObjectUrl(lastRecordUrlRef.current);\n lastRecordUrlRef.current = null;\n }, []);\n\n const makeRecordUrl = useCallback(\n (blob: Blob) => {\n revokeLastRecordUrl();\n const url = URL.createObjectURL(blob);\n lastRecordUrlRef.current = url;\n return url;\n },\n [revokeLastRecordUrl],\n );\n\n const stopElapsedTimer = useCallback(() => {\n if (elapsedIntervalRef.current) {\n clearInterval(elapsedIntervalRef.current);\n elapsedIntervalRef.current = null;\n }\n }, []);\n\n const startElapsedTimer = useCallback(() => {\n stopElapsedTimer();\n startedAtRef.current = Date.now();\n setElapsedMs(0);\n elapsedIntervalRef.current = setInterval(() => {\n setElapsedMs(Date.now() - startedAtRef.current);\n }, 100);\n }, [stopElapsedTimer]);\n\n const stopWaveform = useCallback(() => {\n waveformStopRef.current?.();\n waveformStopRef.current = null;\n try {\n audioSourceRef.current?.disconnect();\n } catch {\n // A node that is already disconnected throws, and there is nothing left to tear down.\n }\n audioSourceRef.current = null;\n audioAnalyserRef.current = null;\n const ctx = audioContextRef.current;\n audioContextRef.current = null;\n if (ctx && ctx.state !== 'closed') {\n // Closing a context the browser is already closing rejects; the context is released either way.\n ctx.close().catch(() => {});\n }\n }, []);\n\n const startWaveform = useCallback(\n (stream: MediaStream) => {\n stopWaveform();\n try {\n // lib.dom does not declare Safari's prefixed webkitAudioContext.\n const prefixed = window as Window & { webkitAudioContext?: typeof AudioContext };\n const AudioCtx: typeof AudioContext | undefined =\n window.AudioContext || prefixed.webkitAudioContext;\n if (!AudioCtx) return;\n\n const ctx = new AudioCtx();\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n source.connect(analyser);\n\n audioContextRef.current = ctx;\n audioSourceRef.current = source;\n audioAnalyserRef.current = analyser;\n\n waveformStopRef.current = startWaveformLoop({\n analyser,\n getCanvas: () => waveformCanvasRef.current,\n mode: 'line',\n });\n } catch {\n // The waveform is decoration: a browser that refuses an audio graph records without one.\n }\n },\n [stopWaveform],\n );\n\n const teardownCapture = useCallback(() => {\n acquireGenRef.current += 1;\n if (autoStopTimeoutRef.current) {\n clearTimeout(autoStopTimeoutRef.current);\n autoStopTimeoutRef.current = null;\n }\n mediaRecorderRef.current = null;\n\n const s = recordStreamRef.current;\n recordStreamRef.current = null;\n liveStreamKindRef.current = null;\n if (s) s.getTracks().forEach((t) => t.stop());\n\n if (livePreviewRef.current) {\n livePreviewRef.current.srcObject = null;\n }\n\n recordChunksRef.current = [];\n\n stopElapsedTimer();\n stopWaveform();\n }, [stopElapsedTimer, stopWaveform]);\n\n const acquireStream = useCallback(\n async (kind: 'audio' | 'video') => {\n const wantVideo = kind === 'video';\n const facingMode = cameraFacingMode ?? 'user';\n const gen = acquireGenRef.current;\n // TEMPORARY diagnostic\n logRecordDialog('stream_request', { kind, gen });\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: wantVideo ? { facingMode } : false,\n });\n if (gen !== acquireGenRef.current) {\n // TEMPORARY diagnostic\n logRecordDialog('stream_discarded', { kind, gen, currentGen: acquireGenRef.current });\n stream.getTracks().forEach((t) => t.stop());\n return null;\n }\n // TEMPORARY diagnostic\n logRecordDialog('stream_ready', {\n kind,\n gen,\n tracks: stream.getTracks().map((t) => ({ kind: t.kind, readyState: t.readyState })),\n });\n recordStreamRef.current = stream;\n liveStreamKindRef.current = kind;\n setError(null);\n setStreamStatus('ready');\n if (wantVideo && livePreviewRef.current) {\n livePreviewRef.current.srcObject = stream;\n livePreviewRef.current.muted = true;\n try {\n await livePreviewRef.current.play();\n } catch (e) {\n // A browser that blocks preview playback rejects; recording does not depend on it.\n // TEMPORARY diagnostic\n logRecordDialog('preview_play_failed', errorFields(e));\n }\n } else if (!wantVideo) {\n startWaveform(stream);\n }\n return stream;\n },\n [cameraFacingMode, startWaveform],\n );\n\n const startLivePreview = useCallback(\n async (kind: 'audio' | 'video') => {\n teardownCapture();\n const req = ++previewReqRef.current;\n setStreamStatus('acquiring');\n try {\n await acquireStream(kind);\n } catch (e) {\n // TEMPORARY diagnostic\n logRecordDialog('stream_failed', {\n kind,\n req,\n currentReq: previewReqRef.current,\n ...errorFields(e),\n });\n if (req !== previewReqRef.current) return;\n setError(e instanceof Error ? e.message : 'Camera/microphone access denied.');\n setStreamStatus('failed');\n }\n },\n [teardownCapture, acquireStream],\n );\n\n useEffect(() => {\n if (open) {\n // TEMPORARY diagnostic\n logRecordDialog('open', { initialKind });\n void startLivePreview(initialKind);\n }\n return () => {\n teardownCapture();\n revokeLastRecordUrl();\n setStreamStatus('acquiring');\n };\n }, [open, initialKind, startLivePreview, teardownCapture, revokeLastRecordUrl]);\n\n const startRecording = useCallback(\n async (kind: 'audio' | 'video') => {\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setRecordKind(kind);\n\n const wantVideo = kind === 'video';\n\n let stream = recordStreamRef.current;\n // TEMPORARY diagnostic\n logRecordDialog('start_branch', {\n kind,\n branch: !stream || liveStreamKindRef.current !== kind ? 'reacquire' : 'reuse',\n liveKind: liveStreamKindRef.current,\n trackStates: stream?.getTracks().map((t) => t.readyState) ?? [],\n });\n if (!stream || liveStreamKindRef.current !== kind) {\n try {\n stream = await acquireStream(kind);\n } catch (e) {\n // TEMPORARY diagnostic\n logRecordDialog('start_stream_failed', { kind, ...errorFields(e) });\n setError(e instanceof Error ? e.message : 'Camera/microphone access denied.');\n return;\n }\n }\n if (!stream) {\n // TEMPORARY diagnostic\n logRecordDialog('start_no_stream', { kind });\n return;\n }\n\n const preferredMimeType = pickRecorderMimeType(kind);\n let mr: MediaRecorder;\n try {\n mr = preferredMimeType\n ? new MediaRecorder(stream, { mimeType: preferredMimeType })\n : new MediaRecorder(stream);\n } catch (e) {\n // TEMPORARY diagnostic\n logRecordDialog('recorder_create_fallback', { preferredMimeType, ...errorFields(e) });\n mr = new MediaRecorder(stream);\n }\n // TEMPORARY diagnostic\n logRecordDialog('recorder_created', { preferredMimeType, mimeType: mr.mimeType });\n mediaRecorderRef.current = mr;\n recordChunksRef.current = [];\n\n mr.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) recordChunksRef.current.push(e.data);\n };\n\n mr.onerror = (e) => {\n // TEMPORARY diagnostic\n logRecordDialog('recorder_error', { type: e.type });\n };\n\n mr.onstop = () => {\n // TEMPORARY diagnostic\n logRecordDialog('recorder_stopped', { chunks: recordChunksRef.current.length });\n const blob = new Blob(recordChunksRef.current, {\n type: mr.mimeType || preferredMimeType || (wantVideo ? 'video/webm' : 'audio/webm'),\n });\n\n const rawFile = new File([blob], recordingFileName(blob.type, kind), { type: blob.type });\n const file = ensureFileWithContentType(rawFile, wantVideo ? 'video' : 'audio');\n const url = makeRecordUrl(file);\n\n setRecordedFile(file);\n setRecordPreviewUrl(url);\n setRecorderState('preview');\n\n teardownCapture();\n };\n\n try {\n mr.start();\n } catch (e) {\n // TEMPORARY diagnostic\n logRecordDialog('recorder_start_failed', errorFields(e));\n throw e;\n }\n // TEMPORARY diagnostic\n logRecordDialog('recorder_started', { state: mr.state });\n setRecorderState('recording');\n startElapsedTimer();\n\n if (maxRecordDurationSec && maxRecordDurationSec > 0) {\n autoStopTimeoutRef.current = setTimeout(\n () => stopIfRecording(mediaRecorderRef.current),\n autoStopDelayMs(maxRecordDurationSec),\n );\n }\n },\n [\n acquireStream,\n maxRecordDurationSec,\n makeRecordUrl,\n revokeLastRecordUrl,\n teardownCapture,\n startElapsedTimer,\n ],\n );\n\n const stopRecording = useCallback(() => {\n const mr = mediaRecorderRef.current;\n if (mr && mr.state === 'recording') {\n try {\n mr.stop();\n } catch {\n teardownCapture();\n setRecorderState('idle');\n }\n } else {\n teardownCapture();\n setRecorderState('idle');\n }\n }, [teardownCapture]);\n\n const handleReRecord = useCallback(() => {\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setElapsedMs(0);\n setError(null);\n setRecorderState('idle');\n void startLivePreview(recordKind);\n }, [revokeLastRecordUrl, startLivePreview, recordKind]);\n\n const handleSelectKind = useCallback(\n (kind: 'audio' | 'video') => {\n setRecordKind(kind);\n setError(null);\n void startLivePreview(kind);\n },\n [startLivePreview],\n );\n\n const reportError = useCallback((e: unknown) => {\n setError(e instanceof Error ? e.message : 'Something went wrong with the recording.');\n }, []);\n\n const start = useAsyncAction((kind: 'audio' | 'video') => startRecording(kind), {\n onError: reportError,\n });\n\n const save = useAsyncAction(\n async () => {\n if (!recordedFile || !recordPreviewUrl) return;\n await onRecorded(recordedFile, recordPreviewUrl, recordKind);\n onOpenChange(false);\n },\n { onError: reportError },\n );\n\n const closeDialogImmediately = useCallback(() => {\n stopIfRecording(mediaRecorderRef.current);\n teardownCapture();\n revokeLastRecordUrl();\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setRecorderState('idle');\n setDiscardConfirmOpen(false);\n onOpenChange(false);\n }, [teardownCapture, revokeLastRecordUrl, onOpenChange]);\n\n const handleDialogOpenChange = useCallback(\n (v: boolean) => {\n if (v) return;\n if (save.pending) return;\n if (recorderState === 'preview') {\n setDiscardConfirmOpen(true);\n return;\n }\n closeDialogImmediately();\n },\n [recorderState, save.pending, closeDialogImmediately],\n );\n\n const isRecording = recorderState === 'recording';\n const isPreview = recorderState === 'preview';\n const isIdle = recorderState === 'idle';\n const acquiring = streamStatus === 'acquiring';\n\n const elapsedLabel = maxRecordDurationSec\n ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`\n : formatDuration(elapsedMs);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Record</DialogTitle>\n <DialogDescription>\n {isIdle && 'Choose audio or video, then press Start.'}\n {isRecording && 'Recording…'}\n {isPreview && 'Review your recording. Save to upload, or Re-record to try again.'}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"flex flex-wrap gap-2\">\n {canRecVideo && (\n <Button\n type=\"button\"\n variant={recordKind === 'video' ? 'default' : 'secondary'}\n aria-pressed={recordKind === 'video'}\n onClick={() => handleSelectKind('video')}\n disabled={!isIdle || acquiring}\n icon={<Video className=\"icon-xs\" />}\n >\n Video\n </Button>\n )}\n {canRecAudio && (\n <Button\n type=\"button\"\n variant={recordKind === 'audio' ? 'default' : 'secondary'}\n aria-pressed={recordKind === 'audio'}\n onClick={() => handleSelectKind('audio')}\n disabled={!isIdle || acquiring}\n icon={<Mic className=\"icon-xs\" />}\n >\n Audio\n </Button>\n )}\n {canPhoto && onRequestPhoto && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n onRequestPhoto();\n onOpenChange(false);\n }}\n disabled={!isIdle}\n icon={<Camera className=\"icon-xs\" />}\n >\n Photo\n </Button>\n )}\n </div>\n\n {recordKind === 'video' && !isPreview && (\n <div className=\"relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video\">\n <video\n ref={livePreviewRef}\n className=\"w-full h-full object-contain\"\n playsInline\n muted\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting camera\" />\n </div>\n )}\n </div>\n )}\n\n {recordKind === 'audio' && !isPreview && (\n <div className=\"relative mt-2 rounded-md bg-muted p-2 text-primary\">\n <canvas\n ref={waveformCanvasRef}\n width={480}\n height={80}\n className=\"w-full h-20\"\n aria-label=\"Audio waveform\"\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting microphone\" />\n </div>\n )}\n </div>\n )}\n\n {error && (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {error}\n </p>\n )}\n\n {isRecording && (\n <div className=\"flex items-center gap-2 text-sm\" aria-live=\"polite\">\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse\"\n aria-label=\"Recording\"\n />\n <span className=\"tabular-nums\">{elapsedLabel}</span>\n </div>\n )}\n\n {isPreview && recordPreviewUrl && (\n <div className=\"mt-2\">\n <MediaPreview\n url={recordPreviewUrl}\n type={recordKind}\n controls\n priority\n className=\"w-full rounded-md\"\n />\n </div>\n )}\n\n <DialogFooter className=\"flex-row justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleDialogOpenChange(false)}\n disabled={save.pending}\n icon={<X className=\"icon-xs\" />}\n >\n Close\n </Button>\n\n {isIdle && (\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => {\n // TEMPORARY diagnostic\n logRecordDialog('start_click', { kind: recordKind, streamStatus });\n void start.run(recordKind);\n }}\n disabled={disabled || isLoading || acquiring}\n pending={start.pending}\n icon={\n recordKind === 'video' ? (\n <Video className=\"icon-xs\" />\n ) : (\n <Mic className=\"icon-xs\" />\n )\n }\n >\n Start\n </Button>\n )}\n\n {isRecording && (\n <Button type=\"button\" variant=\"default\" onClick={stopRecording}>\n Stop\n </Button>\n )}\n\n {isPreview && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={handleReRecord}\n disabled={save.pending}\n icon={<RotateCcw className=\"icon-xs\" />}\n >\n Re-record\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void save.run()}\n disabled={disabled || isLoading}\n pending={save.pending}\n icon={<Check className=\"icon-xs\" />}\n >\n Save\n </Button>\n </div>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n <AlertDialog open={discardConfirmOpen} onOpenChange={setDiscardConfirmOpen}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Discard recording?</AlertDialogTitle>\n <AlertDialogDescription>\n You have a recorded clip that hasn't been saved. Closing now will discard it.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep recording</AlertDialogCancel>\n <AlertDialogAction onClick={closeDialogImmediately}>Discard</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/file-input",
3
- "version": "0.11.18",
3
+ "version": "0.11.19",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",
@@ -31,9 +31,9 @@
31
31
  "prepublishOnly": "npm run clean && npm run build"
32
32
  },
33
33
  "dependencies": {
34
- "@ttt-productions/ui-core": "^0.11.36",
34
+ "@ttt-productions/ui-core": "^0.11.37",
35
35
  "@ttt-productions/media-schemas": "^0.14.13",
36
- "@ttt-productions/media-viewer": "^0.10.20",
36
+ "@ttt-productions/media-viewer": "^0.10.21",
37
37
  "react-easy-crop": "^6.2.3"
38
38
  },
39
39
  "peerDependencies": {