Is this the best way to buffer audio in javascript? - javascript

I have streaming audio in wave format being played out through html5 capabilities of firefox. Because some of our users complained about choppy audio we decided to come up with a strategy for measuring if the rate of the audio was good enough to sustain playback. We first tried the canplaythrough event, but apparently the browser was too optimistic about it and it did not quite work. Here is some code that I am proposing using jquery. I am a js/jquery beginner so i wanted to see if anyone had any better ideas.
$(document).ready(function() {
var previous_buffer = 0;
var start_time = (new Date()).getTime();
// function to check how fast buffer is
function checkBufferSpeed() {
// if we havent started buffering yet, return
if (this.buffered.length < 1)
{
return;
}
// get the current buffer status
var current_buffer = this.buffered.end(0);
console.log("current_buffer:" + current_buffer);
// if we get the same current buffer twice, browser is done buffering
if (current_buffer > 0 && current_buffer == previous_buffer)
{
this.play();
$("#audio").off("progress");
return;
}
// get the time spent
var time_spent = ((new Date()).getTime() - start_time)/1000;
console.log("time_spent:" + time_spent);
// if we buffered faster than time spent, play
if (current_buffer > time_spent)
{
this.play();
$("#audio").off("progress");
return;
}
previous_buffer = current_buffer
}
$("#audio").on("progress", checkBufferSpeed);
});
also, the reason i am not checking the audio elements duration variable because firefox seems to always report that as infinite, unless the stream is already cached.
also, it seems like firefox always fires 2 progress events even if the stream is fully available in cache which is why i have the check to see if i have the same buffer twice and if so, to just play the sound.
is there a chance that the browser will never fire progress events?
any enhancements I could make to this code to make it better?

Related

Set videoElement.currentTime in Hulu video player doesn't work, and breaks the player

I have a Chrome extension in which I'm trying to jump forward or backward (based on a user command) to a specific time in the video by setting the currentTime property of the video object. Before trying to set currentTime, a variety of operations work just fine. For example:
document.getElementsByTagName("video")[1].play(); // works fine
document.getElementsByTagName("video")[1].pause(); // works fine
document.getElementsByTagName("video")[1].muted = true; // works fine
document.getElementsByTagName("video")[1].muted = false; // works fine
BUT as soon as I try to jump to a specific point in the video by doing something like this:
document.getElementsByTagName("video")[1].currentTime = 500; // doesn't work
No errors are thrown, the video pauses, and any attempted actions after this point do nothing. So the items shown above (play/pause/mute/unmute) no longer work after attempting to set currentTime. If I read the value of currentTime after setting it, it correctly displays the new time that I just set it to. Yet nothing I do will make it play, and in fact even trying to make the video play by clicking the built-in toolbar no longer works. So, apparently setting currentTime wreaks all kinds of havoc in the video player. Yet if I reload the video, all works as before as long as I don't try to set currentTime.
I can easily jump to various times (backward or forward) by sliding the slider on the toolbar, so there must be some way internally to do that. Is there some way I can discover what code does a successful time jump? Because it's a Chrome extension I can inject custom js into the executing Hulu js, but I don't know what command I would send.
Any ideas?
Okay I fiddled around with it for a little while to see how I could reproduce the click event on the player and came up with the following solution:
handleViewer = function(){
var thumbnailMarker = $('.thumbnail-marker'),
progressBarTotal = thumbnailMarker.parent(),
controlsBar = $('.controls-bar'),
videoPlayer = $('#content-video-player');
var init = function(){
thumbnailMarker = $('.thumbnail-marker');
progressBarTotal = thumbnailMarker.parent();
controlsBar = $('.controls-bar');
videoPlayer = $('#content-video-player');
},
check = function(){
if(!thumbnailMarker || !thumbnailMarker.length){
init();
}
},
show = function(){
thumbnailMarker.show();
progressBarTotal.show();
controlsBar.show();
},
hide = function(){
controlsBar.hide();
},
getProgressBarWidth = function(){
return progressBarTotal[0].offsetWidth;
};
return {
goToTime: function(time){
var seekPercentage,
duration;
check();
duration = videoPlayer[0].duration;
if(time > 0 && time < duration){
seekPercentage = time/duration;
this.jumpToPercentage(seekPercentage);
}
},
jumpToPercentage: function(percentage){
check();
if(percentage >= 1 && percentage <= 100){
percentage = percentage/100;
}
if(percentage >= 0 && percentage < 1){
show();
thumbnailMarker[0].style.left = (getProgressBarWidth()*percentage)+"px";
thumbnailMarker[0].click();
hide();
}
}
}
}();
Once that code is initialized you can do the following:
handleViewer.goToTime(500);
Alternatively
handleViewer.jumpToPercentage(50);
I've tested this in chrome on a MacBook pro. Let me know if you run into any issues.
Rather than try to find the javascript responsible for changing the time, why not try to simulate the user events that cause the time to change?
Figure out the exact sequence of mouse events that trigger the time change.
This is probably some combination of mouseover, mousedown, mouseup, and click.
Then recreate those events synthetically and dispatch them to the appropriate elements.
This is the approach taken by extensions like Stream Keys and Vimium.
The video should be ready to play before setting the currentTime.
Try adding this line before setting currentTime?
document.getElementsByTagName("video")[1].play();
document.getElementsByTagName("video")[1].currentTime = 500;
Looks like it works if you first pause, then set currentTime, then play again.
document.getElementsByTagName("video")[1].pause()
document.getElementsByTagName("video")[1].currentTime = 800.000000
document.getElementsByTagName("video")[1].play()
Probably would need to hook into some event like onseeked to put in the play command to make it more robust.

