Ish
Ish

Reputation: 29566

Is it possible to play HTML5 video in reverse?

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.

Upvotes: 47

Views: 60927

Answers (8)

Fr&#233;d&#233;ric Hamidi
Fr&#233;d&#233;ric Hamidi

Reputation: 262979

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.

Upvotes: 36

Strahinja
Strahinja

Reputation: 11

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.

Upvotes: 1

RCNeil
RCNeil

Reputation: 8759

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".

Upvotes: -1

Mikeec3
Mikeec3

Reputation: 649

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

Upvotes: 1

Christopher
Christopher

Reputation: 3667

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

Upvotes: 14

KHAYRI R.R. WOULFE
KHAYRI R.R. WOULFE

Reputation: 321

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.

Upvotes: 0

BaptisteB
BaptisteB

Reputation: 1268

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.

Upvotes: 16

Eli Grey
Eli Grey

Reputation: 35895

aMediaElement.playbackRate = -1;

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

Upvotes: 10

Related Questions