This document describes the API for programmatically switching audio and subtitle tracks in the FastPix Player.
<fastpix-player
playback-id="your-playback-id"
default-audio-track="French"
default-subtitle-track="English"
>
</fastpix-player>
<script>
const player = document.querySelector('fastpix-player');
// Wait for tracks to be available
player.addEventListener('fastpixtracksready', (e) => {
const { audioTracks, currentAudioTrackLoaded } = e.detail;
console.log('Available audio tracks:', audioTracks);
console.log('Current track:', currentAudioTrackLoaded);
});
// Switch to a different audio track (label only)
player.setAudioTrack('French');
// Listen for track changes
player.addEventListener('fastpixaudiochange', (e) => {
console.log('Switched to:', e.detail.currentTrack);
});
</script>The tables below reflect the current support in fastpix web player:
| Category | Name | Implemented | Notes |
|---|---|---|---|
| Attributes | default-audio-track |
✅ | Case-insensitive match against HLS audio track name; applied during HLS MANIFEST_PARSED setup (no fastpixaudiochange emitted for the initial default). |
| Attributes | default-subtitle-track |
✅ | Case-insensitive match against video.textTracks[].label; applied on loadedmetadata (no fastpixsubtitlechange emitted for the initial default). |
| Attributes | disable-hidden-captions |
✅ | Starts with all captions/subtitles Off on load via an internal disable-all call; does not emit fastpixsubtitlechange for this initial state. Users or code can still enable subtitles later. |
| Attributes | hide-native-subtitles |
✅ | Prevents the internal subtitle container from painting any text while leaving TextTrack state and fastpixsubtitlecue events fully active. Recommended when you render a custom overlay and never want built‑in text. |
| Methods | getAudioTracks() |
✅ | Returns a snapshot de-duped by label (case-insensitive). |
| Methods | setAudioTrack(label) |
✅ | Switches by label/name only (case-insensitive). Emits fastpixaudiochange. |
| Methods | getSubtitleTracks() |
✅ | Returns a snapshot de-duped by label (case-insensitive). |
| Methods | setSubtitleTrack(label | null) |
✅ | null turns subtitles Off. Emits fastpixsubtitlechange. |
| Methods | disableSubtitles() |
✅ | Equivalent to the built-in UI “Off”. Emits fastpixsubtitlechange (payload is slightly smaller; see Events table). |
| Properties | audioTracks / subtitleTracks |
✅ | Public snapshots (same shape as TrackInfo[]). Prefer event + method snapshots for integration. |
| Properties | currentAudioTrackId / currentSubtitleTrackId |
✅ | Current underlying id/index (or null for Off). Do not persist ids across assets. |
| Events | fastpixtracksready |
✅ | Emitted after manifest parse, and may re-emit once subtitle textTracks attach. |
| Events | fastpixaudiochange |
✅ | Emitted only for explicit audio changes (menu click or setAudioTrack). |
| Events | fastpixsubtitlechange |
✅ | Emitted only for explicit subtitle changes (menu click / Off / setSubtitleTrack / disableSubtitles). |
| Events | fastpixsubtitlecue |
✅ | Emitted on subtitle cue changes with { text, language, startTime, endTime }. |
Returns an array of available audio tracks.
getAudioTracks(): TrackInfo[]Returns: Array of TrackInfo objects
Example:
const tracks = player.getAudioTracks();
// [
// { id: 0, label: "English", language: "en", isDefault: false, isCurrent: true },
// { id: 1, label: "French", language: "fr", isDefault: false, isCurrent: false },
// { id: 2, label: "Hindi", language: "hi", isDefault: false, isCurrent: false }
// ]Switches to the specified audio track.
setAudioTrack(languageName: string): voidParameters:
languageName(string) - Tracklabel(e.g."French")
Example:
// Switch by label
player.setAudioTrack('French');Note: Dispatches fastpixaudiochange event after switching.
Returns an array of available subtitle/caption tracks.
getSubtitleTracks(): TrackInfo[]Returns: Array of TrackInfo objects
Example:
const subtitles = player.getSubtitleTracks();
// [
// { id: 0, label: "English", language: "en", isDefault: false, isCurrent: true },
// { id: 1, label: "Spanish", language: "es", isDefault: false, isCurrent: false }
// ]Switches to the specified subtitle track, or turns subtitles off.
setSubtitleTrack(languageName: string | null): voidParameters:
languageName(string | null) - Tracklabel, ornullto turn off subtitles
Example:
// Enable by label
player.setSubtitleTrack('English');
// Turn off subtitles
player.setSubtitleTrack(null);Note: Dispatches fastpixsubtitlechange event after switching.
Turns subtitles Off (equivalent to selecting “Off” in the built-in subtitle menu).
disableSubtitles(): voidExample:
player.disableSubtitles();Note: Dispatches fastpixsubtitlechange. In the current implementation, this event may omit currentTrack in event.detail (see Events table).
| Property | Type | Description |
|---|---|---|
audioTracks |
TrackInfo[] |
Array of available audio tracks |
subtitleTracks |
TrackInfo[] |
Array of available subtitle tracks |
currentAudioTrackId |
number | null |
ID of the currently active audio track |
currentSubtitleTrackId |
number | null |
ID of the currently active subtitle track |
Fired when tracks are loaded and available (after HLS manifest is parsed).
Because subtitle textTracks can attach slightly later than HLS MANIFEST_PARSED, the player may re-emit fastpixtracksready once subtitle tracks become available.
Detail:
{
audioTracks: TrackInfo[]; // All available audio tracks (de-duped by label)
subtitleTracks: TrackInfo[]; // All available subtitle tracks (de-duped by label)
// Preferred (full objects)
currentAudioTrackLoaded: TrackInfo | null; // Currently active audio track (object)
currentSubtitleLoaded: TrackInfo | null; // Currently active subtitle track (object)
// Backward compatible (numeric ids)
currentAudioId: number | null;
currentSubtitleId: number | null;
}Example:
player.addEventListener('fastpixtracksready', (e) => {
const { audioTracks, subtitleTracks, currentAudioTrackLoaded, currentSubtitleLoaded } = e.detail;
console.log('Audio tracks:', audioTracks);
console.log('Current audio:', currentAudioTrackLoaded);
console.log('Subtitle tracks:', subtitleTracks);
console.log('Current subtitle:', currentSubtitleLoaded);
});Fired when the audio track is explicitly changed (menu click or setAudioTrack).
Detail:
{
tracks: TrackInfo[]; // Updated audio snapshot
currentId: number | null; // Current underlying HLS audioTrack index
currentTrack: TrackInfo | null; // The newly active audio track (object)
}Example:
player.addEventListener('fastpixaudiochange', (e) => {
const { currentTrack } = e.detail;
console.log('Switched to:', currentTrack);
// { id: 2, label: "Hindi", language: "hi", isCurrent: true, isDefault: false }
});Fired when the subtitle track is explicitly changed (menu click / Off / setSubtitleTrack / disableSubtitles).
Detail:
{
tracks: TrackInfo[]; // Updated subtitle snapshot
currentId: number | null; // Current subtitle track id (null if Off)
currentTrack: TrackInfo | null; // Newly active subtitle track, or null if Off
}Implementation note: When subtitles are turned Off via disableSubtitles(), the event is still fastpixsubtitlechange, but event.detail may be { tracks, currentId } (without currentTrack). Consumers should treat currentTrack as optional and derive it from tracks.find(t => t.isCurrent) when needed.
Example:
player.addEventListener('fastpixsubtitlechange', (e) => {
const { currentTrack } = e.detail;
if (currentTrack) {
console.log('Subtitles enabled:', currentTrack.label);
} else {
console.log('Subtitles turned off');
}
});Fired on cue updates for subtitle/caption tracks.
Detail:
{
text: string;
language?: string;
startTime?: number;
endTime?: number;
}Sets the initial audio track by label/name (case-insensitive) when the player loads.
<fastpix-player
playback-id="your-playback-id"
default-audio-track="French">
</fastpix-player>If not provided (or no match), the player falls back to the manifest default track (or the first track).
Sets the initial subtitle/caption track by label/name (case-insensitive) when the player loads.
<fastpix-player
playback-id="your-playback-id"
default-subtitle-track="English">
</fastpix-player>If not provided (or no match), the player follows the existing subtitle initialization behavior.
disable-hidden-captions
Disables all subtitles/captions automatically on load. Useful when you want subtitles to start Off, even if the stream includes them.
<fastpix-player playback-id="your-playback-id" disable-hidden-captions></fastpix-player>Note: This is an automatic initialization path and does not emit fastpixsubtitlechange.
Each track is represented by a TrackInfo object:
type TrackInfo = {
id: number; // Internal numeric id (do not use for selection)
label: string; // Display name (e.g., "English", "French")
language?: string; // Language code (e.g., "en", "fr", "hi")
isDefault: boolean; // Audio: manifest DEFAULT flag. Subtitles: currently showing (implementation treats showing as default).
isCurrent: boolean; // Whether this track is currently active
};| Field | Audio tracks | Subtitle tracks |
|---|---|---|
id |
Underlying HLS audio track index | Underlying video.textTracks index |
label |
HLS name (fallbacks to lang / Track N) |
TextTrack label (fallbacks to language / Track N) |
language |
HLS lang (if present) |
TextTrack language (if present) |
isDefault |
true if marked default in manifest |
true when the track is currently showing (same as isCurrent) |
isCurrent |
true for the current active track |
true for the track with mode === "showing" |
This example is equivalent to the track demo: it renders audio/subtitle buttons, switches tracks by label/name, and renders a custom subtitle overlay per player.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Audio/Subtitle Tracks Demo</title>
<script src="./dist/player.js"></script>
<style>
.player-container {
position: relative;
width: 100%;
max-width: 900px;
margin: 16px auto;
aspect-ratio: 16/9;
}
.custom-subtitle {
position: absolute;
left: 50%;
bottom: 10%;
transform: translateX(-50%);
max-width: 90%;
padding: 6px 12px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
display: none;
border-radius: 6px;
text-align: center;
pointer-events: none;
}
.track-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin: 12px 0;
}
.track-btn {
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 8px;
background: #fff;
cursor: pointer;
}
.track-btn.active {
background: #007bff;
color: #fff;
border-color: #007bff;
}
</style>
</head>
<body>
<div class="player-container">
<fastpix-player
id="player"
playback-id="YOUR_PLAYBACK_ID"
auto-play
loop
muted
preload="auto"
default-audio-track="French"
default-subtitle-track="English"
></fastpix-player>
<div class="custom-subtitle" data-role="custom-subtitle"></div>
</div>
<h3>Audio Tracks</h3>
<div id="audioButtons" class="track-buttons"></div>
<h3>Subtitle Tracks</h3>
<div id="subtitleButtons" class="track-buttons"></div>
<script>
const player = document.getElementById("player");
const audioButtons = document.getElementById("audioButtons");
const subtitleButtons = document.getElementById("subtitleButtons");
const getCustomSubtitleDiv = (p) =>
p.closest(".player-container")?.querySelector('[data-role="custom-subtitle"]') ??
null;
const renderButtons = (container, tracks, onSelect) => {
container.innerHTML = "";
(tracks || []).forEach((t) => {
const b = document.createElement("button");
b.className = "track-btn" + (t.isCurrent ? " active" : "");
b.textContent = `${t.label} (${t.language || "unknown"})`;
b.onclick = () => onSelect(t);
container.appendChild(b);
});
};
const renderSubtitleButtons = (tracks) => {
subtitleButtons.innerHTML = "";
// Off button
const off = document.createElement("button");
off.className =
"track-btn" + (!(tracks || []).some((t) => t.isCurrent) ? " active" : "");
off.textContent = "Off";
off.onclick = () => player.setSubtitleTrack(null);
subtitleButtons.appendChild(off);
renderButtons(subtitleButtons, tracks || [], (t) => player.setSubtitleTrack(t.label));
};
// Tracks snapshot (may fire more than once)
player.addEventListener("fastpixtracksready", (e) => {
const d = /** @type {CustomEvent} */ (e).detail || {};
renderButtons(audioButtons, d.audioTracks || player.getAudioTracks(), (t) =>
player.setAudioTrack(t.label)
);
renderSubtitleButtons(d.subtitleTracks || player.getSubtitleTracks());
});
// Explicit changes
player.addEventListener("fastpixaudiochange", (e) => {
const d = /** @type {CustomEvent} */ (e).detail || {};
renderButtons(audioButtons, d.tracks || player.getAudioTracks(), (t) =>
player.setAudioTrack(t.label)
);
});
player.addEventListener("fastpixsubtitlechange", (e) => {
const d = /** @type {CustomEvent} */ (e).detail || {};
renderSubtitleButtons(d.tracks || player.getSubtitleTracks());
});
// Custom subtitle rendering (per player)
player.addEventListener("fastpixsubtitlecue", (e) => {
const cue = /** @type {CustomEvent} */ (e).detail || {};
const tracks = player.getSubtitleTracks();
const hasActive = Array.isArray(tracks) && tracks.some((t) => t.isCurrent);
const el = getCustomSubtitleDiv(player);
if (!el) return;
if (hasActive && cue.text) {
el.textContent = cue.text;
el.style.display = "block";
} else {
el.textContent = "";
el.style.display = "none";
}
});
</script>
</body>
</html>const player = document.querySelector('fastpix-player');
const audioSelect = document.getElementById('audioSelect');
// Populate dropdown when tracks are ready
player.addEventListener('fastpixtracksready', (e) => {
const { audioTracks, currentAudioTrackLoaded } = e.detail;
audioSelect.innerHTML = audioTracks.map(track =>
`<option value="${track.label}" ${track.isCurrent ? 'selected' : ''}>
${track.label} (${track.language || 'unknown'})
</option>`
).join('');
});
// Handle selection change
audioSelect.addEventListener('change', (e) => {
player.setAudioTrack(e.target.value);
});
// Update UI when track changes
player.addEventListener('fastpixaudiochange', (e) => {
const t = e.detail.currentTrack;
audioSelect.value = t ? t.label : '';
});const player = document.querySelector('fastpix-player');
const ccButton = document.getElementById('ccButton');
let subtitlesEnabled = false;
let lastSubtitleLabel = '';
player.addEventListener('fastpixtracksready', (e) => {
const { subtitleTracks, currentSubtitleLoaded } = e.detail;
if (subtitleTracks.length > 0) {
lastSubtitleLabel = subtitleTracks[0].label;
subtitlesEnabled = currentSubtitleLoaded !== null;
updateButton();
}
});
ccButton.addEventListener('click', () => {
if (subtitlesEnabled) {
player.setSubtitleTrack(null);
} else {
player.setSubtitleTrack(lastSubtitleLabel);
}
});
player.addEventListener('fastpixsubtitlechange', (e) => {
subtitlesEnabled = e.detail.currentTrack !== null;
if (e.detail.currentTrack) {
lastSubtitleLabel = e.detail.currentTrack.label;
}
updateButton();
});
function updateButton() {
ccButton.classList.toggle('active', subtitlesEnabled);
ccButton.textContent = subtitlesEnabled ? 'CC ON' : 'CC OFF';
}Tracks are not available immediately. Always wait for the event before calling getAudioTracks() or setAudioTrack().
// ❌ Wrong - called too early
const tracks = player.getAudioTracks(); // Returns []
// ✅ Correct - wait for event
player.addEventListener('fastpixtracksready', () => {
const tracks = player.getAudioTracks(); // Returns actual tracks
});The id is an internal numeric index that may differ between videos. Use language code for storing user preferences, and use the track label (languageName) when calling setAudioTrack / setSubtitleTrack.
// ✅ Better - language code is consistent
localStorage.setItem('audioLang', 'fr');Not all videos have multiple audio tracks or subtitles.
player.addEventListener('fastpixtracksready', (e) => {
const { audioTracks, subtitleTracks } = e.detail;
// Hide audio selector if only one track
audioSelector.style.display = audioTracks.length > 1 ? 'block' : 'none';
// Hide CC button if no subtitles
ccButton.style.display = subtitleTracks.length > 0 ? 'block' : 'none';
});Remove event listeners when components unmount to avoid memory leaks.
const handleTracksReady = (e) => { /* ... */ };
const handleAudioChange = (e) => { /* ... */ };
// Add listeners
player.addEventListener('fastpixtracksready', handleTracksReady);
player.addEventListener('fastpixaudiochange', handleAudioChange);
// Remove on cleanup
player.removeEventListener('fastpixtracksready', handleTracksReady);
player.removeEventListener('fastpixaudiochange', handleAudioChange);This API works in all browsers that support HLS.js and the HTMLMediaElement API:
- Chrome 51+
- Firefox 54+
- Safari 10+
- Edge 79+