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.
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.
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.
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.
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 clearInterval(reverse);
–
Lenity 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;
}
aMediaElement.playbackRate = -1;
UAs may not support this, though it is valid to set playbackRate
to a negative value.
I tried request animation frame, calculated the diff and updated currentTime. This does not work, the video tag doesn't repaint fast enough.
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.
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.
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".
© 2022 - 2024 — McMap. All rights reserved.