12 KiB
Streaming Subsystem
The streaming subsystem handles live, VOD, and timeshift delivery. It is the hot path (~10K-100K req/min, <50ms p99) and uses a separate lightweight bootstrap to avoid loading the full admin stack.
Navigation
- Request Flow
- Directory Layout
- Bootstrap Pipeline
- Token Authentication
- Stream Delivery
- Connection Management
- Load Balancing
- Rate Limiting and Flood Protection
- HLS Encryption
- Performance
- Related Files
Request Flow
client request
|
nginx rewrite (/auth/{token} -> /stream/live.php?token={token})
|
StreamingRequestBootstrap::init()
|
StreamingBootstrap::bootstrap()
|
LegacyInitializer::initStreaming()
|
endpoint logic (live.php / vod.php / timeshift.php)
|
ShutdownHandler::handle()
nginx rewrites all streaming URLs to PHP entry points under www/stream/:
| URL pattern | Entry point | Purpose |
|---|---|---|
/auth/{token} |
live.php |
Live stream delivery |
/vauth/{token} |
vod.php |
Video-on-demand delivery |
/tsauth/{token} |
timeshift.php |
Archive/timeshift playback |
/hls/{token} |
segment.php |
HLS segment delivery |
/key/{token} |
key.php |
AES-128 encryption key |
/subauth/{token} |
subtitle.php |
Subtitle delivery |
Directory Layout
src/streaming/
├── StreamingBootstrap.php
├── AsyncFileOperations.php
├── TimeshiftClient.php
├── Auth/
│ ├── StreamAuth.php
│ └── StreamAuthMiddleware.php
├── Balancer/
│ └── ProxySelector.php
├── Codec/
│ ├── FFmpegCommand.php
│ ├── FfmpegPaths.php
│ └── FFprobeRunner.php
├── Delivery/
│ ├── HLSGenerator.php
│ ├── OffAirHandler.php
│ ├── SegmentReader.php
│ ├── SignalSender.php
│ └── StreamRedirector.php
├── Health/
│ └── ProcessChecker.php
├── Lifecycle/
│ └── ShutdownHandler.php
└── Protection/
└── ConnectionLimiter.php
src/www/stream/
├── init.php # Legacy bootstrap shim (deprecated)
├── auth.php # Token validation gateway
├── live.php # Live streaming delivery
├── vod.php # VOD delivery
├── timeshift.php # Archive/timeshift playback
├── segment.php # HLS segment delivery
├── key.php # Encryption key delivery
├── subtitle.php # Subtitle delivery
├── thumb.php # Thumbnail delivery
└── rtmp.php # RTMP publishing endpoint
Bootstrap Pipeline
1. StreamingRequestBootstrap::init()
File: src/infrastructure/bootstrap/StreamingRequestBootstrap.php
Actions in order:
- Load error codes, handler, paths, config, binaries.
- Flood protection (HTTP only): check for
FLOOD_TMP_PATH . 'block_' . $rIP. - Load settings from file cache (
CACHE_TMP_PATH . 'settings'). - Host verification (HTTP only): validate against
allowed_domains. - Initialize logger.
- Fail-closed gate: return 404 if settings missing (except
/status). - Call
StreamingBootstrap::bootstrap().
2. StreamingBootstrap::bootstrap()
File: src/streaming/StreamingBootstrap.php
public static function bootstrap($rFilename, $rSettings)
Classifies the endpoint:
- Probe endpoints:
probe,player_api(light load) - Default endpoints:
live,thumb,subtitle,timeshift,vod,status - Privileged endpoints:
rtmp,portal
Loads AsyncFileOperations.php and DatabaseHandler.php, stores settings in $GLOBALS['rSettings'] and access data in $GLOBALS['rAccess'], then calls LegacyInitializer::initStreaming().
Returns the $db database instance (used by legacy entry points).
3. LegacyInitializer::initStreaming()
File: src/core/Init/LegacyInitializer.php
Populates global variables from cache:
$GLOBALS['rSettings'],$GLOBALS['rServers'],$GLOBALS['rBouquets']$GLOBALS['rBlockedUA'],$GLOBALS['rBlockedISP'],$GLOBALS['rBlockedIPs']$GLOBALS['rAllowedIPs'],$GLOBALS['rProxies'],$GLOBALS['rSegmentSettings']$GLOBALS['rFFMPEG_CPU'],$GLOBALS['rFFMPEG_GPU'],$GLOBALS['rFFPROBE']
Connects to database/Redis based on $rSettings['redis_handler'].
Important: The streaming path reads exclusively from file cache. It does not query the database for settings or user lookups during normal operation.
Token Authentication
File: src/streaming/Auth/StreamAuthMiddleware.php
StreamAuthMiddleware::decryptToken($rToken, $rSettings, $rServers, $rIP): array
Token contents:
| Field | Description |
|---|---|
username |
Line username |
password |
Line password |
stream_id |
Target stream ID |
expires |
Token expiration timestamp |
channel_info |
Stream metadata (on_demand, proxy, pid) |
user_info |
User permissions (max_connections, is_restreamer) |
country_code |
GeoIP country code |
video_codec |
Requested video codec |
Validation:
- Decrypt token using
live_streaming_pass. - Check expiration:
$rTokenData['expires'] < time() - $rServers[SERVER_ID]['time_offset']. - Return parsed token data or trigger error.
Response headers are set via StreamAuthMiddleware::sendStreamHeaders():
Access-Control-Allow-Origin: *
X-XSS-Protection: 0
X-Content-Type-Options: nosniff
Alt-Svc: h3-29, h3-T051, h3-Q050 (HTTP/3 hints)
Stream Delivery
Live (live.php)
Main delivery endpoint (~650 lines):
- Decrypt token via
StreamAuthMiddleware::decryptToken(). - Resolve server/proxy:
StreamAuth::checkAccess()+ProxySelector::availableProxy(). - Enforce connection limits:
StreamAuth::validateConnections(). - Create connection record:
ConnectionTracker::createConnection(). - Deliver content:
- M3U8:
HLSGenerator::generateHLS()→ client fetches segments viasegment.php. - TS: Loop segments using
AsyncFileOperations::awaitFileExists().
- M3U8:
- Every 5 minutes: refresh settings, update
hls_last_read, verify process alive. - On exit:
ShutdownHandler::handle()→ close connection record.
VOD (vod.php)
Same auth flow as live. Reads from VOD_PATH instead of STREAMS_PATH.
Timeshift (timeshift.php)
Serves archived segments. Uses TimeshiftClient for archive file resolution.
Connection Management
ConnectionTracker
Manages live connection state. Backend is selected by $rSettings['redis_handler']:
Redis (preferred for scale):
- Connections stored in sorted sets:
LINE#{identity}— connections for userSTREAM#{stream_id}— connections for streamSERVER#{server_id}— connections on server
MySQL (fallback):
- Table:
lines_livewith fields:activity_id,user_id,stream_id,server_id,uuid,pid,hls_end
Key methods:
ConnectionTracker::createConnection($data)
ConnectionTracker::updateConnection($connection, $changes, 'open'|'close')
ConnectionTracker::getConnection($uuid)
ConnectionTracker::getLineConnections($user_id)
ConnectionTracker::getCapacity()
ConnectionLimiter
File: src/streaming/Protection/ConnectionLimiter.php
Enforces per-user connection limits when max_connections is exceeded:
| Priority | Criteria | Action |
|---|---|---|
| 2 | Same IP + same User-Agent | Kill first |
| 1 | Same IP (any UA) | Kill next |
| 0 | Any connection | Kill as fallback |
Settings:
disallow_2nd_ip_con— enforce single IP per userip_subnet_match— match by /24 subnet instead of exact IPrestrict_same_ip— return error on IP mismatch instead of killing
ShutdownHandler
File: src/streaming/Lifecycle/ShutdownHandler.php
Registered via register_shutdown_function(). On PHP process exit:
- Close connection record in
lines_liveor Redis. - Delete tmp files at
CONS_TMP_PATH . $uuid. - Remove on-demand stream from queue if applicable.
Load Balancing
Server Selection (StreamAuth::checkAccess)
File: src/streaming/Auth/StreamAuth.php
public static function checkAccess($rUserInfo, $rUserIP, $rCountryCode, $rUserISP = ''): int|false
Algorithm:
- Get available servers:
server_online == true,server_type == 0,online_clients < total_clients. - Sort by capacity (ascending) — least loaded first.
- Apply GeoIP routing (if
enable_geoip == 1):- Exact country match → select immediately.
geoip_type == 'strict'→ exclude non-matching.- Otherwise → assign priority weight.
- Apply ISP routing (if
enable_isp == 1): same logic as GeoIP. - Return server with lowest capacity from highest-priority group.
Proxy Selection (ProxySelector::availableProxy)
File: src/streaming/Balancer/ProxySelector.php
public static function availableProxy($rProxies, $rCountryCode, $rUserISP = ''): int|null
Same algorithm as StreamAuth::checkAccess() but applied to proxy server list.
Rate Limiting and Flood Protection
Three layers:
1. nginx (connection level)
limit_req_zone $binary_remote_addr zone=one:30m rate=20r/s;
limit_req zone=one burst=8;
20 requests/second per IP with 8-request burst. 30-minute sliding window.
2. StreamingRequestBootstrap (IP block)
if (file_exists(FLOOD_TMP_PATH . 'block_' . $rIP)) {
http_response_code(403);
exit();
}
File-based IP blocking. Block files are created by upstream flood detection logic.
3. ConnectionLimiter (per-user)
Enforced after token validation. Limits concurrent streams per user based on max_connections.
HLS Encryption
File: src/streaming/Delivery/HLSGenerator.php
public static function generateHLS($rSettings, $rM3U8, $rUsername, $rPassword,
$rStreamID, $rUUID, $rIP, ...): string|false
When encrypt_hls == true:
- Generate AES-128 key token from IP + StreamID + salt.
- Replace IV with content from
STREAMS_PATH . $rStreamID . '_.iv'. - Encrypt each segment reference:
IP/StreamID/Segment/UUID/SERVER_ID/VideoCodec/OnDemand. - Replace segment names with
/hls/{encrypted_token}.
Key delivery happens via key.php using the same token mechanism.
Performance
Key design decisions for throughput and latency:
| Feature | Mechanism |
|---|---|
| Non-blocking file wait | AsyncFileOperations::awaitFileExists() uses inotify (Linux) or optimized polling |
| Zero-CPU sleep | time_nanosleep() via AsyncFileOperations::efficientSleep() |
| nginx buffering | 128 x 32KB buffers per request |
| Connection pooling | Redis (preferred) or persistent MySQL |
| Cache-only reads | Settings and user data read from file cache, no DB queries |
| Early exit | Monitors connection_status() every 5 seconds to detect client disconnect |
| Settings refresh | Every 5 minutes (300s) to catch config changes without restart |
File System Paths
STREAMS_PATH = /home/xc_vm/www/stream/
CONS_TMP_PATH = /home/xc_vm/tmp/
CACHE_TMP_PATH = /home/xc_vm/tmp/cache/
FLOOD_TMP_PATH = /home/xc_vm/tmp/flood/
SIGNALS_PATH = /home/xc_vm/tmp/signals/
VIDEO_PATH = /home/xc_vm/www/video/
ARCHIVE_PATH = /home/xc_vm/www/archive/
VOD_PATH = /home/xc_vm/www/vod/
Related Files
| File | Purpose |
|---|---|
src/streaming/StreamingBootstrap.php |
core streaming bootstrap |
src/infrastructure/bootstrap/StreamingRequestBootstrap.php |
HTTP-level init |
src/streaming/Auth/StreamAuth.php |
server selection and connection validation |
src/streaming/Auth/StreamAuthMiddleware.php |
token decryption and response headers |
src/streaming/Balancer/ProxySelector.php |
proxy server selection |
src/streaming/Protection/ConnectionLimiter.php |
per-user connection limits |
src/streaming/Delivery/HLSGenerator.php |
M3U8 playlist generation |
src/streaming/Delivery/SegmentReader.php |
segment extraction from playlists |
src/streaming/Delivery/StreamRedirector.php |
stream availability and server routing |
src/streaming/AsyncFileOperations.php |
non-blocking filesystem utilities |
src/streaming/Lifecycle/ShutdownHandler.php |
connection cleanup on exit |
src/domain/Stream/ConnectionTracker.php |
connection state in Redis/MySQL |
src/core/Init/LegacyInitializer.php |
global variable setup for streaming |