Реалізація отправки відео у чаті мобільного додатку
Відео в чаті — це не «зображення побільше». Тут інший pipeline: транскодування, потокова завантаження, HLS або прогресивний MP4, превью-кадр. Якщо зробити «в лоб» — завантажити оригінал з камери — користувач чекає хвилини, а на слабому з'єднанні просто отримує помилку таймаута.
Головна проблема: розмір та формат
Відео з iPhone 14 Pro в 4K/60fps важить ~400 МБ в хвилину. Навіть клип на 15 секунд — 100 МБ. Завантажувати такое напрямку — неприємно ні за часом, ні за трафіком.
На iOS відео з PHPickerViewController або UIImagePickerController приходить у форматі MOV (HEVC або H.264). Перед завантаженням його потрібно транскодувати. Стандартний путь — AVAssetExportSession з presetом AVAssetExportPresetMediumQuality (720p, ~2 Мбіт/s) або AVAssetExportPreset1280x720. Експорт асинхронний, займає від 2 до 30 секунд в залежності від довжини клипу та моделі пристрою. На iPhone SE 2nd gen транскодування 30-секундного відео в 720p — близько 8 секунд.
На Android ситуація складніша: MediaMuxer + MediaCodec дають максимальний контроль, але вимагають багато коду. У більшості проектів використовуємо FFmpegKit (форк мобільного FFmpeg) — дозволяє задати бітрейт, розрішення та кодек однією командою: -vcodec libx264 -crf 28 -vf scale=720:-2. Мінус — прибавляє ~15 МБ до APK.
Upload з возобновленням
Відео неможливо завантажувати одним запитом без поддержки возобновлення. З'єднання перервуться — і весь прогрес потерян.
Правильний підхід — multipart upload на стороні сервера (AWS S3 Multipart Upload, Cloudflare R2, або кастомний чанкований upload). Файл ділиться на частини по 5–10 МБ, кожна завантажується окремим запитом. При обриві — возобновлюємо з останнього успішного чанка.
На iOS реалізуємо через URLSession з Background Configuration (URLSessionConfiguration.background(withIdentifier:)): завантаження продовжується навіть після сворачування додатку. На Android — WorkManager з UploadWorker, який отримує setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) для роботи у фоні.
У React Native — react-native-background-upload або кастомний нативний модуль. Стандартний fetch з FormData не підтримує background upload на iOS.
Превью та воспроизведення
Превью-кадр генеруємо до завантаження: на iOS — AVAssetImageGenerator з generateCGImagesAsynchronously, на Android — MediaMetadataRetriever.getFrameAtTime(). Кадр компресуємо до JPEG 480p та завантажуємо окремо — він з'явиться в чаті мгновенно, поки відео ще грузиться.
Для воспроизведення в чаті не встраюємо повний плеер у ячейку — це убить продуктивність при скролі. Показуємо thumbnail з кнопкою play, по тапу відкриваємо AVPlayerViewController (iOS) або ExoPlayer (Android) поверх чата. ExoPlayer з SimpleExoPlayer та DefaultMediaSourceFactory підтримує HLS, DASH та progressive MP4 з коробки.
Обмеження та UX
Ліміт довжини відео — не технічний, а UX-рішення. Рекомендуємо 60–120 секунд для чатів. Перевищення — показуємо попередження до початку транскодування, не після.
Прогресс-бар завантаження з відображенням швидкості (2.3 МБ/s) та оставшегося часу — обов'язковий елемент. Кнопка відмени повинна зупинити як транскодування, так і upload, та видалити тимчасові файли.
| Етап | Час (клип 30с, 720p) |
|---|---|
| Транскодування (iPhone 14) | 3–5 с |
| Транскодування (iPhone SE 2) | 7–12 с |
| Upload 15 МБ на 10 Мбіт/s | ~12 с |
| Генерація превью | < 1 с |
Терміни
Базова реалізація (вибір, транскодування, chunked upload, превью, воспроізведення) — 3–5 днів при готовому бекенді з поддержкою multipart upload. Background upload + возобновлення — ще 1–2 дні. Вартість рассчитується індивідуально після аналізу вимог.







