# DVR and sliding windows for live streams

Use the seekable window to rewind a live stream or jump back to the live edge.

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

### Seek controls

−30 s+30 sStart of windowGo live

**Buffered**

**Seekable**

| DVR window        | —–— (—) |
| ----------------- | ------- |
| Behind live       | —       |
| Current time      | 0.0 s   |
| Program date/time | —       |

A live stream can have `duration === Infinity`. The `seekable` ranges show the part of the live stream that is available for seeking. Open Video UI uses a live button to return to the end of the window.

```js
const lastRange = player.seekable.length - 1;
const liveEdge = player.seekable.end(lastRange);
player.currentTime = liveEdge - 30;
```

Code

demo.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>DVR</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 CDN.
      For production use, host the library yourself and update this URL and the script above.

      licenseUrl: Point this to your license file, or replace it with a license property.
    -->
    <theoplayer-default-ui
      configuration='{"libraryLocation":"https://cdn.theoplayer.com/dash/theoplayer/","licenseUrl":"../../../../theoplayer-license.txt"}'
      source='{"sources":{"src":"https://demo.unified-streaming.com/k8s/live/stable/live.isml/.m3u8","type":"application/x-mpegurl"},"metadata":{"title":"Live DVR stream"}}'
    ></theoplayer-default-ui>
    <script src="../utils.js"></script>
    <script>
      onPlayerReady((player) => {
        function ranges(timeRanges) {
          const result = [];
          for (let index = 0; index < timeRanges.length; index += 1) {
            result.push({ start: timeRanges.start(index), end: timeRanges.end(index) });
          }
          return result;
        }

        function currentWindow() {
          const ranges = player.seekable;
          if (ranges.length === 0) return null;
          return { start: ranges.start(0), end: ranges.end(ranges.length - 1) };
        }

        function report() {
          const range = currentWindow();
          const programDateTime = player.currentProgramDateTime;
          sendToParent('dvr', {
            currentTime: player.currentTime,
            duration: player.duration,
            seekableStart: range ? range.start : null,
            seekableEnd: range ? range.end : null,
            seekableLength: range ? range.end - range.start : null,
            behindLive: range ? Math.max(0, range.end - player.currentTime) : null,
            programDateTime: programDateTime instanceof Date && Number.isFinite(programDateTime.getTime()) ? programDateTime.toISOString() : null,
            buffered: ranges(player.buffered),
            seekable: ranges(player.seekable),
          });
        }

        onParentMessage('dvr-seek', (message) => {
          const range = currentWindow();
          if (!range) return;
          if (message.action === 'back') player.currentTime = Math.max(range.start, player.currentTime - 30);
          if (message.action === 'forward') player.currentTime = Math.min(range.end, player.currentTime + 30);
          if (message.action === 'start') player.currentTime = range.start;
          if (message.action === 'live') player.currentTime = player.seekable.end(player.seekable.length - 1);
          report();
        });

        player.addEventListener(['timeupdate', 'progress', 'durationchange', 'seeked'], report);
        let pausedTimer = 0;
        function updatePausedTimer() {
          window.clearInterval(pausedTimer);
          if (player.paused) pausedTimer = window.setInterval(report, 1000);
        }
        player.addEventListener(['play', 'pause'], updatePausedTimer);
        window.addEventListener('pagehide', () => window.clearInterval(pausedTimer));

        report();
        updatePausedTimer();
      });
    </script>
  </body>
</html>
```
