Files
XC_VM/docs/ru/development/streaming-subsystem.md
T
Divarion_D dbf535bcc5 docs(translate): robust {N} sentinels + possessive handling; regenerate ru
Fix Markdown-mangling artifacts the free web engine (yandex) produced in the
committed docs/ru, and regenerate the whole tree cleanly (0 fallbacks):

- Sentinel format @@N@@ -> {N}. MT engines are trained to preserve curly
  format-string placeholders, so {N} survives code-heavy lines where @@N@@ (and
  ZZZ…ZZZ, which also duplicated its Z) were split/moved — e.g. the stray
  "@0@@" in the FAQ and "load balancerZ" in the README are gone.
- Possessive: a trailing English `'s` is consumed INTO the masked span and
  dropped on restore. Every sentinel format breaks when a bare `'s` sits right
  after it, and Russian has no possessive `'s`.
- Validate + retry: after restore, any leftover brace fragment triggers a retry
  (the engine is non-deterministic); after a few failures the line stays English
  so a broken token is never emitted.
- Glossary += KeyDB, yt-dlp, Ubuntu, iptables, MAGSCAN.

Regenerated docs/ru (37 files, translators/yandex): no residual sentinels,
mkdocs build --strict clean.
2026-08-21 16:31:01 +03:00

28 KiB
Raw Blame History

Подсистема потоковой передачи

Подсистема потоковой передачи обрабатывает доставку в реальном времени, VOD и timeshift. Это быстрый путь (~10-100 тыс. запросов в минуту, <50 мс p99), и он использует отдельный облегченный bootstrap, чтобы избежать загрузки всего стека администратора.


Поток запросов

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 переписывает все URL-адреса потоковой передачи на PHP точки входа в соответствии с www/stream/:

Шаблон URL-адреса Точка входа Цель
/auth/{token} live.php Прямая трансляция
/vauth/{token} vod.php Доставка видео по запросу
/tsauth/{token} timeshift.php Архив/timeshift воспроизведение
/hls/{token} segment.php HLS сегментная доставка
/key/{token} key.php Ключ шифрования AES-128
/subauth/{token} subtitle.php Передача субтитров

Расположение каталога

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
│   └── 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

Конвейер начальной загрузки

1. StreamingRequestBootstrap::init()

Файл: src/Infrastructure/Bootstrap/StreamingRequestBootstrap.php

Действия в порядке:

  1. Загружайте коды ошибок, обработчик, пути, конфигурацию, двоичные файлы.
  2. Защита от наводнений (только HTTP): проверьте наличие FLOOD_TMP_PATH . 'block_' . $rIP.
  3. Загрузите настройки из файлового кэша (CACHE_TMP_PATH . 'settings').
  4. Проверка хоста (только HTTP): проверка на соответствие allowed_domains.
  5. Инициализируйте регистратор.
  6. Аварийно закрытый шлюз: возвращает 404, если настройки отсутствуют (кроме /status).
  7. Вызовите StreamingBootstrap::bootstrap().

2. Потоковый загрузчик::bootstrap()

Файл: src/Streaming/StreamingBootstrap.php

public static function bootstrap($rFilename, $rSettings)

Классифицирует конечную точку:

  • Конечные точки зондирования: probe, player_api (небольшая нагрузка)
  • Конечные точки по умолчанию: live, thumb, subtitle, timeshift, vod, status
  • Привилегированные конечные точки: rtmp, portal

Загружает AsyncFileOperations.php и DatabaseHandler.php, сохраняет настройки в $GLOBALS['rSettings'] и получает доступ к данным в $GLOBALS['rAccess'], затем вызывает LegacyInitializer::initStreaming().

Возвращает экземпляр базы данных $db (используемый устаревшими точками входа).

3. LegacyInitializer::Инициализация потока()

Файл: src/Core/Init/LegacyInitializer.php

Заполняет глобальные переменные из кэша:

  • $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']

