Реализация отправки видео в чате мобильного приложения
Видео в чате — это не «изображение побольше». Тут другой pipeline: транскодирование, потоковая загрузка, HLS или прогрессивный MP4, превью-кадр. Если сделать «в лоб» — загрузить оригинал с камеры — пользователь ждёт минуты, а на слабом соединении просто получает ошибку таймаута.
Главная проблема: размер и формат
Видео с iPhone 14 Pro в 4K/60fps весит ~400 МБ в минуту. Даже клип на 15 секунд — 100 МБ. Загружать такое напрямую — неприемлемо ни по времени, ни по трафику.
На iOS видео из PHPickerViewController или UIImagePickerController приходит в формате MOV (HEVC или H.264). Перед загрузкой его нужно транскодировать. Стандартный путь — AVAssetExportSession с presetом AVAssetExportPresetMediumQuality (720p, ~2 Мбит/с) или 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 МБ/с) и оставшегося времени — обязательный элемент. Кнопка отмены должна останавливать и транскодирование, и upload, и удалять временные файлы.
| Этап | Время (клип 30с, 720p) |
|---|---|
| Транскодирование (iPhone 14) | 3–5 с |
| Транскодирование (iPhone SE 2) | 7–12 с |
| Upload 15 МБ на 10 Мбит/с | ~12 с |
| Генерация превью | < 1 с |
Сроки
Базовая реализация (выбор, транскодирование, chunked upload, превью, воспроизведение) — 3–5 дней при готовом бэкенде с поддержкой multipart upload. Background upload + возобновление — ещё 1–2 дня. Стоимость рассчитывается индивидуально после анализа требований.







