1// Audio & Speech Recognition Module 2// Extracted from main.js for better modularity 3 4/** 5 * Speech Recognition Manager 6 * Handles browser-based speech recognition functionality 7 */ 8class SpeechRecognitionManager { 9 constructor() { 10 this.currentRecognition = null; 11 this.isRecording = false; 12 this.mediaRecorder = null; 13 this.audioChunks = []; 14 } 15 16 /** 17 * Check if speech recognition is supported 18 * @returns {boolean} - Whether speech recognition is supported 19 */ 20 isSupported() { 21 return 'webkitSpeechRecognition' in window || 'SpeechRecognition' in window; 22 } 23 24 /** 25 * Toggle recording state 26 */ 27 toggleRecording() { 28 if (this.isRecording) { 29 this.stopRecording(); 30 } else { 31 this.startRecording(); 32 } 33 } 34 35 /** 36 * Start recording/speech recognition 37 * @param {Object} options - Configuration options 38 * @param {Function} options.onResult - Callback for speech result 39 * @param {Function} options.onError - Callback for errors 40 * @param {Function} options.onStatusUpdate - Callback for status updates 41 * @param {HTMLElement} options.recordButton - Record button element 42 */ 43 async startRecording(options = {}) { 44 const { 45 onResult = () => {}, 46 onError = () => {}, 47 onStatusUpdate = () => {}, 48 recordButton = null 49 } = options; 50 51 try { 52 if (this.isSupported()) { 53 console.log('Using Web Speech API directly'); 54 this.isRecording = true; 55 if (recordButton) recordButton.classList.add('recording'); 56 57 await navigator.mediaDevices.getUserMedia({ audio: true }); 58 onStatusUpdate('Listening...'); 59 60 this.handleSpeechToText(onResult, onError, onStatusUpdate) 61 .then(() => { 62 this.isRecording = false; 63 if (recordButton) recordButton.classList.remove('recording'); 64 }) 65 .catch(error => { 66 console.error('Speech recognition error:', error); 67 this.isRecording = false; 68 if (recordButton) recordButton.classList.remove('recording'); 69 onError(error); 70 }); 71 return; 72 } 73 else if (window.MediaRecorder) { 74 console.log('Using MediaRecorder for recording visualization'); 75 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 76 77 let mimeType = 'audio/webm'; 78 if (MediaRecorder.isTypeSupported('audio/webm')) { 79 mimeType = 'audio/webm'; 80 } else if (MediaRecorder.isTypeSupported('audio/mp4')) { 81 mimeType = 'audio/mp4'; 82 } 83 84 console.log('Using format:', mimeType); 85 this.mediaRecorder = new MediaRecorder(stream, { mimeType }); 86 this.audioChunks = []; 87 88 this.mediaRecorder.addEventListener('dataavailable', event => { 89 this.audioChunks.push(event.data); 90 }); 91 92 this.mediaRecorder.addEventListener('stop', () => { 93 this.handleSpeechToText(onResult, onError, onStatusUpdate); 94 }); 95 96 this.mediaRecorder.start(); 97 this.isRecording = true; 98 if (recordButton) recordButton.classList.add('recording'); 99 onStatusUpdate('Recording...'); 100 } else { 101 onError(new Error('Speech recognition not supported in this browser')); 102 } 103 } catch (err) { 104 console.error('Error starting recording:', err); 105 onError(new Error('Error accessing microphone')); 106 } 107 } 108 109 /** 110 * Stop recording 111 * @param {Function} onStatusUpdate - Callback for status updates 112 * @param {HTMLElement} recordButton - Record button element 113 */
114 stopRecording(onStatusUpdate = () => {}, recordButton = null) { 115 if (this.mediaRecorder && this.mediaRecorder.state === 'recording') { 116 this.mediaRecorder.stop(); 117 this.isRecording = false; 118 if (recordButton) recordButton.classList.remove('recording'); 119 onStatusUpdate('Processing audio...'); 120 } else if (this.isRecording) { 121 onStatusUpdate('Finishing up...'); 122 } 123 } 124 125 /** 126 * Start browser speech recognition 127 * @param {Function} onResult - Callback for speech result 128 * @param {Function} onError - Callback for errors 129 * @param {Function} onStatusUpdate - Callback for status updates 130 */ 131 startBrowserSpeechRecognition(onResult = () => {}, onError = () => {}, onStatusUpdate = () => {}) { 132 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 133 if (!SpeechRecognition) { 134 console.error('Browser speech recognition not supported'); 135 onError(new Error('Speech recognition not supported in this browser')); 136 return; 137 } 138 139 try { 140 const recognition = new SpeechRecognition(); 141 recognition.lang = 'en-US'; 142 recognition.interimResults = false; 143 recognition.maxAlternatives = 1; 144 145 // Show loading indicator if available 146 if (window.showCopilotTicker) { 147 window.showCopilotTicker('Listening for your voice...'); 148 } 149 150 recognition.start(); 151 152 recognition.onresult = (event) => { 153 const transcript = event.results[0][0].transcript; 154 console.log('Browser speech recognition result:', transcript); 155 156 if (transcript && transcript.trim()) { 157 onResult(transcript.trim()); 158 onStatusUpdate('Recognition complete. Press Send to continue.'); 159 } else { 160 onStatusUpdate('Could not understand speech. Please try again or type your message.'); 161 } 162 163 if (window.hideCopilotTicker) { 164 window.hideCopilotTicker(); 165 } 166 }; 167 168 recognition.onerror = (event) => { 169 console.error('Speech recognition error:', event.error); 170 onError(new Error(`Speech recognition error: ${event.error}`)); 171 172 if (window.hideCopilotTicker) { 173 window.hideCopilotTicker(); 174 } 175 }; 176 177 recognition.onend = () => { 178 console.log('Browser speech recognition ended'); 179 if (window.hideCopilotTicker) { 180 window.hideCopilotTicker(); 181 } 182 }; 183 184 } catch (error) { 185 console.error('Error initializing speech recognition:', error); 186 onError(new Error('Could not initialize speech recognition')); 187 } 188 } 189 190 /** 191 * Handle speech to text conversion 192 * @param {Function} onResult - Callback for speech result 193 * @param {Function} onError - Callback for errors 194 * @param {Function} onStatusUpdate - Callback for status updates 195 */ 196 async handleSpeechToText(onResult = () => {}, onError = () => {}, onStatusUpdate = () => {}) { 197 onStatusUpdate("Initializing speech recognition..."); 198 199 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 200 if (!SpeechRecognition) { 201 console.error('Browser speech recognition not supported'); 202 onError(new Error('Speech recognition not supported in this browser')); 203 return; 204 } 205 206 try { 207 const recognition = new SpeechRecognition(); 208 recognition.lang = 'en-US'; 209 recognition.interimResults = false; 210 recognition.continuous = false; 211 recognition.maxAlternatives = 1; 212 213 if (window.showCopilotTicker) { 214 window.showCopilotTicker('Listening for your voice...'); 215 } 216 217 recognition.start(); 218 219 recognition.onresult = (event) => { 220 const transcript = event.results[0][0].transcript; 221 console.log('Speech recognition result:', transcript); 222 223 if (transcript && transcript.trim()) { 224 onResult(transcript.trim()); 225 onStatusUpdate('Recognition complete. Press Send to continue.'); 226 } else { 227 onStatusUpdate('Could not understand speech. Please try again or type your message.'); 228 } 229 230 if (window.hideCopilotTicker) { 231 window.hideCopilotTicker(); 232 } 233 }; 234 235 recognition.onerror = (event) => { 236 console.error('Speech recognition error:', event.error); 237 onError(new Error(`Speech recognition error: ${event.error}`)); 238 239 if (window.hideCopilotTicker) { 240 window.hideCopilotTicker(); 241 } 242 }; 243 244 recognition.onend = () => { 245 console.log('Speech recognition ended'); 246 if (window.hideCopilotTicker) { 247 window.hideCopilotTicker(); 248 } 249 }; 250 251 } catch (error) { 252 console.error("Speech recognition failed:", error); 253 onError(error); 254 throw error; 255 } 256 } 257 258 /** 259 * Get current recording state 260 * @returns {boolean} - Whether currently recording 261 */ 262 isCurrentlyRecording() { 263 return this.isRecording; 264 } 265 266 /** 267 * Cleanup and reset 268 */ 269 cleanup() { 270 if (this.mediaRecorder && this.mediaRecorder.state === 'recording') { 271 this.mediaRecorder.stop(); 272 } 273 274 if (this.currentRecognition) { 275 this.currentRecognition.stop(); 276 } 277 278 this.isRecording = false;
279 this.audioChunks = []; 280 } 281} 282 283// Export for use in other modules 284if (typeof module !== 'undefined' && module.exports) { 285 // Node.js environment 286 module.exports = { 287 SpeechRecognitionManager 288 }; 289} else { 290 // Browser environment - attach to window 291 window.SpeechRecognitionManager = SpeechRecognitionManager; 292 293 // Create global instance for backward compatibility 294 window.speechRecognitionManager = new SpeechRecognitionManager(); 295}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.