├── test ├── init_corti.js ├── spec │ ├── .jshintrc │ └── IssuesSpec.js ├── SpecRunner.html └── vendor │ └── corti.js ├── demo ├── annyang.min.js ├── images │ ├── flickr.png │ ├── icon_js.png │ ├── palette.png │ ├── icon_user.png │ ├── tpscover.jpg │ ├── icon_speech.png │ ├── mini_icon_say.png │ └── footer_background.jpg ├── vendor │ ├── css │ │ ├── tomorrow.css │ │ ├── github.css │ │ └── default.css │ ├── js │ │ └── highlight.pack.js │ └── html │ │ └── github-btn.html ├── css │ ├── main.css │ └── main.min.css └── index.html ├── .gitattributes ├── .gitignore ├── sites ├── README.md ├── geektime.js ├── facebook.js ├── geektime.min.js └── facebook.min.js ├── .editorconfig ├── .travis.yml ├── .eslintrc.json ├── bower.json ├── .jshintrc ├── LICENSE ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── package.json ├── README.md ├── dist ├── annyang.min.js └── annyang.js ├── Gruntfile.js ├── CONTRIBUTING.md ├── docs ├── FAQ.md └── README.md ├── require.js └── src └── annyang.js /test/init_corti.js: -------------------------------------------------------------------------------- 1 | Corti.patch(); -------------------------------------------------------------------------------- /demo/annyang.min.js: -------------------------------------------------------------------------------- 1 | ../dist/annyang.min.js -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | demo/* linguist-documentation 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .idea 4 | .grunt 5 | .DS_Store 6 | test/coverage/ 7 | -------------------------------------------------------------------------------- /demo/images/flickr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/flickr.png -------------------------------------------------------------------------------- /demo/images/icon_js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/icon_js.png -------------------------------------------------------------------------------- /demo/images/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/palette.png -------------------------------------------------------------------------------- /demo/images/icon_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/icon_user.png -------------------------------------------------------------------------------- /demo/images/tpscover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/tpscover.jpg -------------------------------------------------------------------------------- /demo/images/icon_speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/icon_speech.png -------------------------------------------------------------------------------- /demo/images/mini_icon_say.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/mini_icon_say.png -------------------------------------------------------------------------------- /demo/images/footer_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinanf-boy/annyang/master/demo/images/footer_background.jpg -------------------------------------------------------------------------------- /sites/README.md: -------------------------------------------------------------------------------- 1 | annyang samples 2 | =============== 3 | 4 | Sample script files to control popular sites with voice commands. Intended as inspiration. 5 | 6 | To try it, run the site's minified source code in the web developer console. 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '6' 5 | - '5' 6 | - '4' 7 | - 'node' 8 | before_script: 9 | - 'npm install -g grunt-cli' 10 | - 'grunt' 11 | deploy: 12 | provider: npm 13 | email: tal@talater.com 14 | api_key: 15 | secure: B7djVh1jMSccBqDoFYO+yDcucgd5u7vR8dy6U1p48BKcA78bNlIm58MO2XF22Qx0uqKAv3piD385DgUoICoHU4Au9BXFBEdMpxbLsd4LVIOekPCnOlF2GHz9p8mhUzNbXP4qXLjoNz1tZlcd9LgSujN35PnNDp00AKdjs8vIceo= 16 | on: 17 | tags: true 18 | branch: master 19 | node: '5' 20 | repo: TalAter/annyang 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended" 8 | ], 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "no-console": 0, 14 | "indent": [ 15 | "error", 16 | 2 17 | ], 18 | "linebreak-style": [ 19 | "error", 20 | "unix" 21 | ], 22 | "quotes": [ 23 | "error", 24 | "single" 25 | ], 26 | "semi": [ 27 | "error", 28 | "always" 29 | ] 30 | }, 31 | "globals": { 32 | "define": false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "annyang", 3 | "description": "A javascript library for adding voice commands to your site, using speech recognition", 4 | "main": "dist/annyang.min.js", 5 | "keywords": [ 6 | "annyang", 7 | "annyang.js", 8 | "speechrecognition", 9 | "webkitspeechrecognition", 10 | "voice", 11 | "speech", 12 | "recognition" 13 | ], 14 | "author": "Tal Ater (https://www.talater.com/)", 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "Gruntfile.js", 20 | "package.json", 21 | "bower.json", 22 | "demo", 23 | "sites" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node" : true, 3 | "browser" : true, 4 | "devel" : false, 5 | "camelcase" : true, 6 | "curly" : true, 7 | "latedef" : true, 8 | "unused" : true, 9 | "trailing" : true, 10 | "eqeqeq" : true, 11 | "eqnull" : true, 12 | "evil" : false, 13 | "forin" : true, 14 | "immed" : true, 15 | "laxbreak" : false, 16 | "newcap" : true, 17 | "noarg" : true, 18 | "noempty" : false, 19 | "nonew" : true, 20 | "onevar" : false, 21 | "plusplus" : false, 22 | "undef" : true, 23 | "sub" : true, 24 | "strict" : true, 25 | "white" : false, 26 | "indent" : 2, 27 | "esversion" : 6, 28 | "globals" : { 29 | "define" : false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/spec/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node" : true, 3 | "browser" : true, 4 | "devel" : false, 5 | "camelcase" : true, 6 | "curly" : true, 7 | "latedef" : true, 8 | "unused" : true, 9 | "trailing" : true, 10 | "eqeqeq" : true, 11 | "eqnull" : true, 12 | "evil" : false, 13 | "forin" : true, 14 | "immed" : true, 15 | "laxbreak" : false, 16 | "newcap" : true, 17 | "noarg" : true, 18 | "noempty" : false, 19 | "nonew" : true, 20 | "onevar" : false, 21 | "plusplus" : false, 22 | "undef" : true, 23 | "sub" : true, 24 | "strict" : true, 25 | "white" : false, 26 | "indent" : 2, 27 | "globals": { 28 | "afterEach": false, 29 | "beforeEach": false, 30 | "describe": false, 31 | "xdescribe": false, 32 | "expect": false, 33 | "jasmine": false, 34 | "spyOn": false, 35 | "it": false, 36 | "xit": false, 37 | "annyang": false, 38 | "define": false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tal Ater 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Version used: 30 | * Browser Name and version: 31 | * Operating System and version (desktop or mobile): 32 | * Link to your project: 33 | -------------------------------------------------------------------------------- /test/spec/IssuesSpec.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | // jshint strict: false 3 | if (typeof module === 'object' && module.exports) { // CommonJS 4 | factory(require('../../dist/annyang.js')); 5 | } else if (typeof define === 'function' && define.amd) { // AMD 6 | define(['annyang'], factory); 7 | } else { // Browser globals 8 | factory(root.annyang); 9 | } 10 | }(typeof window !== 'undefined' ? window : this, function factory(annyang) { 11 | "use strict"; 12 | 13 | // Issue #193 (https://github.com/TalAter/annyang/issues/193) 14 | describe('Speech recognition aborting while annyang is paused', function() { 15 | 16 | var recognition; 17 | 18 | beforeEach(function() { 19 | recognition = annyang.getSpeechRecognizer(); 20 | jasmine.clock().install(); 21 | annyang.abort(); 22 | annyang.removeCommands(); 23 | }); 24 | 25 | afterEach(function() { 26 | jasmine.clock().tick(2000); 27 | jasmine.clock().uninstall(); 28 | }); 29 | 30 | it('should not unpause annyang on restart', function() { 31 | annyang.start({ autoRestart: true, continuous: false }); 32 | annyang.pause(); 33 | recognition.abort(); 34 | expect(annyang.isListening()).toBe(false); 35 | jasmine.clock().tick(1000); 36 | expect(annyang.isListening()).toBe(false); 37 | }); 38 | 39 | }); 40 | 41 | })); 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Types of changes 16 | 17 | - [ ] Bug fix (non-breaking change which fixes an issue) 18 | - [ ] New feature (non-breaking change which adds functionality) 19 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 20 | 21 | ## Checklist: 22 | 23 | 24 | - [ ] My code follows the code style of this project. 25 | - [ ] My change requires a change to the documentation. 26 | - [ ] I have updated the documentation accordingly. 27 | - [ ] I have read the **CONTRIBUTING** document. 28 | - [ ] I have added tests to cover my changes. 29 | - [ ] All new and existing tests passed. 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "annyang", 3 | "version": "2.6.0", 4 | "description": "A javascript library for adding voice commands to your site, using speech recognition", 5 | "homepage": "https://www.talater.com/annyang/", 6 | "main": "dist/annyang.min.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/TalAter/annyang.git" 10 | }, 11 | "keywords": [ 12 | "annyang", 13 | "annyang.js", 14 | "speechrecognition", 15 | "webkitspeechrecognition", 16 | "voice", 17 | "speech", 18 | "recognition" 19 | ], 20 | "author": "Tal Ater (https://www.talater.com/)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/TalAter/annyang/issues" 24 | }, 25 | "scripts": { 26 | "start": "grunt dev", 27 | "test": "grunt test" 28 | }, 29 | "devDependencies": { 30 | "async": "^2.4.1", 31 | "babel-preset-es2015": "^6.24.1", 32 | "grunt": "~0.4.5", 33 | "grunt-babel": "^6.0.0", 34 | "grunt-contrib-connect": "^1.0.2", 35 | "grunt-contrib-cssmin": "^2.2.0", 36 | "grunt-contrib-imagemin": "^1.0.1", 37 | "grunt-contrib-jasmine": "^1.1.0", 38 | "grunt-contrib-jshint": "^1.1.0", 39 | "grunt-contrib-uglify": "^3.0.1", 40 | "grunt-contrib-watch": "^1.0.0", 41 | "grunt-markdox": "^1.2.1", 42 | "grunt-template-jasmine-istanbul": "^0.5.0", 43 | "grunt-template-jasmine-requirejs": "^0.2.3", 44 | "load-grunt-tasks": "^3.5.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /demo/vendor/css/tomorrow.css: -------------------------------------------------------------------------------- 1 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 2 | .tomorrow-comment, pre .comment, pre .title { 3 | color: #8e908c; 4 | } 5 | 6 | .tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo { 7 | color: #c82829; 8 | } 9 | 10 | .tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant { 11 | color: #f5871f; 12 | } 13 | 14 | .tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute { 15 | color: #eab700; 16 | } 17 | 18 | .tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata { 19 | color: #718c00; 20 | } 21 | 22 | .tomorrow-aqua, pre .css .hexcolor { 23 | color: #3e999f; 24 | } 25 | 26 | .tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title { 27 | color: #4271ae; 28 | } 29 | 30 | .tomorrow-purple, pre .keyword, pre .javascript .function { 31 | color: #8959a8; 32 | } 33 | 34 | pre code { 35 | display: block; 36 | background: white; 37 | color: #4d4d4c; 38 | padding: 0.5em; 39 | } 40 | 41 | pre .coffeescript .javascript, 42 | pre .javascript .xml, 43 | pre .tex .formula, 44 | pre .xml .javascript, 45 | pre .xml .vbscript, 46 | pre .xml .css, 47 | pre .xml .cdata { 48 | opacity: 0.5; 49 | } 50 | -------------------------------------------------------------------------------- /demo/vendor/css/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | pre code { 8 | display: block; padding: 0.5em; 9 | color: #333; 10 | background: #f8f8ff 11 | } 12 | 13 | pre .comment, 14 | pre .template_comment, 15 | pre .diff .header, 16 | pre .javadoc { 17 | color: #998; 18 | font-style: italic 19 | } 20 | 21 | pre .keyword, 22 | pre .css .rule .keyword, 23 | pre .winutils, 24 | pre .javascript .title, 25 | pre .nginx .title, 26 | pre .subst, 27 | pre .request, 28 | pre .status { 29 | color: #333; 30 | font-weight: bold 31 | } 32 | 33 | pre .number, 34 | pre .hexcolor, 35 | pre .ruby .constant { 36 | color: #099; 37 | } 38 | 39 | pre .string, 40 | pre .tag .value, 41 | pre .phpdoc, 42 | pre .tex .formula { 43 | color: #d14 44 | } 45 | 46 | pre .title, 47 | pre .id { 48 | color: #900; 49 | font-weight: bold 50 | } 51 | 52 | pre .javascript .title, 53 | pre .lisp .title, 54 | pre .clojure .title, 55 | pre .subst { 56 | font-weight: normal 57 | } 58 | 59 | pre .class .title, 60 | pre .haskell .type, 61 | pre .vhdl .literal, 62 | pre .tex .command { 63 | color: #458; 64 | font-weight: bold 65 | } 66 | 67 | pre .tag, 68 | pre .tag .title, 69 | pre .rules .property, 70 | pre .django .tag .keyword { 71 | color: #000080; 72 | font-weight: normal 73 | } 74 | 75 | pre .attribute, 76 | pre .variable, 77 | pre .lisp .body { 78 | color: #008080 79 | } 80 | 81 | pre .regexp { 82 | color: #009926 83 | } 84 | 85 | pre .class { 86 | color: #458; 87 | font-weight: bold 88 | } 89 | 90 | pre .symbol, 91 | pre .ruby .symbol .string, 92 | pre .lisp .keyword, 93 | pre .tex .special, 94 | pre .prompt { 95 | color: #990073 96 | } 97 | 98 | pre .built_in, 99 | pre .lisp .title, 100 | pre .clojure .built_in { 101 | color: #0086b3 102 | } 103 | 104 | pre .preprocessor, 105 | pre .pi, 106 | pre .doctype, 107 | pre .shebang, 108 | pre .cdata { 109 | color: #999; 110 | font-weight: bold 111 | } 112 | 113 | pre .deletion { 114 | background: #fdd 115 | } 116 | 117 | pre .addition { 118 | background: #dfd 119 | } 120 | 121 | pre .diff .change { 122 | background: #0086b3 123 | } 124 | 125 | pre .chunk { 126 | color: #aaa 127 | } 128 | -------------------------------------------------------------------------------- /demo/vendor/css/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | pre code { 8 | display: block; padding: 0.5em; 9 | background: #F0F0F0; 10 | } 11 | 12 | pre code, 13 | pre .subst, 14 | pre .tag .title, 15 | pre .lisp .title, 16 | pre .clojure .built_in, 17 | pre .nginx .title { 18 | color: black; 19 | } 20 | 21 | pre .string, 22 | pre .title, 23 | pre .constant, 24 | pre .parent, 25 | pre .tag .value, 26 | pre .rules .value, 27 | pre .rules .value .number, 28 | pre .preprocessor, 29 | pre .ruby .symbol, 30 | pre .ruby .symbol .string, 31 | pre .aggregate, 32 | pre .template_tag, 33 | pre .django .variable, 34 | pre .smalltalk .class, 35 | pre .addition, 36 | pre .flow, 37 | pre .stream, 38 | pre .bash .variable, 39 | pre .apache .tag, 40 | pre .apache .cbracket, 41 | pre .tex .command, 42 | pre .tex .special, 43 | pre .erlang_repl .function_or_atom, 44 | pre .markdown .header { 45 | color: #800; 46 | } 47 | 48 | pre .comment, 49 | pre .annotation, 50 | pre .template_comment, 51 | pre .diff .header, 52 | pre .chunk, 53 | pre .markdown .blockquote { 54 | color: #888; 55 | } 56 | 57 | pre .number, 58 | pre .date, 59 | pre .regexp, 60 | pre .literal, 61 | pre .smalltalk .symbol, 62 | pre .smalltalk .char, 63 | pre .go .constant, 64 | pre .change, 65 | pre .markdown .bullet, 66 | pre .markdown .link_url { 67 | color: #080; 68 | } 69 | 70 | pre .label, 71 | pre .javadoc, 72 | pre .ruby .string, 73 | pre .decorator, 74 | pre .filter .argument, 75 | pre .localvars, 76 | pre .array, 77 | pre .attr_selector, 78 | pre .important, 79 | pre .pseudo, 80 | pre .pi, 81 | pre .doctype, 82 | pre .deletion, 83 | pre .envvar, 84 | pre .shebang, 85 | pre .apache .sqbracket, 86 | pre .nginx .built_in, 87 | pre .tex .formula, 88 | pre .erlang_repl .reserved, 89 | pre .prompt, 90 | pre .markdown .link_label, 91 | pre .vhdl .attribute, 92 | pre .clojure .attribute, 93 | pre .coffeescript .property { 94 | color: #88F 95 | } 96 | 97 | pre .keyword, 98 | pre .id, 99 | pre .phpdoc, 100 | pre .title, 101 | pre .built_in, 102 | pre .aggregate, 103 | pre .css .tag, 104 | pre .javadoctag, 105 | pre .phpdoc, 106 | pre .yardoctag, 107 | pre .smalltalk .class, 108 | pre .winutils, 109 | pre .bash .variable, 110 | pre .apache .tag, 111 | pre .go .typename, 112 | pre .tex .command, 113 | pre .markdown .strong, 114 | pre .request, 115 | pre .status { 116 | font-weight: bold; 117 | } 118 | 119 | pre .markdown .emphasis { 120 | font-style: italic; 121 | } 122 | 123 | pre .nginx .built_in { 124 | font-weight: normal; 125 | } 126 | 127 | pre .coffeescript .javascript, 128 | pre .javascript .xml, 129 | pre .tex .formula, 130 | pre .xml .javascript, 131 | pre .xml .vbscript, 132 | pre .xml .css, 133 | pre .xml .cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /sites/geektime.js: -------------------------------------------------------------------------------- 1 | //! annyang - Geektime.co.il 2 | //! version : 0.1.0 3 | //! author : Tal Ater @TalAter 4 | //! license : MIT 5 | //! https://www.TalAter.com/annyang/ 6 | 7 | (function () { 8 | /*global annyang,jQuery */ 9 | "use strict"; 10 | var root = this; 11 | 12 | if (annyang) { 13 | annyang.debug(); 14 | annyang.setLanguage('he'); 15 | 16 | var nextPage = function() { 17 | root.location.href = jQuery('.next-page a').attr("href") || '/'; 18 | }; 19 | 20 | var prevPage = function() { 21 | root.location.href = jQuery('.prev-page a').attr("href") || '/'; 22 | }; 23 | 24 | var gotoHome = function() { 25 | root.location.href = '/'; 26 | }; 27 | 28 | var gotoGadgets = function() { 29 | root.location.href = '/category/gadgets/'; 30 | }; 31 | 32 | var gotoStartup = function() { 33 | root.location.href = '/category/startup/'; 34 | }; 35 | 36 | var gotoDevelopment = function() { 37 | root.location.href = '/category/development/'; 38 | }; 39 | 40 | var gotoInternet = function() { 41 | root.location.href = '/category/internet/'; 42 | }; 43 | 44 | var gotoHitech = function() { 45 | root.location.href = '/category/hi-tech/'; 46 | }; 47 | 48 | var gotoGames = function() { 49 | root.location.href = '/category/games1/'; 50 | }; 51 | 52 | var gotoScience = function() { 53 | root.location.href = '/category/science/'; 54 | }; 55 | 56 | var gotoEvents = function() { 57 | root.location.href = '/eventsboard/'; 58 | }; 59 | 60 | var gotoJobs = function() { 61 | root.location.href = 'http://geekjob.co.il/'; 62 | }; 63 | 64 | var gotoSearch = function(search) { 65 | root.location.href = '/?s='+search; 66 | }; 67 | 68 | var scrollToTop = function() { 69 | jQuery("html, body").animate({ scrollTop: 0 }, 1500); 70 | return false; 71 | }; 72 | 73 | annyang.addCommands({ 74 | '(דף) (ה)בית': gotoHome, 75 | '(דף) הבא': nextPage, 76 | '(דף) (ה)קודם': prevPage, 77 | 78 | 'גאג׳טים*etc': gotoGadgets, 79 | 'מובייל*etc': gotoGadgets, 80 | 'mobile*etc': gotoGadgets, 81 | 82 | 'סטארט אפ*etc': gotoStartup, 83 | ':etc סיכון': gotoStartup, 84 | 85 | 'פיתוח*etc': gotoDevelopment, 86 | 87 | 'אינטרנט*etc': gotoInternet, 88 | 89 | 'היי טק*etc': gotoHitech, 90 | 91 | 'משחקים*etc': gotoGames, 92 | 93 | 'מדע*etc': gotoScience, 94 | 95 | 'אירוע*etc': gotoEvents, 96 | 97 | 'משרות*etc': gotoJobs, 98 | 'דרושים*etc': gotoJobs, 99 | 100 | ':geek me up scotty': scrollToTop, 101 | 102 | 'חפש *etc': gotoSearch, 103 | 'חפס *etc': gotoSearch, 104 | }); 105 | annyang.start(); 106 | } 107 | 108 | }).call(this); 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *annyang!* 2 | ----------------------------------------------- 3 | 4 | A tiny javascript SpeechRecognition library that lets your users control your site with voice commands. 5 | 6 | annyang has no dependencies, weighs just 2 KB, and is free to use and modify under the MIT license. 7 | 8 | Demo & Tutorial 9 | --------------- 10 | [Play with some live speech recognition demos](https://www.talater.com/annyang) 11 | 12 | FAQ, Technical Documentation and API Reference 13 | ---------------------------------------------- 14 | - [annyang Frequently Asked Questions](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md) 15 | - [annyang API reference](https://github.com/TalAter/annyang/blob/master/docs/README.md) 16 | - [annyang tutorial](https://www.talater.com/annyang) 17 | 18 | Hello World 19 | ----------- 20 | It's as easy as adding [one javascript file](//cdnjs.cloudflare.com/ajax/libs/annyang/2.6.0/annyang.min.js) to your document, and defining the commands you want. 21 | ````html 22 | 23 | 37 | ```` 38 | **Check out some [live speech recognition demos and advanced samples](https://www.talater.com/annyang), then read the full [API Docs](https://github.com/TalAter/annyang/blob/master/docs/README.md).** 39 | 40 | Adding a GUI 41 | ------------ 42 | You can easily add a GUI for the user to interact with Speech Recognition using [Speech KITT](https://github.com/TalAter/SpeechKITT). 43 | 44 | Speech KITT makes it easy to add a graphical interface for the user to start or stop Speech Recognition and see its current status. KITT also provides clear visual hints to the user on how to interact with your site using their voice, providing instructions and sample commands. 45 | 46 | Speech KITT is fully customizable, and comes with many different themes (and instructions on how to create your own designs). 47 | 48 | [![Speech Recognition GUI with Speech KITT](https://raw.githubusercontent.com/TalAter/SpeechKITT/master/demo/speechkitt-demo.gif)](https://github.com/TalAter/SpeechKITT) 49 | 50 | ````html 51 | 52 | 53 | 70 | ```` 71 | 72 | For help with setting up a GUI with KITT, check out the [Speech KITT page](https://github.com/TalAter/SpeechKITT). 73 | 74 | Author 75 | ------ 76 | Tal Ater: [@TalAter](https://twitter.com/TalAter) 77 | 78 | License 79 | ------- 80 | Licensed under [MIT](https://github.com/TalAter/annyang/blob/master/LICENSE). 81 | -------------------------------------------------------------------------------- /demo/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('../images/palette.png') repeat-x 0 -2px; 3 | font: 14px/1.6 helvetica, sans-serif; 4 | margin: 0 0; 5 | padding: 0 0; 6 | color: #333; 7 | } 8 | 9 | .hidden { 10 | display: none; 11 | } 12 | 13 | a, a:visited { 14 | color: #90b8de; 15 | text-decoration: underline; 16 | } 17 | a:hover { 18 | text-decoration: none; 19 | } 20 | 21 | h2 { 22 | font-size: 2em; 23 | } 24 | 25 | pre code a, pre code a:visited { 26 | color: #00f; 27 | } 28 | 29 | #tpsreport { 30 | position: fixed; 31 | z-index: 1000; 32 | bottom: -500px; 33 | right: 10px; 34 | -webkit-transform: rotate(-15deg); 35 | -moz-transform: rotate(-15deg); 36 | -ms-transform: rotate(-15deg); 37 | -o-transform: rotate(-15deg); 38 | transform: rotate(-15deg); 39 | -webkit-box-shadow: 2px 1px 6px 1px #555; 40 | box-shadow: 2px 1px 6px 1px #555; 41 | } 42 | #section_header h2 { 43 | margin: 5px 0; 44 | font-size: 20px; 45 | line-height: 30px; 46 | font-weight: 400; 47 | } 48 | 49 | pre code { 50 | overflow: scroll; 51 | padding: 10px 20px!important; 52 | margin-left: 50px; 53 | } 54 | h1 em { 55 | font-weight: 800; 56 | margin-right: 9px; 57 | } 58 | h1 { 59 | font-weight: 400; 60 | font-size: 40px; 61 | line-height: 41px; 62 | margin: 25px 0; 63 | } 64 | p em { 65 | font-weight: 600; 66 | font-size: 23px; 67 | line-height: 45px; 68 | } 69 | p.voice_instructions { 70 | background: url('../images/mini_icon_say.png') no-repeat 0 6px; 71 | padding-left: 24px; 72 | } 73 | #hello { 74 | font-size: 2.5em; 75 | font-weight: 600; 76 | } 77 | #flickrLoader p { 78 | background: url('../images/flickr.png') no-repeat 0 7px; 79 | padding-left: 39px; 80 | } 81 | #flickrLoader { 82 | height: 40px; 83 | } 84 | section { 85 | padding: 3% 10%; 86 | font-size: 20px; 87 | line-height: 30px; 88 | font-weight: 400; 89 | } 90 | #section_header img { 91 | margin: 30px 50px 0; 92 | } 93 | #section_header { 94 | padding-top: 70px; 95 | text-align: center; 96 | } 97 | #section_hello { 98 | background-color: #1abc96; 99 | color: #fff; 100 | } 101 | #section_image_search { 102 | background-color: #407eb8; 103 | color: #fff; 104 | } 105 | #section_biz_use { 106 | background-color: #95a5a6; 107 | color: #fff; 108 | } 109 | #section_footer { 110 | background: #000 url('../images/footer_background.jpg') repeat-x top center; 111 | background-size: auto 100%; 112 | text-align: center; 113 | background-color: #34495d; 114 | color: #fff; 115 | padding: 2% 10%; 116 | } 117 | div.social{ 118 | margin-top: 40px; 119 | } 120 | div.copyright{ 121 | margin-top: 50px; 122 | font-size: 12px; 123 | line-height: 18px; 124 | font-weight: 100; 125 | } 126 | div#unsupported { 127 | text-align: center; 128 | position: fixed; 129 | bottom: 0; 130 | width: 100%; 131 | max-height: 60%; 132 | color: #b94a48; 133 | background-color: #f2dede; 134 | border: 1px solid #eed3d7; 135 | -webkit-box-shadow: -32px -50px 109px rgba(0, 0, 0, 0.92); 136 | -moz-box-shadow: -32px -50px 109px rgba(0, 0, 0, 0.92); 137 | box-shadow: -32px -50px 109px rgba(0, 0, 0, 0.92); 138 | font-size: 1.3em; 139 | line-height: 1.4em; 140 | padding-bottom: 10px; 141 | } 142 | div#unsupported a, div#unsupported a:visited { 143 | color: #b94a48; 144 | font-weight: bold; 145 | } 146 | div#unsupported h4 { 147 | font-size: 1.5em; 148 | margin: 15px 0 10px 0; 149 | } 150 | div#unsupported p { 151 | line-height: 20px; 152 | } 153 | -------------------------------------------------------------------------------- /sites/facebook.js: -------------------------------------------------------------------------------- 1 | //! annyang - facebook.com 2 | //! version : 0.1.0 3 | //! author : Tal Ater @TalAter 4 | //! license : MIT 5 | //! https://www.TalAter.com/annyang/ 6 | 7 | (function () { 8 | /*global annyang,$ */ 9 | "use strict"; 10 | var root = this; 11 | 12 | if (annyang) { 13 | var chatToggle = function() { 14 | root.Chat.toggleSidebar(); 15 | }; 16 | 17 | var chatOnline = function() { 18 | root.ChatVisibility.goOnline(); 19 | }; 20 | 21 | var chatOffline = function() { 22 | root.ChatVisibility.goOffline(); 23 | }; 24 | 25 | var searchMore = function(term) { 26 | root.goURI('/search/more/?q='+term); 27 | }; 28 | 29 | var gotoPage = function(term) { 30 | root.goURI('/'+term.replace(/[^\w]/ig, '')); 31 | }; 32 | 33 | var stream = function() { 34 | root.goURI('/'); 35 | }; 36 | 37 | var appcenter = function() { 38 | root.goURI('/appcenter'); 39 | }; 40 | 41 | var messages = function() { 42 | root.goURI('/messages'); 43 | }; 44 | 45 | var friendRequests = function() { 46 | root.goURI('/friends/requests'); 47 | }; 48 | 49 | var friends = function() { 50 | root.goURI('/friends'); 51 | }; 52 | 53 | var profile = function() { 54 | root.goURI('/me'); 55 | }; 56 | 57 | var events = function() { 58 | root.goURI('/events/list'); 59 | }; 60 | 61 | var calendar = function() { 62 | root.goURI('/events/calendar'); 63 | }; 64 | 65 | var photos = function(name) { 66 | root.goURI('/'+(name || 'me').replace(/[^\w]/ig, '')+'/photos'); 67 | }; 68 | 69 | var albums = function(name) { 70 | root.goURI('/'+(name || 'me').replace(/[^\w]/ig, '')+'/photos_albums'); 71 | }; 72 | 73 | var goBack = function() { 74 | history.back(); 75 | }; 76 | 77 | var pageScroll = function(direction) { 78 | if (direction === 'up') { 79 | window.scrollBy(0, -(window.innerHeight-$('pageHead').offsetHeight)); 80 | } else { 81 | window.scrollBy(0, window.innerHeight-$('pageHead').offsetHeight); 82 | } 83 | }; 84 | 85 | annyang.addCommands({ 86 | 'back': goBack, 87 | 'go back': goBack, 88 | 'go home': stream, 89 | 'home': stream, 90 | 'news': stream, 91 | 'profile': profile, 92 | 'me': profile, 93 | 'games': appcenter, 94 | 'apps': appcenter, 95 | 'messages': messages, 96 | 'inbox': messages, 97 | 'mail': messages, 98 | 'email': messages, 99 | 'events': events, 100 | 'calendar': calendar, 101 | 'photos': photos, 102 | 'photos *page': photos, 103 | 'albums': albums, 104 | 'albums *page': albums, 105 | 'friend requests': friendRequests, 106 | 'friends': friends, 107 | 'chat': chatToggle, 108 | 'chat online': chatOnline, 109 | 'go online': chatOnline, 110 | 'chat offline': chatOffline, 111 | 'go offline': chatOffline, 112 | 'scroll': pageScroll, 113 | 'scroll :direction': pageScroll, 114 | 'find *term': searchMore, 115 | 'search for *term': searchMore, 116 | 'search *term': searchMore, 117 | 'go to *page': gotoPage, 118 | 'go *page': gotoPage, 119 | }); 120 | annyang.start(); 121 | } 122 | 123 | }).call(this); 124 | -------------------------------------------------------------------------------- /dist/annyang.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e}; 2 | //! annyang 3 | //! version : 2.6.0 4 | //! author : Tal Ater @TalAter 5 | //! license : MIT 6 | //! https://www.TalAter.com/annyang/ 7 | !function(e,n){"function"==typeof define&&define.amd?define([],function(){return e.annyang=n(e)}):"object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports?module.exports=n(e):e.annyang=n(e)}("undefined"!=typeof window?window:void 0,function(e,n){var t,o=e.SpeechRecognition||e.webkitSpeechRecognition||e.mozSpeechRecognition||e.msSpeechRecognition||e.oSpeechRecognition;if(!o)return null;var r,i,a=[],c={start:[],error:[],end:[],soundstart:[],result:[],resultMatch:[],resultNoMatch:[],errorNetwork:[],errorPermissionBlocked:[],errorPermissionDenied:[]},s=0,u=0,f=!1,l="font-weight: bold; color: #00f;",d=!1,p=!1,g=function(e){return e=e.replace(/[\-{}\[\]+?.,\\\^$|#]/g,"\\$&").replace(/\s*\((.*?)\)\s*/g,"(?:$1)?").replace(/(\(\?)?:\w+/g,function(e,n){return n?e:"([^\\s]+)"}).replace(/\*\w+/g,"(.*?)").replace(/(\(\?:[^)]+\))\?/g,"\\s*$1?\\s*"),new RegExp("^"+e+"$","i")},m=function(e){for(var n=arguments.length,t=Array(n>1?n-1:0),o=1;o1&&arguments[1]!==n)||arguments[1];r&&r.abort&&r.abort(),(r=new o).maxAlternatives=5,r.continuous="http:"===e.location.protocol,r.lang="en-US",r.onstart=function(){p=!0,m(c.start)},r.onsoundstart=function(){m(c.soundstart)},r.onerror=function(e){switch(m(c.error,e),e.error){case"network":m(c.errorNetwork,e);break;case"not-allowed":case"service-not-allowed":i=!1,(new Date).getTime()-s<200?m(c.errorPermissionBlocked,e):m(c.errorPermissionDenied,e)}},r.onend=function(){if(p=!1,m(c.end),i){var e=(new Date).getTime()-s;(u+=1)%10==0&&f&&y("Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips."),e<1e3?setTimeout(function(){t.start({paused:d})},1e3-e):t.start({paused:d})}},r.onresult=function(e){if(d)return f&&y("Speech heard, but annyang is paused"),!1;for(var n=e.results[e.resultIndex],t=[],o=0;o0&&arguments[0]!==n)||arguments[0];f=!!e},setLanguage:function(e){b(),r.lang=e},addCommands:function(n){var t;b();for(var o in n)if(n.hasOwnProperty(o))if("function"==typeof(t=e[n[o]]||n[o]))v(g(o),t,o);else{if(!("object"===(void 0===t?"undefined":_typeof(t))&&t.regexp instanceof RegExp)){f&&y("Can not register command: %c"+o,l);continue}v(new RegExp(t.regexp.source,"i"),t.callback,o)}},removeCommands:function(e){e===n?a=[]:(e=Array.isArray(e)?e:[e],a=a.filter(function(n){for(var t=0;t1?n-1:0),o=1;o1&&arguments[1]!==n)||arguments[1];r&&r.abort&&r.abort(),(r=new o).maxAlternatives=5,r.continuous="http:"===e.location.protocol,r.lang="en-US",r.onstart=function(){d=!0,h(c.start)},r.onsoundstart=function(){h(c.soundstart)},r.onerror=function(e){switch(h(c.error,e),e.error){case"network":h(c.errorNetwork,e);break;case"not-allowed":case"service-not-allowed":a=!1,(new Date).getTime()-u<200?h(c.errorPermissionBlocked,e):h(c.errorPermissionDenied,e)}},r.onend=function(){if(d=!1,h(c.end),a){var e=(new Date).getTime()-u;(s+=1)%10==0&&f&&m("Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips."),e<1e3?setTimeout(function(){t.start({paused:g})},1e3-e):t.start({paused:g})}},r.onresult=function(e){if(g)return f&&m("Speech heard, but annyang is paused"),!1;for(var n=e.results[e.resultIndex],t=[],o=0;o0&&arguments[0]!==n)||arguments[0];f=!!e},setLanguage:function(e){b(),r.lang=e},addCommands:function(n){var t;b();for(var o in n)if(n.hasOwnProperty(o))if("function"==typeof(t=e[n[o]]||n[o]))v(p(o),t,o);else{if(!("object"===(void 0===t?"undefined":_typeof(t))&&t.regexp instanceof RegExp)){f&&m("Can not register command: %c"+o,l);continue}v(new RegExp(t.regexp.source,"i"),t.callback,o)}},removeCommands:function(e){e===n?i=[]:(e=Array.isArray(e)?e:[e],i=i.filter(function(n){for(var t=0;t1?e-1:0),o=1;o1&&arguments[1]!==e)||arguments[1];r&&r.abort&&r.abort(),(r=new o).maxAlternatives=5,r.continuous="http:"===n.location.protocol,r.lang="en-US",r.onstart=function(){d=!0,h(c.start)},r.onsoundstart=function(){h(c.soundstart)},r.onerror=function(n){switch(h(c.error,n),n.error){case"network":h(c.errorNetwork,n);break;case"not-allowed":case"service-not-allowed":i=!1,(new Date).getTime()-s<200?h(c.errorPermissionBlocked,n):h(c.errorPermissionDenied,n)}},r.onend=function(){if(d=!1,h(c.end),i){var n=(new Date).getTime()-s;(u+=1)%10==0&&f&&y("Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips."),n<1e3?setTimeout(function(){t.start({paused:g})},1e3-n):t.start({paused:g})}},r.onresult=function(n){if(g)return f&&y("Speech heard, but annyang is paused"),!1;for(var e=n.results[n.resultIndex],t=[],o=0;o0&&arguments[0]!==e)||arguments[0];f=!!n},setLanguage:function(n){b(),r.lang=n},addCommands:function(e){var t;b();for(var o in e)if(e.hasOwnProperty(o))if("function"==typeof(t=n[e[o]]||e[o]))w(p(o),t,o);else{if(!("object"===(void 0===t?"undefined":_typeof(t))&&t.regexp instanceof RegExp)){f&&y("Can not register command: %c"+o,l);continue}w(new RegExp(t.regexp.source,"i"),t.callback,o)}},removeCommands:function(n){n===e?a=[]:(n=Array.isArray(n)?n:[n],a=a.filter(function(e){for(var t=0;t= this.length) { 125 | return null; 126 | } else { 127 | return this[index]; 128 | } 129 | }; 130 | for (commandIterator = 0; commandIterator<_maxAlternatives; commandIterator++) { 131 | var etc = ''; 132 | for (etcIterator = 0; etcIterator/gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.javascript=function(a){return{k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const",literal:"true false null undefined NaN Infinity"},c:[a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,{cN:"regexp",b:"/",e:"/[gim]*",i:"\\n",c:[{b:"\\\\/"}]},{b:"<",e:">;",sL:"xml"}],r:0},{cN:"function",bWK:true,e:"{",k:"function",c:[{cN:"title",b:"[A-Za-z$_][0-9A-Za-z$_]*"},{cN:"params",b:"\\(",e:"\\)",c:[a.CLCM,a.CBLCLM],i:"[\"'\\(]"}],i:"\\[|%"}]}}(hljs);hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs); -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | - [What languages are supported?](#what-languages-are-supported) 4 | - [Why does the browser repeatedly ask for permission to use the microphone?](#why-does-the-browser-repeatedly-ask-for-permission-to-use-the-microphone) 5 | - [What can I do to make speech recognition results return faster?](#what-can-i-do-to-make-speech-recognition-results-return-faster) 6 | - [How can I contribute to annyang's development?](#how-can-i-contribute-to-annyangs-development) 7 | - [Why does Speech Recognition repeatedly starts and stops?](#why-does-speech-recognition-repeatedly-starts-and-stops) 8 | - [Can annyang work offline?](#can-annyang-work-offline) 9 | - [Can annyang be used to capture the full text spoken by the user?](#can-annyang-be-used-to-capture-the-full-text-spoken-by-the-user) 10 | - [Can I detect when the user starts and stops speaking?](#can-i-detect-when-the-user-starts-and-stops-speaking) 11 | - [Can annyang be used in Chromium or Electron?](#can-annyang-be-used-in-chromium-or-electron) 12 | - [Can annyang be used in Cordova?](#can-annyang-be-used-in-cordova) 13 | 14 | ## What languages are supported? 15 | 16 | Language support is up to each browser. While there isn't an official list of supported languages in Chrome, here is a list based on [anecdotal evidence](http://stackoverflow.com/a/14302134/338039). 17 | 18 | * Afrikaans `af` 19 | * Basque `eu` 20 | * Bulgarian `bg` 21 | * Catalan `ca` 22 | * Arabic (Egypt) `ar-EG` 23 | * Arabic (Jordan) `ar-JO` 24 | * Arabic (Kuwait) `ar-KW` 25 | * Arabic (Lebanon) `ar-LB` 26 | * Arabic (Qatar) `ar-QA` 27 | * Arabic (UAE) `ar-AE` 28 | * Arabic (Morocco) `ar-MA` 29 | * Arabic (Iraq) `ar-IQ` 30 | * Arabic (Algeria) `ar-DZ` 31 | * Arabic (Bahrain) `ar-BH` 32 | * Arabic (Lybia) `ar-LY` 33 | * Arabic (Oman) `ar-OM` 34 | * Arabic (Saudi Arabia) `ar-SA` 35 | * Arabic (Tunisia) `ar-TN` 36 | * Arabic (Yemen) `ar-YE` 37 | * Czech `cs` 38 | * Dutch `nl-NL` 39 | * English (Australia) `en-AU` 40 | * English (Canada) `en-CA` 41 | * English (India) `en-IN` 42 | * English (New Zealand) `en-NZ` 43 | * English (South Africa) `en-ZA` 44 | * English(UK) `en-GB` 45 | * English(US) `en-US` 46 | * Finnish `fi` 47 | * French `fr-FR` 48 | * Galician `gl` 49 | * German `de-DE` 50 | * Hebrew `he` 51 | * Hungarian `hu` 52 | * Icelandic `is` 53 | * Italian `it-IT` 54 | * Indonesian `id` 55 | * Japanese `ja` 56 | * Korean `ko` 57 | * Latin `la` 58 | * Mandarin Chinese `zh-CN` 59 | * Traditional Taiwan `zh-TW` 60 | * Simplified China zh-CN `?` 61 | * Simplified Hong Kong `zh-HK` 62 | * Yue Chinese (Traditional Hong Kong) `zh-yue` 63 | * Malaysian `ms-MY` 64 | * Norwegian `no-NO` 65 | * Polish `pl` 66 | * Pig Latin `xx-piglatin` 67 | * Portuguese `pt-PT` 68 | * Portuguese (Brasil) `pt-BR` 69 | * Romanian `ro-RO` 70 | * Russian `ru` 71 | * Serbian `sr-SP` 72 | * Slovak `sk` 73 | * Spanish (Argentina) `es-AR` 74 | * Spanish (Bolivia) `es-BO` 75 | * Spanish (Chile) `es-CL` 76 | * Spanish (Colombia) `es-CO` 77 | * Spanish (Costa Rica) `es-CR` 78 | * Spanish (Dominican Republic) `es-DO` 79 | * Spanish (Ecuador) `es-EC` 80 | * Spanish (El Salvador) `es-SV` 81 | * Spanish (Guatemala) `es-GT` 82 | * Spanish (Honduras) `es-HN` 83 | * Spanish (Mexico) `es-MX` 84 | * Spanish (Nicaragua) `es-NI` 85 | * Spanish (Panama) `es-PA` 86 | * Spanish (Paraguay) `es-PY` 87 | * Spanish (Peru) `es-PE` 88 | * Spanish (Puerto Rico) `es-PR` 89 | * Spanish (Spain) `es-ES` 90 | * Spanish (US) `es-US` 91 | * Spanish (Uruguay) `es-UY` 92 | * Spanish (Venezuela) `es-VE` 93 | * Swedish `sv-SE` 94 | * Turkish `tr` 95 | * Zulu `zu` 96 | 97 | ## Why does the browser repeatedly ask for permission to use the microphone? 98 | 99 | ![](http://i.imgur.com/Z3zooUC.png) 100 | 101 | Chrome's speech recognition behaves differently based on the protocol used: 102 | 103 | - `https://` Asks for permission once and remembers the choice. 104 | 105 | - `http://` Asks for permission repeatedly **on every page load**. Results are also returned significantly slower in HTTP. 106 | 107 | For a great user experience, don't compromise on anything less than HTTPS (available free with CloudFlare and Let's Encrypt). 108 | 109 | ## What can I do to make speech recognition results return faster? 110 | 111 | First, remember that because the actual speech-to-text processing is done in the cloud, a faster connection can mean faster results. 112 | 113 | Second, when the speech recognition is in continuous mode, results are returned slower (the browser waits after you finish talking to see if there's anything else you'd like to add). 114 | 115 | Turning continuous mode off tends to make the browser return recognized results much faster. 116 | 117 | To start annyang in non-continuous mode, you can pass `continuous: false` in the options object that `annyang.start()` accepts. You will most likely want to also turn on `autoRestart` if you do that. You can read more about both options in the [annyang API Docs](https://github.com/TalAter/annyang/blob/master/docs/README.md#startoptions) 118 | 119 | For example: 120 | 121 | ````javascript 122 | annyang.start({ autoRestart: true, continuous: false }); 123 | ```` 124 | 125 | Note that these settings are already the default if you are using HTTPS. If you are using HTTP, continuous mode will be turned on by default (resulting in slower recognition) to prevent [repeated security notices](#why-does-the-browser-repeatedly-ask-for-permission-to-use-the-microphone). 126 | 127 | ## How can I contribute to annyang's development? 128 | 129 | There are three main ways for you to help. Check out the [CONTRIBUTING](https://github.com/TalAter/annyang/blob/master/CONTRIBUTING.md) guide for more details. 130 | 131 | ## Why does Speech Recognition repeatedly starts and stops? 132 | 133 | The most common reason for this is because you have opened more than one tab or window that uses Speech Recognition in your browser at the same time (e.g. if you open annyang's homepage in one tab, and the Speech Recognition app you are developing in another). 134 | 135 | When a browser detects that one tab has started Speech Recognition, it aborts all other Speech Recognition processes in other tabs. annyang detects when it is aborted by an external process and restarts itself. If you have two windows aborting each other, and restarting themselves you may experience Speech Recognition starting and stopping over and over again. 136 | 137 | Another possible reason for this might be that you are offline. 138 | 139 | ## Can annyang work offline? 140 | 141 | No. annyang relies on the browser's own speech recognition engine. In Chrome, this engine performs the recognition in the cloud. 142 | 143 | ## Can annyang be used to capture the full text spoken by the user? 144 | 145 | Yes. You can listen to the `result` event which is triggered whenever speech is recognized. This event will fire with a list of possible phrases the user may have said, regardless of whether any of them matched an annyang command or not. You can even do this without registering any commands: 146 | 147 | ````javascript 148 | annyang.addCallback('result', function(phrases) { 149 | console.log("I think the user said: ", phrases[0]); 150 | console.log("But then again, it could be any of the following: ", phrases); 151 | }); 152 | ```` 153 | 154 | Alternatively, you may choose to only capture what the user said when it matches an annyang command (`resultMatch`), or when it does not match a command (`resultNoMatch`). 155 | 156 | ````javascript 157 | annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) { 158 | console.log(userSaid); // sample output: 'hello' 159 | console.log(commandText); // sample output: 'hello (there)' 160 | console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty'] 161 | }); 162 | 163 | annyang.addCallback('resultNoMatch', function(phrases) { 164 | console.log("I think the user said: ", phrases[0]); 165 | console.log("But then again, it could be any of the following: ", phrases); 166 | }); 167 | ```` 168 | 169 | ## Can I detect when the user starts and stops speaking? 170 | 171 | Yes. Sometimes. 172 | 173 | You can detect when a sound is first detected by the microphone with the `soundstart` event. Unfortunately, due to a [bug in Chrome](https://bugs.chromium.org/p/chromium/issues/detail?id=572697&thanks=572697&ts=1451323087), this event will only fire once in every speech recognition session. If you are in non-continuous mode and annyang is restarting after every sentence recognized (the default in HTTPS), this will not be a problem. Because speech recognition will abort and restart, soundstart will fire again correctly. 174 | 175 | The following code will detect when a user starts and stops speaking. 176 | 177 | ````javascript 178 | annyang.addCallback('soundstart', function() { 179 | console.log('sound detected'); 180 | }); 181 | 182 | annyang.addCallback('result', function() { 183 | console.log('sound stopped'); 184 | }); 185 | 186 | ```` 187 | 188 | *Note*: The `soundstart` event is only available in annyang v2.6.0 and up. 189 | 190 | ## Can annyang be used in Chromium or Electron? 191 | 192 | Yes, however you must create your own Chromium keys and are limited to 50 requests/day. To do this you'll need to provide your own keys at runtime by following the instructions for [Acquiring Keys](https://www.chromium.org/developers/how-tos/api-keys) in the Chromium developer docs. 193 | 194 | ## Can annyang be used in Cordova? 195 | 196 | Yes. In order to use `webKitSpeechRecognition` you will need to use [Crosswalk](https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview) and the [Cordova Media Plugin](https://github.com/apache/cordova-plugin-media). These can be added to an existing cordova project with the following commands: 197 | 198 | ``` 199 | cordova plugin add cordova-plugin-crosswalk-webview 200 | cordova plugin add cordova-plugin-media 201 | ``` 202 | **Known issues:** 203 | - This has only been verified to work on Android. 204 | - This will not work on a device without a SpeechRecognizer ([like the Kindle Fire](https://forums.developer.amazon.com/questions/13597/does-speech-recognition-work-in-kindle-fire-hd-tab.html)) unless you sideload the [Google Now Launcher](https://play.google.com/store/apps/details?id=com.google.android.launcher). 205 | - Your app will ding with the native speech recognition sound, you have no control over this. See [#194](https://github.com/TalAter/annyang/issues/194). 206 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | annyang! Easily add speech recognition to your site 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 100 | 101 | 102 | 103 |
104 |

annyang! SpeechRecognition that just works

105 |

annyang is a tiny javascript library that lets your visitors control your site with voice commands.

106 |

annyang supports multiple languages, has no dependencies, weighs just 2kb and is free to use.

107 | 108 | 109 | 110 |
111 |
112 |

Go ahead, try it…

113 |

Say "Hello!"

114 | 115 |
116 | 124 |
125 |

That's cool, but in the real world it's not all kittens and hello world.

126 |

No problem, say "Show TPS report"

127 | 128 |
129 |
130 |

How did you do that?

131 |

Simple. Here is all the code needed to achieve that:

132 |
<script src="//cdnjs.cloudflare.com/ajax/libs/annyang/2.6.0/annyang.min.js"></script>
133 | <script>
134 | if (annyang) {
135 |   // Let's define our first command. First the text we expect, and then the function it should call
136 |   var commands = {
137 |     'show tps report': function() {
138 |       $('#tpsreport').animate({bottom: '-100px'});
139 |     }
140 |   };
141 | 
142 |   // Add our commands to annyang
143 |   annyang.addCommands(commands);
144 | 
145 |   // Start listening. You can call this here, or attach this call to an event, button, etc.
146 |   annyang.start();
147 | }
148 | </script>
149 |
150 |
151 |

What about more complicated commands?

152 |

annyang understands commands with named variables, splats, and optional words.

153 |

Use named variables for one word arguments in your command.

154 |

Use splats to capture multi-word text at the end of your command (greedy).

155 |

Use optional words or phrases to define a part of the command as optional.

156 |
<script>
157 | var commands = {
158 |   // annyang will capture anything after a splat (*) and pass it to the function.
159 |   // e.g. saying "Show me Batman and Robin" is the same as calling showFlickr('Batman and Robin');
160 |   'show me *tag': showFlickr,
161 | 
162 |   // A named variable is a one word variable, that can fit anywhere in your command.
163 |   // e.g. saying "calculate October stats" will call calculateStats('October');
164 |   'calculate :month stats': calculateStats,
165 | 
166 |   // By defining a part of the following command as optional, annyang will respond to both:
167 |   // "say hello to my little friend" as well as "say hello friend"
168 |   'say hello (to my little) friend': greeting
169 | };
170 | 
171 | var showFlickr = function(tag) {
172 |   var url = 'http://api.flickr.com/services/rest/?tags='+tag;
173 |   $.getJSON(url);
174 | }
175 | 
176 | var calculateStats = function(month) {
177 |   $('#stats').text('Statistics for '+month);
178 | }
179 | 
180 | var greeting = function() {
181 |   $('#greeting').text('Hello!');
182 | }
183 | 
184 | </script>
185 |
186 |
187 |

What about browser support?

188 |

annyang plays nicely with all browsers, progressively enhancing browsers that support SpeechRecognition, while leaving users with older browsers unaffected.

189 |
190 | 205 | 210 | 220 | 221 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # Quick Tutorial, Intro and Demos 6 | 7 | The quickest way to get started is to visit the [annyang homepage](https://www.talater.com/annyang/). 8 | 9 | For a more in-depth look at annyang, read on. 10 | 11 | # API Reference 12 | 13 | ## init(commands, [resetCommands=true]) 14 | 15 | Initialize annyang with a list of commands to recognize. 16 | 17 | #### Examples: 18 | ````javascript 19 | var commands = {'hello :name': helloFunction}; 20 | var commands2 = {'hi': helloFunction}; 21 | 22 | // initialize annyang, overwriting any previously added commands 23 | annyang.init(commands, true); 24 | // adds an additional command without removing the previous commands 25 | annyang.init(commands2, false); 26 | ```` 27 | As of v1.1.0 it is no longer required to call init(). Just start() listening whenever you want, and addCommands() whenever, and as often as you like. 28 | 29 | **Deprecated** 30 | 31 | See: [Commands Object](#commands-object) 32 | 33 | ### Params: 34 | 35 | * **Object** *commands* - Commands that annyang should listen to 36 | * **boolean** *[resetCommands=true]* - Remove all commands before initializing? 37 | 38 | ## start([options]) 39 | 40 | Start listening. 41 | It's a good idea to call this after adding some commands first, but not mandatory. 42 | 43 | Receives an optional options object which supports the following options: 44 | 45 | - `autoRestart` (boolean, default: true) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts? 46 | - `continuous` (boolean) Allow forcing continuous mode on or off. Annyang is pretty smart about this, so only set this if you know what you're doing. 47 | - `paused` (boolean, default: true) Start annyang in paused mode. 48 | 49 | #### Examples: 50 | ````javascript 51 | // Start listening, don't restart automatically 52 | annyang.start({ autoRestart: false }); 53 | // Start listening, don't restart automatically, stop recognition after first phrase recognized 54 | annyang.start({ autoRestart: false, continuous: false }); 55 | ```` 56 | 57 | ### Params: 58 | 59 | * **Object** *[options]* - Optional options. 60 | 61 | ## abort() 62 | 63 | Stop listening, and turn off mic. 64 | 65 | Alternatively, to only temporarily pause annyang responding to commands without stopping the SpeechRecognition engine or closing the mic, use pause() instead. 66 | 67 | See: [pause()](#pause) 68 | 69 | ## pause() 70 | 71 | Pause listening. annyang will stop responding to commands (until the resume or start methods are called), without turning off the browser's SpeechRecognition engine or the mic. 72 | 73 | Alternatively, to stop the SpeechRecognition engine and close the mic, use abort() instead. 74 | 75 | See: [abort()](#abort) 76 | 77 | ## resume() 78 | 79 | Resumes listening and restores command callback execution when a result matches. 80 | If SpeechRecognition was aborted (stopped), start it. 81 | 82 | ## debug([newState=true]) 83 | 84 | Turn on output of debug messages to the console. Ugly, but super-handy! 85 | 86 | ### Params: 87 | 88 | * **boolean** *[newState=true]* - Turn on/off debug messages 89 | 90 | ## setLanguage(language) 91 | 92 | Set the language the user will speak in. If this method is not called, defaults to 'en-US'. 93 | 94 | See: [Languages](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md#what-languages-are-supported) 95 | 96 | ### Params: 97 | 98 | * **String** *language* - The language (locale) 99 | 100 | ## addCommands(commands) 101 | 102 | Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands. 103 | 104 | #### Examples: 105 | ````javascript 106 | var commands = {'hello :name': helloFunction, 'howdy': helloFunction}; 107 | var commands2 = {'hi': helloFunction}; 108 | 109 | annyang.addCommands(commands); 110 | annyang.addCommands(commands2); 111 | // annyang will now listen to all three commands 112 | ```` 113 | 114 | See: [Commands Object](#commands-object) 115 | 116 | ### Params: 117 | 118 | * **Object** *commands* - Commands that annyang should listen to 119 | 120 | ## removeCommands([commandsToRemove]) 121 | 122 | Remove existing commands. Called with a single phrase, array of phrases, or methodically. Pass no params to remove all commands. 123 | 124 | #### Examples: 125 | ````javascript 126 | var commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction}; 127 | 128 | // Remove all existing commands 129 | annyang.removeCommands(); 130 | 131 | // Add some commands 132 | annyang.addCommands(commands); 133 | 134 | // Don't respond to hello 135 | annyang.removeCommands('hello'); 136 | 137 | // Don't respond to howdy or hi 138 | annyang.removeCommands(['howdy', 'hi']); 139 | ```` 140 | 141 | ### Params: 142 | 143 | * **String|Array|Undefined** *[commandsToRemove]* - Commands to remove 144 | 145 | ## addCallback(type, callback, [context]) 146 | 147 | Add a callback function to be called in case one of the following events happens: 148 | 149 | * `start` - Fired as soon as the browser's Speech Recognition engine starts listening 150 | * `soundstart` - Fired as soon as any sound (possibly speech) has been detected. 151 | This will fire once per Speech Recognition starting. See https://is.gd/annyang_sound_start 152 | * `error` - Fired when the browser's Speech Recogntion engine returns an error, this generic error callback will be followed by more accurate error callbacks (both will fire if both are defined) 153 | Callback function will be called with the error event as the first argument 154 | * `errorNetwork` - Fired when Speech Recognition fails because of a network error 155 | Callback function will be called with the error event as the first argument 156 | * `errorPermissionBlocked` - Fired when the browser blocks the permission request to use Speech Recognition. 157 | Callback function will be called with the error event as the first argument 158 | * `errorPermissionDenied` - Fired when the user blocks the permission request to use Speech Recognition. 159 | Callback function will be called with the error event as the first argument 160 | * `end` - Fired when the browser's Speech Recognition engine stops 161 | * `result` - Fired as soon as some speech was identified. This generic callback will be followed by either the `resultMatch` or `resultNoMatch` callbacks. 162 | Callback functions for to this event will be called with an array of possible phrases the user said as the first argument 163 | * `resultMatch` - Fired when annyang was able to match between what the user said and a registered command 164 | Callback functions for this event will be called with three arguments in the following order: 165 | * The phrase the user said that matched a command 166 | * The command that was matched 167 | * An array of possible alternative phrases the user might have said 168 | * `resultNoMatch` - Fired when what the user said didn't match any of the registered commands. 169 | Callback functions for this event will be called with an array of possible phrases the user might've said as the first argument 170 | 171 | #### Examples: 172 | ````javascript 173 | annyang.addCallback('error', function() { 174 | $('.myErrorText').text('There was an error!'); 175 | }); 176 | 177 | annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) { 178 | console.log(userSaid); // sample output: 'hello' 179 | console.log(commandText); // sample output: 'hello (there)' 180 | console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty'] 181 | }); 182 | 183 | // pass local context to a global function called notConnected 184 | annyang.addCallback('errorNetwork', notConnected, this); 185 | ```` 186 | 187 | ### Params: 188 | 189 | * **String** *type* - Name of event that will trigger this callback 190 | * **Function** *callback* - The function to call when event is triggered 191 | * **Object** *[context]* - Optional context for the callback function 192 | 193 | ## removeCallback(type, callback) 194 | 195 | Remove callbacks from events. 196 | 197 | - Pass an event name and a callback command to remove that callback command from that event type. 198 | - Pass just an event name to remove all callback commands from that event type. 199 | - Pass undefined as event name and a callback command to remove that callback command from all event types. 200 | - Pass no params to remove all callback commands from all event types. 201 | 202 | #### Examples: 203 | ````javascript 204 | annyang.addCallback('start', myFunction1); 205 | annyang.addCallback('start', myFunction2); 206 | annyang.addCallback('end', myFunction1); 207 | annyang.addCallback('end', myFunction2); 208 | 209 | // Remove all callbacks from all events: 210 | annyang.removeCallback(); 211 | 212 | // Remove all callbacks attached to end event: 213 | annyang.removeCallback('end'); 214 | 215 | // Remove myFunction2 from being called on start: 216 | annyang.removeCallback('start', myFunction2); 217 | 218 | // Remove myFunction1 from being called on all events: 219 | annyang.removeCallback(undefined, myFunction1); 220 | ```` 221 | 222 | ### Params: 223 | 224 | * *type* Name of event type to remove callback from 225 | * *callback* The callback function to remove 226 | 227 | ### Return: 228 | 229 | * undefined 230 | 231 | ## isListening() 232 | 233 | Returns true if speech recognition is currently on. 234 | Returns false if speech recognition is off or annyang is paused. 235 | 236 | ### Return: 237 | 238 | * boolean true = SpeechRecognition is on and annyang is listening 239 | 240 | ## getSpeechRecognizer() 241 | 242 | Returns the instance of the browser's SpeechRecognition object used by annyang. 243 | Useful in case you want direct access to the browser's Speech Recognition engine. 244 | 245 | ### Return: 246 | 247 | * SpeechRecognition The browser's Speech Recognizer currently used by annyang 248 | 249 | ## trigger(string|array) 250 | 251 | Simulate speech being recognized. This will trigger the same events and behavior as when the Speech Recognition 252 | detects speech. 253 | 254 | Can accept either a string containing a single sentence, or an array containing multiple sentences to be checked 255 | in order until one of them matches a command (similar to the way Speech Recognition Alternatives are parsed) 256 | 257 | #### Examples: 258 | ````javascript 259 | annyang.trigger('Time for some thrilling heroics'); 260 | annyang.trigger( 261 | ['Time for some thrilling heroics', 'Time for some thrilling aerobics'] 262 | ); 263 | ```` 264 | 265 | ### Params: 266 | 267 | * *string|array* sentences A sentence as a string or an array of strings of possible sentences 268 | 269 | ### Return: 270 | 271 | * undefined 272 | 273 | # Good to Know 274 | 275 | ## Commands Object 276 | 277 | Both the [init()]() and addCommands() methods receive a `commands` object. 278 | 279 | annyang understands commands with `named variables`, `splats`, and `optional words`. 280 | 281 | * Use `named variables` for one word arguments in your command. 282 | * Use `splats` to capture multi-word text at the end of your command (greedy). 283 | * Use `optional words` or phrases to define a part of the command as optional. 284 | 285 | #### Examples: 286 | ````html 287 | 315 | ```` 316 | 317 | ### Using Regular Expressions in commands 318 | For advanced commands, you can pass a regular expression object, instead of 319 | a simple string command. 320 | 321 | This is done by passing an object containing two properties: `regexp`, and 322 | `callback` instead of the function. 323 | 324 | #### Examples: 325 | ````javascript 326 | var calculateFunction = function(month) { console.log(month); } 327 | var commands = { 328 | // This example will accept any word as the "month" 329 | 'calculate :month stats': calculateFunction, 330 | // This example will only accept months which are at the start of a quarter 331 | 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction} 332 | } 333 | ```` 334 | 335 | 336 | 337 | -------------------------------------------------------------------------------- /require.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var e;for(e=0;ethis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{d=i.execCb(c,m,b,d)}catch(e){a=e}else d=i.execCb(c,m,b,d);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!== 19 | this.exports?d=b.exports:void 0===d&&this.usingExports&&(d=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else d=m;this.exports=d;if(this.map.isDefine&&!this.ignore&&(r[c]=d,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= 20 | !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,e=n(a.prefix);this.depMaps.push(e);s(e,"defined",u(this,function(d){var m,e;e=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(d.normalize&&(e=d.normalize(e,function(a){return c(a,g,!0)})||""),d=n(a.prefix+"!"+e,this.map.parentMap),s(d,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})), 21 | e=l(p,d.id)){this.depMaps.push(d);if(this.events.error)e.on("error",u(this,function(a){this.emit("error",a)}));e.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(d,c){var e=a.name,g=n(e),B=O;c&&(d=c);B&&(O=!1);q(g);t(k.config,b)&&(k.config[e]=k.config[b]);try{j.exec(d)}catch(ca){return v(A("fromtexteval", 22 | "fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(e);h([e],m)}),d.load(a.name,h,m,k)}));i.enable(e,this);this.pluginMaps[e.id]=e},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,d;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&s(a,"error",u(this,this.errback))}c=a.id;d=p[c];!t(N,c)&&(d&&!d.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n, 24 | nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,d={paths:!0,config:!0,map:!0};F(a,function(a,b){d[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name, 25 | location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function h(d,c,e){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof d){if(H(c))return v(A("requireargs", 26 | "Invalid require call"),e);if(a&&t(N,d))return N[d](p[a.id]);if(j.get)return j.get(i,d,a,h);g=n(d,a,!1,!0);g=g.id;return!t(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(d,c,e,{enabled:!0});C()});return h}f=f||{};Q(h,{isBrowser:z,toUrl:function(b){var f,e=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==e&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error", 34 | b.onScriptError,!1)),h.src=e,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(e),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+e,l,[c]))}};z&&!s.skipDataMain&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,s.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",s.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),s.deps=s.deps?s.deps.concat(q): 35 | [q],!0});define=function(b,c,e){var h,j;"string"!==typeof b&&(e=c,c=b,b=null);I(c)||(e=c,c=null);!c&&H(e)&&(c=[],e.length&&(e.toString().replace(la,"").replace(ma,function(b,e){c.push(e)}),c=(1===e.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j? 36 | j.defQueue:R).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(s)}})(this); 37 | -------------------------------------------------------------------------------- /demo/vendor/html/github-btn.html: -------------------------------------------------------------------------------- 1 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 244 | -------------------------------------------------------------------------------- /src/annyang.js: -------------------------------------------------------------------------------- 1 | //! annyang 2 | //! version : 2.6.0 3 | //! author : Tal Ater @TalAter 4 | //! license : MIT 5 | //! https://www.TalAter.com/annyang/ 6 | (function (root, factory) { 7 | 'use strict'; 8 | if (typeof define === 'function' && define.amd) { // AMD + global 9 | define([], function () { 10 | return (root.annyang = factory(root)); 11 | }); 12 | } else if (typeof module === 'object' && module.exports) { // CommonJS 13 | module.exports = factory(root); 14 | } else { // Browser globals 15 | root.annyang = factory(root); 16 | } 17 | }(typeof window !== 'undefined' ? window : this, function (root, undefined) { 18 | 'use strict'; 19 | 20 | /** 21 | * # Quick Tutorial, Intro and Demos 22 | * 23 | * The quickest way to get started is to visit the [annyang homepage](https://www.talater.com/annyang/). 24 | * 25 | * For a more in-depth look at annyang, read on. 26 | * 27 | * # API Reference 28 | */ 29 | 30 | var annyang; 31 | 32 | // Get the SpeechRecognition object, while handling browser prefixes 33 | var SpeechRecognition = root.SpeechRecognition || 34 | root.webkitSpeechRecognition || 35 | root.mozSpeechRecognition || 36 | root.msSpeechRecognition || 37 | root.oSpeechRecognition; 38 | 39 | // Check browser support 40 | // This is done as early as possible, to make it as fast as possible for unsupported browsers 41 | if (!SpeechRecognition) { 42 | return null; 43 | } 44 | 45 | var commandsList = []; 46 | var recognition; 47 | var callbacks = { start: [], error: [], end: [], soundstart: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] }; 48 | var autoRestart; 49 | var lastStartedAt = 0; 50 | var autoRestartCount = 0; 51 | var debugState = false; 52 | var debugStyle = 'font-weight: bold; color: #00f;'; 53 | var pauseListening = false; 54 | var isListening = false; 55 | 56 | // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license. 57 | var optionalParam = /\s*\((.*?)\)\s*/g; 58 | var optionalRegex = /(\(\?:[^)]+\))\?/g; 59 | var namedParam = /(\(\?)?:\w+/g; 60 | var splatParam = /\*\w+/g; 61 | var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#]/g; 62 | var commandToRegExp = function(command) { 63 | command = command.replace(escapeRegExp, '\\$&') 64 | .replace(optionalParam, '(?:$1)?') 65 | .replace(namedParam, function(match, optional) { 66 | return optional ? match : '([^\\s]+)'; 67 | }) 68 | .replace(splatParam, '(.*?)') 69 | .replace(optionalRegex, '\\s*$1?\\s*'); 70 | return new RegExp('^' + command + '$', 'i'); 71 | }; 72 | 73 | // This method receives an array of callbacks to iterate over, and invokes each of them 74 | var invokeCallbacks = function(callbacks, ...args) { 75 | callbacks.forEach(function(callback) { 76 | callback.callback.apply(callback.context, args); 77 | }); 78 | }; 79 | 80 | var isInitialized = function() { 81 | return recognition !== undefined; 82 | }; 83 | 84 | // method for logging in developer console when debug mode is on 85 | var logMessage = function(text, extraParameters) { 86 | if (text.indexOf('%c') === -1 && !extraParameters) { 87 | console.log(text); 88 | } else { 89 | console.log(text, extraParameters || debugStyle); 90 | } 91 | }; 92 | 93 | var initIfNeeded = function() { 94 | if (!isInitialized()) { 95 | annyang.init({}, false); 96 | } 97 | }; 98 | 99 | var registerCommand = function(command, callback, originalPhrase) { 100 | commandsList.push({ command, callback, originalPhrase }); 101 | if (debugState) { 102 | logMessage('Command successfully loaded: %c'+originalPhrase, debugStyle); 103 | } 104 | }; 105 | 106 | var parseResults = function(results) { 107 | invokeCallbacks(callbacks.result, results); 108 | var commandText; 109 | // go over each of the 5 results and alternative results received (we've set maxAlternatives to 5 above) 110 | for (let i = 0; i { 436 | for (let i = 0; i 633 | * var commands = { 634 | * // annyang will capture anything after a splat (*) and pass it to the function. 635 | * // e.g. saying "Show me Batman and Robin" will call showFlickr('Batman and Robin'); 636 | * 'show me *tag': showFlickr, 637 | * 638 | * // A named variable is a one word variable, that can fit anywhere in your command. 639 | * // e.g. saying "calculate October stats" will call calculateStats('October'); 640 | * 'calculate :month stats': calculateStats, 641 | * 642 | * // By defining a part of the following command as optional, annyang will respond 643 | * // to both: "say hello to my little friend" as well as "say hello friend" 644 | * 'say hello (to my little) friend': greeting 645 | * }; 646 | * 647 | * var showFlickr = function(tag) { 648 | * var url = 'http://api.flickr.com/services/rest/?tags='+tag; 649 | * $.getJSON(url); 650 | * } 651 | * 652 | * var calculateStats = function(month) { 653 | * $('#stats').text('Statistics for '+month); 654 | * } 655 | * 656 | * var greeting = function() { 657 | * $('#greeting').text('Hello!'); 658 | * } 659 | * 660 | * ```` 661 | * 662 | * ### Using Regular Expressions in commands 663 | * For advanced commands, you can pass a regular expression object, instead of 664 | * a simple string command. 665 | * 666 | * This is done by passing an object containing two properties: `regexp`, and 667 | * `callback` instead of the function. 668 | * 669 | * #### Examples: 670 | * ````javascript 671 | * var calculateFunction = function(month) { console.log(month); } 672 | * var commands = { 673 | * // This example will accept any word as the "month" 674 | * 'calculate :month stats': calculateFunction, 675 | * // This example will only accept months which are at the start of a quarter 676 | * 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction} 677 | * } 678 | ```` 679 | * 680 | */ 681 | -------------------------------------------------------------------------------- /dist/annyang.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | 5 | //! annyang 6 | //! version : 2.6.0 7 | //! author : Tal Ater @TalAter 8 | //! license : MIT 9 | //! https://www.TalAter.com/annyang/ 10 | (function (root, factory) { 11 | 'use strict'; 12 | 13 | if (typeof define === 'function' && define.amd) { 14 | // AMD + global 15 | define([], function () { 16 | return root.annyang = factory(root); 17 | }); 18 | } else if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object' && module.exports) { 19 | // CommonJS 20 | module.exports = factory(root); 21 | } else { 22 | // Browser globals 23 | root.annyang = factory(root); 24 | } 25 | })(typeof window !== 'undefined' ? window : undefined, function (root, undefined) { 26 | 'use strict'; 27 | 28 | /** 29 | * # Quick Tutorial, Intro and Demos 30 | * 31 | * The quickest way to get started is to visit the [annyang homepage](https://www.talater.com/annyang/). 32 | * 33 | * For a more in-depth look at annyang, read on. 34 | * 35 | * # API Reference 36 | */ 37 | 38 | var annyang; 39 | 40 | // Get the SpeechRecognition object, while handling browser prefixes 41 | var SpeechRecognition = root.SpeechRecognition || root.webkitSpeechRecognition || root.mozSpeechRecognition || root.msSpeechRecognition || root.oSpeechRecognition; 42 | 43 | // Check browser support 44 | // This is done as early as possible, to make it as fast as possible for unsupported browsers 45 | if (!SpeechRecognition) { 46 | return null; 47 | } 48 | 49 | var commandsList = []; 50 | var recognition; 51 | var callbacks = { start: [], error: [], end: [], soundstart: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] }; 52 | var autoRestart; 53 | var lastStartedAt = 0; 54 | var autoRestartCount = 0; 55 | var debugState = false; 56 | var debugStyle = 'font-weight: bold; color: #00f;'; 57 | var pauseListening = false; 58 | var _isListening = false; 59 | 60 | // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license. 61 | var optionalParam = /\s*\((.*?)\)\s*/g; 62 | var optionalRegex = /(\(\?:[^)]+\))\?/g; 63 | var namedParam = /(\(\?)?:\w+/g; 64 | var splatParam = /\*\w+/g; 65 | var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#]/g; 66 | var commandToRegExp = function commandToRegExp(command) { 67 | command = command.replace(escapeRegExp, '\\$&').replace(optionalParam, '(?:$1)?').replace(namedParam, function (match, optional) { 68 | return optional ? match : '([^\\s]+)'; 69 | }).replace(splatParam, '(.*?)').replace(optionalRegex, '\\s*$1?\\s*'); 70 | return new RegExp('^' + command + '$', 'i'); 71 | }; 72 | 73 | // This method receives an array of callbacks to iterate over, and invokes each of them 74 | var invokeCallbacks = function invokeCallbacks(callbacks) { 75 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 76 | args[_key - 1] = arguments[_key]; 77 | } 78 | 79 | callbacks.forEach(function (callback) { 80 | callback.callback.apply(callback.context, args); 81 | }); 82 | }; 83 | 84 | var isInitialized = function isInitialized() { 85 | return recognition !== undefined; 86 | }; 87 | 88 | // method for logging in developer console when debug mode is on 89 | var logMessage = function logMessage(text, extraParameters) { 90 | if (text.indexOf('%c') === -1 && !extraParameters) { 91 | console.log(text); 92 | } else { 93 | console.log(text, extraParameters || debugStyle); 94 | } 95 | }; 96 | 97 | var initIfNeeded = function initIfNeeded() { 98 | if (!isInitialized()) { 99 | annyang.init({}, false); 100 | } 101 | }; 102 | 103 | var registerCommand = function registerCommand(command, callback, originalPhrase) { 104 | commandsList.push({ command: command, callback: callback, originalPhrase: originalPhrase }); 105 | if (debugState) { 106 | logMessage('Command successfully loaded: %c' + originalPhrase, debugStyle); 107 | } 108 | }; 109 | 110 | var parseResults = function parseResults(results) { 111 | invokeCallbacks(callbacks.result, results); 112 | var commandText; 113 | // go over each of the 5 results and alternative results received (we've set maxAlternatives to 5 above) 114 | for (var i = 0; i < results.length; i++) { 115 | // the text recognized 116 | commandText = results[i].trim(); 117 | if (debugState) { 118 | logMessage('Speech recognized: %c' + commandText, debugStyle); 119 | } 120 | 121 | // try and match recognized text to one of the commands on the list 122 | for (var j = 0, l = commandsList.length; j < l; j++) { 123 | var currentCommand = commandsList[j]; 124 | var result = currentCommand.command.exec(commandText); 125 | if (result) { 126 | var parameters = result.slice(1); 127 | if (debugState) { 128 | logMessage('command matched: %c' + currentCommand.originalPhrase, debugStyle); 129 | if (parameters.length) { 130 | logMessage('with parameters', parameters); 131 | } 132 | } 133 | // execute the matched command 134 | currentCommand.callback.apply(this, parameters); 135 | invokeCallbacks(callbacks.resultMatch, commandText, currentCommand.originalPhrase, results); 136 | return; 137 | } 138 | } 139 | } 140 | invokeCallbacks(callbacks.resultNoMatch, results); 141 | }; 142 | 143 | annyang = { 144 | 145 | /** 146 | * Initialize annyang with a list of commands to recognize. 147 | * 148 | * #### Examples: 149 | * ````javascript 150 | * var commands = {'hello :name': helloFunction}; 151 | * var commands2 = {'hi': helloFunction}; 152 | * 153 | * // initialize annyang, overwriting any previously added commands 154 | * annyang.init(commands, true); 155 | * // adds an additional command without removing the previous commands 156 | * annyang.init(commands2, false); 157 | * ```` 158 | * As of v1.1.0 it is no longer required to call init(). Just start() listening whenever you want, and addCommands() whenever, and as often as you like. 159 | * 160 | * @param {Object} commands - Commands that annyang should listen to 161 | * @param {boolean} [resetCommands=true] - Remove all commands before initializing? 162 | * @method init 163 | * @deprecated 164 | * @see [Commands Object](#commands-object) 165 | */ 166 | init: function init(commands) { 167 | var resetCommands = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 168 | 169 | // Abort previous instances of recognition already running 170 | if (recognition && recognition.abort) { 171 | recognition.abort(); 172 | } 173 | 174 | // initiate SpeechRecognition 175 | recognition = new SpeechRecognition(); 176 | 177 | // Set the max number of alternative transcripts to try and match with a command 178 | recognition.maxAlternatives = 5; 179 | 180 | // In HTTPS, turn off continuous mode for faster results. 181 | // In HTTP, turn on continuous mode for much slower results, but no repeating security notices 182 | recognition.continuous = root.location.protocol === 'http:'; 183 | 184 | // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage() 185 | recognition.lang = 'en-US'; 186 | 187 | recognition.onstart = function () { 188 | _isListening = true; 189 | invokeCallbacks(callbacks.start); 190 | }; 191 | 192 | recognition.onsoundstart = function () { 193 | invokeCallbacks(callbacks.soundstart); 194 | }; 195 | 196 | recognition.onerror = function (event) { 197 | invokeCallbacks(callbacks.error, event); 198 | switch (event.error) { 199 | case 'network': 200 | invokeCallbacks(callbacks.errorNetwork, event); 201 | break; 202 | case 'not-allowed': 203 | case 'service-not-allowed': 204 | // if permission to use the mic is denied, turn off auto-restart 205 | autoRestart = false; 206 | // determine if permission was denied by user or automatically. 207 | if (new Date().getTime() - lastStartedAt < 200) { 208 | invokeCallbacks(callbacks.errorPermissionBlocked, event); 209 | } else { 210 | invokeCallbacks(callbacks.errorPermissionDenied, event); 211 | } 212 | break; 213 | } 214 | }; 215 | 216 | recognition.onend = function () { 217 | _isListening = false; 218 | invokeCallbacks(callbacks.end); 219 | // annyang will auto restart if it is closed automatically and not by user action. 220 | if (autoRestart) { 221 | // play nicely with the browser, and never restart annyang automatically more than once per second 222 | var timeSinceLastStart = new Date().getTime() - lastStartedAt; 223 | autoRestartCount += 1; 224 | if (autoRestartCount % 10 === 0) { 225 | if (debugState) { 226 | logMessage('Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips.'); 227 | } 228 | } 229 | if (timeSinceLastStart < 1000) { 230 | setTimeout(function () { 231 | annyang.start({ paused: pauseListening }); 232 | }, 1000 - timeSinceLastStart); 233 | } else { 234 | annyang.start({ paused: pauseListening }); 235 | } 236 | } 237 | }; 238 | 239 | recognition.onresult = function (event) { 240 | if (pauseListening) { 241 | if (debugState) { 242 | logMessage('Speech heard, but annyang is paused'); 243 | } 244 | return false; 245 | } 246 | 247 | // Map the results to an array 248 | var SpeechRecognitionResult = event.results[event.resultIndex]; 249 | var results = []; 250 | for (var k = 0; k < SpeechRecognitionResult.length; k++) { 251 | results[k] = SpeechRecognitionResult[k].transcript; 252 | } 253 | 254 | parseResults(results); 255 | }; 256 | 257 | // build commands list 258 | if (resetCommands) { 259 | commandsList = []; 260 | } 261 | if (commands.length) { 262 | this.addCommands(commands); 263 | } 264 | }, 265 | 266 | /** 267 | * Start listening. 268 | * It's a good idea to call this after adding some commands first, but not mandatory. 269 | * 270 | * Receives an optional options object which supports the following options: 271 | * 272 | * - `autoRestart` (boolean, default: true) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts? 273 | * - `continuous` (boolean) Allow forcing continuous mode on or off. Annyang is pretty smart about this, so only set this if you know what you're doing. 274 | * - `paused` (boolean, default: true) Start annyang in paused mode. 275 | * 276 | * #### Examples: 277 | * ````javascript 278 | * // Start listening, don't restart automatically 279 | * annyang.start({ autoRestart: false }); 280 | * // Start listening, don't restart automatically, stop recognition after first phrase recognized 281 | * annyang.start({ autoRestart: false, continuous: false }); 282 | * ```` 283 | * @param {Object} [options] - Optional options. 284 | * @method start 285 | */ 286 | start: function start(options) { 287 | initIfNeeded(); 288 | options = options || {}; 289 | if (options.paused !== undefined) { 290 | pauseListening = !!options.paused; 291 | } else { 292 | pauseListening = false; 293 | } 294 | if (options.autoRestart !== undefined) { 295 | autoRestart = !!options.autoRestart; 296 | } else { 297 | autoRestart = true; 298 | } 299 | if (options.continuous !== undefined) { 300 | recognition.continuous = !!options.continuous; 301 | } 302 | 303 | lastStartedAt = new Date().getTime(); 304 | try { 305 | recognition.start(); 306 | } catch (e) { 307 | if (debugState) { 308 | logMessage(e.message); 309 | } 310 | } 311 | }, 312 | 313 | /** 314 | * Stop listening, and turn off mic. 315 | * 316 | * Alternatively, to only temporarily pause annyang responding to commands without stopping the SpeechRecognition engine or closing the mic, use pause() instead. 317 | * @see [pause()](#pause) 318 | * 319 | * @method abort 320 | */ 321 | abort: function abort() { 322 | autoRestart = false; 323 | autoRestartCount = 0; 324 | if (isInitialized()) { 325 | recognition.abort(); 326 | } 327 | }, 328 | 329 | /** 330 | * Pause listening. annyang will stop responding to commands (until the resume or start methods are called), without turning off the browser's SpeechRecognition engine or the mic. 331 | * 332 | * Alternatively, to stop the SpeechRecognition engine and close the mic, use abort() instead. 333 | * @see [abort()](#abort) 334 | * 335 | * @method pause 336 | */ 337 | pause: function pause() { 338 | pauseListening = true; 339 | }, 340 | 341 | /** 342 | * Resumes listening and restores command callback execution when a result matches. 343 | * If SpeechRecognition was aborted (stopped), start it. 344 | * 345 | * @method resume 346 | */ 347 | resume: function resume() { 348 | annyang.start(); 349 | }, 350 | 351 | /** 352 | * Turn on output of debug messages to the console. Ugly, but super-handy! 353 | * 354 | * @param {boolean} [newState=true] - Turn on/off debug messages 355 | * @method debug 356 | */ 357 | debug: function debug() { 358 | var newState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 359 | 360 | debugState = !!newState; 361 | }, 362 | 363 | /** 364 | * Set the language the user will speak in. If this method is not called, defaults to 'en-US'. 365 | * 366 | * @param {String} language - The language (locale) 367 | * @method setLanguage 368 | * @see [Languages](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md#what-languages-are-supported) 369 | */ 370 | setLanguage: function setLanguage(language) { 371 | initIfNeeded(); 372 | recognition.lang = language; 373 | }, 374 | 375 | /** 376 | * Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands. 377 | * 378 | * #### Examples: 379 | * ````javascript 380 | * var commands = {'hello :name': helloFunction, 'howdy': helloFunction}; 381 | * var commands2 = {'hi': helloFunction}; 382 | * 383 | * annyang.addCommands(commands); 384 | * annyang.addCommands(commands2); 385 | * // annyang will now listen to all three commands 386 | * ```` 387 | * 388 | * @param {Object} commands - Commands that annyang should listen to 389 | * @method addCommands 390 | * @see [Commands Object](#commands-object) 391 | */ 392 | addCommands: function addCommands(commands) { 393 | var cb; 394 | 395 | initIfNeeded(); 396 | 397 | for (var phrase in commands) { 398 | if (commands.hasOwnProperty(phrase)) { 399 | cb = root[commands[phrase]] || commands[phrase]; 400 | if (typeof cb === 'function') { 401 | // convert command to regex then register the command 402 | registerCommand(commandToRegExp(phrase), cb, phrase); 403 | } else if ((typeof cb === 'undefined' ? 'undefined' : _typeof(cb)) === 'object' && cb.regexp instanceof RegExp) { 404 | // register the command 405 | registerCommand(new RegExp(cb.regexp.source, 'i'), cb.callback, phrase); 406 | } else { 407 | if (debugState) { 408 | logMessage('Can not register command: %c' + phrase, debugStyle); 409 | } 410 | continue; 411 | } 412 | } 413 | } 414 | }, 415 | 416 | /** 417 | * Remove existing commands. Called with a single phrase, array of phrases, or methodically. Pass no params to remove all commands. 418 | * 419 | * #### Examples: 420 | * ````javascript 421 | * var commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction}; 422 | * 423 | * // Remove all existing commands 424 | * annyang.removeCommands(); 425 | * 426 | * // Add some commands 427 | * annyang.addCommands(commands); 428 | * 429 | * // Don't respond to hello 430 | * annyang.removeCommands('hello'); 431 | * 432 | * // Don't respond to howdy or hi 433 | * annyang.removeCommands(['howdy', 'hi']); 434 | * ```` 435 | * @param {String|Array|Undefined} [commandsToRemove] - Commands to remove 436 | * @method removeCommands 437 | */ 438 | removeCommands: function removeCommands(commandsToRemove) { 439 | if (commandsToRemove === undefined) { 440 | commandsList = []; 441 | } else { 442 | commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove]; 443 | commandsList = commandsList.filter(function (command) { 444 | for (var i = 0; i < commandsToRemove.length; i++) { 445 | if (commandsToRemove[i] === command.originalPhrase) { 446 | return false; 447 | } 448 | } 449 | return true; 450 | }); 451 | } 452 | }, 453 | 454 | /** 455 | * Add a callback function to be called in case one of the following events happens: 456 | * 457 | * * `start` - Fired as soon as the browser's Speech Recognition engine starts listening 458 | * * `soundstart` - Fired as soon as any sound (possibly speech) has been detected. 459 | * This will fire once per Speech Recognition starting. See https://is.gd/annyang_sound_start 460 | * * `error` - Fired when the browser's Speech Recogntion engine returns an error, this generic error callback will be followed by more accurate error callbacks (both will fire if both are defined) 461 | * Callback function will be called with the error event as the first argument 462 | * * `errorNetwork` - Fired when Speech Recognition fails because of a network error 463 | * Callback function will be called with the error event as the first argument 464 | * * `errorPermissionBlocked` - Fired when the browser blocks the permission request to use Speech Recognition. 465 | * Callback function will be called with the error event as the first argument 466 | * * `errorPermissionDenied` - Fired when the user blocks the permission request to use Speech Recognition. 467 | * Callback function will be called with the error event as the first argument 468 | * * `end` - Fired when the browser's Speech Recognition engine stops 469 | * * `result` - Fired as soon as some speech was identified. This generic callback will be followed by either the `resultMatch` or `resultNoMatch` callbacks. 470 | * Callback functions for to this event will be called with an array of possible phrases the user said as the first argument 471 | * * `resultMatch` - Fired when annyang was able to match between what the user said and a registered command 472 | * Callback functions for this event will be called with three arguments in the following order: 473 | * * The phrase the user said that matched a command 474 | * * The command that was matched 475 | * * An array of possible alternative phrases the user might have said 476 | * * `resultNoMatch` - Fired when what the user said didn't match any of the registered commands. 477 | * Callback functions for this event will be called with an array of possible phrases the user might've said as the first argument 478 | * 479 | * #### Examples: 480 | * ````javascript 481 | * annyang.addCallback('error', function() { 482 | * $('.myErrorText').text('There was an error!'); 483 | * }); 484 | * 485 | * annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) { 486 | * console.log(userSaid); // sample output: 'hello' 487 | * console.log(commandText); // sample output: 'hello (there)' 488 | * console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty'] 489 | * }); 490 | * 491 | * // pass local context to a global function called notConnected 492 | * annyang.addCallback('errorNetwork', notConnected, this); 493 | * ```` 494 | * @param {String} type - Name of event that will trigger this callback 495 | * @param {Function} callback - The function to call when event is triggered 496 | * @param {Object} [context] - Optional context for the callback function 497 | * @method addCallback 498 | */ 499 | addCallback: function addCallback(type, callback, context) { 500 | var cb = root[callback] || callback; 501 | if (typeof cb === 'function' && callbacks[type] !== undefined) { 502 | callbacks[type].push({ callback: cb, context: context || this }); 503 | } 504 | }, 505 | 506 | /** 507 | * Remove callbacks from events. 508 | * 509 | * - Pass an event name and a callback command to remove that callback command from that event type. 510 | * - Pass just an event name to remove all callback commands from that event type. 511 | * - Pass undefined as event name and a callback command to remove that callback command from all event types. 512 | * - Pass no params to remove all callback commands from all event types. 513 | * 514 | * #### Examples: 515 | * ````javascript 516 | * annyang.addCallback('start', myFunction1); 517 | * annyang.addCallback('start', myFunction2); 518 | * annyang.addCallback('end', myFunction1); 519 | * annyang.addCallback('end', myFunction2); 520 | * 521 | * // Remove all callbacks from all events: 522 | * annyang.removeCallback(); 523 | * 524 | * // Remove all callbacks attached to end event: 525 | * annyang.removeCallback('end'); 526 | * 527 | * // Remove myFunction2 from being called on start: 528 | * annyang.removeCallback('start', myFunction2); 529 | * 530 | * // Remove myFunction1 from being called on all events: 531 | * annyang.removeCallback(undefined, myFunction1); 532 | * ```` 533 | * 534 | * @param type Name of event type to remove callback from 535 | * @param callback The callback function to remove 536 | * @returns undefined 537 | * @method removeCallback 538 | */ 539 | removeCallback: function removeCallback(type, callback) { 540 | var compareWithCallbackParameter = function compareWithCallbackParameter(cb) { 541 | return cb.callback !== callback; 542 | }; 543 | // Go over each callback type in callbacks store object 544 | for (var callbackType in callbacks) { 545 | if (callbacks.hasOwnProperty(callbackType)) { 546 | // if this is the type user asked to delete, or he asked to delete all, go ahead. 547 | if (type === undefined || type === callbackType) { 548 | // If user asked to delete all callbacks in this type or all types 549 | if (callback === undefined) { 550 | callbacks[callbackType] = []; 551 | } else { 552 | // Remove all matching callbacks 553 | callbacks[callbackType] = callbacks[callbackType].filter(compareWithCallbackParameter); 554 | } 555 | } 556 | } 557 | } 558 | }, 559 | 560 | /** 561 | * Returns true if speech recognition is currently on. 562 | * Returns false if speech recognition is off or annyang is paused. 563 | * 564 | * @return boolean true = SpeechRecognition is on and annyang is listening 565 | * @method isListening 566 | */ 567 | isListening: function isListening() { 568 | return _isListening && !pauseListening; 569 | }, 570 | 571 | /** 572 | * Returns the instance of the browser's SpeechRecognition object used by annyang. 573 | * Useful in case you want direct access to the browser's Speech Recognition engine. 574 | * 575 | * @returns SpeechRecognition The browser's Speech Recognizer currently used by annyang 576 | * @method getSpeechRecognizer 577 | */ 578 | getSpeechRecognizer: function getSpeechRecognizer() { 579 | return recognition; 580 | }, 581 | 582 | /** 583 | * Simulate speech being recognized. This will trigger the same events and behavior as when the Speech Recognition 584 | * detects speech. 585 | * 586 | * Can accept either a string containing a single sentence, or an array containing multiple sentences to be checked 587 | * in order until one of them matches a command (similar to the way Speech Recognition Alternatives are parsed) 588 | * 589 | * #### Examples: 590 | * ````javascript 591 | * annyang.trigger('Time for some thrilling heroics'); 592 | * annyang.trigger( 593 | * ['Time for some thrilling heroics', 'Time for some thrilling aerobics'] 594 | * ); 595 | * ```` 596 | * 597 | * @param string|array sentences A sentence as a string or an array of strings of possible sentences 598 | * @returns undefined 599 | * @method trigger 600 | */ 601 | trigger: function trigger(sentences) { 602 | if (!annyang.isListening()) { 603 | if (debugState) { 604 | if (!_isListening) { 605 | logMessage('Cannot trigger while annyang is aborted'); 606 | } else { 607 | logMessage('Speech heard, but annyang is paused'); 608 | } 609 | } 610 | return; 611 | } 612 | 613 | if (!Array.isArray(sentences)) { 614 | sentences = [sentences]; 615 | } 616 | 617 | parseResults(sentences); 618 | } 619 | }; 620 | 621 | return annyang; 622 | }); 623 | 624 | /** 625 | * # Good to Know 626 | * 627 | * ## Commands Object 628 | * 629 | * Both the [init()]() and addCommands() methods receive a `commands` object. 630 | * 631 | * annyang understands commands with `named variables`, `splats`, and `optional words`. 632 | * 633 | * * Use `named variables` for one word arguments in your command. 634 | * * Use `splats` to capture multi-word text at the end of your command (greedy). 635 | * * Use `optional words` or phrases to define a part of the command as optional. 636 | * 637 | * #### Examples: 638 | * ````html 639 | * 667 | * ```` 668 | * 669 | * ### Using Regular Expressions in commands 670 | * For advanced commands, you can pass a regular expression object, instead of 671 | * a simple string command. 672 | * 673 | * This is done by passing an object containing two properties: `regexp`, and 674 | * `callback` instead of the function. 675 | * 676 | * #### Examples: 677 | * ````javascript 678 | * var calculateFunction = function(month) { console.log(month); } 679 | * var commands = { 680 | * // This example will accept any word as the "month" 681 | * 'calculate :month stats': calculateFunction, 682 | * // This example will only accept months which are at the start of a quarter 683 | * 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction} 684 | * } 685 | ```` 686 | * 687 | */ 688 | //# sourceMappingURL=annyang.js.map 689 | --------------------------------------------------------------------------------