Разработка видеозвонков в мобильном приложении
Видеозвонок — это не просто WebRTC-соединение. Это управление камерой и микрофоном, lifecycle на уровне системы, обработка прерываний (входящий звонок, уведомление, блокировка экрана), корректная работа в фоне и на низком заряде батареи. Каждая из этих деталей — отдельная задача с нетривиальными решениями на iOS и Android.
Выбор транспорта: WebRTC vs готовые SDK
Чистый WebRTC — это RTCPeerConnection, RTCSessionDescription, ICE/STUN/TURN серверы, getUserMedia, сигналинг через WebSocket. Полная реализация с нуля занимает 3–6 недель только на транспортный слой, без UI.
Большинство проектов выбирают SDK поверх WebRTC: Twilio Video, Daily.co, 100ms, Livekit или Agora. Они берут на себя ICE negotiation, codec negotiation (VP8/VP9/H.264/AV1), адаптивный битрейт, и дают готовые нативные обёртки для iOS и Android.
Если требования к кастомизации невысоки и важна скорость запуска — выбираем SDK. Если нужен полный контроль над кодеком, шифрованием или минимальным весом бинарника — чистый WebRTC через GoogleWebRTC (iOS) или org.webrtc (Android).
Нативная реализация на iOS: AVCaptureSession и CallKit
Видео с камеры захватывается через AVCaptureSession. Правильная инициализация:
let session = AVCaptureSession()
session.sessionPreset = .hd1280x720
let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
let input = try AVCaptureDeviceInput(device: camera!)
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
AVCaptureVideoDataOutput с делегатом даёт сырые CMSampleBuffer — их передаём в WebRTC через RTCVideoSource. Важно: AVCaptureSession.startRunning() вызывается строго в фоновом потоке, не на главном — иначе UI заморозится на 200–400 мс.
Переключение фронтальной/основной камеры — AVCaptureSession.beginConfiguration() + удаление старого input + добавление нового + commitConfiguration(). Без begin/commit вокруг этих операций — флicker на видео во время переключения.
CallKit — обязательный компонент для iOS-приложений с видеозвонками. Без него входящий звонок отображается как push-уведомление, которое может быть пропущено. С CallKit — полноэкранный системный экран звонка, интеграция с Bluetooth/AirPods, правильная обработка прерывания аудио. Реализуем через CXProvider и CXCallController. VoIP push через PKPushRegistry — единственный способ разбудить приложение для входящего звонка.
Нативная реализация на Android: Camera2 и ConnectionService
На Android Camera2 API (CameraManager.openCamera()) даёт доступ к ImageReader с форматом YUV_420_888 — стандартный формат для WebRTC на Android. Для большинства задач хватает уровня CameraDevice.StateCallback + CaptureRequest.Builder.
ConnectionService — Android-аналог CallKit. Регистрируем PhoneAccount, через TelecomManager управляем состоянием звонка. Без этого на Android 10+ приложение не может работать в foreground во время звонка без постоянного уведомления.
Для сохранения соединения при сворачивании — ForegroundService с типом mediaProjection или phoneCall. На Android 14 это требует явного указания в манифесте: android:foregroundServiceType="camera|microphone".
Управление камерой и микрофоном
Mute микрофона — не отключение устройства, а замена аудио-трека на тишину. В WebRTC: RTCAudioTrack.isEnabled = false. Полное отключение микрофона на уровне AVAudioSession — ломает эхоподавление.
Flip камеры во время звонка — через RTCCameraVideoCapturer.stopCapture() + смена устройства + startCapture(with:fps:). Без полной остановки — крэш на некоторых устройствах Huawei и Xiaomi из-за состояния гонки в Camera2.
Поворот экрана — RTCVideoTrack сам по себе не учитывает ориентацию. Нужно передавать RTCVideoRotation в RTCVideoFrame при захвате с камеры, иначе собеседник увидит повёрнутое на 90° видео на устройствах с portrait-lock.
Качество соединения и адаптация
Адаптивный битрейт — базовая функция WebRTC (GCC алгоритм). Но нужно явно задать диапазон: RTCRtpEncodingParameters с minBitrateBps: 100_000 и maxBitrateBps: 1_500_000. Без ограничения сверху на Wi-Fi WebRTC попытается использовать 4–8 Мбит/с — это некомфортно для других пользователей сети.
Индикатор качества соединения через RTCPeerConnection.getStats() — парсим RTCInboundRtpStreamStats для framesPerSecond и packetsLost. Потери > 5% — показываем предупреждение пользователю.
Процесс
Аудит требований → выбор транспорта (WebRTC или SDK) → реализация сигналинга → интеграция камеры/микрофона → CallKit/ConnectionService → UI (превью локального видео, удалённое видео, кнопки управления) → тестирование на реальных устройствах.
Базовый видеозвонок 1-на-1 через SDK (Twilio/Agora/100ms) с управлением камерой, mute и flip — 1–2 недели. Чистый WebRTC с сигналингом, CallKit, фоновый режим — 3–5 недель. Стоимость рассчитывается после анализа требований.







