Why isn't my audio rewinding? - javascript

I'm having a bit of trouble rewinding audio in Javascript. I basically have a countdown that beeps each second as it gets towards the end of the countdown.
I tried using;
var bip = new Audio("http://www.soundjay.com/button/beep-7.wav");
bip.play();
but it didn't beep every second which I'm guessing has something to do withit having to load a new sound every second. I then tried loading the sound externally and triggering it with the following code.
bip.pause();
bip.currentTime = 0;
console.log(bip.currentTime);
bip.play();
but this only plays the sound once then completely fails to rewind it (this is shown by the console logging a time of 0.19 seconds after the first playthrough).
Is there something I'm missing here?

In google chrome I noticed that it only works if you have the audio file in same domain. I have no problems if the audio file is in same domain. Event setting .currentTime works.
Cross-domain:
var bip = new Audio("http://www.soundjay.com/button/beep-7.wav");
bip.play();
bip.currentTime; //0.10950099676847458
bip.currentTime = 0;
bip.currentTime; //0.10950099676847458
Same-domain:
var bip = new Audio("beep-7.wav");
bip.play();
bip.currentTime; //0.10950099676847458
bip.currentTime = 0;
bip.currentTime; //0
I tried googling for a while and could find nothing in specs about this or even any discussion.

when I want rewind I simply load the audio again:
my_audio.load()
*btw, I also use a eventlistener for 'canplay' to trigger the my_audio.play(). It seems that this is necessary in android, and maybe other devices also*

To further dsdsdsdsd's answer with a bit of paint-by-numbers for the "whole shebang"
NOTE: In my app, loc1 is a dummy that refers to the song's stored location
// ... code before ...
tune = new Audio(loc1); // First, create audio event using js
// set up "song's over' event listener & action
tune.addEventListener('ended', function(){
tune.load(); //reload audio event (and reset currentTime!)
tune.play(); //play audio event for subsequent 'ended' events
},false);
tune.play(); // plays it the first time thru
// ... code after ...
I spent days and days trying to figure out what I was doing wrong, but it all works fine now... at least on the desktop browsers...

As of Chrome version 37.0.2062.120 m, the behaviour described by #Esailija has not changed.
I workaround this issue by encoding the audio data in base64 encoding and feed the data to Audio() using data: URL.
Test code:
snd = new Audio('data:audio/ogg;base64,[...base64 encoded data...]');
snd.onloadeddata = function() {
snd.currentTime = 0;
snd.play();
setTimeout(function() {
snd.currentTime = 0;
snd.play();
}, 200);
};
(I am surprised that there are no bug reports or references on this matter... or maybe my Google-fu is not strong enough.)

Related

I'm having trouble playing audio in javascript at the same time

So far, I have tried using this function to create a new audio element every time it is called:
function createAudio(src, type){
var sound = new Audio();
var source = document.createElement('source');
source.type = type;
source.src = src;
sound.appendChild('source');
sound.play();
};
That way, if you want to play a single audio file called, for example, "gunshot.mp3", but multiple times you can use this line of code:
createSound('gunshot.mp3','audio/mpeg');
This works ok, but what will happen is that when the sound is loaded a bunch of times, it will just stop running entirely. Every time that you reload the game its like rolling dice to see which sounds will or won't run, and even when it does run its patchy and inconsistent.
I'm guessing that it has something do with the html page being overloaded with new elements, so is there maybe a way to clear the elements every once and a while, like clearing a particle array so it doesn't get too long? Or is it just that my function is not correct in the first place?
Thanks!

Web Audio API and real current time when playing an audio file

