From 03452582476fcd6e1e9cbffb7203e0f6e5b4c57d Mon Sep 17 00:00:00 2001 From: kcar Date: Thu, 21 May 2026 19:32:43 +0000 Subject: [PATCH] fix: transcription getUserMedia called before enumerateDevices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old startTranscription() called enumerateDevices() first to find a device ID, then getUserMedia. Without microphone permission, enumerateDevices() returns devices with deviceId="" (empty string, falsy). This caused the !this.selectedDeviceId check to bail out early, never calling getUserMedia, never prompting the user for mic permission, and never creating the WebSocket. Result: user clicks Start Recording → isRecording=true → button changes to Stop Recording → but no mic prompt, no WebSocket, no transcription. Fix: call getUserMedia directly (with optional echoCancellation/noiseSuppression if no device pre-selected). getUserMedia triggers the browser permission prompt automatically. Device selection is still honoured via exact deviceId constraint when one has been explicitly chosen. Co-Authored-By: Claude Sonnet 4.6 --- .../cc-transcription/transcriptionService.tsx | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/utils/tldraw/cc-base/cc-transcription/transcriptionService.tsx b/src/utils/tldraw/cc-base/cc-transcription/transcriptionService.tsx index 31a0403..7e27112 100644 --- a/src/utils/tldraw/cc-base/cc-transcription/transcriptionService.tsx +++ b/src/utils/tldraw/cc-base/cc-transcription/transcriptionService.tsx @@ -26,28 +26,19 @@ export class TranscriptionService { async startTranscription(config: TranscriptionConfig = {}) { console.log('🎙️ Starting transcription service...'); - + try { - // Get default audio device if none selected - if (!this.selectedDeviceId) { - console.log('No device selected, getting default device...'); - const devices = await navigator.mediaDevices.enumerateDevices(); - const audioDevice = devices.find(device => device.kind === 'audioinput'); - if (audioDevice) { - this.selectedDeviceId = audioDevice.deviceId; - console.log('Found default audio device:', audioDevice.label); - } - } + logger.info('transcription-service', '🔊 Requesting microphone access...'); - if (!this.selectedDeviceId) { - logger.error('transcription-service', '⚠️ No audio device available'); - return; - } + // Call getUserMedia directly — this triggers the browser permission prompt. + // The old code called enumerateDevices() first to find a device ID, but + // without microphone permission deviceId is always "" (empty string, falsy), + // causing an early return that never prompted the user for permission. + const audioConstraints: MediaTrackConstraints = this.selectedDeviceId + ? { deviceId: { exact: this.selectedDeviceId } } + : { echoCancellation: true, noiseSuppression: true }; - logger.info('transcription-service', '🔊 Accessing user media...'); - this.stream = await navigator.mediaDevices.getUserMedia({ - audio: { deviceId: this.selectedDeviceId }, - }); + this.stream = await navigator.mediaDevices.getUserMedia({ audio: audioConstraints }); console.log('Got audio stream'); const uuid = crypto.randomUUID();