Skip to main content
Version: 11.12.1

Frame accurate seeking

Tip: open your browser's developer console and use the player and THEOplayer variables to interact with this demo.

Current frame-
Current time-
Frame rate-
Status-

This example shows how to step through a video frame by frame, or jump directly to a specific frame.

A frame number is converted to a time by dividing it by the frame rate of the video (24 frames per second for this video), and the player seeks to the middle of that frame by setting player.currentTime. The current frame number is calculated the other way around, from player.currentTime.

Code
demo.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Frame accurate seeking</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" />
<style>
html,
body {
margin: 0;
padding: 0;
}

theoplayer-default-ui:not(:defined) {
display: inline-block;
box-sizing: border-box;
}

theoplayer-default-ui {
width: 100%;
aspect-ratio: 16 / 9;
font-family: 'Noto Sans', sans-serif;
background: #000;
}
</style>
<script src="https://cdn.theoplayer.com/dash/theoplayer/THEOplayer.js"></script>
<script nomodule src="https://unpkg.com/@theoplayer/web-ui@2/polyfills"></script>
<script async src="https://unpkg.com/@theoplayer/web-ui@2"></script>
</head>
<body>
<!--
libraryLocation: For demonstration purposes, we use the theoplayer.com CDN.
For production use, we recommend hosting THEOplayer yourself
and changing this (as well as the <script> tags above)
to point to THEOplayer's location on your own website.

licenseUrl: Change this to point to your THEOplayer license file.
Alternatively, replace it with a "license" property whose value is your THEOplayer license itself.
-->
<theoplayer-default-ui
configuration='{"libraryLocation":"https://cdn.theoplayer.com/dash/theoplayer/","licenseUrl":"../../../../theoplayer-license.txt"}'
source='{"sources":{"src":"https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny.m3u8","type":"application/x-mpegurl"},"poster":"https://cdn.theoplayer.com/video/big_buck_bunny/poster.jpg","metadata":{"title":"Big Buck Bunny"}}'
></theoplayer-default-ui>
<script src="../utils.js"></script>
<script>
// The frame rate of the stream. Big Buck Bunny runs at 24 frames per second.
const DEFAULT_FRAME_RATE = 24;

onPlayerReady((player) => {
function getFrameRate() {
const track = Array.from(player.videoTracks).find((track) => track.enabled);
const quality = track && track.activeQuality;
return (quality && quality.frameRate) || DEFAULT_FRAME_RATE;
}

function getCurrentFrame() {
// Add a small margin to avoid rounding errors.
return Math.floor(player.currentTime * getFrameRate() + 0.001);
}

function seekToFrame(frame) {
player.pause();
// Seek to the middle of the frame, so the player shows exactly that frame.
player.currentTime = (Math.max(0, frame) + 0.5) / getFrameRate();
}

function reportState() {
sendToParent('frame', {
frame: getCurrentFrame(),
currentTime: player.currentTime,
frameRate: getFrameRate(),
paused: player.paused,
});
}
player.addEventListener(['timeupdate', 'seeked', 'play', 'pause', 'loadedmetadata'], reportState);

onParentMessage('frame', ({ action, frame }) => {
switch (action) {
case 'previous':
seekToFrame(getCurrentFrame() - 1);
break;
case 'next':
seekToFrame(getCurrentFrame() + 1);
break;
case 'goto':
seekToFrame(frame);
break;
}
});

reportState();
});
</script>
</body>
</html>