├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── demo ├── annyang.min.js ├── css │ ├── main.css │ └── main.min.css ├── images │ ├── flickr.png │ ├── footer_background.jpg │ ├── icon_js.png │ ├── icon_speech.png │ ├── icon_user.png │ ├── mini_icon_say.png │ ├── palette.png │ └── tpscover.jpg ├── index.html └── vendor │ ├── css │ ├── default.css │ ├── github.css │ └── tomorrow.css │ ├── html │ └── github-btn.html │ └── js │ └── highlight.pack.js ├── dist ├── annyang.js ├── annyang.js.map └── annyang.min.js ├── docs ├── FAQ.md └── README.md ├── package-lock.json ├── package.json ├── require.js ├── src └── annyang.js └── test ├── SpecRunner.html ├── init_corti.js ├── spec ├── .jshintrc ├── BasicSpec.js └── IssuesSpec.js └── vendor └── corti.js /.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 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jasmine"], 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "jasmine": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:jasmine/recommended"], 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "no-console": 0, 14 | "indent": ["error", 2, { "SwitchCase": 1 }], 15 | "linebreak-style": ["error", "unix"], 16 | "quotes": ["error", "single", { "avoidEscape": true }], 17 | "semi": ["error", "always"] 18 | }, 19 | "globals": { 20 | "define": false, 21 | "Corti": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | demo/* linguist-documentation 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .idea 4 | .grunt 5 | .DS_Store 6 | test/coverage/ 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '10' 5 | - '9' 6 | - '8' 7 | - '7' 8 | - '6' 9 | - 'node' 10 | deploy: 11 | provider: npm 12 | email: tal@talater.com 13 | api_key: 14 | secure: B7djVh1jMSccBqDoFYO+yDcucgd5u7vR8dy6U1p48BKcA78bNlIm58MO2XF22Qx0uqKAv3piD385DgUoICoHU4Au9BXFBEdMpxbLsd4LVIOekPCnOlF2GHz9p8mhUzNbXP4qXLjoNz1tZlcd9LgSujN35PnNDp00AKdjs8vIceo= 15 | on: 16 | tags: true 17 | branch: master 18 | node: '5' 19 | repo: TalAter/annyang 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to annyang 2 | 3 | Thank you for taking the time to get involved with annyang! :+1: 4 | 5 | There are several ways you can help the project out: 6 | 7 | * [Contributing code](#contributing-code) 8 | * [Reporting Bugs](#reporting-bugs) 9 | * [Feature Requests and Ideas](#feature-requests-and-ideas) 10 | 11 | ## How Can I Contribute? 12 | 13 | ### Contributing Code 14 | 15 | A lot of annyang's functionality came from pull requests sent over GitHub. Here is how you can contribute too: 16 | 17 | - [x] Fork the repository from the [annyang GitHub page](https://github.com/TalAter/annyang). 18 | - [x] Clone a copy to your local machine with `$ git clone git@github.com:YOUR-GITHUB-USER-NAME/annyang.git` 19 | - [x] Make sure you have *node.js* and *npm* installed on your machine. You can use this [guide](https://docs.npmjs.com/getting-started/installing-node) for help. 20 | - [x] Install all of annyang's development dependencies with npm. `$ cd annyang; npm install` 21 | - [x] If you do not have the grunt-cli installed globally, run `$ npm install -g grunt-cli` 22 | - [x] Run grunt to make sure everything runs smoothly `$ grunt` 23 | - [x] Add tests for your code. [See details below](#automated-testing). 24 | - [x] Code, code, code. Changes should be done in `/src/annyang.js`. They will be transpiled to `/dist/annyang.js` and `/dist/annyang.min.js`. 25 | - [x] Run `$ grunt` after making changes to verify that everything still works and the tests all pass. 26 | 27 | :bulb: A great alternative to repeatedly running `$ grunt` is to run `$ grunt watch` once, and leave this process running. It will continuously run all the tests and build the files every time you make a change to one of annyang's files. It will even *beep* if you make an error, and help you debug it. :+1: 28 | - [x] You can run a local server to test your work in the browser by running `$ grunt dev`. This will also automatically run `$ grunt watch` for you. 29 | Point your browser to `https://localhost:8443/demo/` to see the demo page. 30 | Since it's using a self-signed certificate, you might need to click *"Proceed Anyway"*. 31 | - [x] Before committing your changes, the last step must always be running `$ grunt`. This makes sure everything works, and all files are kept up to date with your changes. 32 | - [x] Once you've made sure all your changes work correctly and have been committed, push your local changes back to github with `$ git push -u origin master` 33 | - [x] Visit your fork on GitHub.com ([https://github.com/YOUR-USER-NAME/annyang](https://github.com/YOUR-USER-NAME/annyang)) and create a pull request for your changes. 34 | - [x] Makes sure your pull request describes exactly what you changed and if it relates to an open issue references that issue (just include the issue number in the title like this: #49) 35 | 36 | #### Important: 37 | 38 | * Make sure to run `npm install` and `grunt` and make sure all tasks completed successfully before committing. 39 | * Do not change the [API docs](https://github.com/TalAter/annyang/blob/master/docs/README.md) in `/docs/README.md` directly. This file is generated automatically, and your changes will be overwritten. Instead, update the relevant comments in `src/annyang.js` 40 | * annyang is still not completely automatically tested :disappointed:. If you make a change, please make sure to test your change thoroughly to make sure no backward functionality was broken, and that your change works as intended. 41 | * Do not update the version number yourself. 42 | * Please stick to the project's existing coding style. Coding styles don't need to have a consensus, they just need to be consistent :smile:. 43 | * Push your changes to a topic branch in your fork of the repository. Your branch should be based on the `master` branch. 44 | * When submitting [pull request](https://help.github.com/articles/using-pull-requests/), please elaborate as much as possible about the change, your motivation for the change, etc. 45 | 46 | #### Automated Testing 47 | 48 | annyang is tested using [Jasmine](http://jasmine.github.io/2.0/introduction.html). 49 | 50 | Please include tests for any changes you make: 51 | * If you found a bug, please write a test that fails because of that bug, then fix the bug so that the test passes. 52 | * If you are adding a new feature, write tests that thoroughly test every possible use of your code. 53 | * If you are changing existing functionality, make sure to update existing tests so they pass. (This is a last resort move. Whenever possible try to maintain backward compatibility) 54 | 55 | The tests reside in *BasicSpec.js*. The file contains a series of spec groups (e.g. `describe('a spec group', function() {});`) which each contain 1 or more specs (e.g. `it('should do stuff', function() {});`). Some of the spec groups also contain some code which runs before each spec (`beforeEach(function() {});`). 56 | 57 | To simulate Speech Recognition in the testing environment, annyang uses a mock object called [Corti](https://github.com/TalAter/Corti) which mocks the browser's SpeechRecognition object. Corti also adds a number of utility functions to the SpeechRecognition object which simulate user actions (e.g. `say('Hello there')`), and allow checking the SpeechRecognition's status (e.g. `isListening() === true`). 58 | 59 | ### Reporting Bugs 60 | 61 | Bugs are tracked as [GitHub issues](https://github.com/TalAter/annyang/issues). If you found a bug with annyang, the quickest way to get help would be to look through existing open and closed [GitHub issues](https://github.com/TalAter/annyang/issues?q=is%3Aissue). If the issue is already being discussed and hasn't been resolved yet, you can join the discussion and provide details about the problem you are having. If this is a new bug, please open a [new issue](https://github.com/TalAter/annyang/issues/new). 62 | 63 | When you are creating a bug report, please include as many details as possible. 64 | 65 | Explain the problem and include additional details to help maintainers reproduce the problem. 66 | 67 | * Use a clear and descriptive title for the issue to identify the problem. 68 | * Describe the exact steps which reproduce the problem. Share the relevant code to reproduce the issue if possible. 69 | * Try to isolate the issue as much as possible, reducing unrelated code until you get to the minimal amount of code in which the bug still reproduces. This is the most important step to help the community solve the issue. 70 | 71 | ### Feature Requests and Ideas 72 | 73 | We track discussions of new features, proposed changes, and other ideas as [GitHub issues](https://github.com/TalAter/annyang/issues). If you would like to discuss one of those, please first look through existing open and closed [GitHub issues](https://github.com/TalAter/annyang/issues?q=is%3Aissue) and see if there is already a discussion on this topic which you can join. If there isn't, please open a [new issue](https://github.com/TalAter/annyang/issues/new). 74 | 75 | When discussing new ideas or proposing changes, please take the time to be as descriptive as possible about the topic at hand. Please take the time to explain the issue you are facing, or the problem you propose to solve in as much detail as possible. 76 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | jshint: { 8 | all: [ 9 | 'Gruntfile.js', 10 | 'src/annyang.js', 11 | 'test/spec/*Spec.js' 12 | ], 13 | options: { 14 | jshintrc: true 15 | } 16 | }, 17 | 'babel': { 18 | options: { 19 | sourceMap: true, 20 | presets: ['env'] 21 | }, 22 | dist: { 23 | files: { 24 | 'dist/annyang.js': 'src/annyang.js' 25 | } 26 | } 27 | }, 28 | watch: { 29 | files: ['src/*.js', 'demo/css/*.css', 'test/spec/*Spec.js', '!**/node_modules/**'], 30 | tasks: ['default'] 31 | }, 32 | uglify: { 33 | options: { 34 | output: { 35 | comments: /^\! / 36 | } 37 | }, 38 | all: { 39 | files: { 40 | 'dist/annyang.min.js': ['dist/annyang.js'] 41 | } 42 | } 43 | }, 44 | imagemin: { 45 | demoimages: { // Target 46 | options: { // Target options 47 | }, 48 | files: [{ 49 | expand: true, // Enable dynamic expansion 50 | cwd: 'demo/images', // Src matches are relative to this path 51 | src: ['*.{png,jpg,gif}'], // Actual patterns to match 52 | dest: 'demo/images' // Destination path prefix 53 | }] 54 | } 55 | }, 56 | cssmin: { 57 | combine: { 58 | files: { 59 | 'demo/css/main.min.css': ['demo/css/main.css', 'demo/vendor/css/default.css', 'demo/vendor/css/github.css'] 60 | } 61 | } 62 | }, 63 | markdox: { 64 | target: { 65 | files: [ 66 | {src: 'src/annyang.js', dest: 'docs/README.md'} 67 | ] 68 | } 69 | }, 70 | connect: { 71 | server: { 72 | options: { 73 | protocol: 'https', 74 | port: 8443, 75 | hostname: '*', 76 | base: '.', 77 | open: 'https://localhost:8443/demo' 78 | } 79 | } 80 | }, 81 | jasmine: { 82 | browserAMD: { 83 | src: ['dist/annyang.min.js'], 84 | options: { 85 | specs: 'test/spec/*Spec.js', 86 | outfile: 'test/SpecRunner.html', 87 | vendor: ['test/vendor/corti.js', 'test/init_corti.js'], 88 | keepRunner: true, 89 | template: require('grunt-template-jasmine-requirejs'), 90 | templateOptions: { 91 | requireConfig: { 92 | baseUrl: '../dist/' 93 | } 94 | } 95 | } 96 | } 97 | } 98 | }); 99 | 100 | // Load NPM tasks 101 | require('load-grunt-tasks')(grunt, { 102 | pattern: ['grunt-*', '!grunt-template-jasmine-istanbul'] 103 | }); 104 | 105 | // Register tasks 106 | grunt.registerTask('default', ['jshint', 'babel', 'uglify', 'cssmin', 'markdox']); 107 | grunt.registerTask('dev', ['default', 'connect', 'watch']); 108 | grunt.registerTask('test', ['default', 'jasmine']); 109 | 110 | }; 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # annyang! 2 | 3 | A tiny JavaScript Speech Recognition library that lets your users control your site with voice commands. 4 | 5 | **annyang** has no dependencies, weighs just 2 KB, and is free to use and modify under the MIT license. 6 | 7 | ## Demo and Tutorial 8 | 9 | [Play with some live speech recognition demos](https://www.talater.com/annyang) 10 | 11 | ## FAQ, Technical Documentation, and API Reference 12 | 13 | - [annyang Frequently Asked Questions](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md) 14 | - [annyang API reference](https://github.com/TalAter/annyang/blob/master/docs/README.md) 15 | - [annyang tutorial](https://www.talater.com/annyang) 16 | 17 | ## Hello World 18 | 19 | It's as easy as adding [one javascript file](//cdnjs.cloudflare.com/ajax/libs/annyang/2.6.1/annyang.min.js) to your document and defining the commands you want. 20 | 21 | ````html 22 | 23 | 37 | ```` 38 | 39 | **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).** 40 | 41 | ## Adding a GUI 42 | 43 | You can easily add a GUI for the user to interact with Speech Recognition using [Speech KITT](https://github.com/TalAter/SpeechKITT). 44 | 45 | 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. 46 | 47 | Speech KITT is fully customizable and comes with many different themes, and instructions on how to create your own designs. 48 | 49 | [![Speech Recognition GUI with Speech KITT](https://raw.githubusercontent.com/TalAter/SpeechKITT/master/demo/speechkitt-demo.gif)](https://github.com/TalAter/SpeechKITT) 50 | 51 | ````html 52 | 53 | 54 | 71 | ```` 72 | 73 | For help with setting up a GUI with KITT, check out the [Speech KITT page](https://github.com/TalAter/SpeechKITT). 74 | 75 | ## Author 76 | 77 | Tal Ater: [@TalAter](https://twitter.com/TalAter) 78 | 79 | ## License 80 | 81 | Licensed under [MIT](https://github.com/TalAter/annyang/blob/master/LICENSE). 82 | -------------------------------------------------------------------------------- /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 | ] 24 | } 25 | -------------------------------------------------------------------------------- /demo/annyang.min.js: -------------------------------------------------------------------------------- 1 | ../dist/annyang.min.js -------------------------------------------------------------------------------- /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: #d9e7f3; 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: #0b4f3f; 99 | color: #fff; 100 | } 101 | #section_image_search { 102 | background-color: #295074; 103 | color: #fff; 104 | } 105 | #section_biz_use { 106 | background-color: #4a5758; 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 | -------------------------------------------------------------------------------- /demo/css/main.min.css: -------------------------------------------------------------------------------- 1 | body{background:url(../images/palette.png) repeat-x 0 -2px;font:14px/1.6 helvetica,sans-serif;margin:0 0;padding:0 0;color:#333}.hidden{display:none}a,a:visited{color:#d9e7f3;text-decoration:underline}a:hover{text-decoration:none}h2{font-size:2em}pre code a,pre code a:visited{color:#00f}#tpsreport{position:fixed;z-index:1000;bottom:-500px;right:10px;-webkit-transform:rotate(-15deg);-moz-transform:rotate(-15deg);-ms-transform:rotate(-15deg);-o-transform:rotate(-15deg);transform:rotate(-15deg);-webkit-box-shadow:2px 1px 6px 1px #555;box-shadow:2px 1px 6px 1px #555}#section_header h2{margin:5px 0;font-size:20px;line-height:30px;font-weight:400}pre code{overflow:scroll;padding:10px 20px!important;margin-left:50px}h1 em{font-weight:800;margin-right:9px}h1{font-weight:400;font-size:40px;line-height:41px;margin:25px 0}p em{font-weight:600;font-size:23px;line-height:45px}p.voice_instructions{background:url(../images/mini_icon_say.png) no-repeat 0 6px;padding-left:24px}#hello{font-size:2.5em;font-weight:600}#flickrLoader p{background:url(../images/flickr.png) no-repeat 0 7px;padding-left:39px}#flickrLoader{height:40px}section{padding:3% 10%;font-size:20px;line-height:30px;font-weight:400}#section_header img{margin:30px 50px 0}#section_header{padding-top:70px;text-align:center}#section_hello{background-color:#0b4f3f;color:#fff}#section_image_search{background-color:#295074;color:#fff}#section_biz_use{background-color:#4a5758;color:#fff}#section_footer{background:#000 url(../images/footer_background.jpg) repeat-x top center;background-size:auto 100%;text-align:center;background-color:#34495d;color:#fff;padding:2% 10%}div.social{margin-top:40px}div.copyright{margin-top:50px;font-size:12px;line-height:18px;font-weight:100}div#unsupported{text-align:center;position:fixed;bottom:0;width:100%;max-height:60%;color:#b94a48;background-color:#f2dede;border:1px solid #eed3d7;-webkit-box-shadow:-32px -50px 109px rgba(0,0,0,.92);-moz-box-shadow:-32px -50px 109px rgba(0,0,0,.92);box-shadow:-32px -50px 109px rgba(0,0,0,.92);font-size:1.3em;line-height:1.4em;padding-bottom:10px}div#unsupported a,div#unsupported a:visited{color:#b94a48;font-weight:700}div#unsupported h4{font-size:1.5em;margin:15px 0 10px 0}div#unsupported p{line-height:20px}pre code{display:block;padding:.5em;background:#f0f0f0}pre .clojure .built_in,pre .lisp .title,pre .nginx .title,pre .subst,pre .tag .title,pre code{color:#000}pre .addition,pre .aggregate,pre .apache .cbracket,pre .apache .tag,pre .bash .variable,pre .constant,pre .django .variable,pre .erlang_repl .function_or_atom,pre .flow,pre .markdown .header,pre .parent,pre .preprocessor,pre .ruby .symbol,pre .ruby .symbol .string,pre .rules .value,pre .rules .value .number,pre .smalltalk .class,pre .stream,pre .string,pre .tag .value,pre .template_tag,pre .tex .command,pre .tex .special,pre .title{color:#800}pre .annotation,pre .chunk,pre .comment,pre .diff .header,pre .markdown .blockquote,pre .template_comment{color:#6b6b5b}pre .change,pre .date,pre .go .constant,pre .literal,pre .markdown .bullet,pre .markdown .link_url,pre .number,pre .regexp,pre .smalltalk .char,pre .smalltalk .symbol{color:#080}pre .apache .sqbracket,pre .array,pre .attr_selector,pre .clojure .attribute,pre .coffeescript .property,pre .decorator,pre .deletion,pre .doctype,pre .envvar,pre .erlang_repl .reserved,pre .filter .argument,pre .important,pre .javadoc,pre .label,pre .localvars,pre .markdown .link_label,pre .nginx .built_in,pre .pi,pre .prompt,pre .pseudo,pre .ruby .string,pre .shebang,pre .tex .formula,pre .vhdl .attribute{color:#88f}pre .aggregate,pre .apache .tag,pre .bash .variable,pre .built_in,pre .css .tag,pre .go .typename,pre .id,pre .javadoctag,pre .keyword,pre .markdown .strong,pre .phpdoc,pre .request,pre .smalltalk .class,pre .status,pre .tex .command,pre .title,pre .winutils,pre .yardoctag{font-weight:700}pre .markdown .emphasis{font-style:italic}pre .nginx .built_in{font-weight:400}pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula,pre .xml .cdata,pre .xml .css,pre .xml .javascript,pre .xml .vbscript{opacity:1}pre code{display:block;padding:.5em;color:#333;background:#f8f8ff}pre .comment,pre .diff .header,pre .javadoc,pre .template_comment{color:#6b6b5b;font-style:italic}pre .css .rule .keyword,pre .javascript .title,pre .keyword,pre .nginx .title,pre .request,pre .status,pre .subst,pre .winutils{color:#333;font-weight:700}pre .hexcolor,pre .number,pre .ruby .constant{color:#099}pre .phpdoc,pre .string,pre .tag .value,pre .tex .formula{color:#d14}pre .id,pre .title{color:#900;font-weight:700}pre .clojure .title,pre .javascript .title,pre .lisp .title,pre .subst{font-weight:400}pre .class .title,pre .haskell .type,pre .tex .command,pre .vhdl .literal{color:#458;font-weight:700}pre .django .tag .keyword,pre .rules .property,pre .tag,pre .tag .title{color:navy;font-weight:400}pre .attribute,pre .lisp .body,pre .variable{color:teal}pre .regexp{color:#009926}pre .class{color:#458;font-weight:700}pre .lisp .keyword,pre .prompt,pre .ruby .symbol .string,pre .symbol,pre .tex .special{color:#990073}pre .built_in,pre .clojure .built_in,pre .lisp .title{color:#0086b3}pre .cdata,pre .doctype,pre .pi,pre .preprocessor,pre .shebang{color:#999;font-weight:700}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa} -------------------------------------------------------------------------------- /demo/images/flickr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/flickr.png -------------------------------------------------------------------------------- /demo/images/footer_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/footer_background.jpg -------------------------------------------------------------------------------- /demo/images/icon_js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/icon_js.png -------------------------------------------------------------------------------- /demo/images/icon_speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/icon_speech.png -------------------------------------------------------------------------------- /demo/images/icon_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/icon_user.png -------------------------------------------------------------------------------- /demo/images/mini_icon_say.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/mini_icon_say.png -------------------------------------------------------------------------------- /demo/images/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/palette.png -------------------------------------------------------------------------------- /demo/images/tpscover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAter/annyang/46d3f1c212d43cae147e633ff9c52b36605c5728/demo/images/tpscover.jpg -------------------------------------------------------------------------------- /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 | User icon 108 | Speech baloon icon 109 | JavaScript icon 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.1/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 |   const 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 | const 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 | const showFlickr = function(tag) {
172 |   var url = 'http://api.flickr.com/services/rest/?tags='+tag;
173 |   $.getJSON(url);
174 | }
175 | 
176 | const calculateStats = function(month) {
177 |   $('#stats').text('Statistics for '+month);
178 | }
179 | 
180 | const 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 | -------------------------------------------------------------------------------- /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: #6b6b5b; 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: 1; 135 | } 136 | -------------------------------------------------------------------------------- /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: #6b6b5b; 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/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/html/github-btn.html: -------------------------------------------------------------------------------- 1 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 244 | -------------------------------------------------------------------------------- /demo/vendor/js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//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); -------------------------------------------------------------------------------- /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.1 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 = { 52 | start: [], 53 | error: [], 54 | end: [], 55 | soundstart: [], 56 | result: [], 57 | resultMatch: [], 58 | resultNoMatch: [], 59 | errorNetwork: [], 60 | errorPermissionBlocked: [], 61 | errorPermissionDenied: [] 62 | }; 63 | var autoRestart; 64 | var lastStartedAt = 0; 65 | var autoRestartCount = 0; 66 | var debugState = false; 67 | var debugStyle = 'font-weight: bold; color: #00f;'; 68 | var pauseListening = false; 69 | var _isListening = false; 70 | 71 | // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license. 72 | var optionalParam = /\s*\((.*?)\)\s*/g; 73 | var optionalRegex = /(\(\?:[^)]+\))\?/g; 74 | var namedParam = /(\(\?)?:\w+/g; 75 | var splatParam = /\*\w+/g; 76 | var escapeRegExp = /[-{}[\]+?.,\\^$|#]/g; 77 | var commandToRegExp = function commandToRegExp(command) { 78 | command = command.replace(escapeRegExp, '\\$&').replace(optionalParam, '(?:$1)?').replace(namedParam, function (match, optional) { 79 | return optional ? match : '([^\\s]+)'; 80 | }).replace(splatParam, '(.*?)').replace(optionalRegex, '\\s*$1?\\s*'); 81 | return new RegExp('^' + command + '$', 'i'); 82 | }; 83 | 84 | // This method receives an array of callbacks to iterate over, and invokes each of them 85 | var invokeCallbacks = function invokeCallbacks(callbacks) { 86 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 87 | args[_key - 1] = arguments[_key]; 88 | } 89 | 90 | callbacks.forEach(function (callback) { 91 | callback.callback.apply(callback.context, args); 92 | }); 93 | }; 94 | 95 | var isInitialized = function isInitialized() { 96 | return recognition !== undefined; 97 | }; 98 | 99 | // method for logging in developer console when debug mode is on 100 | var logMessage = function logMessage(text, extraParameters) { 101 | if (text.indexOf('%c') === -1 && !extraParameters) { 102 | console.log(text); 103 | } else { 104 | console.log(text, extraParameters || debugStyle); 105 | } 106 | }; 107 | 108 | var initIfNeeded = function initIfNeeded() { 109 | if (!isInitialized()) { 110 | annyang.init({}, false); 111 | } 112 | }; 113 | 114 | var registerCommand = function registerCommand(command, callback, originalPhrase) { 115 | commandsList.push({ command: command, callback: callback, originalPhrase: originalPhrase }); 116 | if (debugState) { 117 | logMessage('Command successfully loaded: %c' + originalPhrase, debugStyle); 118 | } 119 | }; 120 | 121 | var parseResults = function parseResults(results) { 122 | invokeCallbacks(callbacks.result, results); 123 | var commandText; 124 | // go over each of the 5 results and alternative results received (we have set maxAlternatives to 5 above) 125 | for (var i = 0; i < results.length; i++) { 126 | // the text recognized 127 | commandText = results[i].trim(); 128 | if (debugState) { 129 | logMessage('Speech recognized: %c' + commandText, debugStyle); 130 | } 131 | 132 | // try and match the recognized text to one of the commands on the list 133 | for (var j = 0, l = commandsList.length; j < l; j++) { 134 | var currentCommand = commandsList[j]; 135 | var result = currentCommand.command.exec(commandText); 136 | if (result) { 137 | var parameters = result.slice(1); 138 | if (debugState) { 139 | logMessage('command matched: %c' + currentCommand.originalPhrase, debugStyle); 140 | if (parameters.length) { 141 | logMessage('with parameters', parameters); 142 | } 143 | } 144 | // execute the matched command 145 | currentCommand.callback.apply(this, parameters); 146 | invokeCallbacks(callbacks.resultMatch, commandText, currentCommand.originalPhrase, results); 147 | return; 148 | } 149 | } 150 | } 151 | invokeCallbacks(callbacks.resultNoMatch, results); 152 | }; 153 | 154 | annyang = { 155 | /** 156 | * Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands. 157 | * 158 | * #### Examples: 159 | * ````javascript 160 | * const commands = {'hello :name': helloFunction, 'howdy': helloFunction}; 161 | * const commands2 = {'hi': helloFunction}; 162 | * 163 | * annyang.addCommands(commands); 164 | * annyang.addCommands(commands2); 165 | * // annyang will now listen for all three commands 166 | * ```` 167 | * 168 | * @param {Object} commands - Commands that annyang should listen for 169 | * @method addCommands 170 | * @see [Commands Object](#commands-object) 171 | */ 172 | addCommands: function addCommands(commands) { 173 | var cb; 174 | 175 | initIfNeeded(); 176 | 177 | for (var phrase in commands) { 178 | if (commands.hasOwnProperty(phrase)) { 179 | cb = root[commands[phrase]] || commands[phrase]; 180 | if (typeof cb === 'function') { 181 | // convert command to regex then register the command 182 | registerCommand(commandToRegExp(phrase), cb, phrase); 183 | } else if ((typeof cb === 'undefined' ? 'undefined' : _typeof(cb)) === 'object' && cb.regexp instanceof RegExp) { 184 | // register the command 185 | registerCommand(new RegExp(cb.regexp.source, 'i'), cb.callback, phrase); 186 | } else { 187 | if (debugState) { 188 | logMessage('Can not register command: %c' + phrase, debugStyle); 189 | } 190 | continue; 191 | } 192 | } 193 | } 194 | }, 195 | 196 | /** 197 | * Start listening. 198 | * It's a good idea to call this after adding some commands first (but not mandatory) 199 | * 200 | * Receives an optional options object which supports the following options: 201 | * 202 | * - `autoRestart` (boolean) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts? 203 | * - `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. 204 | * - `paused` (boolean) Start annyang in paused mode. 205 | * 206 | * #### Examples: 207 | * ````javascript 208 | * // Start listening, don't restart automatically 209 | * annyang.start({ autoRestart: false }); 210 | * // Start listening, don't restart automatically, stop recognition after first phrase recognized 211 | * annyang.start({ autoRestart: false, continuous: false }); 212 | * ```` 213 | * @param {Object} [options] - Optional options. 214 | * @method start 215 | */ 216 | start: function start(options) { 217 | initIfNeeded(); 218 | options = options || {}; 219 | if (options.paused !== undefined) { 220 | pauseListening = !!options.paused; 221 | } else { 222 | pauseListening = false; 223 | } 224 | if (options.autoRestart !== undefined) { 225 | autoRestart = !!options.autoRestart; 226 | } else { 227 | autoRestart = true; 228 | } 229 | if (options.continuous !== undefined) { 230 | recognition.continuous = !!options.continuous; 231 | } 232 | 233 | lastStartedAt = new Date().getTime(); 234 | try { 235 | recognition.start(); 236 | } catch (e) { 237 | if (debugState) { 238 | logMessage(e.message); 239 | } 240 | } 241 | }, 242 | 243 | /** 244 | * Stop listening, and turn off mic. 245 | * 246 | * Alternatively, to only temporarily pause annyang responding to commands without stopping the SpeechRecognition engine or closing the mic, use pause() instead. 247 | * @see [pause()](#pause) 248 | * 249 | * @method abort 250 | */ 251 | abort: function abort() { 252 | autoRestart = false; 253 | autoRestartCount = 0; 254 | if (isInitialized()) { 255 | recognition.abort(); 256 | } 257 | }, 258 | 259 | /** 260 | * 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. 261 | * 262 | * Alternatively, to stop the SpeechRecognition engine and close the mic, use abort() instead. 263 | * @see [abort()](#abort) 264 | * 265 | * @method pause 266 | */ 267 | pause: function pause() { 268 | pauseListening = true; 269 | }, 270 | 271 | /** 272 | * Resumes listening and restore command callback execution when a command is matched. 273 | * If SpeechRecognition was aborted (stopped), start it. 274 | * 275 | * @method resume 276 | */ 277 | resume: function resume() { 278 | annyang.start(); 279 | }, 280 | 281 | /** 282 | * Turn on the output of debug messages to the console. Ugly, but super-handy! 283 | * 284 | * @param {boolean} [newState=true] - Turn on/off debug messages 285 | * @method debug 286 | */ 287 | debug: function debug() { 288 | var newState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 289 | 290 | debugState = !!newState; 291 | }, 292 | 293 | /** 294 | * Set the language the user will speak in. If this method is not called, defaults to 'en-US'. 295 | * 296 | * @param {String} language - The language (locale) 297 | * @method setLanguage 298 | * @see [Languages](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md#what-languages-are-supported) 299 | */ 300 | setLanguage: function setLanguage(language) { 301 | initIfNeeded(); 302 | recognition.lang = language; 303 | }, 304 | 305 | /** 306 | * Remove existing commands. Called with a single phrase, an array of phrases, or methodically. Pass no params to remove all commands. 307 | * 308 | * #### Examples: 309 | * ````javascript 310 | * const commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction}; 311 | * 312 | * // Remove all existing commands 313 | * annyang.removeCommands(); 314 | * 315 | * // Add some commands 316 | * annyang.addCommands(commands); 317 | * 318 | * // Don't respond to hello 319 | * annyang.removeCommands('hello'); 320 | * 321 | * // Don't respond to howdy or hi 322 | * annyang.removeCommands(['howdy', 'hi']); 323 | * ```` 324 | * @param {String|Array|Undefined} [commandsToRemove] - Commands to remove 325 | * @method removeCommands 326 | */ 327 | removeCommands: function removeCommands(commandsToRemove) { 328 | if (commandsToRemove === undefined) { 329 | commandsList = []; 330 | } else { 331 | commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove]; 332 | commandsList = commandsList.filter(function (command) { 333 | for (var i = 0; i < commandsToRemove.length; i++) { 334 | if (commandsToRemove[i] === command.originalPhrase) { 335 | return false; 336 | } 337 | } 338 | return true; 339 | }); 340 | } 341 | }, 342 | 343 | /** 344 | * Add a callback function to be called in case one of the following events happens: 345 | * 346 | * * `start` - Fired as soon as the browser's Speech Recognition engine starts listening. 347 | * 348 | * * `soundstart` - Fired as soon as any sound (possibly speech) has been detected. 349 | * 350 | * This will fire once per Speech Recognition starting. See https://is.gd/annyang_sound_start. 351 | * 352 | * * `error` - Fired when the browser's Speech Recognition engine returns an error, this generic error callback will be followed by more accurate error callbacks (both will fire if both are defined). 353 | * 354 | * The Callback function will be called with the error event as the first argument. 355 | * 356 | * * `errorNetwork` - Fired when Speech Recognition fails because of a network error. 357 | * 358 | * The Callback function will be called with the error event as the first argument. 359 | * 360 | * * `errorPermissionBlocked` - Fired when the browser blocks the permission request to use Speech Recognition. 361 | * 362 | * The Callback function will be called with the error event as the first argument. 363 | * 364 | * * `errorPermissionDenied` - Fired when the user blocks the permission request to use Speech Recognition. 365 | * 366 | * The Callback function will be called with the error event as the first argument. 367 | * 368 | * * `end` - Fired when the browser's Speech Recognition engine stops. 369 | * 370 | * * `result` - Fired as soon as some speech was identified. This generic callback will be followed by either the `resultMatch` or `resultNoMatch` callbacks. 371 | * 372 | * The Callback functions for this event will be called with an array of possible phrases the user said as the first argument. 373 | * 374 | * * `resultMatch` - Fired when annyang was able to match between what the user said and a registered command. 375 | * 376 | * The Callback functions for this event will be called with three arguments in the following order: 377 | * 378 | * * The phrase the user said that matched a command. 379 | * * The command that was matched. 380 | * * An array of possible alternative phrases the user might have said. 381 | * 382 | * * `resultNoMatch` - Fired when what the user said didn't match any of the registered commands. 383 | * 384 | * Callback functions for this event will be called with an array of possible phrases the user might have said as the first argument. 385 | * 386 | * #### Examples: 387 | * ````javascript 388 | * annyang.addCallback('error', function() { 389 | * $('.myErrorText').text('There was an error!'); 390 | * }); 391 | * 392 | * annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) { 393 | * console.log(userSaid); // sample output: 'hello' 394 | * console.log(commandText); // sample output: 'hello (there)' 395 | * console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty'] 396 | * }); 397 | * 398 | * // pass local context to a global function called notConnected 399 | * annyang.addCallback('errorNetwork', notConnected, this); 400 | * ```` 401 | * @param {String} type - Name of event that will trigger this callback 402 | * @param {Function} callback - The function to call when event is triggered 403 | * @param {Object} [context] - Optional context for the callback function 404 | * @method addCallback 405 | */ 406 | addCallback: function addCallback(type, callback, context) { 407 | var cb = root[callback] || callback; 408 | if (typeof cb === 'function' && callbacks[type] !== undefined) { 409 | callbacks[type].push({ callback: cb, context: context || this }); 410 | } 411 | }, 412 | 413 | /** 414 | * Remove callbacks from events. 415 | * 416 | * - Pass an event name and a callback command to remove that callback command from that event type. 417 | * - Pass just an event name to remove all callback commands from that event type. 418 | * - Pass undefined as event name and a callback command to remove that callback command from all event types. 419 | * - Pass no params to remove all callback commands from all event types. 420 | * 421 | * #### Examples: 422 | * ````javascript 423 | * annyang.addCallback('start', myFunction1); 424 | * annyang.addCallback('start', myFunction2); 425 | * annyang.addCallback('end', myFunction1); 426 | * annyang.addCallback('end', myFunction2); 427 | * 428 | * // Remove all callbacks from all events: 429 | * annyang.removeCallback(); 430 | * 431 | * // Remove all callbacks attached to end event: 432 | * annyang.removeCallback('end'); 433 | * 434 | * // Remove myFunction2 from being called on start: 435 | * annyang.removeCallback('start', myFunction2); 436 | * 437 | * // Remove myFunction1 from being called on all events: 438 | * annyang.removeCallback(undefined, myFunction1); 439 | * ```` 440 | * 441 | * @param type Name of event type to remove callback from 442 | * @param callback The callback function to remove 443 | * @returns undefined 444 | * @method removeCallback 445 | */ 446 | removeCallback: function removeCallback(type, callback) { 447 | var compareWithCallbackParameter = function compareWithCallbackParameter(cb) { 448 | return cb.callback !== callback; 449 | }; 450 | // Go over each callback type in callbacks store object 451 | for (var callbackType in callbacks) { 452 | if (callbacks.hasOwnProperty(callbackType)) { 453 | // if this is the type user asked to delete, or he asked to delete all, go ahead. 454 | if (type === undefined || type === callbackType) { 455 | // If user asked to delete all callbacks in this type or all types 456 | if (callback === undefined) { 457 | callbacks[callbackType] = []; 458 | } else { 459 | // Remove all matching callbacks 460 | callbacks[callbackType] = callbacks[callbackType].filter(compareWithCallbackParameter); 461 | } 462 | } 463 | } 464 | } 465 | }, 466 | 467 | /** 468 | * Returns true if speech recognition is currently on. 469 | * Returns false if speech recognition is off or annyang is paused. 470 | * 471 | * @return boolean true = SpeechRecognition is on and annyang is listening 472 | * @method isListening 473 | */ 474 | isListening: function isListening() { 475 | return _isListening && !pauseListening; 476 | }, 477 | 478 | /** 479 | * Returns the instance of the browser's SpeechRecognition object used by annyang. 480 | * Useful in case you want direct access to the browser's Speech Recognition engine. 481 | * 482 | * @returns SpeechRecognition The browser's Speech Recognizer currently used by annyang 483 | * @method getSpeechRecognizer 484 | */ 485 | getSpeechRecognizer: function getSpeechRecognizer() { 486 | return recognition; 487 | }, 488 | 489 | /** 490 | * Simulate speech being recognized. This will trigger the same events and behavior as when the Speech Recognition 491 | * detects speech. 492 | * 493 | * Can accept either a string containing a single sentence or an array containing multiple sentences to be checked 494 | * in order until one of them matches a command (similar to the way Speech Recognition Alternatives are parsed) 495 | * 496 | * #### Examples: 497 | * ````javascript 498 | * annyang.trigger('Time for some thrilling heroics'); 499 | * annyang.trigger( 500 | * ['Time for some thrilling heroics', 'Time for some thrilling aerobics'] 501 | * ); 502 | * ```` 503 | * 504 | * @param string|array sentences A sentence as a string or an array of strings of possible sentences 505 | * @returns undefined 506 | * @method trigger 507 | */ 508 | trigger: function trigger(sentences) { 509 | if (!annyang.isListening()) { 510 | if (debugState) { 511 | if (!_isListening) { 512 | logMessage('Cannot trigger while annyang is aborted'); 513 | } else { 514 | logMessage('Speech heard, but annyang is paused'); 515 | } 516 | } 517 | return; 518 | } 519 | 520 | if (!Array.isArray(sentences)) { 521 | sentences = [sentences]; 522 | } 523 | 524 | parseResults(sentences); 525 | }, 526 | 527 | /** 528 | * Initialize annyang with a list of commands to recognize. 529 | * 530 | * #### Examples: 531 | * ````javascript 532 | * const commands = {'hello :name': helloFunction}; 533 | * const commands2 = {'hi': helloFunction}; 534 | * 535 | * // initialize annyang, overwriting any previously added commands 536 | * annyang.init(commands, true); 537 | * // adds an additional command without removing the previous commands 538 | * annyang.init(commands2, false); 539 | * ```` 540 | * 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. 541 | * 542 | * @param {Object} commands - Commands that annyang should listen to 543 | * @param {boolean} [resetCommands=true] - Remove all commands before initializing? 544 | * @method init 545 | * @deprecated 546 | * @see [Commands Object](#commands-object) 547 | */ 548 | init: function init(commands) { 549 | var resetCommands = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 550 | 551 | // Abort previous instances of recognition already running 552 | if (recognition && recognition.abort) { 553 | recognition.abort(); 554 | } 555 | 556 | // initiate SpeechRecognition 557 | recognition = new SpeechRecognition(); 558 | 559 | // Set the max number of alternative transcripts to try and match with a command 560 | recognition.maxAlternatives = 5; 561 | 562 | // In HTTPS, turn off continuous mode for faster results. 563 | // In HTTP, turn on continuous mode for much slower results, but no repeating security notices 564 | recognition.continuous = root.location.protocol === 'http:'; 565 | 566 | // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage() 567 | recognition.lang = 'en-US'; 568 | 569 | recognition.onstart = function () { 570 | _isListening = true; 571 | invokeCallbacks(callbacks.start); 572 | }; 573 | 574 | recognition.onsoundstart = function () { 575 | invokeCallbacks(callbacks.soundstart); 576 | }; 577 | 578 | recognition.onerror = function (event) { 579 | invokeCallbacks(callbacks.error, event); 580 | switch (event.error) { 581 | case 'network': 582 | invokeCallbacks(callbacks.errorNetwork, event); 583 | break; 584 | case 'not-allowed': 585 | case 'service-not-allowed': 586 | // if permission to use the mic is denied, turn off auto-restart 587 | autoRestart = false; 588 | // determine if permission was denied by user or automatically. 589 | if (new Date().getTime() - lastStartedAt < 200) { 590 | invokeCallbacks(callbacks.errorPermissionBlocked, event); 591 | } else { 592 | invokeCallbacks(callbacks.errorPermissionDenied, event); 593 | } 594 | break; 595 | } 596 | }; 597 | 598 | recognition.onend = function () { 599 | _isListening = false; 600 | invokeCallbacks(callbacks.end); 601 | // annyang will auto restart if it is closed automatically and not by user action. 602 | if (autoRestart) { 603 | // play nicely with the browser, and never restart annyang automatically more than once per second 604 | var timeSinceLastStart = new Date().getTime() - lastStartedAt; 605 | autoRestartCount += 1; 606 | if (autoRestartCount % 10 === 0) { 607 | if (debugState) { 608 | logMessage('Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips.'); 609 | } 610 | } 611 | if (timeSinceLastStart < 1000) { 612 | setTimeout(function () { 613 | annyang.start({ paused: pauseListening }); 614 | }, 1000 - timeSinceLastStart); 615 | } else { 616 | annyang.start({ paused: pauseListening }); 617 | } 618 | } 619 | }; 620 | 621 | recognition.onresult = function (event) { 622 | if (pauseListening) { 623 | if (debugState) { 624 | logMessage('Speech heard, but annyang is paused'); 625 | } 626 | return false; 627 | } 628 | 629 | // Map the results to an array 630 | var SpeechRecognitionResult = event.results[event.resultIndex]; 631 | var results = []; 632 | for (var k = 0; k < SpeechRecognitionResult.length; k++) { 633 | results[k] = SpeechRecognitionResult[k].transcript; 634 | } 635 | 636 | parseResults(results); 637 | }; 638 | 639 | // build commands list 640 | if (resetCommands) { 641 | commandsList = []; 642 | } 643 | if (commands.length) { 644 | this.addCommands(commands); 645 | } 646 | } 647 | }; 648 | 649 | return annyang; 650 | }); 651 | 652 | /** 653 | * # Good to Know 654 | * 655 | * ## Commands Object 656 | * 657 | * Both the [init()]() and addCommands() methods receive a `commands` object. 658 | * 659 | * annyang understands commands with `named variables`, `splats`, and `optional words`. 660 | * 661 | * * Use `named variables` for one-word arguments in your command. 662 | * * Use `splats` to capture multi-word text at the end of your command (greedy). 663 | * * Use `optional words` or phrases to define a part of the command as optional. 664 | * 665 | * #### Examples: 666 | * ````html 667 | * 695 | * ```` 696 | * 697 | * ### Using Regular Expressions in commands 698 | * For advanced commands, you can pass a regular expression object, instead of 699 | * a simple string command. 700 | * 701 | * This is done by passing an object containing two properties: `regexp`, and 702 | * `callback` instead of the function. 703 | * 704 | * #### Examples: 705 | * ````javascript 706 | * const calculateFunction = function(month) { console.log(month); } 707 | * const commands = { 708 | * // This example will accept any word as the "month" 709 | * 'calculate :month stats': calculateFunction, 710 | * // This example will only accept months which are at the start of a quarter 711 | * 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction} 712 | * } 713 | ```` 714 | * 715 | */ 716 | //# sourceMappingURL=annyang.js.map 717 | -------------------------------------------------------------------------------- /dist/annyang.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/annyang.js"],"names":["root","factory","define","amd","annyang","module","exports","window","undefined","SpeechRecognition","webkitSpeechRecognition","mozSpeechRecognition","msSpeechRecognition","oSpeechRecognition","commandsList","recognition","callbacks","start","error","end","soundstart","result","resultMatch","resultNoMatch","errorNetwork","errorPermissionBlocked","errorPermissionDenied","autoRestart","lastStartedAt","autoRestartCount","debugState","debugStyle","pauseListening","isListening","optionalParam","optionalRegex","namedParam","splatParam","escapeRegExp","commandToRegExp","command","replace","match","optional","RegExp","invokeCallbacks","args","forEach","callback","apply","context","isInitialized","logMessage","text","extraParameters","indexOf","console","log","initIfNeeded","init","registerCommand","originalPhrase","push","parseResults","results","commandText","i","length","trim","j","l","currentCommand","exec","parameters","slice","addCommands","commands","cb","phrase","hasOwnProperty","regexp","source","options","paused","continuous","Date","getTime","e","message","abort","pause","resume","debug","newState","setLanguage","language","lang","removeCommands","commandsToRemove","Array","isArray","filter","addCallback","type","removeCallback","compareWithCallbackParameter","callbackType","getSpeechRecognizer","trigger","sentences","resetCommands","maxAlternatives","location","protocol","onstart","onsoundstart","onerror","event","onend","timeSinceLastStart","setTimeout","onresult","SpeechRecognitionResult","resultIndex","k","transcript"],"mappings":"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","file":"annyang.js","sourcesContent":["//! annyang\n//! version : 2.6.1\n//! author : Tal Ater @TalAter\n//! license : MIT\n//! https://www.TalAter.com/annyang/\n(function(root, factory) {\n 'use strict';\n if (typeof define === 'function' && define.amd) { // AMD + global\n define([], function () {\n return (root.annyang = factory(root));\n });\n } else if (typeof module === 'object' && module.exports) { // CommonJS\n module.exports = factory(root);\n } else { // Browser globals\n root.annyang = factory(root);\n }\n})(typeof window !== 'undefined' ? window : this, function(root, undefined) {\n 'use strict';\n\n /**\n * # Quick Tutorial, Intro, and Demos\n *\n * The quickest way to get started is to visit the [annyang homepage](https://www.talater.com/annyang/).\n *\n * For a more in-depth look at annyang, read on.\n *\n * # API Reference\n */\n\n var annyang;\n\n // Get the SpeechRecognition object, while handling browser prefixes\n var SpeechRecognition = root.SpeechRecognition ||\n root.webkitSpeechRecognition ||\n root.mozSpeechRecognition ||\n root.msSpeechRecognition ||\n root.oSpeechRecognition;\n\n // Check browser support\n // This is done as early as possible, to make it as fast as possible for unsupported browsers\n if (!SpeechRecognition) {\n return null;\n }\n\n var commandsList = [];\n var recognition;\n var callbacks = {\n start: [],\n error: [],\n end: [],\n soundstart: [],\n result: [],\n resultMatch: [],\n resultNoMatch: [],\n errorNetwork: [],\n errorPermissionBlocked: [],\n errorPermissionDenied: [],\n };\n var autoRestart;\n var lastStartedAt = 0;\n var autoRestartCount = 0;\n var debugState = false;\n var debugStyle = 'font-weight: bold; color: #00f;';\n var pauseListening = false;\n var isListening = false;\n\n // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.\n var optionalParam = /\\s*\\((.*?)\\)\\s*/g;\n var optionalRegex = /(\\(\\?:[^)]+\\))\\?/g;\n var namedParam = /(\\(\\?)?:\\w+/g;\n var splatParam = /\\*\\w+/g;\n var escapeRegExp = /[-{}[\\]+?.,\\\\^$|#]/g;\n var commandToRegExp = function(command) {\n command = command\n .replace(escapeRegExp, '\\\\$&')\n .replace(optionalParam, '(?:$1)?')\n .replace(namedParam, function(match, optional) {\n return optional ? match : '([^\\\\s]+)';\n })\n .replace(splatParam, '(.*?)')\n .replace(optionalRegex, '\\\\s*$1?\\\\s*');\n return new RegExp('^' + command + '$', 'i');\n };\n\n // This method receives an array of callbacks to iterate over, and invokes each of them\n var invokeCallbacks = function(callbacks, ...args) {\n callbacks.forEach(function(callback) {\n callback.callback.apply(callback.context, args);\n });\n };\n\n var isInitialized = function() {\n return recognition !== undefined;\n };\n\n // method for logging in developer console when debug mode is on\n var logMessage = function(text, extraParameters) {\n if (text.indexOf('%c') === -1 && !extraParameters) {\n console.log(text);\n } else {\n console.log(text, extraParameters || debugStyle);\n }\n };\n\n var initIfNeeded = function() {\n if (!isInitialized()) {\n annyang.init({}, false);\n }\n };\n\n var registerCommand = function(command, callback, originalPhrase) {\n commandsList.push({ command, callback, originalPhrase });\n if (debugState) {\n logMessage(\n 'Command successfully loaded: %c' + originalPhrase,\n debugStyle\n );\n }\n };\n\n var parseResults = function(results) {\n invokeCallbacks(callbacks.result, results);\n var commandText;\n // go over each of the 5 results and alternative results received (we have set maxAlternatives to 5 above)\n for (let i = 0; i < results.length; i++) {\n // the text recognized\n commandText = results[i].trim();\n if (debugState) {\n logMessage('Speech recognized: %c' + commandText, debugStyle);\n }\n\n // try and match recognized text to one of the commands on the list\n for (let j = 0, l = commandsList.length; j < l; j++) {\n var currentCommand = commandsList[j];\n var result = currentCommand.command.exec(commandText);\n if (result) {\n var parameters = result.slice(1);\n if (debugState) {\n logMessage(\n 'command matched: %c' + currentCommand.originalPhrase,\n debugStyle\n );\n if (parameters.length) {\n logMessage('with parameters', parameters);\n }\n }\n // execute the matched command\n currentCommand.callback.apply(this, parameters);\n invokeCallbacks(\n callbacks.resultMatch,\n commandText,\n currentCommand.originalPhrase,\n results\n );\n return;\n }\n }\n }\n invokeCallbacks(callbacks.resultNoMatch, results);\n };\n\n annyang = {\n /**\n * Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands.\n *\n * #### Examples:\n * ````javascript\n * const commands = {'hello :name': helloFunction, 'howdy': helloFunction};\n * const commands2 = {'hi': helloFunction};\n *\n * annyang.addCommands(commands);\n * annyang.addCommands(commands2);\n * // annyang will now listen to all three commands\n * ````\n *\n * @param {Object} commands - Commands that annyang should listen to\n * @method addCommands\n * @see [Commands Object](#commands-object)\n */\n addCommands: function(commands) {\n var cb;\n\n initIfNeeded();\n\n for (let phrase in commands) {\n if (commands.hasOwnProperty(phrase)) {\n cb = root[commands[phrase]] || commands[phrase];\n if (typeof cb === 'function') {\n // convert command to regex then register the command\n registerCommand(commandToRegExp(phrase), cb, phrase);\n } else if (typeof cb === 'object' && cb.regexp instanceof RegExp) {\n // register the command\n registerCommand(\n new RegExp(cb.regexp.source, 'i'),\n cb.callback,\n phrase\n );\n } else {\n if (debugState) {\n logMessage('Can not register command: %c' + phrase, debugStyle);\n }\n continue;\n }\n }\n }\n },\n\n /**\n * Start listening.\n * It's a good idea to call this after adding some commands first, but not mandatory.\n *\n * Receives an optional options object which supports the following options:\n *\n * - `autoRestart` (boolean) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts?\n * - `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.\n * - `paused` (boolean) Start annyang in paused mode.\n *\n * #### Examples:\n * ````javascript\n * // Start listening, don't restart automatically\n * annyang.start({ autoRestart: false });\n * // Start listening, don't restart automatically, stop recognition after first phrase recognized\n * annyang.start({ autoRestart: false, continuous: false });\n * ````\n * @param {Object} [options] - Optional options.\n * @method start\n */\n start: function(options) {\n initIfNeeded();\n options = options || {};\n if (options.paused !== undefined) {\n pauseListening = !!options.paused;\n } else {\n pauseListening = false;\n }\n if (options.autoRestart !== undefined) {\n autoRestart = !!options.autoRestart;\n } else {\n autoRestart = true;\n }\n if (options.continuous !== undefined) {\n recognition.continuous = !!options.continuous;\n }\n\n lastStartedAt = new Date().getTime();\n try {\n recognition.start();\n } catch (e) {\n if (debugState) {\n logMessage(e.message);\n }\n }\n },\n\n /**\n * Stop listening, and turn off mic.\n *\n * Alternatively, to only temporarily pause annyang responding to commands without stopping the SpeechRecognition engine or closing the mic, use pause() instead.\n * @see [pause()](#pause)\n *\n * @method abort\n */\n abort: function() {\n autoRestart = false;\n autoRestartCount = 0;\n if (isInitialized()) {\n recognition.abort();\n }\n },\n\n /**\n * 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.\n *\n * Alternatively, to stop the SpeechRecognition engine and close the mic, use abort() instead.\n * @see [abort()](#abort)\n *\n * @method pause\n */\n pause: function() {\n pauseListening = true;\n },\n\n /**\n * Resumes listening and restore command callback execution when a result matches.\n * If SpeechRecognition was aborted (stopped), start it.\n *\n * @method resume\n */\n resume: function() {\n annyang.start();\n },\n\n /**\n * Turn on the output of debug messages to the console. Ugly, but super-handy!\n *\n * @param {boolean} [newState=true] - Turn on/off debug messages\n * @method debug\n */\n debug: function(newState = true) {\n debugState = !!newState;\n },\n\n /**\n * Set the language the user will speak in. If this method is not called, defaults to 'en-US'.\n *\n * @param {String} language - The language (locale)\n * @method setLanguage\n * @see [Languages](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md#what-languages-are-supported)\n */\n setLanguage: function(language) {\n initIfNeeded();\n recognition.lang = language;\n },\n\n /**\n * Remove existing commands. Called with a single phrase, an array of phrases, or methodically. Pass no params to remove all commands.\n *\n * #### Examples:\n * ````javascript\n * const commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction};\n *\n * // Remove all existing commands\n * annyang.removeCommands();\n *\n * // Add some commands\n * annyang.addCommands(commands);\n *\n * // Don't respond to hello\n * annyang.removeCommands('hello');\n *\n * // Don't respond to howdy or hi\n * annyang.removeCommands(['howdy', 'hi']);\n * ````\n * @param {String|Array|Undefined} [commandsToRemove] - Commands to remove\n * @method removeCommands\n */\n removeCommands: function(commandsToRemove) {\n if (commandsToRemove === undefined) {\n commandsList = [];\n } else {\n commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove];\n commandsList = commandsList.filter(command => {\n for (let i = 0; i < commandsToRemove.length; i++) {\n if (commandsToRemove[i] === command.originalPhrase) {\n return false;\n }\n }\n return true;\n });\n }\n },\n\n /**\n * Add a callback function to be called in case one of the following events happens:\n *\n * * `start` - Fired as soon as the browser's Speech Recognition engine starts listening.\n *\n * * `soundstart` - Fired as soon as any sound (possibly speech) has been detected.\n *\n * This will fire once per Speech Recognition starting. See https://is.gd/annyang_sound_start.\n *\n * * `error` - Fired when the browser's Speech Recognition engine returns an error, this generic error callback will be followed by more accurate error callbacks (both will fire if both are defined).\n *\n * The Callback function will be called with the error event as the first argument.\n *\n * * `errorNetwork` - Fired when Speech Recognition fails because of a network error.\n *\n * The Callback function will be called with the error event as the first argument.\n *\n * * `errorPermissionBlocked` - Fired when the browser blocks the permission request to use Speech Recognition.\n *\n * The Callback function will be called with the error event as the first argument.\n *\n * * `errorPermissionDenied` - Fired when the user blocks the permission request to use Speech Recognition.\n *\n * The Callback function will be called with the error event as the first argument.\n *\n * * `end` - Fired when the browser's Speech Recognition engine stops.\n *\n * * `result` - Fired as soon as some speech was identified. This generic callback will be followed by either the `resultMatch` or `resultNoMatch` callbacks.\n *\n * The Callback functions for this event will be called with an array of possible phrases the user said as the first argument.\n *\n * * `resultMatch` - Fired when annyang was able to match between what the user said and a registered command.\n *\n * The Callback functions for this event will be called with three arguments in the following order:\n *\n * * The phrase the user said that matched a command.\n * * The command that was matched.\n * * An array of possible alternative phrases the user might have said.\n *\n * * `resultNoMatch` - Fired when what the user said didn't match any of the registered commands.\n *\n * Callback functions for this event will be called with an array of possible phrases the user might have said as the first argument.\n *\n * #### Examples:\n * ````javascript\n * annyang.addCallback('error', function() {\n * $('.myErrorText').text('There was an error!');\n * });\n *\n * annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) {\n * console.log(userSaid); // sample output: 'hello'\n * console.log(commandText); // sample output: 'hello (there)'\n * console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty']\n * });\n *\n * // pass local context to a global function called notConnected\n * annyang.addCallback('errorNetwork', notConnected, this);\n * ````\n * @param {String} type - Name of event that will trigger this callback\n * @param {Function} callback - The function to call when event is triggered\n * @param {Object} [context] - Optional context for the callback function\n * @method addCallback\n */\n addCallback: function(type, callback, context) {\n var cb = root[callback] || callback;\n if (typeof cb === 'function' && callbacks[type] !== undefined) {\n callbacks[type].push({ callback: cb, context: context || this });\n }\n },\n\n /**\n * Remove callbacks from events.\n *\n * - Pass an event name and a callback command to remove that callback command from that event type.\n * - Pass just an event name to remove all callback commands from that event type.\n * - Pass undefined as event name and a callback command to remove that callback command from all event types.\n * - Pass no params to remove all callback commands from all event types.\n *\n * #### Examples:\n * ````javascript\n * annyang.addCallback('start', myFunction1);\n * annyang.addCallback('start', myFunction2);\n * annyang.addCallback('end', myFunction1);\n * annyang.addCallback('end', myFunction2);\n *\n * // Remove all callbacks from all events:\n * annyang.removeCallback();\n *\n * // Remove all callbacks attached to end event:\n * annyang.removeCallback('end');\n *\n * // Remove myFunction2 from being called on start:\n * annyang.removeCallback('start', myFunction2);\n *\n * // Remove myFunction1 from being called on all events:\n * annyang.removeCallback(undefined, myFunction1);\n * ````\n *\n * @param type Name of event type to remove callback from\n * @param callback The callback function to remove\n * @returns undefined\n * @method removeCallback\n */\n removeCallback: function(type, callback) {\n var compareWithCallbackParameter = function(cb) {\n return cb.callback !== callback;\n };\n // Go over each callback type in callbacks store object\n for (let callbackType in callbacks) {\n if (callbacks.hasOwnProperty(callbackType)) {\n // if this is the type user asked to delete, or he asked to delete all, go ahead.\n if (type === undefined || type === callbackType) {\n // If user asked to delete all callbacks in this type or all types\n if (callback === undefined) {\n callbacks[callbackType] = [];\n } else {\n // Remove all matching callbacks\n callbacks[callbackType] = callbacks[callbackType].filter(compareWithCallbackParameter);\n }\n }\n }\n }\n },\n\n /**\n * Returns true if speech recognition is currently on.\n * Returns false if speech recognition is off or annyang is paused.\n *\n * @return boolean true = SpeechRecognition is on and annyang is listening\n * @method isListening\n */\n isListening: function() {\n return isListening && !pauseListening;\n },\n\n /**\n * Returns the instance of the browser's SpeechRecognition object used by annyang.\n * Useful in case you want direct access to the browser's Speech Recognition engine.\n *\n * @returns SpeechRecognition The browser's Speech Recognizer currently used by annyang\n * @method getSpeechRecognizer\n */\n getSpeechRecognizer: function() {\n return recognition;\n },\n\n /**\n * Simulate speech being recognized. This will trigger the same events and behavior as when the Speech Recognition\n * detects speech.\n *\n * Can accept either a string containing a single sentence or an array containing multiple sentences to be checked\n * in order until one of them matches a command (similar to the way Speech Recognition Alternatives are parsed)\n *\n * #### Examples:\n * ````javascript\n * annyang.trigger('Time for some thrilling heroics');\n * annyang.trigger(\n * ['Time for some thrilling heroics', 'Time for some thrilling aerobics']\n * );\n * ````\n *\n * @param string|array sentences A sentence as a string or an array of strings of possible sentences\n * @returns undefined\n * @method trigger\n */\n trigger: function(sentences) {\n if (!annyang.isListening()) {\n if (debugState) {\n if (!isListening) {\n logMessage('Cannot trigger while annyang is aborted');\n } else {\n logMessage('Speech heard, but annyang is paused');\n }\n }\n return;\n }\n\n if (!Array.isArray(sentences)) {\n sentences = [sentences];\n }\n\n parseResults(sentences);\n },\n\n /**\n * Initialize annyang with a list of commands to recognize.\n *\n * #### Examples:\n * ````javascript\n * const commands = {'hello :name': helloFunction};\n * const commands2 = {'hi': helloFunction};\n *\n * // initialize annyang, overwriting any previously added commands\n * annyang.init(commands, true);\n * // adds an additional command without removing the previous commands\n * annyang.init(commands2, false);\n * ````\n * 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.\n *\n * @param {Object} commands - Commands that annyang should listen to\n * @param {boolean} [resetCommands=true] - Remove all commands before initializing?\n * @method init\n * @deprecated\n * @see [Commands Object](#commands-object)\n */\n init: function(commands, resetCommands = true) {\n // Abort previous instances of recognition already running\n if (recognition && recognition.abort) {\n recognition.abort();\n }\n\n // initiate SpeechRecognition\n recognition = new SpeechRecognition();\n\n // Set the max number of alternative transcripts to try and match with a command\n recognition.maxAlternatives = 5;\n\n // In HTTPS, turn off continuous mode for faster results.\n // In HTTP, turn on continuous mode for much slower results, but no repeating security notices\n recognition.continuous = root.location.protocol === 'http:';\n\n // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage()\n recognition.lang = 'en-US';\n\n recognition.onstart = function() {\n isListening = true;\n invokeCallbacks(callbacks.start);\n };\n\n recognition.onsoundstart = function() {\n invokeCallbacks(callbacks.soundstart);\n };\n\n recognition.onerror = function(event) {\n invokeCallbacks(callbacks.error, event);\n switch (event.error) {\n case 'network':\n invokeCallbacks(callbacks.errorNetwork, event);\n break;\n case 'not-allowed':\n case 'service-not-allowed':\n // if permission to use the mic is denied, turn off auto-restart\n autoRestart = false;\n // determine if permission was denied by user or automatically.\n if (new Date().getTime() - lastStartedAt < 200) {\n invokeCallbacks(callbacks.errorPermissionBlocked, event);\n } else {\n invokeCallbacks(callbacks.errorPermissionDenied, event);\n }\n break;\n }\n };\n\n recognition.onend = function() {\n isListening = false;\n invokeCallbacks(callbacks.end);\n // annyang will auto restart if it is closed automatically and not by user action.\n if (autoRestart) {\n // play nicely with the browser, and never restart annyang automatically more than once per second\n var timeSinceLastStart = new Date().getTime() - lastStartedAt;\n autoRestartCount += 1;\n if (autoRestartCount % 10 === 0) {\n if (debugState) {\n logMessage(\n 'Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips.'\n );\n }\n }\n if (timeSinceLastStart < 1000) {\n setTimeout(function() {\n annyang.start({ paused: pauseListening });\n }, 1000 - timeSinceLastStart);\n } else {\n annyang.start({ paused: pauseListening });\n }\n }\n };\n\n recognition.onresult = function(event) {\n if (pauseListening) {\n if (debugState) {\n logMessage('Speech heard, but annyang is paused');\n }\n return false;\n }\n\n // Map the results to an array\n var SpeechRecognitionResult = event.results[event.resultIndex];\n var results = [];\n for (let k = 0; k < SpeechRecognitionResult.length; k++) {\n results[k] = SpeechRecognitionResult[k].transcript;\n }\n\n parseResults(results);\n };\n\n // build commands list\n if (resetCommands) {\n commandsList = [];\n }\n if (commands.length) {\n this.addCommands(commands);\n }\n },\n };\n\n return annyang;\n});\n\n/**\n * # Good to Know\n *\n * ## Commands Object\n *\n * Both the [init()]() and addCommands() methods receive a `commands` object.\n *\n * annyang understands commands with `named variables`, `splats`, and `optional words`.\n *\n * * Use `named variables` for one-word arguments in your command.\n * * Use `splats` to capture multi-word text at the end of your command (greedy).\n * * Use `optional words` or phrases to define a part of the command as optional.\n *\n * #### Examples:\n * ````html\n * \n * ````\n *\n * ### Using Regular Expressions in commands\n * For advanced commands, you can pass a regular expression object, instead of\n * a simple string command.\n *\n * This is done by passing an object containing two properties: `regexp`, and\n * `callback` instead of the function.\n *\n * #### Examples:\n * ````javascript\n * const calculateFunction = function(month) { console.log(month); }\n * const commands = {\n * // This example will accept any word as the \"month\"\n * 'calculate :month stats': calculateFunction,\n * // This example will only accept months which are at the start of a quarter\n * 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction}\n * }\n ````\n *\n */\n"]} -------------------------------------------------------------------------------- /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.1 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(r,i){var o,a=r.SpeechRecognition||r.webkitSpeechRecognition||r.mozSpeechRecognition||r.msSpeechRecognition||r.oSpeechRecognition;if(!a)return null;function s(e){for(var n=arguments.length,t=Array(1 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 | ## addCommands(commands) 14 | 15 | Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands. 16 | 17 | #### Examples: 18 | ````javascript 19 | const commands = {'hello :name': helloFunction, 'howdy': helloFunction}; 20 | const commands2 = {'hi': helloFunction}; 21 | 22 | annyang.addCommands(commands); 23 | annyang.addCommands(commands2); 24 | // annyang will now listen for all three commands 25 | ```` 26 | 27 | See: [Commands Object](#commands-object) 28 | 29 | ### Params: 30 | 31 | * **Object** *commands* - Commands that annyang should listen for 32 | 33 | ## start([options]) 34 | 35 | Start listening. 36 | It's a good idea to call this after adding some commands first (but not mandatory) 37 | 38 | Receives an optional options object which supports the following options: 39 | 40 | - `autoRestart` (boolean) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts? 41 | - `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. 42 | - `paused` (boolean) Start annyang in paused mode. 43 | 44 | #### Examples: 45 | ````javascript 46 | // Start listening, don't restart automatically 47 | annyang.start({ autoRestart: false }); 48 | // Start listening, don't restart automatically, stop recognition after first phrase recognized 49 | annyang.start({ autoRestart: false, continuous: false }); 50 | ```` 51 | 52 | ### Params: 53 | 54 | * **Object** *[options]* - Optional options. 55 | 56 | ## abort() 57 | 58 | Stop listening, and turn off mic. 59 | 60 | Alternatively, to only temporarily pause annyang responding to commands without stopping the SpeechRecognition engine or closing the mic, use pause() instead. 61 | 62 | See: [pause()](#pause) 63 | 64 | ## pause() 65 | 66 | 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. 67 | 68 | Alternatively, to stop the SpeechRecognition engine and close the mic, use abort() instead. 69 | 70 | See: [abort()](#abort) 71 | 72 | ## resume() 73 | 74 | Resumes listening and restore command callback execution when a command is matched. 75 | If SpeechRecognition was aborted (stopped), start it. 76 | 77 | ## debug([newState=true]) 78 | 79 | Turn on the output of debug messages to the console. Ugly, but super-handy! 80 | 81 | ### Params: 82 | 83 | * **boolean** *[newState=true]* - Turn on/off debug messages 84 | 85 | ## setLanguage(language) 86 | 87 | Set the language the user will speak in. If this method is not called, defaults to 'en-US'. 88 | 89 | See: [Languages](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md#what-languages-are-supported) 90 | 91 | ### Params: 92 | 93 | * **String** *language* - The language (locale) 94 | 95 | ## removeCommands([commandsToRemove]) 96 | 97 | Remove existing commands. Called with a single phrase, an array of phrases, or methodically. Pass no params to remove all commands. 98 | 99 | #### Examples: 100 | ````javascript 101 | const commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction}; 102 | 103 | // Remove all existing commands 104 | annyang.removeCommands(); 105 | 106 | // Add some commands 107 | annyang.addCommands(commands); 108 | 109 | // Don't respond to hello 110 | annyang.removeCommands('hello'); 111 | 112 | // Don't respond to howdy or hi 113 | annyang.removeCommands(['howdy', 'hi']); 114 | ```` 115 | 116 | ### Params: 117 | 118 | * **String|Array|Undefined** *[commandsToRemove]* - Commands to remove 119 | 120 | ## addCallback(type, callback, [context]) 121 | 122 | Add a callback function to be called in case one of the following events happens: 123 | 124 | * `start` - Fired as soon as the browser's Speech Recognition engine starts listening. 125 | 126 | * `soundstart` - Fired as soon as any sound (possibly speech) has been detected. 127 | 128 | This will fire once per Speech Recognition starting. See https://is.gd/annyang_sound_start. 129 | 130 | * `error` - Fired when the browser's Speech Recognition engine returns an error, this generic error callback will be followed by more accurate error callbacks (both will fire if both are defined). 131 | 132 | The Callback function will be called with the error event as the first argument. 133 | 134 | * `errorNetwork` - Fired when Speech Recognition fails because of a network error. 135 | 136 | The Callback function will be called with the error event as the first argument. 137 | 138 | * `errorPermissionBlocked` - Fired when the browser blocks the permission request to use Speech Recognition. 139 | 140 | The Callback function will be called with the error event as the first argument. 141 | 142 | * `errorPermissionDenied` - Fired when the user blocks the permission request to use Speech Recognition. 143 | 144 | The Callback function will be called with the error event as the first argument. 145 | 146 | * `end` - Fired when the browser's Speech Recognition engine stops. 147 | 148 | * `result` - Fired as soon as some speech was identified. This generic callback will be followed by either the `resultMatch` or `resultNoMatch` callbacks. 149 | 150 | The Callback functions for this event will be called with an array of possible phrases the user said as the first argument. 151 | 152 | * `resultMatch` - Fired when annyang was able to match between what the user said and a registered command. 153 | 154 | The Callback functions for this event will be called with three arguments in the following order: 155 | 156 | * The phrase the user said that matched a command. 157 | * The command that was matched. 158 | * An array of possible alternative phrases the user might have said. 159 | 160 | * `resultNoMatch` - Fired when what the user said didn't match any of the registered commands. 161 | 162 | Callback functions for this event will be called with an array of possible phrases the user might have said as the first argument. 163 | 164 | #### Examples: 165 | ````javascript 166 | annyang.addCallback('error', function() { 167 | $('.myErrorText').text('There was an error!'); 168 | }); 169 | 170 | annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) { 171 | console.log(userSaid); // sample output: 'hello' 172 | console.log(commandText); // sample output: 'hello (there)' 173 | console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty'] 174 | }); 175 | 176 | // pass local context to a global function called notConnected 177 | annyang.addCallback('errorNetwork', notConnected, this); 178 | ```` 179 | 180 | ### Params: 181 | 182 | * **String** *type* - Name of event that will trigger this callback 183 | * **Function** *callback* - The function to call when event is triggered 184 | * **Object** *[context]* - Optional context for the callback function 185 | 186 | ## removeCallback(type, callback) 187 | 188 | Remove callbacks from events. 189 | 190 | - Pass an event name and a callback command to remove that callback command from that event type. 191 | - Pass just an event name to remove all callback commands from that event type. 192 | - Pass undefined as event name and a callback command to remove that callback command from all event types. 193 | - Pass no params to remove all callback commands from all event types. 194 | 195 | #### Examples: 196 | ````javascript 197 | annyang.addCallback('start', myFunction1); 198 | annyang.addCallback('start', myFunction2); 199 | annyang.addCallback('end', myFunction1); 200 | annyang.addCallback('end', myFunction2); 201 | 202 | // Remove all callbacks from all events: 203 | annyang.removeCallback(); 204 | 205 | // Remove all callbacks attached to end event: 206 | annyang.removeCallback('end'); 207 | 208 | // Remove myFunction2 from being called on start: 209 | annyang.removeCallback('start', myFunction2); 210 | 211 | // Remove myFunction1 from being called on all events: 212 | annyang.removeCallback(undefined, myFunction1); 213 | ```` 214 | 215 | ### Params: 216 | 217 | * *type* Name of event type to remove callback from 218 | * *callback* The callback function to remove 219 | 220 | ### Return: 221 | 222 | * undefined 223 | 224 | ## isListening() 225 | 226 | Returns true if speech recognition is currently on. 227 | Returns false if speech recognition is off or annyang is paused. 228 | 229 | ### Return: 230 | 231 | * boolean true = SpeechRecognition is on and annyang is listening 232 | 233 | ## getSpeechRecognizer() 234 | 235 | Returns the instance of the browser's SpeechRecognition object used by annyang. 236 | Useful in case you want direct access to the browser's Speech Recognition engine. 237 | 238 | ### Return: 239 | 240 | * SpeechRecognition The browser's Speech Recognizer currently used by annyang 241 | 242 | ## trigger(string|array) 243 | 244 | Simulate speech being recognized. This will trigger the same events and behavior as when the Speech Recognition 245 | detects speech. 246 | 247 | Can accept either a string containing a single sentence or an array containing multiple sentences to be checked 248 | in order until one of them matches a command (similar to the way Speech Recognition Alternatives are parsed) 249 | 250 | #### Examples: 251 | ````javascript 252 | annyang.trigger('Time for some thrilling heroics'); 253 | annyang.trigger( 254 | ['Time for some thrilling heroics', 'Time for some thrilling aerobics'] 255 | ); 256 | ```` 257 | 258 | ### Params: 259 | 260 | * *string|array* sentences A sentence as a string or an array of strings of possible sentences 261 | 262 | ### Return: 263 | 264 | * undefined 265 | 266 | ## init(commands, [resetCommands=true]) 267 | 268 | Initialize annyang with a list of commands to recognize. 269 | 270 | #### Examples: 271 | ````javascript 272 | const commands = {'hello :name': helloFunction}; 273 | const commands2 = {'hi': helloFunction}; 274 | 275 | // initialize annyang, overwriting any previously added commands 276 | annyang.init(commands, true); 277 | // adds an additional command without removing the previous commands 278 | annyang.init(commands2, false); 279 | ```` 280 | 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. 281 | 282 | **Deprecated** 283 | 284 | See: [Commands Object](#commands-object) 285 | 286 | ### Params: 287 | 288 | * **Object** *commands* - Commands that annyang should listen to 289 | * **boolean** *[resetCommands=true]* - Remove all commands before initializing? 290 | 291 | # Good to Know 292 | 293 | ## Commands Object 294 | 295 | Both the [init()]() and addCommands() methods receive a `commands` object. 296 | 297 | annyang understands commands with `named variables`, `splats`, and `optional words`. 298 | 299 | * Use `named variables` for one-word arguments in your command. 300 | * Use `splats` to capture multi-word text at the end of your command (greedy). 301 | * Use `optional words` or phrases to define a part of the command as optional. 302 | 303 | #### Examples: 304 | ````html 305 | 333 | ```` 334 | 335 | ### Using Regular Expressions in commands 336 | For advanced commands, you can pass a regular expression object, instead of 337 | a simple string command. 338 | 339 | This is done by passing an object containing two properties: `regexp`, and 340 | `callback` instead of the function. 341 | 342 | #### Examples: 343 | ````javascript 344 | const calculateFunction = function(month) { console.log(month); } 345 | const commands = { 346 | // This example will accept any word as the "month" 347 | 'calculate :month stats': calculateFunction, 348 | // This example will only accept months which are at the start of a quarter 349 | 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction} 350 | } 351 | ```` 352 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "annyang", 3 | "version": "2.6.1", 4 | "description": "A javascript library for adding voice commands to your site, using speech recognition", 5 | "keywords": [ 6 | "annyang", 7 | "annyang.js", 8 | "recognition", 9 | "speech", 10 | "speechrecognition", 11 | "voice", 12 | "webkitspeechrecognition" 13 | ], 14 | "homepage": "https://www.talater.com/annyang/", 15 | "bugs": { 16 | "url": "https://github.com/TalAter/annyang/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/TalAter/annyang.git" 21 | }, 22 | "license": "MIT", 23 | "author": "Tal Ater (https://www.talater.com/)", 24 | "main": "dist/annyang.min.js", 25 | "scripts": { 26 | "start": "grunt dev", 27 | "test": "grunt test" 28 | }, 29 | "dependencies": {}, 30 | "devDependencies": { 31 | "async": "^2.6.1", 32 | "babel-core": "^6.26.3", 33 | "babel-preset-env": "^1.7.0", 34 | "grunt": "^0.4.5", 35 | "grunt-babel": "^7.0.0", 36 | "grunt-cli": "^1.3.2", 37 | "grunt-contrib-connect": "^2.1.0", 38 | "grunt-contrib-cssmin": "^3.0.0", 39 | "grunt-contrib-imagemin": "^3.1.0", 40 | "grunt-contrib-jasmine": "2.1.0", 41 | "grunt-contrib-jshint": "^2.1.0", 42 | "grunt-contrib-uglify": "^4.0.1", 43 | "grunt-contrib-watch": "^1.1.0", 44 | "grunt-markdox": "^1.2.1", 45 | "grunt-template-jasmine-istanbul": "^0.5.0", 46 | "grunt-template-jasmine-requirejs": "^0.2.3", 47 | "load-grunt-tasks": "^4.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/annyang.js: -------------------------------------------------------------------------------- 1 | //! annyang 2 | //! version : 2.6.1 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 = { 48 | start: [], 49 | error: [], 50 | end: [], 51 | soundstart: [], 52 | result: [], 53 | resultMatch: [], 54 | resultNoMatch: [], 55 | errorNetwork: [], 56 | errorPermissionBlocked: [], 57 | errorPermissionDenied: [], 58 | }; 59 | var autoRestart; 60 | var lastStartedAt = 0; 61 | var autoRestartCount = 0; 62 | var debugState = false; 63 | var debugStyle = 'font-weight: bold; color: #00f;'; 64 | var pauseListening = false; 65 | var isListening = false; 66 | 67 | // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license. 68 | var optionalParam = /\s*\((.*?)\)\s*/g; 69 | var optionalRegex = /(\(\?:[^)]+\))\?/g; 70 | var namedParam = /(\(\?)?:\w+/g; 71 | var splatParam = /\*\w+/g; 72 | var escapeRegExp = /[-{}[\]+?.,\\^$|#]/g; 73 | var commandToRegExp = function(command) { 74 | command = command 75 | .replace(escapeRegExp, '\\$&') 76 | .replace(optionalParam, '(?:$1)?') 77 | .replace(namedParam, function(match, optional) { 78 | return optional ? match : '([^\\s]+)'; 79 | }) 80 | .replace(splatParam, '(.*?)') 81 | .replace(optionalRegex, '\\s*$1?\\s*'); 82 | return new RegExp('^' + command + '$', 'i'); 83 | }; 84 | 85 | // This method receives an array of callbacks to iterate over, and invokes each of them 86 | var invokeCallbacks = function(callbacks, ...args) { 87 | callbacks.forEach(function(callback) { 88 | callback.callback.apply(callback.context, args); 89 | }); 90 | }; 91 | 92 | var isInitialized = function() { 93 | return recognition !== undefined; 94 | }; 95 | 96 | // method for logging in developer console when debug mode is on 97 | var logMessage = function(text, extraParameters) { 98 | if (text.indexOf('%c') === -1 && !extraParameters) { 99 | console.log(text); 100 | } else { 101 | console.log(text, extraParameters || debugStyle); 102 | } 103 | }; 104 | 105 | var initIfNeeded = function() { 106 | if (!isInitialized()) { 107 | annyang.init({}, false); 108 | } 109 | }; 110 | 111 | var registerCommand = function(command, callback, originalPhrase) { 112 | commandsList.push({ command, callback, originalPhrase }); 113 | if (debugState) { 114 | logMessage( 115 | 'Command successfully loaded: %c' + originalPhrase, 116 | debugStyle 117 | ); 118 | } 119 | }; 120 | 121 | var parseResults = function(results) { 122 | invokeCallbacks(callbacks.result, results); 123 | var commandText; 124 | // go over each of the 5 results and alternative results received (we have set maxAlternatives to 5 above) 125 | for (let i = 0; i < results.length; i++) { 126 | // the text recognized 127 | commandText = results[i].trim(); 128 | if (debugState) { 129 | logMessage('Speech recognized: %c' + commandText, debugStyle); 130 | } 131 | 132 | // try and match the recognized text to one of the commands on the list 133 | for (let j = 0, l = commandsList.length; j < l; j++) { 134 | var currentCommand = commandsList[j]; 135 | var result = currentCommand.command.exec(commandText); 136 | if (result) { 137 | var parameters = result.slice(1); 138 | if (debugState) { 139 | logMessage( 140 | 'command matched: %c' + currentCommand.originalPhrase, 141 | debugStyle 142 | ); 143 | if (parameters.length) { 144 | logMessage('with parameters', parameters); 145 | } 146 | } 147 | // execute the matched command 148 | currentCommand.callback.apply(this, parameters); 149 | invokeCallbacks( 150 | callbacks.resultMatch, 151 | commandText, 152 | currentCommand.originalPhrase, 153 | results 154 | ); 155 | return; 156 | } 157 | } 158 | } 159 | invokeCallbacks(callbacks.resultNoMatch, results); 160 | }; 161 | 162 | annyang = { 163 | /** 164 | * Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands. 165 | * 166 | * #### Examples: 167 | * ````javascript 168 | * const commands = {'hello :name': helloFunction, 'howdy': helloFunction}; 169 | * const commands2 = {'hi': helloFunction}; 170 | * 171 | * annyang.addCommands(commands); 172 | * annyang.addCommands(commands2); 173 | * // annyang will now listen for all three commands 174 | * ```` 175 | * 176 | * @param {Object} commands - Commands that annyang should listen for 177 | * @method addCommands 178 | * @see [Commands Object](#commands-object) 179 | */ 180 | addCommands: function(commands) { 181 | var cb; 182 | 183 | initIfNeeded(); 184 | 185 | for (let phrase in commands) { 186 | if (commands.hasOwnProperty(phrase)) { 187 | cb = root[commands[phrase]] || commands[phrase]; 188 | if (typeof cb === 'function') { 189 | // convert command to regex then register the command 190 | registerCommand(commandToRegExp(phrase), cb, phrase); 191 | } else if (typeof cb === 'object' && cb.regexp instanceof RegExp) { 192 | // register the command 193 | registerCommand( 194 | new RegExp(cb.regexp.source, 'i'), 195 | cb.callback, 196 | phrase 197 | ); 198 | } else { 199 | if (debugState) { 200 | logMessage('Can not register command: %c' + phrase, debugStyle); 201 | } 202 | continue; 203 | } 204 | } 205 | } 206 | }, 207 | 208 | /** 209 | * Start listening. 210 | * It's a good idea to call this after adding some commands first (but not mandatory) 211 | * 212 | * Receives an optional options object which supports the following options: 213 | * 214 | * - `autoRestart` (boolean) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts? 215 | * - `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. 216 | * - `paused` (boolean) Start annyang in paused mode. 217 | * 218 | * #### Examples: 219 | * ````javascript 220 | * // Start listening, don't restart automatically 221 | * annyang.start({ autoRestart: false }); 222 | * // Start listening, don't restart automatically, stop recognition after first phrase recognized 223 | * annyang.start({ autoRestart: false, continuous: false }); 224 | * ```` 225 | * @param {Object} [options] - Optional options. 226 | * @method start 227 | */ 228 | start: function(options) { 229 | initIfNeeded(); 230 | options = options || {}; 231 | if (options.paused !== undefined) { 232 | pauseListening = !!options.paused; 233 | } else { 234 | pauseListening = false; 235 | } 236 | if (options.autoRestart !== undefined) { 237 | autoRestart = !!options.autoRestart; 238 | } else { 239 | autoRestart = true; 240 | } 241 | if (options.continuous !== undefined) { 242 | recognition.continuous = !!options.continuous; 243 | } 244 | 245 | lastStartedAt = new Date().getTime(); 246 | try { 247 | recognition.start(); 248 | } catch (e) { 249 | if (debugState) { 250 | logMessage(e.message); 251 | } 252 | } 253 | }, 254 | 255 | /** 256 | * Stop listening, and turn off mic. 257 | * 258 | * Alternatively, to only temporarily pause annyang responding to commands without stopping the SpeechRecognition engine or closing the mic, use pause() instead. 259 | * @see [pause()](#pause) 260 | * 261 | * @method abort 262 | */ 263 | abort: function() { 264 | autoRestart = false; 265 | autoRestartCount = 0; 266 | if (isInitialized()) { 267 | recognition.abort(); 268 | } 269 | }, 270 | 271 | /** 272 | * 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. 273 | * 274 | * Alternatively, to stop the SpeechRecognition engine and close the mic, use abort() instead. 275 | * @see [abort()](#abort) 276 | * 277 | * @method pause 278 | */ 279 | pause: function() { 280 | pauseListening = true; 281 | }, 282 | 283 | /** 284 | * Resumes listening and restore command callback execution when a command is matched. 285 | * If SpeechRecognition was aborted (stopped), start it. 286 | * 287 | * @method resume 288 | */ 289 | resume: function() { 290 | annyang.start(); 291 | }, 292 | 293 | /** 294 | * Turn on the output of debug messages to the console. Ugly, but super-handy! 295 | * 296 | * @param {boolean} [newState=true] - Turn on/off debug messages 297 | * @method debug 298 | */ 299 | debug: function(newState = true) { 300 | debugState = !!newState; 301 | }, 302 | 303 | /** 304 | * Set the language the user will speak in. If this method is not called, defaults to 'en-US'. 305 | * 306 | * @param {String} language - The language (locale) 307 | * @method setLanguage 308 | * @see [Languages](https://github.com/TalAter/annyang/blob/master/docs/FAQ.md#what-languages-are-supported) 309 | */ 310 | setLanguage: function(language) { 311 | initIfNeeded(); 312 | recognition.lang = language; 313 | }, 314 | 315 | /** 316 | * Remove existing commands. Called with a single phrase, an array of phrases, or methodically. Pass no params to remove all commands. 317 | * 318 | * #### Examples: 319 | * ````javascript 320 | * const commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction}; 321 | * 322 | * // Remove all existing commands 323 | * annyang.removeCommands(); 324 | * 325 | * // Add some commands 326 | * annyang.addCommands(commands); 327 | * 328 | * // Don't respond to hello 329 | * annyang.removeCommands('hello'); 330 | * 331 | * // Don't respond to howdy or hi 332 | * annyang.removeCommands(['howdy', 'hi']); 333 | * ```` 334 | * @param {String|Array|Undefined} [commandsToRemove] - Commands to remove 335 | * @method removeCommands 336 | */ 337 | removeCommands: function(commandsToRemove) { 338 | if (commandsToRemove === undefined) { 339 | commandsList = []; 340 | } else { 341 | commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove]; 342 | commandsList = commandsList.filter(command => { 343 | for (let i = 0; i < commandsToRemove.length; i++) { 344 | if (commandsToRemove[i] === command.originalPhrase) { 345 | return false; 346 | } 347 | } 348 | return true; 349 | }); 350 | } 351 | }, 352 | 353 | /** 354 | * Add a callback function to be called in case one of the following events happens: 355 | * 356 | * * `start` - Fired as soon as the browser's Speech Recognition engine starts listening. 357 | * 358 | * * `soundstart` - Fired as soon as any sound (possibly speech) has been detected. 359 | * 360 | * This will fire once per Speech Recognition starting. See https://is.gd/annyang_sound_start. 361 | * 362 | * * `error` - Fired when the browser's Speech Recognition engine returns an error, this generic error callback will be followed by more accurate error callbacks (both will fire if both are defined). 363 | * 364 | * The Callback function will be called with the error event as the first argument. 365 | * 366 | * * `errorNetwork` - Fired when Speech Recognition fails because of a network error. 367 | * 368 | * The Callback function will be called with the error event as the first argument. 369 | * 370 | * * `errorPermissionBlocked` - Fired when the browser blocks the permission request to use Speech Recognition. 371 | * 372 | * The Callback function will be called with the error event as the first argument. 373 | * 374 | * * `errorPermissionDenied` - Fired when the user blocks the permission request to use Speech Recognition. 375 | * 376 | * The Callback function will be called with the error event as the first argument. 377 | * 378 | * * `end` - Fired when the browser's Speech Recognition engine stops. 379 | * 380 | * * `result` - Fired as soon as some speech was identified. This generic callback will be followed by either the `resultMatch` or `resultNoMatch` callbacks. 381 | * 382 | * The Callback functions for this event will be called with an array of possible phrases the user said as the first argument. 383 | * 384 | * * `resultMatch` - Fired when annyang was able to match between what the user said and a registered command. 385 | * 386 | * The Callback functions for this event will be called with three arguments in the following order: 387 | * 388 | * * The phrase the user said that matched a command. 389 | * * The command that was matched. 390 | * * An array of possible alternative phrases the user might have said. 391 | * 392 | * * `resultNoMatch` - Fired when what the user said didn't match any of the registered commands. 393 | * 394 | * Callback functions for this event will be called with an array of possible phrases the user might have said as the first argument. 395 | * 396 | * #### Examples: 397 | * ````javascript 398 | * annyang.addCallback('error', function() { 399 | * $('.myErrorText').text('There was an error!'); 400 | * }); 401 | * 402 | * annyang.addCallback('resultMatch', function(userSaid, commandText, phrases) { 403 | * console.log(userSaid); // sample output: 'hello' 404 | * console.log(commandText); // sample output: 'hello (there)' 405 | * console.log(phrases); // sample output: ['hello', 'halo', 'yellow', 'polo', 'hello kitty'] 406 | * }); 407 | * 408 | * // pass local context to a global function called notConnected 409 | * annyang.addCallback('errorNetwork', notConnected, this); 410 | * ```` 411 | * @param {String} type - Name of event that will trigger this callback 412 | * @param {Function} callback - The function to call when event is triggered 413 | * @param {Object} [context] - Optional context for the callback function 414 | * @method addCallback 415 | */ 416 | addCallback: function(type, callback, context) { 417 | var cb = root[callback] || callback; 418 | if (typeof cb === 'function' && callbacks[type] !== undefined) { 419 | callbacks[type].push({ callback: cb, context: context || this }); 420 | } 421 | }, 422 | 423 | /** 424 | * Remove callbacks from events. 425 | * 426 | * - Pass an event name and a callback command to remove that callback command from that event type. 427 | * - Pass just an event name to remove all callback commands from that event type. 428 | * - Pass undefined as event name and a callback command to remove that callback command from all event types. 429 | * - Pass no params to remove all callback commands from all event types. 430 | * 431 | * #### Examples: 432 | * ````javascript 433 | * annyang.addCallback('start', myFunction1); 434 | * annyang.addCallback('start', myFunction2); 435 | * annyang.addCallback('end', myFunction1); 436 | * annyang.addCallback('end', myFunction2); 437 | * 438 | * // Remove all callbacks from all events: 439 | * annyang.removeCallback(); 440 | * 441 | * // Remove all callbacks attached to end event: 442 | * annyang.removeCallback('end'); 443 | * 444 | * // Remove myFunction2 from being called on start: 445 | * annyang.removeCallback('start', myFunction2); 446 | * 447 | * // Remove myFunction1 from being called on all events: 448 | * annyang.removeCallback(undefined, myFunction1); 449 | * ```` 450 | * 451 | * @param type Name of event type to remove callback from 452 | * @param callback The callback function to remove 453 | * @returns undefined 454 | * @method removeCallback 455 | */ 456 | removeCallback: function(type, callback) { 457 | var compareWithCallbackParameter = function(cb) { 458 | return cb.callback !== callback; 459 | }; 460 | // Go over each callback type in callbacks store object 461 | for (let callbackType in callbacks) { 462 | if (callbacks.hasOwnProperty(callbackType)) { 463 | // if this is the type user asked to delete, or he asked to delete all, go ahead. 464 | if (type === undefined || type === callbackType) { 465 | // If user asked to delete all callbacks in this type or all types 466 | if (callback === undefined) { 467 | callbacks[callbackType] = []; 468 | } else { 469 | // Remove all matching callbacks 470 | callbacks[callbackType] = callbacks[callbackType].filter(compareWithCallbackParameter); 471 | } 472 | } 473 | } 474 | } 475 | }, 476 | 477 | /** 478 | * Returns true if speech recognition is currently on. 479 | * Returns false if speech recognition is off or annyang is paused. 480 | * 481 | * @return boolean true = SpeechRecognition is on and annyang is listening 482 | * @method isListening 483 | */ 484 | isListening: function() { 485 | return isListening && !pauseListening; 486 | }, 487 | 488 | /** 489 | * Returns the instance of the browser's SpeechRecognition object used by annyang. 490 | * Useful in case you want direct access to the browser's Speech Recognition engine. 491 | * 492 | * @returns SpeechRecognition The browser's Speech Recognizer currently used by annyang 493 | * @method getSpeechRecognizer 494 | */ 495 | getSpeechRecognizer: function() { 496 | return recognition; 497 | }, 498 | 499 | /** 500 | * Simulate speech being recognized. This will trigger the same events and behavior as when the Speech Recognition 501 | * detects speech. 502 | * 503 | * Can accept either a string containing a single sentence or an array containing multiple sentences to be checked 504 | * in order until one of them matches a command (similar to the way Speech Recognition Alternatives are parsed) 505 | * 506 | * #### Examples: 507 | * ````javascript 508 | * annyang.trigger('Time for some thrilling heroics'); 509 | * annyang.trigger( 510 | * ['Time for some thrilling heroics', 'Time for some thrilling aerobics'] 511 | * ); 512 | * ```` 513 | * 514 | * @param string|array sentences A sentence as a string or an array of strings of possible sentences 515 | * @returns undefined 516 | * @method trigger 517 | */ 518 | trigger: function(sentences) { 519 | if (!annyang.isListening()) { 520 | if (debugState) { 521 | if (!isListening) { 522 | logMessage('Cannot trigger while annyang is aborted'); 523 | } else { 524 | logMessage('Speech heard, but annyang is paused'); 525 | } 526 | } 527 | return; 528 | } 529 | 530 | if (!Array.isArray(sentences)) { 531 | sentences = [sentences]; 532 | } 533 | 534 | parseResults(sentences); 535 | }, 536 | 537 | /** 538 | * Initialize annyang with a list of commands to recognize. 539 | * 540 | * #### Examples: 541 | * ````javascript 542 | * const commands = {'hello :name': helloFunction}; 543 | * const commands2 = {'hi': helloFunction}; 544 | * 545 | * // initialize annyang, overwriting any previously added commands 546 | * annyang.init(commands, true); 547 | * // adds an additional command without removing the previous commands 548 | * annyang.init(commands2, false); 549 | * ```` 550 | * 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. 551 | * 552 | * @param {Object} commands - Commands that annyang should listen to 553 | * @param {boolean} [resetCommands=true] - Remove all commands before initializing? 554 | * @method init 555 | * @deprecated 556 | * @see [Commands Object](#commands-object) 557 | */ 558 | init: function(commands, resetCommands = true) { 559 | // Abort previous instances of recognition already running 560 | if (recognition && recognition.abort) { 561 | recognition.abort(); 562 | } 563 | 564 | // initiate SpeechRecognition 565 | recognition = new SpeechRecognition(); 566 | 567 | // Set the max number of alternative transcripts to try and match with a command 568 | recognition.maxAlternatives = 5; 569 | 570 | // In HTTPS, turn off continuous mode for faster results. 571 | // In HTTP, turn on continuous mode for much slower results, but no repeating security notices 572 | recognition.continuous = root.location.protocol === 'http:'; 573 | 574 | // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage() 575 | recognition.lang = 'en-US'; 576 | 577 | recognition.onstart = function() { 578 | isListening = true; 579 | invokeCallbacks(callbacks.start); 580 | }; 581 | 582 | recognition.onsoundstart = function() { 583 | invokeCallbacks(callbacks.soundstart); 584 | }; 585 | 586 | recognition.onerror = function(event) { 587 | invokeCallbacks(callbacks.error, event); 588 | switch (event.error) { 589 | case 'network': 590 | invokeCallbacks(callbacks.errorNetwork, event); 591 | break; 592 | case 'not-allowed': 593 | case 'service-not-allowed': 594 | // if permission to use the mic is denied, turn off auto-restart 595 | autoRestart = false; 596 | // determine if permission was denied by user or automatically. 597 | if (new Date().getTime() - lastStartedAt < 200) { 598 | invokeCallbacks(callbacks.errorPermissionBlocked, event); 599 | } else { 600 | invokeCallbacks(callbacks.errorPermissionDenied, event); 601 | } 602 | break; 603 | } 604 | }; 605 | 606 | recognition.onend = function() { 607 | isListening = false; 608 | invokeCallbacks(callbacks.end); 609 | // annyang will auto restart if it is closed automatically and not by user action. 610 | if (autoRestart) { 611 | // play nicely with the browser, and never restart annyang automatically more than once per second 612 | var timeSinceLastStart = new Date().getTime() - lastStartedAt; 613 | autoRestartCount += 1; 614 | if (autoRestartCount % 10 === 0) { 615 | if (debugState) { 616 | logMessage( 617 | 'Speech Recognition is repeatedly stopping and starting. See http://is.gd/annyang_restarts for tips.' 618 | ); 619 | } 620 | } 621 | if (timeSinceLastStart < 1000) { 622 | setTimeout(function() { 623 | annyang.start({ paused: pauseListening }); 624 | }, 1000 - timeSinceLastStart); 625 | } else { 626 | annyang.start({ paused: pauseListening }); 627 | } 628 | } 629 | }; 630 | 631 | recognition.onresult = function(event) { 632 | if (pauseListening) { 633 | if (debugState) { 634 | logMessage('Speech heard, but annyang is paused'); 635 | } 636 | return false; 637 | } 638 | 639 | // Map the results to an array 640 | var SpeechRecognitionResult = event.results[event.resultIndex]; 641 | var results = []; 642 | for (let k = 0; k < SpeechRecognitionResult.length; k++) { 643 | results[k] = SpeechRecognitionResult[k].transcript; 644 | } 645 | 646 | parseResults(results); 647 | }; 648 | 649 | // build commands list 650 | if (resetCommands) { 651 | commandsList = []; 652 | } 653 | if (commands.length) { 654 | this.addCommands(commands); 655 | } 656 | }, 657 | }; 658 | 659 | return annyang; 660 | }); 661 | 662 | /** 663 | * # Good to Know 664 | * 665 | * ## Commands Object 666 | * 667 | * Both the [init()]() and addCommands() methods receive a `commands` object. 668 | * 669 | * annyang understands commands with `named variables`, `splats`, and `optional words`. 670 | * 671 | * * Use `named variables` for one-word arguments in your command. 672 | * * Use `splats` to capture multi-word text at the end of your command (greedy). 673 | * * Use `optional words` or phrases to define a part of the command as optional. 674 | * 675 | * #### Examples: 676 | * ````html 677 | * 705 | * ```` 706 | * 707 | * ### Using Regular Expressions in commands 708 | * For advanced commands, you can pass a regular expression object, instead of 709 | * a simple string command. 710 | * 711 | * This is done by passing an object containing two properties: `regexp`, and 712 | * `callback` instead of the function. 713 | * 714 | * #### Examples: 715 | * ````javascript 716 | * const calculateFunction = function(month) { console.log(month); } 717 | * const commands = { 718 | * // This example will accept any word as the "month" 719 | * 'calculate :month stats': calculateFunction, 720 | * // This example will only accept months which are at the start of a quarter 721 | * 'calculate :quarter stats': {'regexp': /^calculate (January|April|July|October) stats$/, 'callback': calculateFunction} 722 | * } 723 | ```` 724 | * 725 | */ 726 | -------------------------------------------------------------------------------- /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 | 45 | 46 | 55 | 56 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /test/init_corti.js: -------------------------------------------------------------------------------- 1 | Corti.patch(); 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/spec/IssuesSpec.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | // jshint strict: false 3 | if (typeof module === 'object' && module.exports) { 4 | // CommonJS 5 | factory(require('../../dist/annyang.js')); 6 | } else if (typeof define === 'function' && define.amd) { 7 | // AMD 8 | define(['annyang'], factory); 9 | } else { 10 | // Browser globals 11 | factory(root.annyang); 12 | } 13 | })(typeof window !== 'undefined' ? window : this, function factory(annyang) { 14 | 'use strict'; 15 | 16 | var env = jasmine.getEnv(); 17 | env.configure({ 18 | random: false 19 | }); 20 | 21 | // Issue #193 (https://github.com/TalAter/annyang/issues/193) 22 | describe('Speech recognition aborting while annyang is paused', function() { 23 | var recognition; 24 | 25 | beforeEach(function() { 26 | recognition = annyang.getSpeechRecognizer(); 27 | jasmine.clock().install(); 28 | annyang.abort(); 29 | annyang.removeCommands(); 30 | }); 31 | 32 | afterEach(function() { 33 | jasmine.clock().tick(2000); 34 | jasmine.clock().uninstall(); 35 | }); 36 | 37 | it('should not unpause annyang on restart', function() { 38 | annyang.start({ autoRestart: true, continuous: false }); 39 | annyang.pause(); 40 | recognition.abort(); 41 | 42 | expect(annyang.isListening()).toBe(false); 43 | jasmine.clock().tick(1000); 44 | 45 | expect(annyang.isListening()).toBe(false); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/vendor/corti.js: -------------------------------------------------------------------------------- 1 | //! Corti - Replaces the browser's SpeechRecognition with a fake object. 2 | //! version : 0.4.0 3 | //! author : Tal Ater @TalAter 4 | //! license : MIT 5 | //! https://github.com/TalAter/Corti 6 | 7 | (function(undefined) { 8 | 'use strict'; 9 | 10 | // Save a reference to the global object (window in the browser) 11 | var _root = this; 12 | 13 | // Holds the browser's implementation 14 | var _productionVersion = false; 15 | 16 | // Patch DOMException 17 | var DOMException = DOMException || TypeError; 18 | 19 | // Speech Recognition attributes 20 | var _maxAlternatives = 1; 21 | var _lang = ''; 22 | var _continuous = false; 23 | var _interimResults = false; 24 | 25 | var newSpeechRecognition = function() { 26 | var _self = this; 27 | var _listeners = document.createElement('div'); 28 | _self._started = false; 29 | _self._soundStarted = false; 30 | _self.eventListenerTypes = ['start', 'soundstart', 'end', 'result']; 31 | _self.maxAlternatives = 1; 32 | 33 | // Add listeners for events registered through attributes (e.g. recognition.onend = function) and not as proper listeners 34 | _self.eventListenerTypes.forEach(function(eventName) { 35 | _listeners.addEventListener( 36 | eventName, 37 | function() { 38 | if (typeof _self['on' + eventName] === 'function') { 39 | _self['on' + eventName].apply(_listeners, arguments); 40 | } 41 | }, 42 | false, 43 | ); 44 | }); 45 | 46 | Object.defineProperty(this, 'maxAlternatives', { 47 | get: function() { 48 | return _maxAlternatives; 49 | }, 50 | set: function(val) { 51 | if (typeof val === 'number') { 52 | _maxAlternatives = Math.floor(val); 53 | } else { 54 | _maxAlternatives = 0; 55 | } 56 | }, 57 | }); 58 | 59 | Object.defineProperty(this, 'lang', { 60 | get: function() { 61 | return _lang; 62 | }, 63 | set: function(val) { 64 | if (val === undefined) { 65 | val = 'undefined'; 66 | } 67 | _lang = val.toString(); 68 | }, 69 | }); 70 | 71 | Object.defineProperty(this, 'continuous', { 72 | get: function() { 73 | return _continuous; 74 | }, 75 | set: function(val) { 76 | _continuous = Boolean(val); 77 | }, 78 | }); 79 | 80 | Object.defineProperty(this, 'interimResults', { 81 | get: function() { 82 | return _interimResults; 83 | }, 84 | set: function(val) { 85 | _interimResults = Boolean(val); 86 | }, 87 | }); 88 | 89 | this.start = function() { 90 | if (_self._started) { 91 | throw new DOMException( 92 | "Failed to execute 'start' on 'SpeechRecognition': recognition has already started.", 93 | ); 94 | } 95 | _self._started = true; 96 | // Create and dispatch an event 97 | var event = document.createEvent('CustomEvent'); 98 | event.initCustomEvent('start', false, false, null); 99 | _listeners.dispatchEvent(event); 100 | }; 101 | 102 | this.abort = function() { 103 | if (!_self._started) { 104 | return; 105 | } 106 | _self._started = false; 107 | _self._soundStarted = false; 108 | // Create and dispatch an event 109 | var event = document.createEvent('CustomEvent'); 110 | event.initCustomEvent('end', false, false, null); 111 | _listeners.dispatchEvent(event); 112 | }; 113 | 114 | this.stop = function() { 115 | return _self.abort(); 116 | }; 117 | 118 | this.isStarted = function() { 119 | return _self._started; 120 | }; 121 | 122 | this.say = function(sentence) { 123 | if (!_self._started) { 124 | return; 125 | } 126 | // Create some speech alternatives 127 | var results = []; 128 | var commandIterator; 129 | var etcIterator; 130 | var itemFunction = function(index) { 131 | if (undefined === index) { 132 | throw new DOMException( 133 | "Failed to execute 'item' on 'SpeechRecognitionResult': 1 argument required, but only 0 present.", 134 | ); 135 | } 136 | index = Number(index); 137 | if (isNaN(index)) { 138 | index = 0; 139 | } 140 | if (index >= this.length) { 141 | return null; 142 | } else { 143 | return this[index]; 144 | } 145 | }; 146 | for ( 147 | commandIterator = 0; 148 | commandIterator < _maxAlternatives; 149 | commandIterator++ 150 | ) { 151 | var etc = ''; 152 | for (etcIterator = 0; etcIterator < commandIterator; etcIterator++) { 153 | etc += ' and so on'; 154 | } 155 | results.push(sentence + etc); 156 | } 157 | 158 | // Create the start event 159 | var startEvent = document.createEvent('CustomEvent'); 160 | startEvent.initCustomEvent('result', false, false, { 161 | sentence: sentence, 162 | }); 163 | startEvent.resultIndex = 0; 164 | startEvent.results = { 165 | item: itemFunction, 166 | 0: { 167 | item: itemFunction, 168 | final: true, 169 | }, 170 | }; 171 | for ( 172 | commandIterator = 0; 173 | commandIterator < _maxAlternatives; 174 | commandIterator++ 175 | ) { 176 | startEvent.results[0][commandIterator] = { 177 | transcript: results[commandIterator], 178 | confidence: Math.max(1 - 0.01 * commandIterator, 0.001), 179 | }; 180 | } 181 | Object.defineProperty(startEvent.results, 'length', { 182 | get: function() { 183 | return 1; 184 | }, 185 | }); 186 | Object.defineProperty(startEvent.results[0], 'length', { 187 | get: function() { 188 | return _maxAlternatives; 189 | }, 190 | }); 191 | startEvent.interpretation = null; 192 | startEvent.emma = null; 193 | _listeners.dispatchEvent(startEvent); 194 | 195 | // Create soundstart event 196 | if (!_self._soundStarted) { 197 | _self._soundStarted = true; 198 | var soundStartEvent = document.createEvent('CustomEvent'); 199 | soundStartEvent.initCustomEvent('soundstart', false, false, null); 200 | _listeners.dispatchEvent(soundStartEvent); 201 | } 202 | 203 | //stop if not set to continuous mode 204 | if (!_self.continuous) { 205 | _self.abort(); 206 | } 207 | }; 208 | 209 | this.addEventListener = function(event, callback) { 210 | _listeners.addEventListener(event, callback, false); 211 | }; 212 | }; 213 | 214 | // Expose functionality 215 | _root.Corti = { 216 | patch: function() { 217 | if (_productionVersion === false) { 218 | _productionVersion = 219 | _root.SpeechRecognition || 220 | _root.webkitSpeechRecognition || 221 | _root.mozSpeechRecognition || 222 | _root.msSpeechRecognition || 223 | _root.oSpeechRecognition; 224 | } 225 | _root.SpeechRecognition = newSpeechRecognition; 226 | }, 227 | 228 | unpatch: function() { 229 | _root.SpeechRecognition = _productionVersion; 230 | }, 231 | }; 232 | }.call(this)); 233 | --------------------------------------------------------------------------------