James Cridland

HTML5 audio player with VTT captions

I’ve spent much of the evening trying to get an audio player working with VTT captions.

The previous code I used worked on everything (Chrome on desktop, Chrome on mobile, Safari on mobile) but not on Safari on desktop, which was a bit annoying. It turns out that, for whatever reason, Safari desktop sets the track as “disabled” for some reason, and needs specifically telling that it needs to show it. I suspect this is because there’s no space in the audio controls to show the captions.

Anyway, this is the code you can use, including the workaround for Safari.

Example

Here’s a short clip from the BBC’s PM programme, from earlier in 2025. Hit play!

The code

<div style="height:150px;width:100%;text-align:center;">
    <audio style="width:100%;" id="vtt-player" controls src="/uploads/{{ .Get 0 }}.m4a">
        <track default kind="captions" label="captions" srclang="en" src="/uploads/{{ .Get 0 }}.vtt" />
    </audio>
    <div style="display:none;text-align:center;font-family:monospace;font-weight:bold;text-wrap:balance" id="vtt-text"></div>
    <!-- Text-wrap:balance above helps text wrap without one word on the next line, making it easier to read
     https://developer.chrome.com/docs/css-ui/css-text-wrap-balance -->
</div>

<script>
// Safari needs to be specifically told to show this track, for some reason.
document.getElementById('vtt-player').textTracks[0].mode="showing";

document.getElementById('vtt-player').addEventListener('play', function() {
    //We hid the captions in the div above, since Safari shows them on page load
    //Now the listener has hit play, show them.
    document.getElementById('vtt-text').style.display="block";
});

document.getElementById('vtt-player').textTracks[0].addEventListener('cuechange', function() {
    //Update the caption
    document.getElementById('vtt-text').innerText = this.activeCues[0].text;
});
</script>

Notes on using this

Replace {{ .Get 0 }} with the name of your audio file, and the name of the VTT captions file. In my case, they’re always the same.

If you’re using Hugo as a static site generator, then save this as audio-player.html in the shortcodes folder in you site template, and call it as:
{{< audio-player nameoffile >}}

Important: Add preload="none" if you’re using this for podcasts; otherwise you’re kicking off a download that an advertiser might be paying for, even if it’s never actually played.

How to produce the VTT file

You’ll want to install whisper-cpp which is relatively easy on a Mac.

VTT files are also compatible with the podcast:transcription tag; they do support individual speakers, too, though this code above will not do deal with that.

Previously...