· 8 years ago · Dec 17, 2017, 01:28 AM
1"use strict";
2/**
3 * Artyom.js is a voice control, speech recognition and speech synthesis JavaScript library.
4 *
5 * @requires {webkitSpeechRecognition && speechSynthesis}
6 * @license MIT
7 * @version 1.0.6
8 * @copyright 2017 Our Code World (www.ourcodeworld.com) All Rights Reserved.
9 * @author Carlos Delgado (https://github.com/sdkcarlos) and Sema GarcÃa (https://github.com/semagarcia)
10 * @see https://sdkcarlos.github.io/sites/artyom.html
11 * @see http://docs.ourcodeworld.com/projects/artyom-js
12 */
13Object.defineProperty(exports, "__esModule", { value: true });
14/// <reference path="artyom.d.ts" />
15// Remove "export default " keywords if willing to build with `npm run artyom-build-window`
16var Artyom = (function () {
17 // Triggered at the declaration of
18 function Artyom() {
19 this.ArtyomCommands = [];
20 this.ArtyomVoicesIdentifiers = {
21 // German
22 "de-DE": ["Google Deutsch", "de-DE", "de_DE"],
23 // Spanish
24 "es-ES": ["Google español", "es-ES", "es_ES", "es-MX", "es_MX"],
25 // Italian
26 "it-IT": ["Google italiano", "it-IT", "it_IT"],
27 // Japanese
28 "jp-JP": ["Google 日本人", "ja-JP", "ja_JP"],
29 // English USA
30 "en-US": ["Google US English", "en-US", "en_US"],
31 // English UK
32 "en-GB": ["Google UK English Male", "Google UK English Female", "en-GB", "en_GB"],
33 // Brazilian Portuguese
34 "pt-BR": ["Google português do Brasil", "pt-PT", "pt-BR", "pt_PT", "pt_BR"],
35 // Portugal Portuguese
36 // Note: in desktop, there's no voice for portugal Portuguese
37 "pt-PT": ["Google português do Brasil", "pt-PT", "pt_PT"],
38 // Russian
39 "ru-RU": ["Google руÑÑкий", "ru-RU", "ru_RU"],
40 // Dutch (holland)
41 "nl-NL": ["Google Nederlands", "nl-NL", "nl_NL"],
42 // French
43 "fr-FR": ["Google français", "fr-FR", "fr_FR"],
44 // Polish
45 "pl-PL": ["Google polski", "pl-PL", "pl_PL"],
46 // Indonesian
47 "id-ID": ["Google Bahasa Indonesia", "id-ID", "id_ID"],
48 // Hindi
49 "hi-IN": ["Google हिनà¥à¤¦à¥€", "hi-IN", "hi_IN"],
50 // Mandarin Chinese
51 "zh-CN": ["Google 普通è¯ï¼ˆä¸å›½å¤§é™†ï¼‰", "zh-CN", "zh_CN"],
52 // Cantonese Chinese
53 "zh-HK": ["Google 粤語(香港)", "zh-HK", "zh_HK"],
54 // Native voice
55 "native": ["native"]
56 };
57 // Important: retrieve the voices of the browser as soon as possible.
58 // Normally, the execution of speechSynthesis.getVoices will return at the first time an empty array.
59 if (window.hasOwnProperty('speechSynthesis')) {
60 speechSynthesis.getVoices();
61 }
62 else {
63 console.error("Artyom.js can't speak without the Speech Synthesis API.");
64 }
65 // This instance of webkitSpeechRecognition is the one used by Artyom.
66 if (window.hasOwnProperty('webkitSpeechRecognition')) {
67 this.ArtyomWebkitSpeechRecognition = new window.webkitSpeechRecognition();
68 }
69 else {
70 console.error("Artyom.js can't recognize voice without the Speech Recognition API.");
71 }
72 this.ArtyomProperties = {
73 lang: 'en-GB',
74 recognizing: false,
75 continuous: false,
76 speed: 1,
77 volume: 1,
78 listen: false,
79 mode: "normal",
80 debug: false,
81 helpers: {
82 redirectRecognizedTextOutput: null,
83 remoteProcessorHandler: null,
84 lastSay: null,
85 fatalityPromiseCallback: null
86 },
87 executionKeyword: null,
88 obeyKeyword: null,
89 speaking: false,
90 obeying: true,
91 soundex: false,
92 name: null
93 };
94 this.ArtyomGarbageCollection = [];
95 this.ArtyomFlags = {
96 restartRecognition: false
97 };
98 this.ArtyomGlobalEvents = {
99 ERROR: "ERROR",
100 SPEECH_SYNTHESIS_START: "SPEECH_SYNTHESIS_START",
101 SPEECH_SYNTHESIS_END: "SPEECH_SYNTHESIS_END",
102 TEXT_RECOGNIZED: "TEXT_RECOGNIZED",
103 COMMAND_RECOGNITION_START: "COMMAND_RECOGNITION_START",
104 COMMAND_RECOGNITION_END: "COMMAND_RECOGNITION_END",
105 COMMAND_MATCHED: "COMMAND_MATCHED",
106 NOT_COMMAND_MATCHED: "NOT_COMMAND_MATCHED"
107 };
108 this.Device = {
109 isMobile: false,
110 isChrome: true
111 };
112 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)) {
113 this.Device.isMobile = true;
114 }
115 if (navigator.userAgent.indexOf("Chrome") == -1) {
116 this.Device.isChrome = false;
117 }
118 /**
119 * The default voice of Artyom in the Desktop. In mobile, you will need to initialize (or force the language)
120 * with a language code in order to find an available voice in the device, otherwise it will use the native voice.
121 */
122 this.ArtyomVoice = {
123 default: false,
124 lang: "en-GB",
125 localService: false,
126 name: "Google UK English Male",
127 voiceURI: "Google UK English Male"
128 };
129 }
130 /**
131 * Add dinamically commands to artyom using
132 * You can even add commands while artyom is active.
133 *
134 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/addcommands
135 * @since 0.6
136 * @param {Object | Array[Objects]} param
137 * @returns {undefined}
138 */
139 Artyom.prototype.addCommands = function (param) {
140 var _this = this;
141 var processCommand = function (command) {
142 if (command.hasOwnProperty("indexes")) {
143 _this.ArtyomCommands.push(command);
144 }
145 else {
146 console.error("The given command doesn't provide any index to execute.");
147 }
148 };
149 if (param instanceof Array) {
150 for (var i = 0; i < param.length; i++) {
151 processCommand(param[i]);
152 }
153 }
154 else {
155 processCommand(param);
156 }
157 return true;
158 };
159 ;
160 /**
161 * The SpeechSynthesisUtterance objects are stored in the artyom_garbage_collector variable
162 * to prevent the wrong behaviour of artyom.say.
163 * Use this method to clear all spoken SpeechSynthesisUtterance unused objects.
164 *
165 * @returns {Array<any>}
166 */
167 Artyom.prototype.clearGarbageCollection = function () {
168 return this.ArtyomGarbageCollection = [];
169 };
170 ;
171 /**
172 * Displays a message in the console if the artyom propery DEBUG is set to true.
173 *
174 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/debug
175 * @returns {undefined}
176 */
177 Artyom.prototype.debug = function (message, type) {
178 var preMessage = "[v" + this.getVersion() + "] Artyom.js";
179 if (this.ArtyomProperties.debug === true) {
180 switch (type) {
181 case "error":
182 console.log("%c" + preMessage + ":%c " + message, 'background: #C12127; color: black;', 'color:black;');
183 break;
184 case "warn":
185 console.warn(message);
186 break;
187 case "info":
188 console.log("%c" + preMessage + ":%c " + message, 'background: #4285F4; color: #FFFFFF', 'color:black;');
189 break;
190 default:
191 console.log("%c" + preMessage + ":%c " + message, 'background: #005454; color: #BFF8F8', 'color:black;');
192 break;
193 }
194 }
195 };
196 /**
197 * Artyom have it's own diagnostics.
198 * Run this function in order to detect why artyom is not initialized.
199 *
200 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/detecterrors
201 * @param {type} callback
202 * @returns {}
203 */
204 Artyom.prototype.detectErrors = function () {
205 var _this = this;
206 if ((window.location.protocol) == "file:") {
207 var message = "Error: running Artyom directly from a file. The APIs require a different communication protocol like HTTP or HTTPS";
208 console.error(message);
209 return {
210 code: "artyom_error_localfile",
211 message: message
212 };
213 }
214 if (!_this.Device.isChrome) {
215 var message = "Error: the Speech Recognition and Speech Synthesis APIs require the Google Chrome Browser to work.";
216 console.error(message);
217 return {
218 code: "artyom_error_browser_unsupported",
219 message: message
220 };
221 }
222 if (window.location.protocol != "https:") {
223 console.warn("Warning: artyom is being executed using the '" + window.location.protocol + "' protocol. The continuous mode requires a secure protocol (HTTPS)");
224 }
225 return false;
226 };
227 /**
228 * Removes all the added commands of artyom.
229 *
230 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/emptycommands
231 * @since 0.6
232 * @returns {Array}
233 */
234 Artyom.prototype.emptyCommands = function () {
235 return this.ArtyomCommands = [];
236 };
237 /**
238 * Returns an object with data of the matched element
239 *
240 * @private
241 * @param {string} comando
242 * @returns {MatchedCommand}
243 */
244 Artyom.prototype.execute = function (voz) {
245 var _this = this;
246 if (!voz) {
247 console.warn("Internal error: Execution of empty command");
248 return;
249 }
250 // If artyom was initialized with a name, verify that the name begins with it to allow the execution of commands.
251 if (_this.ArtyomProperties.name) {
252 if (voz.indexOf(_this.ArtyomProperties.name) != 0) {
253 _this.debug("Artyom requires with a name \"" + _this.ArtyomProperties.name + "\" but the name wasn't spoken.", "warn");
254 return;
255 }
256 // Remove name from voice command
257 voz = voz.substr(_this.ArtyomProperties.name.length);
258 }
259 _this.debug(">> " + voz);
260 /** @3
261 * Artyom needs time to think that
262 */
263 for (var i = 0; i < _this.ArtyomCommands.length; i++) {
264 var instruction = _this.ArtyomCommands[i];
265 var opciones = instruction.indexes;
266 var encontrado = -1;
267 var wildy = "";
268 for (var c = 0; c < opciones.length; c++) {
269 var opcion = opciones[c];
270 if (!instruction.smart) {
271 continue; //Jump if is not smart command
272 }
273 // Process RegExp
274 if (opcion instanceof RegExp) {
275 // If RegExp matches
276 if (opcion.test(voz)) {
277 _this.debug(">> REGEX " + opcion.toString() + " MATCHED AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info");
278 encontrado = parseInt(c.toString());
279 }
280 // Otherwise just wildcards
281 }
282 else {
283 if (opcion.indexOf("*") != -1) {
284 ///LOGIC HERE
285 var grupo = opcion.split("*");
286 if (grupo.length > 2) {
287 console.warn("Artyom found a smart command with " + (grupo.length - 1) + " wildcards. Artyom only support 1 wildcard for each command. Sorry");
288 continue;
289 }
290 //START SMART COMMAND
291 var before = grupo[0];
292 var later = grupo[1];
293 // Wildcard in the end
294 if ((later == "") || (later == " ")) {
295 if ((voz.indexOf(before) != -1) || ((voz.toLowerCase()).indexOf(before.toLowerCase()) != -1)) {
296 wildy = voz.replace(before, '');
297 wildy = (wildy.toLowerCase()).replace(before.toLowerCase(), '');
298 encontrado = parseInt(c.toString());
299 }
300 }
301 else {
302 if ((voz.indexOf(before) != -1) || ((voz.toLowerCase()).indexOf(before.toLowerCase()) != -1)) {
303 if ((voz.indexOf(later) != -1) || ((voz.toLowerCase()).indexOf(later.toLowerCase()) != -1)) {
304 wildy = voz.replace(before, '').replace(later, '');
305 wildy = (wildy.toLowerCase()).replace(before.toLowerCase(), '').replace(later.toLowerCase(), '');
306 wildy = (wildy.toLowerCase()).replace(later.toLowerCase(), '');
307 encontrado = parseInt(c.toString());
308 }
309 }
310 }
311 }
312 else {
313 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 *");
314 }
315 }
316 if ((encontrado >= 0)) {
317 encontrado = parseInt(c.toString());
318 break;
319 }
320 }
321 if (encontrado >= 0) {
322 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED);
323 var response = {
324 index: encontrado,
325 instruction: instruction,
326 wildcard: {
327 item: wildy,
328 full: voz
329 }
330 };
331 return response;
332 }
333 } //End @3
334 /** @1
335 * Search for IDENTICAL matches in the commands if nothing matches
336 * start with a index match in commands
337 */
338 for (var i = 0; i < _this.ArtyomCommands.length; i++) {
339 var instruction = _this.ArtyomCommands[i];
340 var opciones = instruction.indexes;
341 var encontrado = -1;
342 /**
343 * Execution of match with identical commands
344 */
345 for (var c = 0; c < opciones.length; c++) {
346 var opcion = opciones[c];
347 if (instruction.smart) {
348 continue; //Jump wildcard commands
349 }
350 if ((voz === opcion)) {
351 _this.debug(">> MATCHED FULL EXACT OPTION " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info");
352 encontrado = parseInt(c.toString());
353 break;
354 }
355 else if ((voz.toLowerCase() === opcion.toLowerCase())) {
356 _this.debug(">> MATCHED OPTION CHANGING ALL TO LOWERCASE " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info");
357 encontrado = parseInt(c.toString());
358 break;
359 }
360 }
361 if (encontrado >= 0) {
362 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED);
363 var response = {
364 index: encontrado,
365 instruction: instruction
366 };
367 return response;
368 }
369 } //End @1
370 /**
371 * Step 3 Commands recognition.
372 * If the command is not smart, and any of the commands match exactly then try to find
373 * a command in all the quote.
374 */
375 for (var i = 0; i < _this.ArtyomCommands.length; i++) {
376 var instruction = _this.ArtyomCommands[i];
377 var opciones = instruction.indexes;
378 var encontrado = -1;
379 /**
380 * Execution of match with index
381 */
382 for (var c = 0; c < opciones.length; c++) {
383 if (instruction.smart) {
384 continue; //Jump wildcard commands
385 }
386 var opcion = opciones[c];
387 if ((voz.indexOf(opcion) >= 0)) {
388 _this.debug(">> MATCHED INDEX EXACT OPTION " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info");
389 encontrado = parseInt(c.toString());
390 break;
391 }
392 else if (((voz.toLowerCase()).indexOf(opcion.toLowerCase()) >= 0)) {
393 _this.debug(">> MATCHED INDEX OPTION CHANGING ALL TO LOWERCASE " + opcion + " AGAINST " + voz + " WITH INDEX " + c + " IN COMMAND ", "info");
394 encontrado = parseInt(c.toString());
395 break;
396 }
397 }
398 if (encontrado >= 0) {
399 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED);
400 var response = {
401 index: encontrado,
402 instruction: instruction
403 };
404 return response;
405 }
406 } //End Step 3
407 /**
408 * If the soundex options is enabled, proceed to process the commands in case that any of the previous
409 * ways of processing (exact, lowercase and command in quote) didn't match anything.
410 * Based on the soundex algorithm match a command if the spoken text is similar to any of the artyom commands.
411 * Example :
412 * If you have a command with "Open Wallmart" and "Open Willmar" is recognized, the open wallmart command will be triggered.
413 * soundex("Open Wallmart") == soundex("Open Willmar") <= true
414 *
415 */
416 if (_this.ArtyomProperties.soundex) {
417 for (var i = 0; i < _this.ArtyomCommands.length; i++) {
418 var instruction = _this.ArtyomCommands[i];
419 var opciones = instruction.indexes;
420 var encontrado = -1;
421 for (var c = 0; c < opciones.length; c++) {
422 var opcion = opciones[c];
423 if (instruction.smart) {
424 continue; //Jump wildcard commands
425 }
426 if (_this.soundex(voz) == _this.soundex(opcion)) {
427 _this.debug(">> Matched Soundex command '" + opcion + "' AGAINST '" + voz + "' with index " + c, "info");
428 encontrado = parseInt(c.toString());
429 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_MATCHED);
430 var response = {
431 index: encontrado,
432 instruction: instruction
433 };
434 return response;
435 }
436 }
437 }
438 }
439 _this.debug("Event reached : " + _this.ArtyomGlobalEvents.NOT_COMMAND_MATCHED);
440 _this.triggerEvent(_this.ArtyomGlobalEvents.NOT_COMMAND_MATCHED);
441 return;
442 };
443 /**
444 * Force artyom to stop listen even if is in continuos mode.
445 *
446 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/fatality
447 * @returns {Boolean}
448 */
449 Artyom.prototype.fatality = function () {
450 var _this = this;
451 //fatalityPromiseCallback
452 return new Promise(function (resolve, reject) {
453 // Expose the fatality promise callback to the helpers object of Artyom.
454 // The promise isn't resolved here itself but in the onend callback of
455 // the speechRecognition instance of artyom
456 _this.ArtyomProperties.helpers.fatalityPromiseCallback = resolve;
457 try {
458 // If config is continuous mode, deactivate anyway.
459 _this.ArtyomFlags.restartRecognition = false;
460 _this.ArtyomWebkitSpeechRecognition.stop();
461 }
462 catch (e) {
463 reject(e);
464 }
465 });
466 };
467 /**
468 * Returns an array with all the available commands for artyom.
469 *
470 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getavailablecommands
471 * @readonly
472 * @returns {Array}
473 */
474 Artyom.prototype.getAvailableCommands = function () {
475 return this.ArtyomCommands;
476 };
477 /**
478 * Artyom can return inmediately the voices available in your browser.
479 *
480 * @readonly
481 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getvoices
482 * @returns {Array}
483 */
484 Artyom.prototype.getVoices = function () {
485 return window.speechSynthesis.getVoices();
486 };
487 /**
488 * Verify if the browser supports speechSynthesis.
489 *
490 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/speechsupported
491 * @returns {Boolean}
492 */
493 Artyom.prototype.speechSupported = function () {
494 return 'speechSynthesis' in window;
495 };
496 /**
497 * Verify if the browser supports webkitSpeechRecognition.
498 *
499 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/recognizingsupported
500 * @returns {Boolean}
501 */
502 Artyom.prototype.recognizingSupported = function () {
503 return 'webkitSpeechRecognition' in window;
504 };
505 /**
506 * Stops the actual and pendings messages that artyom have to say.
507 *
508 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/shutup
509 * @returns {undefined}
510 */
511 Artyom.prototype.shutUp = function () {
512 if ('speechSynthesis' in window) {
513 do {
514 window.speechSynthesis.cancel();
515 } while (window.speechSynthesis.pending === true);
516 }
517 this.ArtyomProperties.speaking = false;
518 this.clearGarbageCollection();
519 };
520 /**
521 * Returns an object with the actual properties of artyom.
522 *
523 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getproperties
524 * @returns {object}
525 */
526 Artyom.prototype.getProperties = function () {
527 return this.ArtyomProperties;
528 };
529 /**
530 * Returns the code language of artyom according to initialize function.
531 * if initialize not used returns english GB.
532 *
533 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/getlanguage
534 * @returns {String}
535 */
536 Artyom.prototype.getLanguage = function () {
537 return this.ArtyomProperties.lang;
538 };
539 /**
540 * Retrieves the used version of Artyom.js
541 *
542 * @returns {String}
543 */
544 Artyom.prototype.getVersion = function () {
545 return '1.0.6';
546 };
547 /**
548 * Artyom awaits for orders when this function
549 * is executed.
550 *
551 * If artyom gets a first parameter the instance will be stopped.
552 *
553 * @private
554 * @returns {undefined}
555 */
556 Artyom.prototype.hey = function (resolve, reject) {
557 var start_timestamp;
558 var artyom_is_allowed;
559 var _this = this;
560 /**
561 * On mobile devices the recognized text is always thrown twice.
562 * By setting the following configuration, fixes the issue
563 */
564 if (this.Device.isMobile) {
565 this.ArtyomWebkitSpeechRecognition.continuous = false;
566 this.ArtyomWebkitSpeechRecognition.interimResults = false;
567 this.ArtyomWebkitSpeechRecognition.maxAlternatives = 1;
568 }
569 else {
570 this.ArtyomWebkitSpeechRecognition.continuous = true;
571 this.ArtyomWebkitSpeechRecognition.interimResults = true;
572 }
573 this.ArtyomWebkitSpeechRecognition.lang = this.ArtyomProperties.lang;
574 this.ArtyomWebkitSpeechRecognition.onstart = function () {
575 _this.debug("Event reached : " + _this.ArtyomGlobalEvents.COMMAND_RECOGNITION_START);
576 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_START);
577 _this.ArtyomProperties.recognizing = true;
578 artyom_is_allowed = true;
579 resolve();
580 };
581 /**
582 * Handle all artyom posible exceptions
583 *
584 * @param {type} event
585 * @returns {undefined}
586 */
587 this.ArtyomWebkitSpeechRecognition.onerror = function (event) {
588 // Reject promise on initialization
589 reject(event.error);
590 // Dispath error globally (artyom.when)
591 _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, {
592 code: event.error
593 });
594 if (event.error == 'audio-capture') {
595 artyom_is_allowed = false;
596 }
597 if (event.error == 'not-allowed') {
598 artyom_is_allowed = false;
599 if (event.timeStamp - start_timestamp < 100) {
600 _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, {
601 code: "info-blocked",
602 message: "Artyom needs the permision of the microphone, is blocked."
603 });
604 }
605 else {
606 _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, {
607 code: "info-denied",
608 message: "Artyom needs the permision of the microphone, is denied"
609 });
610 }
611 }
612 };
613 /**
614 * Check if continuous mode is active and restart the recognition.
615 * Throw events too.
616 *
617 * @returns {undefined}
618 */
619 _this.ArtyomWebkitSpeechRecognition.onend = function () {
620 if (_this.ArtyomFlags.restartRecognition === true) {
621 if (artyom_is_allowed === true) {
622 _this.ArtyomWebkitSpeechRecognition.start();
623 _this.debug("Continuous mode enabled, restarting", "info");
624 }
625 else {
626 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");
627 }
628 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END, {
629 code: "continuous_mode_enabled",
630 message: "OnEnd event reached with continuous mode"
631 });
632 }
633 else {
634 // If the fatality promise callback was set, invoke it
635 if (_this.ArtyomProperties.helpers.fatalityPromiseCallback) {
636 // As the speech recognition doesn't finish really, wait 500ms
637 // to trigger the real fatality callback
638 setTimeout(function () {
639 _this.ArtyomProperties.helpers.fatalityPromiseCallback();
640 }, 500);
641 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END, {
642 code: "continuous_mode_disabled",
643 message: "OnEnd event reached without continuous mode"
644 });
645 }
646 }
647 _this.ArtyomProperties.recognizing = false;
648 };
649 /**
650 * Declare the processor dinamycally according to the mode of artyom
651 * to increase the performance.
652 *
653 * @type {Function}
654 * @return
655 */
656 var onResultProcessor;
657 // Process the recognition in normal mode
658 if (_this.ArtyomProperties.mode == "normal") {
659 onResultProcessor = function (event) {
660 if (!_this.ArtyomCommands.length) {
661 _this.debug("No commands to process in normal mode.");
662 return;
663 }
664 var cantidadResultados = event.results.length;
665 _this.triggerEvent(_this.ArtyomGlobalEvents.TEXT_RECOGNIZED);
666 for (var i = event.resultIndex; i < cantidadResultados; ++i) {
667 var identificated = event.results[i][0].transcript;
668 if (event.results[i].isFinal) {
669 var comando = _this.execute(identificated.trim());
670 // Redirect the output of the text if necessary
671 if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") {
672 _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, true);
673 }
674 if ((comando) && (_this.ArtyomProperties.recognizing == true)) {
675 _this.debug("<< Executing Matching Recognition in normal mode >>", "info");
676 _this.ArtyomWebkitSpeechRecognition.stop();
677 _this.ArtyomProperties.recognizing = false;
678 // Execute the command if smart
679 if (comando.wildcard) {
680 comando.instruction.action(comando.index, comando.wildcard.item, comando.wildcard.full);
681 // Execute a normal command
682 }
683 else {
684 comando.instruction.action(comando.index);
685 }
686 break;
687 }
688 }
689 else {
690 // Redirect output when necesary
691 if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") {
692 _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, false);
693 }
694 if (typeof (_this.ArtyomProperties.executionKeyword) === "string") {
695 if (identificated.indexOf(_this.ArtyomProperties.executionKeyword) != -1) {
696 var comando = _this.execute(identificated.replace(_this.ArtyomProperties.executionKeyword, '').trim());
697 if ((comando) && (_this.ArtyomProperties.recognizing == true)) {
698 _this.debug("<< Executing command ordered by ExecutionKeyword >>", 'info');
699 _this.ArtyomWebkitSpeechRecognition.stop();
700 _this.ArtyomProperties.recognizing = false;
701 //Executing Command Action
702 if (comando.wildcard) {
703 comando.instruction.action(comando.index, comando.wildcard.item, comando.wildcard.full);
704 }
705 else {
706 comando.instruction.action(comando.index);
707 }
708 break;
709 }
710 }
711 }
712 _this.debug("Normal mode : " + identificated);
713 }
714 }
715 };
716 }
717 // Process the recognition in quick mode
718 if (_this.ArtyomProperties.mode == "quick") {
719 onResultProcessor = function (event) {
720 if (!_this.ArtyomCommands.length) {
721 _this.debug("No commands to process.");
722 return;
723 }
724 var cantidadResultados = event.results.length;
725 _this.triggerEvent(_this.ArtyomGlobalEvents.TEXT_RECOGNIZED);
726 for (var i = event.resultIndex; i < cantidadResultados; ++i) {
727 var identificated = event.results[i][0].transcript;
728 if (!event.results[i].isFinal) {
729 var comando = _this.execute(identificated.trim());
730 //Redirect output when necesary
731 if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") {
732 _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, true);
733 }
734 if ((comando) && (_this.ArtyomProperties.recognizing == true)) {
735 _this.debug("<< Executing Matching Recognition in quick mode >>", "info");
736 _this.ArtyomWebkitSpeechRecognition.stop();
737 _this.ArtyomProperties.recognizing = false;
738 //Executing Command Action
739 if (comando.wildcard) {
740 comando.instruction.action(comando.index, comando.wildcard.item);
741 }
742 else {
743 comando.instruction.action(comando.index);
744 }
745 break;
746 }
747 }
748 else {
749 var comando = _this.execute(identificated.trim());
750 //Redirect output when necesary
751 if (typeof (_this.ArtyomProperties.helpers.redirectRecognizedTextOutput) === "function") {
752 _this.ArtyomProperties.helpers.redirectRecognizedTextOutput(identificated, false);
753 }
754 if ((comando) && (_this.ArtyomProperties.recognizing == true)) {
755 _this.debug("<< Executing Matching Recognition in quick mode >>", "info");
756 _this.ArtyomWebkitSpeechRecognition.stop();
757 _this.ArtyomProperties.recognizing = false;
758 //Executing Command Action
759 if (comando.wildcard) {
760 comando.instruction.action(comando.index, comando.wildcard.item);
761 }
762 else {
763 comando.instruction.action(comando.index);
764 }
765 break;
766 }
767 }
768 _this.debug("Quick mode : " + identificated);
769 }
770 };
771 }
772 // Process the recognition in remote mode
773 if (_this.ArtyomProperties.mode == "remote") {
774 onResultProcessor = function (event) {
775 var cantidadResultados = event.results.length;
776 _this.triggerEvent(_this.ArtyomGlobalEvents.TEXT_RECOGNIZED);
777 if (typeof (_this.ArtyomProperties.helpers.remoteProcessorHandler) !== "function") {
778 return _this.debug("The remoteProcessorService is undefined.", "warn");
779 }
780 for (var i = event.resultIndex; i < cantidadResultados; ++i) {
781 var identificated = event.results[i][0].transcript;
782 _this.ArtyomProperties.helpers.remoteProcessorHandler({
783 text: identificated,
784 isFinal: event.results[i].isFinal
785 });
786 }
787 };
788 }
789 /**
790 * Process the recognition event with the previously
791 * declared processor function.
792 *
793 * @param {type} event
794 * @returns {undefined}
795 */
796 _this.ArtyomWebkitSpeechRecognition.onresult = function (event) {
797 if (_this.ArtyomProperties.obeying) {
798 onResultProcessor(event);
799 }
800 else {
801 // Handle obeyKeyword if exists and artyom is not obeying
802 if (!_this.ArtyomProperties.obeyKeyword) {
803 return;
804 }
805 var temporal = "";
806 var interim = "";
807 for (var i = 0; i < event.results.length; ++i) {
808 if (event.results[i].isFinal) {
809 temporal += event.results[i][0].transcript;
810 }
811 else {
812 interim += event.results[i][0].transcript;
813 }
814 }
815 _this.debug("Artyom is not obeying", "warn");
816 // If the obeyKeyword is found in the recognized text
817 // enable command recognition again
818 if (((interim).indexOf(_this.ArtyomProperties.obeyKeyword) > -1) || (temporal).indexOf(_this.ArtyomProperties.obeyKeyword) > -1) {
819 _this.ArtyomProperties.obeying = true;
820 }
821 }
822 };
823 if (_this.ArtyomProperties.recognizing) {
824 _this.ArtyomWebkitSpeechRecognition.stop();
825 _this.debug("Event reached : " + _this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END);
826 _this.triggerEvent(_this.ArtyomGlobalEvents.COMMAND_RECOGNITION_END);
827 }
828 else {
829 try {
830 _this.ArtyomWebkitSpeechRecognition.start();
831 }
832 catch (e) {
833 _this.triggerEvent(_this.ArtyomGlobalEvents.ERROR, {
834 code: "recognition_overlap",
835 message: "A webkitSpeechRecognition instance has been started while there's already running. Is recommendable to restart the Browser"
836 });
837 }
838 }
839 };
840 /**
841 * Set up artyom for the application.
842 *
843 * This function will set the default language used by artyom
844 * or notice the user if artyom is not supported in the actual
845 * browser
846 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/initialize
847 * @param {Object} config
848 * @returns {Boolean}
849 */
850 Artyom.prototype.initialize = function (config) {
851 var _this = this;
852 if (typeof (config) !== "object") {
853 return Promise.reject("You must give the configuration for start artyom properly.");
854 }
855 if (config.hasOwnProperty("lang")) {
856 _this.ArtyomVoice = _this.getVoice(config.lang);
857 _this.ArtyomProperties.lang = config.lang;
858 }
859 if (config.hasOwnProperty("continuous")) {
860 if (config.continuous) {
861 this.ArtyomProperties.continuous = true;
862 this.ArtyomFlags.restartRecognition = true;
863 }
864 else {
865 this.ArtyomProperties.continuous = false;
866 this.ArtyomFlags.restartRecognition = false;
867 }
868 }
869 if (config.hasOwnProperty("speed")) {
870 this.ArtyomProperties.speed = config.speed;
871 }
872 if (config.hasOwnProperty("soundex")) {
873 this.ArtyomProperties.soundex = config.soundex;
874 }
875 if (config.hasOwnProperty("executionKeyword")) {
876 this.ArtyomProperties.executionKeyword = config.executionKeyword;
877 }
878 if (config.hasOwnProperty("obeyKeyword")) {
879 this.ArtyomProperties.obeyKeyword = config.obeyKeyword;
880 }
881 if (config.hasOwnProperty("volume")) {
882 this.ArtyomProperties.volume = config.volume;
883 }
884 if (config.hasOwnProperty("listen")) {
885 this.ArtyomProperties.listen = config.listen;
886 }
887 if (config.hasOwnProperty("name")) {
888 this.ArtyomProperties.name = config.name;
889 }
890 if (config.hasOwnProperty("debug")) {
891 this.ArtyomProperties.debug = config.debug;
892 }
893 else {
894 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.");
895 }
896 if (config.mode) {
897 this.ArtyomProperties.mode = config.mode;
898 }
899 if (this.ArtyomProperties.listen === true) {
900 return new Promise(function (resolve, reject) {
901 _this.hey(resolve, reject);
902 });
903 }
904 return Promise.resolve(true);
905 };
906 /**
907 * Add commands like an artisan. If you use artyom for simple tasks
908 * then probably you don't like to write a lot to achieve it.
909 *
910 * Use the artisan syntax to write less, but with the same accuracy.
911 *
912 * @disclaimer Not a promise-based implementation, just syntax.
913 * @returns {Boolean}
914 */
915 Artyom.prototype.on = function (indexes, smart) {
916 var _this = this;
917 return {
918 then: function (action) {
919 var command = {
920 indexes: indexes,
921 action: action
922 };
923 if (smart) {
924 command.smart = true;
925 }
926 _this.addCommands(command);
927 }
928 };
929 };
930 /**
931 * Generates an artyom event with the designed name
932 *
933 * @param {type} name
934 * @returns {undefined}
935 */
936 Artyom.prototype.triggerEvent = function (name, param) {
937 var event = new CustomEvent(name, {
938 'detail': param
939 });
940 document.dispatchEvent(event);
941 return event;
942 };
943 /**
944 * Repeats the last sentence that artyom said.
945 * Useful in noisy environments.
946 *
947 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/repeatlastsay
948 * @param {Boolean} returnObject If set to true, an object with the text and the timestamp when was executed will be returned.
949 * @returns {Object}
950 */
951 Artyom.prototype.repeatLastSay = function (returnObject) {
952 var last = this.ArtyomProperties.helpers.lastSay;
953 if (returnObject) {
954 return last;
955 }
956 else {
957 if (last != null) {
958 this.say(last.text);
959 }
960 }
961 };
962 /**
963 * Create a listener when an artyom action is called.
964 *
965 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/when
966 * @param {type} event
967 * @param {type} action
968 * @returns {undefined}
969 */
970 Artyom.prototype.when = function (event, action) {
971 return document.addEventListener(event, function (e) {
972 action(e["detail"]);
973 }, false);
974 };
975 /**
976 * Process the recognized text if artyom is active in remote mode.
977 *
978 * @returns {Boolean}
979 */
980 Artyom.prototype.remoteProcessorService = function (action) {
981 this.ArtyomProperties.helpers.remoteProcessorHandler = action;
982 return true;
983 };
984 /**
985 * Verify if there's a voice available for a language using its language code identifier.
986 *
987 * @return {Boolean}
988 */
989 Artyom.prototype.voiceAvailable = function (languageCode) {
990 return typeof (this.getVoice(languageCode)) !== "undefined";
991 };
992 /**
993 * A boolean to check if artyom is obeying commands or not.
994 *
995 * @returns {Boolean}
996 */
997 Artyom.prototype.isObeying = function () {
998 return this.ArtyomProperties.obeying;
999 };
1000 /**
1001 * Allow artyom to obey commands again.
1002 *
1003 * @returns {Boolean}
1004 */
1005 Artyom.prototype.obey = function () {
1006 return this.ArtyomProperties.obeying = true;
1007 };
1008 /**
1009 * Pause the processing of commands. Artyom still listening in the background and it can be resumed after a couple of seconds.
1010 *
1011 * @returns {Boolean}
1012 */
1013 Artyom.prototype.dontObey = function () {
1014 return this.ArtyomProperties.obeying = false;
1015 };
1016 /**
1017 * This function returns a boolean according to the speechSynthesis status
1018 * if artyom is speaking, will return true.
1019 *
1020 * Note: This is not a feature of speechSynthesis, therefore this value hangs on
1021 * the fiability of the onStart and onEnd events of the speechSynthesis
1022 *
1023 * @since 0.9.3
1024 * @summary Returns true if speechSynthesis is active
1025 * @returns {Boolean}
1026 */
1027 Artyom.prototype.isSpeaking = function () {
1028 return this.ArtyomProperties.speaking;
1029 };
1030 /**
1031 * This function returns a boolean according to the SpeechRecognition status
1032 * if artyom is listening, will return true.
1033 *
1034 * Note: This is not a feature of SpeechRecognition, therefore this value hangs on
1035 * the fiability of the onStart and onEnd events of the SpeechRecognition
1036 *
1037 * @since 0.9.3
1038 * @summary Returns true if SpeechRecognition is active
1039 * @returns {Boolean}
1040 */
1041 Artyom.prototype.isRecognizing = function () {
1042 return this.ArtyomProperties.recognizing;
1043 };
1044 /**
1045 * This function will return the webkitSpeechRecognition object used by artyom
1046 * retrieve it only to debug on it or get some values, do not make changes directly
1047 *
1048 * @readonly
1049 * @since 0.9.2
1050 * @summary Retrieve the native webkitSpeechRecognition object
1051 * @returns {Object webkitSpeechRecognition}
1052 */
1053 Artyom.prototype.getNativeApi = function () {
1054 return this.ArtyomWebkitSpeechRecognition;
1055 };
1056 /**
1057 * Returns the SpeechSynthesisUtterance garbageobjects.
1058 *
1059 * @returns {Array}
1060 */
1061 Artyom.prototype.getGarbageCollection = function () {
1062 return this.ArtyomGarbageCollection;
1063 };
1064 /**
1065 * Retrieve a single voice of the browser by it's language code.
1066 * It will return the first voice available for the language on every device.
1067 *
1068 * @param languageCode
1069 */
1070 Artyom.prototype.getVoice = function (languageCode) {
1071 var voiceIdentifiersArray = this.ArtyomVoicesIdentifiers[languageCode];
1072 if (!voiceIdentifiersArray) {
1073 console.warn("The providen language " + languageCode + " isn't available, using English Great britain as default");
1074 voiceIdentifiersArray = this.ArtyomVoicesIdentifiers["en-GB"];
1075 }
1076 var voice = undefined;
1077 var voices = speechSynthesis.getVoices();
1078 var voicesLength = voiceIdentifiersArray.length;
1079 var _loop_1 = function (i) {
1080 var foundVoice = voices.filter(function (voice) {
1081 return ((voice.name == voiceIdentifiersArray[i]) || (voice.lang == voiceIdentifiersArray[i]));
1082 })[0];
1083 if (foundVoice) {
1084 voice = foundVoice;
1085 return "break";
1086 }
1087 };
1088 for (var i = 0; i < voicesLength; i++) {
1089 var state_1 = _loop_1(i);
1090 if (state_1 === "break")
1091 break;
1092 }
1093 return voice;
1094 };
1095 /**
1096 * Artyom provide an easy way to create a
1097 * dictation for your user.
1098 *
1099 * Just create an instance and start and stop when you want
1100 *
1101 * @returns Object | newDictation
1102 */
1103 Artyom.prototype.newDictation = function (settings) {
1104 var _this = this;
1105 if (!_this.recognizingSupported()) {
1106 console.error("SpeechRecognition is not supported in this browser");
1107 return false;
1108 }
1109 var dictado = new window.webkitSpeechRecognition();
1110 dictado.continuous = true;
1111 dictado.interimResults = true;
1112 dictado.lang = _this.ArtyomProperties.lang;
1113 dictado.onresult = function (event) {
1114 var temporal = "";
1115 var interim = "";
1116 for (var i = 0; i < event.results.length; ++i) {
1117 if (event.results[i].isFinal) {
1118 temporal += event.results[i][0].transcript;
1119 }
1120 else {
1121 interim += event.results[i][0].transcript;
1122 }
1123 }
1124 if (settings.onResult) {
1125 settings.onResult(interim, temporal);
1126 }
1127 };
1128 return new function () {
1129 var dictation = dictado;
1130 var flagStartCallback = true;
1131 var flagRestart = false;
1132 this.onError = null;
1133 this.start = function () {
1134 if (settings.continuous === true) {
1135 flagRestart = true;
1136 }
1137 dictation.onstart = function () {
1138 if (typeof (settings.onStart) === "function") {
1139 if (flagStartCallback === true) {
1140 settings.onStart();
1141 }
1142 }
1143 };
1144 dictation.onend = function () {
1145 if (flagRestart === true) {
1146 flagStartCallback = false;
1147 dictation.start();
1148 }
1149 else {
1150 flagStartCallback = true;
1151 if (typeof (settings.onEnd) === "function") {
1152 settings.onEnd();
1153 }
1154 }
1155 };
1156 dictation.start();
1157 };
1158 this.stop = function () {
1159 flagRestart = false;
1160 dictation.stop();
1161 };
1162 if (typeof (settings.onError) === "function") {
1163 dictation.onerror = settings.onError;
1164 }
1165 };
1166 };
1167 /**
1168 * A voice prompt will be executed.
1169 *
1170 * @param {type} config
1171 * @returns {undefined}
1172 */
1173 Artyom.prototype.newPrompt = function (config) {
1174 if (typeof (config) !== "object") {
1175 console.error("Expected the prompt configuration.");
1176 }
1177 var copyActualCommands = Object.assign([], this.ArtyomCommands);
1178 var _this = this;
1179 this.emptyCommands();
1180 var promptCommand = {
1181 description: "Setting the artyom commands only for the prompt. The commands will be restored after the prompt finishes",
1182 indexes: config.options,
1183 action: function (i, wildcard) {
1184 _this.ArtyomCommands = copyActualCommands;
1185 var toExe = config.onMatch(i, wildcard);
1186 if (typeof (toExe) !== "function") {
1187 console.error("onMatch function expects a returning function to be executed");
1188 return;
1189 }
1190 toExe();
1191 }
1192 };
1193 if (config.smart) {
1194 promptCommand.smart = true;
1195 }
1196 this.addCommands(promptCommand);
1197 if (typeof (config.beforePrompt) !== "undefined") {
1198 config.beforePrompt();
1199 }
1200 var callbacks = {
1201 onStart: function () {
1202 if (typeof (config.onStartPrompt) !== "undefined") {
1203 config.onStartPrompt();
1204 }
1205 },
1206 onEnd: function () {
1207 if (typeof (config.onEndPrompt) !== "undefined") {
1208 config.onEndPrompt();
1209 }
1210 }
1211 };
1212 this.say(config.question, callbacks);
1213 };
1214 /**
1215 * Says a random quote and returns it's object
1216 *
1217 * @param {type} data
1218 * @returns {object}
1219 */
1220 Artyom.prototype.sayRandom = function (data) {
1221 if (data instanceof Array) {
1222 var index = Math.floor(Math.random() * data.length);
1223 this.say(data[index]);
1224 return {
1225 text: data[index],
1226 index: index
1227 };
1228 }
1229 else {
1230 console.error("Random quotes must be in an array !");
1231 return null;
1232 }
1233 };
1234 /**
1235 * Shortcut method to enable the artyom debug on the fly.
1236 *
1237 * @returns {Array}
1238 */
1239 Artyom.prototype.setDebug = function (status) {
1240 if (status) {
1241 return this.ArtyomProperties.debug = true;
1242 }
1243 else {
1244 return this.ArtyomProperties.debug = false;
1245 }
1246 };
1247 /**
1248 * Simulate a voice command via JS
1249 *
1250 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/simulateinstruction
1251 * @param {type} sentence
1252 * @returns {undefined}
1253 */
1254 Artyom.prototype.simulateInstruction = function (sentence) {
1255 var _this = this;
1256 if ((!sentence) || (typeof (sentence) !== "string")) {
1257 console.warn("Cannot execute a non string command");
1258 return false;
1259 }
1260 var foundCommand = _this.execute(sentence); //Command founded object
1261 if (typeof (foundCommand) === "object") {
1262 if (foundCommand.instruction) {
1263 if (foundCommand.instruction.smart) {
1264 _this.debug('Smart command matches with simulation, executing', "info");
1265 foundCommand.instruction.action(foundCommand.index, foundCommand.wildcard.item, foundCommand.wildcard.full);
1266 }
1267 else {
1268 _this.debug('Command matches with simulation, executing', "info");
1269 foundCommand.instruction.action(foundCommand.index); //Execute Normal command
1270 }
1271 return true;
1272 }
1273 }
1274 else {
1275 console.warn("No command founded trying with " + sentence);
1276 return false;
1277 }
1278 };
1279 /**
1280 * Javascript implementation of the soundex algorithm.
1281 * @see https://gist.github.com/shawndumas/1262659
1282 * @returns {String}
1283 */
1284 Artyom.prototype.soundex = function (s) {
1285 var a = s.toLowerCase().split('');
1286 var f = a.shift();
1287 var r = '';
1288 var codes = { a: "", e: "", i: "", o: "", u: "", b: 1, f: 1, p: 1, v: 1, c: 2, g: 2, j: 2, k: 2, q: 2, s: 2, x: 2, z: 2, d: 3, t: 3, l: 4, m: 5, n: 5, r: 6 };
1289 r = f + a
1290 .map(function (v, i, a) {
1291 return codes[v];
1292 })
1293 .filter(function (v, i, a) {
1294 return ((i === 0) ? v !== codes[f] : v !== a[i - 1]);
1295 })
1296 .join('');
1297 return (r + '000').slice(0, 4).toUpperCase();
1298 };
1299 /**
1300 * Splits a string into an array of strings with a limited size (chunk_length).
1301 *
1302 * @param {String} input text to split into chunks
1303 * @param {Integer} chunk_length limit of characters in every chunk
1304 */
1305 Artyom.prototype.splitStringByChunks = function (input, chunk_length) {
1306 input = input || "";
1307 chunk_length = chunk_length || 100;
1308 var curr = chunk_length;
1309 var prev = 0;
1310 var output = [];
1311 while (input[curr]) {
1312 if (input[curr++] == ' ') {
1313 output.push(input.substring(prev, curr));
1314 prev = curr;
1315 curr += chunk_length;
1316 }
1317 }
1318 output.push(input.substr(prev));
1319 return output;
1320 };
1321 /**
1322 * Allows to retrieve the recognized spoken text of artyom
1323 * and do something with it everytime something is recognized
1324 *
1325 * @param {String} action
1326 * @returns {Boolean}
1327 */
1328 Artyom.prototype.redirectRecognizedTextOutput = function (action) {
1329 if (typeof (action) != "function") {
1330 console.warn("Expected function to handle the recognized text ...");
1331 return false;
1332 }
1333 this.ArtyomProperties.helpers.redirectRecognizedTextOutput = action;
1334 return true;
1335 };
1336 /**
1337 * Restarts artyom with the initial configuration.
1338 *
1339 * @param configuration
1340 */
1341 Artyom.prototype.restart = function () {
1342 var _this = this;
1343 var _copyInit = _this.ArtyomProperties;
1344 return new Promise(function (resolve, reject) {
1345 _this.fatality().then(function () {
1346 _this.initialize(_copyInit).then(resolve, reject);
1347 });
1348 });
1349 };
1350 /**
1351 * Talks a text according to the given parameters.
1352 *
1353 * @private This function is only to be used internally.
1354 * @param {String} text Text to be spoken
1355 * @param {Int} actualChunk Number of chunk of the
1356 * @param {Int} totalChunks
1357 * @returns {undefined}
1358 */
1359 Artyom.prototype.talk = function (text, actualChunk, totalChunks, callbacks) {
1360 var _this = this;
1361 var msg = new SpeechSynthesisUtterance();
1362 msg.text = text;
1363 msg.volume = this.ArtyomProperties.volume;
1364 msg.rate = this.ArtyomProperties.speed;
1365 // Select the voice according to the selected
1366 var availableVoice = _this.getVoice(_this.ArtyomProperties.lang);
1367 if (callbacks) {
1368 // If the language to speak has been forced, use it
1369 if (callbacks.hasOwnProperty("lang")) {
1370 availableVoice = _this.getVoice(callbacks.lang);
1371 }
1372 }
1373 // If is a mobile device, provide only the language code in the lang property i.e "es_ES"
1374 if (this.Device.isMobile) {
1375 // Try to set the voice only if exists, otherwise don't use anything to use the native voice
1376 if (availableVoice) {
1377 msg.lang = availableVoice.lang;
1378 }
1379 // If browser provide the entire object
1380 }
1381 else {
1382 msg.voice = availableVoice;
1383 }
1384 // If is first text chunk (onStart)
1385 if (actualChunk == 1) {
1386 msg.addEventListener('start', function () {
1387 // Set artyom is talking
1388 _this.ArtyomProperties.speaking = true;
1389 // Trigger the onSpeechSynthesisStart event
1390 _this.debug("Event reached : " + _this.ArtyomGlobalEvents.SPEECH_SYNTHESIS_START);
1391 _this.triggerEvent(_this.ArtyomGlobalEvents.SPEECH_SYNTHESIS_START);
1392 // Trigger the onStart callback if exists
1393 if (callbacks) {
1394 if (typeof (callbacks.onStart) == "function") {
1395 callbacks.onStart.call(msg);
1396 }
1397 }
1398 });
1399 }
1400 // If is final text chunk (onEnd)
1401 if ((actualChunk) >= totalChunks) {
1402 msg.addEventListener('end', function () {
1403 // Set artyom is talking
1404 _this.ArtyomProperties.speaking = false;
1405 // Trigger the onSpeechSynthesisEnd event
1406 _this.debug("Event reached : " + _this.ArtyomGlobalEvents.SPEECH_SYNTHESIS_END);
1407 _this.triggerEvent(_this.ArtyomGlobalEvents.SPEECH_SYNTHESIS_END);
1408 // Trigger the onEnd callback if exists.
1409 if (callbacks) {
1410 if (typeof (callbacks.onEnd) == "function") {
1411 callbacks.onEnd.call(msg);
1412 }
1413 }
1414 });
1415 }
1416 // Notice how many chunks were processed for the given text.
1417 this.debug((actualChunk) + " text chunk processed succesfully out of " + totalChunks);
1418 // Important : Save the SpeechSynthesisUtterance object in memory, otherwise it will get lost
1419 this.ArtyomGarbageCollection.push(msg);
1420 window.speechSynthesis.speak(msg);
1421 };
1422 /**
1423 * Process the given text into chunks and execute the private function talk
1424 *
1425 * @tutorial http://docs.ourcodeworld.com/projects/artyom-js/documentation/methods/say
1426 * @param {String} message Text to be spoken
1427 * @param {Object} callbacks
1428 * @returns {undefined}
1429 */
1430 Artyom.prototype.say = function (message, callbacks) {
1431 var artyom_say_max_chunk_length = 115;
1432 var _this = this;
1433 var definitive = [];
1434 if (this.speechSupported()) {
1435 if (typeof (message) != 'string') {
1436 return console.warn("Artyom expects a string to speak " + typeof message + " given");
1437 }
1438 if (!message.length) {
1439 return console.warn("Cannot speak empty string");
1440 }
1441 // If the providen text is long, proceed to split it
1442 if (message.length > artyom_say_max_chunk_length) {
1443 // Split the given text by pause reading characters [",",":",";",". "] to provide a natural reading feeling.
1444 var naturalReading = message.split(/,|:|\. |;/);
1445 naturalReading.forEach(function (chunk, index) {
1446 // If the sentence is too long and could block the API, split it to prevent any errors.
1447 if (chunk.length > artyom_say_max_chunk_length) {
1448 // Process the providen string into strings (withing an array) of maximum aprox. 115 characters to prevent any error with the API.
1449 var temp_processed = _this.splitStringByChunks(chunk, artyom_say_max_chunk_length);
1450 // Add items of the processed sentence into the definitive chunk.
1451 definitive.push.apply(definitive, temp_processed);
1452 }
1453 else {
1454 // Otherwise just add the sentence to being spoken.
1455 definitive.push(chunk);
1456 }
1457 });
1458 }
1459 else {
1460 definitive.push(message);
1461 }
1462 // Clean any empty item in array
1463 definitive = definitive.filter(function (e) { return e; });
1464 // Finally proceed to talk the chunks and assign the callbacks.
1465 definitive.forEach(function (chunk, index) {
1466 var numberOfChunk = (index + 1);
1467 if (chunk) {
1468 _this.talk(chunk, numberOfChunk, definitive.length, callbacks);
1469 }
1470 });
1471 // Save the spoken text into the lastSay object of artyom
1472 _this.ArtyomProperties.helpers.lastSay = {
1473 text: message,
1474 date: new Date()
1475 };
1476 }
1477 };
1478 return Artyom;
1479}());
1480exports.default = Artyom;