For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
StatusSupportDiscussionsLog inSign Up
Docs HomeAPI ReferenceVideo on DemandAI FeaturesLive StreamingVideo PlayerVideo DataCloud PlayoutRecipes
Docs HomeAPI ReferenceVideo on DemandAI FeaturesLive StreamingVideo PlayerVideo DataCloud PlayoutRecipes
  • Player SDKs
    • Introduction
  • Web player
    • Install the FastPix web player
    • Play uploaded videos
    • Handle playback errors
      • Build a custom track switcher UI
      • Build a custom quality selector
  • Android player
    • Install FastPix Android player
    • Set up the player
    • Play uploaded videos
    • Handle playback errors
  • iOS player
    • Install FastPix iOS player
    • Play uploaded videos
    • Handle playback errors
  • Flutter player
    • Install FastPix Flutter player
    • Play uploaded videos
    • Handle playback errors
LogoLogo
StatusSupportDiscussionsLog inSign Up
On this page
  • Key terms
  • Retrieve available quality levels
  • Lock playback to a specific resolution
  • Re-enable adaptive bitrate
  • Read the current quality state
  • Listen for quality events
  • fastpixqualitylevelsready
  • fastpixqualitychange
  • fastpixqualityfailed
  • Build the full example
  • What each section does
  • Understand the typical flow
  • Quick reference
  • Frequently asked questions
  • What’s next?
Web playerExamples

Build a custom quality selector

Was this page helpful?
Previous

Install FastPix Android player

Next
Built with

The FastPix Player exposes a JavaScript API for reading and switching video quality levels, which lets you build a branded quality selector that gives viewers explicit resolution control while preserving adaptive bitrate (ABR) fallback for unstable networks. Combine the quality methods with slot-based overlays to position your custom UI directly over the video without managing absolute positioning yourself.


Key terms

  • Rendition is one resolution/bitrate variant in the HLS manifest that FastPix generates during transcoding (for example, 360p at 800 kbps, 720p at 2.5 Mbps).
  • Adaptive Bitrate Streaming (ABR) is the default mode where the player switches renditions automatically based on the viewer’s network speed.
  • playbackId is the access-controlled identifier FastPix assigns for constructing the HLS playback URL: https://stream.fastpix.com/{PLAYBACK_ID}.m3u8.

Retrieve available quality levels

Call player.getQualityLevels() after the manifest loads to get the full rendition ladder.

1const levels = player.getQualityLevels();
2// Returns: [{ id, label, height, width, bitrate, frameRate }, ...]

Each entry contains:

PropertyTypeDescription
idnumberPass this to setQualityLevel() to lock playback to this rendition
labelstringHuman-readable label (for example, “720p”) — display this in your UI
heightnumberVertical resolution in pixels
widthnumberHorizontal resolution in pixels
bitratenumberBits per second for this rendition
frameRatenumberFrames per second

NOTE

The array is empty for audio-only streams or single-rendition videos. Wait for the fastpixqualitylevelsready event before calling this method on a new video.


Lock playback to a specific resolution

Call player.setQualityLevel(id) to disable ABR and force the player to use one rendition.

1player.setQualityLevel(2); // Lock to the rendition with id 2 (e.g. "720p")

The id value comes from a getQualityLevels() entry. Do not hard-code IDs across videos — each manifest generates its own ID set.


Re-enable adaptive bitrate

Call player.setQualityAuto() to hand control back to the ABR engine. The player resumes switching renditions based on network conditions.

1player.setQualityAuto();

Use this when the viewer selects “Auto” in your menu, or as a fallback after a quality error.


Read the current quality state

Call player.getPlaybackQuality() to get a snapshot of the player’s current quality mode and active rendition.

1const q = player.getPlaybackQuality();
2// Returns: { mode, lockedLevel, loadedLevel }
PropertyTypeDescription
mode"auto" or "manual""auto" = ABR active; "manual" = viewer locked a rendition
lockedLevelobject or nullThe rendition the viewer locked to, or null in auto mode
loadedLevelobjectThe rendition currently being decoded and played
1const q = player.getPlaybackQuality();
2if (q.mode === "auto") {
3 // Highlight the "Auto" button
4} else if (q.lockedLevel) {
5 // Highlight the button matching q.lockedLevel.id
6}
7console.log("Currently playing:", q.loadedLevel?.label); // e.g. "720p"

NOTE

loadedLevel reflects what is actually on screen. Under ABR, it can differ from lockedLevel briefly during a rendition switch.


Listen for quality events

