Proper onload for <audio> - javascript

I've been looking around and I'm starting to worry that this isn't possible.
Is there any way to make a standard <audio> tag with fallbacks...
<audio>
<source src='song.ogg' type='audio/ogg'></source>
<source src='song.mp3' type='audio/mp3'></source>
</audio>
...have an onload event. I've looked around and all I could find are some hacks that may or may not work (they don't for me on Chrome) and the canplaythrough event.
The reason I want this is because I am making a presentation that has lots of audio clips to play at certain points. I don't want the presentation to start until all of the audio is loaded (otherwise things could get out of sync). I want the clips to be loaded 1 at a time so that I can create a sort of loading bar. I really don't want to resort to using Flash sound because this is supposed to demonstrate pure web technologies.
So basically I've got this one loadAudio function that cycles through the array of audio files to be loaded audioQueue. loadAudio is called once and then it calls itself until all the files are loaded.
Problem is I haven't found the correct event to trigger loading the next file.
loadAudio = function(index)
{
mer = audioQueue[index];
var ob = "<audio id='" + mer + "'><source src='resources/sounds/" + mer + ".ogg' type='audio/ogg'></source><source src='resources/sounds/" + mer + ".mp3' type='audio/mp3'></source></audio>";
$("#audios").append(ob);
$("#" + mer).get(0).addEventListener('A WORKING EVENT RIGHT HERE WOULD BE NICE', function() { alert("loaded");
if (index + 1 < audioQueue) { loadAudio(index + 1); } }, false);
}
So. Any chance for a proper audio onload? I'm basically willing to do anything as long as it's still all HTML and Javascript.

You can use the loadeddata-MediaEvent. For example you can put all of your audio files in an Array and do something like:
var files = ['a.mp3', 'b.mp3'];
$.each(files, function() {
$(new Audio())
.on('loadeddata', function() {
var i = files.indexOf(this);
files.splice(i, 1);
if (!files.length) {
alert('Preloading done!');
}
})
.attr('src', this);
});
EDIT: this would a little more modern approach as of 2016:
var files = ['a.mp3','b.mp3'];
Promise
.all(files.map(function(file) {
return new Promise(function(resolve) {
var tmp = new Audio();
tmp.src = file;
tmp.addEventListener('loadeddata', resolve);
});
})).then(function() {
alert('Preloading done!');
});

I did a small PONG-game with WebGL and some audio-tags for the sounds. I borrowed the audio-implementation from Opera's Emberwind HTML5 implementation: https://github.com/operasoftware/Emberwind/blob/master/src/Audio.js
Their solution worked fine for me (Chrome, Opera and Firefox). Maybe it could be of interest to you? They have some code that will try to find a playable format from line 22 and below.

Related

Create Seamless Loop of Audio - Web