Подключается к базе данных/Redis на основе $rSettings['redis_handler'].

Важно: Путь к потоковой передаче считывается исключительно из файлового кэша. При обычной работе программа не запрашивает настройки в базе данных или запросы пользователей.


Аутентификация по токену

Файл: src/Streaming/Auth/StreamAuthMiddleware.php

StreamAuthMiddleware::decryptToken($rToken, $rSettings, $rServers, $rIP): array

Содержимое токена:

Поле Описание
username Имя пользователя строки
password Пароль к строке
stream_id Идентификатор целевого потока
expires Временная метка истечения срока действия токена
channel_info Потоковые метаданные (on_demand, прокси, pid)
user_info Разрешения пользователя (max_connections, is_restreamer)
country_code GeoIP код страны
video_codec Запрашиваемый видеокодек

Утверждение:

  1. Расшифруйте токен, используя live_streaming_pass.
  2. Проверьте истечение срока действия: $rTokenData['expires'] < time() - $rServers[SERVER_ID]['time_offset'].
  3. Возвращает проанализированные данные токена или вызывает ошибку.

Заголовки ответов задаются через 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)

Потоковая доставка

Жить (live.php)

Основная конечная точка доставки (~650 строк):

  1. Расшифруйте токен с помощью StreamAuthMiddleware::decryptToken().
  2. Разрешить использование сервера/прокси-сервера: StreamAuth::checkAccess() + ProxySelector::availableProxy().
  3. Установите ограничения на подключение: StreamAuth::validateConnections().
  4. Создайте запись о подключении: ConnectionTracker::createConnection().
  5. Hand delivery to the xc_fanout daemon (see below): PHP emits an X-Accel-Redirect и завершает байтовый путь — nginx передает байты в потоковом режиме.
    • ТС: X-Accel-Redirect: /xc_fanout/<id>?c=<uuid>&prebuffer=N (nginx перезаписывается в файл демона /live/<id>).
    • HLS: список воспроизведения указывает на выделенные сегменты; segment.php транслируется в прямом эфире сегментирует только через демон (/xc_fanout_hls/<id>_<seq>), иначе 404.
  6. При выходе: ShutdownHandler::handle() → закрыть запись о подключении.

VOD (vod.php)

Тот же процесс аутентификации, что и в live. Считывается из VOD_PATH вместо STREAMS_PATH.

Временной сдвиг (timeshift.php)

Обслуживает архивные сегменты. Использует TimeshiftClient для разрешения архивного файла.

Доставка демона — xc_fanout

Live client delivery (TS and HLS) is daemon-only: PHP authorizes the средство просмотра, а затем полностью покидает байтовый путь, так что средство просмотра больше не закрепляет PHP-FPM работник, отвечающий за жизнедеятельность потока.

  • Fan-out. xc_fanout (a bundled Go daemon) pulls each source once and предоставляет его каждому пользователю через сокет unix с помощью встроенного в оперативную память сегментатора HLS. PHP не соответствует байтовому пути для каждого зрителя; старый цикл поиска и чтения (AsyncFileOperations::awaitFileExists()) и HLSGenerator::generateHLS() сервировочные дорожки были удалены при разделке.
  • Два сокета. Клиентский сокет (ориентированный на nginx) обслуживает /live/<id> и /hls/...; управляющий сокет, предназначенный только для PHP, регистрирует источники (PUT /streams/<id> / /ingest/<id>), отвечает на вопросы о статусе выхода в эфир (GET /streams/<id>, GET /probe/<id>) и предоставляет доступ к телеметрии.
  • Telemetry / reconciliation. fanout_sync polls GET /rates (per-uuid КБИТ/с → lines_divergence) и GET /connections (согласовывает lines_live строк, поскольку PHP не может видеть разъединение в X-Accel).
  • Отключен. Если демон сообщает об отсутствии данных (has_data=false / устаревшие), PHP показывает страницу "не в эфире" вместо того, чтобы позволить зрителю зависнуть.
  • Сохранено на диске HLS только для timeshift / миниатюр / .analyse / MonitorCommand — не для доставки клиенту.

