Skip to content

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 Original stream-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 ​

FeatureHLSDASH
Developed byAppleDASH-IF
Manifest format.m3u8 playlist.mpd XML
Segment format.ts (MPEG-TS).m4s (MPEG-4)
Browser supportNative Safari, limitedNative support via MSE
Codec supportH.264/AACH.264/AAC, H.265/AAC
Low-latency modeHLS v4DASH-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 .ts container 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.m3u8

DASH MPD (Media Presentation Description) ​

xml
<?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) ​

javascript
// Using dash.js
const player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector('#video'), manifestUrl, true);

JavaScript Example (HLS) ​

javascript
// Using hls.js
const hls = new Hls();
hls.loadSource(playlistUrl);
hls.attachMedia(document.querySelector('#video'));

Automatic Selection Strategy ​

  1. Detect browser capabilities - Check for MediaSource Extensions support
  2. Platform detection - Prioritize HLS on Safari/iOS, DASH elsewhere
  3. Use DASH-IF guidelines for cross-platform applications
  4. 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):

EndpointProtocolDescription
GET /hls/{jobId}/playlistHLSJSON { playlist_url } pointer
GET /hls/{jobId}/{file}HLSmaster.m3u8, media_N.m3u8, init-N.m4s, chunk-*.m4s
GET /dash/{jobId}/manifestDASHJSON { manifest_url } pointer
GET /dash/{jobId}/{file}DASHmanifest.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 writes manifest.mpd (DASH), master.m3u8 + media_N.m3u8 (HLS v7), and shared init-N.m4s / chunk-N-NNNNN.m4s segments. There is no second encode and no duplicate storage — the same .m4s segments are served under both the /hls and /dash prefixes. The web player uses HLS via hls.js; DASH clients use manifest.mpd.

This is DASH's actual pipeline today. For HLS, master.m3u8/media_N.m3u8 are now multi-variant (one media_v{renditionId}.m3u8 per ABR rung, segments named seg-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:

  1. Start — POST /api/v1/media/{id}/transcode?profile=web (or the resolved X-Phlix-Device-Type profile). Returns a job_id, master_url (HLS), dash_url (DASH, still CMAF-based per above), and — for HLS — a variants[] 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.
  2. Poll — GET /api/v1/transcode/{jobId}/status until playlist_ready (master.m3u8 exists on disk). Completion/failure is detected from .complete / .failed markers FFmpeg's wrapper writes on exit, so readiness survives worker reloads.
  3. Play — point hls.js (native HLS on Safari/iOS) at master_url, or a DASH player at dash_url. For HLS, master_url now resolves to a multi-variant master; hls.js (or native HLS) performs ABR across its rungs automatically.

Getting the Correct Manifest URL ​

php
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) ​

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) ​

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 window

Further Reading ​

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 ​

FeatureDescription
FormatDASH-IF / HLS spec-compliant "BIF" (Bitmap Image Format)
Grid layout8Ɨ4 (32 thumbnails per grid image, configurable)
Thumbnail size160Ɨ90 pixels (configurable)
Interval10 seconds between thumbnails (configurable)
Image formatJPEG or PNG with quality settings

How It Works ​

  1. Generation — After transcoding completes, TrickplayGenerator extracts frames at fixed intervals using FFmpeg batch extraction
  2. Grid Assembly — Frames are assembled into grid images using FFmpeg's tile filter (e.g., tile=8x4:margin=2:padding=3)
  3. Index Generation — A BIF index XML maps each thumbnail index to its time position and byte offset in the grid file
  4. Serving — TrickplayController serves grid images and the index XML with correct Content-Type headers

BIF Index Format ​

xml
<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 XML

Routes ​

EndpointDescription
GET /trickplay/{jobId}/thumb-{index}.jpgThumbnail grid image
GET /trickplay/{jobId}/index.xmlBIF index XML

Configuration ​

php
// 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:

php
// 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, format
  • TrickplayResult — Result container with job ID, image file metadata, index XML path
  • TrickplayGenerator — Extracts frames, assembles grids, generates BIF index XML
  • TrickplayController — HTTP handler for serving thumbnails and index with byte-range support

BSD-3-Clause