# Frame accurate seeking

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

Previous frameNext frame

Frame:240Go to frame

| 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

```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>
```
