PageSourceSearch

https://dynamic-salmiakki-095693.netlify.app/js/audio/speech-recognition.js

js dynamic-salmiakki-095693.netlify.app collected 2026-10-03 10:40:35 UTC 10,905 bytes, 295 lines download raw bytes

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.