js / html5 audio: Why is canplaythrough not fired on iOS safari? - javascript

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.

Related

does new Audio preload the sound file?

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

Shutting down audio in a particular website

I tried to use the following mutationObserver to observe for all audio tags to remove them so to totally shut down sound in a particular website. I ran the script with Greasemonkey.
new MutationObserver(() => {
document.querySelectorAll('audio').forEach(node=>node.remove())
}).observe(document, {subtree: true, childList: true});
This wasn't enough, because some audio tags are loaded before the DOMContentLoaded.
What could I do to remove these audio tags as well (i.e, those loaded before being catched by the mutationObserver)?
I though of using setInterval() each 1 millisecond but this seems to me as something I really need to avoid as it would feel my PC's memory and stuck the browser.
Let me emphasize: It's just a script I ran on whatever website, so there is no HTML or CSS to show in this question.
Not all audio is done with <audio> tags. Audio can come from media elements not attached to the DOM. It can also be produced with the Web Audio API, perhaps even in conjunction with the MediaDevice API. Audio can also be produced by plugins.
You will need to write a browser extension and use the API for the particular browser you're extending to mute the tab.

XMLHttpRequest to download large HTML5 video in chunks?

I am trying to load a large video into a tag using XMLHttpRequest. I've successfully gotten this to work with small video files using the following code:
window.URL = window.URL || window.webkitURL;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'quicktest.mp4', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
var video = document.createElement('video');
video.src = window.URL.createObjectURL(this.response);
video.autoplay = true;
document.body.appendChild(video);
};
xhr.send();
No problem if the video is small. However, my file is quite large and creates an out-of-memory error, causing Chrome to crash. Firefox won't even finish loading it, either. I've seen two seperate instances after hours of searching of people suggesting to 'download the file in chunks' and put them back together before sending it to the tag, and I've even found one very promising solution that deals with this very exact scenario. However, I'm at a complete loss as to implementing this workaround. If someone could point me in the right direction.. what would be required to impliment this fix, or anything, I would extremely appreciate it.
If curious why I'm even bothering to load video using XHR.. I'm wanting to autoplay my large video, but Chrome always jumps the gun and starts playing it immediately, thinking that it can play the entire thing with no problem (unreliable "canplaythrough" event) and I can't seem to find a way to get Chrome to buffer the entire video before playing. So I'm resorting to XHR, but having no luck.
It's unfortunate that the browser needs to do so much copying in this scenario - the code does seem like it should work, since you're creating a blob URL and not a super long dataURI string. But sure enough, this method is very slow. However, there is another solution that should give you what you want without messing about with XHR.
Create a video element and set src to your mp4 (or webm for firefox) file. Add a listener for the 'progress' event, which should fire regularly as the browser downloads the file. In that listener, you can check the buffered property to see how much of the video has downloaded and make your own decision as to whether you're ready to start playing.
Now, here it gets a little bit ugly. Even with preload, the browser still will only buffer a little bit of the video, probably about the same amount that it needs to fire canplaythrough. You'll only get one or two progress events, and then nothing. So, when you create the video element, set it to autoplay, and then hide it and mute it. That should force the browser to download the rest of the video and trigger more progress events. When it's fully buffered, or enough to satisfy you, set currentTime back to zero, unmute and unhide.

Animation runs slower and doesn't replay without page reload in chrome