How to check if html5 video is buffered enough to play without stopping to buffer?

So My question is how would you check a video to see if its buffered enough to play but without stopping to buffer again and if this is true then play the video.
OR how would I check if the video has buffered 50% and then if it has play the video.
What I've tried (But it didn't seem right when I looked at the buff amount in the controls it seemed to not of buffered alot)
var Video = document.getElementById("videoPlayer");
Video.oncanplaythrough = function HasBuff() {
alert("Is Buffered");
Video.play();
};
As said before this code didnt seem to have alot of buff when looking in the controls of the video not even 1/4 was buffered, Prehaps What would be better is to check if the video has buffered 50% or so and then play it, though I'm not to sure of how to do this or go about it.
Thank you for reading,
I'm at "beginer level" so Sorry if this seems an easy or silly question but we all have to start somewhere right? :)
Thanks again.
I think you can use this onplaying event
var vid = document.getElementById("myVideo");
vid.onplaying = function() {
alert("The video is now playing");
};
or if you want to check the video is currently buffer you can use onwaiting event more info here http://www.w3schools.com/tags/ref_av_dom.asp
I made a little test:
var vid = document.getElementById("video");
var buffered = function() {
var bufferedPercent =
vid.duration > 0 && vid.buffered.length > 0 ?
vid.buffered.end(0) / vid.duration * 100 :
0;
return 'buffered ' + bufferedPercent.toFixed(0) + '%';
};
vid.onprogress = function() {
console.log('progress: ' + buffered());
};
vid.oncanplay = function() {
console.log('canplay: ' + buffered());
};
vid.oncanplaythrough = function() {
console.log('canplaythrough: ' + buffered());
};
vid.onsuspend = function() {
console.log('suspend: ' + buffered());
};
On Chrome I get output like this:
canplay: buffered 5%
canplaythrough: buffered 5%
progress: buffered 15%
progress: buffered 25%
suspend: buffered 25%
With my test video "buffered" never gets beyond 25% (autoplay disabled). On Firefox in stops at 19%.
So it looks like you cannot force the browser to buffer more than it wants to. I also tried calling load() explicitly, no difference. Maybe if you seek forward it might buffer more, but then it might discard the already buffered part (I didn't try that out). But the browsers seemed to buffer quite a bit more after firing canplaythrough event, and then fired suspend event when they stopped buffering, so maybe you can use that.
In summary, the options I see are:
The ideal way: Trust the browser's estimation on when to start playing (autoplay = true).
The hacky way: Wait for the suspend event and start the video then (autoplay = false). These two browsers seemed to buffer a bit more that way.
Something like this:
vid.onsuspend = function() {
if (vid.paused) {
vid.play();
}
};

Throttling HTML5 Video currentTime

I have an app that tracks video views and integrates it with other marketing activities. In doing so, I needed to keep track of how long a person watches a html5 video and post it back to my app (via an API). I'm using videojs player, but really this is just a wrapper around the HTML5's api for this attribute. This is in an app with various videos can be loaded based on what page they are watching, so I needed a solution that tracked regardless of video length.
The problem I had, as a video plays the API reports back every ~300MS and I didn't want to hit my API that often. So I needed a solution to keep track of last time I posted. After digging around, I couldn't find an answer, so in case someone else with a similar need, my solution to this problem is below.
We've decided that I wanted to post my video viewing results every 5 seconds, but since we have no guarantee that the currentTime will report back at exactly 5 seconds, so we just need to round to closest whole integer value.
On my video wrapper div, I've added a data attribute called data-last-time-push. I post the rounded time every time I push and check to see if we have exceed the interval before we post again.
HTML
<div id="video-wrapper" data-time-last-push="0">
Javascript
Bind the videojs container to the timeupdate property.
var vid = videojs("video-container", {}, function() {
this.on('timeupdate', videoTracker);
});
function for posting ajax...
var videoTracker = function() {
var player = this;
var last_push, wrapper, current;
wrapper = $('#video-wrapper');
last_push = wrapper.attr("data-time-last-push");
current = Math.round(player.currentTime());
//you could make the 5 here to be a variable or your own interval...
if (current%5 === 0) {
if (current > last_push) {
//do your AJAX post here...
wrapper.attr("data-time-last-push", current);
console.log('currentTime = ' + player.currentTime());
console.log(' duration: ' + player.duration());
}
}
};
Note, I tried to do a jsfiddle to show it working, but ended up running into HTTPS videos because the sample videos don't work through secure connections.

Get audio duration on Chrome for Android

I'm getting the audio/video duration of a file without appending it to the screen. "Using the same code", when I try to get the video duration on both sides it works as expected. But when using audio files it says that the duration is 0 on Android, but it works on a desktop computer.
// Only working on Desktop
var audio = new Audio(url);
// Hide audio player
// player.appendChild(audio);
audio.addEventListener('loadedmetadata', function() {
alert(audio.duration);
});
And the below code is working:
// Working on Desktop and Android
var video = document.createElement('video');
video.src = url;
// Hide video
// player.appendChild(video);
video.addEventListener('loadedmetadata', function() {
alert(video.duration);
});
There is a different approach you can try but, if duration doesn't work with your device (which IMO is a bug) then it's likely this doesn't either; worth a shot though:
audio.seekable.end(audio.seekable.length-1);
or even
audio.buffered.end(audio.buffered.length-1);
though the latter is dependent on content being loaded which in this case probably then won't help.
EDIT: Using the durationchange event is much easier. First the 0 is being output, but as soon as the file is loaded (that's where loadedmetadata fails I guess) the updated and real duration will be output.
audio.addEventListener('durationchange', function(e) {
console.log(e.target.duration); //FIRST 0, THEN REAL DURATION
});
OLD WAY (ABOVE IS MUCH FASTER)
Looks like this "bug" (if this is actually a real bug) is still around. Chrome (40) for Android still outputs 0 as the audio files duration. Researching the web didn't get me a solution but I found out the bug also occurs on iOS. I figured I should post my fix here for you guys.
While audio.duration outputs 0, logging audio outputs the object and you can see that the duration is displayed just right there. All this is happening in the loadedmetadata event.
audio.addEventListener('loadedmetadata', function(e) {
console.log(e.target.duration); //0
});
If you log audio.duration in the timeupdate event though, the real duration is being output. To only output it once you could do something like:
var fix = true;
audio.addEventListener('timeupdate', function(e) {
if(fix === true) {
console.log(e.target.duration); //REAL DURATION
fix = false;
}
console.log(e.target.currentTime); //UPDATED TIME POSITION
});
I'm not sure why all this is happening. But let's be happy it's nothing serious.

In JavaScript, How to make make changes to document elements while complex functions are performed?

I'm processing a large amount of data with JavaScript. I put a circular gif and a progress div to show how much progress has been done in creating the model. However, when it gets to the bulky part of code processing, the loading gif stops spinning and the percentage updating stops working. (until the very end for a split second)
This is the block of code that freezes the gif.
// convert binary normals to ascii
for(i=0;i<norms.length;i++){ //<-- the array length is about 200,000, and could be larger
normals.push(toAscii(norms[i], mins[5], maxes[5])); //nx
normals.push(toAscii(norms[i+1], mins[6], maxes[6]));//ny
normals.push(toAscii(norms[i+2], mins[7], maxes[7]));//nz
i = i+2; //skip next 2 as they're already converted
percentComplete = (normals.length/norms.length)*100;
percentComplete = Math.round(percentComplete);
document.getElementById('loadingScrn').innerHTML = "Processing "
+percentComplete + "%" + " Complete"; //<-- The loading gif is right below this element on the webpage and neither update while this function is running
}
How can I get the browser update the display while JavaScript functions process large data? Is there a way to thread activities so that both updating the Document and JavaScript processing occur simultaneously?
JavaScript runs on the same thread as the browser GUI in most cases (or the tab's GUI, if each tab is given its own process). You will have to break the work into small pieces and schedule the next piece from the currently-executing one using setTimeout().
For example, this might work in your case:
var i = 0;
function doWork() {
do {
// One iteration here...
i++;
} while (i % 100 != 0 && i < norms.length);
// ^^^
// Break work into pieces of 100 elements each; adjust this
// number as needed.
if (i < norms.length) {
setTimeout(doWork, 1);
}
}
setTimeout(doWork, 1);
See this example jsfiddle.
You are correctly observing that JavaScript code runs in the same thread as the document's interface, blocking it when you perform large operations.
Web Workers are a JavaScript feature that is designed to help solve this problem. It allows you to spawn new threads that run along side the document, and communicate results asynchronously as they become available. Unfortunately this is not yet supported in Internet Explorer, but it is planned for IE10, and other browsers already support it.
As suggested by cdhowie and Jonathan M, another solution (inferior, but supported everywhere) is to use setTimeout to pause your code occasionally and let the browser respond to events. You would need to make your code somewhat more complicated to make this work. To give you an idea, to pause every 1000 items you would do something like this:
var workSliceSize = 1000;
var doWorkFromIndex = function(start) {
for (var i = start; i < norms.length; i++) {
if (i - start > workSliceSize) {
setTimeout(0, doWorkFromIndex, i + 1);
break;
}
normals.push... // your code here
}
}
doWorkFromIndex(0);
Try setTimeout(). It processes code asynchronously. I've used it to free up the screen processing by doing:
setTimeout(function() {
// the stuff I want to accomplish while keeping the gif going
},
0
);
Here I've set the timeout time period at zero milliseconds, but it can be whatever you want.
Try
var i = 0;
function processNormals() {
normals.push(toAscii(norms[i], mins[5], maxes[5])); //nx
normals.push(toAscii(norms[i+1], mins[6], maxes[6]));//ny
normals.push(toAscii(norms[i+2], mins[7], maxes[7]));//nz
i = i+3; //skip next 2 as they're already converted
percentComplete = (normals.length/norms.length)*100;
percentComplete = Math.round(percentComplete);
document.getElementById('loadingScrn').innerHTML = "Processing " +percentComplete + "%" + " Complete";
if(i < norms.length) setTimeout(processNormals, 20);
}

Categories