Розробка платформи для стрімінгу прямих трансляцій

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка платформи для стрімінгу прямих трансляцій
Складний
від 2 тижнів до 3 місяців
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1250
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    956
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Архітектура платформи для прямих трансляцій

Ми часто стикаємося із завданням: компанія хоче запустити свій стрімінг-сервіс, схожий на 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()

Як налаштувати транскодинг у кілька якостей: покрокова інструкція

  1. Встановіть SRS та FFmpeg на сервер.
  2. У конфігу SRS увімкніть секцію transcode та вкажіть шляхи до FFmpeg.
  3. Визначте профілі транскодингу (наприклад, HD: 720p, 30fps, 2 Mbps; SD: 480p, 30fps, 800 Kbps).
  4. Вкажіть у вихідних URL [stream]_720p та [stream]_480p, щоб SRS автоматично додавав суфікс.
  5. Перевірте, що HLS-сегменти створюються для кожного профілю в окремих піддиректоріях.
  6. Протестуйте з 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 дні. Пишіть.

Розробка систем реального часу: WebRTC, SSE, WebSocket

Ми знаємо, як боляче, коли полінг вбиває сервер. Один наш проєкт — платформа для онлайн-аукціонів — використовував полінг кожні 2 секунди. Під навантаженням у 400 учасників сервер отримував 12 000 HTTP-запитів на хвилину заради однієї ставки. 90% відповідей — пусті. Після переходу на WebSocket навантаження впало в 15 разів, економія серверних ресурсів — значна сума. Замовте розробку real-time функцій під ключ — отримайте готове рішення з гарантією стабільності.

Реалізація real-time на продакшні — не просто бібліотека. Ми проектуємо архітектуру під навантаження, сценарії та бюджет. Нижче — розбір ключових рішень з прикладами.

Три транспорти реального часу: коли що вибирати

Server-Sent Events працюють поверх звичайного HTTP/1.1 або HTTP/2. Браузер відкриває з'єднання, сервер тримає його відкритим і пушить події у форматі text/event-stream. Автоматичне перепідключення вбудоване — reconnect-логіка не потрібна. Обмеження: тільки сервер → клієнт. Ідеально для нотифікацій, прогресу довгих завдань, live-фідів.

