video 要素はフレームをはじめとした動画のメタ情報を取得するには貧弱な要素です。
<video>: 動画埋め込み要素 – HTML: HyperText Markup Language | MDN
基本的にブラウザで動画を動かす際は如何に再生するかに関心があるため、普段は困らないのですが稀にフレーム単位で色々やりたいという要望があります。そういった時、フレーム単位で処理できないのは困ります。こういった時、よくやるのはあらかじめサーバ側でそういったメタ情報を用意しておき、API 等で動画と一緒にブラウザで使う手法です。これで大体は解決しますが、ユーザが手元の動画ファイルをブラウザ上に置いて再生し、かつコマ送りをしたいなどといった時に困ります。
Firefox 上でこれを解決する方法を以前紹介しました。この手法は Firefox 限定のメソッドを用いる必要があり Google Chrome で同じことはできません。
【Firefox】【JavaScript】video要素の動画のFPSとフレーム数を得る – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
Google Chrome ではまた別のアプローチによって FPS とフレーム数を得られます。
用いるのは requestVideoFrameCallback というメソッドです。これは逆に Chrome 系でのみ使用可能(2021/11/29)なメソッドです。それ故か MDN にリファレンスはなく、次の仕様書が一番頼りになります。
HTMLVideoElement.requestVideoFrameCallback()
requestVideoFrameCallback 動画のフレームが切り替わった際に呼ばれるイベント的な挙動を登録するメソッドです。次の様に使うことでフレームについて推定が可能です。
// ミリ秒間待機する
export const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
/**
* Video 要素の FPS を計測する。Chrome限定の関数を用いる
*/
export const getFpsOnChrome = (video: RVFCVideoElement): Promise<number> => {
let frameCount = 0;
let startTime = 0.0; // 動画の再生を始めた時刻
/** 計測した FPS らを格納する。後に平均なり、中央値なりで均す */
const fps: number[] = [];
/** フレームが変わるたびに呼ばれる。動画再生時間から FPS を計って追加 */
const addFpsRecord = function (now: number) {
fps.push(+(++frameCount / ((now - startTime) / 1000.0)).toFixed(3));
video.requestVideoFrameCallback(addFpsRecord);
};
/* 初期化と最初の計測 */
const initFpsLogger = function (now: number) {
startTime = now;
video.requestVideoFrameCallback(addFpsRecord);
};
// 最初の呼出し
video.requestVideoFrameCallback(initFpsLogger);
// 動画を再生して3秒待ち、3秒の間に計測した fps をまとめて返す
return video.play().then(() => {
return sleep(3000).then(() => {
// なぜか精度の特に悪い戦闘付近を除去して中央値
const ret = fps.slice(30).sort()[Math.floor((fps.length - 30) / 2)];
// 計測が終わったら最初からの位置に戻す
video.pause();
video.currentTime = 0;
return ret;
});
});
};
再生が始まってほぼ線形に動画時間が経過する中で、描画されているコマが変わるたびに FPS を計測、最終的に計測したFPSをまとめることでより精度がよい FPS が得られます。