Having issue while while getting duration of audio play - javascript

let audioElement = new Audio('1.mp3');
let myProgressBar = document.getElementById('myProgressBar');
let progress = parseInt((audioElement.currentTime / audioElement.duration) * 100);
console.log("progress", progress);
myProgressBar.value = progress;
In console, it's showing NaN for progress. I tried getting it by separate calculation for e.g:
let currentTime = audioElement.currentTime;
let duration = audioElement.duration;
console.log(currentTime); // No problem in audioElement.currentTime
console.log(duration); // NaN

As the documentation for HTMLMediaElement.duration says:
If no media data is available, the value NaN is returned.
I believe you you are trying to get the duration of the audio before it is loaded. If so, the duration will be NaN. Multiplying a number by NaN, as you are doing, will always return NaN.
There are several ways of fixing this. Here's one:
The constructor you are using for the Audio element takes the url parameter and begins loading the audio asynchronously. (source). So you have to wait for the metadata to load before accessing duration. Try subscribing to the onloadedmetadata event:
audioElement.addEventListener('loadedmetadata', () => {
console.log(audioElement.duration)
})

Related

AudioContext.currentTime sometimes freezes for ~230ms shortly after start

I'm creating a music related app using Web Audio API, which requires precisely synchronizing sound, visuals and MIDI input processing.
The sound production follows the pattern described in this article: requestAnimationFrame regularly calls a function that schedules events on the AudioContext. This works fine most of the time, except in some occasions where the audio inexplicably lags behind the visuals.
After much poking around, I ran into the AudioContext.currentTime specification which hints:
Elapsed time in this system corresponds to elapsed time in the audio stream generated by the BaseAudioContext, which may not be synchronized with other clocks in the system.
And indeed I was able to verify that these sporadic delays in the audio come down to a problem in the AudioContext clock itself, which seems to pause for a bit sometimes just after starting up. Note that this doesn't happen every time, but frequently enough to be an issue (maybe 10-15% of the time...). Each time it happens the pattern is the same: currentTime starts increasing then gets stuck at 23ms for a bit, then starts going again regularly without any further issue after accumulating a total of ~230ms of lag behind the system/wall clock...
I've created a simple script which reproduces the problem (if you want to try it, just open the console to see the output, and press any key to start the test... as the issue is sporadic you may need to retry or sometimes reload multiple times before it happens...):
<script>
const audioContext = new AudioContext();
// reference points for both clocks (JS and audioContext)
var animStartTime = null;
var audioStartTime = null;
// loop function to be called by requestAnimationFrame
function play(timestamp) {
// set animStartTime on first invocation
if (animStartTime == null) animStartTime = timestamp;
// compute elapsed time for both clocks
var animElapsedTime = timestamp - animStartTime;
var audioElapsedTime = (audioContext.currentTime - audioStartTime) * 1000;
console.log('Animation ts: ' + animElapsedTime +
', Audio ts: ' + audioElapsedTime +
', Diff: ' + (animElapsedTime - audioElapsedTime)
);
// keep this going for 1 second
if (animElapsedTime < 1000)
requestAnimationFrame(play);
}
function start() {
audioStartTime = audioContext.currentTime;
animStartTime = null; // use the timestamp provided by requestAnimationFrame
// create a simple oscillator and schedule it to produce a single beep when it starts
const osc = audioContext.createOscillator();
osc.frequency.value = 800;
osc.connect(audioContext.destination);
osc.start(audioStartTime);
osc.stop(audioStartTime + 0.03);
// launch animation loop
requestAnimationFrame(play);
}
// press any key to start the test
window.addEventListener('keypress', function(e) {start()});
</script>
And here is the console output of a bogus run, where you can see the Audio timestamp freezing up to a delay of ~230ms behind the JS main thread timestamp before starting again:
Could someone explain to me:
What is going on? Why is this freezing sporadically?
Is there anything I can do to prevent this? I can think of some ways to mitigate the issue if I can get convinced that it happens only when starting up, but without fully understanding the root cause I fear seeing these freezes happen again at other times...
This is probably https://crbug.com/693978 Resuming the context may take "some" time.
Unfortunately, they don't really wait for the context has started before resolving the Promise returned by context.resume(), so we have to resort to ugly workarounds.
One such workaround would be to wait for currentTime to start updating after context.resume() resolves before starting your animation.
const audioContext = new AudioContext();
// reference points for both clocks (JS and audioContext)
var animStartTime = null;
var audioStartTime = null;
// loop function to be called by requestAnimationFrame
function play(timestamp) {
// set animStartTime on first invocation
if (animStartTime == null) animStartTime = timestamp;
// compute elapsed time for both clocks
var animElapsedTime = timestamp - animStartTime;
var audioElapsedTime = (audioContext.currentTime - audioStartTime) * 1000;
console.log('Animation ts: ' + animElapsedTime +
', Audio ts: ' + audioElapsedTime +
', Diff: ' + (animElapsedTime - audioElapsedTime)
);
// keep this going for 1 second
if (animElapsedTime < 1000)
requestAnimationFrame(play);
}
async function start() {
// wait for the context to resume
// needs to be there, for we still handle the user-gesture
await audioContext.resume();
// though resume() might be a lie in Chrome,
// so we also wait for currentTime to update
const startTime = audioContext.currentTime;
while (startTime === audioContext.currentTime) {
await new Promise((res) => setTimeout(res));
}
// now our AudioContext is ready.
audioStartTime = audioContext.currentTime;
animStartTime = null; // use the timestamp provided by requestAnimationFrame
// create a simple oscillator and schedule it to produce a single beep when it starts
const osc = audioContext.createOscillator();
osc.frequency.value = 800;
osc.connect(audioContext.destination);
osc.start(0);
osc.stop(0.3);
// launch animation loop
requestAnimationFrame(play);
}
// using a button for a clearer snippet
document.querySelector("button")
.addEventListener('click', start);
<button>start</button>

