# Fast source switching

Compare how long the player takes to start after changing the source.

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

### Presets

Big Buck BunnyFrame counter 60 fpsAdult Swim

### Custom stream

URLTypeAuto (auto)Load

### Latest TTFF

Waiting for the first measurement.

### Recent switches

| Source                 | TTFF | Since source |
| ---------------------- | ---- | ------------ |
| No completed switches. |      |              |

The initial source stays paused until you start playback, so the time since it was set is left blank. When you load another source, the demo measures time to first frame and time since that source was set. It ignores events from superseded switches.

```js
function loadSource(streamUrl) {
  player.autoplay = true;
  player.source = {
    sources: { src: streamUrl, type: 'application/x-mpegurl' },
  };
}
```

Code

demo.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Fast source switching</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","mutedAutoplay":"all"}'
    ></theoplayer-default-ui>
    <script src="../utils.js"></script>
    <script>
      const initialSource = {
        src: 'https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny_metadata.m3u8',
        label: 'Big Buck Bunny',
        mediaType: 'hls',
        poster: 'https://cdn.theoplayer.com/video/big_buck_bunny/poster.jpg',
      };
      let switchToken = 0;
      let activeListeners = [];

      function sourceConfiguration(source) {
        const contentType = source.mediaType === 'hls' ? 'application/x-mpegurl' : source.mediaType === 'dash' ? 'application/dash+xml' : null;
        return {
          sources: {
            src: source.src,
            ...(contentType ? { type: contentType } : {}),
          },
          ...(source.poster ? { poster: source.poster } : {}),
          metadata: { title: source.label },
        };
      }

      onPlayerReady((player) => {
        let history = [];

        function report(record) {
          history = [record, ...history].slice(0, 10);
          sendToParent('switch-measurements', { latest: record, history });
        }

        function removeActiveListeners() {
          activeListeners.forEach(([eventName, listener]) => player.removeEventListener(eventName, listener));
          activeListeners = [];
        }

        function trackSwitch(source, sourceSetAt) {
          removeActiveListeners();
          const token = ++switchToken;
          const measurement = {
            source: source.label,
            playStartedAt: null,
            complete: false,
          };

          function isCurrent() {
            return token === switchToken;
          }

          function finish(error) {
            if (!isCurrent() || measurement.complete) return;
            measurement.complete = true;
            const now = performance.now();
            report({
              source: measurement.source,
              ttff: measurement.playStartedAt === null || error ? null : now - measurement.playStartedAt,
              sinceSourceChange: sourceSetAt === null || error ? null : now - sourceSetAt,
              error: error || null,
            });
            removeActiveListeners();
          }

          function onPlay() {
            if (!isCurrent() || measurement.complete || measurement.playStartedAt !== null) return;
            measurement.playStartedAt = performance.now();
          }

          function onPlaying() {
            if (!isCurrent() || measurement.complete || measurement.playStartedAt === null) return;
            finish(null);
          }

          function onError(event) {
            const playerError = player.errorObject;
            const message =
              (playerError && playerError.message) || (event && event.message) || (event && event.error && event.error.message) || 'Player error';
            finish(message);
          }

          activeListeners = [
            ['play', onPlay],
            ['playing', onPlaying],
            ['error', onError],
          ];
          activeListeners.forEach(([eventName, listener]) => player.addEventListener(eventName, listener));
        }

        function beginSwitch(source) {
          const sourceSetAt = performance.now();
          trackSwitch(source, sourceSetAt);
          player.source = sourceConfiguration(source);
        }

        onParentMessage('switch-source', (message) => {
          if (typeof message.src !== 'string' || !message.src) return;
          const mediaType = message.mediaType === 'hls' || message.mediaType === 'dash' ? message.mediaType : 'auto';
          player.autoplay = true;
          beginSwitch({
            src: message.src,
            label: typeof message.label === 'string' ? message.label : message.src,
            mediaType,
          });
        });

        trackSwitch(initialSource, null);
        player.source = sourceConfiguration(initialSource);
      });
    </script>
  </body>
</html>
```