Hi I'm following a tutorial in using easelJS for writing browser based games. And the tutorial is going fine but I've noticed some problems whilst playing the animations on chrome.
Chrome runs the animations slower and once played through once the same animation will not play again unless I reload the page.
Running in Firefox it doesn't have any of these problems.
Here's the link: (use inspect element for code)
http://david.blob.core.windows.net/easeljstutorials/easelJSSpritesTutorial03.html
I've heard there are some caching problems in chrome so I thought this might be the problem.
Manually deleting the cache does in fact allow the animation to play again without a page reload, but it still runs slowly (compare it to firefox).
As I want to code for cross browser compatibility is there a supported way in chrome to combat these problems? Such as blocking storing the images in cache or something? (A last resort I hope)
Thanks in advance,
Kyohei
EDIT: It seems the speed of the animation is the same on ie10 so not sure what speed it should be you know.
The reason this will not work after a "reset" is that you are relying on the image load events to kick off the animation. This will work the first time, but the second time, the image is already loaded, and therefore you will never get an onload event from the images (meaning no "startGame()".
Since this is a simple example, maybe the best approach is to create new Images each time. Just move these lines into your init:
function init() {
var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();
// Other stuff
}
i read an article Fix your Timestep it will help you , just need to convert it to javascript , read it carefully .

How to cancel an image from loading

Suppose in Javascript that you assign the SRC to an IMG tag. It is a large SRC and you want to cancel it before it has finished loading. Assigning the SRC to another image will not stop the data from loading.
That is, in the middle of the load you can assign the SRC to another smaller image and the smaller image will be loaded and appear in your browser. However, the original SRC still continues downloading.
Similarly, deleting the IMG node will not prevent the SRC from continuing to download. No guesses please, look at the repro steps.
REPRO
Load this URL in Chrome in Windows: http://68.178.240.17/imgLoadTest/imgLoadTest.htm
Open up the developer panel by pressing CTRL-SHIFT-J
On the top row of icons in the Chrome developer panel click the Network icon to watch network activity.
On the web page loaded in Step 1 click the Load Image button and watch the developer panel as a large (32meg) image starts loading.
On the web page click the Try To Cancel button to load a different image.
A small image loads, but watch the network in the developer panel and notice that the large image continues to download.
Quick answer
Setting the src attribute of the img tag to the empty string will interrupt the current download, even on Chrome.
Details
Nowadays most of browsers implemented that out-of-standard mechanism thought in the old answer to programmatically abort the connection. This is not achieved through a protocol request, but with a client-side in-memory operation. Keep in mind that is not a standard behaviour, but most of vendors courtesy. That is, it could not work on every browser.
I've prepared a jsfiddle showing this mechanism in action (keep an eye at the network panel of the inspector).
Old answer (2011)
Your browser asks for that image with a specific HTTP GET request, as specified in HTTP protocol. Once it asks for it, the http server starts the transfer.
So, it is between the browser (as http client) and the http server.
Since http protocol does not takes into account the option to abort a transfer, the browser should implement a out-of-standard mechanism to programmatically abort the connection. But since this is not specified in any standard, i think you won't find any way to do that with javascript or any client side code.
Cancel with transparent base64 encoded GIF to avoid additional requests and double page load on android:
var img = new Image();
img.src = 'http://google.com/favicon.ico';
img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAI=;'
Although I can't find the bug report now, I believe this is a long-standing logged WebKit bug. Firefox (and IE I think) have more sane behavior. I'm going back a bit in my brain, but if I recall on those browsers, resetting the img.src will in fact cancel outstanding downloads. I am positive that Firefox does this when a download is "waiting in line" for a chance at an open HTTP connection, and I seem to recall that it will actually force close connections sometimes.
I've never found a way to coax WebKit based browsers into cancelling an img download in progress, whether it is just queued or already actively downloading.
This really sucks if you're making something like a mapping client and need more fine grained control over images.
Setting the .src property to an empty string should cancel the load:
//load image from url
var img = new Image();
img.src = 'http://somedomain.com/image.jpg';
......
//cancel load
img.src = '';
Yes, page is downloaded twice on Android when an img tag has an src="" attribute.
This still occurs on recent Android versions.
I have not found any other browser that does that.
But I found a solution: use an img tag without src attribute.
The ultimative answer is web workers.
Load the image inside an webworker and stopping the web worker will stop the image loading.
You can get the idea from this code:
https://github.com/NathanWalker/ng2-image-lazy-load
this work for me:
imageVarName.src = '';
imageVarName.onload = null;
imageVarName.onerror = null;
the src property must be a valid non-empty URL
So null or empty string are not legal (even though they sometimes work).
Use:
img.src='http://xxxx';
img.onerror=null;
(see here)
Sadly, setting src to an empty string does not work in WebKit-based browsers like Safari. Here is the link to the bug report which Stella mentioned.
https://bugs.webkit.org/show_bug.cgi?id=6656

Categories

Resources