Detect sound is ended in THREE.PositionalAudio? - javascript

I want to detect when sounds is ending, but all examples that i found not working.
// Create sound
var sound1 = new THREE.PositionalAudio( listener );
sound1.load( 'sounds/Example.ogg' );
sound1.setRefDistance( 30 );
sound1.autoplay = false;
sound1.setLoop(false);
mesh1.add( sound1 );
// Start sound
setTimeout(function() {
sound1.play();
}, 2000);
// Try to detect end #1
sound1.onended = function() {
console.log('sound1 ended #1');
};
// Try to detect end #1
sound1.addEventListener('ended', function() {
console.log('sound1 ended #2');
});
Live example: http://codepen.io/anon/pen/wMRoWQ

Three.Audio is wrapper around a buffer source audio object. https://github.com/mrdoob/three.js/blob/ddab1fda4fd1e21babf65aa454fc0fe15bfabc33/src/audio/Audio.js#L12
It looks like it overrides the onended event and binds it to its own function, so we just do the same:
sound1.source.onended = function() {
console.log('sound1 ended');
this.isPlaying = false; /* sets Three wrapper property correctly */
};
We set isPlaying = false because this is what Three's Audio onended function does, which we just overrode.
working pen: http://codepen.io/anon/pen/GoambE

I just had to solve this problem in 2020. I wasn't looking at the docs right at first, but it is correctly listed, it seems.
Similar to the previously correct answer, but a bit different now (that solution did not work for me):
var sound1 = new THREE.PositionalAudio( listener );
sound1.onEnded = () => console.log("track ended")
Here's the appropriate docs: https://threejs.org/docs/#api/en/audio/Audio.onEnded
There's not much there, basically:
.onEnded () : null
Called automatically when playback finished.
Things that did not work for me:
sound1.onEnded(() => {})
el.object3D.source.onended(() => {})
sound1.source.onended(() => {}); // previous answer
If you do this, you can intercept a call, but I don't think it's necessary anymore:
THREE.Audio.prototype.onEnded = function() {
console.warn("arguments to onEnded!", arguments)
};

Related

why doesn't removeEventListener work?

I'm not sure what's wrong here, but testing in the chromium and firefox, I find that I'm doing it wrong with respect to removing an EventListener from an element in javascript.
The context is a canvas game. At first, there's a splash screen shown where you click to begin the game. After you click to begin, I want to remove the listener.
The main point of interest is the removeEventListener in the startGame function. It doesn't throw an error. And the code executes (I see the game starting message in the console and I can see that "this" is the Game instance). I'm totally confused why if I keep on clicking on the canvas runs startGame each time. The expected behavior is that clicking there does nothing once the EventListener is removed.
Help!
function Game(canvas) {
this.c = canvas;
this.ctx = this.c.getContext("2d");
this.c.width = CANVAS_WIDTH;
this.c.height = CANVAS_HEIGHT;
// Background image
this.bgReady = false;
this.bgImage = new Image();
this.bgImage.onload = function () {
window.g.bgReady = true;
};
this.bgImage.src = MAIN_BACKGROUND;
}
Game.prototype.setSplash = function() {
if (this.bgReady) {
this.ctx.drawImage(window.g.bgImage, 0, 0);
this.ctx.font="48px Helvetica";
this.ctx.textAlign = "center";
this.ctx.fillStyle="rgb(0,0,255)";
this.ctx.fillText("Click To Start",310,240);
document.getElementById("cnvs").addEventListener(
'click',this.startGame.bind(this),true);
} else {
// since setSplash is an early function
// wait a bit for the background image and then try again
setTimeout(this.setSplash.bind(this),100);
console.log("bgImage not ready...");
}
}
Game.prototype.startGame = function() {
console.log("game starting ...");
console.log(this);
// step 1, remove the click listener for this function
// why isn't this working?!
document.getElementById("cnvs").removeEventListener(
'click',this.startGame,true);
}
...
// other stuff ...
function initialize() {
// Get the canvas
var c = document.getElementById("cnvs");
// Create a game object
window.g = new Game(c);
// Set the splash page
g.setSplash();
}
window.onload=initialize;
Further info:
I also had a version where the non-working removal was written as:
this.c.removeEventListener('click',this.startGame,true);
Same behavior as the code referenced above.
EDIT: in reply to the first answer by mczepiel
I'm trying to implement your answer like this:
Typer.prototype.setSplash = function() {
if (this.bgReady) {
this.ctx.drawImage(window.t.bgImage, 0, 0);
this.ctx.font="48px Helvetica";
this.ctx.textAlign = "center";
this.ctx.fillStyle="rgb(0,0,255)";
this.ctx.fillText("Click To Start",310,240);
var boundFunction = this.startGame.bind(this);
document.getElementById("cnvs").addEventListener(
'click',boundFunction,true,boundFunction);
} else {
// since setSplash is an early function
// wait a bit for the background image and then try again
setTimeout(this.setSplash.bind(this),100);
console.log("bgImage not ready...");
}
}
Typer.prototype.startGame = function(boundFunction) {
console.log("game starting ...");
console.log(this); // strangely, now this is an Object rather
// than Game, it still has the properties of
// Game tho
// step 1, remove the click listener for this function
// still isn't working...
document.getElementById("cnvs").removeEventListener(
'click',boundFunction,true);
}
I think I understood your suggestion, but perhaps not. The code above still doesn't remove the listener. Any help appreciated.
You'll need to store a reference to the result of calling this.startGame.bind(this) and pass that same value to both addEventListener and removeEventListener
The remove call is expecting to remove the exact same object that was added as a listener.
Likely duplicate of removeEventListener is not working and others if you want to see the same issue in various flavors.
EDIT untested off-the-cuff suggestion:
Typer.prototype.setSplash = function() {
if (this.bgReady) {
// draw stuff
var canvasElement = document.getElementById("cnvs");
var dismissSplash = function (evt) {
canvasElement.removeEventListener('click', dismissSplash, true);
this.startGame();
}.bind(this);
canvasElement.addEventListener('click', dismissSplash, true);
} else {
// try to show splash later
}
}
Typer.prototype.startGame = function() {
// start game
}

