Build a fully custom audio and subtitle track switcher UI using the FastPix Player JavaScript API, with cue rendering, preference persistence, and real-time sync.
The built-in subtitle and audio menus handle the default use case well. But if you’re building your own player controls — or need subtitle rendering you can fully style — FastPix Player exposes a complete JavaScript API for reading tracks, switching between them, and reacting to changes in real time.
This guide walks through the full demo implementation, step by step.
By the end of this guide you’ll have:
fastpixsubtitlecue event (you listen for the event and set the overlay’s text; when subtitles are Off or the cue ends, you clear or hide it)You’ll need a FastPix stream URL that already has multiple audio or subtitle tracks. If your stream doesn’t have them yet and you’d like to add some, see Add own audio tracks and Add own subtitle tracks.
The examples on this page use the CDN-hosted player script. If you’ve installed the player via npm, replace the <script> tag with your local build path.
Start with a <fastpix-player> wrapped in a positioned container. The container is required if you plan to overlay a custom subtitle div — position: relative on the wrapper is what makes absolute positioning of the subtitle layer work correctly.
The data-role="custom-subtitle" attribute is used later to locate this overlay relative to its specific player — this is what makes the pattern work cleanly when you have multiple players on the same page. In Step 8 you’ll add the subtitle display logic: listen for the fastpixsubtitlecue event and update this element’s textContent (and show/hide it) so the current cue text appears in your overlay.
You can optionally set default tracks and subtitle rendering behavior with HTML attributes on <fastpix-player>:
default-audio-track — Start with a specific audio track by label (case-insensitive).default-subtitle-track — Start with a specific subtitle track by label (case-insensitive).disable-hidden-captions — Start with all subtitles/captions Off on load without firing fastpixsubtitlechange. Users (or your code) can still turn subtitles on later via the built-in menu or setSubtitleTrack(...).hide-native-subtitles — Keep the player’s internal subtitle overlay visually empty while still emitting fastpixsubtitlecue and track events. Use this when you render subtitles yourself so you never see duplicate text.Example — add them on the same <fastpix-player> as in Step 1 (replace labels with your manifest’s track names):
Full behavior and examples for these attributes are in the Full API reference and in the TrackInfo reference on this page. For the rest of this guide we’ll assume you add them to the player as needed.
Below the player, add the HTML containers that will hold the rendered track buttons. Show a loading state until tracks are ready.
The demo also includes simple skip-ahead / skip-back controls using the player’s seekForward(seconds) and seekBackward(seconds) methods. Add a small control group and wire it in script:
Seconds are clamped to the media range. You can use the same pattern with any skip value (e.g. 5 or 30).
Tracks are discovered asynchronously after the HLS manifest is parsed. The fastpixtracksready event is your signal that tracks are available. Always wait for this event before calling any track methods.
Get the player reference, wire up the event, and call getAudioTracks() to retrieve the current audio snapshot:
getAudioTracks() returns an array of TrackInfo objects. The isCurrent flag tells you which track is active so you can set the initial selected state in your UI. See the TrackInfo reference for a full description of all fields.
Now write renderAudioButtons to turn that array into buttons:
Note that setAudioTrack takes a label string — not a numeric id. The id field is an internal index and must never be used for switching or persistence.
Subtitle textTracks attach to the player slightly later than audio tracks — after MANIFEST_PARSED fires but before the player finishes registering them. This means fastpixtracksready can fire before subtitle tracks are ready, and calling getSubtitleTracks() immediately in the handler can return an empty array.
The reliable pattern is to poll getSubtitleTracks() for a short window after fastpixtracksready fires, and render as soon as the array becomes non-empty:
Why not just use
e.detail.subtitleTracks? The event detail does includesubtitleTracks, but it reflects the state at the moment the event fired. If that was before subtitle tracks were registered, the array will be empty. PollinggetSubtitleTracks()directly catches the second registration.
Subtitle buttons work the same as audio buttons, with one addition: always render an Off button first. Off is the state where no track has isCurrent: true — which happens after setSubtitleTrack(null) or disableSubtitles() is called.
disableSubtitles() vs setSubtitleTrack(null): Both turn subtitles off and produce the same result. disableSubtitles() is a named convenience method added in newer player builds. The feature-detect guard (typeof player.disableSubtitles === 'function') ensures the code works across all build versions — setSubtitleTrack(null) is the stable fallback.
Clear the overlay immediately on Off. When the user turns subtitles off,
fastpixsubtitlecuemay not fire again for several seconds — not until the next cue boundary. If you only clear the overlay in the cue handler, the last cue text stays visible on screen after the user has already turned subtitles off. Clearing it directly in the Off button’sonclickprevents this.
fastpixaudiochange and fastpixsubtitlechange fire whenever a track switch happens — whether the user clicked one of your buttons, a programmatic call was made, or the built-in player menu was used. Re-render buttons in these handlers so the active state always reflects reality.
Both events also carry currentTrack (a full TrackInfo object, or null for Off) and currentId if you need them for logging or analytics.
This step is the subtitle display logic: you listen for the fastpixsubtitlecue event and use it to show the current cue text in your custom overlay div (from Step 1), or clear/hide the overlay when subtitles are Off or the cue ends.
With hide-native-subtitles on the player, the player’s internal subtitle container never paints text (so the built-in overlay stays hidden). Cue data is still available: fastpixsubtitlecue still fires and you’re responsible for displaying that text in your own overlay.
The event fires each time the active cue changes. Its detail object carries:
For most overlays you’ll only need text. The startTime and endTime fields are useful if you want timed animations, karaoke-style highlighting, or precise cue scheduling.
Using document.querySelectorAll('fastpix-player') and p.closest('.player-container') rather than referencing a single player and overlay by id is intentional — this exact code works unchanged whether you have one player on the page or ten, because each cue listener finds its own scoped overlay through the DOM.
Because hide-native-subtitles keeps the player’s internal subtitle layer from painting, your overlay is the only thing rendering subtitles. Style it however you want — font, position, background, animation — without any risk of conflicting with the player’s own styles.
Store the user’s preferred language so it’s automatically applied the next time they load a video. Use the language code (e.g. "fr") for storage — it’s stable across videos. Use the label when calling setAudioTrack, since that’s what the selection API accepts.
Note: Restore audio preference in fastpixtracksready (audio tracks are ready then). Restore subtitle preference when subtitle tracks first become available — e.g. in the same place you first call renderSubtitleButtons(subtitleTracks) (e.g. inside the Step 5 poll callback when getSubtitleTracks() returns a non-empty array). If you restore subtitles in fastpixtracksready only, getSubtitleTracks() may still be empty and the saved preference won’t apply.
Here’s the complete implementation with every step assembled (matches demo/audio_subtitle_tracks.html; the “Current:” lines use label + language as in this guide, not the numeric id):
Every track method and event uses a TrackInfo object to represent a single track:
Key rules:
label — e.g. setAudioTrack('French')language — e.g. localStorage.setItem('fp_audioLang', 'fr')id — it is internal and can differ between videosTrack lists vary per asset. Don’t assume all videos expose the same set of audio or subtitle tracks. Check tracks.length before rendering and hide controls that don’t apply — for example, hide the audio selector entirely if only one audio track is available.
fastpixtracksready can fire more than once. Treat every emission as “tracks snapshot updated” and re-render idempotently. Clearing containers with innerHTML = '' before each render is enough.
Clear the custom subtitle overlay immediately on Off. fastpixsubtitlecue may not fire again for several seconds after subtitles are turned off. Don’t rely on the cue handler to clear it — clear the overlay directly in your Off button handler.
Use the playing event to clear the overlay after a source change. If the player source changes mid-session, any stale subtitle text in the overlay should be cleared. The player forwards the video element’s playing event, so listen for playing on the <fastpix-player> and reset the overlay when it fires.
Clean up event listeners when components unmount. In React, Vue, or any single-page app, ghost listeners accumulate across navigations if you don’t remove them. Use named handler functions and pair every addEventListener with a corresponding removeEventListener in your cleanup or beforeUnmount hook.
This guide covers the integration patterns and the demo implementation. For the complete reference — all methods, properties, events, attributes, and additional usage examples — see the Audio & Subtitle Tracks API reference in the FastPix web player repository.
To see how this track switcher and custom subtitle overlay are integrated in a React app (e.g. a vertical shorts feed with track menu and subtitle pill), go through the FastPix React Shorts Demo. That repo uses the same APIs (getAudioTracks, setAudioTrack, getSubtitleTracks, setSubtitleTrack, fastpixtracksready, fastpixsubtitlecue, etc.), mounts the player with document.createElement('fastpix-player') in useEffect, and shows React patterns for refs, cleanup, and feed-level state. Clone it, run npm install and npm run dev, then replace the feed playback IDs with your own multi-track assets.