I am having problems when I want to know the current time of a file playing using the Web Audio API. My code plays the file nicely and the current time returned by the getCurrentTime() function is accurate enough when it comes to short files which load fast.
But when I try to load big files, sometimes the current time returned by the getCurrentTime() function is accurate and sometimes not. Sometimes, after waiting for example for 20 seconds to hear the file playing, when it starts playing it says that the current time is about 20 seconds (which is not true because it is just playing the beginning of the file). It happens with any audio format (OGG, MP3, WAV...) but only sometimes.
I am using a slow system (Asus EEE PC 901 with an Intel Atom 1.60 Ghz and 2 GB RAM with Windows XP Home Edition and SP3) and Firefox 41.0.1.
I am not sure, but it seems that the source.start() method starts playing the sound way too late, so the line after calling that method, where I set the value for the startTime variable, is not the real starting time.
Here is the code (simplified):
var context, buffer, startTime, source;
var stopped = true;
function load(file, startAt)
{
//Here creates the AudioContext and loads the file through XHR (AJAX) and gets the buffer. All works fine.
//When it gots the buffer through XHR (AJAX) and all is fine, it calls play(startAt) function immediately.
//Note: normally, startAt is 0.
}
function play(startAt)
{
source = context.createBufferSource(); //Context created before.
source.buffer = buffer; //Buffer got before from XHR (AJAX).
//Creates a gain node to be able to set the volume later:
var gainNode = context.createGain();
source.connect(gainNode);
gainNode.connect(context.destination);
//Plays the sound:
source.loop = false;
source.start(startAt, 0, buffer.duration - 3); //I don't want the last 3 seconds.
//Stores the start time (useful for pause/resume):
startTime = context.currentTime - startAt; //Here I store the startTime but maybe the file has still not begun to play (should it be just startTime = context.currentTime?).
stopped = false;
}
function stop()
{
source.stop(0);
stopped = true;
}
function getCurrentTime()
{
return (stopped) ? 0 : context.currentTime - startTime;
}
How can I detect when exactly the source.start() method starts playing the file? So I can set the startTime variable value just at that moment, and never before.
Thank you very much in advance. I would really appreciate any kind of help.
From MDN (https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/start), about the first parameter of the start() function:
when (Optional)
The time, in seconds, at which the sound should begin to play, in the same time coordinate system used by the AudioContext. If when is less than (AudioContext.currentTime, or if it's 0, the sound begins to play at once. The default value is 0.
There is no evident issue with your code (although there is no example a call to play()): if you call play(0) or play(context.currentTime + someDelayInSeconds), start() should behave as expected. Unfortunately here the issue is that AudioBufferSource is not meant for big files. Again from the MDN doc of AudioBuffer (https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer):
Objects of these types are designed to hold small audio snippets, typically less than 45 s.
I suspect that for big something doesn't work very well with the "sound begins play at once" assumption (I also experienced it, although 20 seconds seems way too much...). Unfortunately there is no way to get the exact start time of AudioBufferSource in WebAudio yet.
If you don't have any real reason to load this big file with AudioBufferSource, I suggest you use a MediaElementSourceNode (https://developer.mozilla.org/en-US/docs/Web/API/MediaElementAudioSourceNode): as you can see from the example on the linked doc, it allows you to plug a simple HTML5 Audio element into the AudioContext. You then can have all usual control over the element itself, i.e. you also have access to the audioElement.currentTime property, which tells you the current playout time (in this case of the file itself, which is what you need). Additionally, you don't have to handle loading of the file in memory and could start playing as soon as some data is available.
context.currentTime starts counting the second you create the context object. That means if it takes 20 seconds for your audio to load, context.currentTime == 20.
To account for this delay, you can set a simple timer from the time that you create the context to the time that audio loading completes.
var context; //Create your context here
var audioLoadStart = new Date();
//Do audio load
var audioLoadOffset = (new Date() - audioLoadStart) / 1000;
currentTime = context.currentTime - audioLoadOffset - startTime;

<video> element with looping does not loop videos seamlessly in Chrome or Firefox

<video width="640" height="360" src="http://jakelauer.com/fireplace.mp4" autoplay loop muted/>
Fiddle here: http://jsfiddle.net/bWqVf/
IE9 does a decent job of it. Is there any recommendation for ways to overcome this? It is very obvious in videos like this one that SHOULD seamlessly loop, but have an annoying skip/pause.
EDIT:
As you can see, if I use javascript to simulate the loop, there's a measurable lag: http://jsfiddle.net/bWqVf/13/
The problems seem to be related to how both Chrome and FF fills the pre-load buffers. In both cases they seem to ignore the loop flag and "reset" the buffers from start meaning in that case that at the end the buffers are emptied and pre-loaded again when video starts causing a slight delay/jump.
IE seem to consider the loop flag and continue to fill also towards the end.
This means it's gonna be very hard to make this look seamless. I tried several techniques over a few hours including pre-caching the first frames to 15 frames off-screen canvases. The closest I could get to seamless was modifying the video to have two segments in it (I do not (no longer) have capable hardware so I needed to reduce the dimension as well to test - see fiddle).
However, there are drawbacks here as well:
The video is double length
You need to play two instances at the same time
Two downloads of the same video happens
Lag compensation will vary from computer to computer
Browser updates in the future can influence good/bad how the result will end up to be.
In other words - there is no stable solution to get around the problem with these browsers.
I would recommend an extension to what I mention above, to pre-loop some segments. This way you can reduce the glitch.
However, to share what I did here goes.
First I extended the video with an extra segment (and reduced the dimension to run it on my computer):
Then I used the following code to do an overlapping loop. That is:
I start the videos at the same time, but one video from the middle.
The video that is currently => middle is shown
I use a canvas element to draw the video onto
When at end the current video is switched so that the new video is still the one being played from the middle
The theory here is that this will mask the glitch you get at the start as the video playing is always in the middle (starting on the second segment).
The code looks like this:
As the videos are loaded async we need to count the loads as this technique uses two video instances and the browser seem to be unable to share the download.
We also set a new position for video 1 to be at the middle. An event is raised for this when video is moved and ready, so we start everything from that point:
v1.addEventListener('canplay', init, false);
v2.addEventListener('canplay', init, false);
v1.addEventListener('timeupdate', go, false);
Handlers:
function init() {
count--; /// = 2
/// both videos are loaded, prep:
if (count === 0) {
length = v1.duration;
mid = length * 0.5;
current = mid;
/// set first video's start to middle
v1.currentTime = mid + lag;
}
}
function go() {
/// remove listener or this will be called for each "frame"
v1.removeEventListener('timeupdate', go, false);
v1.play();
v2.play();
draw();
}
The lag value is an attempt to compensate for the difference between the two videos starting as they don't start at the exact same time.
The main code draw simply switches between the videos depending on the position of the main video (v1) - the frame rate is also reduce to 30 fps to reduce overhead of drawImage as requestAnimationFrame runs optimally at 60 fps (the video here is 30 fps so we only need to draw a frame every other time):
function draw() {
/// reduce frame-rate from 60 to 30
if (reduce === true) {
reduce = false;
requestAnimationFrame(draw);
return;
} else {
reduce = true;
}
/// use video that is >= middle time
var v = v1.currentTime >= mid ? v1 : v2;
/// draw video frame onto canvas
ctx.drawImage(v, 0, 0);
requestAnimationFrame(draw);
}
Now, using canvas opens up other possibilities as well such as making for example a cross-fade between the two videos to smooth the transition further. I didn't implement this as it is outside the scope (in size/broadness), but worth to mention as that could be a solution in itself.
In any case - as mentioned, this is a solution with many drawbacks but it is the closest I could get to reduce the glitch (using Chrome).
The only solution that can work properly is an internal browser driven one as you would need access to the buffers to be able to do this fully seamlessly.
My "solution" is in essence saying: forget it! It won't work in these browsers, use an repeated looped video instead. :-)
I think the problem is related to browser-specific-video-handling.
As a quirk, you can achieve less latency converting the video to webm, but you should place it before mp4 source, ie:
<video width="640" height="360" autoplay loop muted>
<source src="http://jakelauer.com/fireplace.webm" type="video/webm" />
<source src="http://jakelauer.com/fireplace.mp4" type="video/mp4" />
</video>
Heureka!
We've found the actual, real, work-around-free solution to this problem over at where I work. It explains the inconsistent behavior through multiple developers as well.
The tl;dr version is: Bitrates. Who would've guessed? What I suppose is that many people use standard values for this that usually are around 10 Mbit/s for HD videos if you use the Adobe Media Encoder. This is not sufficient. The correct value would be 18 Mbit/s or maybe even higher. 16 is still a bit janky. I cannot express how well this works. I've, by now, tried the messiest workarounds for about five hours until I found this together with our video editor.
I hope this helps everyone and saves you tons of time!
I also hope it's okay that I posted this in another thread as well, but there are a bunch of questions of the same type about this and I wanted to reach a lot of people.
I don't think your problem is "code-related". It has more to do with the actual video itself. It would be much better if you edit your video for a seamless looping.
Have a look HERE as it will give you some guidance on how to do so.
Hope this helps you.
EDIT: You can try breaking the video up into two sections: the intro and the looping part. Make a <video> element for each one and position them in the same place, with the second video hidden. Set an "ended" event on the intro to swap out the display and start the second video. Then, you can set the loop attribute on the second video element.
You shouldn't have a problem getting the two videos to play seamlessly together as long as you have the preload attribute on at least the looping video.
If that doesn't work, try making two video elements with the same looping video. While one is playing, you can hide the other and set its currentTime back to zero, so any seeking delay will happen when nobody is looking.
If none of the above works for you, then you can try an other way with javascript. Note that i haven't tested the below code. What it does is starting the video from the 2nd second and when the video reaches the 4th second it will start it again (from the 2nd second).
function playVideo() {
var starttime = 2; // start at 2 seconds
var endtime = 4; // stop at 4 seconds
var video = document.getElementById('player1');
//handler should be bound first
video.addEventListener("timeupdate", function() {
if (this.currentTime >= endtime) {
this.play();
}
}, false);
//suppose that video src has been already set properly
video.load();
video.play(); //must call this otherwise can't seek on some browsers, e.g. Firefox 4
try {
video.currentTime = starttime;
} catch (ex) {
//handle exceptions here
}
}
The solution that worked for me (and doesn't require a huge amount of JavaScript) is something like:
var video = document.getElementById('background-video');
var loopPoint = 15; // s
function resetVideo() {
if (video.currentTime >= loopPoint) {
video.currentTime = 0;
}
}
video.addEventListener('timeupdate', resetVideo);
Unfortunately I guess this is quite expensive because it will use a callback every time the time of the video/audio updates.
This issue happens to me using the Chromium wrapper with Electron. Regardless of that, I got closer to solving the issue ( not close enough ). Here's a list of things that improved the looping to near seamless jumping back from cuepoint A to B:
A mp4 video with keyframes only was key (increases video size a bit)
Get a framerate-sensitive loop. This little tool helps a lot when using keyframes and timecodes: http://x3technologygroup.github.io/VideoFrameDocs/#!/documentation/FrameRates
( 3. The last thing is only needed if things in 1 & 2 do not help. I've loaded the whole video with an XmlHTTPrequest to fill the buffer completely. )
var xhr = new XMLHttpRequest();
xhr.open('GET', '../assets/video/Comp1.mp4', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 0) { // I used chromium and electron, usually status == 200 !
var myBlob = this.response;
var vid = URL.createObjectURL(myBlob);
// myBlob is now the blob that the object URL pointed to.
var v = document.getElementById("video");
v.src = vid;
// not needed if autoplay is set for the video element
v.play();
// This requires the VideoFrame-tool (see Nr. 2.)
var videoFrame = new VideoFrame({
id: 'v',
frameRate: 25, // ! must match your video frame rate
callback: function(response) {
// I jump from fram 146 to 72
if (videoFrame.get() === 146) {
// now, jump! Dealbreaker is that the seek is stopping the video
// and the few ms to play it again bugger up the experience.
// Any improvements welcome!
videoFrame.seekBackward(71, function() {
v.play();
});
}
}
});
videoFrame.listen('frame', 25);
v1.play();
}
}
xhr.send(null);
The only issue I encounter with this code is that the seeking stops the video and play() needs to be triggered again. This causes a glitch which I solved by going 3 frames back before the actual cuepoint I want to jump to.
This is still inaccurate if used on different hardware with different videos, but maybe it gets you closer to a solution -- an me too! :)
The problem is nothing.
The starting slide and ending slide is different. If both the slides are same, the looping will looks fine. Because of mismatch in these slides only, it looks like pausing at some seconds. Avoid those things and try out.
check below jsFiddle URL carefully i add console.log and trace video tag event like play, pause, ended etc, i check in window chrome version 28 (working loop for me without fire pause event )
http://jsfiddle.net/bWqVf/6/
Ok... after much trial and error, this is what finally worked for me. It seemed to me that the video is not updating after it's ended, so I just remind it all of its properties again when it finishes playing.
myVid.setAttribute('src', "videos/clip1.mp4");
myVid.autoplay = true;
myVid.addEventListener('ended', vidEnded);
function vidEnded()
{
myVid.setAttribute('src', "videos/clip1.mp4");
myVid.autoplay = true;
}

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); });
});

