Files
XC_VM/docs/en/development/streaming-subsystem.md
T

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

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:

  1. Load error codes, handler, paths, config, binaries.
  2. Flood protection (HTTP only): check for FLOOD_TMP_PATH . 'block_' . $rIP.
  3. Load settings from file cache (CACHE_TMP_PATH . 'settings').
  4. Host verification (HTTP only): validate against allowed_domains.
  5. Initialize logger.
  6. Fail-closed gate: return 404 if settings missing (except /status).
  7. 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:

  1. Decrypt token using live_streaming_pass.
  2. Check expiration: $rTokenData['expires'] < time() - $rServers[SERVER_ID]['time_offset'].
  3. 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):

  1. Decrypt token via StreamAuthMiddleware::decryptToken().
  2. Resolve server/proxy: StreamAuth::checkAccess() + ProxySelector::availableProxy().
  3. Enforce connection limits: StreamAuth::validateConnections().
  4. Create connection record: ConnectionTracker::createConnection().
  5. Deliver content:
    • M3U8: HLSGenerator::generateHLS() → client fetches segments via segment.php.
    • TS: Loop segments using AsyncFileOperations::awaitFileExists().
  6. Every 5 minutes: refresh settings, update hls_last_read, verify process alive.
  7. 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 user
    • STREAM#{stream_id} — connections for stream
    • SERVER#{server_id} — connections on server

MySQL (fallback):

  • Table: lines_live with 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 user
  • ip_subnet_match — match by /24 subnet instead of exact IP
  • restrict_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:

  1. Close connection record in lines_live or Redis.
  2. Delete tmp files at CONS_TMP_PATH . $uuid.
  3. 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:

  1. Get available servers: server_online == true, server_type == 0, online_clients < total_clients.
  2. Sort by capacity (ascending) — least loaded first.
  3. Apply GeoIP routing (if enable_geoip == 1):
    • Exact country match → select immediately.
    • geoip_type == 'strict' → exclude non-matching.
    • Otherwise → assign priority weight.
  4. Apply ISP routing (if enable_isp == 1): same logic as GeoIP.
  5. 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:

  1. Generate AES-128 key token from IP + StreamID + salt.
  2. Replace IV with content from STREAMS_PATH . $rStreamID . '_.iv'.
  3. Encrypt each segment reference: IP/StreamID/Segment/UUID/SERVER_ID/VideoCodec/OnDemand.
  4. 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/

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