Function Won't Recurse

I am attempting to make a 'web player' that plays music when you click the play button, and displays how far along you are in the track on a bar. I have made a function that plays the audio and checks how far along the track you are to set the width of the playback bar, but for the life of me i can't get it to recurse. I know the function works, because the playback bar jumps to the correct position when you click the play button, but i cant seem to automate it. Here is the code:
function play(x) {
var audio = document.getElementById(x);
audio.play();
var progress = audio.currentTime;
var duration = audio.duration;
var percent = progress/duration;
var width = percent*100;
var progbar = document.getElementById("play-line-elapsed");
var width = width + "%";
progbar.style.width = width;
setTimeout(play("Bring-Out-Yer-Dead"), 5000);
}
const createClock = setInterval(play, 100);
I have tried while loops, do while loops, setTimeout and finally setInterval so far. I am also a complete beginner to javascript, so it'll probably be completely obvious when someone points it out to me. Thanks in advance
Here is the code that should work
function play(x) {
var audio = document.getElementById(x);
audio.play();
var progress = audio.currentTime;
var duration = audio.duration;
var percent = progress/duration;
var width = percent*100;
var progbar = document.getElementById("play-line-elapsed");
var width = width + "%";
progbar.style.width = width;
setTimeout(function () {play("Bring-Out-Yer-Dead")}, 5000);
}
const createClock = setTimeout(function () {play("Bring-Out-Yer-Dead")}, 100);
What you did was to create an interval that calls play every 0.1 seconds, without any arguments. I suppose that wouldn't work as the function requires an argument.
Your setTimeout at the end of play wouldn't work too, because you're just calling it. You should provide a function object (not very correct word, but you get it) to setTimout, not calling it. The way you did it is to call play and give its return value (which is undefined) to setTimout, which makes setTimeout do nothing.
The corrected code creates a timeout that calls play after 0.1 seconds, then play continues to call itself every 5 seconds.
You have to provide a 0 arity function to setTimeout/setInterval, meaning a function that accepts no arguments. I used an anonymous function for that, but you could write another function with no arguments and use it there.
But I think you would be better off using events. I haven't tested any of these, but these events seem promising for what you are trying to do.
https://www.w3schools.com/jsref/event_ondurationchange.asp
https://www.w3schools.com/jsref/event_onplaying.asp
https://www.w3schools.com/jsref/event_onplay.asp

