Is it possible to play HTML5 video in reverse?
Asked Answered
A

8

45

Can HTML5 <video> tag be played in reverse, or do I have to download 2 videos (forward and backward play)?

I'm looking for a solution that avoids a user from downloading 2 videos.

Approve answered 11/3, 2011 at 19:13 Comment(1)
I'm strongly leaning towards NO, as most compression schemes assume you're playing Forward.Fecundate
C
35

Without even going into HTML5 or Javascript, many video formats are streaming formats that are designed to be played forward. Playing it backwards would require decoding the whole stream, storing each raw frame on the disk to avoid clobbering memory, then rendering the frames backwards.

At least one person actually tried that using mplayer, though, so it can be done, at least in principle.

Clod answered 11/3, 2011 at 19:20 Comment(5)
Thanks @Frédéric, Now i know having 2 separate video's is better solution to my problem.Approve
But Adding two separate Videos required to load multiple players or changing current player video source. changing current player video source will cost loading time.Hock
@Arham, but buffering the whole input stream locally in order to reverse it on the client is not an option either (yet), I'm afraid.Sorption
@FrédéricHamidi. you can set initially current time equal to video duration and use setInterval to subtract current time on every interval but you would need to fine tune that interval duration and subtraction of current time.Hock
@Arham, that would be murder for forward-streaming formats. Every time you track back to a point that does not exactly match a keyframe, the previous keyframe and all the relative frames between it and your track point would have to be rendered. Even four years later, I doubt most machines have the raw power to do that with acceptable performance. Oh, and you would still have to stream the whole content before you can play it.Sorption
D
15

I managed to do it in an update method. Every frame I decrease video.currentTime to the time elapsed and so far it is the best result I managed to get.

Diageotropism answered 14/6, 2013 at 22:38 Comment(3)
It would be nice to see a jsfiddle of this.Haymaker
Here's a very rough one I got to work, it's a little choppy but you can set the rate by changing 0.1 and 100 (i.e. 0.1 seconds being equal to 100 ms): js var v = document.getElementById('video'); var reverse = setInterval(function() { v.currentTime = v.currentTime - 0.1; }, 100); Lenity
Sorry and to stop it playing in reverse, you run clearInterval(reverse);Lenity
C
12

This snippet just shows, how it could be done, but it takes some time to copy each frame. Which highly depends on the hardware.

It generates a canvas of each frame it has to play. When it's on 100% the playback starts directly and plays backward, forward... and so on. The original Video is also attached after the generation, but won't play automatically due to iframe rules.

It is fine for short videos and as a proof of concept.

Update: I changed the order of the capture part so that the frame at max duration is skipped but the one at 0 is captured (forgot it before).

The frame at max duration caused a white canvas on every video i tried.

I also changed the playback to play it in reverse order as soon as the last frame is reached for an endless playback. So you easily see, that this playback is a bit CPU intensive compared to hardware accelerated videos.