JS .play() on iPad plays wrong file...suggestions?

So, I am building a web app that has a div with text that changes on various user actions (it's stepping through an array of pieces of text). I'm trying to add audio to it, so I made another array with the sound files in the appropriate positions:
var phrases=['Please hand me the awl.','etc1','etc2','etc3'];
var phrasesAudio=['awl.mp3','etc1.mp3','etc2.mp3','etc3.mp3'];
And on each action completion, a 'counter' variable in incremented, and each array looks for the object at that counter
var audio = document.createElement("audio"),
canPlayMP3 = (typeof audio.canPlayType === "function" &&
audio.canPlayType("audio/mpeg") !== "");
function onAction(){
correct++;
document.getElementById('speech').innerHTML=phrases[correct];
if(canPlayMP3){
snd = new Audio(phrasesAudio[correct]);
}
else{
snd = new Audio(phrasesAudioOgg[correct]);
}
snd.play();
}
(the text replaces a div's HTML and I use .play() for the audio object)...usually works okay (and ALWAYS does in a 'real' browser), but on the iPad (the actual target device) after a few successful iterations, the TEXT continues to progress accurately, but the AUDIO will hiccup and repeat a sound file one or more times. I added some logging and looking there it reports that it's playing screw.mp3 (just an example, no particular file is more or less error prone), but in actuality it plays screwdriver.mp3 (the prior file, if there is an error, the audio always LAGS, never LEADS)...because of this I am thinking that the problem is with my use of .play()...so I tried setting snd=null; between each sound, but nothing changed...Any suggestions for how to proceed? This is my first use of audio elements so any advice is appreciated. Thanks.
edit: I've also tried setting the files with snd.src (based on https://wiki.mozilla.org/Audio_Data_API) and this caused no audio to play
for iPad you need to call snd.load() before snd.play() - otherwise you get "odd" behaviour...
see for some insight:
http://jnjnjn.com/187/playing-audio-on-the-ipad-with-html5-and-javascript/
Autoplay audio files on an iPad with HTML5
EDIT - as per comment from the OP:
Here https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox you can find a tip on halting a currently playing piece of media with
var mediaElement = document.getElementById("myMediaElementID");
mediaElement.pause();
mediaElement.src = "";
and, following that with setting the correct src, load(), play() works great

Categories

Resources