# Closed captions and subtitles

Compare sideloaded subtitles with captions and subtitles included in a stream. For sideloaded text tracks, set `format` on the text track description (for example, `'srt'`, `'webvtt'`, and other supported formats).

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

### Sources

Sideloaded SRTSideloaded WebVTTCEA-608 captionsDASH TTML

### Subtitle style

DefaultYellow on blackLarge with edge

### Text tracks

| Label                        | Lang | Kind | Format | Mode |
| ---------------------------- | ---- | ---- | ------ | ---- |
| No text tracks reported yet. |      |      |        |      |

WebVTT, TTML, and CEA-608 tracks can be included in a manifest. You can also add a subtitle file when setting the source. The demo filters out metadata tracks and reports each text track's label, language, kind, format, and mode.

## Style subtitles

You can set `player.textTrackStyle` directly. See the [`TextTrackStyle` API](https://optiview.dolby.com/docs/theoplayer/v11/api-reference/web/interfaces/TextTrackStyle) for supported properties.

```js
player.textTrackStyle = {
  backgroundColor: 'rgba(0, 0, 0, 0.85)',
  edgeStyle: 'dropshadow',
  fontColor: '#ffff00',
  fontSize: '150%',
};
```

See [how to insert subtitles](https://optiview.dolby.com/docs/theoplayer/how-to-guides/web/text-tracks/insert-subtitles.md) for more details.

Code

demo.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Closed captions and subtitles</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"}'
      source='{"sources":{"src":"https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8","type":"application/x-mpegurl"},"poster":"https://cdn.theoplayer.com/video/elephants-dream/playlist.png","textTracks":[{"src":"https://cdn.theoplayer.com/video/elephants-dream/captions.nl.srt","srclang":"nl","label":"Dutch (SRT)","kind":"subtitles","format":"srt","default":true}],"metadata":{"title":"Elephants Dream"}}'
    ></theoplayer-default-ui>
    <script src="../utils.js"></script>
    <script>
      const sourceConfigurations = {
        srt: {
          sources: {
            src: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8',
            type: 'application/x-mpegurl',
          },
          poster: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.png',
          textTracks: [
            {
              src: 'https://cdn.theoplayer.com/video/elephants-dream/captions.nl.srt',
              srclang: 'nl',
              label: 'Dutch (SRT)',
              kind: 'subtitles',
              format: 'srt',
              default: true,
            },
          ],
          metadata: { title: 'Elephants Dream' },
        },
        webvtt: {
          sources: {
            src: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8',
            type: 'application/x-mpegurl',
          },
          poster: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.png',
          textTracks: [
            {
              src: 'https://cdn.theoplayer.com/video/elephants-dream/captions.en.vtt',
              srclang: 'en',
              label: 'English (WebVTT)',
              kind: 'subtitles',
              format: 'webvtt',
              default: true,
            },
          ],
          metadata: { title: 'Elephants Dream' },
        },
        cea608: {
          sources: {
            src: 'https://cdn.theoplayer.com/video/adultswim2/clip.m3u8',
            type: 'application/x-mpegurl',
          },
          poster: 'https://cdn.theoplayer.com/video/adultswim2/poster.jpg',
          metadata: { title: 'Adult Swim (CEA-608)' },
        },
        ttml: {
          sources: {
            src: 'https://livesim.dashif.org/dash/vod/testpic_2s/multi_subs.mpd',
            type: 'application/dash+xml',
          },
          metadata: { title: 'DASH TTML subtitles' },
        },
      };

      const resetStyle = {
        backgroundColor: undefined,
        edgeStyle: undefined,
        fontColor: undefined,
        fontFamily: undefined,
        fontSize: undefined,
        marginBottom: undefined,
        marginLeft: undefined,
        marginRight: undefined,
        marginTop: undefined,
        windowColor: undefined,
      };

      const stylePresets = {
        default: {},
        yellow: {
          backgroundColor: 'rgba(0, 0, 0, 0.85)',
          edgeStyle: 'none',
          fontColor: '#ffff00',
          windowColor: 'rgba(0, 0, 0, 0.85)',
        },
        large: {
          backgroundColor: 'rgba(0, 0, 0, 0.65)',
          edgeStyle: 'dropshadow',
          fontColor: '#ffffff',
          fontSize: '150%',
          windowColor: 'rgba(0, 0, 0, 0.65)',
        },
      };

      onPlayerReady((player) => {
        let currentSource = 'srt';
        let autoSelectedTrack;

        function reportTracks() {
          const tracks = Array.from(player.textTracks)
            .filter((track) => track.kind !== 'metadata')
            .map((track) => ({
              label: track.label || track.kind,
              language: track.language || '',
              kind: track.kind,
              format: track.type || track.format || '—',
              mode: track.mode,
            }));
          sendToParent('text-tracks', { tracks });
        }

        function selectSubtitleTrack() {
          if (currentSource !== 'cea608' && currentSource !== 'ttml') return;
          const tracks = Array.from(player.textTracks).filter((track) => track.kind === 'subtitles' || track.kind === 'captions');
          const showingTrack = tracks.find((track) => track.mode === 'showing');
          const englishTrack = currentSource === 'ttml' ? tracks.find((track) => (track.language || '').toLowerCase().startsWith('en')) : undefined;

          if (showingTrack) {
            if (englishTrack && showingTrack === autoSelectedTrack && showingTrack !== englishTrack) {
              showingTrack.mode = 'disabled';
              englishTrack.mode = 'showing';
              autoSelectedTrack = englishTrack;
              reportTracks();
            }
            return;
          }

          const track = englishTrack || tracks[0];
          if (track) {
            track.mode = 'showing';
            autoSelectedTrack = track;
            reportTracks();
          }
        }

        player.textTracks.addEventListener(['addtrack', 'removetrack', 'change'], reportTracks);
        player.textTracks.addEventListener('addtrack', selectSubtitleTrack);
        player.addEventListener('sourcechange', reportTracks);

        onParentMessage('text-source', (message) => {
          if (typeof message.name !== 'string' || !sourceConfigurations[message.name]) return;
          if (message.name === 'cea608' || message.name === 'ttml') {
            Array.from(player.textTracks)
              .filter((track) => track.kind === 'subtitles' || track.kind === 'captions')
              .forEach((track) => {
                track.mode = 'disabled';
              });
          }
          currentSource = message.name;
          autoSelectedTrack = undefined;
          player.autoplay = true;
          player.source = sourceConfigurations[message.name];
          reportTracks();
        });
        onParentMessage('text-style', (message) => {
          if (typeof message.name !== 'string' || !stylePresets[message.name]) return;
          Object.assign(player.textTrackStyle, resetStyle, stylePresets[message.name]);
        });

        reportTracks();
      });
    </script>
  </body>
</html>
```
