Can't avoid delay in javascript audio - javascript

I wonder whether this is an unresolved issue or not.
OK, so I have this very simple test code:
var audio = new Audio("0.mp3");
audio.oncanplay = function() {
audio.play();
setTimeout(function() {
audio.pause();
}, 30);
}
What I intend to do is to play my sound for a very short period of time.
I know for sure that the audio (a middle-C note) starts in 0:00:00.
Note that I use the oncanplay event to make sure the audio is loaded when I try to play it.
The problem I have is that I get unpredictable results. Sometimes (most of the times, really), audio is not heard at all. Other times, audio is heard but not always for the same period of time.
I know that Javascript can be slow, but I wonder, for example in the first case, why is the timeout set at all if the audio isn't playing yet.
Is this a known issue? It is possible to have a better control over Audio?
-Thanks

Avoid using setTimeout, which is not accurate and may result (as in your case) in a race condition. Use the 'timeupdate' event to keep track of the progress. Here the song will play and auto-pause after 7 seconds:
var audio = new Audio("0.mp3");
audio.oncanplay = function() {
audio.play();
audio.addEventListener('timeupdate', function() {
console.log(audio.currentTime.toFixed());
if ( audio.currentTime >= 7 ) audio.pause();
});
}
JSFiddle Demo

Related

How to autoplay audio when the countdown timer is finished?

I want to play a sound after the countdown timer is done.
Normally I will do it using this peace of code
var audio = new Audio('path to file.mp3');
audio.play();
But I get the following error Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
The thing is ... Google it self is doing it using a HTML5 audio tag
If you type countdown timer into google search field it should show you the widget that plays a sound after the countdown timer is finished.
Here is how Googles timer look like, if you guys don't know what I'm talking about :)
By making you click this "START" button, they ask for an user gesture and thus have marked their document as approved-by-user to play audio. This means they are not subject for chrome's autoplay policies anymore.
Now, Safari by default is even stricter than Chrome here, and a simple click on the document doesn't work: in this browser you need to start the playback from the user-event itself.
So in your case, it won't work, even if you did start the countdown from a click like Google.
The solution is then to start the playback from the click event, and to pause it immediately after. Doing so, your Element will be marked as approved-by-user and you will ave full control over it.
const audio = new Audio("https://dl.dropboxusercontent.com/s/1cdwpm3gca9mlo0/kick.mp3");
let time = 5;
btn.onclick = e => {
// mark our audio element as approved by the user
audio.play().then(() => { // pause directly
audio.pause();
audio.currentTime = 0;
});
countdown();
btn.disabled = true;
};
function countdown() {
pre.textContent = --time;
if(time === 0) return onend();
setTimeout(countdown, 1000);
}
function onend() {
audio.play(); // now we're safe to play it
time = 5;
btn.disabled = false;
}
<button id="btn">start countdown</button><br>
<pre id="pre"></pre>

Delay in big files using Javascript native audio library

Heys guys!
I'm just trying to play an audio from a certain position using the native audio class and it works great in small audios (some seconds) but when I try the same in a 20 minutes audio, it plays with a delay of something about half a second, which is a lot for what I need.
I made a simple jsfiddle to illustrate this, I hope it helps to understand the problem.
Both positions, in the long and short audio, should play exactly the same but actually they don't. I also attach two screen captures to show that the position in the audio is the correct one.
Any ideas? Thanks in advance!!
jsfiddle: https://jsfiddle.net/oxmanroman/n4sLLuk9/4/
playShortAudio().then(() => {
setTimeout(playLongAudio, 1000);
})
function playShortAudio() {
// play from 2 seconds, 400 milliseconds
return playAudio('https://s3.amazonaws.com/thespanishapp/test.mp3', 2.400);
}
function playLongAudio() {
// play from 4 minutes, 27 seconds, 750 milliseconds
return playAudio('https://s3.amazonaws.com/thespanishapp/large-test.mp3', 267.750);
}
function playAudio(url, startingPoint) {
return new Promise(resolve => {
const audio = new Audio(url);
// set audio position to listen 'hello miss...'
audio.currentTime = startingPoint;
// play audio when ready
audio.oncanplaythrough = () => {
audio.play();
// stop the audio and resolve the promise after two seconds of audio
setTimeout(() => {
audio.pause();
resolve();
}, 2000);
}
});
}
The exact issue is the MP3 format.
The start time is correct, but MP3 has "time drift" which is worse in some browsers than others. I would strongly recommend using AAC (.m4a) format instead. It doesn't only remove the time drifting, but AAC also has better portability across browsers, both desktop and mobile.

Java script -All audio plays at once when attempting to play audio one by one in a sequence (through a for loop)

