1// ***************************************************************************************************** 2// ******* speak2web UNIVERSAL VOICE SEARCH *********** 3// ******* Get your subscription at *********** 4// ******* https://speak2web.com/plugin#plans *********** 5// ******* Need support? https://speak2web.com/support *********** 6// ******* Licensed GPLv2+ *********** 7//****************************************************************************************************** 8 9// Cross browser 'trim()' funtion support 10if (typeof String.prototype.trim !== 'function') { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); } } 11 12var respTimeOut = false; 13var errcnt = 0; 14let uvsHostName = typeof(uvsCurrentHostName) != 'undefined' ? uvsCurrentHostName : null; 15 16/** 17 * An audio player handler Object 18 * 19 */ 20var uvsAudioPlayer = { 21 'htmlAudioElement': document.createElement('AUDIO'), 22 'lastFilePath': null, 23 'antiMuteButtonPlaybacks': [uvsSilenceSoundPath], 24 'isAntiMutePlayback': false, 25 'configure': function(filePath = null, playbackEndedCallback = null) { 26 try { 27 let pathOfFile = typeof filePath != 'undefined' && filePath ? filePath : null; 28 29 if (pathOfFile) { 30 let partialFilePath = !!uvsIsMuteSimon ? uvsSilenceSoundPath : pathOfFile; 31 this.htmlAudioElement.src = _uvsPath + partialFilePath; 32 this.htmlAudioElement.preload = 'auto'; 33 this.lastFilePath = pathOfFile; 34 35 if (this.antiMuteButtonPlaybacks.indexOf(pathOfFile) !== -1) { 36 this.isAntiMutePlayback = true; 37 } else { 38 this.isAntiMutePlayback = false; 39 } 40 } else { 41 this.htmlAudioElement.src = ''; 42 this.isAntiMutePlayback = false; 43 } 44 45 /** 46 * The play event occurs when the audio has been started or is no longer paused. 47 */ 48 this.htmlAudioElement.onplay = function() { 49 // Do nothing for now 50 }.bind(this); 51 52 /** 53 * The ended event occurs when the audio has reached the end. 54 */ 55 this.htmlAudioElement.onended = function() { 56 this.htmlAudioElement.src = '' 57 this.isAntiMutePlayback = false; 58 59 // Callback to be executed when video playback ends 60 if (pathOfFile && (typeof playbackEndedCallback === "function")) { 61 playbackEndedCallback(); 62 playbackEndedCallback = null; 63 } 64 }.bind(this); 65 66 /** 67 * The error event occurs when an error occurred during the loading of an audio 68 */ 69 this.htmlAudioElement.onerror = function() { 70 this.isAntiMutePlayback = false; 71 }.bind(this); 72 73 /** 74 * The playing event occurs when the audio is playing after having been paused or stopped for buffering. 75 */ 76 this.htmlAudioElement.onplaying = function() { 77 // Do nothing for now 78 }.bind(this); 79 } catch (err) { 80 this.clear(); 81 this.isAntiMutePlayback = false; 82 } 83 }, 84 'play': function() { 85 try { 86 if (this.htmlAudioElement && !!this.htmlAudioElement.src) { 87 this.htmlAudioElement.play().catch(function(error){ 88 console.log('VDN Exception: Failed to play audio.'); 89 }); 90 } 91 } catch (err) { 92 this.clear(); 93 } 94 }, 95 'stop': function() { 96 try { 97 this.clear(); 98 } catch (err) { 99 this.clear(); 100 } 101 }, 102 'clear': function() { 103 try { 104 if (this.htmlAudioElement) { 105 let duration = isNaN(this.htmlAudioElement.duration) ? 0 : this.htmlAudioElement.duration; 106 this.htmlAudioElement.currentTime = duration; 107 } 108 109 this.lastFilePath = null; 110 } catch (err) { 111 this.lastFilePath = null; 112 this.isAntiMutePlayback = false;
113 } 114 }, 115 'isPlaying': function() { 116 let currentTime = isNaN(this.htmlAudioElement.currentTime) ? 0 : this.htmlAudioElement.currentTime; 117 let duration = isNaN(this.htmlAudioElement.duration) ? 0 : this.htmlAudioElement.duration; 118 119 return currentTime < duration; 120 } 121}; 122 123function stt(blob, errorRecovery, cb) { 124 if (errorRecovery == false) { 125 let i = Math.floor(Math.random() * 10); 126 let resp = uvsAlternativeResponse['randomLib']; 127 128 if (respTimeOut == false) { 129 // Play 'random' playback 130 uvsAudioPlayer.configure(resp[i]); 131 uvsAudioPlayer.play(); 132 133 respTimeOut = true; 134 135 setTimeout(function () { 136 respTimeOut = false; 137 }, 6000); 138 } 139 } 140 141} 142 143/** 144 * Function to make asynch call to GCP server for STT 145 * 146 * @param String base64AudioStr Base64 String representation of Audio Blob 147 * @retur Promise 148 * 149 */ 150function uvsGcpStt(base64AudioStr) { 151 return new Promise(function(resolve, reject){ 152 if (!( 153 !!uvsSttLanguageContext['gcp']['endPoint'] && 154 !!uvsSttLanguageContext['gcp']['key'] && 155 !!uvsSttLanguageContext['gcp']['langCode'] && 156 typeof base64AudioStr != 'undefined' && 157 !!base64AudioStr 158 )) 159 { 160 reject(null); 161 return; 162 } 163 164 if (errcnt == 0) { 165 let i = Math.floor(Math.random() * 10); 166 let resp = uvsAlternativeResponse['randomLib']; 167 168 // Play 'random' playback 169 uvsAudioPlayer.configure(resp[i]); 170 uvsAudioPlayer.play(); 171 } 172 173 let uvsXhr = new XMLHttpRequest(); 174 175 uvsXhr.onreadystatechange = function () { 176 if (this.readyState == 4) { 177 try { 178 let res = JSON.parse(this.response); 179 180 if (this.status === 200) { 181 errcnt = 0; 182 let results = typeof res != 'undefined' && res instanceof Object && 'results' in res ? res['results'] : []; 183 let efficientResult = !!results && results.length > 0 && results[0] instanceof Object ? results[0] : {}; 184 let alternatives = 'alternatives' in efficientResult && !!efficientResult['alternatives'] ? efficientResult['alternatives'] : []; 185 let alternativeObj = alternatives.length > 0 && alternatives[0] instanceof Object ? alternatives[0] : {}; 186 let transcript = 'transcript' in alternativeObj && !!alternativeObj['transcript'] ? alternativeObj['transcript'] : null; 187 188 if (typeof transcript != 'undefined' && !!transcript) { 189 resolve(transcript); 190 } else { 191 reject(null); 192 } 193 } else { 194 // Handle response errors 195 let error = 'error' in res ? res['error'] : {}; 196 let message = 'message' in error && !!error['message'] ? error['message'].toLowerCase() : ''; 197 198 if (errcnt < 1 && !!message && message.indexOf('api key') !== -1) { 199 errcnt++; 200 201 //$$$$$$$$$$$$$$$ FETCH NEW TOKEN MIGHT HAVE EXPIRED $$$$$$$$$$$$$$$$$$ 202 uvsRefreshVoiceServicesKeys().then(function(result) { 203 uvsSttLanguageContext['gcp']['key'] = result; 204 205 // Try to transcript again with updated key 206 uvsGcpStt().then(function(res){ 207 if (!!res) { 208 resolve(res); 209 } else { 210 errcnt = 0; 211 reject(null); 212 } 213 }).catch(function(err){ 214 errcnt = 0; 215 reject(null); 216 }) 217 }).catch(function(error) { 218 alert(error); 219 errcnt = 0; 220 reject(null); 221 }); 222 } else { 223 errcnt = 0; 224 reject(null); 225 } 226 } 227 } catch(err) { 228 reject(null); 229 } 230 } 231 } 232 233 // Handle parsing or transmission errors 234 uvsXhr.onerror = function(error) { reject(null); } 235 236 uvsXhr.open("POST", uvsSttLanguageContext['gcp']['endPoint'] + uvsSttLanguageContext['gcp']['qs']['key'] + uvsSttLanguageContext['gcp']['key'], true); 237 uvsXhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 238 239 let recognitionConfig = { 240 'config': { 241 'encoding': 'ENCODING_UNSPECIFIED', 242 'languageCode': uvsSttLanguageContext['gcp']['langCode'], 243 'enableWordTimeOffsets': false, 244 }, 245 'audio': { 246 'content': base64AudioStr 247 }, 248 }; 249
250 uvsXhr.send(JSON.stringify(recognitionConfig, null, true)); 251 }) 252} 253 254/** 255 * Function to log STT service call 256 * 257 * @param {uvsUpdateLastValue - int/Number} : 0 to not to update last value or 1 to update last value 258 */ 259function uvsLogServiceCall(uvsUpdateLastValue = 0) { 260 try { 261 let uvsXhr = new XMLHttpRequest(); 262 263 uvsXhr.onreadystatechange = function () { 264 if (this.readyState == 4 && this.status == 200) { 265 let res = JSON.parse(this.responseText); 266 267 // Update localized variables of service log 268 uvsServiceLogs.updatedAt = res.updatedAt || uvsServiceLogs.updatedAt; 269 uvsServiceLogs.currentValue = res.currentValue || uvsServiceLogs.currentValue; 270 uvsServiceLogs.lastValue = res.lastValue || uvsServiceLogs.lastValue; 271 } 272 }; 273 274 uvsXhr.open("POST", uvsAjaxObj.ajax_url , true); 275 uvsXhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 276 uvsXhr.send("action=uvs_log_service_call&_ajax_nonce=" + uvsAjaxObj.nonce + "&updateLastValue=" + uvsUpdateLastValue); 277 } catch (err) { 278 // Do nothing for now 279 } 280} 281 282/** 283 * Function to get current host/domain full URL 284 * 285 */ 286function uvsGetCurrentHostURL() { 287 var currentHostUrl = null; 288 try { 289 if (!(typeof(window.location) != 'undefined' 290 && typeof(window.location.hostname) != 'undefined' 291 && typeof(window.location.protocol) != 'undefined')) { 292 return uvsGetHostName(); 293 } 294 295 var thisProtocol = window.location.protocol; 296 var thisHostname = window.location.hostname; 297 298 currentHostUrl = thisProtocol + '//' + thisHostname; 299 } catch (err) { 300 currentHostUrl = uvsGetHostName(); 301 console.log('Something went wrong while discovering current domain.'); 302 } 303 304 return currentHostUrl; 305} 306 307/** 308 * Function to get current host name from backend. 309 */ 310function uvsGetHostName() { 311 return uvsHostName; 312} 313
314/** 315 * Function to obtain voice services token and keys 316 * 317 */ 318function uvsRefreshVoiceServicesKeys() { 319 return new Promise(function(resolve, reject){ 320 let uvsXhr = new XMLHttpRequest(); 321 322 uvsXhr.onreadystatechange = function () { 323 if (this.readyState == 4) { 324 if (this.status === 200) { 325 let res = JSON.parse(this.response); 326 327 if (typeof res == 'undefined') reject(uvsErrorLibrary['outOfService']); 328 329 if (!!uvsSttLanguageContext['gcp']['stt'] && 'gStt' in res && !!res['gStt']) { 330 resolve(res['gStt']); 331 return; 332 } 333 334 reject(uvsErrorLibrary['outOfService']); 335 } else { 336 // Handle response errors 337 reject(uvsErrorLibrary['outOfService']); 338 } 339 } 340 }; 341 342 let queryString = "?action=uvs_refresh_access_keys&_ajax_nonce=" + uvsAjaxObj.keys_nonce; 343 uvsXhr.open("GET", uvsAjaxObj.ajax_url + queryString , true); 344 345 // Handle parsing or transmission errors 346 uvsXhr.onerror = function(error) { reject(uvsErrorLibrary['outOfService']); } 347 348 uvsXhr.send(null); 349 }); 350} 351 352
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.