Реалізація відеопотоку з IoT-камери в мобільному додатку (RTSP/WebRTC)
Клієнт хочет дивитися камеру відеоспостереження прямо в додатку. Звучит просто. На практиці: RTSP-поток від IP-камери не можна відкрити нативними плеєрами iOS та Android напрямки. RTMP застарів. WebRTC працює, але потребує сигнальний сервер. А низька затримка в 200 мс — це зовсім інша архітектура, ніж HLS з 5-секундним буфером.
RTSP: чому не можна просто відкрити посилання
AVPlayer не підтримує rtsp:// — лише http(s):// та HLS. ExoPlayer офіційно видалив RTSP в Media3 (хоча підтримка є через RtspMediaSource, вона нестабільна на ряді виробників з нестандартними прошивками). На Flutter немає зрілого нативного RTSP-плеєра.
Рецепт два робочих підходи.
Підхід 1: Транскодування на сервері (RTSP → HLS/WebRTC)
Медіасервер (MediaMTX, Nginx-RTMP + ffmpeg, Ant Media Server) приймає RTSP від камери і видає HLS або WebRTC endpoint клієнту. Клієнт отримує HLS — AVPlayer / ExoPlayer читають без проблем. Затримка HLS: 3-8 секунд при стандартному чанку 2 сек. Для моніторингу це прийнятно. Для домофона — ні.
Конфіг MediaMTX для RTSP → HLS:
paths:
cam1:
source: rtsp://admin:[email protected]:554/stream1
hlsAlwaysRemux: yes
Клієнт підключається до http://media-server/cam1/index.m3u8.
Підхід 2: Нативний RTSP-декодер в додатку
iOS: VLCKit (MobileVLCKit) — обгортка над libVLC. Підтримує RTSP, RTMP, H.264, H.265. VLCMediaPlayer з drawable = UIView — рендерит прямо в view. Затримка: 500-800 мс при мережевому буфері 300 мс. Мінус: бінарник +30 МБ, App Store приймає без проблем.
Кастомний шлях: FFmpeg через ffmpeg-kit-ios (FFmpegKitConfig.executeAsync), декодуємо поток та рендеримо через AVSampleBufferDisplayLayer. Дає повний контроль над буферизацією і затримкою (можна довести до 100-200 мс при rtsp_transport tcp та мінімальному analyzeduration). Складніше в реалізації, але результат кращий.
Android: ExoPlayer RtspMediaSource — для простих випадків. Для складних (RTSP через TCP, H.265, багатопоточні камери) — ijkplayer або FFmpegKit. ijkplayer працює з Jetpack Compose через AndroidView.
Flutter: flutter_vlc_player (MobileVLCKit / libVLC Android) — кросплатформений варіант. video_player плагін RTSP не підтримує.
WebRTC: низька затримка для домофонів та PTZ-камер
Якщо затримка важлива (домофон, управління PTZ-камерою), WebRTC — правильний вибір. Затримка 100-300 мс проти 3-8 секунд у HLS.
Архітектура: IoT-камера → WebRTC-сумісний медіасервер (Janus, Kurento, MediaSoup, Ant Media) → мобільний клієнт через ICE/STUN/TURN.
iOS: WebRTC framework (pod 'GoogleWebRTC' або pod 'WebRTC-SDK'). Створюємо RTCPeerConnection, отримуємо SDP offer від сервера, відповідаємо answer, отримуємо RTCVideoTrack та рендеримо через RTCMTLVideoView (Metal-рендеринг, апаратне прискорення). Сигналізація — WebSocket (Starscream, URLSessionWebSocketTask).
Android: io.getstream:stream-webrtc-android або офіційний WebRTC від Google. SurfaceViewRenderer для рендерингу VideoTrack.
Flutter: flutter_webrtc — використовує нативний WebRTC під капотом. RTCVideoRenderer + RTCVideoView.
Для проходження через NAT обов'язковий STUN (безплатний Google stun.l.google.com:19302) та TURN-сервер для симетричних NAT (Coturn на своєму сервері).
PTZ-управління
Pan/Tilt/Zoom через ONVIF або власний HTTP API камери. На мобільному: UIPanGestureRecognizer → обчислюємо дельту → відправляємо ONVIF ContinuousMove запит через HTTP. Pinch → AbsoluteMove з zoom-координатою.
Дроблинг запитів: не відправляємо кожну подію жесту — throttle(300ms), інакше камера не встигає обробляти команди.
Запис та снапшоти
Снапшот з камери: дешевше запросити JPEG snapshot URL напрямки у камери (більшість IP-камер підтримують http://cam/snapshot.jpg) ніж захоплювати кадр з потоку.
Запис потоку на пристрої: AVAssetWriter (iOS) пише CMSampleBuffer з декодованого потоку в MP4. На Android — MediaMuxer + MediaCodec. Для запису на сервері — ffmpeg -i rtsp://... -c copy output.mp4 через медіасервер.
Мультикамерність
Список камер + мініатюри потоків. Не відтворюємо всі потоки одночасно — лише активну. Превью: статичний снапшот, оновлюваний кожні 5 секунд (URLSession.dataTask + UIImageView). Економить батарею та трафік на 90%.
| Протокол | Затримка | Складність клієнта | Прим. |
|---|---|---|---|
| HLS (RTSP→HLS на сервері) | 3-8 сек | Низька (нативний плеєр) | Моніторинг |
| RTSP (VLCKit/FFmpegKit) | 300-800 мс | Середня | Універсально |
| WebRTC | 100-300 мс | Висока | Домофон, PTZ |
Строки: RTSP-перегляд однієї камери — 3-4 дні. Мультикамерний моніторинг з WebRTC та PTZ — 2-3 тижні.







