I'm currently writing an application that makes use of the HTML5 Audio API. In Chrome, IE and Firefox I've noticed I can create a Javascript audio object, set it to play a sound file, then make it undefined and the sound will still play, as per this example:
var a = new Audio;
a.src = 'longAudioFile.mp3';
a.play();
a = undefined;
As I am working with many Audio objects in a similar way, will this cause memory leaks if I set one to undefined or will the browser clean it up when it's finished playing/set to paused?
According to spec:
Media elements must not stop playing just because all references to them have been removed; only once a media element is in a state where no further audio could ever be played by that element may the element be garbage collected.
It is possible for an element to which no explicit references exist to play audio, even if such an element is not still actively playing: for instance, it could have a current media controller that still has references and can still be unpaused, or it could be unpaused but stalled waiting for content to buffer.
Playing the Media Resource
There are also cleanup instructions:
<...> to release resources held by media elements when they are done playing, either by being very careful about removing all references to the element and allowing it to be garbage collected, or, even better, by removing the element's src attribute and any source element descendants, and invoking the element's load() method.
Best practices for authors using media elements
As much as I know The browser will clean it up when it's finished playing.
Related
I get an audio element and play the sound like so:
let audio = document.querySelector(`audio[data-key="${e.key}"]`);
audio.play();
However sometimes (I use Chrome) there is initially a delay when I play a sound, so I also added this code:
let audioElems = document.querySelectorAll('audio');
audioElems.forEach(function (audio) {
new Audio(`./sounds/${audio.dataset.key}.mp3`);
});
It seemed to make a difference at first but now again sometimes I get the delay. Does the extra code make a difference, will using new Audio actually preload the sound file?
It doesn't necessarily preload it, but it creates an HTMLAudioElement with the preload attribute set to auto.
This means that UAs are told that they should preload the resource if they dim it appropriate, e.g, they could ignore it if on mobile data.
console.log(new Audio)
Now to your issue, Chrome is known to not accept more than 6 simultaneous requests. So if your audioElems NodeList contains more than 6 elements, that would be the cause for some of them to be delayed until the first ones are fetched.
Also, there will always be at least a little delay, since all in MediaElement is asynchronous. Depending on what you are after, you may get better results using the Web Audio API.
HTML5 audio/video tags have an optional preload attribute. Is this attribute currently enabled on your audio tag?
https://www.w3schools.com/tags/av_prop_preload.asp
Using the new Audio() constructor defaults to preload="auto", so that does make a difference.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement
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.
I have got an issue using alert() in JavaScript. When message appears, tag stops playing .mp3 file. Is it JavaScript bug or I'm doing something wrong?
alert has nothing to do with your audio stopping. The audio are not running on the same JS thread with your code. Take a look at this demo and the HTML5 specification:
When an audio element is potentially playing, it must have its audio data played synchronised with the current playback position, at the element's effective media volume.
By saying "potentially playing", it means the following:
A media element is said to be potentially playing when its paused attribute is false, the element has not ended playback, playback has not stopped due to errors, the element either has no current media controller or has a current media controller but is not blocked on its media controller, and the element is not a blocked media element.
alert doesn't fit into anywhere in the specification so it should not affect your audio.
So your audio might be stopped by many reasons. It might be that your audio file is broken, or perhaps some of your code isn't functioning as you expected (did you check to see if there is any .pause() method calling from your code?). Without seeing any of your code it is hard to diagnose the problem, but it is sure that alert isn't the one causing the issue.
So I have this audio tag:
<audio src=".....mp3" autoplay id="aud"></audio>
And 20 seconds later I fire this code:
var obj=$('#aud')
obj[0].volume=0;
obj[0].pause();
obj.prop('volume',0);
obj.trigger('pause');
obj.attr('src','');
obj.remove();
console.log('REMOVED!!');
But after all of this, the audio is still playing??
The audio tag has been successfully removed by obj.remove(), but the audio goes on.
The console.log() logs correctly. I get no errors. But despite using several methods to mute, pause and remove the audio tag, the audio goes on.
Can anyone explain why?
I need to purge this audio with salt and flame. Any help will be most appreciated, this is slowly sending me insane...
So I fiddled with this when writing this as an answer and I conclude the following:
Manually creating the audio tag with autoplay property causes it to play even before being added to the DOM tree 2 times(on older jquery implementations). Even after adding that element to the DOM tree, it will be usually impossible to stop both audio streams - prolly one of the streams gets disconnected from the element when second stream is played. Did not dig into that too deeply. I reproduced the issue here:
http://jsfiddle.net/2gaBs/3/
When you click Create w/o autoplay button you can pause it properly, but when you click Create autoplay it will start immedietally (even before adding to the DOM tree) 2 times! You probably didn't notice the 2nd playback(neither did I) because when mp3 is on the same machine 2 audio streams are almost perfectly in sync. Pressing Try stop will stop one of the audio streams (you need to wait 5 seconds, note the setTimeout).
Also note that if you switch to jQuery ver to 1.8.3 or above this issue no longer occurs, so it seems that both of us were working on old jQuery libs that day^^
So 2 solutions are: update jquery version to 1.8.3 or above, or create the element without autoplay property and start it later.
I was wondering if there was any Javascript method that could help getting information on the state of an audio tag:
is the audio playing
has it stopped
is it muted
is it paused
and the sort. I know that we can use play(), pause() and others but not quite sure on how to check in the script that the audio is playing in order to trigger another event/action maybe ?
Yes you can check a number of attributes on the HTML5 media elements, a list of which you can find in the W3C HTML5 Specification itself.
Under the "playback state" list you can see such attributes as paused, muted, ended etc.