diff options
| author | cashmere <cashmere@autistici.org> | 2026-08-16 06:16:56 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-08-16 06:16:56 +0200 |
| commit | f1043734438c8518a302f698d9ae6583a75f0c14 (patch) | |
| tree | d3e636a96e6349c1ad17afa7b28c9ffe5c882e37 /apps/web/src/voiceover/recorder.ts | |
| parent | 37d22b1e390bd6cea04bf8e15069281ee125cf10 (diff) | |
| download | opencut-f1043734438c8518a302f698d9ae6583a75f0c14.tar.gz | |
feat: add transitions, track mixing, and voiceover controls
Diffstat (limited to 'apps/web/src/voiceover/recorder.ts')
| -rw-r--r-- | apps/web/src/voiceover/recorder.ts | 40 |
1 files changed, 38 insertions, 2 deletions
diff --git a/apps/web/src/voiceover/recorder.ts b/apps/web/src/voiceover/recorder.ts index b71ca74f..48322898 100644 --- a/apps/web/src/voiceover/recorder.ts +++ b/apps/web/src/voiceover/recorder.ts @@ -5,6 +5,8 @@ * actual microphone stream. */ +import { buildVoiceOverAudioConstraints } from "@/voiceover/devices"; + export const PREFERRED_MIME_TYPES = [ "audio/webm;codecs=opus", "audio/webm", @@ -95,6 +97,11 @@ interface ActiveRecording { startedAtMs: number; } +export interface VoiceOverRecordingSession { + stream: MediaStream; + mimeType: string; +} + let activeRecording: ActiveRecording | null = null; function stopStreamTracks({ stream }: { stream: MediaStream }): void { @@ -103,7 +110,15 @@ function stopStreamTracks({ stream }: { stream: MediaStream }): void { } } -export async function startVoiceOverRecording(): Promise<void> { +export function getActiveVoiceOverRecordingStream(): MediaStream | null { + return activeRecording?.stream ?? null; +} + +export async function startVoiceOverRecording({ + deviceId, +}: { + deviceId?: string | null; +} = {}): Promise<VoiceOverRecordingSession> { if (activeRecording) { throw new Error("A voice-over recording is already in progress"); } @@ -113,7 +128,24 @@ export async function startVoiceOverRecording(): Promise<void> { throw new Error("Voice-over recording is not supported in this browser"); } - const stream = await mediaDevices.getUserMedia({ audio: true }); + let stream: MediaStream; + try { + stream = await mediaDevices.getUserMedia( + buildVoiceOverAudioConstraints({ deviceId }), + ); + } catch (error) { + if ( + deviceId && + error instanceof DOMException && + (error.name === "NotFoundError" || error.name === "OverconstrainedError") + ) { + stream = await mediaDevices.getUserMedia( + buildVoiceOverAudioConstraints({ deviceId: null }), + ); + } else { + throw error; + } + } try { const mimeType = pickSupportedMimeType({ isTypeSupported: (candidate) => MediaRecorder.isTypeSupported(candidate), @@ -135,6 +167,10 @@ export async function startVoiceOverRecording(): Promise<void> { mimeType: recorder.mimeType || mimeType || "audio/webm", startedAtMs: performance.now(), }; + return { + stream, + mimeType: activeRecording.mimeType, + }; } catch (error) { stopStreamTracks({ stream }); throw error; |
