1jQuery(document).ready(function($) { 2 const micBtn = $('.voice_search_mic_button, #mic-button'); 3 const overlay = $('#voice-fullscreen-overlay'); 4 const voiceMsg = $('.voice-message'); 5 const voiceClose = $('.voice-close'); 6 const searchInput = $('#search'); 7 const $formContainer = $('.goiSearch'); 8 9 let mediaRecorder = null; 10 let audioChunks = []; 11 let recordingTimeout = null; 12 13 micBtn.on('click', function() { 14 overlay.addClass('active').show(); 15 $formContainer.attr('inert', '').attr('aria-hidden', 'true'); 16 // overlay.attr({ 17 // 'role': 'dialog', 18 // 'aria-modal': 'true', 19 // 'aria-labelledby': 'voice-message', 20 // 'tabindex': '0' 21 // }); 22 23 setTimeout(() => { 24 voiceClose.focus(); 25 trapFocus(overlay); 26 }, 250); 27 const speakNowText = bhashiniAjax.speak_now_text; 28 if (voiceMsg.text().trim() !== speakNowText) { 29 voiceMsg.text(speakNowText); 30 } 31 32 startRecording(); 33 }); 34 35 voiceClose.on('click', function() { 36 stopRecording(true); 37 overlay.fadeOut(200); 38 micBtn.focus(); 39 }); 40 41 $(document).on('keydown', function(e) { 42 if (e.key === 'Escape' && overlay.is(':visible')) { 43 stopRecording(true); 44 overlay.fadeOut(200); 45 micBtn.focus(); 46 } 47 }); 48 function closeOverlay() { 49 stopRecording(true); 50 overlay.removeClass('active').fadeOut(200, function() { 51 overlay.off('keydown.trapFocus').removeAttr('tabindex role aria-modal aria-labelledby'); 52 $formContainer.removeAttr('inert aria-hidden'); 53 }); 54 micBtn.focus(); 55 } 56 57 voiceClose.on('click', closeOverlay); 58 59 $(document).on('keydown.bhashini', function(e) { 60 if (e.key === 'Escape' && overlay.hasClass('active')) { 61 closeOverlay(); 62 } 63 }); 64 function trapFocus(element) { 65 const focusable = element.find('button, [href], input, textarea, [tabindex]:not([tabindex="-1"])').filter(':visible'); 66 const first = focusable.first()[0]; 67 const last = focusable.last()[0]; 68 69 if (!first || !last) return; 70 71 element.off('keydown.trapFocus').on('keydown.trapFocus', function(e) { 72 if (e.key === 'Tab' || e.keyCode === 9) { 73 if (e.shiftKey && document.activeElement === first) { 74 e.preventDefault(); 75 last.focus(); 76 } else if (!e.shiftKey && document.activeElement === last) { 77 e.preventDefault(); 78 first.focus(); 79 } 80 } 81 }); 82 83 setTimeout(() => first.focus(), 50); 84 } 85 86 function startRecording() { 87 let mimeType = 'audio/webm'; 88 if (MediaRecorder.isTypeSupported('audio/wav')) { 89 mimeType = 'audio/wav'; 90 } else if (MediaRecorder.isTypeSupported('audio/webm')) { 91 mimeType = 'audio/webm'; 92 } else { 93 console.error('No supported audio format (WAV or WebM) found.'); 94 voiceMsg.text('Your browser does not support required audio formats.'); 95 setTimeout(() => overlay.fadeOut(300), 2000); 96 return; 97 } 98 console.log('Selected MIME type:', mimeType); 99 100 let language = localStorage.getItem('preferredLanguage'); 101 if (!language || language === 'null') { 102 language = typeof pll_language !== 'undefined' ? pll_language : 'en'; 103 } 104 console.log('Selected language:', language); 105 106 navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { 107 mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType }); 108 audioChunks = []; 109 110 setTimeout(() => { 111 voiceMsg.text(bhashiniAjax.listening_text); 112 }, 1000); 113 // voiceMsg.attr('aria-live', 'off'); 114 // setTimeout(() => { 115 // voiceMsg.text(bhashiniAjax.listening_text) 116 // voiceMsg.attr('aria-live', 'assertive'); 117 // }, 1000); 118 mediaRecorder.ondataavailable = e => { 119 audioChunks.push(e.data); 120 console.log('Audio chunk received, type:', e.data.type); 121 }; 122 123 mediaRecorder.onstop = () => { 124 clearTimeout(recordingTimeout); 125 const audioBlob = new Blob(audioChunks, { type: mimeType }); 126 const reader = new FileReader();
127 reader.readAsDataURL(audioBlob); 128 reader.onloadend = () => { 129 const base64Audio = reader.result.split(',')[1]; 130 const audioFormat = mimeType.includes('wav') ? 'wav' : 'webm'; 131 overlay.attr('aria-busy', 'true'); 132 $.ajax({ 133 url: bhashiniAjax.ajaxurl, 134 type: 'POST', 135 data: { 136 action: 'bhashini_asr', 137 audio_content: base64Audio, 138 language: language, 139 audio_format: audioFormat 140 }, 141 success: (response) => { 142 overlay.attr('aria-busy', 'false'); 143 console.log('Server response:', response); 144 let text = response?.data?.text?.trim()?.toLowerCase(); 145 146 // List of screen reader/system phrases to exclude 147 const excludePhrases = [ 148 "close voice search dialogue button listening", 149 "this page is accessing your microphone change the setting", 150 "this page is accessing your microphone change this", 151 "listening", 152 "speak now", 153 "search my voice button, dialogue speak now, listening", 154 "firefox sharing indicator alert", 155 "dialogue box", 156 "dialogue speed now listening okay query", 157 "you", 158 'firebox sharing educator alert', 159 "close button", 160 "dialogue speed now listening", 161 "dialogue speed, listen", 162 "dialogue's too pale listen", 163 "dialogue speak" 164 ]; 165 166 const isExcluded = excludePhrases.some(phrase => text.includes(phrase)); 167 168 if (response.success && text && text.length >= 3 && !isExcluded) { 169 text = text.replace(/\./g, ''); 170 voiceMsg.text(text); 171 searchInput.val(text).focus(); 172 setTimeout(() => { 173 overlay.fadeOut(200); 174 const form = searchInput[0].form; 175 if (form) form.submit(); 176 }, 2000); 177 } else { 178 console.warn('Text excluded (screen reader or invalid):', text); 179 voiceMsg.text(bhashiniAjax.client_error); 180 setTimeout(() => overlay.fadeOut(300), 2000); 181 } 182 }, 183 error: (xhr, status, error) => { 184 overlay.attr('aria-busy', 'false'); 185 console.error('AJAX error:', status, error, xhr.responseText); 186 voiceMsg.text(bhashiniAjax.server_error_text); 187 setTimeout(() => overlay.fadeOut(300), 2000); 188 } 189 }); 190 }; 191 }; 192 193 mediaRecorder.start(); 194 recordingTimeout = setTimeout(() => { 195 if (mediaRecorder.state !== 'inactive') { 196 mediaRecorder.stop(); 197 } 198 }, 3000); 199 }).catch((err) => { 200 console.error('Microphone error:', err); 201 voiceMsg.text(bhashiniAjax.no_mic_detected); 202 setTimeout(() => overlay.fadeOut(300), 3000); 203 }); 204 } 205 206 function stopRecording(cancel = false) { 207 if (mediaRecorder && mediaRecorder.state !== 'inactive') { 208 mediaRecorder.stop(); 209 } 210 if (cancel) { 211 audioChunks = []; 212 clearTimeout(recordingTimeout); 213 } 214 } 215});
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.