Here is the link to code pen to my project https://codepen.io/RajTheSHow/pen/vZZPqZ
when you press the wrong button it is supposed to play the sequence(with audio and button press in order).however what actually happens is when you press the wrong button it plays all the audio and changes the color at once and doesn't execute what is in the sleep function.you can see the problem in the pen.
the function that runs the sequence when you press the wrong button is below
cal is where the order is stored of the sequence.
//plays the sequence
function callBut(){
onr=0;
for(i=0;i<cal.length;i++){
// or eval ("s"+cal[i])
window["s"+cal[i]].play();
// set the but# to Clicked Color Ccol# then after 1 sec go back to normal color
$("[but="+cal[i]+"]").css("background",window["Ccol"+cal[i]])
sleep(500).then(() => {
// set the button# to Standard color Scol#
$("[but="+cal[i]+"]").css("background",window["Scol"+cal[i]])
});
}
What'd you expect? The loop does not wait for the audio to finish. It's better to use recursion to play the next song.
let arr = cal.map(element => window[`s${element}`]); // asuming 'cal' is something significant
let index = 0;
playAudio(arr[index]);
function playAudio(audio) {
if (!audio || !(audio instanceof Audio)) return;
audio.addEventListener('ended', function() {
index++;
playAudio(arr[index]);
})
audio.play();
}
This is the correct and expected behaviour.
What you want to achieve requires to wait for the end event and only then invoke play on the next clip.
This can be achieved by properly handling the audio events you can look up online and "chaining" them playing one at a time, much like this:
var nextSound;
function playNext()
{
var audio;
if (nextSound >= cal.length)
{
return;
}
audio = $(window["s"+cal[nextSound++]]);
audio.on("ended", playNext);
audio[0].play();
}
nextSound = 0;
playNext();

How to pause/stop video in actually time (like 3.00 secs)

I was wondering is there possible to pause the video in actually time ?
I tested and video always pause at x.xxx not x.00
video.load();
video.play();
video.ontimeupdate = function(){
if(this.currentTime >= 3) {
video.pause();
}
};
Demo : https://jsfiddle.net/l2aelba/4gh7a058/
Any trick ?
PS: Should be good performance as possible also
I believe you cannot guarantee that the video will stop at an exact moment in time.
According to the documentation for media controller:
Every 15 to 250ms, or whenever the MediaController’s media controller
position changes, whichever happens least often, the user agent must
queue a task to fire a simple event named timeupdate at the
MediaController.
timeupdate event will fire when it can using the least often scenario. It does not give you the option to choose the exact fire times for the updates.
A trick you could do is the following: Remove the timeupdate event, set your own interval and using that check the time.
setInterval(function () {
var ct = video.currentTime;
current_time_el.innerHTML = 'Current time : ' + ct;
if(ct >= 3) {
video.pause();
}
}, 40);
This approach will force you to be more careful with your code though. (e.g clean up your interval with clearInterval() when it is not needed any more)

How can I find when HTML5 audio actually starts playing (onplay event does not seem to work anywhere)

I need to catch the exact moment when HTML5 audio starts producing sound.
It turns out not so simple as it seems.
You might expect audio starts playing when onplay or onplaying event is fired? No way. At least in WebKit family, it seems to be no browser event that fires exactly at this point of time. In Chrome, Safari and Firefox onplay and onplaying events are just faking their behaviour by simply firing together with oncanplay!
I've prepared a simple test to prove that fact. It demonstrates that audio actually starts playing after some reasonable time (over 100ms - 400ms) when all the events had already been fired.
You can notice this by your ears and ears if you look at console log. In the log I output currentTime every 15ms. It seems to reflect the actual audio state correctly, and it starts changing 10-40 polls after any event has been fired. So the audio is still freezed after play is fired.
Test code looks like this:
var audioEl = new Audio('http://www.w3schools.com/tags/horse.ogg');
audioEl.oncanplay = function () {
console.log('oncanplay');
audioEl.currentTime = 1;
console.log('ready state is: ' + audioEl.readyState);
audioEl.play();
}
audioEl.oncanplay = function () {
console.log('oncanplay again');
}
audioEl.onplay = function() {
console.log('onplay -----------------');
}
audioEl.onplaying = function() {
console.log('onplaying ----------------');
}
setInterval(function () {
console.log(audioEl.currentTime);
}, 15);
JsFiddle
I critically need to know the exact moment when the audio starts playing for precise synchronisation with visual animations.
Of course, I can find this moment roughly using quick polling. This is very bad for performance in real-time app, especially on mobile.
I'm asking if anyone knows any better solution for this. HTML audio implementation looks to be still so poor in 2014 :(
As #justin says, you can listen for the playing event to get the (more or less) precise moment the media starts actually playing. But yeah I've been seeing some spotty support for media events and readyState in general, even in latest Chrome.
Whether those events work or not, I advise against using setInterval for animation (or just about anything else, for that matter). Instead, use requestAnimationFrame, which will fire more often and should synchronize with the browser and video card's repaint. And you can poll for the currentTime value on every frame to calculate where you should be in your animation. Performance shouldn't be a problem; your case is exactly what requestAnimationFrame was designed for.
function update() {
var currentTime = audioEl.currentTime;
// update your animation here
requestAnimationFrame(update);
}
update();
While you're at it, don't set currentTime to 5 until after readyState > 0 or the loadedmetadata event fires. If you try to set currentTime before the browser has loaded enough of the video file to know the duration, it will throw an error. But you can call play() whenever you want; it doesn't have to wait for canplay.
Try the canplaythrough instead. Might help and would be better to be sure your audio can be palyed all the way to the end anyway..
audioEl.oncanplay = function () {
console.log('ready state is: ' + audioEl.readyState);
audioEl.play();
}

Categories