From 49dba2c43e5f63975b9f59061fc4874471ddbd0f Mon Sep 17 00:00:00 2001 From: egg Date: Wed, 17 Dec 2025 16:47:09 +0800 Subject: [PATCH] fix: Improve microphone permission handling and audio capture robustness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add device enumeration check before attempting to capture audio - Use simpler audio constraints (audio: true) instead of specific options - Add fallback to explicit device ID if simple constraints fail - Add more descriptive error messages for different failure modes - Enhance Electron permission handlers with better logging - Add setDevicePermissionHandler for audio device access - Include 'microphone' in allowed permissions list 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- client/src/main.js | 21 ++++++++++++++---- client/src/pages/meeting-detail.html | 33 ++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/client/src/main.js b/client/src/main.js index afd85ef..cda103f 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -446,8 +446,10 @@ app.whenReady().then(async () => { loadConfig(); // Grant microphone permission automatically - session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { - const allowedPermissions = ['media', 'mediaKeySystem', 'audioCapture']; + session.defaultSession.setPermissionRequestHandler((webContents, permission, callback, details) => { + console.log(`Permission request: ${permission}`, details); + // Allow all media-related permissions + const allowedPermissions = ['media', 'mediaKeySystem', 'audioCapture', 'microphone']; if (allowedPermissions.includes(permission)) { console.log(`Granting permission: ${permission}`); callback(true); @@ -458,11 +460,22 @@ app.whenReady().then(async () => { }); // Also handle permission check (for some Electron versions) - session.defaultSession.setPermissionCheckHandler((webContents, permission) => { - const allowedPermissions = ['media', 'mediaKeySystem', 'audioCapture']; + session.defaultSession.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => { + console.log(`Permission check: ${permission}`, { requestingOrigin, details }); + const allowedPermissions = ['media', 'mediaKeySystem', 'audioCapture', 'microphone']; return allowedPermissions.includes(permission); }); + // Set device permission handler for media devices + session.defaultSession.setDevicePermissionHandler((details) => { + console.log('Device permission request:', details); + // Allow all audio devices + if (details.deviceType === 'audio' || details.deviceType === 'hid') { + return true; + } + return false; + }); + // Start backend sidecar if embedded mode is enabled startBackendSidecar(); diff --git a/client/src/pages/meeting-detail.html b/client/src/pages/meeting-detail.html index 35cca35..1fe6157 100644 --- a/client/src/pages/meeting-detail.html +++ b/client/src/pages/meeting-detail.html @@ -405,9 +405,26 @@ return; } - mediaStream = await navigator.mediaDevices.getUserMedia({ - audio: { echoCancellation: true, noiseSuppression: true } - }); + // Check for available audio devices first + const devices = await navigator.mediaDevices.enumerateDevices(); + const audioInputs = devices.filter(d => d.kind === 'audioinput'); + console.log('Available audio inputs:', audioInputs.length, audioInputs); + + if (audioInputs.length === 0) { + alert('No microphone found. Please connect a microphone and try again.'); + return; + } + + // Try with simple constraints first, fall back to more specific ones + try { + mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); + } catch (simpleErr) { + console.warn('Simple audio constraints failed, trying with device ID:', simpleErr); + // Try with explicit device ID + mediaStream = await navigator.mediaDevices.getUserMedia({ + audio: { deviceId: audioInputs[0].deviceId } + }); + } isRecording = true; recordBtn.textContent = 'Stop Recording'; @@ -422,7 +439,15 @@ } catch (error) { console.error('Start recording error:', error); - alert('Error starting recording: ' + error.message); + let errorMsg = 'Error starting recording: ' + error.message; + if (error.name === 'NotAllowedError') { + errorMsg = 'Microphone access denied. Please grant permission and try again.'; + } else if (error.name === 'NotFoundError') { + errorMsg = 'No microphone found. Please connect a microphone and try again.'; + } else if (error.name === 'NotReadableError') { + errorMsg = 'Microphone is in use by another application. Please close other apps using the microphone.'; + } + alert(errorMsg); await cleanupRecording(); } }