Edit: I was able to figure this out, scroll down to the top answer to see it. AFAIK no other post on Stack (or anywhere online) with this question had a working and consistent answer.
Anyway...
I’m building a retiming tool for videos on YouTube and other websites, and finding the frame rate of the video element is necessary for frame-by-frame seeking and rounding reasons.
The way I’m doing it so far is by using requestVideoFrameCallback()
and finding the smallest difference between two frame times: (sorry for bad formatting, I’m on mobile)
var v = document.querySelector(“video”)
var c = 0; // time of last frame
var framelength = 1; // length of one frame
var fps;
function check(t, m) {
var diff = Math.abs(m.mediaTime - c); // difference between this frame and the last
if (diff && diff < framelength) {
framelength = diff;
fps = Math.round( 1 / framelength)
}
c = m.mediaTime;
v.requestVideoFrameCallback(check);
}
v.requestVideoFrameCallback(check);
However, there are sometimes rounding issues due to the mediaTime
having only 3 decimal places, so is there a better way to find the FPS with JavaScript?
Edit: I was able to figure this out, scroll down to the top answer to see it. AFAIK no other post on Stack (or anywhere online) with this question had a working and consistent answer.
Anyway...
I’m building a retiming tool for videos on YouTube and other websites, and finding the frame rate of the video element is necessary for frame-by-frame seeking and rounding reasons.
The way I’m doing it so far is by using requestVideoFrameCallback()
and finding the smallest difference between two frame times: (sorry for bad formatting, I’m on mobile)
var v = document.querySelector(“video”)
var c = 0; // time of last frame
var framelength = 1; // length of one frame
var fps;
function check(t, m) {
var diff = Math.abs(m.mediaTime - c); // difference between this frame and the last
if (diff && diff < framelength) {
framelength = diff;
fps = Math.round( 1 / framelength)
}
c = m.mediaTime;
v.requestVideoFrameCallback(check);
}
v.requestVideoFrameCallback(check);
However, there are sometimes rounding issues due to the mediaTime
having only 3 decimal places, so is there a better way to find the FPS with JavaScript?
-
"How do I get the frame rate of an HTML video with JavaScript?" You cannot. Also you're talking about content that exists on external servers so CORS could be an issue. The only way is to check the video file's bytes, so you need a direct link to the MP4 (and PHP to avoid CORS when reading the file bytes back into JS) then check these bytes for the FPS (depends on format like MP4 or WebM or OGV). PS: Youtube mentions frame rate in their video's page (not in embed-version), so load the page source with PHP then look for first occurrence of
"fps":
and then extract next following 2 digits. – VC.One Commented Jul 16, 2022 at 21:14 - Well, actually I was able to tweak my code and get a consistent FPS that pretty much matched the video every time, sooo. YAY – derder56 Commented Jul 17, 2022 at 0:50
- Does this answer your question? How to determine the intended frame-rate on an html video element – Ethan Commented Jul 24, 2022 at 0:12
- Nope, I already figured it out, check the answers for this question @Ethan – derder56 Commented Jul 24, 2022 at 0:14
- I've deleted my ment. The MediaTrackSettings method always returns 30 fps. I'm going to instead go with using mediainfo.js (github./buzz/mediainfo.js) which can actually parse the video file itself for information. – Samuel Commented Jan 18, 2023 at 1:07
1 Answer
Reset to default 7Using a mix of some hacky requestVideoFrameCallback
stuff, as well as making an array and rounding it, I was able to get a consistent and accurate FPS for any video. I wish I had been able to found this online when I needed it, but here it is for anybody else who had the same question as me:
Step 1: Set variables
vid
is the HTML video elementlast_media_time
andlast_frame_run
will be used later to determine differences between frames and get the FPSfps
is obviousfps_rounder
is for rounding and stopping stuttering video frames from messing up the FPS, it actually contains the differences between frames, we make it have like 50 items to be sure, more about this in step 2frame_not_seeked
is to stop users from moving around the video and messing up FPS, more about this in step 3
var vid = document.querySelector("video");
var last_media_time, last_frame_num, fps;
var fps_rounder = [];
var frame_not_seeked = true;
Step 2: Set a ticker function with rVFC
We use .mediaTime
and .presentedFrames
to get a good diff
, which would be either 0.016 or 0.017, assuming the video is 60fps. Then we shove it all in fps_rounder
so we can actually have it even out to 0.016666ish so we can be sure it's 60fps and not like 59 or 61. I would remend not trying to reference the FPS in the code unless fps_rounder
has at least 50 items in it, just so you can be sure that stuttering won't mess it up.
function ticker(useless, metadata) {
var media_time_diff = Math.abs(metadata.mediaTime - last_media_time);
var frame_num_diff = Math.abs(metadata.presentedFrames - last_frame_num);
var diff = media_time_diff / frame_num_diff;
if (diff && diff < 1 && frame_not_seeked && fps_rounder.length < 50 && vid.playbackRate === 1 && document.hasFocus()) {
fps_rounder.push(diff);
fps = Math.round(1 / get_fps_average());
}
frame_not_seeked = true;
last_media_time = metadata.mediaTime;
last_frame_num = metadata.presentedFrames;
vid.requestVideoFrameCallback(ticker);
}
vid.requestVideoFrameCallback(ticker);
Step 3: Stop seeking from messing it up
As you might have seen in the above code, it references frame_not_seeked
. This is because when a user clicks and changes the timestamp of a video, it can sometimes mess things up since .mediaTime
is changing a lot but .presentedFrames
is only changing by 1. So when the video is seeked, we remove the last item from fps_rounder
, and stop the next diff
from being added to fps_rounder
by setting frame_not_seeked
to false, just to be safe.
vid.addEventListener("seeked", function () {
fps_rounder.pop();
frame_not_seeked = false;
});
Step 4: Make an averaging function
This is mentioned in step 2, so obviously we need it to exist. It's just a simple function that averages an array using .reduce()
, in this case getting the average of fps_rounder
and therefore an accurate FPS.
function get_fps_average() {
return fps_rounder.reduce((a, b) => a + b) / fps_rounder.length;
}
Putting it all together
// Part 1:
var vid = document.querySelector("video");
var last_media_time, last_frame_num, fps;
var fps_rounder = [];
var frame_not_seeked = true;
// Part 2 (with some modifications):
function ticker(useless, metadata) {
var media_time_diff = Math.abs(metadata.mediaTime - last_media_time);
var frame_num_diff = Math.abs(metadata.presentedFrames - last_frame_num);
var diff = media_time_diff / frame_num_diff;
if (
diff &&
diff < 1 &&
frame_not_seeked &&
fps_rounder.length < 50 &&
vid.playbackRate === 1 &&
document.hasFocus()
) {
fps_rounder.push(diff);
fps = Math.round(1 / get_fps_average());
document.querySelector("p").textContent = "FPS: " + fps + ", certainty: " + fps_rounder.length * 2 + "%";
}
frame_not_seeked = true;
last_media_time = metadata.mediaTime;
last_frame_num = metadata.presentedFrames;
vid.requestVideoFrameCallback(ticker);
}
vid.requestVideoFrameCallback(ticker);
// Part 3:
vid.addEventListener("seeked", function () {
fps_rounder.pop();
frame_not_seeked = false;
});
// Part 4:
function get_fps_average() {
return fps_rounder.reduce((a, b) => a + b) / fps_rounder.length;
}
<p>The FPS will appear here!</p>
<video id="myVideo" width="320" height="176" controls>
<source src="https://www.w3schools./tags/mov_bbb.mp4" type="video/mp4">
</video>
Have fun!