html5 load audio time/metadata but not audio? - javascript

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

Related

audio duration is NaN

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.

How can I call revokeObjectURL only after the URL has loaded?

I have a web application. It runs in Google Chrome and is not required to work in any other browser.
I have PDF data which has been generated on the server and sent back to the client in an AJAX request.
I create a blob from the PDF data.
I use window.URL.createObjectURL to create a URL from the blob, which I then load into a window (my preview_window) which has previously been created to show the PDF.
To load the URL, I set preview_window.location.href.
I would like to call revokeObjectURL to avoid wasting more and more resources as new PDFs are generated and previewed in the window.
The problem is that calling it immediately after setting preview_window.location.href is too soon, and stops the PDF from being displayed. So I would like to call revokeObjectURL only once the URL has been loaded.
I have tried setting preview_window.onload to a callback for this purpose, but it never gets called.
I would like to know:
Is it possible to trigger a callback when the window has loaded the URL, as I am trying to do? How?
Is there another approach to ensure revokeObjectURL gets called in a timely manner?
If I cannot trigger revokeObjectURL when the window finishes loading the URL, I may revoke each URL immediately before generating a new one. But I would rather revoke the URL as soon as it is done loading, if possible.
I have prepared a html file which demonstrates the situation pretty well:
<html>
<head>
<title>Show PDF Demo</title>
<script>
var build_blob = function(mime_type, data) {
var buf = new ArrayBuffer(data.length);
var ia = new Uint8Array(buf);
for (var i = 0; i < data.length; i++) ia[i] = data.charCodeAt(i);
var blob = new Blob([ buf ], { type: mime_type });
return blob;
};
window.onload = function(e) {
document.getElementById('preview_button').onclick = function(e) {
// open the window in the onclick handler so we don't trigger popup blocking
var preview_window = window.open(null, 'preview_window');
// use setTimeout to simulate an asynchronous AJAX request
setTimeout(function(e) {
var pdf_data = atob(
"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZwovT3V0bGluZXMgMiAwIFIKL1BhZ2Vz" +
"IDMgMCBSCj4+CmVuZG9iagoyIDAgb2JqCjw8IC9UeXBlIC9PdXRsaW5lcwovQ291bnQgMAo+Pgpl" +
"bmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZXMKL0tpZHMgWzQgMCBSXQovQ291bnQgMQo+Pgpl" +
"bmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAw" +
"IDUwMCAyMDBdCi9Db250ZW50cyA1IDAgUgovUmVzb3VyY2VzIDw8IC9Qcm9jU2V0IDYgMCBSCi9G" +
"b250IDw8IC9GMSA3IDAgUiA+Pgo+Pgo+PgplbmRvYmoKNSAwIG9iago8PCAvTGVuZ3RoIDczID4+" +
"CnN0cmVhbQpCVAovRjEgMjQgVGYKMTAwIDEwMCBUZAooU01BTEwgVEVTVCBQREYgRklMRSkgVGoK" +
"RVQKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqClsvUERGIC9UZXh0XQplbmRvYmoKNyAwIG9iago8" +
"PCAvVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL05hbWUgL0YxCi9CYXNlRm9udCAvSGVsdmV0" +
"aWNhCi9FbmNvZGluZyAvTWFjUm9tYW5FbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDgKMDAwMDAw" +
"MDAwMCA2NTUzNSBmCjAwMDAwMDAwMDkgMDAwMDAgbgowMDAwMDAwMDc0IDAwMDAwIG4KMDAwMDAw" +
"MDEyMCAwMDAwMCBuCjAwMDAwMDAxNzkgMDAwMDAgbgowMDAwMDAwMzY0IDAwMDAwIG4KMDAwMDAw" +
"MDQ2NiAwMDAwMCBuCjAwMDAwMDA0OTYgMDAwMDAgbgp0cmFpbGVyCjw8IC9TaXplIDgKL1Jvb3Qg" +
"MSAwIFIKPj4Kc3RhcnR4cmVmCjYyNQolJUVPRg=="
);
/*
Warning: for my Chrome (Version 44.0.2403.155 m), the in-built PDF viewer doesn't seem
to work with a blob when this html page is loaded from the local filesystem. I have only
got this to work when fetching this page via HTTP.
*/
var pdf_blob = build_blob('application/pdf', pdf_data);
var pdf_url = window.URL.createObjectURL(pdf_blob);
preview_window.onload = function(e) {
console.log("preview_window.onload called"); // never happens
window.URL.revokeObjectURL(pdf_url);
};
preview_window.location.href = pdf_url;
console.log("preview_window.location.href set");
}, 500);
};
};
</script>
</head>
<body>
<button id="preview_button">Show Preview</button>
</body>
</html>
Although my demo code above avoids it, I do have jQuery loaded for my application, so if that makes things easier I'm open to using it.
I did find this question in a search, but in that situation the main window ("window") is pointed to a new URL, and the OP never got a response when asking in comments whether it makes a difference if the window came from window.open.
As you found out, you can't set open()ed windows' onload event from the opener.
You will have to inject some script in the second page that will call its window.opener functions.
But since you are opening a pdf file, the browser will re-parse entirely your page and your injected code will vanish.
The solution, as you found out yourself in the comments, is to inject the blob's url in an iframe, and wait for this iframe's load event.
Here is how :
index.html
<script>
// The callback that our pop-up will call when loaded
function imDone(url){
window.URL.revokeObjectURL(url);
}
var build_blob = function(mime_type, data) {
var buf = new ArrayBuffer(data.length);
var ia = new Uint8Array(buf);
for (var i = 0; i < data.length; i++) ia[i] = data.charCodeAt(i);
var blob = new Blob([ buf ], { type: mime_type });
return blob;
};
var preview_window=null;
window.onload = function(e) {
document.getElementById('preview_button').onclick = function(e) {
if(preview_window===null || preview_window.closed){
// open the window in the onclick handler so we don't trigger popup blocking
preview_window = window.open('html2.html', 'preview_window');
}
// avoid reopening the window since it may cache our last blob
else preview_window.focus();
// use setTimeout to simulate an asynchronous AJAX request
setTimeout(function(e) {
var pdf_data = /* Your pdf data */
var pdf_blob = build_blob('application/pdf', pdf_data);
var pdf_url = window.URL.createObjectURL(pdf_blob);
// Simple loop if our target document is not ready yet
var loopLoad = function(url){
var doc = preview_window.document;
if(doc){
var iframe = doc.querySelector('iframe');
if(iframe)iframe.src = url;
else setTimeout(function(){loopLoad(url);},200);
}
else setTimeout(function(){loopLoad(url);},200)
};
loopLoad(pdf_url);
}, 0);
};
};
</script>
and the html2.html
<html>
<head>
<title>Iframe PDF Demo</title>
<style>
body, html, iframe{margin:0; border:0}
</style>
</head>
<body>
<iframe width="100%" height="100%"></iframe>
<script>
document.querySelector('iframe').onload = function(){
//first check that our src is set
if(this.src.indexOf('blob')===0)
// then call index.html's callback
window.opener.imDone(this.src);
}
</script>
</body>
</html>
Live Demo

