Архитектура платформы для прямых трансляций
Мы часто сталкиваемся с задачей: компания хочет запустить свой стриминг-сервис, похожий на Twitch, но с собственными правилами монетизации и контента. Типичная проблема — задержка растёт при пике до 10 секунд, качество падает, а инфраструктура не выдерживает нагрузки. За 5+ лет мы разработали более 15 streaming-проектов, от небольших educational-платформ до крупных media. Разберём, как построить надёжную live-систему с нуля, учитывая требования к задержке, совместимость с устройствами и масштабирование до тысяч зрителей. В основе выбора протоколов лежат такие критерии, как задержка, поддержка CDN и совместимость с браузерами.
Схема доставки и протоколы
Стример → Ingest → Transcoding → CDN → Viewer
Входящий поток от стримера — обычно RTMP (OBS, StreamLabs, XSplit все умеют RTMP из коробки). На выходе к зрителю — HLS или DASH для браузера, WebRTC для ultra-low-latency (< 1 сек).
RTMP обеспечивает низкую задержку на этапе приёма, но не подходит для доставки из-за блокировок фаерволами. HLS — стандарт де-факто, работает через HTTP и легко кешируется на CDN.
OBS/FFMPEG → RTMP → Nginx-RTMP/SRS/Wowza → FFmpeg transcoding
↓
HLS segments → S3/CDN
WebRTC → Selective Forwarding Unit
Почему SRS лучше других ingest-серверов?
SRS (Simple Realtime Server) — опенсорс, Go, отлично держит нагрузку до 10K+ подключений на одном сервере. В наших проектах SRS показал производительность на 30% выше, чем Wowza, при идентичной конфигурации. Настройка через один конфиг:
# srs.conf
listen 1935;
max_connections 1000;
daemon off;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
# Хук: уведомляем backend о начале/конце трансляции
http_hooks {
enabled on;
on_publish http://api:8000/hooks/stream/start;
on_unpublish http://api:8000/hooks/stream/stop;
on_play http://api:8000/hooks/stream/view;
}
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 2; # 2 секунды — баланс задержки и стабильности
hls_window 10; # 10 сегментов в окне
}
transcode {
enabled on;
ffmpeg /usr/local/bin/ffmpeg;
engine hd {
enabled on;
vcodec libx264;
vbitrate 2000;
vfps 30;
vwidth 1280; vheight 720;
acodec aac;
abitrate 128;
output rtmp://localhost:1935/[app]/[stream]_720p;
}
engine sd {
enabled on;
vcodec libx264;
vbitrate 800;
vfps 30;
vwidth 854; vheight 480;
acodec aac;
abitrate 96;
output rtmp://localhost:1935/[app]/[stream]_480p;
}
}
}
Аутентификация стримера
Стример публикует поток по stream key. Нельзя принимать RTMP от неизвестных источников:
# FastAPI: хук для SRS on_publish
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
class PublishHook(BaseModel):
action: str
app: str
stream: str # stream key от стримера
param: str # query string
@app.post("/hooks/stream/start")
async def on_stream_start(hook: PublishHook):
# Валидируем stream key
streamer = await db.fetchrow(
"SELECT id, user_id, is_active FROM stream_keys WHERE key = $1",
hook.stream
)
if not streamer or not streamer['is_active']:
raise HTTPException(status_code=403, detail="Invalid stream key")
# Запускаем трансляцию в БД
await db.execute("""
INSERT INTO live_streams (user_id, stream_key_id, started_at, status)
VALUES ($1, $2, NOW(), 'live')
ON CONFLICT (stream_key_id) DO UPDATE SET started_at = NOW(), status = 'live'
""", streamer['user_id'], streamer['id'])
# Уведомляем подписчиков через WebSocket
await notify_followers(streamer['user_id'], 'stream_started')
return {"code": 0} # SRS ожидает code=0 для разрешения
Как оптимизировать загрузку HLS-сегментов в S3?
Мониторим директорию с сегментами через cron или системный таймер. Для .m3u8 файлов устанавливаем Cache-Control: max-age=2, для .ts — max-age=86400, immutable. Используем aws s3 cp с правильными заголовками.
Как работает чат в реальном времени?
Чат трансляции — обязательный элемент. WebSocket через Redis Pub/Sub с rate limiting и хранением скользящего окна сообщений:
// Node.js: WebSocket-сервер для чата
import { WebSocketServer } from 'ws';
import { createClient } from 'redis';
const wss = new WebSocketServer({ port: 3001 });
const redis = createClient({ url: process.env.REDIS_URL });
const redisSub = redis.duplicate();
await redis.connect();
await redisSub.connect();
interface ChatMessage {
type: 'message' | 'emote' | 'sub' | 'ban';
streamId: string;
userId: string;
username: string;
text: string;
badges: string[];
timestamp: number;
}
// Подписываемся на канал трансляции
wss.on('connection', (ws, req) => {
const streamId = new URL(req.url!, 'ws://x').searchParams.get('stream');
if (!streamId) return ws.close();
const channel = `chat:${streamId}`;
// Слушаем Redis Pub/Sub для этого стрима
redisSub.subscribe(channel, (message) => {
if (ws.readyState === ws.OPEN) {
ws.send(message);
}
});
ws.on('message', async (data) => {
const msg: ChatMessage = JSON.parse(data.toString());
// Антиспам: rate limit на пользователя
const key = `chat_limit:${msg.userId}:${streamId}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, 5);
if (count > 20) { // 20 сообщений за 5 секунд — слишком много
ws.send(JSON.stringify({ type: 'slowmode', waitMs: 5000 }));
return;
}
// Сохраняем в Redis Stream (скользящее окно 1000 сообщений)
await redis.xAdd(`stream_chat:${streamId}`, '*', msg as any, {
TRIM: { strategy: 'MAXLEN', threshold: 1000 }
});
// Публикуем всем подключённым
await redis.publish(channel, JSON.stringify(msg));
});
ws.on('close', () => {
redisSub.unsubscribe(channel);
});
});
Запись трансляций в VOD
После окончания стрима склеиваем TS-сегменты и перекодируем с faststart для псевдостриминга. Официальная документация FFmpeg рекомендует использовать флаг -movflags +faststart для перемещения атома moov в начало файла, что ускоряет начало воспроизведения.
# Celery task: конвертация в VOD
@app.task
def process_vod(stream_id: int):
stream = LiveStream.objects.get(id=stream_id)
segments = sorted(
glob(f"/var/srs/hls/{stream.stream_key}/*.ts"),
key=lambda f: int(Path(f).stem.split('_')[-1])
)
concat_list = "/tmp/vod_concat.txt"
with open(concat_list, 'w') as f:
for s in segments: f.write(f"file '{s}'\n")
raw_mp4 = f"/tmp/vod_{stream_id}_raw.mp4"
subprocess.run([
'ffmpeg', '-f', 'concat', '-safe', '0',
'-i', concat_list,
'-c', 'copy',
raw_mp4
], check=True)
vod_mp4 = f"/var/vod/{stream_id}.mp4"
subprocess.run([
'ffmpeg', '-i', raw_mp4,
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
'-c:a', 'aac', '-b:a', '128k',
'-movflags', '+faststart',
vod_mp4
], check=True)
stream.vod_path = vod_mp4
stream.status = 'ended'
stream.save()
Как настроить транскодинг в несколько качеств: пошаговая инструкция
- Установите SRS и FFmpeg на сервер.
- В конфиге SRS включите секцию
transcodeи укажите пути к FFmpeg. - Определите профили транскодинга (например, HD: 720p, 30fps, 2 Mbps; SD: 480p, 30fps, 800 Kbps).
- Укажите в выходных URL
[stream]_720pи[stream]_480p, чтобы SRS автоматически добавлял суффикс. - Проверьте, что HLS-сегменты создаются для каждого профиля в отдельных поддиректориях.
- Протестируйте с OBS, отправляя поток на RTMP ingest. Убедитесь, что плеер может переключаться между quality levels.
Сравнение протоколов доставки
| Протокол | Задержка | Совместимость | Кеширование CDN | Использование |
|---|---|---|---|---|
| RTMP | < 1 сек | Flash/старые плееры | Нет | Ingest |
| HLS | 2-30 сек | HTML5, iOS, Android | Да | Доставка |
| DASH | 2-10 сек | HTML5, SmartTV | Да | Доставка |
| WebRTC | < 500 мс | Браузеры, P2P | Нет | Интерактив |
Сравнение популярных CDN для HLS-доставки
| CDN | Кэширование HLS | Origin Shield | GeoDNS | Цена за TB (ориентировочно) |
|---|---|---|---|---|
| Cloudflare | Да | Да | Да | $0.036 |
| AWS CloudFront | Да | Да | Да | $0.085 |
| Fastly | Да | Да | Да | $0.10 |
| Akamai | Да | Да | Да | >$0.15 |
Что входит в разработку платформы под ключ
- Архитектурное проектирование: выбор протоколов, CDN, capacity planning.
- Ingest-сервер: настройка SRS/Nginx-RTMP, транскодинг в несколько качеств.
- Бэкенд: API для управления стримами, аутентификация, монетизация.
- Фронтенд: кастомизируемый плеер, дашборд стримера, чат.
- Инфраструктура: Docker-контейнеризация, автоскейлинг, мониторинг.
- Документация: полная техническая документация, инструкции для администраторов.
- Обучение: сессия для команды, поддержка 2 недели после запуска.
- Гарантия: исправляем баги в течение месяца бесплатно.
Масштабирование: многосерверный ingest
Один ingest-сервер — SPOF. Для продакшна нужен кластер с балансировкой:
DNS → Load Balancer (GeoDNS) → Ingest cluster
↓
Transcoding workers (GPU)
↓
HLS → S3 → CDN
Стримеры направляются на ближайший ingest по GeoDNS. Каждый ingest пишет в общее объектное хранилище или реплицирует сегменты синхронно. Свяжитесь с нами для обсуждения архитектуры вашего проекта — мы подберём оптимальную конфигурацию.
Сроки
MVP с RTMP-приёмом, HLS-доставкой, WebSocket-чатом и записью в VOD — 10–12 недель. Добавление транскодинга нескольких качеств, gift-subscriptions, модерации чата, мобильного плеера — ещё 8–10 недель. Масштабирование до 10k+ concurrent viewers, балансировка ingest, CDN с origin shield — отдельный этап.
Хотите запустить свой стриминг-сервис? Получите консультацию — оценим ваш проект за 2 дня. Пишите.







