Skip to content

GuidePlayback

Show captions and subtitles

Show captions and subtitles, and let users turn them on and pick a language.

Add WebVTT tracks as <track> children of the media component, and give users a <media-captions-button> to toggle them.

The <track> loads /docs/demos/text-tracks/captions.vtt, shown in the captions.vtt tab. Host a WebVTT file for your video and point src at its URL.

Captions Off Captions On
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop>
      <track kind="captions" src="/docs/demos/text-tracks/captions.vtt" srclang="en" label="English" default />
    </video>
    <media-captions-button class="media-captions-button">
      <span class="active">Captions Off</span>
      <span class="inactive">Captions On</span>
    </media-captions-button>
  </media-container>
</video-player>

How it works

The text track feature mirrors the media component’s textTracks into player state:

  • textTrackList holds every track with its id, kind, label, language, and mode.
  • subtitlesShowing is true when any caption or subtitle track is showing.
  • toggleSubtitles(forceShow?) hides captions if a caption or subtitle track is showing. Otherwise it shows one track, chosen in this order: the last track shown, a track matching the browser language, then the first available track. Pass true or false to force captions on or off.
  • selectSubtitlesTrack(id) shows one track and disables the rest; pass null to disable all.

Tracks don’t have to come from <track> elements: tracks that streaming media exposes (for example in-manifest HLS captions) appear in textTrackList the same way.

The browser renders the cues. Style them with the ::cue pseudo-element.

Availability and constraints

  • Captions availability is 'unavailable' until the media has at least one caption or subtitle track; caption controls render nothing in that case.
  • Cues load asynchronously. A track appears in textTrackList before its cues are parsed, so cue-driven UI fills in when the track finishes loading.
  • Cross-origin track files require CORS: serve the VTT with CORS headers and set crossorigin on the media component.
  • Track selection UIs identify tracks by label and language; give every track both.
  • default on a <track> marks it as the preferred initial track. The browser’s caption preferences can select another track.

Common variations

Language selection menu

For multiple languages, render a menu instead of a toggle.

Replace the Captions button in the primary example with a menu containing <media-captions-radio-group>. Keep the player, container, and track setup, and add these imports:

<button type="button" commandfor="captions-menu">Captions</button>
<media-menu id="captions-menu" side="top" align="end">
  <media-menu-content>
    <media-captions-radio-group label="Captions">
      <template>
        <media-menu-radio-item>
          <span data-part="label"></span>
          <media-menu-item-indicator>✓</media-menu-item-indicator>
        </media-menu-radio-item>
      </template>
    </media-captions-radio-group>
  </media-menu-content>
</media-menu>
<script type="module">
  import '@videojs/html/ui/menu';
  import '@videojs/html/ui/menu-content';
  import '@videojs/html/ui/menu-radio-item';
  import '@videojs/html/ui/menu-item-indicator';
  import '@videojs/html/ui/captions-radio-group';
</script>

Troubleshooting

Captions don’t appear

Confirm that:

  • The track kind is captions or subtitles.
  • The VTT file loads (check the network panel for the track request).
  • The track is enabled: default on the track element, a <media-captions-button> toggle, or toggleSubtitles(true).

Captions work locally but not in production

The track file is served from another origin without CORS headers. Serve it with Access-Control-Allow-Origin and set crossorigin on the media component.

The captions menu is empty

The radio group hides when there are no caption or subtitle tracks, but the example’s plain Captions button still renders. Hide or disable that trigger while textTrackList contains no caption or subtitle entries. For streaming sources, confirm the manifest actually declares text tracks.

Components

API

Guides