Html Audio Tag and cache - javascript

Im currently building a Web App which plays sound files now and again via JS:
var sound = new Audio();
function playSound(audioUrl) {
sound.src = audioUrl;
sound.play();
}
playSound("/my/audio/url.wav");
As timing is essential i want to preload all the possible soundfiles before the user can interact with the application. im doing this simply via this snippet of code:
var preloader = new Audio();
preloader.addEventListener("loadeddata", checkPreload(), true);
preloader.src = audioUrl;
This works fine and i can see in Chromes DevTools network-section that all the Soundfiles are loaded. Also when im playing a sound it does not make another request.
If i wait a minute however and play the sound again (without reloading the page or anything like that), then the wav file is reloaded from the webserver and taking again 200 ms of loading time.
I wonder how can i prevent this. Can i manually extend the life of the audiofile cache through some setting in the response headers on the server? Can i manipulate the Audio() Tag somehow to allow me to play the same audio without reloading it from the server?
I looked into the ServiceWorkers and AppCache, but ServiceWorkers doesent support all Browsers and AppCache seems to be also depricated on Chromium browsers, so i dont exactly know how to proceed here.
Any help would be appriciated,
cheers

Using createObjectURL, it will only request it once.
var url;
fetch("/path/to/file")
.then(response => response.blob())
.then(blob => {
url = URL.createObjectURL(blob);
// now you can play all the audio, setting the src value to the url variable
// e.g.
playSound(url);
});

Related

Trouble with playing sounds from JavaScript [duplicate]

I'm trying to get sound working on my iPhone game using the Web Audio API. The problem is that this app is entirely client side. I want to store my mp3s in a local folder (and without being user input driven) so I can't use XMLHttpRequest to read the data. I was looking into using FileSystem but Safari doesn't support it.
Is there any alternative?
Edit: Thanks for the below responses. Unfortunately the Audio API is horribly slow for games. I had this working and the latency just makes the user experience unacceptable. To clarify, what I need is sounething like -
var request = new XMLHttpRequest();
request.open('GET', 'file:///./../sounds/beep-1.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
dogBarkingBuffer = buffer;
}, onError);
}
request.send();
But this gives me the errors -
XMLHttpRequest cannot load file:///sounds/beep-1.mp3. Cross origin requests are only supported for HTTP.
Uncaught Error: NETWORK_ERR: XMLHttpRequest Exception 101
I understand the security risks with reading local files but surely within your own domain should be ok?
I had the same problem and I found this very simple solution.
audio_file.onchange = function(){
var files = this.files;
var file = URL.createObjectURL(files[0]);
audio_player.src = file;
audio_player.play();
};
<input id="audio_file" type="file" accept="audio/*" />
<audio id="audio_player" />
You can test here:
http://jsfiddle.net/Tv8Cm/
Ok, it's taken me two days of prototyping different solutions and I've finally figured out how I can do this without storing my resources on a server. There's a few blogs that detail this but I couldn't find the full solution in one place so I'm adding it here. This may be considered a bit hacky by seasoned programmers but it's the only way I can see this working, so if anyone has a more elegent solution I'd love to hear it.
The solution was to store my sound files as a Base64 encoded string. The sound files are relatively small (less than 30kb) so I'm hoping performance won't be too much of an issue. Note that I put 'xxx' in front of some of the hyperlinks as my n00b status means I can't post more than two links.
Step 1: create Base 64 sound font
First I need to convert my mp3 to a Base64 encoded string and store it as JSON. I found a website that does this conversion for me here - xxxhttp://www.mobilefish.com/services/base64/base64.php
You may need to remove return characters using a text editor but for anyone that needs an example I found some piano tones here - xxxhttps://raw.github.com/mudcube/MIDI.js/master/soundfont/acoustic_grand_piano-mp3.js
Note that in order to work with my example you're need to remove the header part data:audio/mpeg;base64,
Step 2: decode sound font to ArrayBuffer
You could implement this yourself but I found an API that does this perfectly (why re-invent the wheel, right?) - https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js
Resource taken from - here
Step 3: Adding the rest of the code
Fairly straightforward
var cNote = acoustic_grand_piano.C2;
var byteArray = Base64Binary.decodeArrayBuffer(cNote);
var context = new webkitAudioContext();
context.decodeAudioData(byteArray, function(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer;
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.noteOn(0);
}, function(err) { console.log("err(decodeAudioData): "+err); });
And that's it! I have this working through my desktop version of Chrome and also running on mobile Safari (iOS 6 only of course as Web Audio is not supported in older versions). It takes a couple of seconds to load on mobile Safari (Vs less than 1 second on desktop Chrome) but this might be due to the fact that it spends time downloading the sound fonts. It might also be the fact that iOS prevents any sound playing until a user interaction event has occured. I need to do more work looking at how it performs.
Hope this saves someone else the grief I went through.
Because ios apps are sandboxed, the web view (basically safari wrapped in phonegap) allows you to store your mp3 file locally. I.e, there is no "cross domain" security issue.
This is as of ios6 as previous ios versions didn't support web audio api
Use HTML5 Audio tag for playing audio file in browser.
Ajax request works with http protocol so when you try to get audio file using file://, browser mark this request as cross domain request. Set following code in request header -
header('Access-Control-Allow-Origin: *');

