Setting currentTime for HTML5 video window.onscroll is lagging
Asked Answered
A

4

14

I'm trying to set the currentTime for the html5 video on window scroll event. Basically the idea is to move forward or backward in the video timeline as you scroll the page.

This example here is doing it nicely without a problem: http://codepen.io/ollieRogers/pen/lfeLc

Here is the code:

// select video element
var vid = document.getElementById('v0');
//var vid = $('#v0')[0]; // jquery option

// pause video on load
vid.pause();

// alternative & optimized implementation  thanks to http://codepen.io/daveroma/
window.onscroll = function(){
  vid.currentTime = window.pageYOffset/400;
};
#set-height  
  display block
  height 13500px
#v0
  position fixed 
  top 0  
  left 0  
  width 100%

p font-family helvetica 
  font-size 24px
<div id="set-height"></div>
<p id="time"></p>
<video id="v0" tabindex="0" autobuffer="autobuffer" preload="preload">
  <source type="video/webm; codecs=&quot;vp8, vorbis&quot;" src="http://www.html5rocks.com/tutorials/video/basics/Chrome_ImF.webm"></source>
  <source type="video/ogg; codecs=&quot;theora, vorbis&quot;" src="http://www.html5rocks.com/tutorials/video/basics/Chrome_ImF.ogv"></source>
  <source type="video/mp4; codecs=&quot;avc1.42E01E, mp4a.40.2&quot;" src="http://www.html5rocks.com/tutorials/video/basics/Chrome_ImF.mp4"></source>
  <p>Sorry, your browser does not support the &lt;video&gt; element.</p>
</video>

But when I try it with my own video, the video lags: http://codepen.io/futurecrazy/pen/ZWGYBj

Here is my code:

// select video element
var vid = document.getElementById('v0');
//var vid = $('#v0')[0]; // jquery option

// pause video on load
vid.pause();

// alternative & optimized implementation  thanks to http://codepen.io/daveroma/
window.onscroll = function(){
  vid.currentTime = window.pageYOffset/400;
};
#set-height  
  display block
  height 13500px
#v0
  position fixed 
  top 0  
  left 0  
  width 100%

p font-family helvetica 
  font-size 24px
<div id="set-height"></div>
<p id="time"></p>
<video id="v0" tabindex="0" autobuffer="autobuffer" preload="preload">
  <source type="video/webm" src="http://philippsokolov.com/fm-4.webm"></source>
  <source type="video/ogg" src="http://philippsokolov.com/fm-4.ogv"></source>
  <source type="video/mp4" src="http://philippsokolov.com/fm-4.m4v"></source>
  <p>Sorry, your browser does not support the &lt;video&gt; element.</p>
</video>

I've tried different video compressions but still cant fix the issue.

Would appreciate any help.

Aviva answered 2/3, 2016 at 7:44 Comment(4)
I forgot to add that it seems to work fine on Safari, and the issue only happens on other browsers (Chrome, Firefox)Aviva
Have you found a solution to this? @philippCarrol
Hi @QuinnKeaveney, unfortunately notAviva
I found that this problem was happening to me because the video quality was too high so I decreased the video quality and I also made the video slower and decreased the /400 in the javascript at the askers code which for me was in a variable called playbackConstGil
M
17

I ran into a similar problem, the issue was the video encoding.
Having a low video keyframe rate causes the lag.

My guess is that changing video.currentTime makes the browser's video decoder search for the closest keyframe to the specified time position, and this can take a while on videos with rare keyframes. Reencoding the video with higher keyframe rate fixed the problem for me.

Note that keyframe spacing can be controled with FFMPEGs -g flag.

Configuring Video Streams for Seeking Performance

Macguiness answered 14/7, 2017 at 20:12 Comment(1)
This should be marked as the correct answer. Had the same issue, and re-encoding the video with higher keyframe rate (e.g. frequency of key frames per frame) solved the problem. You cannot just insert/modify frames, the video needs to be re-encoded.Bossy
S
9

I experienced the exact same thing — what a pain to solve! After poking around, I noticed that the video would only visibly change once the seeked event had fired. Evidently, every time you update currentTime (in my case, on any scroll event), it kicks off a new seeking process, which delays the rendering of the new currentTime until a seeking process is resolved.

I fixed it by listening for the seeking and seeked events, and only updating currentTime if the video is not currently seeking — let the seeking process finish, so the updates don't just keep piling up.

Here's the relevant bits of my script:

let seeking = false;

heroVideo.addEventListener('seeking', function() {
    seeking = true;
});

heroVideo.addEventListener('seeked', function() {
    seeking = false;
});

function tick(lastKnownScrollPosition) {
    if (!seeking) heroVideo.currentTime = lastKnownScrollPosition / 300;
}

let ticking = false;

window.addEventListener('scroll', function(e) {
  const lastKnownScrollPosition = window.scrollY;

  if (!ticking) {
    window.requestAnimationFrame(function() {
      tick(lastKnownScrollPosition);
      ticking = false;
    });

    ticking = true;
  }
});
Smocking answered 20/3, 2022 at 5:29 Comment(1)
Thank you! I'm still getting a slight stutter but a least it works good enough. I was getting this exact problem in Firefox only. It would only update the video when the scroll had ended. I see now why apple use image sequences rather than a video source on all its product pages.Whitleather
F
3

I was doing the same thing, but was unable to achieve a usable level of performance using HTML5 <video>. There are unfortunately no supported codecs that do intraframe compression, so seeking is just unfortunately going to be expensive if you're using HTML5 video.

Instead I extracted the frames from the video with ffmpeg and tar'd them. Then, on the browser side, I fetched the tar, used js-untar to extract the individual JPGs. From there it's straightforward to manually change the image source on an tag, or use a canvas, to animate the video.

Here's what that looks like:

  let images = [];

   fetch("assets/launch.tar").then(response => {
       return response.arrayBuffer();
   })
       .then(buffer => untar(buffer))
       .then(files => {
           files.forEach((file) => {
               let img = new Image();
               img.src = file.getBlobUrl();
               images.push(img);
           });
       });

   let div = document.getElementById("yourDiv");


   let frame = () => {
       let scrolled = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
       let currentFrame = Math.floor(scrolled * (images.length - 1));
       let img = images[currentFrame];
       img.decode().then(() => {
           div.style.backgroundImage = `url(${img.src})`;
       });
       window.requestAnimationFrame(frame);
   }
   window.requestAnimationFrame(frame);
Foreknow answered 11/9, 2022 at 21:25 Comment(0)
R
1

Instead of adding event listeners for 'seeking' and 'seeked' as in Mick's answer, you can use video element's boolean seeking property like this:

let ticking = false;

window.addEventListener('scroll', function(e) {
  if (!ticking) {
    window.requestAnimationFrame(function() {
      if (!videoElement.seeking) videoElement.currentTime = window.scrollY / 300;
      ticking = false;
    });

    ticking = true;
  }
});

More info about the flag can be found here.

Reconvert answered 16/1 at 19:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.