Jake
Jake

Reputation: 4234

<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/

Upvotes: 9

Views: 14390

Answers (9)

Merritt6616
Merritt6616

Reputation: 307

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.

Upvotes: 2

interactivesites
interactivesites

Reputation: 1

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:

  1. A mp4 video with keyframes only was key (increases video size a bit)
  2. 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! :)

Upvotes: 0

HerrHerrmann
HerrHerrmann

Reputation: 18

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.

Upvotes: 0

user1693593
user1693593

Reputation:

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):

premiere

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. :-)

Upvotes: 15

Hank
Hank

Reputation: 1678

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

Upvotes: -2

artygus
artygus

Reputation: 615

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>

Upvotes: 3

Vipul Vaghasiya
Vipul Vaghasiya

Reputation: 479

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/

Upvotes: -2

Thanos
Thanos

Reputation: 3059

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

Upvotes: 0

Pandiyan Cool
Pandiyan Cool

Reputation: 6565

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.

Upvotes: -2

Related Questions