iOS doesn't allow Web Audio to be used without a user input to trigger it. To get around this, we have a touchend listener that does this:
initAudio: function () {
// create a blank buffer
var buffer = this._atx.createBuffer(1, 1, 22050); // this._atx is the AudioContext
var node = this._atx.createBufferSource();
node.buffer = buffer;
node.start(0); // or noteOn if the browser doesn't support this, removed that check/code for brevity
}
This is working fine in most cases. We have an overlay over the game, which intercepts the first click, and calls the above function. At the end of the function, this._atx.state is "running" (it is "suspended" before the node.start(0) call).
However, if the callback is triggered by a quick swipe on the screen, rather than a tap, it goes through this code, and at the end of the function, the state is still "suspended". It does exactly the same code both times, the only difference is the nature of the user input.
This is the code that adds the listeners:
this._boundSoundTapped = this._onSoundTapped.bind(this);
this._confirmPopup.addEventListener("touchend", this._boundSoundTapped);
this._confirmPopup.addEventListener("click", this._boundSoundTapped);
And the onSoundTapped function:
_onSoundTapped: function(e){
e.stopPropagation();
e.preventDefault();
if(this._soundsPressed === false) {
this._confirmPopup.removeEventListener("touchend", this._boundSoundTapped);
this._confirmPopup.removeEventListener("click", this._boundSoundTapped);
this._soundsPressed = true;
this._player.initAudio();
}
},
I really can't see why the swipe rather than the click would have a different effect, they both trigger touchend, and the same code gets executed either way.
Related
I'd like to be informed about a MediaStreamTrack's end. According to MDN an ended event is
Sent when playback of the track ends (when the value readyState changes to ended).
Also available using the onended event handler property.
So I should be able to setup my callback(s) like:
const [track] = stream.getVideoTracks();
track.addEventListener('ended', () => console.log('track ended'));
track.onended = () => console.log('track onended');
and I expect those to be invoked, once I stop the track via:
tracks.forEach(track => track.stop());
// for good measure? See
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop#Stopping_a_video_stream
videoElem.srcObject = null;
The problem I'm having is that the callbacks are not invoked. I built the following JSFiddle, where 3 MediaStreams are created in 3 different ways:
getUserMedia
getDisplayMedia
getCaptureStream (canvas element)
I also have 3 buttons which stop all tracks for the respective MediaStream. The behaviour is as follows:
All 3 streams are inactive, the MediaStream's oninactive callback is triggered (in Chrome, seems like Firefox doesn't support this).
All tracks have a readyState of ended after being stopped.
If I stop the screen stream (2. getDisplayMedia) via Chrome's UI, the track ended callback(s) are invoked.
I know that you have to watch out for the track being used by multiple sources, but that shouldn't be the case here, right? Am I missing something obvious?
Since multiple tracks may use the same source (for example, if two tabs are using the device's microphone), the source itself isn't necessarily immediately stopped. It is instead disassociated from the track and the track object is stopped. Once no media tracks are using the source, the source may actually be completely stopped.
Why is the 'ended' event not firing for this MediaStreamTrack?
Because ended is explicitly not fired when you call track.stop() yourself. It only fires when a track ends for other reasons. From the spec:
Fired when...
The MediaStreamTrack object's source will no longer provide any data, either because the user revoked the permissions, or because the source device has been ejected, or because the remote peer permanently stopped sending data.
This is by design. The thinking was you don't need an event when you stop it yourself. To work around it do:
track.stop();
track.dispatchEvent(new Event("ended"));
MediaStream's oninactive callback is triggered (in Chrome, seems like Firefox doesn't support this).
stream.oninactive and the inactive event are deprecated, and no longer in the spec.
As a workaround for that, you can use the similar ended event on a media element:
video.srcObject = stream;
await new Promise(resolve => video.onloadedmetadata = resolve);
video.addEventListener("ended", () => console.log("inactive!"));
Alas, that does not appear to work in Chrome yet, but it works in Firefox.
I have a Chrome extension that tracks the order in which tabs are accessed by listening for tab-related events like tabs.onActivated, onRemoved, etc. It uses an event page instead of a persistent background page to add the event listeners. The tab access order is stored in chrome.storage.local.
The extension works fine while in the normal course of using the browser. But when Chrome is first launched and restores the previous session, it reopens the windows in the order they were originally opened, firing onActivated events for the reopened tabs.
If the extension listened to these events, they would cause the stored tab access order to change, which I'm trying to avoid. I don't want to start listening to the tab events until Chrome has finished restoring the session and has settled down. But I'm not sure how to detect that change in state using an event page that normally has to re-add the event listeners every time it's loaded.
I've tried something like the following to delay adding the tab event listeners until shortly after the last window has been created during startup (it listens for windows.onCreated because Chrome will start up in the background when you restart Windows, but no windows are created at that point):
var gStartingUp = false;
chrome.runtime.onStartup.addListener(() => {
var timer = null;
gStartingUp = true;
chrome.windows.onCreated.addListener(window => {
clearTimeout(timer);
timer = setTimeout(() => {
gStartingUp = false;
addListeners();
}, 750);
});
);
if (!gStartingUp) {
addListeners();
}
In normal usage, gStartingUp would default to false and the listeners would get added. But when the browser fires the onStartup event, the handler isn't called fast enough to prevent the addListeners() call from happening, so the listeners are added during startup as well. I suppose I could add a timeout before calling addListeners(), but that would delay adding them during normal usage.
Is there a way for an extension's event page to reliably detect that Chrome has finished its startup processing?
My initial solution below is too unreliable. I actually saw a case where Chrome restarted, the event page loaded, the timeout to add the listeners fired after 100ms, but the onStartup event didn't fire for another two whole seconds.
So I gave up on trying to delay adding the event handlers until after the onStartup event was handled. Instead, each handler now checks a startingUp flag that's set in the onStartup handler, and only processes the event if it's not in the startup phase. That check is unnecessary for most of the lifetime of the extension, but it at least avoids adding an artificial delay before handling events.
I also switched to listening for tabs.onActivated events during startup, as Chrome seems to reopen the windows first during startup, and then reopens the tabs. So waiting for a pause in tab activations should be a better signal that Chrome has finished starting up.
var startingUp = false;
chrome.runtime.onStartup.addListener(() => {
var timer = null;
startingUp = true;
function onActivated()
{
clearTimeout(timer);
timer = setTimeout(() => {
startingUp = false;
chrome.tabs.onActivated.removeListener(onActivated);
}, 500);
}
chrome.tabs.onActivated.addListener(onActivated);
);
chrome.tabs.onActivated.addListener(tabID => {
if (!startingUp) {
// handle event
}
});
Initial solution
The answer to that question would appear to be "no". The best I've been able to do is indeed delay calling addListeners() long enough for the onStartup listener to fire when Chrome is starting up:
var startingUp = false,
addedListeners = false;
chrome.runtime.onStartup.addListener(() => {
var timer = null;
startingUp = true;
chrome.windows.onCreated.addListener(window => {
clearTimeout(timer);
timer = setTimeout(() => {
startingUp = false;
addListeners();
}, 500);
});
);
setTimeout(function() {
if (!startingUp && !addedListeners) {
addListeners();
}
}, 100);
This seems to mostly work, and the 100ms delay in adding event handlers every time the page is reloaded doesn't seem to be noticeable in actual usage (I've tried 0, 10 and 50ms delays, but they'd sometimes fire before the onStartup handler).
But it all feels very kludgy and brittle if the timing is off. Any improved solutions are welcome.
When events are queued with setTimeout/setInterval, and the user is viewing a separate tab, Chrome and Firefox enforce a minimum 1000ms lag before the event is executed. This article details the behaviour.
This has been discussed on StackOverflow previously, but the questions and answers only applied to animations. Obviously, an animation can just be forced to update to the latest state when a user re-enters the tab.
But the solution does not work for sequenced audio. I have Web Audio API playing several audio files in sequence, and setTimeout is used to countdown to when the next audio file plays. If you put the tab in the background, you get an annoying 1 second gap between each pattern -- an extreme flaw in an API designed for advanced audio.
You can witness this behaviour in various HTML5 sequencers, e.g. with PatternSketch -- just by entering a pattern, playing, and going to another tab.
So I'm in need of a workaround: a way to queue events without the 1000ms clamp. Does anyone know of a way?
The only solution I can think of is to have window.postMessage run every single millisecond and check each time if the event is to execute. That is definitely detrimental to performance. Is this the only option?
Apparently there is no event system planned for Web Audio API, so that is out of question.
EDIT: Another answer is to use WebWorkers per https://stackoverflow.com/a/12522580/1481489 - this answer is a little specific, so here's something more generic:
interval.js
var intervalId = null;
onmessage = function(event) {
if ( event.data.start ) {
intervalId = setInterval(function(){
postMessage('interval.start');
},event.data.ms||0);
}
if ( event.data.stop && intervalId !== null ) {
clearInterval(intervalId);
}
};
and your main program:
var stuff = { // your custom class or object or whatever...
first: Date.now(),
last: Date.now(),
callback: function callback() {
var cur = Date.now();
document.title = ((cur-this.last)/1000).toString()+' | '+((cur-this.first)/1000).toString();
this.last = cur;
}
};
var doWork = new Worker('interval.js');
doWork.onmessage = function(event) {
if ( event.data === 'interval.start' ) {
stuff.callback(); // queue your custom methods in here or whatever
}
};
doWork.postMessage({start:true,ms:250}); // tell the worker to start up with 250ms intervals
// doWork.postMessage({stop:true}); // or tell it just to stop.
Totally ugly, but you could open up a child popup window. However, all this does is transfer some of the caveats to the child window, i.e. if child window is minimized the 1000ms problem appears, but if it is simply out of focus, there isn't an issue. Then again, if it is closed, then it stops, but all the user has to do is click the start button again.
So, I suppose this doesn't really solve your problem... but here's a rough draft:
var mainIntervalMs = 250;
var stuff = { // your custom class or object or whatever...
first: Date.now(),
last: Date.now(),
callback: function callback(){
var cur = Date.now();
document.title = ((cur-this.last)/1000).toString()+' | '+((cur-this.first)/1000).toString();
this.last = cur;
}
};
function openerCallbackHandler() {
stuff.callback(); // queue your custom methods in here or whatever
}
function openerTick(childIntervalMs) { // this isn't actually used in this window, but makes it easier to embed the code in the child window
setInterval(function() {
window.opener.openerCallbackHandler();
},childIntervalMs);
}
// build the popup that will handle the interval
function buildIntervalWindow() {
var controlWindow = window.open('about:blank','controlWindow','width=10,height=10');
var script = controlWindow.document.createElement('script');
script.type = 'text/javascript';
script.textContent = '('+openerTick+')('+mainIntervalMs+');';
controlWindow.document.body.appendChild(script);
}
// write the start button to circumvent popup blockers
document.write('<input type="button" onclick="buildIntervalWindow();return false;" value="Start" />');
I'd recommend working out a better way to organize, write, etc. but at the least it should point you in the right direction. It should also work in a lot of diff browsers (in theory, only tested in chrome). I'll leave you to the rest.
Oh, and don't forget to build in auto-closing of the child window if the parent drops.
I've faced the following scenario quite often so I'm wondering if there is a built-in jQuery way of solving the issue.
Imagine the following code:
$(document).click(function() {
paintCanvas();
});
The problem with this code is that if the user clicks on the screen 50 times in rapid succession you are going to overload the browser with 50 calls to paintCanvas.
If paintCanvas is currently executing and a new request is created, we want to queue the new request so that it waits until paintCanvas is finished executing. However, at the same time, we can drop any previously queued calls to paintCanvas as we only care about the final state of the mouse, not all the intermediate states.
Here is some code that solves the problem:
var _isExecuting, _isQueued;
function paintCanvas() {
if (_isExecuting) {
if (!_isQueued) {
_isQueued = true;
setTimeout(function() {
_isQueued = false;
paintCanvas();
}, 150);
}
return;
}
_isExecuting = true;
// ... code goes here
_isExecuting = false;
};
This AJAX queue plugin essentially implements this functionality, but does so only in terms of AJAX. Surely this is a very common problem that can be solved in more generic way?
You shouldn't have to solve this problem with mousemove because the system already does that for you. While paintCanvas is executing, it is not generating hundreds of mousemove events even if the mouse is moving vigorously. Rather, the next event will be the current location of the mouse, not a queue of all the intervening mouse events.
Look at this jsFiddle: http://jsfiddle.net/jfriend00/4ZuMn/.
Wiggle your mouse around in the body (lower, right pane) as fast as you want. Then move the mouse out of the pane and notice that the count stops immediately - there are no more mouse events. It doesn't stack up mouse events ever. Whenever the system is ready for the next mouse event, it gets the latest position of the mouse. Individual mouse moves are NOT queued up - they do not accumulate. You can also see in the listing of mouse events that lots of intervening mouse events are not present (e.g. lots of coordinates are missing) even though the mouse went through more positions. This is because the system wasn't ready to make a mouse event when the mouse was in that position so that position was skipped.
Further, because javascript is single threaded, you will never get a new mouse event while you are currently processing one. The system won't generate a new one until you're done processing the one you're already one. So, you will never, ever see _isExecuting as true in javascript in your code. You simply don't need that check. And, since you don't need that check and it will never be true, none of your queuing code will ever execute. You can see here in this jsFiddle, that you can never catch a mousemove event that was re-entered: http://jsfiddle.net/jfriend00/ngnUT/. The inAction flag is never caught as true, no matter how fast or much you wiggle your mouse around.
Sounds like you want throttle/debounce features.
There are no built in methods that I know of from jQuery, you can use any of these though:
http://benalman.com/projects/jquery-throttle-debounce-plugin/
http://jsperf.com/jquery-throttle-methods
Though #rkw provided a link, I always prefer to show code here on SO. Here's some simple code that kind does what you want. A function that returns a buffered version of another function. This will keep delaying until it stops receiving the event for the given delay. You can tweak this if you don't want to to wait for the delay after the last event. All you'd need to do is keep track of when you first set the timeout and offset the subsequent calls to setTimeout.
Here's a working example http://jsfiddle.net/mendesjuan/qfFjZ/
function createBuffered(handler, delay) {
var timeoutId = null;
return function() {
var me = this;
if (timeoutId) {
window.clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
handle.apply(me, arguments);
timeoutId = null;
}, delay);
}
}
In my web app, I use the onkeydown event to capture key strokes.
For example, I capture the 'j' key to animate a scroll down the page (and do some other stuff meanwhile).
My problem is the user might keep the 'j' key down to scroll further down the page (this is equivalent to fast multiple key strokes).
In my app, this result in a series of animations that doesn't look that good.
How can I know when the key has been released, and know the amount of key stokes I should have captured? This way I could run one long animation instead of multiple short ones.
Building on #JMD:
var animate = false;
function startanimation()
{
animate = true;
runanimation();
}
function stopanimation()
{
animate = false;
}
function runanimation()
{
if ( animation_over )
{
if ( !animate )
{
return;
}
return startanimation();
}
// animation code
var timeout = 25;
setTimeout(function(){runanimation();},timeout);
}
document.onkeydown = startanimation;
document.onkeyup = stopanimation;
You'll need to add some checks for starting/ending animations, however.
Edit: added a return to the JS; would've recursed endlessly.
Rather than trying to stack up the animations, you could start an animation on keyDown, and if at the end of the animation you haven't yet received keyUp then start another animation. As soon as you reach the end of an animation and you do have keyUp then you're done.
setTimeout returns a timer ID. So what I would do is after you receive a keyDown event, I would start a timer with a very short wait period, like so:
var globalTimerId = -1;
var keyDownCount = 0;
function handleKeyDown(e) {
if (globalTimerId != -1) {
clearTimeout(globalTimerId);
keyDownCount++;
}
/* 500 means 1/2 a second, adjust for your needs */
globalTimerId = setTimeout(handleKeyDownAfterWait, 500);
keyDownCount = 1;
}
function handleKeyDownAfterWait() {
globalTimerId = -1;
/* keyDownCount will have the number of times handleKeyDown was called */
}
So the idea is that each time a keyDown event is received, the timer is cleared (assuming it hasn't elapsed yet) and restarted. If the timer expires, the user has let go of the key and you can handle that in handleKeyDownAfterWait.
There may be other more elegant solutions with jQuery or another JS library however. This is just something quick and dirty (and possibly buggy ;))
you can try using my repsonsiveness plugin see:
http://notetodogself.blogspot.com/2008/12/jquery-responsiveness-plugin-for-fast.html
see the demo here:
http://doesthatevencompile.com/current-projects/jquery.responsiveness/
the one where you type stuff fast. you can adapt that to your needs. like so:
the animation event will be bound with the plugin, and execute when the user stops doing something fast. you can count how many times he does the fast thing by normal binding of the event.