//! annyang //! version : 1.1.0 //! author : Tal Ater @TalAter //! license : MIT //! https://www.TalAter.com/annyang/ (function (undefined) { "use strict"; // Save a reference to the global object (window in the browser) var root = this; // Get the SpeechRecognition object, while handling browser prefixes var SpeechRecognition = root.SpeechRecognition || root.webkitSpeechRecognition || root.mozSpeechRecognition || root.msSpeechRecognition || root.oSpeechRecognition; // Check browser support // This is done as early as possible, to make it as fast as possible for unsupported browsers if (!SpeechRecognition) { root.annyang = null; return undefined; } var commandsList = []; var recognition; var callbacks = { start: [], error: [], end: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] }; var autoRestart; var lastStartedAt = 0; var debugState = false; var debugStyle = 'font-weight: bold; color: #00f;'; // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license. var optionalParam = /\s*\((.*?)\)\s*/g; var optionalRegex = /(\(\?:[^)]+\))\?/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#]/g; var commandToRegExp = function(command) { command = command.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional) { return optional ? match : '([^\\s]+)'; }) .replace(splatParam, '(.*?)') .replace(optionalRegex, '\\s*$1?\\s*'); return new RegExp('^' + command + '$', 'i'); }; // This method receives an array of callbacks to iterate over, and invokes each of them var invokeCallbacks = function(callbacks) { callbacks.forEach(function(callback) { callback.callback.apply(callback.context); }); }; var initIfNeeded = function() { if (recognition === undefined) { root.annyang.init({}, false); } }; root.annyang = { // Initialize annyang with a list of commands to recognize. // e.g. annyang.init({'hello :name': helloFunction}) // annyang understands commands with named variables, splats, and optional words. init: function(commands, resetCommands) { // resetCommands defaults to true if (resetCommands === undefined) { resetCommands = true; } else { resetCommands = !!resetCommands; } try { // Abort previous instances of recognition already running if (recognition && recognition.abort) { recognition.abort(); } // initiate SpeechRecognition recognition = new SpeechRecognition(); // Set the max number of alternative transcripts to try and match with a command recognition.maxAlternatives = 5; recognition.continuous = true; // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage() recognition.lang = 'en-US'; } catch(err) { root.annyang = null; return undefined; } recognition.onstart = function() { invokeCallbacks(callbacks.start); }; recognition.onerror = function(event) { invokeCallbacks(callbacks.error); switch (event.error) { case 'network': invokeCallbacks(callbacks.errorNetwork); break; case 'not-allowed': case 'service-not-allowed': // if permission to use the mic is denied, turn off auto-restart autoRestart = false; // determine if permission was denied by user or automatically. if (new Date().getTime()-lastStartedAt < 200) { invokeCallbacks(callbacks.errorPermissionBlocked); } else { invokeCallbacks(callbacks.errorPermissionDenied); } break; } }; recognition.onend = function() { invokeCallbacks(callbacks.end); // annyang will auto restart if it is closed automatically and not by user action. if (autoRestart) { // play nicely with the browser, and never restart annyang automatically more than once per second var timeSinceLastStart = new Date().getTime()-lastStartedAt; if (timeSinceLastStart < 1000) { setTimeout(root.annyang.start, 1000-timeSinceLastStart); } else { root.annyang.start(); } } }; recognition.onresult = function(event) { invokeCallbacks(callbacks.result); var results = event.results[event.resultIndex]; var commandText; // go over each of the 5 results and alternative results received (we've set maxAlternatives to 5 above) for (var i = 0; i 0) { debugState = !!newState; } else { debugState = true; } }, // Set the language the user will speak in. If not called, defaults to 'en-US'. // e.g. 'fr-FR' (French-France), 'es-CR' (EspaƱol-Costa Rica) setLanguage: function(language) { initIfNeeded(); recognition.lang = language; }, setContinuous: function(cont) { initIfNeeded(); recognition.continuous=cont; }, // Add additional commands that annyang will respond to. Similar in syntax to annyang.init() addCommands: function(commands) { var cb, command; initIfNeeded(); for (var phrase in commands) { if (commands.hasOwnProperty(phrase)) { cb = root[commands[phrase]] || commands[phrase]; if (typeof cb !== 'function') { continue; } //convert command to regex command = commandToRegExp(phrase); commandsList.push({ command: command, callback: cb, originalPhrase: phrase }); } } if (debugState) { root.console.log('Commands successfully loaded: %c'+commandsList.length, debugStyle); } }, // Remove existing commands. Called with a single phrase or an array of phrases removeCommands: function(commandsToRemove) { commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove]; commandsList = commandsList.filter(function(command) { for (var i = 0; i