Chrome issue changing source of audio element when using AudioContext - javascript

The Audio sound is distorded (like playing twice at the same time or something like that) when I change its source dynamically, if the element was used in the createMediaElementSource of an AudioContext.
Here is a minimalist example of the error: http://jsfiddle.net/BaliBalo/wkFpv/ (It works well at first but it is going crazy when you click a link to change the source).
var audio = document.getElementById('music');
var actx = new webkitAudioContext();
var node, processor = actx.createScriptProcessor(1024, 1, 1);
processor.onaudioprocess = function(e) { /* STUFF */ };
processor.connect(actx.destination);
audio.addEventListener('canplay', canPlayFired);
function canPlayFired(event)
{
node = actx.createMediaElementSource(audio);
node.connect(processor);
audio.removeEventListener('canplay', canPlayFired);
}
$('a.changeMusic').click(function(e){
e.preventDefault();
audio.src = $(this).attr('href');
});
If I put node.disconnect(0); before audio.src = ... it works but the data is no more processed. I tried a lot of thing like creating a new context but it seems not to erase the previously set javascript node.
Do you know how I could fix it ?
Thanks in advance.

I would suggest taking a look at this: jsbin.com/acolet/1
It seems to be doing the same thing you are looking for.
I found this from this post.

Related

Better way to call new Audio();

