UNPKG

artyom.js

Version:

Artyom is a Robust Wrapper of the Google Chrome SpeechSynthesis and SpeechRecognition that allows you to create a virtual assistent

1,147 lines (1,146 loc) 63.4 kB
/** * Artyom.js is a voice control, speech recognition and speech synthesis JavaScript library. * * @requires {webkitSpeechRecognition && speechSynthesis} * @license MIT * @version 1.0.6 * @copyright 2017 Our Code World (www.ourcodeworld.com) All Rights Reserved. * @author Carlos Delgado (https://github.com/sdkcarlos) and Sema García (https://github.com/semagarcia) * @see https://sdkcarlos.github.io/sites/artyom.html * @see http://docs.ourcodeworld.com/projects/artyom-js */ /// <reference path="artyom.d.ts" /> // Remove "export default " keywords if willing to build with `npm run artyom-build-window` var Artyom = (function () { // Triggered at the declaration of function Artyom() { this.ArtyomCommands = []; this.ArtyomVoicesIdentifiers = { // German "de-DE": ["Google Deutsch", "de-DE", "de_DE"], // Spanish "es-ES": ["Google español", "es-ES", "es_ES", "es-MX", "es_MX"], // Italian "it-IT": ["Google italiano", "it-IT", "it_IT"], // Japanese "jp-JP": ["Google 日本人", "ja-JP", "ja_JP"], // English USA "en-US": ["Google US English", "en-US", "en_US"], // English UK "en-GB": ["Google UK English Male", "Google UK English Female", "en-GB", "en_GB"], // Brazilian Portuguese "pt-BR": ["Google português do Brasil", "pt-PT", "pt-BR", "pt_PT", "pt_BR"], // Portugal Portuguese // Note: in desktop, there's no voice for portugal Portuguese "pt-PT": ["Google português do Brasil", "pt-PT", "pt_PT"], // Russian "ru-RU": ["Google русский", "ru-RU", "ru_RU"], // Dutch (holland) "nl-NL": ["Google Nederlands", "nl-NL", "nl_NL"], // French "fr-FR": ["Google français", "fr-FR", "fr_FR"], // Polish "pl-PL": ["Google polski", "pl-PL", "pl_PL"], // Indonesian "id-ID": ["Google Bahasa Indonesia", "id-ID", "id_ID"], // Hindi "hi-IN": ["Google हिन्दी", "hi-IN", "hi_IN"], // Mandarin Chinese "zh-CN": ["Google 普通话(中国大陆)", "zh-CN", "zh_CN"], // Cantonese Chinese "zh-HK": ["Google 粤語(香港)", "zh-HK", "zh_HK"], // Native voice "native": ["native"] }; // Important: retrieve the voices of the browser as soon as possible. // Normally, the execution of speechSynthesis.getVoices will return at the first time an empty array. if (window.hasOwnProperty('speechSynthesis')) { speechSynthesis.getVoices(); } else { console.error("Artyom.js can't speak without the Speech Synthesis API."); } // This instance of webkitSpeechRecognition is the one used by Artyom. if (window.hasOwnProperty('webkitSpeechRecognition')) { this.ArtyomWebkitSpeechRecognition = new window.webkitSpeechRecognition(); } else { console.error("Artyom.js can't recognize voice without the Speech Recognition API."); } this.ArtyomProperties = { lang: 'en-GB', recognizing: false, continuous: false, speed: 1, volume: 1, listen: false, mode: "normal", debug: false, helpers: { redirectRecognizedTextOutput: null, remoteProcessorHandler: null, lastSay: null, fatalityPromiseCallback: null }, executionKeyword: null, obeyKeyword: null, speaking: false, obeying: true, soundex: false, name: null }; this.ArtyomGarbageCollection = []; this.ArtyomFlags = { restartRecognition: false }; this.ArtyomGlobalEvents = { ERROR: "ERROR", SPEECH_SYNTHESIS_START: "SPEECH_SYNTHESIS_START", SPEECH_SYNTHESIS_END: "SPEECH_SYNTHESIS_END", TEXT_RECOGNIZED: "TEXT_RECOGNIZED", COMMAND_RECOGNITION_START: "COMMAND_RECOGNITION_START", COMMAND_RECOGNITION_END: "COMMAND_RECOGNITION_END", COMMAND_MATCHED: "COMMAND_MATCHED", NOT_COMMAND_MATCHED: "NOT_COMMAND_MATCHED" }; this.Device = { isMobile: false, isChrome: true }; if (navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i)) { this.Device.isMobile = true; } if (navigator.userAgent.indexOf("Chrome") == -1) { this.Device.isChrome = false; } /** * The default voice of Artyom in the Desktop. In mobile, you will need to initialize (or force the language) * with a language code in order to find an available voice in the device, otherwise it will use the native voice. */ this.ArtyomVoice = { default: false, lang: "en-GB", localService: false, name: "Google UK English Male", voiceURI: "Google UK English Male" }; } /** * Add dinamically commands to artyom using * You can even add commands while artyom is active. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/addcommands * @since 0.6 * @param {Object | Array[Objects]} param * @returns {undefined} */ Artyom.prototype.addCommands = function (param) { var _this = this; var processCommand = function (command) { if (command.hasOwnProperty("indexes")) { _this.ArtyomCommands.push(command); } else { console.error("The given command doesn't provide any index to execute."); } }; if (param instanceof Array) { for (var i = 0; i < param.length; i++) { processCommand(param[i]); } } else { processCommand(param); } return true; }; ; /** * The SpeechSynthesisUtterance objects are stored in the artyom_garbage_collector variable * to prevent the wrong behaviour of artyom.say. * Use this method to clear all spoken SpeechSynthesisUtterance unused objects. * * @returns {Array<any>} */ Artyom.prototype.clearGarbageCollection = function () { return this.ArtyomGarbageCollection = []; }; ; /** * Displays a message in the console if the artyom propery DEBUG is set to true. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/debug * @returns {undefined} */ Artyom.prototype.debug = function (message, type) { var preMessage = "[v" + this.getVersion() + "] Artyom.js"; if (this.ArtyomProperties.debug === true) { switch (type) { case "error": console.log("%c" + preMessage + ":%c " + message, 'background: #C12127; color: black;', 'color:black;'); break; case "warn": console.warn(message); break; case "info": console.log("%c" + preMessage + ":%c " + message, 'background: #4285F4; color: #FFFFFF', 'color:black;'); break; default: console.log("%c" + preMessage + ":%c " + message, 'background: #005454; color: #BFF8F8', 'color:black;'); break; } } }; /** * Artyom have it's own diagnostics. * Run this function in order to detect why artyom is not initialized. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/detecterrors * @param {type} callback * @returns {} */ Artyom.prototype.detectErrors = function () { var _this = this; if ((window.location.protocol) == "file:") { var message = "Error: running Artyom directly from a file. The APIs require a different communication protocol like HTTP or HTTPS"; console.error(message); return { code: "artyom_error_localfile", message: message }; } if (!_this.Device.isChrome) { var message = "Error: the Speech Recognition and Speech Synthesis APIs require the Google Chrome Browser to work."; console.error(message); return { code: "artyom_error_browser_unsupported", message: message }; } if (window.location.protocol != "https:") { console.warn("Warning: artyom is being executed using the '" + window.location.protocol + "' protocol. The continuous mode requires a secure protocol (HTTPS)"); } return false; }; /** * Removes all the added commands of artyom. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/emptycommands * @since 0.6 * @returns {Array} */ Artyom.prototype.emptyCommands = function () { return this.ArtyomCommands = []; }; /** * Returns an object with data of the matched element * * @private * @param {string} comando * @returns {MatchedCommand} */ Artyom.prototype.execute = function (voz) { var _this = this; if (!voz) { console.warn("Internal error: Execution of empty command"); return; } // If artyom was initialized with a name, verify that the name begins with it to allow the execution of commands. if (_this.ArtyomProperties.name) { if (voz.indexOf(_this.ArtyomProperties.name) != 0) { _this.debug("Artyom requires with a name \"" + _this.ArtyomProperties.name + "\" but the name wasn't spoken.", "warn"); return; } // Remove name from voice command voz = voz.substr(_this.ArtyomProperties.name.length); } _this.debug(">> " + voz); /** @3 * Artyom needs time to think that */ for (var i = 0; i < _this.ArtyomCommands.length; i++) { var instruction = _this.ArtyomCommands[i]; var opciones = instruction.indexes; var encontrado = -1; var wildy = ""; for (var c = 0; c < opciones.length; c++) { var opcion = opciones[c]; if (!instruction.smart) { continue; //Jump if is not smart command } // Process RegExp if (opcion instanceof RegExp) { // If RegExp matches if (opcion.test(voz)) { _this.debug(">> REGEX " + opcion.toString() + " MATCHED AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info"); encontrado = parseInt(c.toString()); } // Otherwise just wildcards } else { if (opcion.indexOf("*") != -1) { ///LOGIC HERE var grupo = opcion.split("*"); if (grupo.length > 2) { console.warn("Artyom found a smart command with " + (grupo.length - 1) + " wildcards. Artyom only support 1 wildcard for each command. Sorry"); continue; } //START SMART COMMAND var before = grupo[0]; var later = grupo[1]; // Wildcard in the end if ((later == "") || (later == " ")) { if ((voz.indexOf(before) != -1) || ((voz.toLowerCase()).indexOf(before.toLowerCase()) != -1)) { wildy = voz.replace(before, ''); wildy = (wildy.toLowerCase()).replace(before.toLowerCase(), ''); encontrado = parseInt(c.toString()); } } else { if ((voz.indexOf(before) != -1) || ((voz.toLowerCase()).indexOf(before.toLowerCase()) != -1)) { if ((voz.indexOf(later) != -1) || ((voz.toLowerCase()).indexOf(later.toLowerCase()) != -1)) { wildy = voz.replace(before, '').replace(later, ''); wildy = (wildy.toLowerCase()).replace(before.toLowerCase(), '').replace(later.toLowerCase(), ''); wildy = (wildy.toLowerCase()).replace(later.toLowerCase(), ''); encontrado = parseInt(c.toString()); } } } } else { console.warn("Founded command marked as SMART but have no wildcard in the indexes, remove the SMART for prevent extensive memory consuming or add the wildcard *"); } } if ((encontrado >= 0)) { encontrado = parseInt(c.toString()); break; } } if (encontrado >= 0) { _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED); var response = { index: encontrado, instruction: instruction, wildcard: { item: wildy, full: voz } }; return response; } } //End @3 /** @1 * Search for IDENTICAL matches in the commands if nothing matches * start with a index match in commands */ for (var i = 0; i < _this.ArtyomCommands.length; i++) { var instruction = _this.ArtyomCommands[i]; var opciones = instruction.indexes; var encontrado = -1; /** * Execution of match with identical commands */ for (var c = 0; c < opciones.length; c++) { var opcion = opciones[c]; if (instruction.smart) { continue; //Jump wildcard commands } if ((voz === opcion)) { _this.debug(">> MATCHED FULL EXACT OPTION " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info"); encontrado = parseInt(c.toString()); break; } else if ((voz.toLowerCase() === opcion.toLowerCase())) { _this.debug(">> MATCHED OPTION CHANGING ALL TO LOWERCASE " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info"); encontrado = parseInt(c.toString()); break; } } if (encontrado >= 0) { _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED); var response = { index: encontrado, instruction: instruction }; return response; } } //End @1 /** * Step 3 Commands recognition. * If the command is not smart, and any of the commands match exactly then try to find * a command in all the quote. */ for (var i = 0; i < _this.ArtyomCommands.length; i++) { var instruction = _this.ArtyomCommands[i]; var opciones = instruction.indexes; var encontrado = -1; /** * Execution of match with index */ for (var c = 0; c < opciones.length; c++) { if (instruction.smart) { continue; //Jump wildcard commands } var opcion = opciones[c]; if ((voz.indexOf(opcion) >= 0)) { _this.debug(">> MATCHED INDEX EXACT OPTION " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info"); encontrado = parseInt(c.toString()); break; } else if (((voz.toLowerCase()).indexOf(opcion.toLowerCase()) >= 0)) { _this.debug(">> MATCHED INDEX OPTION CHANGING ALL TO LOWERCASE " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info"); encontrado = parseInt(c.toString()); break; } } if (encontrado >= 0) { _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED); var response = { index: encontrado, instruction: instruction }; return response; } } //End Step 3 /** * If the soundex options is enabled, proceed to process the commands in case that any of the previous * ways of processing (exact, lowercase and command in quote) didn't match anything. * Based on the soundex algorithm match a command if the spoken text is similar to any of the artyom commands. * Example : * If you have a command with "Open Wallmart" and "Open Willmar" is recognized, the open wallmart command will be triggered. * soundex("Open Wallmart") == soundex("Open Willmar") <= true * */ if (_this.ArtyomProperties.soundex) { for (var i = 0; i < _this.ArtyomCommands.length; i++) { var instruction = _this.ArtyomCommands[i]; var opciones = instruction.indexes; var encontrado = -1; for (var c = 0; c < opciones.length; c++) { var opcion = opciones[c]; if (instruction.smart) { continue; //Jump wildcard commands } if (_this.soundex(voz) == _this.soundex(opcion)) { _this.debug(">> Matched Soundex command '" + opcion + "' AGAINST '" + voz + "' with index " + c, "info"); encontrado = parseInt(c.toString()); _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED); var response = { index: encontrado, instruction: instruction }; return response; } } } } _this.debug("Event reached : " + _this.ArtyomGlobalEvents.NOT_COMMAND_MATCHED); _this.triggerEvent(_this.ArtyomGlobalEvents.NOT_COMMAND_MATCHED); return; }; /** * Force artyom to stop listen even if is in continuos mode. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/fatality * @returns {Boolean} */ Artyom.prototype.fatality = function () { var _this = this; //fatalityPromiseCallback return new Promise(function (resolve, reject) { // Expose the fatality promise callback to the helpers object of Artyom. // The promise isn't resolved here itself but in the onend callback of // the speechRecognition instance of artyom _this.ArtyomProperties.helpers.fatalityPromiseCallback = resolve; try { // If config is continuous mode, deactivate anyway. _this.ArtyomFlags.restartRecognition = false; _this.ArtyomWebkitSpeechRecognition.stop(); } catch (e) { reject(e); } }); }; /** * Returns an array with all the available commands for artyom. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getavailablecommands * @readonly * @returns {Array} */ Artyom.prototype.getAvailableCommands = function () { return this.ArtyomCommands; }; /** * Artyom can return inmediately the voices available in your browser. * * @readonly * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getvoices * @returns {Array} */ Artyom.prototype.getVoices = function () { return window.speechSynthesis.getVoices(); }; /** * Verify if the browser supports speechSynthesis. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/speechsupported * @returns {Boolean} */ Artyom.prototype.speechSupported = function () { return 'speechSynthesis' in window; }; /** * Verify if the browser supports webkitSpeechRecognition. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/recognizingsupported * @returns {Boolean} */ Artyom.prototype.recognizingSupported = function () { return 'webkitSpeechRecognition' in window; }; /** * Stops the actual and pendings messages that artyom have to say. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/shutup * @returns {undefined} */ Artyom.prototype.shutUp = function () { if ('speechSynthesis' in window) { do { window.speechSynthesis.cancel(); } while (window.speechSynthesis.pending === true); } this.ArtyomProperties.speaking = false; this.clearGarbageCollection(); }; /** * Returns an object with the actual properties of artyom. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getproperties * @returns {object} */ Artyom.prototype.getProperties = function () { return this.ArtyomProperties; }; /** * Returns the code language of artyom according to initialize function. * if initialize not used returns english GB. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getlanguage * @returns {String} */ Artyom.prototype.getLanguage = function () { return this.ArtyomProperties.lang; }; /** * Retrieves the used version of Artyom.js * * @returns {String} */ Artyom.prototype.getVersion = function () { return '1.0.6'; }; /** * Artyom awaits for orders when this function * is executed. * * If artyom gets a first parameter the instance will be stopped. * * @private * @returns {undefined} */ Artyom.prototype.hey = function (resolve, reject) { var start_timestamp; var artyom_is_allowed; var _this = this; /** * On mobile devices the recognized text is always thrown twice. * By setting the following configuration, fixes the issue */ if (this.Device.isMobile) { this.ArtyomWebkitSpeechRecognition.continuous = false; this.ArtyomWebkitSpeechRecognition.interimResults = false; this.ArtyomWebkitSpeechRecognition.maxAlternatives = 1; } else { this.ArtyomWebkitSpeechRecognition.continuous = true; this.ArtyomWebkitSpeechRecognition.interimResults = true; } this.ArtyomWebkitSpeechRecognition.lang = this.ArtyomProperties.lang; this.ArtyomWebkitSpeechRecognition.onstart = function () { _this.debug("Event reached : " + _this.ArtyomGlobalEvents.COMMAND_RECOGNITION_START); _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_START); _this.ArtyomProperties.recognizing = true; artyom_is_allowed = true; resolve(); }; /** * Handle all artyom posible exceptions * * @param {type} event * @returns {undefined} */ this.ArtyomWebkitSpeechRecognition.onerror = function (event) { // Reject promise on initialization reject(event.error); // Dispath error globally (artyom.when) _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, { code: event.error }); if (event.error == 'audio-capture') { artyom_is_allowed = false; } if (event.error == 'not-allowed') { artyom_is_allowed = false; if (event.timeStamp - start_timestamp < 100) { _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, { code: "info-blocked", message: "Artyom needs the permision of the microphone, is blocked." }); } else { _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, { code: "info-denied", message: "Artyom needs the permision of the microphone, is denied" }); } } }; /** * Check if continuous mode is active and restart the recognition. * Throw events too. * * @returns {undefined} */ _this.ArtyomWebkitSpeechRecognition.onend = function () { if (_this.ArtyomFlags.restartRecognition === true) { if (artyom_is_allowed === true) { _this.ArtyomWebkitSpeechRecognition.start(); _this.debug("Continuous mode enabled, restarting", "info"); } else { console.error("Verify the microphone and check for the table of errors in sdkcarlos.github.io/sites/artyom.html to solve your problem. If you want to give your user a message when an error appears add an artyom listener"); } _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END, { code: "continuous_mode_enabled", message: "OnEnd event reached with continuous mode" }); } else { // If the fatality promise callback was set, invoke it if (_this.ArtyomProperties.helpers.fatalityPromiseCallback) { // As the speech recognition doesn't finish really, wait 500ms // to trigger the real fatality callback setTimeout(function () { _this.ArtyomProperties.helpers.fatalityPromiseCallback(); }, 500); _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END, { code: "continuous_mode_disabled", message: "OnEnd event reached without continuous mode" }); } } _this.ArtyomProperties.recognizing = false; }; /** * Declare the processor dinamycally according to the mode of artyom * to increase the performance. * * @type {Function} * @return */ var onResultProcessor; // Process the recognition in normal mode if (_this.ArtyomProperties.mode == "normal") { onResultProcessor = function (event) { if (!_this.ArtyomCommands.length) { _this.debug("No commands to process in normal mode."); return; } var cantidadResultados = event.results.length; _this.triggerEvent(_this.ArtyomGlobalEvents.TEXT_RECOGNIZED); for (var i = event.resultIndex; i < cantidadResultados; ++i) { var identificated = event.results[i][0].transcript; if (event.results[i].isFinal) { var comando = _this.execute(identificated.trim()); // Redirect the output of the text if necessary if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") { _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, true); } if ((comando) && (_this.ArtyomProperties.recognizing == true)) { _this.debug("<< Executing Matching Recognition in normal mode >>", "info"); _this.ArtyomWebkitSpeechRecognition.stop(); _this.ArtyomProperties.recognizing = false; // Execute the command if smart if (comando.wildcard) { comando.instruction.action(comando.index, comando.wildcard.item, comando.wildcard.full); // Execute a normal command } else { comando.instruction.action(comando.index); } break; } } else { // Redirect output when necesary if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") { _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, false); } if (typeof (_this.ArtyomProperties.executionKeyword) === "string") { if (identificated.indexOf(_this.ArtyomProperties.executionKeyword) != -1) { var comando = _this.execute(identificated.replace(_this.ArtyomProperties.executionKeyword, '').trim()); if ((comando) && (_this.ArtyomProperties.recognizing == true)) { _this.debug("<< Executing command ordered by ExecutionKeyword >>", 'info'); _this.ArtyomWebkitSpeechRecognition.stop(); _this.ArtyomProperties.recognizing = false; //Executing Command Action if (comando.wildcard) { comando.instruction.action(comando.index, comando.wildcard.item, comando.wildcard.full); } else { comando.instruction.action(comando.index); } break; } } } _this.debug("Normal mode : " + identificated); } } }; } // Process the recognition in quick mode if (_this.ArtyomProperties.mode == "quick") { onResultProcessor = function (event) { if (!_this.ArtyomCommands.length) { _this.debug("No commands to process."); return; } var cantidadResultados = event.results.length; _this.triggerEvent(_this.ArtyomGlobalEvents.TEXT_RECOGNIZED); for (var i = event.resultIndex; i < cantidadResultados; ++i) { var identificated = event.results[i][0].transcript; if (!event.results[i].isFinal) { var comando = _this.execute(identificated.trim()); //Redirect output when necesary if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") { _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, true); } if ((comando) && (_this.ArtyomProperties.recognizing == true)) { _this.debug("<< Executing Matching Recognition in quick mode >>", "info"); _this.ArtyomWebkitSpeechRecognition.stop(); _this.ArtyomProperties.recognizing = false; //Executing Command Action if (comando.wildcard) { comando.instruction.action(comando.index, comando.wildcard.item); } else { comando.instruction.action(comando.index); } break; } } else { var comando = _this.execute(identificated.trim()); //Redirect output when necesary if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") { _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, false); } if ((comando) && (_this.ArtyomProperties.recognizing == true)) { _this.debug("<< Executing Matching Recognition in quick mode >>", "info"); _this.ArtyomWebkitSpeechRecognition.stop(); _this.ArtyomProperties.recognizing = false; //Executing Command Action if (comando.wildcard) { comando.instruction.action(comando.index, comando.wildcard.item); } else { comando.instruction.action(comando.index); } break; } } _this.debug("Quick mode : " + identificated); } }; } // Process the recognition in remote mode if (_this.ArtyomProperties.mode == "remote") { onResultProcessor = function (event) { var cantidadResultados = event.results.length; _this.triggerEvent(_this.ArtyomGlobalEvents.TEXT_RECOGNIZED); if (typeof (_this.ArtyomProperties.helpers.remoteProcessorHandler) !== "function") { return _this.debug("The remoteProcessorService is undefined.", "warn"); } for (var i = event.resultIndex; i < cantidadResultados; ++i) { var identificated = event.results[i][0].transcript; _this.ArtyomProperties.helpers.remoteProcessorHandler({ text: identificated, isFinal: event.results[i].isFinal }); } }; } /** * Process the recognition event with the previously * declared processor function. * * @param {type} event * @returns {undefined} */ _this.ArtyomWebkitSpeechRecognition.onresult = function (event) { if (_this.ArtyomProperties.obeying) { onResultProcessor(event); } else { // Handle obeyKeyword if exists and artyom is not obeying if (!_this.ArtyomProperties.obeyKeyword) { return; } var temporal = ""; var interim = ""; for (var i = 0; i < event.results.length; ++i) { if (event.results[i].isFinal) { temporal += event.results[i][0].transcript; } else { interim += event.results[i][0].transcript; } } _this.debug("Artyom is not obeying", "warn"); // If the obeyKeyword is found in the recognized text // enable command recognition again if (((interim).indexOf(_this.ArtyomProperties.obeyKeyword) > -1) || (temporal).indexOf(_this.ArtyomProperties.obeyKeyword) > -1) { _this.ArtyomProperties.obeying = true; } } }; if (_this.ArtyomProperties.recognizing) { _this.ArtyomWebkitSpeechRecognition.stop(); _this.debug("Event reached : " + _this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END); _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END); } else { try { _this.ArtyomWebkitSpeechRecognition.start(); } catch (e) { _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, { code: "recognition_overlap", message: "A webkitSpeechRecognition instance has been started while there's already running. Is recommendable to restart the Browser" }); } } }; /** * Set up artyom for the application. * * This function will set the default language used by artyom * or notice the user if artyom is not supported in the actual * browser * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/initialize * @param {Object} config * @returns {Boolean} */ Artyom.prototype.initialize = function (config) { var _this = this; if (typeof (config) !== "object") { return Promise.reject("You must give the configuration for start artyom properly."); } if (config.hasOwnProperty("lang")) { _this.ArtyomVoice = _this.getVoice(config.lang); _this.ArtyomProperties.lang = config.lang; } if (config.hasOwnProperty("continuous")) { if (config.continuous) { this.ArtyomProperties.continuous = true; this.ArtyomFlags.restartRecognition = true; } else { this.ArtyomProperties.continuous = false; this.ArtyomFlags.restartRecognition = false; } } if (config.hasOwnProperty("speed")) { this.ArtyomProperties.speed = config.speed; } if (config.hasOwnProperty("soundex")) { this.ArtyomProperties.soundex = config.soundex; } if (config.hasOwnProperty("executionKeyword")) { this.ArtyomProperties.executionKeyword = config.executionKeyword; } if (config.hasOwnProperty("obeyKeyword")) { this.ArtyomProperties.obeyKeyword = config.obeyKeyword; } if (config.hasOwnProperty("volume")) { this.ArtyomProperties.volume = config.volume; } if (config.hasOwnProperty("listen")) { this.ArtyomProperties.listen = config.listen; } if (config.hasOwnProperty("name")) { this.ArtyomProperties.name = config.name; } if (config.hasOwnProperty("debug")) { this.ArtyomProperties.debug = config.debug; } else { console.warn("The initialization doesn't provide how the debug mode should be handled. Is recommendable to set this value either to true or false."); } if (config.mode) { this.ArtyomProperties.mode = config.mode; } if (this.ArtyomProperties.listen === true) { return new Promise(function (resolve, reject) { _this.hey(resolve, reject); }); } return Promise.resolve(true); }; /** * Add commands like an artisan. If you use artyom for simple tasks * then probably you don't like to write a lot to achieve it. * * Use the artisan syntax to write less, but with the same accuracy. * * @disclaimer Not a promise-based implementation, just syntax. * @returns {Boolean} */ Artyom.prototype.on = function (indexes, smart) { var _this = this; return { then: function (action) { var command = { indexes: indexes, action: action }; if (smart) { command.smart = true; } _this.addCommands(command); } }; }; /** * Generates an artyom event with the designed name * * @param {type} name * @returns {undefined} */ Artyom.prototype.triggerEvent = function (name, param) { var event = new CustomEvent(name, { 'detail': param }); document.dispatchEvent(event); return event; }; /** * Repeats the last sentence that artyom said. * Useful in noisy environments. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/repeatlastsay * @param {Boolean} returnObject If set to true, an object with the text and the timestamp when was executed will be returned. * @returns {Object} */ Artyom.prototype.repeatLastSay = function (returnObject) { var last = this.ArtyomProperties.helpers.lastSay; if (returnObject) { return last; } else { if (last != null) { this.say(last.text); } } }; /** * Create a listener when an artyom action is called. * * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/when * @param {type} event * @param {type} action * @returns {undefined} */ Artyom.prototype.when = function (event, action) { return document.addEventListener(event, function (e) { action(e["detail"]); }, false); }; /** * Process the recognized text if artyom is active in remote mode. * * @returns {Boolean} */ Artyom.prototype.remoteProcessorService = function (action) { this.ArtyomProperties.helpers.remoteProcessorHandler = action; return true; }; /** * Verify if there's a voice available for a language using its language code identifier. * * @return {Boolean} */ Artyom.prototype.voiceAvailable = function (languageCode) { return typeof (this.getVoice(languageCode)) !== "undefined"; }; /** * A boolean to check if artyom is obeying commands or not. * * @returns {Boolean} */ Artyom.prototype.isObeying = function () { return this.ArtyomProperties.obeying; }; /** * Allow artyom to obey commands again. * * @returns {Boolean} */ Artyom.prototype.obey = function () { return this.ArtyomProperties.obeying = true; }; /** * Pause the processing of commands. Artyom still listening in the background and it can be resumed after a couple of seconds. * * @returns {Boolean} */ Artyom.prototype.dontObey = function () { return this.ArtyomProperties.obeying = false; }; /** * This function returns a boolean according to the speechSynthesis status * if artyom is speaking, will return true. * * Note: This is not a feature of speechSynthesis, therefore this value hangs on * the fiability of the onStart and onEnd events of the speechSynthesis * * @since 0.9.3 * @summary Returns true if speechSynthesis is active * @returns {Boolean} */ Artyom.prototype.isSpeaking = function () { return this.ArtyomProperties.speaking; }; /** * This function returns a boolean according to the SpeechRecognition status * if artyom is listening, will return true. * * Note: This is not a feature of SpeechRecognition, therefore this value hangs on * the fiability of the onStart and onEnd events of the SpeechRecognition * * @since 0.9.3 * @summary Returns true if SpeechRecognition is active * @returns {Boolean} */ Artyom.prototype.isRecognizing = function () { return this.ArtyomProperties.recognizing; }; /** * This function will return the webkitSpeechRecognition object used by artyom * retrieve it only to debug on it or get some values, do not make changes directly * * @readonly * @since 0.9.2 * @summary Retrieve the native webkitSpeechRecognition object * @returns {Object webkitSpeechRecognition} */ Artyom.prototype.getNativeApi = function () { return this.ArtyomWebkitSpeechRecognition; }; /** * Returns the SpeechSynthesisUtterance garbageobjects. * * @returns {Array} */ Artyom.prototype.getGarbageCollection = function () { return this.ArtyomGarbageCollection; }; /** * Retrieve a single voice of the browser by it's language code. * It will return the first voice available for the language on every device. * * @param languageCode */ Artyom.prototype.getVoice = function (languageCode) { var voiceIdentifiersArray = this.ArtyomVoicesIdentifiers[languageCode]; if (!voiceIdentifiersArray) { console.warn("The providen language " + languageCode + " isn't available, using English Great britain as default"); voiceIdentifiersArray = this.ArtyomVoicesIdentifiers["en-GB"]; } var voice = undefined; var voices = speechSynthesis.getVoices(); var voicesLength = voiceIdentifiersArray.length; var _loop_1 = function (i) { var foundVoice = voices.filter(function (voice) { return ((voice.name == voiceIdentifiersArray[i]) || (voice.lang == voiceIdentifiersArray[i])); })[0]; if (foundVoice) { voice = foundVoice; return "break"; } }; for (var i = 0; i < voicesLength; i++) { var state_1 = _loop_1(i); if (state_1 === "break") break; } return voice; }; /** * Artyom provide an easy way to create a * dictation for your user. * * Just create an instance and start and stop when you want * * @returns Object | newDictation */ Artyom.prototype.newDictation = function (settings) { var _this = this; if (!_this.recognizingSupported()) { console.error("SpeechRecognition is not supported in this browser"); return false; } var dictado = new window.webkitSpeechRecognition(); dictado.continuous = true; dictado.interimResults = true; dictado.lang = _this.ArtyomProperties.lang; dictado.onresult = function (event) { var temporal = ""; var interim = ""; for (var i = 0; i < event.results.length; ++i) { if (event.results[i].isFinal) { temporal += event.results[i][0].transcript; } else { interim += event.results[i][0].transcript; } } if (settings.onResult) { settings.onResult(interim, temporal); } }; return new function () { var dictation = dictado; var flagStartCallback = true; var flagRestart = false; this.onError = null; this.start = function () { if (settings.continuous === true) { flagRestart = true; } dictation.onstart = function () { if (typeof (settings.onStart) === "function") { if (flagStartCallback === true) { settings.onStart(); } } }; dictation.onend = function () { if (flagRestart === true) { flagStartCallback = false; dictation.start(); }