The player triggers three custom events on the <fastpix-player> element. Listen with addEventListener.

fastpixqualitylevelsready

Triggers when the HLS manifest is parsed and quality levels are available. Also triggers again if a new video loads.

1player.addEventListener("fastpixqualitylevelsready", (e) => {
2 const levels = e.detail.levels;
3 // Build your quality menu buttons here
4});

event.detail contains:

1{
2 "levels": [{ "id": 0, "label": "360p", "height": 360, "width": 640, "bitrate": 800000, "frameRate": 30 }]
3}

fastpixqualitychange

Triggers whenever the quality state changes, either from a viewer action (setQualityLevel / setQualityAuto) or from ABR switching renditions automatically.

1player.addEventListener("fastpixqualitychange", (e) => {
2 console.log("Quality changed to:", e.detail.loadedLevel?.label);
3 console.log("Mode:", e.detail.mode);
4 // Re-highlight your menu buttons here
5});

event.detail contains:

PropertyDescription
mode"auto" or "manual"
lockedLevelThe locked rendition, or null
loadedLevelThe rendition now playing
previousLoadedLevelThe rendition before this change

fastpixqualityfailed

Triggers when a quality change fails — either when an invalid ID is passed to setQualityLevel(), or when a specific rendition fails to load from the network.

1player.addEventListener("fastpixqualityfailed", (e) => {
2 console.warn("Quality failed:", e.detail.reason);
3 player.setQualityAuto(); // Fall back to ABR
4});

event.detail contains:

PropertyDescription
reasonHuman-readable error string
levelIdThe ID that failed (if applicable)
rawRaw error from hls.js (for debugging)

Build the full example

A complete, working quality selector combining every API and event described in this guide. The example uses slot overlays to position the menu in the top-right corner of the video. Copy the HTML below, replace your-playback-id with a real playbackId, and open it in a browser.

