Skip to main content
Version: 11.12.1

ID3 metadata

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

Now playing: -

FrameValue
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
<!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>