Audio recording javascript

Is it possibly in Jquery or Javascript, without the use of libraries to record audio for X seconds and save to a audio file. I've looked at getUserMedia, an I see that it can retrieve audio and video from the webcam. I am currently using the webcam with another library: Clmtracker and for that reason I would want to try and do this without any more external libraries.
I want to store the recorded audio in a div. I am currently taking a picture and assigning it a unique name, so I am wondering how I can capture a short section of audio and store it in the same generated div.
Question: How can I achieve getting audio for 5 seconds from webcam?
Sub-Question: How can I store that in an element within a div?
My Code for capturing Img and Data on my page
function capturePage() {
var now = new Date();
if (nextAllowedCapture <= now) {
// Do capture logic
audioElement.play();
counting++
console.log("Capturing...");
$(".capturing").show().delay(500).fadeOut(3000);
var innerDiv = document.createElement('div'),
innerDivImg = document.createElement('canvas'),
image = document.createElement('img'),
ul = document.createElement('ul');
innerDiv.className = 'divCreate';
innerDiv.id = 'img' + counting;
innerDivImg.height = 450;
innerDivImg.width = 600;
innerDivImg.getContext('2d').drawImage(vid, 0, 0);
image.id = 'image' + counting;
image.src = innerDivImg.toDataURL();
image.className = 'divCreateImg';
innerDiv.appendChild(image);
innerDiv.appendChild(ul);
document.getElementById('galleryWrapper').appendChild(innerDiv);
$('#measurements h4').each(function () {
$("#" + innerDiv.id + " " + 'ul').append('<li>' + $(this).text() + ': ' + $(this).next().text());
});
nextAllowedCapture = new Date();
nextAllowedCapture.setSeconds(nextAllowedCapture.getSeconds() + coolDownSeconds);
} else {
nextCapTime = (nextAllowedCapture.getTime() - now.getTime()) / 1000;
console.log("Can't capture again yet. This function can be executed again in " +
(nextAllowedCapture.getTime() - now.getTime()) / 1000 +
" seconds.");
}
}
You can see the code for Recorder.js as an example which implements the capture audio functionality and saves it in wav format using getUserMedia annd yes, just html5 and javascript.
Now you might say that you asked without any plugins. In fact, I checked the code for recorder.js which is just 90 lines of plain javascript, just html5 and javascript.
UPDATE: I found one online demo which experiments with this and it seems to work fine and all source code is open. You might want to check that out.
Here is the link:
Recorder JS
Here is another link to tweak it and make it more functional.
Recording MP3 Using Only HTML5 and JavaScript

Dynamic sounds not playing after two plays

