I am implementing sound effects in HTML5 audio but after a while, it just stops playing any audio. The file type is correct because it starts fine but then seems to give up.
var sound = new Audio(url);
function play() {
sound.play();
}
Is there a better way to do this so it consistently plays sound?
Here is a link to my implementation. Easy to reproduce by pressing spacebar a lot until it eventually gives up (also shoot the lights for added sounds). http://craftyjs.com/elevatoraction/
This occurs for me in the latest version of Chrome (8.0)
Edit: I did as Gaurav suggested and only played the same instance of each sound file, but the same sort of problems are present. It will arbitrarily stop playing.
Edit 2: I just noticed that whenever I try to play the sound, the networkState is always 1 which according to this means it hasn't fully loaded. That is odd seeing as it still plays sometimes and even when it plays the networkState is always 1
Don't create a new audio object each time you want to play a sound, reuse the same resource.
var sound1 = new Audio(url);
function playSound1() {
sound1.play();
}
I think this is related to the bug http://code.google.com/p/chromium/issues/detail?id=57070
Related
I'm trying to make a simple piano with js but I don't want to use audio samples, instead I want to generate sound programmatically. To play single sound I am using this code from this blog https://marcgg.com/blog/2016/11/01/javascript-audio/
var context = new AudioContext()
var o = context.createOscillator()
var g = context.createGain()
o.connect(g)
g.connect(context.destination)
o.start(0)
g.gain.exponentialRampToValueAtTime(0.00001, context.currentTime + 5)
I found that after playing it more than about 50 times this method stops working. No sound plays when running this code, context.currentTime does not change and stays 0. How can I fix it without stopping already playing sound?
Don't create a new AudioContext for each note; create one and use it for all of them. This will not only save resources; it will also help with playing multiple notes at precise times if you decide you want to do that.
You're also accumulating oscillators that are still playing, just extremely quietly. You need to stop them sometime. The good news is, the Web Audio API is designed to make it easy to do this:
// stop after 5 seconds from now
o.stop(context.currentTime + 5);
i use the code below to preload an array of audio files (after user interacts with a button starting the process). After all audio files fired "canplaythrough" the code proceeds:
var loaded = 0;
function loadedAudio() {
// this will be called every time an audio file is loaded
// we keep track of the loaded files vs the requested files
loaded++;
console.log(loaded + " audio files loaded!");
if (loaded == audioFiles.length){
// all have loaded
main();
}
}
function preloadsounds()
{
$("#loader").show();
console.log(level.config);
audioFiles = level.config.soundfiles;
// we start preloading all the audio files with html audio
for (var i in audioFiles) {
preloadAudio(audioFiles[i]);
}
}
function preloadAudio(url)
{
console.log("trying to preload "+ url);
var audio = new Audio();
// once this file loads, it will call loadedAudio()
// the file will be kept by the browser as cache
audio.addEventListener('canplaythrough', loadedAudio, false);
audio.addEventListener('error', function failed(e)
{
console.log("COULD NOT LOAD AUDIO");
$("#NETWORKERROR").show();
});
audio.src = url;
}
works great on Android (Chrome and Firefox) but not a single canplaythrough event is fired on iOs Safari (tested live on 5s and emulated X both 11.x). All files are served from the same Origin. I also don't get any Error messages in my log.
What am I missing?
( the basis for the code above i go from: https://stackoverflow.com/a/31351186/2602592 )
Try calling the load() method after setting the src.
function preloadAudio(url)
{
console.log("trying to preload "+ url);
var audio = new Audio();
// once this file loads, it will call loadedAudio()
// the file will be kept by the browser as cache
audio.addEventListener('canplaythrough', loadedAudio, false);
audio.addEventListener('error', function failed(e)
{
console.log("COULD NOT LOAD AUDIO");
$("#NETWORKERROR").show();
});
audio.src = url;
audio.load(); // add this line
}
Have you checked Network/Server and confirmed Safari is downloading the audio files?
If Safari's not downloading the audio (or only loading metadata instead of the full file), you could try setting audio.preload = 'auto' before setting audio.src
In addition to the above, on a project where I'm reusing the same audio element multiple times by reassigning src at runtime, there were several more steps required. Yes, I would not get any canplaythrough event whatsoever if I did not at least
set preload="auto" on the element before setting src
set src
call load() after setting src
but after most of a day of print-statement debugging and setting watchdog timeouts (since iOS inspection through Mac Safari is highly prone to both hardlocks and losing track of where it is....), I inadvertently stumbled across one more factor:
set audio.currentTime=0 before reassigning src
A ==0 check happened to be the gating condition within my watchdog timeout to see if the audio had in fact cascaded through my canplaythrough handler and begun to play, but it turns out that resetting it ahead of time so it would absolutely be 0 afterwards if the load/play failed... made the load not fail. Go figure. I was, for the record, previously also seeing the error 206 in the asset/network inspector on failed files, as reported by Stephen in earlier answer commentary, so I guess maybe iOS always loads a bit of the file, but gives up trying to load any more if the play head is already further than the load progress?
Anyway, this miraculously let the audio load in some circumstances, but if audio load was triggered by e.g. a message arriving from another frame, I still saw no canplaythrough, and possibly no events whatsoever (didn't check for lesser events since recovering from a playback halt due to canplay-but-not-canplaythrough was going to be worse for me than not playing at all). So that watchdog timer from my debugging became structural:
kick off a setTimeout(()=>{if(audio.readyState==4) audio.play(); else presumeError();},1000);
It turns out that most of the time, the audio is in fact loading, Safari just doesn't let you know.
HOWEVER, it also turns out that in some circumstances where you don't get load events, various other tools are equally broken. Like setTimeout(). It flat out doesn't run the callback. There's at least one StackOverflow on this sort of issue from the mid 2010s circa iOS 6 that has been copypasta'd onto a million other sketchier support sites with the same dubious answer involving not using .bind() or else rewriting .bind() but I doubt most folks are using .bind() to begin with. What some other answers point to is https://gist.github.com/ronkorving/3755461 which may be overkill, and which I didn't use in full, but I did steal the rough concept of:
if setTimeout() isn't working (or you just want finer granularity on your load watcher), write your own based on a (requestAnimationFrame || webkitRequestAnimationFrame)(keepTrackOfRequestedAudio); loop.
So now, if preload isn't handled, you get the notice after you manually load(); if manual load() isn't handled, you get the notice when you check in after a delay, and if the delay isn't handled, you at least get the notice (or can proactively give up) by constantly burning cycles to constantly watch state. Of course, none of this guarantees your audio has permission to play to begin with, but that's an entirely different iOS problem (hint: .play() returns a promise, and you can .catch() that promise for permission errors on any platform I've tried so far).
I see a lot of claims to make an audio object work, especially on Safari!
To go quickly I can tell you that you do not need much, just know to run everything on Safari 5 and more, and all browsers.
Force the call to trough by reloading the first file in your list or file if you only use one. The Trough event is the one that will allow the user to read the file since it allows to know if the file is loaded and ready to be read. If you build a player, you must plan and let the user click on Play only if the through has occurred.
ObjectAudio.src = file;
Use trough to update your player
ObjectAudio.addEventListener ('canplaythrough', Function, false);
Use Progress to update the percentage buffering bar.
ObjectAudio.addEventListener ('progress', Function, false);
Use the timeupdate event to update the read bar.
ObjectAudio.addEventListener ('timeupdate', Function, false);
You do not need complex things as I see what you are doing.
** Just one worry about Safari 5 Widows. For the object to work, QuickTime must be installed on the user, otherwise Safari 5 will not recognize the HTML 5 Audio object.
So im working on a HTML5 / javascript rts game. Obv there are several sounds playing all the time. So what happens for me is after a while the sounds kinda "crashes" and all sound from this browser tab stops working. I can only bring back sound by restarting the browser, restarting the tab (= reloading the game) doesnt fix it. This is what i use to play sounds:
// play sound
soundToPlay.load();
soundToPlay.play();
soundToPlay.volume = volume;
Im on win7 and FF. What also happens when i play the game is that the windows process "audiodg" increases in memory usage, and when the sound stops working its usually up at something like 2,5 gb. When i close the browser, most of the time it will go back down to a couple of MB, where it should be normally. This audiodg thing is a known bug (not with my game, but in general), but i do not havy any issues with audiodg outside of my game. Most users of my game seem to not have this problem, only relatively few, but im definetly not the only one, also its definetly not a FF thing only, ive seen it happen on chrome, too.
so, my guess is that you're creating the new Audio(filename) every time it must be played.
so, even creating these with javascript, you end up with an HTML template anyway.
var a = new Audio('file.mp3');
// <audio preload="auto"></audio>
In light of that you should call new Audio(filename) only once, and store it in a variable that can be referenced when the sound should play:
var clone = $(a).clone()[0];
clone.play();
clone.volume = volume;
clone.onended = function() {
$(clone).remove();
}
I have a piece of javascript where I'm playing a sound like so...
var audio = document.createElement('audio');
audio.src = 'folder/test.wav';
audio.play();
I need to play more than one file spaced apart so you know what it's saying. iOS doesn't allow you to use setTimeout. I tried that in separate functions and it fails to sound off the second audio clip when using setTimeout. Any other ideas?
EDIT
I have this function:
function playaudio(file) {
var audio = document.createElement('audio');
audio.src = 'folder/'+file+'.wav';
audio.play();
}
Calling it like so:
playaudio("test1");
playaudio("test2");
All I need it to pause inbetween test1 and test1 so they don't play together. Any ideas?
You can use iOS Native code to trigger Javascript method calls:
[webViewObject stringByEvaluatingJavaScriptFromString:#"playNextSound();"];
Where playNextSound is a JS method defined in the page loaded by the UIWebView.
And instead of setTimeout, you can have native timers using NSTimer to space out your audio plays.
setTimeout() works on iOS. The issue likely has to do with how the browser handles <audio> and <video> content.
iOS will only play <audio> and <video> content if it is triggered by user input. You mention your initial audio.play() call for the first clip works fine, so I'm guessing it's user-triggered. The problem is that functions inside setTimeout() clear the call stack, so they may no longer be considered user-driven by the browser, even if they are preceded by a click event.
The simplest solution is to combine your multiple audio clips into one. You could also pad the clips you want to delay with silence at the start, and play them all right after the click event (assuming the OS version you're targeting supports multiple audio objects at once)
This is how the sound is "stored":
<audio id = "hammer" src="res/sounds/Building/hammer.wav"></audio>
In the actual game (which I use sounds for), I use this to play the sound:
function playSound(soundid)
{
document.getElementById(soundid).currentTime = 0;
document.getElementById(soundid).play();
}
But the sound plays only the first time, and never again!
I tried resetting the "currentTime" in the console to 0, but I literally get this:
>document.getElementById("hammer").currentTime = 0
0
>document.getElementById("hammer").currentTime
0.340...
Why is this happening, how do I fix it?
See this slide and the preceding three slides of Evan Wallace and Justin Ardini's presentation on html5 game dev.
For all the resources to make some awesome games, part of their talk: http://madebyevan.com/gamedevclass/
Audio still doesn't work consistently across all browsers, as of right now:
An element must be reloaded in Chrome or it will only play once
An element must not be reloaded in Firefox or there will be a delay
function playSoundEvent() {
if (window.chrome) elems[index].load()
elems[index].play()
index = (index + 1) % elems.length
}
I had this issue recently with an html5. Worked everywhere except safari.
Using load() before calling play() solved this problem. It also helps to make sure that sound effects do not overlap with heavy clickers when event-handlers trigger sounds.
Here what I used
<audio id="sound-one" preload="auto">
<source src="http://myurl/foo.mp3"></source>
<source src="http://myurl/foo.ogg"></source>
</audio>
click here
Jquery
$("#navigation-id") //this attached is to an element on the page
.mouseover(function() {
sound-one.load();
sound-one.play();
});
A few months before I faced the same issue while developing a Piano in HTML5. When a key was pressed again and again I had to stop, rewind and play the audio on each key press. I used the same code written in this question which worked in Firefox and Safari, but not in Chrome. In Chrome it didn't play again. Finally I had to modify the code to make it work. Added one listener for the event onseeked, then set currentTime = 0 and in the event handler invoked play. This worked for me. Similarly I had to wait for the event of one action before giving next action in many places. It was an old version of Chrome. Later I found out that even some versions of Browsers support Audio, the way each one supports is slightly different. This difference won't be visible if we simply put an audio tag and play the audio, but will experience when we try to control the audio using Javascript. Anyways its all about older versions of Browsers, its much much better in all latest versions. So please check in the latest version of Chrome ( Even my piano worked in Chrome 10 without the code modification ) and regarding the audio format, I would suggest you to keep mp3 and ogg files of your audio instead of single wav file.
For anyone stumbling across this post in the future, the audio tag is really not a great way to do game audio.
I'd highly recommend taking the time to learn the WebAudio API instead.