Наложение отправленного сообщения

Действие администратора "Отправить сообщение" отображает текстовый баннер на видео одного зрителя. PHP отправляет его в сокет управления демоном (FanoutClient::sendSignal → POST /signal/<uuid>), и демон применяет ffmpeg drawtext наложение на следующий HLS сегмент этого просмотра (или короткий ~5-секундный фрагмент окно), однократный запуск, максимальное усилие - сигнал никогда не прерывает воспроизведение. Демон должен быть запущенным с помощью ffmpeg, который на самом деле имеет фильтр drawtext, так что service программа запуска выбирает сборку с поддержкой drawtext.


Управление подключениями

Средство отслеживания подключений

Управляет текущим состоянием соединения. Серверная часть выбрана с помощью $rSettings['redis_handler']:

Redis (предпочтительно для масштабирования):

  • Соединения, хранящиеся в отсортированных наборах:
    • LINE#{identity} — подключения для пользователя
    • STREAM#{stream_id} — соединения для потока
    • SERVER#{server_id} — соединения на сервере

MySQL (резервный вариант):

  • Таблица: lines_live с полями: activity_id, user_id, stream_id, server_id, uuid, pid, hls_end

Ключевые методы:

ConnectionTracker::createConnection($data)
ConnectionTracker::updateConnection($connection, $changes, 'open'|'close')
ConnectionTracker::getConnection($uuid)
ConnectionTracker::getLineConnections($user_id)
ConnectionTracker::getCapacity()

Ограничитель подключения

Файл: src/Streaming/Protection/ConnectionLimiter.php

Устанавливает ограничения на подключение для каждого пользователя при превышении значения max_connections:

Приоритет Критерий Действие
2 Тот же IP + тот же пользовательский агент Убей первым
1 Тот же IP-адрес (любой UA) Убей следующего
0 Какая-либо связь Убить в качестве запасного варианта

Настройки:

  • disallow_2nd_ip_con — принудительно использовать один IP-адрес для каждого пользователя
  • ip_subnet_match — соответствует подсети /24 вместо точного IP-адреса
  • restrict_same_ip — возвращает ошибку при несоответствии IP-адресов вместо уничтожения

Устройство для выключения

Файл: src/Streaming/Lifecycle/ShutdownHandler.php

Зарегистрирован с помощью register_shutdown_function(). При завершении процесса PHP:

  1. Закройте запись о соединении в lines_live или Redis.
  2. Удалите tmp-файлы со значением CONS_TMP_PATH . $uuid.
  3. Удалите поток по требованию из очереди, если это применимо.

балансировка нагрузки

Выбор сервера (StreamAuth::checkAccess)

Файл: src/Streaming/Auth/StreamAuth.php

public static function checkAccess($rUserInfo, $rUserIP, $rCountryCode, $rUserISP = ''): int|false

Алгоритм:

  1. Получите доступные серверы: server_online == true, server_type == 0, online_clients < total_clients.
  2. Сортировка по вместимости (по возрастанию) — сначала загружается наименее загруженный.
  3. Применить маршрутизацию GeoIP (если enable_geoip == 1):
    • Точное соответствие стране → выберите немедленно.
    • geoip_type == 'strict' → исключить несоответствия.
    • В противном случае → присвоить приоритетный вес.
  4. Примените маршрутизацию через интернет-провайдера (если enable_isp == 1): та же логика, что и GeoIP.
  5. Верните сервер с наименьшей пропускной способностью из группы с наивысшим приоритетом.

Выбор прокси-сервера (ProxySelector::Доступный прокси)

Файл: src/Streaming/Balancer/ProxySelector.php

public static function availableProxy($rProxies, $rCountryCode, $rUserISP = ''): int|null

Тот же алгоритм, что и StreamAuth::checkAccess(), но примененный к списку прокси-серверов.


