Following along the lines of Control start position and duration of play in HTML5 video, I am trying to make a video jump from one segment to the next automatically when each has finished playing. Each segment will have the same duration, and the start times for each segment will be in an array.
I can't seem to figure out how to loop through the array after addEventListener.
var video = document.getElementById('player1');
function settheVariables() {
var videoStartTime= ["4","15","26","39"];
for (var i = 0; i < videoStartTime.length; i++) {
if (video.currentTime < videoStartTime[0] ){
video.currentTime = videoStartTime[i];
}
durationTime = 5;
}
//This part works when I plug in numbers for videoStartTime.
video.addEventListener('timeupdate', function() {
if(this.currentTime > (// videoStartTime [current index] + durationTime)){
this.currentTime = (// videoStartTime with next index);
video.play(); }
});
}
you need to change the values in your array to integers, not strings - you're not comparing apples to apples.
the updated and somewhat simplified sample below plays (initially from the start of the video) until the timestamp hits the current marker plus five seconds then jumps to the next marker (and loops back around).
it doesn't cater for the user scrubbing the video themselves (though it will trap as soon as they go >5s past the start of the current section, but going back will confuse things a little) - if you want to control within those 5s boundaries you'll want to do some smarter examination of the time stamp vs the array to make sure you're where you're supposed to be
anyway ... the code:
<script>
var video = document.getElementById('player1');
var videoStartTime= [4,15,26,39];
durationTime = 5;
currentIndex=0;
video.addEventListener('timeupdate', function() {
// console.log(this.currentTime);
if (this.currentTime > (videoStartTime[currentIndex] + durationTime))
{
currentIndex = (currentIndex + 1) % videoStartTime.length // this just loops us back around
this.currentTime = videoStartTime[currentIndex];
// video.play(); // don't need this if the video is already playing
}
});
</script>
Related
i would like to measure the skipped time during video playback if a user skipped some.
Using video.currentTime
First it looks quite trivial
Listen to seeking and get the currentTime A
Listen to seekend and get the currentTime B
B - A = Seeked Time
When i do that in Chrome the result is 0.
Why is that? If you listen to timeupdate TU it gets quite obvious.
Simplified Sequence:
Press Play
TU: 1
TU: 2
TU: 3 // now i use the mouse to seek forward to 19
TU: 19
//chrome & ff fire pause in between, ie not
Seek Start: 19
TU: 19
Seek End: 19
TU: 19
//chrome & ff fire play, ie not
TU: 20
...
I know i can play dirty and save the currentTime somewhere but not in an reliable way ;-)
Using TimeRanges video.played
I can use TimeRanges to calculate the amount of Time which got seeked/skipped. But the Problem is: TimeRanges come in ordered and normalized form as list. So if the User jumps forth an back the Ranges become merged and ordered => not reliable for accurate tracking.
Is there an easier less complicated approch i just dont see?
How I solved it.
I used a RingBuffer (Size 10) from here https://stackoverflow.com/a/28038535/2588818 and push the Math.round(video.currentTime) with every timeupdate to it.
On seekend I go left (prev) in the RingBuffer and take the first datum which differs from the current time.
Works just fine enough to use it for tracking the skipped time.
var createRingBuffer = createRingBuffer || function(length) {
/* https://stackoverflow.com/a/4774081 */
var pointer = 0, buffer = [];
return {
...
push : function(item){
buffer[pointer] = item;
pointer = (pointer + 1) % length;
return item;
},
...
getFirstDifference: function() {
var last_value = buffer[pointer - 1],
prev_value;
for (var i = 1; i <= length; i++) {
prev_value = buffer[(pointer - i) % length]
//check for undefined or initialize the buffer beforehand
if(prev_value === undefined || last_value === prev_value) {
} else {
return prev_value;
}
}
return last_value;
},
print: function() {
console.log(JSON.stringify(buffer));
}
};
};
...
var seekTimes = createRingBuffer(10);
handleUpdateTime = function() {
seekTimes.push(Math.round(video.currentTime));
},
handleSeekEnd = function(e) {
seekTimes.print();
console.log("%s - %s", seekTimes.getFirstDifference(), Math.round(video.currentTime));
},
...
I need to display a "caption" from a video but I cannot use built in captions provided by a vtt file for my situation. I created an array that stores the key,value pair (time, caption). I traverse that array every time the video time is updated and find which caption fits the current time. This solution works and I haven't had any issues (performance or otherwise) but it just feels like brute force to me, I'm hoping someone can help me either refine what I have or guide me toward a more elegant solution. Any comments or criticism is appreciated.
//this function is called whenever video time is updated
this.onUpdateTime = function(currentTime, totalTime) {
this.currentTime = currentTime;
this.totalTime = totalTime;
/*the for statement traverses the array chapters which contains
[{"time": X,"caption": "XYZ"},...]*/
for (var i = 0; i < $scope.chapters.length; i++) {
/* the first if statement checks if (i is not the same as
chapters.length (this was done to prevent an off by one error) and the time of
the video is greater than or equal to time at chapters[i] and the time of the
video is still less than time at the next chapter marker chapter[i+1]*/
if (i != $scope.chapters.length - 1 && currentTime >= $scope.chapters[i].time && currentTime < $scope.chapters[i + 1].time) {
/*set the caption to the value from chapters[i].caption*/
$rootScope.caption = $scope.chapters[i].caption;
/*I have never used $scope.$apply but it seemed to be needed to get the caption to display in my view*/
$scope.$apply(); {
/*break the for loop so that it does not needlessly loop through the rest of the array after finding a match*/
break;
}
/*this if statement if for when i is equal to chapters.length -1, it is the final value in the array so it does
not check against the "next value" like the previous if statement does because there is not next value*/
} else if (currentTime >= $scope.chapters[i].time && i == $scope.chapters.length - 1) {
/*set the caption to the value from chapters[i].caption*/
$rootScope.caption = $scope.chapters[i].caption;
/*I have never used $scope.$apply but it seemed to be needed to get the caption to display in my view*/
$scope.$apply(); {
/*break the for loop so that it does not needlessly loop through the rest of the array after finding a match*/
break;
}
/*if there is not chapter marker at the current time*/
} else {
/*set the caption to a blank value because no match was found therefore there is no caption*/
$rootScope.caption = "";
$scope.$apply();
}
}
// console.log("onUpdateTime function, currentTime is: " + currentTime);
// console.log("onUpdateTime function, totalTime is: " + totalTime);
};
I have built a script which takes a sequence of images and displays them on a canvas element in a animation loop.
This works really well on my desktop, but on IPAD (3 retina) it is very slow. Could you suggest any way to improve the performance?
var videoItts = 0;
function playVideo() {
if(videoItts < 92) {
setTimeout(function() {
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.drawImage(imagesL[videoItts],0,0,1024,636);
requestAnimationFrame(playVideo);
videoItts ++;
}, 1000/22)
}
}
requestAnimationFrame(playVideo);
The imagesL is an array of pre-loaded images.
I would suggest not mixing setTimeout and requestAnimationFrame. You can solve it using only requestAnimationFrame:
var startTime = Date.now();
var fps = 22;
var lastDrawnIndex = null;
var totalFrames = 92;
function drawVideo() {
var currTime = Date.now();
var currFrameIndex = Math.round((currTime - startTime) / (1000/fps)) % totalFrames;
// Since requestAnimationFrame usually fires at 60 fps,
// we only need to draw the image if the frame to draw
// actually has changed from last call to requestAnimationFrame
if (currFrameIndex !== lastDrawnIndex) {
ctx.drawImage(imagesL[videoItts],0,0,1024,636);
lastDrawnIndex = currFrameIndex;
}
requestAnimationFrame(drawVideo);
}
requestAnimationFrame(drawVideo);
The idea is that for every call to requestAnimationFrame we calculate, based on the elapsed time and the desired animation frame rate, which frame index to draw. If it's different from last calculated frame index we draw it. Then we schedule drawVideo to be called next animation frame by calling requestAnimationFrame(drawVideo) at the end.
The code above will loop frames 0-91 continously at 22 fps. I removed the ctx.clearRect call, it is only needed if the frames contains transparency. So you might want to add that back.
I'm looking for a way to manipulate animation without using libraries
and as usual I make a setTimeout in another setTimout in order to smooth the UI
but I want to make a more accurate function to do it, so if I want to make a 50ms-per-piece
animation, and I type:
............
sum=0,
copy=(new Date()).getMilliseconds()
function change(){
var curTime=(new Date()).getMilliseconds(),
diff=(1000+(curTime-copy))%1000 //caculate the time between each setTimeout
console.log("diff time spam: ",diff)
sum+=diff
copy=curTime
var cur=parseInt(p.style.width)
if (sum<47){//ignore small error
//if time sum is less than 47,since we want a 50ms-per animation
// we wait to count the sum to more than the number
console.log("still wating: ",sum)
}
else{
//here the sum is bigger what we want,so make the UI change
console.log("------------runing: ",sum)
sum=0 //reset the sum to caculate the next diff
if(cur < 100)
{
p.style.width=++cur+"px"
}
else{
clearInterval(temp)
}
}
}
var temp=setInterval(change,10)
I don't know the core thought of my code is right,anyone get some ideas about how to make a more accurate timer in most browser?
Set the JsFiddle url:
http://jsfiddle.net/lanston/Vzdau/1/
Looks too complicated to me, use setInterval and one start date, like:
var start = +new Date();
var frame = -1;
var timer = setInterval(checkIfNewFrame, 20);
function checkIfNewFrame () {
var diff = +new Date() - start;
var f = Math.floor(diff / 50);
if (f > frame) {
// use one of these, depending on whether skip or animate lost frames
++frame; // in case you do not skip
frame = f; // in case you do skip
moveAnimation();
}
}
function moveAnimation () {
... do whatever you want, there is new frame, clear timer past last one
}
Does anyone know what event or property I need to query in order to get a percentage figure of the amount an HTML5 video has loaded? I want to draw a CSS styled "loaded" bar that's width represents this figure. Just like You Tube or any other video player.
So just like you tube a video will play even if the whole video hasn't loaded and give the user feedback on how much of the video has loaded and is left to load.
Just like the Red Bar on YouTube:
The progress event is fired when some data has been downloaded, up to three times per second. The browser provides a list of ranges of available media through the buffered property; a thorough guide to this is available on Media buffering, seeking, and time ranges on MDN.
Single load start
If the user doesn't skip through the video, the file will be loaded in one TimeRange and the buffered property will have one range:
------------------------------------------------------
|=============| |
------------------------------------------------------
0 5 21
| \_ this.buffered.end(0)
|
\_ this.buffered.start(0)
To know how big that range is, read it this way:
video.addEventListener('progress', function() {
var loadedPercentage = this.buffered.end(0) / this.duration;
...
// suggestion: don't use this, use what's below
});
Multiple load starts
If the user changes the playhead position while it's loading, a new request may be triggered. This causes the buffered property to be fragmented:
------------------------------------------------------
|===========| |===========| |
------------------------------------------------------
1 5 15 19 21
| | | \_ this.buffered.end(1)
| | \_ this.buffered.start(1)
| \_ this.buffered.end(0)
\_ this.buffered.start(0)
Notice how the number of the buffer changes.
Since it's no longer a contiguous loaded, the "percentage loaded" doesn't make a lot of sense anymore. You want to know what the current TimeRange is and how much of that is loaded. In this example you get where the load bar should start (since it's not 0) and where it should end.
video.addEventListener('progress', function() {
var range = 0;
var bf = this.buffered;
var time = this.currentTime;
while(!(bf.start(range) <= time && time <= bf.end(range))) {
range += 1;
}
var loadStartPercentage = bf.start(range) / this.duration;
var loadEndPercentage = bf.end(range) / this.duration;
var loadPercentage = loadEndPercentage - loadStartPercentage;
...
});
The other awnsers didn't work for me so I started digging into this problem and this is what I came up with. The solutions uses jquery to make an progressbar.
function loaded()
{
var v = document.getElementById('videoID');
var r = v.buffered;
var total = v.duration;
var start = r.start(0);
var end = r.end(0);
$("#progressB").progressbar({value: (end/total)*100});
}
$('#videoID').bind('progress', function()
{
loaded();
}
);
I hope this helps others as well
Percentage fix for loaded string.. Output something like 99% loaded inside #loaded element...
function loaded() {
var v = document.getElementById('videoID');
var r = v.buffered;
var total = v.duration;
var start = r.start(0);
var end = r.end(0);
var newValue = (end/total)*100;
var loader = newValue.toString().split(".");
$('#loaded').html(loader[0]+' loaded...');
$("#progress").progressbar({
value: newValue
});
}
I think best event to update the buffered progress bar is timeupdate. whenever time of the media is updated event is fired.
It gives buffered property which we can use like this
audio.addEventListener('timeupdate', function () {
if (this.duration) {
let range = 0;
let bf = this.buffered;
let time = this.currentTime;
while (!(bf.start(range) <= time && time <= bf.end(range))) {
range += 1;
}
let loadStartPercentage = bf.start(range) / this.duration;
let loadEndPercentage = bf.end(range) / this.duration;
let loadPercentage = (loadEndPercentage - loadStartPercentage) * 100;
//Update your progressbar DOM here
}
});
Best advantage of this event is this is fired when media is played. Whereas progress event is fired when media is downloaded and notified by browser.
So just like youtube, buffered percentage can only be shown when media is played
My answer is better than all of the other ones because you want to update buffer progress when the video is paused. This happens with the progress event. The time update event fires when progress fails, as it sometimes does.
$("#video").on("timeupdate progress", function(){
var video = document.getElementById("video");
var vidDur = video.duration;
for(var i = 0; i <= vidDur; i++){
var totBuffX = video.buffered.end(i);
var perBuff = totBuffX/vidDur*100;
$("#xVidBuffX").css("width", perBuff+"%");
}
});
you only need video.buffered.end(i).