Delay in big files using Javascript native audio library - javascript

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.

Related

How do I improve the accuracy of the DelayNode?

I'm using the WebAudio API and doing a simple delay on the input using DelayNode. The code is as follows (might not work in the snippet because it requests microphone permissions):
const player = document.querySelector("#player")
const handleSuccess = stream => {
const context = new AudioContext()
const source = context.createMediaStreamSource(stream)
const delay = context.createDelay(179) //seconds
//source.connect(context.destination) connect directly
source.connect(delay)
delay.connect(context.destination)
delay.delayTime.value = 1
}
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(handleSuccess)
However, when I run a metronome at 60 beats per minute (one click every second), the audio coming from the browser is not delayed by exactly one second, and multiple clicks are heard (the delay is slightly longer than expected). Is there any way to make an exact delay?
I guess the problem is not the DelayNode itself but rather that there are multiple other hidden delays in the audio pipeline. If you want to hear the previous click from the metronome at the very same time as the current click you would need to reduce the time of your delay to account for those other delays (also known as latency).
The signal takes a bit of time to travel from your microphone through your A/D converter into the browser. This time is usually available as part of the settings of the MediaStream
stream.getAudioTracks()[0].getSettings().latency
Piping the MediaStream into the Web Audio API will probably add some more latency. And the AudioContext itself will add some latency due to its internal buffer.
context.baseLatency
It will also take some time to get the signal out of the computer again. It will travel from the browser to the OS which passes it on to the hardware. This value is also exposed on the AudioContext.
context.outputLatency
In theory you would only need to subtract all those values from the delay time and it would just work. However in reality every hardware/OS/browser combination is a bit different and you will probably need to make some adjustments in order to successfully overlay the previous with the current click depending on your personal setup.
You can use the async/await syntax (documentation here), here are two examples (taken from here)
const foo = async () => {
await sleep(1000);
// do something
}
const foo = async evt => {
await sleep(1000);
// do something with evt
}

How to track the current time of video tag accurately?

I want to accurately jump to the specific time of my video-tag and pause at the specific endpoint in the video.
let's say I have a simple function for it: myDuration(5, 9) it'll play video from the second 5 and will pause the video when it reaches second 9.
so far I can start the video from the accurate start point but it seems that I can not listen for endpoint and pause the video at exactly second 9.
I think the problem is that I cannot track the current time of the video accurately here in the code and that causes the problem:
if (myVideo[0].currentTime == b) {
myVideo[0].pause();
}
sometimes it pauses at second 10 and sometimes it pauses at second 8 ...??? Why?
How to track the endpoint or listen for the endpoint accurately?
Here is the code:
var myVideo = document.getElementsByTagName('video');
// I want to start from second 5 of the video
myDuration(5, 9);
function myDuration(a, b) {
myVideo[0].currentTime=a;
myVideo[0].play();
// I want to pause at exactly second 9 of the video so I listen for it
myVideo[0].addEventListener("timeupdate", function() {
if (myVideo[0].currentTime == b) {
myVideo[0].pause();
}
}, false);
}

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

HTML 5 Audio Detecting Play Delay

I'm trying to measure the time between when the user clicks the play button, triggering audio.play(), and the time when the audio actually starts to play on the client. I'm just not sure which event to listen to, as per http://www.w3schools.com/tags/ref_av_dom.asp
It seems that 'play' and 'canplay' are giving me similar times, so I'm not sure of the correct one to listen to. It's rather hard for me to test since I have a fairly quick connection. I'm using the following code:
var time;
var audio = document.createElement('audio');
var uri = 'http://www.flashkit.com/imagesvr_ce/flashkit/soundfx/Creatures/Male_zombi_Zapsplat_8347/horror_monster_zombie_male_eating_body_001.mp3'
audio.setAttribute('preload', 'metadata');
audio.src = uri;
audio.addEventListener('canplay', (evt) => {
time = Date.now() - time;
alert(time);
});
audio.play();
time = Date.now()
setTimeout(function() { audio.pause(); }, 1000);
https://jsfiddle.net/67vmr476/1/

Can't avoid delay in javascript audio

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

Categories