Fully preload html5 video from CDN in Safari

I'm looking for a solution to fully preload an html5 video so that I can play it through and seek to different times without any risk of buffering. I've seen solutions that involve using xhr to download the video file as a 'blob' type and subsequently construct a url to that blob using the createObjectURL method. This is the code example in the solution I mentioned above:
var r = new XMLHttpRequest();
r.onload = function() {
myVid.src = URL.createObjectURL(r.response);
myVid.play();
};
if (myVid.canPlayType('video/mp4;codecs="avc1.42E01E, mp4a.40.2"')) {
r.open("GET", "slide.mp4");
}
else {
r.open("GET", "slide.webm");
}
r.responseType = "blob";
r.send();
This works for me in Chrome and Firefox, but not in Safari when using a video hosted on a CDN. This solution does work in Safari if I use a video hosted on the same server. I found this Safari bug, although I'm not sure if the bug is still valid. There's no mention of the Safari bug on the page with the above solution. I've seen another method which essentially pauses the video and waits for it to buffer to 100%, but Chrome doesn't seem to ever fully buffer the video.
I looked into PreloadJS, which apparently supports video preloading, but I couldn't find any working examples. I also looked into html5Preloader, but again I couldn't figure out what to do once the finish event was fired.
I'm not sure if it makes any difference, but I'm using Videogular to play my video, which needs to be fed a video url. I suppose if I use some preloader library such as PreloadJS or html5Preloader, which I'm guessing would in turn use xhr for video, I would need access to a new blob url in my finished handler.
Has anyone come up with a video preloading solution that works in Safari? Thanks in advance.
It turns out the problem was being caused by the content type response header on the videos coming from Amazon S3. They were set to octet-stream, which Chrome and Firefox were able to handle, but Safari threw a media error 4. Changing the content type in the Amazon S3 admin site to 'video/mp4' solved the problem for me.
More info about Safari and octet-stream here in the 'Known issues' tab: http://caniuse.com/#feat=bloburls

Cant abort MJPEG in android "internet" app, or android chrome

I have a python/flask application that sends mjpeg video to a element.
It works fine regarding the streaming, but I have a problem aborting the video. To abort the video at my phone (android) I have to click a link to another page or else the stream continues.
Currently I am controlling the stream with javascript. Setting the "src" to either a url for a static image from the cam, or an url to the video stream.
But between the src-change I first change it to "#".
A problem, using flask, is that when 1 client is receiving the stream (using generator & yield) no other cant communicate with the server. This might be the source to the problem?!
So, With javascript I control the stream with the following code:
if (streaming==false){
document.getElementById(img_id).src=C_vidsource;
streaming = true;
} else {
var currDate = new Date();
document.getElementById(img_id).src="#";
document.getElementById(img_id).src=C_statimage + "?" + currDate.getTime();
streaming = false;
}
I control this using a simple
I Think that androids web browser differ from the one I am using on the computer. It seems like it tries to download content before changing anything on the page. So it lets the videostream continue until the new image is loaded. But the new image will not be loaded until the stream has stopped.
is there a way to solve this?
Thanks!
Directly after i posted the question I found the solution.
I added an delay between the two src-changes.
after:
document.getElementById(img_id).src="";
I added
sleep(1000);
And sleep is a function I created (a very dirty):
function sleep(ms){
stoptime = Date.now() + ms;
while(Date.now() < stoptime){ }
return;
}
I guess that for a longer sleep this is not a good solutoion, but it solves my problem, or at least gives me a hint about what to search for.