I have a little html5 application where you can play a sound by clicking a button.
I have a function that adds an <audio> tag to a <div> with an id "playing." The sound removes itself when it is done.
function sound(track){
$("#playing").append("<audio src=\"" + track + "\" autoplay onended=\"$(this).remove()\"></audio>");
}
For the button I have:
<button onclick="sound('sounds/tada.mp3')">Tada</button>
When I click the button, an <audio> briefly appears in the element inspector and disappears when it is finished, just the way I want it, but after triggering it two times, it just stops working in Chrome, at least. There are no errors in the console either.
What is going on?
Get rid of the onclick/onend in your HTML and reference the button in your js:
HTML
<button id='tada' sound_url='sounds/tada.mp3'>Tada</button>
And the JS
var sound = function(track){
$("#playing").append("<audio id='played_audio' src='\" + track + \"' autoplay='true'></audio>");
}
$('#tada').on('click', function () {
var sound_url = $(this).attr('sound_url');
sound(sound_url);
});
$('#playing').on('end', 'played_audio', function() {
$(this).remove();
});
Okay, lets see..
var audioURL = "http://soundbible.com/mp3/Canadian Geese-SoundBible.com-56609871.mp3";
var audioEl = null;
function removeAudio() {
if (audioEl && audioEl.parentNode)
audioEl.parentNode.removeChild(audioEl);
}
function sound() {
removeAudio();
audioEl = document.createElement("audio");
audioEl.src = audioURL;
audioEl.controls = true;
audioEl.addEventListener("ended", removeAudio); // <-- Note it's ended, not end!
document.getElementById("playing").appendChild(audioEl);
audioEl.play();
}
document.getElementById("tada").addEventListener("click", sound);
<div id="playing">
</div>
<button id="tada">Tada</button>
I'm not seeing any problems with this script.
Decide audioURL, set audioEl to null as it will be used later
When the element with ID "tada" is clicked, run our sound function.
Remove the audio.
Create the audio element.
When the audio is finished, remove the audio.
Append the audio to the element with ID "playing".
Play the audio.
One thing to note is that I use the ended event, not the end event.
(This answer is here because Andrew really wants us to answer it.)

HTML5 video loading time

I am trying to calculate the measure time of html 5 video. I use Javascript to listen to html5 video event loadstart and canplaythrough using:
media.addEventListener('loadstart'getStartTime(){
startTime = new Date().getTime();},
false)
and similar for endTime with event set as canplaythrough to listen.
However I could not get any data.
Can someone please guide me how to measure video load time using Javascript.
Thank you for your response, but the solution is I believe using jQuery; however, I was wondering if it is possible from Javascript. I have attached a copy of my code:
function loadVideo(){
var timeNow = Date.now(), timeStartLoad, timeFinishLoad;
myVideos = new Array();
myVideos[0] = "trailer.mp4";
myVideos[1] = "trailer.ogg";
myVideos[2] = "trailer.m4v";
var videoId = document.getElementById('idForVideo');
var video = document.createElement('video');
for(var i=0; i<myVideos.length; i++){
var source = document.createElement('source');
source.setAttribute('src', myVideos[i]);
video.appendChild(source);
}
video.load();
video.addEventListener('loadstart', function(){
timeStartLoad = Date.now() - timeNow;
}, false);
video.addEventListener('hasenoughdata', function(){
timeFinishLoad = Date.now() - timeStartLoad;
}, false);
idForVideo.appendChild(video);
newDiv = document.getElementById('newDiv');
newDiv.innerHTML = "BodyLoad: " + timeNow + " " + "; Video Load: " + timeStartLoad + "; Video Loaded: " + timeFinishLoad;
//alert(timeStartLoad);
}
However I get undefined for both timestartLoad and timeFinishLoad. My html body has onload method linked to this function.
Your code has some (copy & paste) syntax problems.
var timeInit = Date.now(), timeLoad, timeCanPlay;
$("movie").addEventListener('loadstart', function(){
timeLoad = Date.now();
$("t1").innerHTML = "load: " + (timeLoad - timeInit) + " msecs";
});
$("movie").addEventListener('canplaythrough', function(){
timeCanPlay = Date.now();
$("t2").innerHTML = "canplay: " + (timeCanPlay - timeLoad) + " msecs";
});
$("movie").src = "http://ia600208.us.archive.org/12/items/FarSpeak1935/FarSpeak1935_512kb.mp4";
$("movie").play();
Try out: http://jsfiddle.net/noiv/98xZP/:
Hey, I figured it out.
The reason seems to be due to the fast responsiveness of the browser that it fetches the data before even the event is catched. A solution as provided by opera seems to work. Include the event listener at the inline script and make addEventListener for window object.
More details at:
http://dev.opera.com/articles/view/consistent-event-firing-with-html5-video/
While I can't comment, I have added a fork to the fiddle posted above by #noiv , as the fiddle had some JS errors. Thanks #noiv for putting me on the right direction.
http://jsfiddle.net/truedat101/Gbfj2/7/
$("movie").addEventListener('loadstart', function(){
timeLoad = Date.now();
console.log("loadstart event time: " + timeLoad + ", delta: " + (timeLoad - timeInit));
// alert("loadstart event time: " + timeLoad + ", delta: " + (timeLoad - timeInit));
});
It is worth noting the whatwg is attempting to get some uniformity around video metrics in the browser, so that there will be some common attributes supported by the browser, though it appears already that this work will be split across party lines, with Mozilla supporting their own metrics, Chromium team supporting theirs, and Apple Safari supporting theirs.

Categories