Seamless HTML5 Video Loop - javascript

I have searched extensively to find a solution to this but have not succeeded.
I have created a 4 second video clip that loops seamlessly in an editor.
However when the clip runs in a page via Safari, Chrome or Firefox there is a small but noticeable pause in the playback from end back to beginning.
I have tried using the loop and preload attributes both together and independently.
I have also tried the following javascript:
loopVid.play();
loopVid.addEventListener("timeupdate", function() {
if (loopVid.currentTime >= 4) {
loopVid.currentTime = 0;
loopVid.play();
}
}
But in all cases the momentary pause remains and spoils the effect.
I'm open to any ideas?

Since this question is a top search result in Google, but doesn't "technically" have an answer yet, I'd like to contribute my solution, which works, but has a drawback. Also, fair warning: my answer uses jQuery.
It seems the slight pause in the video loop is because it takes time for html5 video to seek from one position to another. So it won't help anything to try to tell the video to jump to the beginning when it ends, because the seek will still happen. So here's my idea:
Use javascript to clone the tag, and have the clone sit hidden, paused, and at the beginning. Something like this:
var $video = $("video");
var $clone = $video.clone();
$video.after($clone);
var video = $video[0];
var clone = $clone[0];
clone.hidden = true;
clone.pause();
clone.currentTime = 0;
Yes, I used clone.hidden = true instead of $clone.hide(). Sorry.
Anyway, after this the idea is to detect when the original video ends, and then switch to the clone and play it. That way there is only a change in the DOM as to which video is being shown, but there is actually no seeking that has to happen until after the clone starts playing. So after you hide the original video and play the clone, you seek the original back to the beginning and pause it. And then you add the same functionality to the clone so that it switches to the original video when it's done, etc. Just flip flopping back and forth.
Example:
video.ontimeupdate = function() {
if (video.currentTime >= video.duration - .5) {
clone.play();
video.hidden = true;
clone.hidden = false;
video.pause();
video.currentTime = 0;
}
}
clone.ontimeupdate = function() {
if (clone.currentTime >= clone.duration - .5) {
video.play();
clone.hidden = true;
video.hidden = false;
clone.pause();
clone.currentTime = 0;
}
}
The reason I add the - .5 is because in my experience, currentTime never actually reaches the same value as duration for a video object. It gets pretty close, but never quite there. In my case I can afford to chop half a second off the end, but in your case you might want to tailor that .5 value to be as small as possible while still working.
So here's my entire code that I have on my page:
!function($){
$(document).ready(function() {
$("video").each(function($index) {
var $video = $(this);
var $clone = $video.clone();
$video.after($clone);
var video = $video[0];
var clone = $clone[0];
clone.hidden = true;
clone.pause();
clone.currentTime = 0;
video.ontimeupdate = function() {
if (video.currentTime >= video.duration - .5) {
clone.play();
video.hidden = true;
clone.hidden = false;
video.pause();
video.currentTime = 0;
}
}
clone.ontimeupdate = function() {
if (clone.currentTime >= clone.duration - .5) {
video.play();
clone.hidden = true;
video.hidden = false;
clone.pause();
clone.currentTime = 0;
}
}
});
});
}(jQuery);
I hope this will be useful for somebody. It works really well for my application. The drawback is that .5, so if someone comes up with a better way to detect exactly when the video ends, please comment and I will edit this answer.

I've found that Firefox will stutter while looping if I'm using mp4 or ogv files. I changed the code in my page to try using a webm file first instead, and suddenly the stutter was gone and the video looped seamlessly, or at least close enough that I didn't see an issue. If you haven't given that a shot, it could be worth it to try converting the file to a webm format and loading that instead.

Related

Vimeo video, progress and disable fast forward

I am working on a site used for mandatory instruction. We have to make sure the student watches the video and doesn't fast forward. I would also like to remember the progress the student made in the video in case they need to leave then return later to complete watching.
I have used this JS to remove the ability to fast forward. I'm just not sure how to get the code to remember the progress, then start at that point if the student returns later.
var iframe = document.querySelector("iframe");
var player = new Vimeo.Player(iframe);
var timeWatched = 0;
player.on("timeupdate", function(data) {
if (data.seconds - 1 < timeWatched && data.seconds > timeWatched) {
timeWatched = data.seconds;
}
});
player.on("seeked", function(data) {
if (timeWatched < data.seconds) {
player.setCurrentTime(timeWatched);
}
});
Thanks for any help on this.
you can store the current time in database for future use and then pass to the js whenever user views the video

How can I get an event when an audio file reaches a certain point?

I am experimenting with interactive audio applications in HTML, and I would like to be able to seamlessly start playing one audio file just as another audio file reaches a particular playback location; for example, if audio file A is playing, then I would like to receive an event when it reaches 16 seconds in exactly.
I have tried using audio.play(); setTimeout(myCallback, 16000); for this, but I am finding it to be incredibly unstable in some browsers (Safari especially, particularly when the window is in the background), and will sometimes fire slightly early, and other times fire very late. This remains the case even if I explicitly stop, rewind, and .load() the upcoming audio segment when scheduling the callback.
My current (simple) looper that simply toggles between two players every 16 seconds is below:
audio = []
for (var i = 0; i < 2; i++) {
audio[i] = new Audio("mew" + i + ".mp3");
}
var which = 0;
var pump = function() {
audio[which].play();
which = (which + 1) % audio.length;
window.setTimeout(pump, 16000);
}
audio[which].addEventListener('canplaythrough', function() {
pump();
});
for (i in audio) {
audio[i].load();
}
Also, note that while in this case I could use setInterval(), that will not always be the case (and anyway, setInterval() suffers from the same stability problem).

<video> element with looping does not loop videos seamlessly in Chrome or Firefox

<video width="640" height="360" src="http://jakelauer.com/fireplace.mp4" autoplay loop muted/>
Fiddle here: http://jsfiddle.net/bWqVf/
IE9 does a decent job of it. Is there any recommendation for ways to overcome this? It is very obvious in videos like this one that SHOULD seamlessly loop, but have an annoying skip/pause.
EDIT:
As you can see, if I use javascript to simulate the loop, there's a measurable lag: http://jsfiddle.net/bWqVf/13/
The problems seem to be related to how both Chrome and FF fills the pre-load buffers. In both cases they seem to ignore the loop flag and "reset" the buffers from start meaning in that case that at the end the buffers are emptied and pre-loaded again when video starts causing a slight delay/jump.
IE seem to consider the loop flag and continue to fill also towards the end.
This means it's gonna be very hard to make this look seamless. I tried several techniques over a few hours including pre-caching the first frames to 15 frames off-screen canvases. The closest I could get to seamless was modifying the video to have two segments in it (I do not (no longer) have capable hardware so I needed to reduce the dimension as well to test - see fiddle).
However, there are drawbacks here as well:
The video is double length
You need to play two instances at the same time
Two downloads of the same video happens
Lag compensation will vary from computer to computer
Browser updates in the future can influence good/bad how the result will end up to be.
In other words - there is no stable solution to get around the problem with these browsers.
I would recommend an extension to what I mention above, to pre-loop some segments. This way you can reduce the glitch.
However, to share what I did here goes.
First I extended the video with an extra segment (and reduced the dimension to run it on my computer):
Then I used the following code to do an overlapping loop. That is:
I start the videos at the same time, but one video from the middle.
The video that is currently => middle is shown
I use a canvas element to draw the video onto
When at end the current video is switched so that the new video is still the one being played from the middle
The theory here is that this will mask the glitch you get at the start as the video playing is always in the middle (starting on the second segment).
The code looks like this:
As the videos are loaded async we need to count the loads as this technique uses two video instances and the browser seem to be unable to share the download.
We also set a new position for video 1 to be at the middle. An event is raised for this when video is moved and ready, so we start everything from that point:
v1.addEventListener('canplay', init, false);
v2.addEventListener('canplay', init, false);
v1.addEventListener('timeupdate', go, false);
Handlers:
function init() {
count--; /// = 2
/// both videos are loaded, prep:
if (count === 0) {
length = v1.duration;
mid = length * 0.5;
current = mid;
/// set first video's start to middle
v1.currentTime = mid + lag;
}
}
function go() {
/// remove listener or this will be called for each "frame"
v1.removeEventListener('timeupdate', go, false);
v1.play();
v2.play();
draw();
}
The lag value is an attempt to compensate for the difference between the two videos starting as they don't start at the exact same time.
The main code draw simply switches between the videos depending on the position of the main video (v1) - the frame rate is also reduce to 30 fps to reduce overhead of drawImage as requestAnimationFrame runs optimally at 60 fps (the video here is 30 fps so we only need to draw a frame every other time):
function draw() {
/// reduce frame-rate from 60 to 30
if (reduce === true) {
reduce = false;
requestAnimationFrame(draw);
return;
} else {
reduce = true;
}
/// use video that is >= middle time
var v = v1.currentTime >= mid ? v1 : v2;
/// draw video frame onto canvas
ctx.drawImage(v, 0, 0);
requestAnimationFrame(draw);
}
Now, using canvas opens up other possibilities as well such as making for example a cross-fade between the two videos to smooth the transition further. I didn't implement this as it is outside the scope (in size/broadness), but worth to mention as that could be a solution in itself.
In any case - as mentioned, this is a solution with many drawbacks but it is the closest I could get to reduce the glitch (using Chrome).
The only solution that can work properly is an internal browser driven one as you would need access to the buffers to be able to do this fully seamlessly.
My "solution" is in essence saying: forget it! It won't work in these browsers, use an repeated looped video instead. :-)
I think the problem is related to browser-specific-video-handling.
As a quirk, you can achieve less latency converting the video to webm, but you should place it before mp4 source, ie:
<video width="640" height="360" autoplay loop muted>
<source src="http://jakelauer.com/fireplace.webm" type="video/webm" />
<source src="http://jakelauer.com/fireplace.mp4" type="video/mp4" />
</video>
Heureka!
We've found the actual, real, work-around-free solution to this problem over at where I work. It explains the inconsistent behavior through multiple developers as well.
The tl;dr version is: Bitrates. Who would've guessed? What I suppose is that many people use standard values for this that usually are around 10 Mbit/s for HD videos if you use the Adobe Media Encoder. This is not sufficient. The correct value would be 18 Mbit/s or maybe even higher. 16 is still a bit janky. I cannot express how well this works. I've, by now, tried the messiest workarounds for about five hours until I found this together with our video editor.
I hope this helps everyone and saves you tons of time!
I also hope it's okay that I posted this in another thread as well, but there are a bunch of questions of the same type about this and I wanted to reach a lot of people.
I don't think your problem is "code-related". It has more to do with the actual video itself. It would be much better if you edit your video for a seamless looping.
Have a look HERE as it will give you some guidance on how to do so.
Hope this helps you.
EDIT: You can try breaking the video up into two sections: the intro and the looping part. Make a <video> element for each one and position them in the same place, with the second video hidden. Set an "ended" event on the intro to swap out the display and start the second video. Then, you can set the loop attribute on the second video element.
You shouldn't have a problem getting the two videos to play seamlessly together as long as you have the preload attribute on at least the looping video.
If that doesn't work, try making two video elements with the same looping video. While one is playing, you can hide the other and set its currentTime back to zero, so any seeking delay will happen when nobody is looking.
If none of the above works for you, then you can try an other way with javascript. Note that i haven't tested the below code. What it does is starting the video from the 2nd second and when the video reaches the 4th second it will start it again (from the 2nd second).
function playVideo() {
var starttime = 2; // start at 2 seconds
var endtime = 4; // stop at 4 seconds
var video = document.getElementById('player1');
//handler should be bound first
video.addEventListener("timeupdate", function() {
if (this.currentTime >= endtime) {
this.play();
}
}, false);
//suppose that video src has been already set properly
video.load();
video.play(); //must call this otherwise can't seek on some browsers, e.g. Firefox 4
try {
video.currentTime = starttime;
} catch (ex) {
//handle exceptions here
}
}
The solution that worked for me (and doesn't require a huge amount of JavaScript) is something like:
var video = document.getElementById('background-video');
var loopPoint = 15; // s
function resetVideo() {
if (video.currentTime >= loopPoint) {
video.currentTime = 0;
}
}
video.addEventListener('timeupdate', resetVideo);
Unfortunately I guess this is quite expensive because it will use a callback every time the time of the video/audio updates.
This issue happens to me using the Chromium wrapper with Electron. Regardless of that, I got closer to solving the issue ( not close enough ). Here's a list of things that improved the looping to near seamless jumping back from cuepoint A to B:
A mp4 video with keyframes only was key (increases video size a bit)
Get a framerate-sensitive loop. This little tool helps a lot when using keyframes and timecodes: http://x3technologygroup.github.io/VideoFrameDocs/#!/documentation/FrameRates
( 3. The last thing is only needed if things in 1 & 2 do not help. I've loaded the whole video with an XmlHTTPrequest to fill the buffer completely. )
var xhr = new XMLHttpRequest();
xhr.open('GET', '../assets/video/Comp1.mp4', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 0) { // I used chromium and electron, usually status == 200 !
var myBlob = this.response;
var vid = URL.createObjectURL(myBlob);
// myBlob is now the blob that the object URL pointed to.
var v = document.getElementById("video");
v.src = vid;
// not needed if autoplay is set for the video element
v.play();
// This requires the VideoFrame-tool (see Nr. 2.)
var videoFrame = new VideoFrame({
id: 'v',
frameRate: 25, // ! must match your video frame rate
callback: function(response) {
// I jump from fram 146 to 72
if (videoFrame.get() === 146) {
// now, jump! Dealbreaker is that the seek is stopping the video
// and the few ms to play it again bugger up the experience.
// Any improvements welcome!
videoFrame.seekBackward(71, function() {
v.play();
});
}
}
});
videoFrame.listen('frame', 25);
v1.play();
}
}
xhr.send(null);
The only issue I encounter with this code is that the seeking stops the video and play() needs to be triggered again. This causes a glitch which I solved by going 3 frames back before the actual cuepoint I want to jump to.
This is still inaccurate if used on different hardware with different videos, but maybe it gets you closer to a solution -- an me too! :)
The problem is nothing.
The starting slide and ending slide is different. If both the slides are same, the looping will looks fine. Because of mismatch in these slides only, it looks like pausing at some seconds. Avoid those things and try out.
check below jsFiddle URL carefully i add console.log and trace video tag event like play, pause, ended etc, i check in window chrome version 28 (working loop for me without fire pause event )
http://jsfiddle.net/bWqVf/6/
Ok... after much trial and error, this is what finally worked for me. It seemed to me that the video is not updating after it's ended, so I just remind it all of its properties again when it finishes playing.
myVid.setAttribute('src', "videos/clip1.mp4");
myVid.autoplay = true;
myVid.addEventListener('ended', vidEnded);
function vidEnded()
{
myVid.setAttribute('src', "videos/clip1.mp4");
myVid.autoplay = true;
}

setInterval, animating multiple images, stopping setInterval

I need to create some animation using DHTML/Javascript and I am struggling to get anything I do to work. My parameters are it must use the setInterval function, a user defined function and 8 jpg files. There must also be a way of stopping the animation.
If someone could get me pointed in the right direction I would be very happy. I have not been able to find suitable information on how to do this so far and I am still fairly new to Javascript. Thanks.
Sorry for not posting code earlier. It was such a mess I didn't want to embarrass myself. Here's what I have. It's not working.
var slideShow = ['images/pic0.jpg','images/pic1.jpg','images/pic2.jpg','images/pic3.jpg','images/pic4.jpg','images/pic5.jpg','images/pic6.jpg','images/pic7.jpg'];
picO = new Array();
for(i=0; i < slideShow.length; i++) {
picO[i] = new Image();
picO[i].src = slideShow[i];
}
var curPic = -1;
function changeImage(){
curPic = (++curPic > slideShow.length-1)? 0 : curPic;
imgO.src = picO[curPic].src;
setInterval(changeImage,100);
}
window.onload=function(){
imgO = document.getElementById("imgAnim");
changeImage();
}
var t=setTimeout(function(){alert("Welcome to my animated page")},3000)
The other thing it needs to do is pop up an alert box 3 seconds after the animation starts. It's doing that but it's popping up EVERY 3 seconds, which is not what I want.
Thanks for your help so far. I've done pretty well with my Javascript work lately but this one is just something I'm not that familiar with.
To the using of setInterval and stopping the animation
var animation = setInterval(yourAnimation,500); // run yourAnimation every 500ms
clearInterval(animation); // stop animation
To the animation
var slideShow = ["img1.jpg","img2.jpg", ... ,"img8.jpg"];
var counter = 0;
function yourAnimation() {someImage.src = slideShow[++counter%slideShow.length];}

Strange behaviour in Javascript; not showing div while calculating

I'm doing some pretty processor heavy processing on a few audio files. Before I go in to this function called startProcessing I want to show a div which overlays the entire page and says calculating...Problem is the div is only shown after the function has terminated. When I click the button which activates tis code the button freezes and only when the process function has terminated does it unfreeze and show the loader. Anyone seen similar behaviour and was able to solve it?
document.getElementById('loader').innerHTML = "<p>Calculating...</p><p>Please Wait</p><img src='../img/loader.gif' />";
document.getElementById('loader').style.visibility = "visible";
document.getElementById('notification').style.visibility = "visible";
var speed = document.getElementById("playbackSpeed").value/100;
console.log("Changing speed");
if (speed > 1.5){
startProcessing(1.5);
backend.playbackSpeed = 1.5;
} else if (speed < 0.75){
startProcessing(0.75);
backend.playbackSpeed = 0.75;
} else {
startProcessing(speed);
backend.playbackSpeed = speed;
}
You could throw the heavy processing into a Web Worker. That would free up your UI.
Note: Its not IE friendly... only IE10 (I think)
Try to run heavy calculations with some delay:
setTimeout(function(){
var speed = document.getElementById("playbackSpeed").value/100;
console.log("Changing speed");
speed = Math.min(Math.max(speed, 0.75), 1.5);
startProcessing(speed);
backend.playbackSpeed = speed;
}, 13);
One approach could be to use setTimeout or setInterval for your div showing the progress bar. Another way to get around it is doing some (pseudo) multithreading.

Categories

Resources