fetch('https://i.imgur.com/BPQF5yy.mp4')
  .then(res => res.blob())
  .then(blob => {
    return new Promise((res) => {
      const fr = new FileReader();
      fr.onload = e => res(fr.result);
      fr.readAsDataURL(blob);
    })
  }).then(async(base64str) => {
    const video = document.createElement("video");
    video.src = base64str;
    video.controls = true;
    video.className = "w-50";

    while (isNaN(video.duration))
      await new Promise((r) => setTimeout(r, 50));

    const FPS = 25;


    const status = document.createElement("div"),
      length = document.createElement("div");
    length.innerText = `Generating ${Math.ceil(video.duration / (1 / FPS))} frames for a ${FPS} FPS playback (Video Duration = ${video.duration})`;
    document.body.appendChild(length);
    document.body.appendChild(status);

    const frames = [],
      copy = () => {
        const c = document.createElement("canvas");
        Object.assign(c, {
          width: video.videoWidth,
          height: video.videoHeight,
          className: "w-50"
        });
        c.getContext("2d").drawImage(video, 0, 0);
        return c;
      };

    // first seek outside of the loop this image won't be copied
    video.currentTime = video.duration; 
    // this frame seems to be always white/transparent
    
    while (video.currentTime) {
      if (video.currentTime - 1 / FPS < 0)
        video.currentTime = 0;
      else
        video.currentTime = video.currentTime - 1 / FPS;
      await new Promise((next) => {
        video.addEventListener('seeked', () => {
          frames.push(copy());
          status.innerText = (frames.length / (Math.ceil(video.duration / (1 / FPS))) * 100).toFixed(2) + '%';
          next();
        }, {
          once: true
        });
      });      
    }


    /*
     * frames now contains all canvas elements created,
     * I just append the first image and replace it on
     * every tick with the next frame.
     * using last.replaceWith(frames[currentPos]) guarantees a smooth playback.
     */

    let i = 0, last = frames[0];

    document.body.insertAdjacentHTML('beforeend', `<div class="w-50">Captured</div><div class="w-50">Video</div>`);

    document.body.appendChild(frames[0]);

    const interval = setInterval(() => {
      if (frames[++i]) {
        last.replaceWith(frames[i]);
        last = frames[i];
      } else {
        frames.reverse();
        i=0;
      }
    }, 1000 / FPS);

    document.body.appendChild(video);
    // won't :(
    video.play();
  });
/* Just for this example */
.w-50 {
  width: 50%;
  display: inline-block;
}

* {
  margin: 0;
  padding: 0;
  font-family: Sans-Serif;
  font-size: 12px;
}
Complement answered 21/11, 2021 at 6:25 Comment(0)
T
10
aMediaElement.playbackRate = -1;

UAs may not support this, though it is valid to set playbackRate to a negative value.

Ternate answered 12/3, 2011 at 6:11 Comment(4)
Worked with MP4 in Safari, but was very clunky (video was not smooth at all).Tutuila
Note that Chrome doesn't have plans to implement negative playbackRate at this time. code.google.com/p/chromium/issues/detail?id=33099Particiaparticipant
Not supported in Firefox at this time, eitherEcumenicity
From my testing it's supported only in Safari. MDN says "Negative values will not cause the media to play in reverse." (developer.mozilla.org/en-US/Apps/Fundamentals/…) however the specification allows negative values (dev.w3.org/html5/spec-preview/…)Lottielotto
F
1

I tried request animation frame, calculated the diff and updated currentTime. This does not work, the video tag doesn't repaint fast enough.

Funnyman answered 3/8, 2022 at 5:57 Comment(0)
P
1

If it is a short video, what I did was just use an online video reverse tool, saved an extra copy of the video reversed. Then merged the two videos and added the "loop" attribute to the tag in html.

Psalmist answered 2/12, 2023 at 3:52 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Bevis
R
0

Get the HTMLMediaElement's duration then set an Interval that would run every second and playing the media by setting the .currentTime and decrease the value every second by 1. The media element must be fully downloaded for this to work. I've only tested this on 30-second clips and unsure if faster (lesser than 1sec.) intervals are possible. Note that this method still plays the media forward. Try increasing media playback rate to make it feel more seamless.

Receptor answered 3/8, 2021 at 10:8 Comment(0)
O
0

Render a single video that is both playing forwards and in reverse.

Modify your code so you play half the video from the first frame. Then play the second half of the video (which will appear to be the video playing in reverse) until the last frame.

Return to the first frame (which should match the last frame) to run again.

This solution really works for shorter videos, but it sure beats the alternatives.

const video = document.getElementById('your-video');

function playFirstHalf() {
    video.currentTime = 0; // Start from the beginning
    const duration = video.duration;
    video.play();
    
    // Pause the video at 50% duration
    setTimeout(() => {
      video.pause();
    }, duration * 0.5 * 1000);
}

  // Function to play video from 50% to the end
  function playSecondHalf() {
    const duration = video.duration;
    // Set the current time to 50% duration
    video.currentTime = duration * 0.5;
    video.play();
  }

THIS is why Stack Overflow is important. ChatGPT wrote the code, but a human needed to solve the "how".

Ophthalmoscopy answered 20/11, 2023 at 20:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.