Why does performance.now() and Chrome's performance tools show different results?

I created a quick bit of code to test this. I included this script on my page and refreshed the browser:
const startTime = performance.now();
const img = new Image();
img.src = 'https://www.google.co.uk/images/branding/googleg/1x/googleg_standard_color_128dp.png?' + Math.random();
const endTime = performance.now();
const downloadTime = endTime - startTime;
console.log(downloadTime);
The script reports the endTime as 0.19000000000005457. The endTime value is an instance of DOMHighResTimestamp which returns a double in milliseconds.
But the chrome developer tools reports it took 81ms. That's pretty big difference for my problem. If it only took 81ms why does my script report 190ms? Or am I missing something obvious?
Edit
Thanks to Josh's answer for pointing out that it needs to be in the load handler. My bad. But sadly the values are still inconsistent.
Also why the downvotes? This is a legitimate question. I need to know down the last detail exactly how long it took to download a specific image for a very specific reason.
<img> elements are loaded asynchronously. Your code is composed synchronously.
const downloadTime = endTime - startTime;
has no relation to the request for the resources at Network tab at DevTools.
You can use PerformanceObserver to get the performance metrics relating to a request for a "resource"
const observer = new PerformanceObserver((list, obj) => {
for (let entry of list.getEntries()) {
for (let [key, prop] of Object.entries(entry.toJSON())) {
console.log(`${key}: ${prop}`);
}
}
});
observer.observe({
entryTypes: ["resource"]
});
const img = new Image();
img.src = 'https://www.google.co.uk/images/branding/googleg/1x/googleg_standard_color_128dp.png?' + Math.random();
Remember that JavaScript is single threaded — any networking code in particular happens asynchronously through event handlers. Any code that you can read from top to bottom happens more or less instantaneously (barring things like heavy DOM/CSS abuse, or a lot of CPU bound code).
It looks like you want to know how long it took to download the image? Watch for the load event on the image.
const startTime = performance.now();
const img = new Image();
img.addEventListener('load', () => {
const endTime = performance.now();
const downloadTime = endTime - startTime;
console.log(downloadTime);
});
img.src = 'https://www.google.co.uk/images/branding/googleg/1x/googleg_standard_color_128dp.png?' + Math.random();
An example of code that might happen synchronously would if you then inserted the image into the document and asked for the width/height, which would block while the image is decoded and rendered, and layout happens.

HTML5 video - setting video.currentTime breaks the player

