Player Quality & Audio
Phase: P3B-S8
Phlix supports flexible quality selection, multi-audio tracks, subtitle selection, and Picture-in-Picture (PiP) playback.
Quality Selector
Quality Levels
| Label | Resolution | Bitrate (SD) | Bitrate (HD) |
|---|---|---|---|
| Auto | Device-dependent | Variable | Variable |
| 480p | 854×480 | 1.5 Mbps | — |
| 720p | 1280×720 | 3 Mbps | 4 Mbps |
| 1080p | 1920×1080 | 5 Mbps | 8 Mbps |
| 4K | 3840×2160 | 15 Mbps | 20 Mbps |
| Original | Source resolution | Source | Source |
Quality API
GET /api/v1/media/{id}/playback?quality=720pPer-Track Quality Override
Individual tracks can have different quality settings:
{
"tracks": [
{
"id": "video-720p",
"type": "video",
"quality": "720p",
"codec": "h264",
"bitrate": 3000000
},
{
"id": "video-1080p",
"type": "video",
"quality": "1080p",
"codec": "h264",
"bitrate": 8000000
}
]
}ABR (Adaptive Bitrate)
Auto quality uses adaptive bitrate streaming:
- Client measures available bandwidth
- Requests appropriate quality segment
- Switches quality without interruption
- Buffer target: 30 seconds
Audio Track Selection
Track Properties
| Property | Description | Example |
|---|---|---|
language | ISO 639-1 code | en, es, fr |
codec | Audio codec | aac, mp3, opus |
channels | Channel layout | 2.0, 5.1, 7.1 |
bitrate | Audio bitrate | 128, 256, 320 kbps |
Channel Layouts
| Layout | Speakers | Use Case |
|---|---|---|
2.0 | Stereo | Most content |
5.1 | Front L/R, Center, Surround L/R, Sub | Movies |
7.1 | + Rear L/R | Premium content |
Audio Selection UI
┌─────────────────────────────┐
│ Audio Track [×] │
├─────────────────────────────┤
│ ○ English (AAC 5.1) [★] │
│ ○ Spanish (AAC 2.0) │
│ ○ French (AAC 2.0) │
│ ○ Commentary (AAC 2.0) │
├─────────────────────────────┤
│ Current: English (AAC 5.1) │
└─────────────────────────────┘API Usage
GET /api/v1/media/{id}/playback?audio=es
POST /api/v1/media/{id}/playback/audio
{ "trackId": "audio-es-001" }Multi-Audio HLS
HLS Master Playlist
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720
720p.m3u8
# Audio groups
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",URI="audio-en.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Spanish",LANGUAGE="es",URI="audio-es.m3u8"Audio-Only HLS Playlists
For audio-only streams (music, podcasts):
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-TYPE:AUDIO
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.0
segment0.aac
#EXTINF:10.0
segment1.aac
#EXT-X-ENDLISTSubtitle Track Selection
Subtitle Types
| Type | Source | Format |
|---|---|---|
| Embedded | Video container | SRT, ASS, SSA in container |
| Sideloaded | External file | SRT, VTT, ASS |
| Closed (CC) | Broadcast | CEA-608/708 |
| HEVC SEI | Video stream | Burned-in |
Subtitle Selection UI
┌─────────────────────────────┐
│ Subtitles / CC [×] │
├─────────────────────────────┤
│ [✓] Off │
│ [ ] English (SRT) │
│ [ ] Spanish (SRT) │
│ [ ] French (VTT) │
│ [ ] English CC (CEA-608) │
│ [ ] English (Burned-in) │
├─────────────────────────────┤
│ Text Size: [●●●○○] Medium │
│ Text Color: [White] │
│ Background: [Semi] │
└─────────────────────────────┘API Usage
GET /api/v1/media/{id}/playback?subtitles=en
POST /api/v1/media/{id}/playback/subtitles
{ "trackId": "sub-en-001", "mode": "external" }Subtitle Modes
| Mode | Behavior |
|---|---|
off | No subtitles |
external | Display from sideloaded file |
burned | Use baked-in subtitles from video |
cc | Use closed captions from stream |
Picture-in-Picture (PiP)
Browser Support
| Browser | Version | Notes |
|---|---|---|
| Chrome | 71+ | Full support |
| Firefox | 79+ | Full support |
| Safari | 13+ | macOS Safari only |
| Edge | 79+ | Chromium-based |
PiP API
// Enter PiP
const video = document.querySelector('video');
try {
await video.requestPictureInPicture();
} catch (err) {
console.error('PiP failed:', err);
}
// Exit PiP
document.exitPictureInPicture();
// Listen for events
video.addEventListener('enterpictureinpicture', (e) => {
console.log('Entered PiP:', e.pictureInPictureWindow);
});
video.addEventListener('leavepictureinpicture', () => {
console.log('Left PiP');
});PiP Controls
When in PiP mode:
- Play/Pause — Toggle playback
- Skip — Seek forward/back 10 seconds
- Next/Previous — Skip to next/previous track (in playlists)
- Close — Exit PiP and return to main player
Limitations
- One PiP window per browser context
- DRM content may not support PiP
- Audio continues when PiP is minimized
- Touch devices may have limited PiP support
Mobile Considerations
- iOS: PiP requires
PictureInPicturecapability in entitlements - Android: PiP works in Chrome for Android with proper manifest
Complete Playback Session Example
GET /api/v1/media/{id}/playback{
"sessionId": "sess_abc123",
"manifest": "/stream/{id}/master.m3u8",
"quality": "auto",
"audio": {
"tracks": [
{ "id": "en-aac-51", "language": "en", "codec": "aac", "channels": "5.1", "default": true },
{ "id": "es-aac-20", "language": "es", "codec": "aac", "channels": "2.0", "default": false }
],
"selected": "en-aac-51"
},
"subtitles": {
"tracks": [
{ "id": "off", "label": "Off", "type": "none" },
{ "id": "en-srt", "language": "en", "codec": "srt", "type": "external" }
],
"selected": "off"
},
"trickplay": {
"enabled": true,
"spriteUrl": "/stream/{id}/trickplay.jpg",
"thumbWidth": 360,
"thumbHeight": 90
}
}Client Capability Negotiation
A client can tell the server which codecs it is able to decode by sending an X-Phlix-Client-Capabilities request header on playback-info requests. The value is a JSON codec-support map, for example:
X-Phlix-Client-Capabilities: {"eac3":false,"aac":true}When the header is present, the server's direct_play verdict reflects whether the client can decode the item's (first/default) audio codec — a client that declares it cannot decode e.g. E-AC-3 is steered to transcode instead of direct play, avoiding a "video plays but audio is silent" result. When the header is absent, empty, or malformed, direct_play keeps its previous always-true behavior, so existing clients are unaffected.
Loudness Normalization
The server can apply EBU R128 loudness normalization (loudnorm) to transcoded audio so volume is consistent across titles. It is disabled by default and enabled by an operator in config/ffmpeg.php (loudness.enabled = true, with I/LRA/TP targets). See Config files → Loudness normalization.
Because normalization is an audio filter, it applies only to re-encoded audio. Rungs that copy the source audio stream (the original variant) and direct-play sessions are not normalized — a copied stream is never decoded, so no filter can be applied to it.
Browser HLS Compatibility
Web playback uses hls.js with Media Source Extensions (MSE), which is stricter than a native player. For a transcoded stream to play in a normal browser, three things must line up:
- 8-bit H.264 video. 10-bit ("High 10") output cannot be decoded by any browser; Phlix forces 8-bit (
yuv420p) on all software transcode paths. - Stereo AAC audio. Re-encoded HLS audio is downmixed to stereo (
-ac 2). Surround layouts — in particular AC-3 5.1(side) sources — otherwise encode to AAC with a PCE andchannel_configuration=0in the ADTS header, which hls.js cannot parse: it fails to build the audio SourceBuffer and the entire player load errors even when the video is valid. Direct-play / stream-copy (original) rungs keep the source layout untouched. - CSP
blob:allowances. The server's Content-Security-Policy allowsmedia-src 'self' blob:andworker-src 'self' blob:so hls.js can attach its MSEblob:object URL and spawn its transmux Web Worker. If these are stripped (e.g. by a reverse proxy rewriting the CSP), the browser rejects playback withMEDIA_ELEMENT_ERROR: Media load rejected by URL safety check.
See Also
- Stream Quality / ABR — Technical ABR details
- Streaming Protocols — HLS/DASH implementation
- Subtitle Processing — Subtitle extraction and conversion