1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Custom Quality Selector - FastPix Player</title>
7 <script defer src="https://cdn.jsdelivr.net/npm/@fastpix/fp-player@latest"></script>
8 <style>
9 /* ── Page layout ─────────────────────────────────────────── */
10 body {
11 font-family: system-ui, sans-serif;
12 max-width: 800px;
13 margin: 32px auto;
14 padding: 0 16px;
15 background: #f9f9f9;
16 }
17
18 /* ── Player wrapper ──────────────────────────────────────── */
19 .player-shell {
20 position: relative;
21 width: 100%;
22 aspect-ratio: 16 / 9;
23 border-radius: 10px;
24 overflow: hidden;
25 background: #000;
26 }
27 fastpix-player {
28 width: 100%;
29 height: 100%;
30 display: block;
31 /* Hide the built-in resolution button so only the custom menu shows */
32 --resolution-selector: none;
33 /* Raise the slot layer above built-in player chrome */
34 --user-slot-z: 2001;
35 /* Keep bottom-row slots above the seek bar */
36 --user-slot-bottom-clearance: 48px;
37 }
38
39 /* ── Quality toggle button (inside slot="top-right") ──────── */
40 .quality-toggle {
41 padding: 7px 14px;
42 font-size: 13px;
43 font-weight: 600;
44 cursor: pointer;
45 border: none;
46 border-radius: 6px;
47 background: rgba(0, 0, 0, 0.6);
48 color: #fff;
49 backdrop-filter: blur(6px);
50 }
51 .quality-toggle:hover {
52 background: rgba(40, 40, 40, 0.85);
53 }
54 .quality-toggle[aria-expanded="true"] {
55 background: #2563eb;
56 }
57
58 /* ── Dropdown panel ──────────────────────────────────────── */
59 .quality-panel {
60 display: none;
61 flex-direction: column;
62 gap: 5px;
63 min-width: 150px;
64 max-height: 260px;
65 overflow-y: auto;
66 padding: 8px;
67 border-radius: 8px;
68 background: rgba(15, 15, 15, 0.92);
69 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
70 backdrop-filter: blur(8px);
71 margin-top: 6px;
72 }
73 .quality-panel.open {
74 display: flex;
75 }
76
77 /* ── Individual quality option button ─────────────────────── */
78 .q-option {
79 padding: 7px 11px;
80 font-size: 13px;
81 text-align: left;
82 border: 1px solid rgba(255, 255, 255, 0.1);
83 border-radius: 5px;
84 background: rgba(255, 255, 255, 0.05);
85 color: #ddd;
86 cursor: pointer;
87 }
88 .q-option:hover {
89 background: rgba(255, 255, 255, 0.12);
90 }
91 .q-option.active {
92 border-color: #3b82f6;
93 background: rgba(37, 99, 235, 0.3);
94 color: #fff;
95 }
96
97 /* ── Status bar below the player ─────────────────────────── */
98 .status-bar {
99 margin-top: 14px;
100 padding: 10px 14px;
101 font-size: 13px;
102 font-family: ui-monospace, monospace;
103 background: #f0f0f0;
104 border-radius: 7px;
105 color: #333;
106 line-height: 1.6;
107 }
108 </style>
109</head>
110<body>
111 <h2>Custom Quality Selector</h2>
112
113 <div class="player-shell">
114 <fastpix-player
115 id="player"
116 playback-id="your-playback-id"
117 preload="auto"
118 muted
119 >
120 <!-- slot="top-right" places this div in the top-right corner -->
121 <div slot="top-right" id="quality-slot" style="display:flex; flex-direction:column; align-items:flex-end;">
122 <!-- Toggle button -->
123 <button
124 type="button"
125 id="quality-toggle"
126 class="quality-toggle"
127 aria-expanded="false"
128 aria-controls="quality-panel"
129 >
130 Quality
131 </button>
132
133 <!-- Dropdown panel — populated by JS once levels are ready -->
134 <div
135 id="quality-panel"
136 class="quality-panel"
137 role="listbox"
138 aria-label="Video quality options"
139 ></div>
140 </div>
141 </fastpix-player>
142 </div>
143
144 <!-- Live status: updates on every fastpixqualitychange event -->
145 <div class="status-bar" id="status">Mode: - · Playing: - · Locked: -</div>
146
147 <script>
148 customElements.whenDefined("fastpix-player").then(() => {
149 const player = document.getElementById("player");
150 const toggle = document.getElementById("quality-toggle");
151 const panel = document.getElementById("quality-panel");
152 const status = document.getElementById("status");
153
154 /* ── Dropdown open/close ─────────────────────────────── */
155 function openPanel() { panel.classList.add("open"); toggle.setAttribute("aria-expanded", "true"); }
156 function closePanel() { panel.classList.remove("open"); toggle.setAttribute("aria-expanded", "false"); }
157
158 toggle.addEventListener("click", () => {
159 panel.classList.contains("open") ? closePanel() : openPanel();
160 });
161 document.addEventListener("click", (e) => {
162 if (!panel.contains(e.target) && !toggle.contains(e.target)) closePanel();
163 });
164
165 /* ── Status bar ──────────────────────────────────────── */
166 function refreshStatus() {
167 const q = player.getPlaybackQuality();
168 const mode = q.mode === "auto" ? "Auto (ABR)" : "Manual";
169 const playing = q.loadedLevel ? q.loadedLevel.label : "-";
170 const locked = q.mode === "manual" && q.lockedLevel ? q.lockedLevel.label : "-";
171 status.textContent = `Mode: ${mode} · Playing: ${playing} · Locked: ${locked}`;
172 }
173
174 /* ── Highlight active button ─────────────────────────── */
175 function refreshActiveButton() {
176 const q = player.getPlaybackQuality();
177 panel.querySelectorAll(".q-option").forEach(btn => btn.classList.remove("active"));
178
179 if (q.mode === "auto") {
180 const autoBtn = panel.querySelector('[data-kind="auto"]');
181 if (autoBtn) autoBtn.classList.add("active");
182 } else if (q.lockedLevel) {
183 const lockedBtn = panel.querySelector(`[data-level-id="${q.lockedLevel.id}"]`);
184 if (lockedBtn) lockedBtn.classList.add("active");
185 }
186 }
187
188 /* ── Build the dropdown ──────────────────────────────── */
189 function buildMenu(levels) {
190 panel.textContent = "";
191
192 // Auto button — calls setQualityAuto()
193 const autoBtn = document.createElement("button");
194 autoBtn.type = "button";
195 autoBtn.className = "q-option";
196 autoBtn.dataset.kind = "auto";
197 autoBtn.textContent = "Auto (ABR)";
198 autoBtn.addEventListener("click", () => { player.setQualityAuto(); closePanel(); });
199 panel.appendChild(autoBtn);
200
201 // One button per rendition — calls setQualityLevel(id)
202 if (levels.length === 0) {
203 const note = document.createElement("span");
204 note.style.cssText = "font-size:12px;color:#888;padding:4px";
205 note.textContent = "No alternate renditions.";
206 panel.appendChild(note);
207 } else {
208 levels.forEach((lvl) => {
209 const btn = document.createElement("button");
210 btn.type = "button";
211 btn.className = "q-option";
212 btn.dataset.levelId = String(lvl.id);
213 btn.textContent = lvl.label + (lvl.bitrate ? ` · ${Math.round(lvl.bitrate / 1000)} kbps` : "");
214 btn.addEventListener("click", () => { player.setQualityLevel(lvl.id); closePanel(); });
215 panel.appendChild(btn);
216 });
217 }
218
219 refreshActiveButton();
220 refreshStatus();
221 }
222
223 /* ── Event: levels ready ─────────────────────────────── */
224 player.addEventListener("fastpixqualitylevelsready", (e) => {
225 buildMenu(e.detail.levels);
226 });
227
228 /* ── Event: quality changed ──────────────────────────── */
229 player.addEventListener("fastpixqualitychange", () => {
230 refreshActiveButton();
231 refreshStatus();
232 });
233
234 /* ── Event: quality failed ───────────────────────────── */
235 player.addEventListener("fastpixqualityfailed", (e) => {
236 console.warn("[quality] failed:", e.detail.reason);
237 player.setQualityAuto();
238 refreshActiveButton();
239 refreshStatus();
240 });
241
242 /* ── Eager fallback if levels already loaded ─────────── */
243 queueMicrotask(() => {
244 const levels = player.getQualityLevels();
245 if (levels.length > 0) buildMenu(levels);
246 });
247 }); // end customElements.whenDefined
248 </script>
249</body>
250</html>