I am trying to interact with a 3rd-party html5 video player in Chrome. I am able to obtain a valid reference to it thusly:
document.getElementsByTagName("video")[1]
...and the readyState is 4, so it's all good.
I can successfully (and with expected result) call:
document.getElementsByTagName("video")[1].play();
document.getElementsByTagName("video")[1].pause();
BUT when I call:
document.getElementsByTagName("video")[1].currentTime = 500;
...the video freezes and it doesn't advance to the new currentTime. The video duration is much longer than 500 seconds, so it should be able to advance to that spot. I have tried other times besides 500, all with same result. If I examine currentTime, it is correct as to what I just set. But it doesn't actually go there. Also I can no longer interact with the video. It ignores any calls to play() or pause() after I try to set currentTime.
Before I call currentTime, when I call play() I get this valid promise back, and everything else still works:
After I call currentTime, when I call play(), I get this broken promise back, and now nothing works on that video object:
If you have a Hulu account you can easily observe this behavior on any video by simply trying it in the Chrome developer console.
EDIT: It was pointed out to me that skipping very much ahead breaks, but skipping a short distance actually works well. Could be related to commercials interspersed.
Try below code, it will first pause then set your position then again play
document.getElementsByTagName("video")[1].pause();
document.getElementsByTagName("video")[1].currentTime = 500;
document.getElementsByTagName("video")[1].play();
Why don't you try this code.
function setTime(tValue) {
// if no video is loaded, this throws an exception
try {
if (tValue == 0) {
video.currentTime = tValue;
}
else {
video.currentTime += tValue;
}
} catch (err) {
// errMessage(err) // show exception
errMessage("Video content might not be loaded");
}
}
Pls. try this:
hv = document.getElementsByTagName("video")[1];
hv.play();
hv.addEventListener('canplay', function() {
this.currentTime = 500;
});
var myVideo=document.getElementsByTagName("video")
if(myVideo[1] != undefind)
{
myVideo[1].currentTime=500;
}
/* or provide id to each video tag and use getElementById('id') */
var myVideo=document.getElementById("videoId")
if(myVideo != undefind)
{
myVideo.currentTime=500;
}

JavaScript setTimeout Runs Twice

I am trying to make a function that starts in exact intervals to keep stanble update rate. The problem is that it seems to execute in 2 channels. This is the log:
timeElapsed=141; lastDraw=1314040922291
timeElapsed=860; lastDraw=1314040923151
timeElapsed=141; lastDraw=1314040923292
timeElapsed=860; lastDraw=1314040924152
timeElapsed=141; lastDraw=1314040924293
timeElapsed=860; lastDraw=1314040925153
timeElapsed=141; lastDraw=1314040925294
timeElapsed=860; lastDraw=1314040926154
timeElapsed=141; lastDraw=1314040926295
timeElapsed=859; lastDraw=1314040927154
timeElapsed=143; lastDraw=1314040927297
timeElapsed=858; lastDraw=1314040928155
timeElapsed=143; lastDraw=1314040928298
timeElapsed=858; lastDraw=1314040929156
timeElapsed=142; lastDraw=1314040929298
First, I exectute my function using
drawTimer = setTimeout(function(){ draw() }, 1);
and the function looks like this:
var draw = function(){
if(!running)
return;
var miliseconds = getCurrentMiliseconds();
var timeElapsed = miliseconds - lastDraw;
lastDraw = miliseconds;
console.log("timeElapsed=" + timeElapsed + "; lastDraw=" + lastDraw);
onDrawListener(timeElapsed);
if(timeElapsed < timeLapse)
miliseconds = timeLapse - timeElapsed;
else
miliseconds = 1;
drawTimer = setTimeout(function(){ draw() }, miliseconds);
}
It happens in both, Chrome and Firefox. Do you know what is it caused by? And... How to fix it?
P.S. Since everyone seems to be so confused about the running variable, here it is: it's a private parent object member that indicates whether the mechanism is still running or has stopped. It's set by other functions and is just there to make sure this function doesn't continue working after stop() is called.
-- update --
timeLapse is set to 1000 (1 time per seconds) and never changed again.
onDrawListener is set to this function:
function(timeElapsed){
canvas.clear();
moveSnake();
if(snake.body[0] == food){
food = getRandomFreePosition();
++snake.body.lenght;
}
drawBackground();
drawFood();
drawSnake();
}
to explain it: canvas is kinda the engine that takes care of callbacks, key listening and also has a few functions. Other than that seems kinda self-explaining. they do nothing other than some int algorithms and drawing in the canvas.
-- Figured out --
I understood that I should calculate time spent for current function and not since the last one started. My old method worked not in 2 channels but rather in long-short-long-short-long-... delayes
first of all you dont set the running bool and also when you enter the function immediately do a on clearTimeout on drawTimer.
clearTimeout(drawTimer);
In a loop like that, you should consider to write:
if(timeElapsed >= AMOUNT_OF_TIME)
{
// run code
}

Categories