Reputation: 75
I am trying to get the duration of audio, but the result is NaN
. the following is what I did, Can someone tell me what the problem is?
<audio class="my-audio"></audio>
$(document).ready(function(){
addAudioPlayer();
});
//dynamically assign id to an audio player
function addAudioPlayer(){
var index = 0;
//find class"my-audio-player"
$(document).find(".my-audio-player").each(function(){
$(this).attr('id', 'player'+index );
$(this).append("<source src=\""+mediaFiles[index]+"\" type=\"audio/mp3\" >")
new AudioPlayer(mediaFiles[index], index);
index++;
});
}
function AudioPlayer(media, index){
//set objects reference
this.audio = document.getElementById("player"+ index);
this.audio.onloadedmetadata;
this.audio.play();
console.log("audio = " + this.audio);
console.log("audio src = " + this.audio.src);
console.log("audio dur = " + this.audio.duration);
}
Upvotes: 2
Views: 5394
Reputation: 136598
You absolutely don't need to dig through your <audio>
element's childNodes, which is prone to error errors (e.g if you've got multiple <source>
elements as you're supposed to, or if you set a fallback message into the <audio>
element.)
In order to get the address of the currently playing media, you need to check for the AudioElement.currentSrc property.
Your problem of duration returning NaN is just that you tried to get it before the metadata were loaded, because you didn't set anything to the loadedmetadata
event, and even if you did, the this
was not referencing to the good object anymore.
var mediaFiles = ["http://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.mp3"];
//dynamically assign id to an audio player
function addAudioPlayer(){
var index = 0;
//find class"my-audio-player"
$(document).find(".my-audio-player").each(function(){
$(this).attr('id', 'player'+index );
$(this).append("<source src=\""+mediaFiles[index]+"\" type=\"audio/mp3\" >")
new AudioPlayer(mediaFiles[index], index);
index++;
});
}
function AudioPlayer(media, index){
//set objects reference
// instead of passing the index, you could simply pass the element as argument
this.audio = document.getElementById("player"+ index);
// you forgot to set onloadedmetadata as a function
this.audio.onloadedmetadata = function(){
// "this" now refers to the audio element
this.play();
snippet.log("audio = " + this);
// to get the audio src, it is always preferred to get its currentSrc (the one of the choosen media resource)
snippet.log("audio src = " + this.currentSrc);
snippet.log("audio dur = " + this.duration);
}
}
addAudioPlayer();
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<audio class="my-audio-player" controls></audio>
Upvotes: 1
Reputation: 13089
Further to my comment above, here's a working demo. I've opted to swap the jQuery references for vanilla JS, since that's my preference (and I've not taken the time to familiarize myself with jQuery. One of these years perhaps :) )
You can easily put it back in.
You won't be getting a valid result for the source either, since the <audio>
element's src
attribute is empty, what you want is the src
attribute of the source
element that's contained within the <audio>
element.
Another oversight is the scope. Since the onloadedmetadata
event is a part of the <audio>
element object, the this
keyword refers to the <audio>
element
this.audio.play()
should be this.play()
, this.audio
should be replaced with this
, this.audio.src
should be changed to this.childNodes[0].src
(remember, we want the src attribute of the source element) and finally, this.audio.duration
should be this.duration
.
It's also worth pointing out that the this
on the first line of your AudioPlayer
refers to the AudioPlayer
function (object) since you're not assigning the result of new AudioPlayer to anything, this line is essentially doing nothing and can be altered slightly, I've just grabbed a reference to the <audio>
element and from there set its onloadmetadata
event handler.
Phew! Got a sore head yet? Scoping in javascript catches everybody out at some point. Many still never quite get the hang of it. ;)
First, here's the complete code:
(EDIT #2: updated code so that it displays the progress of each of the playing tracks, updated every 500ms and displayed as a % of the track length - it should be sufficient to give you an idea, I hope)
<!DOCTYPE html>
<html>
<head>
<script>
"use strict";
function allByClass(className,parent){return (parent == undefined ? document : parent).getElementsByClassName(className);}
function forEachNode(nodeList, func){for (var i=0, n=nodeList.length; i<n; i++) func(nodeList[i], i, nodeList); }
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
addAudioPlayer();
}
var mediaFiles = ['audio/voice_welcomeback.mp3', 'audio/Some Chords.mp3'];
var progressIntervalHandle;
function addAudioPlayer()
{
var index = 0;
var audioPlayers = allByClass('my-audio-player');
forEachNode(audioPlayers, audioPlayerEnumCallback);
progressIntervalHandle = setInterval(updateProgress, 500);
function audioPlayerEnumCallback(elem, index, elemArray)
{
elem.id = 'player'+index;
elem.innerHTML = "<source src='" + mediaFiles[index] + "' type='audio/mp3'>";
new AudioPlayer(mediaFiles[index], index);
index++;
}
}
function AudioPlayer(media, index)
{
document.getElementById("player"+ index).onloadedmetadata = function(evt)
{
this.play();
console.log("audio = " + this);
console.log("audio src = " + this.childNodes[0].src);
console.log("audio dur = " + this.duration);
}
}
function updateProgress()
{
var audioElems = allByClass('my-audio-player');
var outputMsg = '';
forEachNode(audioElems, audioElemEnumProgressCallback);
document.getElementById('progressOutput').innerHTML = outputMsg;
function audioElemEnumProgressCallback(elem, index, elemAray)
{
outputMsg += "Song " + index + ": " + ((100*elem.currentTime) / elem.duration).toFixed(2) + "%" + "<br>";
}
}
</script>
<style>
</style>
</head>
<body>
<audio class="my-audio-player"></audio>
<audio class="my-audio-player"></audio>
Progress:<br>
<div id='progressOutput'></div>
</body>
</html>
EDIT #1: The console output appears pretty much instantaneously upon page-load and the audio files are (both) playing immediately too. I'd have to use a screen-recording application to get even semi-accurate figures, since it all happens so quickly.
EDIT #3: Here's the console output. (the line numbers should be on the right-hand side)
audioDemo.html:42 audio = [object HTMLAudioElement]
audioDemo.html:43 audio src = file:///C:/xampp/htdocs/enhzflep/audio/voice_welcomeback.mp3
audioDemo.html:44 audio dur = 1.68
audioDemo.html:42 audio = [object HTMLAudioElement]
audioDemo.html:43 audio src = file:///C:/xampp/htdocs/enhzflep/audio/Some%20Chords.mp3
audioDemo.html:44 audio dur = 444.186122
Upvotes: 1
Reputation: 12508
As @PaulRoub pointed out from the linked MDN Arcticle:
If the media data is available but the length is unknown, this value is NaN. If the media is streamed and has no predefined length, the value is Inf.
To fix this, check for the value of NaN
and replace it with something more desirable to the user, such as maybe unknown or unknown duration.
See below for a quick implementation:
console.log("audio dur = "
+ ((this.audio.duration != this.audio.duration) ? "unknown" : this.audio.duration));
This is a quick way of searching for the value NaN
. this.audio.duration != this.audio.duration
will be true if and only if this.audio.duration
is NaN
.
Upvotes: 0