Reputation: 11140
When I have a HTML5 video object, I can set its currentTime
property to jump (seek) to a specific time position.
Is there also a way to jump or seek to a specific frame number?
Or related: is there a way for a paused video to step or skip to the next or previous frame?
I mean when it's currently paused or freezes at some arbitrary position, cause the video to jump to the succeeding or preceding frame.
Upvotes: 3
Views: 4700
Reputation: 31
I have 2 solutions for dealing with frames which require pre-scanning the entire video.
Setup:
let video = document.getElementById("my-video")
video is an HTMLMediaElement
Our toolkit:
video.play()
and video.pause()
video.currentTime
(in seconds) is the position of our video and the best way to change it. (There is also ).video.duration
(in seconds) is the total length of the video.video.playbackRate
default 1.0, max 16.0 (at least for me).video.getVideoPlaybackQuality()
gives counts for totalVideoFrames
, droppedVideoFrames
, and corruptedVideoFrames
let vfcId = video.requestVideoFrameCallback((now,metadata)=>{})
and video.cancelVideoFrameCallback(vfcId)
calls the function when a frame is displayed.video.onended = (event) => {}
(essentially an event listener for when the video ends.)Other useless tools:
video.fastSeek()
which is less accurate but faster than currentTime
.video.muted = true
might prevent from being annoyingvideo.ontimeupdate
/ "timeupdate"
event. Useless because activates only every few frames, which isn't good enough.Goal: Create a working goToFrame(n)
function.
Strategy: Find the frame rate and use it to calculate the frame times.
video.getVideoPlaybackQuality().totalVideoFrames
gives us the total number of frames so far played in the video, so we can't skip around in the video and have to watch it whole. Still, totalVideoFrames / video.duration
will give us the frame rate.
let framerate = None;
function getFrameRate() {
video.playbackRate = 16;
video.currentTime = 0;
video.play();
video.onended = (event) => {
framerate = video.getVideoPlaybackQuality().totalVideoFrames / video.currentTime;
video.onended = () => (null); //could be cleaner
};
}
function timeForFrame(n) {
return Math.round(n/framerate *1000)/1000;
}
function goToFrame(n) {
video.currentTime = timeForFrame(n);
}
Notice that I set the playbackRate
to x16 to make the scanning faster. This makes the video player drop frames but, they are still counted by totalVideoFrames
.
Advantages: Relatively fast at x16 playback, and you can make the pre-scanning into a loading-preview thing. For a long video, you can estimate the frame rate from the first few seconds using a Timeout.
Disadvantages: If video frame rate varies over time, this method could be inaccurate.
Strategy: create a list (frame_times
) with the currentTime
for every frame.
VideoFrameCallback
calls back on every displayed frame, with lots of time metadata. metadata.mediaTime
is the currentTime
for the frame.
Note: requestVideoFrameCallback
is not an EventListener you need to ask for it again and again for every next frame.
let frame_times = [];
let vfcId = 0;
function getFrametimes() {
video.playbackRate = 1; // will start dropping frames if higher.
video.currentTime = 0;
video.requestVideoFrameCallback(frameTimePusher);
video.play();
video.onended = {
video.cancelVideoFrameCallback(vfcId);
video.onended = () => (null);
}
}
function frameTimePusher(now,metadata) {
frame_times.push(metadata.mediaTime); //mediaTime is currentTime
video.requestVideoFrameCallback(frameTimePusher); // !! you need to request again.
}
function goToFrame(n) {
video.currentTime = frame_times[n];
}
Notice that the playbackRate
is set to x1. This is because if it is set any higher, the video will start dropping frames and VideoFrameCallback only activates for non-dropped frames.
Advantages: Very accurate (if running at x1 playback rate)
Disadvantages: Slow (if you are running at x1 playback rate)
Solution 1 is arguably better. It also makes determining your current frame easy.
Upvotes: 1
Reputation: 25481
As mentioned in the comments, and AFAIK, there is no standard defined to support frame accurate seeking for the HTML5 video tag itself, other than the mentioned 'seekToNextFrame' which is not widely supported as you can see here: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seekToNextFrame
There are commercial Javascript HTMl5 players which provide this functionality - e.g.:
You'll also find discussion on this topic in the open source Shaka player discussion groups.
The reason it is not straightforward is that it usually requires the player or the browser (or something client side) to first find the nearest 'I' frame and then to decode forwards and/or backwards from that to get to the exact frame you want. It's certainly technically possible, as proven by the above example implementations, but I would guess there has not been the demand in the browser or open source player community to prioritise this functionality.
Upvotes: 2