Архитектура платформы для прямых трансляций
Мы часто сталкиваемся с задачей: компания хочет запустить свой стриминг-сервис, похожий на 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 дня. Пишите.