I am making game in browser and use sound effects for example shot, explosion and for every generated instance of classes there is also creating new Audio object which is eating memory so much and app is crashing after 2/3 minutes thats mean is getting very slow. Is any better way to do this? Maybe creating new Audio() in another place but just once and call it when need, not every time when generating new enemy, bullet etc.
For example:
class Bullet extends Common {
constructor() {
this.element = document.createElement("div");
this.audio = new Audio("./audio/LaserShot.wav");
}
And in upper class Spaceship I call it every time I shot pressing space:
executeShot() {
const bullet = new Bullet(this.getCurrentPosition(), this.element.offsetTop, this.area);
bullet.init();
this.bullets.push(bullet);
}
Not sure if this works great in all scenario, but you can try the following code, and see if it works.
<button class="btn">Click</button>
class AudioService {
constructor(initialsetup = 1) {
this._audios = [];
for (let i = 0; i < initialsetup; i++) {
this._audios.push(new Audio());
}
}
/**
* use to get available audio
*/
_getAudioElemToPlay() {
const audios = this._audios.filter(audio => {
// if the audio is empty, without a valid url or if the audio is ended
// TODO: not sure if these attributes are more than enough
return !audio.duration || audio.ended;
});
console.log('audios', audios);
if (audios.length == 0) {
const audio = new Audio();
this._audios.push(audio);
return audio;
}
return audios[0];
}
playAudio(url) {
const player = this._getAudioElemToPlay();
player.src = url;
player.load();
player.play();
}
}
const audioService = new AudioService();
let index = 0;
document.querySelector('.btn').addEventListener('click', function() {
index++;
const audioList = new Array(12).fill(0).map((value, index) => {
return `https://www.soundhelix.com/examples/mp3/SoundHelix-Song-${index}.mp3`;
});
audioService.playAudio(audioList[index % audioList.length]);
})
Here is the link to run the above code, https://codepen.io/hphchan/pen/xxqbezb.
You may also change the audio to other audio as you like.
My main idea to solve the issue, is by reusing the audio element created, by having an array to store it, and reuse the element once it finish playing.
Of course, for the demo, I am playing the audio by using a click button. But definitely, you can plug it into your game.
Hope the solution may help you. In case there are any cases not covering, as I have not much exposure to this area, it would be nice if you can post your modified solution here, so we can all learn together.
Have you looked at the Web Audio API? If it works for you, a single AudioBuffer can hold the audio data in memory for a given cue, and you can play it multiple times by spawning AudioBufferSourceNode objects. If you have many different sounds playing, this might not be much help, but if you are reusing sounds continuously (many laser shots), this could a big help. Another benefit is that this way of playing sounds is pretty low latency.
I just used this for the first time, getting it to work yesterday. But I'm loading it with raw PCM data (floats ranging from -1 to 1). There is surely a way to load this or an equivalent in-memory structure with a wav, but I'm too new to the API to know yet how to do this.

Abnormally High Frame Rate in Three.js

I launched this site yesterday (a site for live editing three.js examples) and found that when making updates to the code or navigating to multiple example files, the frame rate skyrockets to around 1000 f/s.
The first mention of this is here. I'm not sure why the frame rate would increase after updating. The WebGL canvas is inside an iframe, and I'm updating the iframe content with this code (iframe has an id of 'preview):
var previewFrame = document.getElementById('preview');
var preview = previewFrame.contentDocument || previewFrame.contentWindow.document;
preview.open();
preview.write(this.props.code);
preview.close();
Does anyone have an idea how to resolve this? The editing is done with CodeMirror and the site is built with React. All src code is in the repo here.
My guess is you're starting multiple requestAnimationFrame loops.
For example
let numLoops = 0;
const countElem = document.querySelector("#count");
const stats = new Stats();
document.body.appendChild(stats.domElement);
function loop() {
stats.update();
requestAnimationFrame(loop);
}
function startLoop() {
++numLoops;
countElem.textContent = numLoops;
requestAnimationFrame(loop);
}
startLoop();
document.querySelector("button").addEventListener('click', startLoop);
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<button>Click to add another requestAnimationFrame loop</button>
<div>Num Loops Running: <span id="count"></span></div>
The way I made my examples editable and then runable on http://webglfundamentals.org is to run the examples in an iframe using a blob. Anytime the user picks "update" I generate a new blob with their edited source and then set the iframe to that new blob's URL. This means the example gets completely reloaded so any old code/loops/events/webgl contexts, etc are discarded by the browser.
You can see the code here which is effectively
function runLastVersionOfUsersCode() {
var url = getSourceBlob(usersEditedHtml);
someIFrameElement.src = url;
}
var blobUrl;
function getSourceBlob(options, htmlForIFrame) {
// if we already did this discard the old one otherwise
// it will stick around wasting memory
if (blobUrl) {
URL.revokeObjectURL(blobUrl);
}
var blob = new Blob([htmlForIFrame], {type: 'text/html'});
blobUrl = URL.createObjectURL(blob);
return blobUrl;
}
If you look at the actual code for getSourceBlob you'll see it does a little more work but that's basically it above.
To build on gman's helpful answer, I used cancelAnimationFrame in the React render loop (not the threejs render loop). The commit is here: https://github.com/ekatzenstein/three.js-live/commit/2cad65afa5fe066618a7aac179e096ee9e29ed76
//in the iframe
window.parent.three_live = requestAnimationFrame(animate)
//in the parent, on render loop
_resetAnimationFrame(){
//disables abnormally high frame rates
if(window.three_live){
var previewWindow = document.getElementById('preview').contentWindow;
previewWindow.cancelAnimationFrame(window.three_live);
}
}
Doing window.parent wasn't necessary but wanted to reference in the global project.

Playing audio files in javascript

I have many small audio files, and I want to play these files one after another, not all of them at the same time. I have used the object Audio in javascript like this
var audio_1 = new Audio();
var audio_2 = new Audio();
var audio_3 = new Audio();
audio_1.src = "/path1";
audio_2.src = "/path2";
audio_3.src = "/path3";
Now I just need to call the function play for every object, but I need to play the audio_1 alone, and play audio_2 when the first one ended.
The solution I found is to test on the property ended of every object
audio_1.ended; // returns true when it ends playing
I found an object onended inside the audio object, I thought it's a function but it's not, can someone help me and give me the best way to solve this problem.
use addEventListener instead of assigning a function to the onended property:
audio.addEventListener('ended', function() {}); // Do
audio.onended = function() {}; // Don't
So, a IMHO dirty way is this:
audio_1.play();
audio_1.addEventListener('ended', function() {
audio_2.play();
audio_2.addEventListener('ended', function() {
audio_3.play();
};
};

Live processing getUserMedia audio using the ScriptProcessorNode

I'm trying to get some live data on microphone data. So I hooked up a ScriptProcessorNode to the output of my live audio as follows (coffeescript):
audioSource = navigator.getUserMedia({audio:true},(stream)->
source = context.createMediaStreamSource(stream)
analyser = context.createScriptProcessor(1024,1,1)
source.connect(analyser)
analyser.onaudioprocess = (e)->
\\Processing Takes Place here
However the onaudioprocess functions is never being called. What do I need to do to make it run?
ScriptProcesser's onaudioprocess event will not start if its output is not connected to some other node.
You can check this fiddle to see it in action.
var scr = context.createScriptProcessor(1024,1,1);
// uncomment the line below and onaudioprocess will start
//scr.connect(context.destination);
scr.onaudioprocess = function(){
console.log('test');
};
Simply connect the output of your ScriptProcessor to context.destination or a dummy gain node and onaudioprocess will start.
Try like this. I think it will work for you.
var source = context.createMediaStreamSource(stream);
var proc = context.createScriptProcessor(2048, 2, 2);
source.connect(proc);
proc.connect(context.destination);
proc.onaudioprocess = function(event)
{
var audio_data = event.inputBuffer.getChannelData(0)|| new Float32Array(2048);
console.log(audio_data);
iosocket.emit('audiomsg',audio_data);
}

mediaElementjs: how to get instance of the player

I'm stuck with a little problem with MediaElement.js player.
To get the instance of the player, I do this (works with html5 compatible browser):
// Get player
this.playerId = $('div#shotlist-player video').attr('id');
this.player = window[this.playerId];
But it's not working as soon as it fallback in flash. In fact, it's not working because I'm not calling an instance of MediaElement itself. But I don't see how I can call it.
The player is created with
$('video').mediaelementplayer({....});
How can I get the mediaelement object?
------------EDIT----------------
Ok I finally found how to make it works:
// Get player
mePlayer = $('div#shotlist-player video.video-js')[0];
this.player = new MediaElementPlayer(mePlayer);
Now I can user mediaElement instance correctly.
This post is a lot of speculation, but may be correct. Docs are lacking (;
The answer by sidonaldson is perfectly acceptable if you wish to create a new MediaElement instance and get a handle on it. If there's one already present, it seems to try to reinitialize another instance on that element and freaks out.
I am pretty sure mediaelement.js augments the builtin HTML5 controls by providing a JavaScript API to manipulate Flash/Silverlight players via those elements. I may be wrong, but other advice I've seen on this issue in multiple places is to do something like:
$playButton.click(function() {
$('video, audio').each(function() {
$(this)[0].player.play();
});
});
To create a play button as an external DOM element which will fire off all players on the page. This indicates to me that the implementation is something like I've described.
Try:
var player = $('video').mediaelementplayer({
success: function (me) {
me.play();
}
});
// then you can use player.id to return the id
// or player.play();

Categories