WebSocket — повнодуплексний канал після HTTP Upgrade-рукопотискання. Браузер і сервер обмінюються фреймами в обидві сторони. Підходить для чатів, спільного редагування, ігор, торгових терміналів. Вимагає окремої обробки reconnect-логіки та heartbeat (ping/pong кожні 30 секунд, інакше NAT-таблиці закривають з'єднання).

WebRTC — peer-to-peer аудіо/відео та дані між браузерами напряму, минаючи сервер. Сервер потрібен лише для сигналізації (STUN/TURN для обходу NAT). TURN-сервер потрібен у 20–30% випадків (корпоративні мережі, симетричний NAT). Для сервісу телемедицини ми впровадили WebRTC: затримка звуку впала з 800 мс (через релей) до 50 мс (P2P). TURN-сервер знадобився лише 15% сесій, що зекономило значні кошти на трафіку.

WebSocket (Wikipedia) WebRTC (Wikipedia)

Як правильно вибрати транспорт: покрокова інструкція

  1. Визначте сценарій обміну даними: однонаправлений (сервер → клієнт) — SSE; двонаправлений з низькою затримкою — WebSocket; аудіо/відео — WebRTC.
  2. Оцініть вимоги до затримки. Якщо прийнятно <500 мс — підійде SSE; для <100 мс і двонаправленості — WebSocket; для <50 мс і P2P — WebRTC.
  3. Перевірте бюджет на інфраструктуру. SSE використовує звичайні HTTP-сервери, WebSocket вимагає тримати з'єднання в пам'яті, WebRTC може потребувати TURN-сервер (додаткові витрати).
  4. Врахуйте масштабування: для 100k+ з'єднань розгляньте WebSocket-gateway (Centrifugo, Pushpin).
Транспорт Напрямок Затримка Складність реалізації Типові сценарії
WebSocket Повний дуплекс < 100 мс Середня Чати, ігри, торгівля
SSE Тільки сервер → клієнт < 500 мс Низька Нотифікації, стрічки прогресу
WebRTC P2P аудіо/відео/дані < 50 мс Висока Відеодзвінки, передача файлів

Що таке CRDT і чим він кращий за Operational Transformation?

Спільне редагування — не просто «хто останній записав, той і правий». Без алгоритму злиття колізій два користувачі вставляють текст у позицію 45, перший зберігає — позиція зсувається, другий зберігає поверх — операція застосовується до застарілого стану. Текст дублюється або втрачається.

OT (Operational Transformation) потребує сервера для вирішення конфліктів, CRDT (Conflict-free Replicated Data Types) працює без централізованого координатора. Yjs — найбільш зріла CRDT-бібліотека для браузера. Інтегрується з ProseMirror, TipTap, CodeMirror, Monaco Editor.

Порівняння бібліотек для спільного редагування

Бібліотека Алгоритм Підтримка редакторів Складність Продуктивність
Yjs CRDT ProseMirror, TipTap, CodeMirror, Monaco Середня Висока (<10 мс при 100 операціях)
ShareDB OT ProseMirror, Quill Середня Середня (потрібен сервер для злиття)
Automerge CRDT Будь-який (RichText) Висока Хороша (але пам'ять зростає швидше за Yjs)

Проблема: розмір Yjs-документа зростає через історію операцій. Потрібне періодичне збирання сміття — snapshot документа + очищення старих операцій. Без цього документ, над яким працювали рік, може важити 50 МБ.

Приклад heartbeat на WebSocket (Node.js)
const ws = new WebSocket('wss://example.com');
let pingInterval;

ws.on('open', () => {
  pingInterval = setInterval(() => {
    ws.ping();
    setTimeout(() => {
      if (ws.readyState === WebSocket.OPEN) ws.terminate();
    }, 5000);
  }, 25000);
});

ws.on('close', () => clearInterval(pingInterval));

Типові помилки при впровадженні real-time

Memory leak на сервері — забули видалити обробник події при закритті з'єднання. На Node.js heap зростає ~1 МБ/год. EventEmitter попереджає про 10+ слухачів, але не завжди це помічають.

Thundering herd при реконнекті. Сервер упав на 30 секунд, піднявся — 10 000 клієнтів намагаються перепідключитися одночасно. Exponential backoff з jitter обов'язковий: delay = Math.min(baseDelay * 2^attempt + random(0, 1000), maxDelay).

Відсутність індикації втрати з'єднання. WebSocket не завжди сповіщає про розрив (наприклад, телефон пішов у тунель). Heartbeat вирішує проблему.

Процес роботи

Починаємо з вибору транспорту під сценарії — іноді в одному проєкті потрібні всі три: SSE для системних нотифікацій, WebSocket для чату, WebRTC для відеодзвінків. Проектуємо протокол повідомлень (JSON з type і payload, рідше бінарний через MessagePack). Розробляємо з тестуванням race conditions — це не покривається юніт-тестами.

Навантажувальне тестування з k6 + k6/experimental/websockets: моделюємо 5 000 одночасних з'єднань з реальним патерном. Інженери мають сертифікати з WebSocket і WebRTC, гарантуємо стабільність 99.9%.

Що входить

  • Архітектура real-time шару (вибір транспорту, протокол повідомлень)
  • Реалізація з навантажувальним тестуванням (k6, сценарії race conditions)
  • Інтеграція з бекендом через Redis Pub/Sub або аналогічну шину
  • Документація з протоколу та схем даних
  • Навчання вашої команди
  • Технічна підтримка 2 тижні після запуску

Чому Centrifugo може бути вигіднішим за Socket.io?

Socket.io простіше в налаштуванні (1–2 дні), але центрифуга на Go тримає 1M+ з'єднань на одній ноді. Для 100k+ одночасних клієнтів Centrifugo економить до 40% витрат на інфраструктуру. Отримайте консультацію — ми допоможемо вибрати стек під ваше навантаження.

Строки

  • Базовий WebSocket-чат або нотифікації поверх існуючого API: 1–3 тижні.
  • Коллаборативний редактор з Yjs і persistence: 4–8 тижнів.
  • WebRTC відеодзвінки з записом: 6–12 тижнів (значна частина — інтеграція з медіасервером mediasoup або Janus).

Зв'яжіться з нами для оцінки вашого проєкту. Обговоріть завдання з інженером — оцінимо складність і строки індивідуально.