How to play ultrasonic WAV/PCM file in javascript without losing data

I need to loop an audio file (WAV, OGG or raw PCM) in the browser that contains segments which are unheard (ultrasonic) by the human ear (yet contain data which is valuable to me).
Using Chrome on Mac, I've noticed that if the segments of unheard sound are relatively short, I get all the data back (heard + unheard). In contrast, if the segments of unheard sound are longer than a certain threshold, it will fade out the whole channel quickly and effectively cancel the rest of the file completely, until the next loop cycle begins.
The way I'm loading and playing the sound is like so:
var b = msg.data; // binary msg received from websocket
b.type = "audio/wav";
var URLObject = window.webkitURL || window.URL;
var url = URLObject.createObjectURL(b);
var snd = document.createElement("audio");
snd.setAttribute("src", url);
snd.addEventListener("loadeddata", function() {
snd.loop = true;
snd.muted = false;
snd.play();
});
I'm looking for a way to cancel this automatic filtering of unheard sounds. Eventually, I would like a way to do this cross-browser. If not possible using JavaScript, a Flash solution will also be accepted.
Sample ultrasonic WAV files (~1MB each):
https://drive.google.com/file/d/0B5sMkxczD6sNbm04MmxMTmIwdlk/edit?usp=sharing
https://drive.google.com/file/d/0B5sMkxczD6sNal91WUhRNWo2d3c/edit?usp=sharing
There isn't a single approach that will work on all browsers, unfortunately.
For most browsers on the desktop and iOS too you can use the Web Audio API as shown here:
http://www.html5rocks.com/en/tutorials/webaudio/intro/
For IE/Android you need to use Flash to play a WAV/PCM, or play OGG with HTML5 Audio tag, but the latter may lose the ultrasonic frequencies.
So in general, you need to write code that will check what the current browser supports and use that, starting with Web Audio API, then trying HTML5 Audio, then Flash.

Streaming MP3 instead of downloading it with HTML5 audio tag

In the document.ready function, I have this:
audioElement = document.createElement('audio');
audioElement.setAttribute('src', 'http://www.mfiles.co.uk/mp3-downloads/Toccata-and-Fugue-Dm.mp3');
$('#ToggleStart').click(function () {
audioElement.play();
});
$('#ToggleStop').click(function () {
audioElement.pause();
});
The problem is that the MP3 is downloaded when the page loads, which causes significant load time since the MP3 is over 2MB. What I want is the MP3 to be streamed. Is this possible and if so, what do I need to change?
jsFiddle here
You're very close to getting it right. I've had a look at your JSFiddle and noticed that the audio does stream already (I can play the file before it's finished downloading). You can easily see this by checking the Network traffic in your browser:
Chrome displays 'partial content' but is playing the mp3 at the same time. Your specific problem seems to be that it is downloading and playing too early. So if we take a look a the spec we can see some options.
preload = "none" or "metadata" or "auto" or "" (empty string) or empty
Represents a hint to the UA about whether optimistic downloading of the audio stream itself or its metadata is considered worthwhile.
- "none": Hints to the UA that the user is not expected to need the audio stream, or that minimizing unnecessary traffic is desirable.
- "metadata": Hints to the UA that the user is not expected to need the audio stream, but that fetching its metadata (duration and so on) is desirable.
- "auto": Hints to the UA that optimistically downloading the entire audio stream is considered desirable.
As you're not displaying any information about the audio file we can ignore the metdata option, this means you want to set the preload="none" attribute. Therefore if you change your JSFiddle slightly to dynamically set this:
audioElement.setAttribute('preload', "none");
audioElement.setAttribute('src', 'http://www.mfiles.co.uk/mp3-downloads/Toccata-and-Fugue-Dm.mp3');
Here is a JSFiddle showing the result, if you bring up the network tab in Chrome you'see see that the download doesn't start till you begin playing the mp3.

Categories