Streaming Protocols ā
Phlix Media Server supports two adaptive streaming protocols: HLS (HTTP Live Streaming) and DASH (Dynamic Adaptive Streaming over HTTP). Both protocols enable adaptive bitrate streaming, allowing clients to select appropriate quality levels based on network conditions and device capabilities.
On-demand HLS is now multi-variant. The description below (one shared CMAF encode driving both a single-variant HLS master and DASH) is accurate for DASH and for the historical/CMAF code paths, but on-demand HLS playback was rebuilt to emit a real multi-variant ABR ladder (240pā2160p clamped to source, plus an
Originalstream-copy/top-rung variant), with segments generated per-variant on demand as plain.ts(not the shared CMAF.m4s). See Stream Quality / ABR for the current, authoritative description of that pipeline ā including the real segment/playlist naming, the per-variant dedup/cap/cache-sweep behavior, the hub relay's streaming pass-through, and client (web/native) ABR support. DASH is unaffected by that work and still uses the single-CMAF-job pipeline described in this page.
Overview ā
| Feature | HLS | DASH |
|---|---|---|
| Developed by | Apple | DASH-IF |
| Manifest format | .m3u8 playlist | .mpd XML |
| Segment format | .ts (MPEG-TS) | .m4s (MPEG-4) |
| Browser support | Native Safari, limited | Native support via MSE |
| Codec support | H.264/AAC | H.264/AAC, H.265/AAC |
| Low-latency mode | HLS v4 | DASH-CMAF |
When to Use Each Protocol ā
HLS (HTTP Live Streaming) ā
Best for:
- Apple ecosystem (iOS, Safari, tvOS)
- Broad compatibility with legacy devices
- Simpler implementation when targeting primarily Apple devices
- Live streaming with moderate latency requirements
Characteristics:
- Master playlist (
playlist.m3u8) lists all quality variants - Variant playlists (
stream_N.m3u8) list segments for each quality - Segments are
.tscontainer format - Native support in Safari; requires MediaSource Extensions for other browsers
DASH (Dynamic Adaptive Streaming over HTTP) ā
Best for:
- Cross-platform web applications using MSE
- Lower latency requirements (DASH-CMAF mode)
- Complex adaptive scenarios with multiple subtitle/audio tracks
- Standards-compliant implementations
Characteristics:
- MPD (Media Presentation Description) is an XML manifest
- Uses SegmentTemplate for efficient segment addressing
- Segments are
.m4s(MPEG-4 container) format - Excellent browser support via MediaSource Extensions
Manifest Structure ā
HLS Master Playlist ā
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,NAME="1080p"
stream_0.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720,NAME="720p"
stream_1.m3u8DASH MPD (Media Presentation Description) ā
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
profiles="urn:mpeg:dash:profile:isoff-live:2011"
type="static"
minBufferTime="PT2S">
<Period id="1" duration="PT0H1M0S">
<AdaptationSet id="1" contentType="video" bandwidth="5000000">
<Representation id="video-1080" codecs="avc1.64001f"
width="1920" height="1080" bandwidth="5000000">
<SegmentTemplate media="$RepresentationID$_$Number%05d$.m4s"
initialization="$RepresentationID$_init.m4s"
startNumber="1" duration="6000"/>
</Representation>
</AdaptationSet>
<AdaptationSet id="2" contentType="audio" bandwidth="128000">
<Representation id="audio-en" codecs="mp4a.40.2"
audioSamplingRate="48000" bandwidth="128000">
<SegmentTemplate media="$RepresentationID$_$Number%05d$.m4s"
initialization="$RepresentationID$_init.m4s"
startNumber="1" duration="6000"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>Client-Side Selection ā
JavaScript Example (DASH) ā
// Using dash.js
const player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector('#video'), manifestUrl, true);JavaScript Example (HLS) ā
// Using hls.js
const hls = new Hls();
hls.loadSource(playlistUrl);
hls.attachMedia(document.querySelector('#video'));Automatic Selection Strategy ā
- Detect browser capabilities - Check for MediaSource Extensions support
- Platform detection - Prioritize HLS on Safari/iOS, DASH elsewhere
- Use DASH-IF guidelines for cross-platform applications
- Consider latency requirements - DASH-CMAF for low-latency
Server-Side Implementation ā
Class Architecture ā
TranscodeManager ā one CMAF encode per job (FFmpeg dash muxer + -hls_playlist)
writes manifest.mpd + master.m3u8 + media_N.m3u8 + shared *.m4s
HlsController āā
DashController āā“ā serve the job dir's files verbatim (TranscodeFileServer trait)The transcode pipeline writes one job directory holding both the DASH manifest and the HLS playlists plus the shared fMP4 segments. The controllers serve those files directly ā playlists/manifest reference segments by relative filename, so no rewriting is needed and the same .m4s segments back both protocols.
Routes ā
Both protocols are produced by one CMAF (fMP4) encode into a single job directory and cross-reference their segments by relative filename, so each protocol is served by a generic per-job file handler (plus a JSON info route):
| Endpoint | Protocol | Description |
|---|---|---|
GET /hls/{jobId}/playlist | HLS | JSON { playlist_url } pointer |
GET /hls/{jobId}/{file} | HLS | master.m3u8, media_N.m3u8, init-N.m4s, chunk-*.m4s |
GET /dash/{jobId}/manifest | DASH | JSON { manifest_url } pointer |
GET /dash/{jobId}/{file} | DASH | manifest.mpd + the shared init-N.m4s / chunk-*.m4s |
One encode, both protocols. The transcode pipeline runs FFmpeg's DASH muxer with
-hls_playlist 1, so a single CMAF/fMP4 pass writesmanifest.mpd(DASH),master.m3u8+media_N.m3u8(HLS v7), and sharedinit-N.m4s/chunk-N-NNNNN.m4ssegments. There is no second encode and no duplicate storage ā the same.m4ssegments are served under both the/hlsand/dashprefixes. The web player uses HLS via hls.js; DASH clients usemanifest.mpd.This is DASH's actual pipeline today. For HLS,
master.m3u8/media_N.m3u8are now multi-variant (onemedia_v{renditionId}.m3u8per ABR rung, segments namedseg-v{renditionId}-NNNNN.ts) and segments are plain.ts, not shared.m4sā see Stream Quality / ABR for the current route/filename reference.
On-Demand Transcode Flow ā
When a file can't be direct-played, the client drives this flow:
- Start ā
POST /api/v1/media/{id}/transcode?profile=web(or the resolvedX-Phlix-Device-Typeprofile). Returns ajob_id,master_url(HLS),dash_url(DASH, still CMAF-based per above), and ā for HLS ā avariants[]quality-ladder array (see Stream Quality / ABR). Idempotent ā a still-valid job for the same item + profile is reused. Segments themselves are generated on demand as each is first requested, not all up front; see Stream Quality / ABR for the current per-variant copy-vs-encode decision, dedup, and cap behavior. - Poll ā
GET /api/v1/transcode/{jobId}/statusuntilplaylist_ready(master.m3u8exists on disk). Completion/failure is detected from.complete/.failedmarkers FFmpeg's wrapper writes on exit, so readiness survives worker reloads. - Play ā point hls.js (native HLS on Safari/iOS) at
master_url, or a DASH player atdash_url. For HLS,master_urlnow resolves to a multi-variant master; hls.js (or native HLS) performs ABR across its rungs automatically.
Getting the Correct Manifest URL ā
use Phlix\Media\Streaming\StreamManager;
// $protocol is 'hls' or 'dash'
$manifestUrl = $streamManager->getManifestUrl($jobId, $protocol);Segment Format Details ā
MPEG-2 Transport Stream (.ts) ā
- Container: MPEG-2 TS (older, wider support)
- Video codec: H.264/AVC
- Audio codec: AAC-LC
- Typical segment duration: 6-10 seconds
MPEG-4 Fragmented (.m4s) ā
- Container: ISO Base Media File Format (MPEG-4)
- Video codec: H.264/AVC or H.265/HEVC
- Audio codec: AAC-LC
- Typical segment duration: 2-6 seconds for low-latency
- Supports CMAF (Common Media Application Format) for ultra-low latency
Configuration ā
FFmpeg (config/ffmpeg.php) ā
'dash' => [
'enabled' => true,
'segment_dir' => '/var/segments',
'default_codecs' => [
'video' => 'avc1.64001f', // H.264 High Profile Level 3.1
'audio' => 'mp4a.40.2', // AAC-LC
],
],DASH-Specific (config/dash.php) ā
'enabled' => true,
'manifest_refresh_seconds' => 30,
'min_buffer_time' => 'PT2S', // 2 seconds
'min_buffer_time_live' => 'PT10S', // 10 seconds for live
'time_shift_buffer_depth' => 'PT30M', // 30 minutes DVR windowFurther Reading ā
- HLS RFC 8216
- DASH-IF Implementation Guidelines
- MediaSource Extensions API
- dash.js Reference
- hls.js Reference
Trickplay / Thumbnail Seek ā
Trickplay (also called "scrub preview" or "thumbnail seek") allows users to preview a video by hovering over the progress bar and seeing thumbnail images at regular intervals.
Overview ā
| Feature | Description |
|---|---|
| Format | DASH-IF / HLS spec-compliant "BIF" (Bitmap Image Format) |
| Grid layout | 8Ć4 (32 thumbnails per grid image, configurable) |
| Thumbnail size | 160Ć90 pixels (configurable) |
| Interval | 10 seconds between thumbnails (configurable) |
| Image format | JPEG or PNG with quality settings |
How It Works ā
- Generation ā After transcoding completes,
TrickplayGeneratorextracts frames at fixed intervals using FFmpeg batch extraction - Grid Assembly ā Frames are assembled into grid images using FFmpeg's
tilefilter (e.g.,tile=8x4:margin=2:padding=3) - Index Generation ā A BIF index XML maps each thumbnail index to its time position and byte offset in the grid file
- Serving ā
TrickplayControllerserves grid images and the index XML with correctContent-Typeheaders
BIF Index Format ā
<ThumbList>
<Thumbs>
<Thumb index="0" time="0" offset="0" length="4096"/>
<Thumb index="1" time="10" offset="4096" length="4096"/>
<Thumb index="2" time="20" offset="8192" length="4096"/>
...
</Thumbs>
</ThumbList>The offset and length attributes enable byte-range requests, allowing clients to download only the portion of the grid image needed for a single thumbnail.
Server-Side Implementation ā
StreamManager
āāā HlsStreamer ā generates .m3u8 playlists + .ts segments
āāā DashStreamer ā generates .mpd manifests + .m4s segments
āāā TrickplayGenerator ā generates BIF thumbnail grids + index XMLRoutes ā
| Endpoint | Description |
|---|---|
GET /trickplay/{jobId}/thumb-{index}.jpg | Thumbnail grid image |
GET /trickplay/{jobId}/index.xml | BIF index XML |
Configuration ā
// config/trickplay.php
[
'enabled' => true,
'interval_seconds' => 10,
'grid_columns' => 8,
'grid_rows' => 4,
'thumb_width' => 160,
'thumb_height' => 90,
'image_format' => 'jpeg',
'jpeg_quality' => 72,
'storage_dir' => '/var/trickplay',
]FFmpeg Extension ā
FfmpegRunner::generateThumbnail() now supports batch extraction:
// Single thumbnail
$runner->generateThumbnail('/video.mkv', '/thumb.jpg', 30);
// Multiple thumbnails (batch)
$runner->generateThumbnailBatch('/video.mkv', [0, 10, 20, 30], '/output/dir');Class Architecture ā
TrickplayConfigā Value object with grid dimensions, thumbnail size, interval, formatTrickplayResultā Result container with job ID, image file metadata, index XML pathTrickplayGeneratorā Extracts frames, assembles grids, generates BIF index XMLTrickplayControllerā HTTP handler for serving thumbnails and index with byte-range support