What each section does

Code sectionAPI / eventPurpose
--resolution-selector: noneCSS variableHides the built-in quality button
slot="top-right"Slot attributePlaces the toggle + panel in the top-right corner
buildMenu(levels)getQualityLevels()Renders one button per rendition
Auto button clicksetQualityAuto()Re-enables adaptive bitrate
Rendition button clicksetQualityLevel(id)Locks to a specific resolution
refreshStatus()getPlaybackQuality()Updates the status bar
refreshActiveButton()getPlaybackQuality()Highlights the active button
fastpixqualitylevelsreadyEventTriggers menu build after manifest loads
fastpixqualitychangeEventKeeps the UI in sync
fastpixqualityfailedEventFalls back to Auto on error
queueMicrotask fallbackgetQualityLevels()Handles pre-loaded manifests

Understand the typical flow

  1. Page loads → <fastpix-player> is created
  2. Video manifest loads → fastpixqualitylevelsready triggers
  3. Your code calls getQualityLevels() and builds quality buttons
  4. Viewer clicks “720p” → your code calls setQualityLevel(id)
  5. Player switches rendition → fastpixqualitychange triggers
  6. Your code calls getPlaybackQuality() and highlights the active button
  7. Viewer clicks “Auto” → your code calls setQualityAuto()
  8. fastpixqualitychange triggers again → UI updates

Quick reference

NameTypeDescription
getQualityLevels()MethodGet all available renditions
setQualityLevel(id)MethodLock to one rendition
setQualityAuto()MethodRe-enable ABR
getPlaybackQuality()MethodRead current quality state
fastpixqualitylevelsreadyEventManifest loaded — build your menu
fastpixqualitychangeEventQuality switched — update your UI
fastpixqualityfailedEventQuality change failed — handle the error
slot="top-right" etc.HTML attributePlace custom HTML over the video
--user-slot-zCSS variableControl slot layer stacking order
--user-slot-bottom-clearanceCSS variableSpace between bottom slots and seek bar
--resolution-selectorCSS variableSet to none to hide the built-in quality button

Frequently asked questions

Do quality level IDs stay the same across different videos?

No. Each video’s HLS manifest generates its own rendition ladder with unique IDs. Always call getQualityLevels() after fastpixqualitylevelsready triggers for the current video. Do not cache IDs from one video and reuse them on another.

What happens if the video has only one rendition?

getQualityLevels() returns an empty array. The player uses the single available rendition and ABR has no alternatives to switch between. Hide or disable your quality menu in this case.

Can I set a default quality lock before the video starts?

Listen for fastpixqualitylevelsready, find the rendition you want by label or height, and call setQualityLevel(id) immediately. The player applies the lock before the first segment plays at the ABR-selected level.

How do I show the currently active quality in the menu without polling?

Listen for fastpixqualitychange — it triggers on every ABR switch and every manual selection. Call getPlaybackQuality() inside the handler and update your UI. No polling or timers are needed.


What’s next?

  • Control playback resolution
  • Adjust player layout and UI
  • Set the aspect ratio