Reputation: 129
I wrote a userscript that runs on the page for a game called TagPro that allows for voice recognition. It listens for key words followed by phrases, and depending on the word it puts the phrase into a different chat channel.
I'm running the script in Chrome's Tampermonkey extension. The speech recognition library I'm using is Annyang.
At the beginning of every game, Chrome confirms that you want to allow the site to use your microphone with a prompt like this:
The problem I'm having is that sometimes, in the middle of a game, the prompt will come up again. It doesn't happen in every game, but when it does happen it usually happens more than once. I haven't noticed any patterns that would make it reproducible. That makes it very hard to diagnose, let alone fix. Is there an error with my script that could be causing this?
// ==UserScript==
// @name TagPro Speech To Text
// @namespace http://www.reddit.com/u/undergroundmonorail
// @description Say a message out loud to say it into chat.
// @include http://tagpro-*.koalabeast.com:*
// @include http://tangent.jukejuice.com:*
// @include http://maptest.newcompte.fr:*
// @include http://justletme.be*
// @license MIT
// @author monorail
// @version 0.2
// ==/UserScript==
(function() {
// https://github.com/TalAter/annyang
// I couldn't figure out how to load it dynamically, so I just copypasted
// the minified version.
// @require works in Firefox, but not Chrome, and this is way easier than
// any alternative I found.
(function(a){"use strict";var b=this,c=b.SpeechRecognition||b.webkitSpeechRecognition||b.mozSpeechRecognition||b.msSpeechRecognition||b.oSpeechRecognition;if(!c)return b.annyang=null,a;var d,e,f=[],g={start:[],error:[],end:[],result:[],resultMatch:[],resultNoMatch:[],errorNetwork:[],errorPermissionBlocked:[],errorPermissionDenied:[]},h=0,i=!1,j="font-weight: bold; color: #00f;",k=/\s*\((.*?)\)\s*/g,l=/(\(\?:[^)]+\))\?/g,m=/(\(\?)?:\w+/g,n=/\*\w+/g,o=/[\-{}\[\]+?.,\\\^$|#]/g,p=function(a){return a=a.replace(o,"\\$&").replace(k,"(?:$1)?").replace(m,function(a,b){return b?a:"([^\\s]+)"}).replace(n,"(.*?)").replace(l,"\\s*$1?\\s*"),new RegExp("^"+a+"$","i")},q=function(a){a.forEach(function(a){a.callback.apply(a.context)})},r=function(){d===a&&b.annyang.init({},!1)};b.annyang={init:function(k,l){l=l===a?!0:!!l,d&&d.abort&&d.abort(),d=new c,d.maxAlternatives=5,d.continuous=!0,d.lang="en-US",d.onstart=function(){q(g.start)},d.onerror=function(a){switch(q(g.error),a.error){case"network":q(g.errorNetwork);break;case"not-allowed":case"service-not-allowed":e=!1,(new Date).getTime()-h<200?q(g.errorPermissionBlocked):q(g.errorPermissionDenied)}},d.onend=function(){if(q(g.end),e){var a=(new Date).getTime()-h;1e3>a?setTimeout(b.annyang.start,1e3-a):b.annyang.start()}},d.onresult=function(a){q(g.result);for(var c,d=a.results[a.resultIndex],e=0;e<d.length;e++){c=d[e].transcript.trim(),i&&b.console.log("Speech recognized: %c"+c,j);for(var h=0,k=f.length;k>h;h++){var l=f[h].command.exec(c);if(l){var m=l.slice(1);return i&&(b.console.log("command matched: %c"+f[h].originalPhrase,j),m.length&&b.console.log("with parameters",m)),f[h].callback.apply(this,m),q(g.resultMatch),!0}}}return q(g.resultNoMatch),!1},l&&(f=[]),k.length&&this.addCommands(k)},start:function(b){r(),b=b||{},e=b.autoRestart!==a?!!b.autoRestart:!0,h=(new Date).getTime(),d.start()},abort:function(){r(),e=!1,d.abort()},debug:function(a){i=arguments.length>0?!!a:!0},setLanguage:function(a){r(),d.lang=a},addCommands:function(a){var c,d;r();for(var e in a)if(a.hasOwnProperty(e)){if(c=b[a[e]]||a[e],"function"!=typeof c)continue;d=p(e),f.push({command:d,callback:c,originalPhrase:e})}i&&b.console.log("Commands successfully loaded: %c"+f.length,j)},removeCommands:function(a){a=Array.isArray(a)?a:[a],f=f.filter(function(b){for(var c=0;c<a.length;c++)if(a[c]===b.originalPhrase)return!1;return!0})},addCallback:function(c,d,e){if(g[c]!==a){var f=b[d]||d;"function"==typeof f&&g[c].push({callback:f,context:e||this})}}}}).call(this);
// The following code is the function for sending a chat message. This is
// how every userscript that touches chat does it. It's almost definitely
// not related to the problem.
var lastMessage = 0;
var chat = function(message, all) {
var limit = 500 + 10;
var now = new Date();
var timeDiff = now - lastMessage;
if (timeDiff > limit) {
tagpro.socket.emit("chat", {
message: message,
toAll: all
});
lastMessage = new Date();
} else if (timeDiff >= 0) {
setTimeout(chat, limit - timeDiff, chatMessage)
}
}
// Code that I wrote begins here.
var team = function(message) { chat(message, 0); };
var all = function(message) { chat(message, 1); };
var group = function(message) {
if (tagpro.group.socket) {tagpro.group.socket.emit('chat', message);}
};
commands = { 'say *message': all,
'team *message': team,
'group *message': group };
annyang.addCommands(commands);
annyang.start();
})();
Upvotes: 1
Views: 1431
Reputation: 1189
Chrome's speech recognition implementation simply stops from time-to-time. To handle this, annyang restarts it (unless it is stopped manually by you or the user). Since your page doesn't use HTTPS, the permission you gave Chrome to use speech recognition on that page doesn't persist, and it asks the user for permission again and again. This is why it is recommended to use HTTPS whenever using speech recognition on the page.
Upvotes: 3
Reputation: 93613
You are probably getting the extra prompts due to AJAX loading in iframes for various purposes. Wrap your code in a frame check:
if (window.top == window.self) {
//-- Only run on the master (top) page...
(function() {
// https://github.com/TalAter/annyang
// I couldn't figure out how to load it dynamically, so I just copypasted
// the minified version.
// @require works in Firefox, but not Chrome, and this is way easier than
// any alternative I found.
(function(a){"use strict";var b=this,c=b.SpeechRecognition||b.webkitSpeechRecognition|| ...
// etc...
}
To eliminate the prompt completely, you have to use a full-blown extension. Then you can use techniques like in this other answer. (But then you also have to hassle with the Chrome store if you want to share the extension easily.)
Upvotes: 1
Reputation: 5692
This was a funny one.
So I unminified annyang.js
to find the onend
and onerror
functions. In the onerror
call I console logged the error to get:
Note error: "no-speech"
.
You see where this is going...
Looking up the W3 spec: https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html#dfn-sre.nospeech
It reads:
"no-speech" No speech was detected.
Long story short, you're being too quiet.
As for PREVENTING it - dig into annyang.js
, particularly the onerror
event. Or sing the ABCs repeatedly while you need it on.
Upvotes: 2