Ограничение скорости и защита от наводнений

Три слоя:

1. nginx (уровень подключения)

limit_req_zone $binary_remote_addr zone=one:30m rate=20r/s;
limit_req zone=one burst=8;

20 запросов в секунду на IP-адрес с пакетом из 8 запросов. 30-минутное скользящее окно.

2. StreamingRequestBootstrap (IP-блокировка)

if (file_exists(FLOOD_TMP_PATH . 'block_' . $rIP)) {
    http_response_code(403);
    exit();
}

IP-блокировка на основе файлов. Файлы блоков создаются с помощью вышестоящей логики обнаружения наводнений.

3. Ограничитель подключений (для каждого пользователя)

Применяется после проверки токена. Ограничивает одновременные потоки для каждого пользователя на основе max_connections.


HLS Шифрование

Файл: src/Streaming/Delivery/HLSGenerator.php

public static function generateHLS($rSettings, $rM3U8, $rUsername, $rPassword,
    $rStreamID, $rUUID, $rIP, ...): string|false

Когда encrypt_hls == true:

  1. Сгенерируйте ключевой токен AES-128 из IP + StreamID + salt.
  2. Замените IV содержимым из STREAMS_PATH . $rStreamID . '_.iv'.
  3. Encrypt each segment reference: IP/StreamID/Segment/UUID/SERVER_ID/VideoCodec/OnDemand.
  4. Замените названия сегментов на /hls/{encrypted_token}.

Доставка ключей происходит через key.php с использованием того же механизма токенов.


Представление

Ключевые проектные решения, касающиеся пропускной способности и задержки:

Особенность Механизм
Ожидание неблокирующего файла AsyncFileOperations::awaitFileExists() использует inotify (Linux) или оптимизированный опрос
Нулевой режим работы процессора time_nanosleep() через AsyncFileOperations::efficientSleep()
nginx буферизация 128 буферов по 32 КБАЙТ на запрос
Объединение подключений в пул Redis (предпочтительно) или постоянный MySQL
Чтение только из кэша Настройки и пользовательские данные считываются из файлового кэша без запросов к базе данных
Ранний выход Отслеживает connection_status() каждые 5 секунд для обнаружения отключения клиента
Обновление настроек Каждые 5 минут (300 секунд) для отслеживания изменений конфигурации без перезапуска

Пути к файловой системе

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/

Диагностика и оснастка

Два инструмента проверяют правильность доставки потока — что сегменты поступают по порядку и очередь на доставку не прерывается.

tools/stream_queue_check.py (Только Python, stdlib)

Автономный монитор целостности сегмента/очереди пакетов с дополнительным ** функцией live панель управления буфером**. Автоматически определяет HLS по сравнению с MPEG-TS.

python3 tools/stream_queue_check.py "<url>" --duration 30        # batch check
python3 tools/stream_queue_check.py "<url>" --json               # cron / monitoring
python3 tools/stream_queue_check.py "<url>" --live --duration 0  # live dashboard

Что означает "неповрежденная очередь" для каждого типа потока:

Течение Проверка очереди
HLS (.m3u8) EXT-X-MEDIA-SEQUENCE монотонный и непрерывный (никаких удаленных или перемотанных сегментов), нет EXT-X-DISCONTINUITY, каждый вновь появляющийся сегмент доступен для загрузки. Основные плейлисты отображаются в их первом варианте.
MPEG-TS (.ts, /play/<token>/ts) per-PID continuity_counter (потерянные / дублированные / переупорядоченные пакеты = разрыв очереди), потеря байта синхронизации, индикатор транспортной ошибки и задержка доставки.

Основные параметры:

Флаг Цель
--duration N секунды для наблюдения (0 = до нажатия Ctrl-C в --live)
--tolerance N разрешить N временных разрывов очереди, прежде чем сообщать о BROKEN (игнорируются редкие сбои источника, переданные -c copy)
--stall-timeout S перерыв в доставке засчитывается как задержка; не превышайте продолжительность сегмента (по умолчанию 15).
--live цветная приборная панель TUI (внизу)
--prebuffer S / --buffer-target S live: предварительный буфер для виртуального игрока и масштаб буферного графика
--json / --no-color машинный вывод / отключение ANSI