How to allow rewind but disable fast-forwarding in html5-video

I have the following code (using videojs)
this.on("timeupdate", function(event) {
previousTime = this.currentTime();
});
this.on("seeking", function(){
if (this.currentTime() > previousTime){
this.currentTime = previousTime;
}
});
Problem is that, once you try to fast-forward one time, it no longers keeps track of the timeupdate on the progress bar. I can not longer seek on the bar - not even rewind the video. What am I missing here?
First off, these are not standard HTMLMediaElement methods and properties, so I'm assuming you're using some kind of framework, perhaps Popcorn.js? And I'm assuming this refers to the object wrapping your video element.
The problem is that you're overwriting the currentTime method. If you were to reference the video element directly, then you would seek the way you're doing it, by setting the currentTime property, like: videoElement.currentTime = previousTime. But since you're using the framework, this.currentTime is supposed to be a function until you change it to a number.
To demonstrate, modify your code as follows:
this.on("seeking", function(){
console.log(typeof this.currentTime); // 'function'
if (this.currentTime() > previousTime){
this.currentTime = previousTime;
console.log(typeof this.currentTime); // 'number'
}
});
The next time you run the "timeupdate" handler, the call to this.currentTime() will throw an error because it is no longer a function.
You can probably fix it like this (assuming you're using Popcorn or something that works similarly):
this.on("seeking", function(){
if (this.currentTime() > previousTime){
this.currentTime(previousTime);
}
});
You'll also want to make sure that you don't set previousTime while seeking, since "timeupdate" may fire before "seeking" does. I'm not sure how to do that with your framework, so here's a link to a working example using the native HTMLVideoElement API:
http://jsbin.com/kilemoto/1/edit
Just had to implement this, seems to be working nicely
var elem = document.querySelector("video");
let previousTime = 0;
elem.ontimeupdate = function() {
setTimeout(() => {
previousTime = elem.currentTime;
}, 1000)
}
elem.onseeking = function() {
if (elem.currentTime > previousTime) {
elem.currentTime = previousTime;
}
}

How to unsubscribe from a socket.io subscription?

Suppose there are objects making subscriptions to a socket server like so:
socket.on('news', obj.socketEvent)
These objects have a short life span and are frequently created, generating many subscriptions. This seems like a memory leak and an error prone situation which would intuitively be prevented this way:
socket.off('news', obj.socketEvent)
before the object is deleted, but alas, there isn't an off method in the socket. Is there another method meant for this?
Edit: having found no answer I'm assigning a blank method to overwrite the wrapper method for the original event handler, an example follows.
var _blank = function(){};
var cbProxy = function(){
obj.socketEvent.apply(obj, arguments)
};
var cbProxyProxy = function(){
cbProxy.apply ({}, arguments)
}
socket.on('news', cbProxyProxy);
// ...and to unsubscribe
cbProxy = _blank;
From looking at the source of socket.io.js (couldn't find it in documentation anywhere), I found these two functions:
removeListener = function(name, fn)
removeAllListeners = function(name)
I used removeAllListeners successfully in my app; you should be able to choose from these:
socket.removeListener("news", cbProxy);
socket.removeAllListeners("news");
Also, I don't think your solution of cbProxy = _blank would actually work; that would only affect the cbProxy variable, not any actual socket.io event.
If you want to create listeners that "listens" only once use socket.once('news',func). Socket.io automatically will distroy the listener after the event happened - it's called "volatile listener".
Looking at the code of current version of Socket.io Client (1.4.8) it seems that off, removeAllListeners, removeEventListener are all pointing to the same function.
Calling any of those, providing event name and/or callback, gives the desired result. Not providing anything at all seems to reset everything.
Please do be cautious about the fn/callback argument. It has to be the same instance used in the code.
Example:
var eventCallback = function(data) {
// do something nice
};
socket.off('eventName', eventCallback);
Would work as expected.
Example (will also work):
function eventCallback(data) {
// do something nice
}
socket.off('eventName', eventCallback);
Please be cautious that the callback you are trying to remove is the one that you passed in (this one can bring a lot of confusion and frustration).
This example implements a wrapper around initial callback, trying to remove that would not work as the real callback being added is an undisclosed closure instance: http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/
Here is the link to that specific line in the codebase: https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1597
Socket.io version 0.9.16 implements removeListener but not off.
You can use removeListener instead of off when unsubscribing, or simply implement off as follows:
var socket = io.connect(url);
socket.off = socket.removeListener;
If you are using the Backbone listenTo event subscription approach, you'll need to implement the above as Backbone calls off when unsubscribing events.
I found that in socket.io 0.9.11 and Chrome24 socket.io removeListener doesn't work.
this modified version works for me:
EventEmitter.prototype.removeListener = function (name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (io.util.isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].toString() === fn.toString() || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else {
if (list.toString() === fn.toString() || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
}
return this;
};
Since I had a spot of troubles making this work figured I'd chime in here as well, along with a nice updated answer for 2017. Thanks to #Pjotr for pointing out that it has to be the same callback instance.
Example with Angular2 TypeScript in a socket-io.subscriber service. Note the "newCallback" wrapper
private subscriptions: Array<{
key: string,
callback: Function
}>;
constructor() {
this.subscriptions = [];
}
subscribe(key: string, callback: Function) {
let newCallback = (response) => callback(response);
this.socket.on(key, newCallback);
return this.subscriptions.push({key: key, callback: newCallback}) - 1;
}
unsubscribe(i: number) {
this.socket.removeListener(this.subscriptions[i].key, this.subscriptions[i].callback);
}
Removing an event listener on the client
var Socket = io.connect();
Socket.removeListener('test', test);
Also on java client, it can be done the same way with the Javascript client. I've pasted from socket.io.
// remove all listeners of the connect event
socket.off(Socket.EVENT_CONNECT);
listener = new Emitter.Listener() { ... };
socket.on(Socket.EVENT_CONNECT, listener);
// remove the specified listener
socket.off(Socket.EVENT_CONNECT, listener);
Pre-store the events using an array, and by the time you need to unsubscribe them, use the off method, which is a built in method from socket.io:
// init
var events = []
// store
events.push("eventName")
// subscribe
socket.on("eventName", cb)
// remove
events = events.filter(event => event!="eventName")
// unsubscribe
socket.off("eventName")
To add to #Andrew Magee, here is an example of unsubscribing socket.io events in Angular JS, and of course works with Vanilla JS:
function handleCarStarted ( data ) { // Do stuff }
function handleCarStopped ( data ) { // Do stuff }
Listen for events:
var io = $window.io(); // Probably put this in a factory, not controller instantiation
io.on('car.started', handleCarStarted);
io.on('car.stopped', handleCarStopped);
$scope.$on('$destroy', function () {
io.removeListener('car.started', handleCarStarted);
io.removeListener('car.stopped', handleCarStopped);
});
This has helped me in both Angular 8 and React 16.8:
receiveMessage() {
let newCallback = (data) => {
this.eventEmitter.emit('add-message-response', data);
};
this.socket.on('add-message-response', newCallback);
this.subscriptions.push({key: 'add-message-response', callback: newCallback});
}
receiveMessageRemoveSocketListener() {
this.findAndRemoveSocketEventListener('add-message-response');
}
findAndRemoveSocketEventListener (eventKey) {
let foundListener = this.subscriptions.find( (subscription) => subscription.key === eventKey );
if(!foundListener) {
return;
}
this.socket.removeListener(foundListener.key, foundListener.callback);
this.subscriptions = this.subscriptions.filter( (subscription) => subscription.key !== eventKey );
}
Reason for using an Array of Subscriptions is that when you Subscribe to an event multiple times and you don't remove an unsubscribed subscription from the Subscription list you will most probably be right at first time you remove the subscription from the list, but later subscriptions will not be removed as you will be finding first instance only every time you unsubscribe the event.
You can simply call receiveMessage(); to subscribe to an the event and receiveMessageRemoveSocketListener(); to Unsubscribe.

Object with eventhandler overloading

Currently I'm trying to write a resourcemanager in JavaScript. It has two methods, one to add a image resource to the manager, and one that preloads all the images after they are added:
ResourceManager = function(){};
ResourceManager.prototype = function(){
var imageList = new Array(),
addImage = function(imageUrl){
imageList.push(imageUrl);
},
loadImages = function(){
//Do stuff for loading here
onComplete();
},
onComplete = function(){
alert('finished');
};
return {
addImage: addImage,
loadImages: loadImages,
onComplete: onComplete
}
}();
Then I want to use it as following:
var rsmgr = new ResourceManager();
rsmgr.onComplete = function(){
alert('completed');
};
rsmgr.addImage('image.png');
rsmgr.loadImages();
You can see a working example on jsfiddle
Now this overload is not working, so why does this happen? I'm guessing it has to do with the prototyping but I can't seem to grasp on how to fix this.
I'm no expert in prototyping, so can't give valid response why it's not working however I can suggest alternative way using "pure function" approach that works just fine:
function ResourceManager() {
this.imageList = new Array();
this.addImage = function(imageUrl) {
this.imageList.push(imageUrl);
};
this.loadImages = function() {
//Do stuff for loading here
this.onComplete();
};
this.onComplete = function() {
alert('finished');
};
};
Having it written like the above cause your original override code to work - updated fiddle.
(If you remove the rsmgr.onComplete override you'll get "finished" as expected)

JavaScript: remove an event listener from within that listener?

I always wondered how clean is such approach - to remove an event listener from within that very listener.
UPDATE:
Internally I keep a hash of objects and listeners, so I potentially can remove event listener from any place. I'm just concerned of removing it from within itself. Will such action do a job actually?
UPDATE
I'm asking about addEventListener, removeEventListener stuff.
You can pass the once option to have a listener act only once, then remove itself. Docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters
Example:
element.addEventListener('eventname', (ev) => {
console.log("event is captured only once.");
// do more stuff...
}, { once: true });
From the same docs link above, modern browser support is good, but is not available for Internet Explorer.
I just saw this because i wondered the exact same question!
arguments.callee is your friend...
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee
so you'd have
blah.addEventListener('click',function(e){
e.source.removeEventListener('click', arguments.callee);
blee bloo bleep
});
this works in Titanium Appcelerator, so it should work in javascript too (because they are The Same Thing Kinda)
NB do NOT add () to the end of arguments.callee in this example, unless you like seeing... bah dum tish!.
In fact, if you don't want to use arguments.callee, this also might work (untested):
blah.addEventListener('click', anyThingYouWantHere = function(e){
e.source.removeEventListener('click', anyThingYouWantHere);
blee bloo bleep
});
Where "anythingYouWantHere" is any variable name you'd like ~ you're effectively "naming" the function as you add it.
I just made a wrapper function that generates a self destructing event listener:
let addSelfDestructingEventListener = (element, eventType, callback) => {
let handler = () => {
callback();
element.removeEventListener(eventType, handler);
};
element.addEventListener(eventType, handler);
};
So far it works great :)
#bharal answer gave me now this solution:
//example
addBlurListener(element, field) {
const listenToBlur = (e) => {
e.target.removeEventListener(e.type, listenToBlur);
//your stuff
};
element.addEventListener('blur', listenToBlur);
},
You could try something like this, depending on how it's called:
some_div.onclick = function () {
...
this.onclick = null;
// or: some_div.onclick = null;
};
Or is it event listeners you're concerned with? Because those are a little bit more complicated.
If you want listener only trigger once, you can use this code:
element.addEventListener('eventname', function callback(){}, { once: true });
Or use wrapper to do the same thing:
function addOneTimeEventListener(element, event, callback) {
const wrapper = e => {
try {callback(e)} finally {
element.removeEventListener(event, wrapper);
};
}
element.addEventListener(event, wrapper);
}
// example
addOneTimeEventListener(document.body, 'click', e => {
console.log('this message only show once.');
});
If you want to decide when to remove listener:
function addEventListener(element, event, callback) {
const wrapper = e => {
callback(e, () => element.removeEventListener(event, wrapper));
}
element.addEventListener(event, wrapper);
}
// example
let count = 0;
addEventListener(document.body, 'click', (e, closeListener) => {
console.log(`click:${++count}`);
if(count == 3) closeListener();
});
If you use jQuery, you will probably have some convenience methods exposed for interacting with event handlers -- see bind()/unbind(), delegate()/undelegate(), one(), and similar methods.
I don't have much experience with other frameworks, but I'd imagine they offer similar functionality. If you're not using a framework at all, #sdleihssirhc has an acceptable answer.
EDIT: Ah, perhaps you're looking for something more like addEventListener() and removeEventListener(). Again, a framework will offer some convenience to your interactions and save you the trouble of reinventing the wheel in some cases.

Categories

Resources