From a0c956de215bd5a7ec42dfdbb356f45fb2cda9f2 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 2 Jun 2026 18:09:34 +0300 Subject: [PATCH] Auto-deploy: 2026-06-02 18:09:34 --- claude-arabic-voice/offscreen.js | 65 ++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/claude-arabic-voice/offscreen.js b/claude-arabic-voice/offscreen.js index 8495179..91ba766 100644 --- a/claude-arabic-voice/offscreen.js +++ b/claude-arabic-voice/offscreen.js @@ -44,6 +44,7 @@ function initRecognition(language) { // Prevent infinite restart loop on fatal errors if (event.error !== 'no-speech') { isRecording = false; + if (typeof stopMediaTracks === 'function') stopMediaTracks(); } chrome.runtime.sendMessage({ @@ -68,29 +69,54 @@ function initRecognition(language) { return recognition; } +let localStream = null; + +function stopMediaTracks() { + if (localStream) { + localStream.getTracks().forEach(track => track.stop()); + localStream = null; + } +} + // Listen for messages from background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'START_RECORDING') { - try { - const lang = message.payload?.language || 'ar-SA'; - - // Re-init if language changed or not init yet - if (!recognition || recognition.lang !== lang) { - recognition = initRecognition(lang); - } + const lang = message.payload?.language || 'ar-SA'; + + // 1. Get an audio stream first. This is REQUIRED in offscreen documents + // to actually activate the microphone and ensure permissions are active. + navigator.mediaDevices.getUserMedia({ audio: true }) + .then((stream) => { + localStream = stream; + + try { + if (!recognition || recognition.lang !== lang) { + recognition = initRecognition(lang); + } - if (!isRecording) { - recognition.start(); - isRecording = true; - sendResponse({ success: true }); - } else { - sendResponse({ success: true, warning: 'Already recording' }); - } - } catch (e) { - console.error('[Offscreen] Failed to start:', e); - sendResponse({ success: false, error: e.message }); - } - return true; + if (!isRecording) { + recognition.start(); + isRecording = true; + sendResponse({ success: true }); + } else { + sendResponse({ success: true, warning: 'Already recording' }); + } + } catch (e) { + console.error('[Offscreen] Failed to start:', e); + sendResponse({ success: false, error: e.message }); + } + }) + .catch((err) => { + console.error('[Offscreen] getUserMedia failed:', err); + // The user hasn't granted permission yet, or hardware error + sendResponse({ success: false, error: 'not-allowed' }); + chrome.runtime.sendMessage({ + type: 'OFFSCREEN_RECORDING_ERROR', + payload: { error: 'not-allowed' } + }); + }); + + return true; // Keep channel open for async sendResponse } if (message.type === 'STOP_RECORDING') { @@ -102,6 +128,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { console.warn('[Offscreen] Stop failed:', e); } } + stopMediaTracks(); sendResponse({ success: true }); return true; }