Код выхода: 0 исправен, 2 проблема с очередью или задержка, 1 использование.

Оперативная панель мониторинга (--live)

Моделирует виртуального проигрывателя: проигрыватель перемещается со скоростью настенных часов, в то время как содержимое "получено". Для TS полученная временная шкала берется из PCR (часы потока).; для HLS из длительностей сегментов EXTINF. Буферизованное время воспроизведения ("кэш") = получено − воспроизведено; если значение достигает нуля, то начало воспроизведения зависает (событие отмены буферизации).

  STREAM QUEUE / BUFFER MONITOR   TS   up 00:22
  cache buffer (s), last 60s:
  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▄▄▇▇▇▆▆▆▅▅▅▄▄▇▇▇▆▆▆▅   <- burst-then-drain = delivery sawtooth
  IN CACHE : [█████████████████░░░░░░░░░░░░░]  11.6s / 20s
  PLAYING  : PLAYING     head 00:18   received 00:29
  rate 1000 kbit/s   received 4.1 MB   last data 7.0s ago
  QUEUE OK   cc:0 sync:0 gaps:0 disc:0   rebuffers:0

График буфера и индикатор окрашены в зеленый (исправный) / желтый (низкий) / красный цвета (голодает). Для HLS строка блоков показывает сегменты, которые все еще находятся в кэше перед началом плейхед.

console.php stream:check (PHP, компаньон)

Проверяет URL-адрес источника и с помощью --decode извлекает и декодирует медиафайл для перехвата поврежденные сегменты. HLS проверяется посегментно; сообщение об ошибке с одним сокетом конечная точка фиксируется с помощью cURL и декодируется в автономном режиме (ffmpeg в режиме реального времени -i зависает на it). Источник: src/Cli/Commands/StreamCheckCommand.php.

console.php stream:check "<url>"                  # metadata probe (type, codecs)
console.php stream:check "<url>" --decode=30 --json

Примечание — темп доставки. Цикл доставки TS в реальном времени в live.php истощает доступные данные без регулирования и приостанавливаются только при достижении значения ffmpeg заголовок записи. Более ранняя версия отключалась на одну секунду после каждого чтения, ограничивая пропускная способность составляет read_buffer_size в секунду, а клиенты голодают; stream_queue_check.py --live визуализирует результирующее поведение буфера.


Связанные файлы

Файл Цель
src/Streaming/StreamingBootstrap.php основной загрузчик потоковой передачи
src/Infrastructure/Bootstrap/StreamingRequestBootstrap.php Инициализация на уровне HTTP
src/Streaming/Auth/StreamAuth.php выбор сервера и проверка подключения
src/Streaming/Auth/StreamAuthMiddleware.php расшифровка токенов и заголовки ответов
src/Streaming/Balancer/ProxySelector.php выбор прокси-сервера
src/Streaming/Protection/ConnectionLimiter.php ограничения на подключение для каждого пользователя
src/Streaming/Delivery/HLSGenerator.php Генерация плейлиста M3U8
src/Streaming/Delivery/StreamRedirector.php доступность потока и маршрутизация сервера
src/Streaming/AsyncFileOperations.php неблокирующие утилиты для файловой системы
src/Streaming/Lifecycle/ShutdownHandler.php очистка соединения при выходе
src/Domain/Stream/ConnectionTracker.php состояние соединения в Redis/MySQL
src/Core/Init/LegacyInitializer.php настройка глобальной переменной для потоковой передачи
src/Cli/Commands/StreamCheckCommand.php stream:check — проверка/декодирование потока на наличие фрагментарных сегментов
tools/stream_queue_check.py мониторинг целостности очереди + панель мониторинга динамического буфера