I want to create a seamless loop of an audio file. But in all approaches I used so far, there was a noticeable gap between end & start.
This is what I tried so far:
First approach was to use the audio in the HTML and it loops but there is still a noticeable delay when going from the end of the track to the beginning.
<audio loop autoplay>
<source src="audio.mp3" type="audio/mpeg">
<audio>
Then I tried it from JavaScript with the same result:
let myAudio = new Audio(file);
myAudio.loop = true;
myAudio.play();
After that I tried this (according to this answer)
myAudio.addEventListener(
'timeupdate',
function() {
var buffer = .44;
if (this.currentTime > this.duration - buffer) {
this.currentTime = 0;
this.play();
}
},
false
);
I played around with the buffer but I only got it to reduce the gap but not leave it out entirely.
I turned to the library SeamlessLoop (GitHub) and got it to work to loop seamlessly in Chromium browsers (but not in the latest Safari. Didn't test in other browsers). Code I used for that:
let loop = new SeamlessLoop();
// My File is 58 Seconds long. Btw there aren't any gaps in the file.
loop.addUri(file, 58000, 'sound1');
loop.callback(soundsLoaded);
function soundsLoaded() {
let n = 1;
loop.start('sound' + n);
}
EDIT: I tried another approach: Looping it trough two different audio elements:
var current_player = "a";
var player_a = document.createElement("audio");
var player_b = document.createElement("audio");
player_a.src = "sounds/back_music.ogg";
player_b.src = player_a.src;
function loopIt(){
var player = null;
if(current_player == "a"){
player = player_b;
current_player = "b";
}
else{
player = player_a;
current_player = "a";
}
player.play();
/*
3104.897 is the length of the audio clip in milliseconds.
Received from player.duration.
This is a different file than the first one
*/
setTimeout(loopIt, 3104.897);
}
loopIt();
But as milliseconds in browsers are not consistent or granular enough this doesn't work too well but it does work much better than the normal "loop" property of the audio.
Can anyone guide me into the right direction to loop the audio seamlessly?
You can use the Web Audio API instead. There are a couple of caveats with this, but it will allow you to loop accurately down to the single sample level.
The caveats are that you have to load the entire file into memory. This may not be practical with large files. If the files are only a few seconds it should however not be any problem.
The second is that you have to write control buttons manually (if needed) as the API has a low-level approach. This means play, pause/stop, mute, volume etc. Scanning and possibly pausing can be a challenge of their own.
And lastly, not all browsers support Web Audio API - in this case you will have to fallback to the regular Audio API or even Flash, but if your target is modern browsers this should not be a major problem nowadays.
Example
This will load a 4 bar drum-loop and play without any gap when looped. The main steps are:
It loads the audio from a CORS enabled source (this is important, either use the same domain as your page or set up the external server to allow for cross-origin usage as Dropbox does for us in this example).
AudioContext then decodes the loaded file
The decoded file is used for the source node
The source node is connected to an output
Looping is enabled and the buffer is played from memory.
var actx = new (AudioContext || webkitAudioContext)(),
src = "https://dl.dropboxusercontent.com/s/fdcf2lwsa748qav/drum44.wav",
audioData, srcNode; // global so we can access them from handlers
// Load some audio (CORS need to be allowed or we won't be able to decode the data)
fetch(src, {mode: "cors"}).then(function(resp) {return resp.arrayBuffer()}).then(decode);
// Decode the audio file, then start the show
function decode(buffer) {
actx.decodeAudioData(buffer, playLoop);
}
// Sets up a new source node as needed as stopping will render current invalid
function playLoop(abuffer) {
if (!audioData) audioData = abuffer; // create a reference for control buttons
srcNode = actx.createBufferSource(); // create audio source
srcNode.buffer = abuffer; // use decoded buffer
srcNode.connect(actx.destination); // create output
srcNode.loop = true; // takes care of perfect looping
srcNode.start(); // play...
}
// Simple example control
document.querySelector("button").onclick = function() {
if (srcNode) {
srcNode.stop();
srcNode = null;
this.innerText = "Play";
} else {
playLoop(audioData);
this.innerText = "Stop";
}
};
<button>Stop</button>
There is a very simple solution for that, just use loopify it makes use of the html5 web audio api and works perfectly well with many formats, not only wav as the dev says.
<script src="loopify.js" type="text/javascript"></script>
<script>
loopify("yourfile.mp3|ogg|webm|flac",ready);
function ready(err,loop){
if (err) {
console.warn(err);
}
loop.play();
}
</script>
This will automatically play the file, if you want to have start and stop buttons for example take a look at his demo

Dynamically created HTML5 audio is not playable in some browsers?

I am creating an audio element in JavaScript and then appending it to my document. I am controlling it through JavaScript as well. It works fine in Firefox, Chrome, and Opera but not on IE and Safari. In those two browsers, the readyState of the audio element never changes from 0 nor do any associated events fire. Here is my basic program:
var init = function() {
var audioElement = createAudioElement();
audioElement.addEventListener('canplay', function() {
audioElement.play();
trace(audioElement.readyState);
}, false);
document.body.appendChild(audioElement);
//audioElement.play();
}
var createAudioElement = function() {
var m4a = 'song.m4a';
var ogg = 'song.ogg';
var m4aSrc = document.createElement('source');
m4aSrc.setAttribute('src', m4a);
m4aSrc.setAttribute('type', 'audio/mp4');
var oggSrc = document.createElement('source');
oggSrc.setAttribute('src', ogg);
oggSrc.setAttribute('type', 'audio/ogg');
var audioEle = document.createElement('audio');
audioEle.setAttribute('preload', 'preload');
audioEle.appendChild(m4aSrc);
audioEle.appendChild(oggSrc);
return audioEle;
}
I know this is a bit late, but still;
I managed to play audio on all those browsers (except IE < 9) without appending to DOM. You can create audios with something like this:
var audios = {};
audios['audioIDorName'] = new Audio('pathToYourAudio'); // create audio element
audios['audioIDorName'].load(); // force load it
audios['audioIDorName'].addEventListener('canplaythrough', audioLoaded, false); // add event for when audio is fully loaded
Then you create audioLoaded() function which checks when all your audios are loaded (if you for instance added more than one in a loop or one after another.
var audiosLoaded = 0
function audioLoaded(){
audiosLoaded++;
console.log("Loaded "+this.src);
// here you can check if certain number of audios loaded and/or remove eventListener for canplaythrough
audios['audioIDorName'].removeEventListener('canplaythrough', audioLoaded, false);
}
Then you manipulate audios with these functions:
audios['yourAudioIDorName'].pause();
audios['yourAudioIDorName'].currentTime = 0; // pause() and currentTime = 0 imitate stop() behaviour which doesn't exist ...
audios['yourAudioIDorName'].play();
This worked for me.
You can also try appending your audio element to an existing element if you insist on appending to DOM. See more details here.
Unfortunately there are still many other issues with html5 audio considering cross-browser usage. Codecs are just one thing; using ogg+mp3 is probably necessary. Flash fallback is not-so-neat solution that is used by libraries/APIs such as SoundManager and Howler.js. There is soundJS as well which may be the best thing to try. See my SO question as well.

Problems preloading audio in Javascript

I'm trying to make a cross-device/browser image and audio preloading scheme for a GameAPI I'm working on. An audio file will preload, and issue a callback once it completes.
The problem is, audio will not start to load on slow page loads, but will usually work on the second try, probably because it cached it and knows it exists.
I've narrowed it down to the audio.load() function. Getting rid of it solves the problem, but interestingly, my motorola droid needs that function.
What are some experiences you've had with HTML5 audio preloading?
Here's my code. Yes, I know loading images in a separate function could cause a race condition :)
var resourcesLoading = 0;
function loadImage(imgSrc) {
//alert("Starting to load an image");
resourcesLoading++;
var image = new Image();
image.src = imgSrc;
image.onload = function() {
//CODE GOES HERE
//alert("A image has been loaded");
resourcesLoading--;
onResourceLoad();
}
}
function loadSound(soundSrc) {
//alert("Starting to load a sound");
resourcesLoading++;
var loaded = false;
//var soundFile = document.createElement("audio");
var soundFile = document.createElement("audio");
console.log(soundFile);
soundFile.autoplay = false;
soundFile.preload = false;
var src = document.createElement("source");
src.src = soundSrc + ".mp3";
soundFile.appendChild(src);
function onLoad() {
loaded = true;
soundFile.removeEventListener("canplaythrough", onLoad, true);
soundFile.removeEventListener("error", onError, true);
//CODE GOES HERE
//alert("A sound has been loaded");
resourcesLoading--;
onResourceLoad();
}
//Attempt to reload the resource 5 times
var retrys = 4;
function onError(e) {
retrys--;
if(retrys > 0) {
soundFile.load();
} else {
loaded = true;
soundFile.removeEventListener("canplaythrough", onLoad, true);
soundFile.removeEventListener("error", onError, true);
alert("A sound has failed to loaded");
resourcesLoading--;
onResourceLoad();
}
}
soundFile.addEventListener("canplaythrough", onLoad, true);
soundFile.addEventListener("error", onError, true);
}
function onResourceLoad() {
if(resourcesLoading == 0)
onLoaded();
}
It's hard to diagnose the problem because it shows no errors and only fails occasionally.
I got it working. The solution was fairly simple actually:
Basically, it works like this:
channel.load();
channel.volume = 0.00000001;
channel.play();
If it isn't obvious, the load function tells browsers and devices that support it to start loading, and then the sound immediately tries to play with the volume virtually at zero. So, if the load function isn't enough, the fact that the sound 'needs' to be played is enough to trigger a load on all the devices I tested.
The load function may actually be redundant now, but based off the inconsistiency with audio implementation, it probably doesn't hurt to have it.
Edit: After testing this on Opera, Safari, Firefox, and Chrome, it looks like setting the volume to 0 will still preload the resource.
canplaythrough fires when enough data has buffered that it probably could play non-stop to the end if you started playing on that event. The HTML Audio element is designed for streaming, so the file may not have completely finished downloading by the time this event fires.
Contrast this to images which only fire their event once they are completely downloaded.
If you navigate away from the page and the audio has not finished completely downloading, the browser probably doesn't cache it at all. However, if it has finished completely downloading, it probably gets cached, which explains the behavior you've seen.
I'd recommend the HTML5 AppCache to make sure the images and audio are certainly cached.
The AppCache, as suggested above, might be your only solution to keep the audio cached from one browser-session to another (that's not what you asked for, right?). but keep in mind the limited amount of space, some browsers offer. Safari for instance allows the user to change this value in the settings but the default is 5MB - hardly enough to save a bunch of songs, especially if other websites that are frequented by your users use AppCache as well. Also IE <10 does not support AppCache.
Alright so I ran into the same problem recently, and my trick was to use a simple ajax request to load the file entirely once (which end into the cache), and then by loading the sound again directly from the cache and use the event binding canplaythrough.
Using Buzz.js as my HTML5 audio library, my code is basically something like that:
var self = this;
$.get(this.file_name+".mp3", function(data) {
self.sound = new buzz.sound(self.file_name, {formats: [ "mp3" ], preload: true});
self.sound.bind("error", function(e) {
console.log("Music Error: " + this.getErrorMessage());
});
self.sound.decreaseVolume(20);
self.sound.bind("canplaythrough",function(){ self.onSoundLoaded(self); });
});

Audio in a tool tip/hover?

I would like to include audio that will automatically play when the user scrolls over. I have not found a tooltip that will do this. Does anyone know of a way to accomplish this?
(Note: The user will be warned about the audio before they have access to the page.)
UPDATE
I got this working thanks to Bakudan - хан ювиги. But is there a flash fall back available using Bakudan - хан ювиги's method? Thanks!
UPDATE 2
Using Bakudan - хан ювиги's recommended method for adding a flash fallback using swfobject leaves me a bit confused. My lack of javascript knowledge is where I get lost. Here is the code I am using for my audio:
<script>
// Mouseover/ Click sound effect- by JavaScript Kit (www.javascriptkit.com)
// Visit JavaScript Kit at http://www.javascriptkit.com/ for full source code
var html5_audiotypes={ //define list of audio file extensions
"mp3": "audio/mpeg",
"ogg": "audio/ogg",
}
function createsoundbite(sound){
var html5audio=document.createElement('audio')
if (html5audio.canPlayType){ //check support for HTML5 audio
for (var i=0; i<arguments.length; i++){
var sourceel=document.createElement('source')
sourceel.setAttribute('src', arguments[i])
if (arguments[i].match(/\.(\w+)$/i))
sourceel.setAttribute('type', html5_audiotypes[RegExp.$1])
html5audio.appendChild(sourceel)
}
html5audio.load()
html5audio.playclip=function(){
html5audio.pause()
html5audio.currentTime=0
html5audio.play()
}
return html5audio
}
else{
return {playclip:function(){throw new Error(
"Your browser doesn't support HTML5 audio unfortunately")}}
}
}
//Initialize sound clips with 1 fallback file each:
var mouseoversound=createsoundbite(
"/messages4u/2011/images/october/laugh.ogg",
"/messages4u/2011/images/october/laugh.mp3")
</script>
I changed the else to the flash instead of the error message. How do I change this using swfobject to play the flash audio file? I am a bit lost by that.
Thanks for the help!
This is a good start. I`ve made a demo. If the audio is a little bit longer, add stopclip function to the createsoundbite function and then add it to the .mouseleave.
Edit:
To add flash change the else part. I'm very familiar with flash, but basically use one of the following:
create object - "document.createElement("object");... etc"
or which I think is better use swfobject
Here's a version that plays sound in older browsers and does not require flash:
var sound = document.createElement("audio");
if (!("src" in sound)) {
sound = document.createElement("bgsound");
}
document.body.appendChild(sound);
function playSound(src) {
sound.src = src;
sound.play && sound.play();
}
...
playSound("/sounds/something.mp3");
Edit: Here's a version that uses .hover() to play the sound when the mouse hovers over your element:
$(function) {
var sound = document.createElement("audio");
if (!("src" in sound)) {
sound = document.createElement("bgsound");
}
document.body.appendChild(sound);
$(".playable").hover(function() {
sound.src = this.soundFile;
sound.play && sound.play();
}, function() {
sound.src = "";
});
});
Set up your elements that you want to play a sound with a class of "playable" and custom attribute "soundFile" containing the source url for the sound file:
<span class="playable" soundFile="/sounds/something.mp3">Something</span>
<span class="playable" soundFile="/sounds/somethingElse.mp3">Something else</span>
soundmanager2

Timing out tracking pixels in webkit browsers

I have a javascript that creates several div elements and inside them an img tag for each of several image urls. I also have a timeout function that after a period is supposed to cause the image to stop loading. In FF this can be achieved by setting the img tag's src attribute to be blank. In webkit browsers it's a different story since all requests are made asynchronously.
The code I have so far looks similar to this:
function tag(url_array, timeout)
{
var _tag_div = document.getElementById('tag_div');
for(var i in url_array)
{
var e = document.createElement('div');
e.setAttribute('id', '_tag_' + i);
e.innerHTML = '<img src="' + urls[i] + '"/>';
_tag_div.appendChild(e);
setTimeout(blank_img.bind(window, i), timeout);
}
function blank_img(x)
{
document.getElementById('_tag_' + x).firstChild.src = '';
}
}
As I said this works well in FF but in webkit browsers it does nothing to stop the loading of the images. Given that this is going to be used for a pixel tracking system on 3rd party pages the code needs to be as lightweight as possible. Does anybody know of a solution to this problem or can maybe assist me in finding one? Thanks in advance,
-CarbonX
Try removing them from the DOM completely with removeChild, e.g.:
function blank_img(x) {
var imgTag = document.getElementById('_tag_' + x);
if(imgTag) { imgTag.parentNode.removeChild(imgTag); }
}
I'm assuming you don't need the tags later...

Categories

Resources