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);
}
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
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>
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.
Related
I am not new to programming (Fortran, Python) but definitively new to javascript. I also apologize for below function having been taken from another Stack Overflow post but that is the best example I found for for helping me understand want I am trying.
I am writing a Reveal.js presentation that shall show satellite pictures named "sat_picts" + ymd + hr + min + ".jpg"". The pictures are updated every quarter of an hour but it may happen that when they are called the latest one is not yet available so I want to present the one before.
My readings make me understand that this is a characteristic of asynchronous behaviour of the problem and that callback functions are one way of solving the issue.
Unfortunately none of the examples or readings I found show explicitely how to retrieve the results of the callback function in my case "error" and "success". I need these results for an "if, else if" block which then writes in the HTML section the actual or the previous satellite pictures.
I would appreciate if someone could show how to resolve my issue or ev. how to better implement such a solution and or indicate me an appropriate reading.
<section> // Defines Reveal.js slide
<h5>Satellite Picture</h5>
<script>
function testImage(url, callback) {
let img = new Image();
img.onerror = img.onabort = function() {
// callback(url, "error");
let resx = callback(url, "error"); // thought was way to access resx i.e. "error"
alert(resx); // alert is working properly
}
img.onload = function() {
callback(url, "success");
let resy = callback(url, "success"); // thought was way to access resy i.e. "success"
alert(resy); // alert is working properly
}
img.src = url;
};
function record(url, result) {
// document.write(url, result); // ==> returns result but not in the Reveal.js HTML Section
return result;
};
let imgurl = "https://server/sat_picts" + ymd + hr + min + ".jpg";
testImage(imgurl, record);
// If the picture can be loaded then write satellite picture to document
if (resy === "success") document.write('<img src="https://server/sat_picts" + ymd + hr + min + ".jpg">')
// If the the picture cannot be loaded then write satellite picture from previous hour to document
else if (resx === "error") document.write('<img src="https://server/sat_picts" + ymd + hr + min_minus_15 + ".jpg">');
</script>
</section>
If I understand what you are trying to do, I think this is what you want:
<section id="targetSection">
<script>
// allow a success and error call back to be passed in as separate args
function testImage(url, successCallback, errorCallback) {
let img = new Image();
img.onerror = img.onabort = function() {
errorCallback();
}
img.onload = function() {
successCallback();
}
img.src = url;
};
// If the picture can be loaded then write satellite picture to document
function. onSuccess() {
// write the new image to the "section"
const img = document.createElement('img');
img.setAttribute('src', "https://server/sat_picts" + ymd + hr + min + ".jpg");
document.getElementById('targetSection').appendChild(img);
}
// If the the picture cannot be loaded then write satellite picture from previous hour to document
function onError() {
// write the new image to the "section"
const img = document.createElement('img');
img.setAttribute('src', "https://server/sat_picts" + ymd + hr + min_minus_15 + ".jpg");
document.getElementById('targetSection').appendChild(img);
}
let imgurl = "https://server/sat_picts" + ymd + hr + min + ".jpg";
// pass the two callbacks to be called on success or error respectively.
testImage(imgurl, onSuccess, onError);
</script>
</section>
I could be wrong about this, but it looks like the error might be the way the img tags are written in the document.write functions. The img tag needs to be closed with a '/' at the end:
<img src="https://server/sat_picts" + ymd + hr + min + ".jpg" />
The other possibility I can think of is a little gotcha in HTML, which is: when the containing element only has an image tag and no other content, then they won't show the image unless they have some dimensions (height, optionally width) associated with them, so you can check your HTML and if necessary add a CSS property to it that gives it some height (and/or width) to see if this is the issue.
e.g.
<div><img src="myImg.jpg" /></div>
would show nothing if the containing div hasn't been given a height (it will automatically span the width of the parent element).
I have an image - image1.png. When I click a button the first time, I want it to change to image2.png. When I click the button for a second time, I want it to change to another image, image3.png.
So far I've got it to change to image2 perfectly, was easy enough. I'm just stuck finding a way to change it a second time.
HTML:
<img id="image" src="image1.png"/>
<button onclick=changeImage()>Click me!</button>
JavaScript:
function changeImage(){
document.getElementById("image").src="image2.png";
}
I'm aware I can change the image source with HTML within the button code, but I believe it'll be cleaner with a JS function. I'm open to all solutions though.
You'll need a counter to bump up the image number. Just set the maxCounter variable to the highest image number you plan to use.
Also, note that this code removes the inline HTML event handler, which is a very outdated way of hooking HTML up to JavaScript. It is not recommended because it actually creates a global wrapper function around your callback code and doesn't follow the W3C DOM Level 2 event handling standards. It also doesn't follow the "separation of concerns" methodology for web development. It's must better to use .addEventListener to hook up your DOM elements to events.
// Wait until the document is fully loaded...,
window.addEventListener("load", function(){
// Now, it's safe to scan the DOM for the elements needed
var b = document.getElementById("btnChange");
var i = document.getElementById("image");
var imgCounter = 2; // Initial value to start with
var maxCounter = 3; // Maximum value used
// Wire the button up to a click event handler:
b.addEventListener("click", function(){
// If we haven't reached the last image yet...
if(imgCounter <= maxCounter){
i.src = "image" + imgCounter + ".png";
console.log(i.src);
imgCounter++;
}
});
}); // End of window.addEventListener()
<img id="image" src="image1.png">
<button id="btnChange">Click me!</button>
For achieve your scenario we have to use of counter flag to assign a next image. so we can go throw it.
We can make it more simple
var cnt=1;
function changeImage(){
cnt++;
document.getElementById("image").src= = "image" + cnt + ".png";
}
try this
function changeImage(){
var img = document.getElementById("image");
img.src = img.src == 'image1.png' ? "image2.png" : "image3.png";
}
Just use an if statement to determine what the image's source currently is, like so:
function changeImage(){
var imageSource = document.getElementById("image").src;
if (imageSource == "image1.png"){
imageSource = "image2.png";
}
else if (imageSource == "image2.png"){
imageSource = "image3.png";
}
else {
imageSource = "image1.png";
}
}
This should make the image rotate between 3 different image files (image1.png, image2.png and image3.png). Bear in mind this will only work if you have a finite number of image files that you want to rotate through, otherwise you'd be better off using counters.
Hope this helps.
Check the below code if you make it as a cyclic:
JS
var imgArray = ["image1.png", "image2.png", "image3.png"];
function changeImage(){
var img = document.getElementById("image").src.split("/"),
src = img[img.length-1];
idx = imgArray.indexOf(src);
if(idx == imgArray.length - 1) {
idx = 0;
}
else{
idx++;
}
document.getElementById("image").src = imgArray[idx];
}
html
<button onclick=changeImage();>Click me!</button>
function changeImage(){
document.getElementById("image").attr("src","image2.png");
}
My code:
I understand that my for loop assigns all array elements to the variable pickSound and that is why I am left with it only playing the last element. So how can I get it to play each element in order and start over once done.
function show() {
var sounds = new Array(
"audio/basement.mp3",
"audio/roll.mp3",
"audio/gatorade.mp3",
"audio/half.mp3",
"audio/hotdogs.mp3",
"audio/keys.mp3",
"audio/heil.mp3",
"audio/money.mp3",
"audio/ours.mp3",
"audio/pass.mp3"
);
for (var i = 0; i < sounds.length; i++){
var pickSound = sounds[i];
}
$('#divOne').html("<embed src=\""+ pickSound +"\" hidden=\"true\" autostart=\"true\" />");
return false;
};
You could try using the audio element and the ended event.
HTML:
<audio id="audio" controls preload></audio>
JS:
(function() {
var audioEl = document.getElementById('audio'),
counter = -1,
songs = [
'http://forestmist.org/wp-content/uploads/2010/04/html5-audio-loop.mp3',
'http://www.quackit.com/music/good_enough.mp3',
'http://forestmist.org/wp-content/uploads/2010/04/html5-audio-loop.mp3',
'http://www.quackit.com/music/good_enough.mp3'];
function nextSong() {
audioEl.removeEventListener('ended', nextSong);
if (songs[++counter]) {
audioEl.src = songs[counter];
audioEl.addEventListener('ended', nextSong);
audioEl.play();
} else {
alert('All done!');
}
}
nextSong();
}());
DEMO
" I should have added that each click of the button plays a new sound. So a new sound can be played at any time."
I take it that means the sounds aren't to be played continuously on an automatic loop. You intend for a click of a button to play whichever sound is next and then stop?
In the following code the nextSound variable holds the index of whatever sound should be played next. When a button is clicked (insert your button's ID or other selector as appropriate) the file name associated with that index is used and then nextSound is incremented using the modulus operator to loop back to zero when it gets to the end of the array.
$(document).ready(function() {
var sounds = ["audio/basement.mp3",
"audio/roll.mp3",
"audio/gatorade.mp3",
"audio/half.mp3",
"audio/hotdogs.mp3",
"audio/keys.mp3",
"audio/heil.mp3",
"audio/money.mp3",
"audio/ours.mp3",
"audio/pass.mp3"],
nextSound = 0;
$("#yourButtonIDHere").click(function() {
$('#divOne').html("<embed src=\""+ sounds[nextSound] +"\" hidden=\"true\" autostart=\"true\" />");
nextSound = (nextSound + 1) % sounds.length;
});
});
Note also that it is generally recommend to declare arrays with the square bracket [] array literal syntax rather than new Array().
I am very stuck. I messed with this for at least 30mins and I can't figure out how to preload metadata only.
if you go here you'll see there is a preload attribute that allows you to specify what to preload. I marked it down as metadata only because I wanted the time length of every audiofile. Since it wasn't loading I tried .load() and that loads the actual audio even though I specified metadata.
How do I load the meta in html5 javascript? if it loaded a second or two of audio I wont mind as long as it isn't trying to preload minutes or the whole file.
http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-networkstate
Value 2 = NETWORK_LOADING, where 1 = idle aka loaded.
http://jsfiddle.net/CD3BZ/
<body>
<div>Click to test if loaded</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8">
try{
var a1 = new Audio('http://freshly-ground.com/data/audio/sm2/20060924%20-%20Ghosts%20&%20Goblins%20Reconstructed.ogg');
$(a1).bind('loadedmetadata', function(e){
//alert('a1 ' + a1.duration + ' ' + a1.networkState);
});
$(a1).bind('canplay', function(e){
alert('a1z ' + ' ' + a1.networkState);
});
$(a1).attr('preload', 'metadata');
a1.preload = 'metadata';
//alert(a1.duration);
//a1.play();
a1.load();
$('div').click(function(e){
alert('a1z ' + ' ' + a1.networkState);
});
}
catch(e){
alert(e);
}
</script>
</body>
You should be able to use the preload attribute:
<audio autobuffer preload=”metadata” src=”audio.foo”></audio>
There's also a suggestion and some discussion on Chris Pearce's blog, which basically suggests pausing and then setting src to an empty string. As explained in comments on the post, this method is not ideal... You could (in theory) do this as soon as the loadedmetadata event has fired, and you've retrieved the metadata you need.
This question suggest setting headers, which also might work...
You shouldn't call the load method.
I created a function that returns a promise which will be resolved when the metadata is loaded. With this function only the metadata of the audio file will be loaded:
function getAudioMetaData(src) {
return new Promise(function(resolve) {
var audio = new Audio();
$(audio).on("loadedmetadata", function() {
resolve(audio);
});
audio.preload = 'metadata';
audio.src = src;
});
}
So in my script I have...
<script type="text/javascript">
var images = new Array();
var numImages = 3;
var index = 0;
function setupSwapper() {
for (i = 0; i < numImages; i++) {
images[i] = new Image(266, 217);
images[i].src = "images/image" + i + ".png";
}
setTimeout("swapImage()", 5000);
}
function swapImage() {
if (index >= numImages) {
index = 0;
}
document.getElementById('myImage').src = images[index].src
index++;
setTimeout("swapImage()", 5000);
}
</script>
And then I have <body onload="setupSwapper()"> to setup the body.
and <img width=266 height=217 id="myImage" name="myImage" src="images/image0.png"></img> elsewhere in my document.
Only the initial image (image0.png) is showing up. I'm probably blind from having looked at this so long. The images are not swapping.
Use FireBug or a similar tool for debugging what's going on:
Does the img DOM element in fact gets its src changed ?
Do you see any network activity trying to load the images ? does it succeed ?
Set up breakpoints in your code and see what happens in the debugger
BTW - You can use setInterval instead of setTimeout - it sets a repeating timer
You're missing the () in the definition of "setupSwapper".
Also it's setTimeout, not setTimeOut.
Finally, get rid of the "type" attribute on your <script> tag.
You might want to start "index" at 1 instead of 0.
The way to go:
setTimeout(swapImage, 5000);
[FORGET] the type attribute has nothing to do with it
[FORGET] the index has nothing to do with it
[OPTIONAL] remove "name" attribute from the image (useless)
[OPTIONAL] close image tags like <img />
Note: 2-5 is about correctness. Only the first one is important to get it work.
Get Firebug, use it's debugger to put breakpoints inside swapImage to see if it is hit after the timeout. Another way is to use the console.* apis to see what's happening(e.g. console.log).