I am using MediaElementAudioSourceNode to play audio files (refer to this question) and it works as expected but I have a doubt about on how can I dispose the Node after it has been disconnected.
Currently I have this code:
//Do we have already an MediaElementSourceNode ?
if (this._sourceNode) {
console.warn("THERE IS AUDIO PLAYING!");
//this._audioContainer.addTextTrack
// disconnect and remove all references.
this._audioContainer.pause();
this._audioContainer = undefined;
this._sourceNode.disconnect();
this._sourceNode = undefined;
console.warn("The _audioContainer was paused and the _sourceNode disconnected");
}
this._audioContainer = new Audio(filePath);
this._audioContainer.oncanplaythrough = () => {
clearTimeout(securityTimeout);
console.log(this._audioContainer.readyState);
console.dir(this._audioContainer);
this.audioPlayerActive = true;
// MediaElementSource are ONE TIME ONLY use kind of Node
this._sourceNode = this._AudioContext.createMediaElementSource(this._audioContainer);
this._sourceNode.connect(this._EqNode);
this._audioContainer.play();
console.log("playAudioFile() END");
};
this._audioContainer.onended = () => {
console.log("__audioContainer.onended()");
this._audioContainer = undefined;
this._sourceNode.disconnect();
this._sourceNode = undefined;
};
Using the Web Audio inspector on firefox I can see that every time I run this code (on button click) it creates a new MediaElementAudioSourceNode disconnecting the previously created one if detected. Also when the audio ends, it disconnect it. BUT The Nodes are still there even after several minutes:
I want to prevent a memory leak. How can I dispose them?
Edit:
There is no function available (I think)
There isn't currently any way to do this: https://github.com/WebAudio/web-audio-api/issues/1202
Related
We are playing a clicker game on a website where a button will be displayed at a random time, first 7 players to click it will be shown as the winners of that round. I wrote the clicker function bellow and always use it on my Chrome console.
(function () {
setInterval(() => {
const button = document.querySelector(“.click-button);
if (!button) return;
button.click();
}, 5);
})();
I was playing it with mobile network of 50 - 70 mbps of speed and was among the top 5 until when other players started using VPS Machine which have over 4gbps of internet speed. I now have a VPS running Windows server 2022 but cant still perform better. So My question is, Is it that my VPS speed is slower than their own or My Laptop have less specification than their own or I need a better javascript code than the one above?
Running the above code on a VPS server with a download speed of 4.3gbps and upload speed of 4.1gbps through a browser console and click faster
Instead of polling for the button, you could use a MutationObserver that will notify you when the button changes or is inserted into the document. Unless you can get to the event that triggers the button activation, this is probably the fastest you can do.
Assuming that the button is inserted into a <div id="button-container"></div>:
const callback = mutations => {mutations.forEach(m => m.addedNodes.forEach(node => node.tagName === 'BUTTON' && node.click()))}
const observer = new MutationObserver(callback)
const container = document.getElementById('button-container')
observer.observe(container, {childList: true})
Have a look at this codepen
Replacing the forEach() with a for will give you a few additional nanoseconds.
Your code can be a little faster by caching the function and the button outside the interval
(function() {
const button = document.querySelector(".click-button");
const buttonClick = () => button.click();
if (button) setInterval(buttonClick, 5);
})();
If the button does not exist at all, then the code above will not work. Then you do need the mutation oberver
I have been through many similar QnAs and articles. Overall it seems the only solution is to connect the userMedia stream to video then use a timer function to take snapshot from the video and put it on a canvas. Now manipulate the stuff on canvas and stream that.
The problem is the timer function. If I use setTimeout or requestAnimationFrame then the video stream becomes choppy or completely stops if the user moves away from the tab. I somehow worked around that using Web worker (using below code).
(function () {
let blob = new Blob([`
onmessage = function(oEvent) {
setTimeout(() => postMessage('tick'), oEvent[1]);
};
`],{type: 'text/javascript'});
window.myTimeout = function (cb, t) {
const worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(oEvent) {
cb();
worker.terminate();
};
worker.postMessage(['start', t]);
};
}());
However this works well on Chrome but on Safari (OSX) this fails completely. Safari seems to pause the JS engine completely. This issue does not happen if I stream userMedia directly.
I've been trying to think on some ideas on what I could make with JavaScript using Web Audio API. I know that depending on the user's browser I know that sometimes it won't let you play audio without a user gesture of some sort. I been doing some research on how to do it and they are pretty useful ways but the problem is that some developers found different ways to do it. For example:
Using a audioContext.resume() and audioContext.suspend() methods to unlock web audio by changing it's state:
function unlockAudioContext(context) {
if (context.state !== "suspended") return;
const b = document.body;
const events = ["touchstart", "touchend", "mousedown", "keydown"];
events.forEach(e => b.addEventListener(e, unlock, false));
function unlock() {context.resume().then(clean);}
function clean() {events.forEach(e => b.removeEventListener(e, unlock));}
}
creating an empty buffer and play it to unlock web audio.
var unlocked = false;
var context = new (window.AudioContext || window.webkitAudioContext)();
function init(e) {
if (unlocked) return;
// create empty buffer and play it
var buffer = context.createBuffer(1, 1, 22050);
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
/*
Phonograph.js use this method to start it
source.start(context.currentTime);
paulbakaus.com suggest to use this method to start it
source.noteOn(0);
*/
source.start(context.currentTime) || source.noteOn(0);
setTimeout(function() {
if (!unlocked) {
if (source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE) {
unlocked = true;
window.removeEventListener("touchend", init, false);
}
}
}, 0);
}
window.addEventListener("touchend", init, false);
I know mostly how both of these methods work but
my question is what is going on here, what is the difference and which method is better etc?
And can someone please explain to me about this source.playbackState from an AudioBufferSourceNode Please? I never heard about that property on there before. It even doesn't have an article or get mentioned in the Mozilla MDN Website.
Also as a bonus question (which you don't have to answer), If both of these methods are useful then could it be possible to put them together as one if you know what I mean?
Sorry if that is a lot to ask. Thanks :)
resources:
https://paulbakaus.com/tutorials/html5/web-audio-on-ios/
https://github.com/Rich-Harris/phonograph/blob/master/src/init.ts
https://www.mattmontag.com/web/unlock-web-audio-in-safari-for-ios-and-macos
Both methods work, but I find the first (resume context in a user gesture) to be cleaner. The AudioBufferSource method is a kind of gross hack for backward compatibility with old sites that started playing buffers in a user gesture. This method doesn't work if you don't start the buffer from a gesture. (I think.)
Which one you want to use is up to you.
I want to create a seamless loop of an audio file. But in all approaches I used so far, there was a noticeable gap between end & start.
This is what I tried so far:
First approach was to use the audio in the HTML and it loops but there is still a noticeable delay when going from the end of the track to the beginning.
<audio loop autoplay>
<source src="audio.mp3" type="audio/mpeg">
<audio>
Then I tried it from JavaScript with the same result:
let myAudio = new Audio(file);
myAudio.loop = true;
myAudio.play();
After that I tried this (according to this answer)
myAudio.addEventListener(
'timeupdate',
function() {
var buffer = .44;
if (this.currentTime > this.duration - buffer) {
this.currentTime = 0;
this.play();
}
},
false
);
I played around with the buffer but I only got it to reduce the gap but not leave it out entirely.
I turned to the library SeamlessLoop (GitHub) and got it to work to loop seamlessly in Chromium browsers (but not in the latest Safari. Didn't test in other browsers). Code I used for that:
let loop = new SeamlessLoop();
// My File is 58 Seconds long. Btw there aren't any gaps in the file.
loop.addUri(file, 58000, 'sound1');
loop.callback(soundsLoaded);
function soundsLoaded() {
let n = 1;
loop.start('sound' + n);
}
EDIT: I tried another approach: Looping it trough two different audio elements:
var current_player = "a";
var player_a = document.createElement("audio");
var player_b = document.createElement("audio");
player_a.src = "sounds/back_music.ogg";
player_b.src = player_a.src;
function loopIt(){
var player = null;
if(current_player == "a"){
player = player_b;
current_player = "b";
}
else{
player = player_a;
current_player = "a";
}
player.play();
/*
3104.897 is the length of the audio clip in milliseconds.
Received from player.duration.
This is a different file than the first one
*/
setTimeout(loopIt, 3104.897);
}
loopIt();
But as milliseconds in browsers are not consistent or granular enough this doesn't work too well but it does work much better than the normal "loop" property of the audio.
Can anyone guide me into the right direction to loop the audio seamlessly?
You can use the Web Audio API instead. There are a couple of caveats with this, but it will allow you to loop accurately down to the single sample level.
The caveats are that you have to load the entire file into memory. This may not be practical with large files. If the files are only a few seconds it should however not be any problem.
The second is that you have to write control buttons manually (if needed) as the API has a low-level approach. This means play, pause/stop, mute, volume etc. Scanning and possibly pausing can be a challenge of their own.
And lastly, not all browsers support Web Audio API - in this case you will have to fallback to the regular Audio API or even Flash, but if your target is modern browsers this should not be a major problem nowadays.
Example
This will load a 4 bar drum-loop and play without any gap when looped. The main steps are:
It loads the audio from a CORS enabled source (this is important, either use the same domain as your page or set up the external server to allow for cross-origin usage as Dropbox does for us in this example).
AudioContext then decodes the loaded file
The decoded file is used for the source node
The source node is connected to an output
Looping is enabled and the buffer is played from memory.
var actx = new (AudioContext || webkitAudioContext)(),
src = "https://dl.dropboxusercontent.com/s/fdcf2lwsa748qav/drum44.wav",
audioData, srcNode; // global so we can access them from handlers
// Load some audio (CORS need to be allowed or we won't be able to decode the data)
fetch(src, {mode: "cors"}).then(function(resp) {return resp.arrayBuffer()}).then(decode);
// Decode the audio file, then start the show
function decode(buffer) {
actx.decodeAudioData(buffer, playLoop);
}
// Sets up a new source node as needed as stopping will render current invalid
function playLoop(abuffer) {
if (!audioData) audioData = abuffer; // create a reference for control buttons
srcNode = actx.createBufferSource(); // create audio source
srcNode.buffer = abuffer; // use decoded buffer
srcNode.connect(actx.destination); // create output
srcNode.loop = true; // takes care of perfect looping
srcNode.start(); // play...
}
// Simple example control
document.querySelector("button").onclick = function() {
if (srcNode) {
srcNode.stop();
srcNode = null;
this.innerText = "Play";
} else {
playLoop(audioData);
this.innerText = "Stop";
}
};
<button>Stop</button>
There is a very simple solution for that, just use loopify it makes use of the html5 web audio api and works perfectly well with many formats, not only wav as the dev says.
<script src="loopify.js" type="text/javascript"></script>
<script>
loopify("yourfile.mp3|ogg|webm|flac",ready);
function ready(err,loop){
if (err) {
console.warn(err);
}
loop.play();
}
</script>
This will automatically play the file, if you want to have start and stop buttons for example take a look at his demo
I'm building an AIR application which loads and plays mp3's from a users computer, I'm using the code pretty much straight out the book to do this:
function startTrack()
{
if (isStarted == 0)
{
theSound = new air.Sound();
urlReq = new air.URLRequest(...dynamicaly generated extension);
theSound.load(urlReq);
soundHandle = theSound.play();
isStarted = 1;
}
else
{
soundHandle.stop();
soundHandle = null;
theSound = null;
urlReq = null;
isStarted = 0;
startTrack();
}
}
There are a series of links on the page of the app which play different mp3's, when you click on one it passes the path of the sound to urlReq and plays it. When you click on a second sound it stops the first playing and plays the next one. What I thought would happen is the old sound would be destroyed by call theSound = null etc but it just seems to stop the sound play the new sound and keep the old one loaded so you eventually run out of system memory after lots of tracks have been started and kept loaded.
So if anyone knows how to unload a sound or how to generally dump things from the system memory the app is using it would be much appreciated.
Thanks all
Will
You call air.System.gc() after deleting all the related elements.