# ID3 metadata

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

Now playing: -

| Frame                         | Value |
| ----------------------------- | ----- |
| No ID3 metadata received yet. |       |

This example plays an HLS audio stream with timed ID3 metadata, such as the title and artist of the song that is playing.

THEOplayer exposes ID3 metadata as a text track of type `id3`. Each cue in this track contains a parsed ID3 frame in its `content` property, for example `{ id: 'TIT2', text: 'Song title' }` or `{ id: 'WXXX', description: 'artwork', url: '...' }`. The example listens for the `entercue` event on this track. It shows artwork from the `WXXX` frame behind the player like a poster, and overlays the artist, title, album and year from the `TPE1`, `TIT2`, `TALB` and `TDRC` frames on top. The box below the player still lists the latest value of every frame.

Code

demo.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>ID3 metadata</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;
        /* This is an audio-only stream, so show the artwork behind the (transparent) video. */
        --theoplayer-background: #000 var(--artwork) center / contain no-repeat;
        --artwork: url('background.svg');
      }

      .song-info[hidden] {
        display: none;
      }

      .song-info p {
        margin: 0;
      }

      .song-artist {
        font-size: 1.25em;
      }

      .song-title {
        font-size: 1.75em;
        font-weight: bold;
      }

      .song-details {
        font-size: 0.9em;
        opacity: 0.8;
      }
    </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/indexcom/index.m3u8","type":"application/x-mpegurl"},"poster":"background.svg"}'
    >
      <!-- Song details, slotted into the title area at the top of the player. -->
      <div class="song-info" slot="title" hidden>
        <p class="song-artist"></p>
        <p class="song-title"></p>
        <p class="song-details"></p>
      </div>
    </theoplayer-default-ui>
    <script src="../utils.js"></script>
    <script>
      // Convert an ID3 frame into a simple { id, description, value } object.
      function parseFrame(frame) {
        switch (frame.id) {
          case 'TXXX': // User defined text
            return { id: frame.id, description: frame.description, value: frame.text };
          case 'WXXX': // User defined URL
            return { id: frame.id, description: frame.description, value: frame.url };
          case 'COMM': // Comments
            return { id: frame.id, description: frame.description, value: frame.text };
          case 'PRIV': // Private data
            return { id: frame.id, description: frame.ownerIdentifier, value: '(private data)' };
          default:
            if ('text' in frame) {
              return { id: frame.id, value: frame.text };
            }
            if ('url' in frame) {
              return { id: frame.id, value: frame.url };
            }
            return { id: frame.id, value: '(binary data)' };
        }
      }

      const ui = document.querySelector('theoplayer-default-ui');
      const songInfo = document.querySelector('.song-info');
      const song = {};

      function updateSongInfo(frame) {
        switch (frame.id) {
          case 'WXXX': {
            // Artwork URL. The artwork of this (old) stream has moved to the theoplayer.com CDN.
            const artwork = frame.url.replace('http://www.indexcom.com/img/rdj/5', 'https://cdn.theoplayer.com/demos/id3/images');
            ui.style.setProperty('--artwork', `url("${artwork}")`);
            return;
          }
          case 'TPE1': // Artist
          case 'TIT2': // Title
          case 'TALB': // Album
          case 'TDRC': // Recording time
            song[frame.id] = frame.text;
            break;
          default:
            return;
        }
        songInfo.querySelector('.song-artist').textContent = song.TPE1 || '';
        songInfo.querySelector('.song-title').textContent = song.TIT2 || '';
        songInfo.querySelector('.song-details').textContent = [song.TALB, song.TDRC].filter(Boolean).join(' \u2022 ');
        songInfo.hidden = false;
      }

      onPlayerReady((player) => {
        player.textTracks.addEventListener('addtrack', (event) => {
          const track = event.track;
          if (track.type !== 'id3') return;
          // Metadata tracks must be enabled to receive cues.
          track.mode = 'hidden';
          track.addEventListener('entercue', (event) => {
            const cue = event.cue;
            const frame = parseFrame(cue.content);
            console.log('ID3 frame', cue.content);
            updateSongInfo(cue.content);
            sendToParent('id3', { frame: { ...frame, startTime: cue.startTime } });
          });
        });
      });
    </script>
  </body>
</html>
```
