Реалізація режиму co-streaming (спільний стрім) у мобільному додатку
Co-streaming — це коли два користувачі трансльють одночасно, бачать й чують один одного в реальному часі, й глядачі бачать обох. Технічно це пересічення двох різних завдань: WebRTC (для peer-to-peer відеозв'язку між стримерами) й RTMP/SRT (для трансляції результату глядачам). Об'єднати їх нетривіально.
Архітектура co-стрима
Стандартна схема:
Стример A: камера → WebRTC → Сигнальний сервер ← WebRTC ← камера: Стример B
↓
Mixing Server (SFU/MCU)
↓
RTMP → Twitch/YouTube/Custom
Але на мобільному додається варіант без MCU — «client-side mixing». Стример A отримує відео стримера B через WebRTC, мішає обидва потоки локально через Metal/OpenGL й відправляє змішаний стрім на RTMP. Це дешевше серверно, але вимагає потужного процесора на пристрої й нестабільна при поганій мережі другого учасника.
В продакшені для додатків з 1000+ одночасних co-стримів — тільки серверний MCU/SFU (LiveKit, mediasoup, Agora). Для MVP з малою нагрузкою — client-side mixing працює.
WebRTC на iOS й Android
iOS: GoogleWebRTC (CocoaPods) або WebRTC.xcframework від Google. Основний об'єкт — RTCPeerConnection. Ініціалізація:
let config = RTCConfiguration()
config.iceServers = [RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"],
username: nil, credential: nil)]
config.sdpSemantics = .unifiedPlan
let constraints = RTCMediaConstraints(
mandatoryConstraints: ["OfferToReceiveVideo": "true",
"OfferToReceiveAudio": "true"],
optionalConstraints: nil
)
let peerConnection = factory.peerConnection(with: config,
constraints: constraints,
delegate: self)
Android: org.webrtc:google-webrtc:1.0.+ або io.getstream:stream-webrtc-android. Логіка аналогічна через PeerConnection.
Сигнальний сервер — WebSocket, який обмінює SDP offer/answer й ICE candidates між стримерами. Пишемо на Node.js (ws) або використовуємо готовий — LiveKit Server, Agora RTM.
Проблеми, з якими сталкиваємось
Задержка аудіо при змішуванні. Коли A чує B через WebRTC з затримкою 150–200ms, а стрім формується з локального аудіо A, глядачі чують рассинхрон. Рішення: компенсація затримки через AVAudioPlayerNode.scheduleBuffer з явним AVAudioTime, щоб локальне аудіо A в итоговом стрімі було затримано на те ж час, що й вхідне від B.
Echo cancellation. Якщо у стримера нема навушників, його мікрофон захоплює звук з динаміка (WebRTC-аудіо від партнера). Вбудований WebRTC AEC працює тільки на RTCPeerConnection-аудіо треку. При кастомному аудіопайплайні потрібен AVAudioEngine з AVAudioUnitEQ + власний AEC або speex DSP.
Переключення між co-стримом і solo. При виході партнера з co-стрима потрібно плавно убрати його вікно з композиції й перебудувати layout без переривання RTMP-трансляції. Це означає: Metal render pass повинен перевіряти наявність другої текстури й коректно рендерити full-frame режим, якщо другий учасник відключився.
Сигналізація стану для глядачів
Глядачі повинні знати, що іде co-стрім. На стороні додатка — overlay з аватарами обох стримерів. Оновлення overlay при зміні стану (партнер підключився/відключився) іде через WebSocket-подію, не через polling.
Терміни
Client-side co-стрім (iOS, два учасники, Metal-композиція, базовий сигнальний сервер): 5–7 тижнів. Повна реалізація з MCU, Android-підтримкою, AEC, управлінням стану: 8–12 тижнів. Вартість розраховується індивідуально після аналізу вимог й вибору архітектури.







