Розробка Reels (короткі відео) у мобільному додатку
Клієнт скаржиться: стрічка вертикальних відео гальмує на iPhone 8 зі старою iOS — перемикання між роликами займає секунду, звук запізнюється, а при швидкому свайпі додаток вилітає через нестачу пам'яті. Знайома проблема? Ми вирішуємо її: реалізуємо плавний paging-скрол на основі shared плеєра, передзавантаження відео за три позиції та кеш AVPlayerItem для п'яти сусідніх роликів — все це дає стабільні 60 fps навіть на пристроях з 2 ГБ ОЗП. Вертикальний відеофід — технічно один з найскладніших UI-патернів: потрібна синхронізація аудіо, відео та дотиків, мінімізація затримки при скролі та правильне управління пам'яттю. Ми реалізували понад 50 проектів з відеофідом, використовуючи Swift 5.9+, Kotlin 1.9+, Combine та Coroutines. Наші інженери сертифіковані Apple і Google, мають досвід роботи з App Store Review Guidelines (Section 4.2, 5.1) та Google Play Console. Ключове рішення — використання єдиного екземпляра AVPlayer (iOS) або ExoPlayer (Android) з переназначенням плеєра між комірками. Це виключає затримки, пов'язані з ініціалізацією плеєра, і знижує навантаження на GPU. Передзавантаження AVPlayerItem за три відео до відображення скорочує час перемикання до 80 мс. Оцінимо ваш проект безкоштовно — зв'яжіться з нами.
Технічні вимоги до пристрою
- iOS 14+, Android 7.0+
- Мінімум 2 ГБ ОЗП
- Підтримка VideoToolbox (iOS) / MediaCodec (Android)
Вертикальний paging-скрол
Основа Reels — UICollectionView з UICollectionViewCompositionalLayout + UICollectionLayoutSectionOrthogonalScrollingBehavior.paging (iOS) або RecyclerView з PagerSnapHelper (Android). Кожен елемент займає весь екран (bounds.size).
На iOS пагінаційний скрол через UICollectionView з isPagingEnabled = true — працює, але не дає контролю над deceleration. Краще: кастомний UICollectionViewFlowLayout з перевизначеним targetContentOffset(forProposedContentOffset:withScrollingVelocity:), який snap'ає до найближчого відео з потрібною анімацією.
Velocity-based перемикання: швидкий свайп перемикає на наступний Reel незалежно від того, наскільки далеко проскролили. sqrt(velocity.x² + velocity.y²) > threshold → snap до наступного елемента.
Як перемикати відео без затримок?
Критичне рішення — один shared AVPlayer з переназначенням AVPlayerItem при скролі. При scrollViewDidEndDecelerating забираємо плеєр з попередньої комірки і призначаємо поточній. AVPlayerLayer.player = avPlayer — миттєво. Плеєр не перестворюється — перемикання без пауз.
На Android — один ExoPlayer на всю активність/фрагмент. PlayerView.player = exoPlayer переназначає. Використовуємо MediaItem з preload: exoPlayer.addMediaItem(nextMediaItem) для наступного відео — ExoPlayer починає буферизацію до фактичного перемикання. ExoPlayer з передзавантаженням працює швидше стандартного MediaPlayer в 4 рази.
Передзавантаження: тримаємо в пам'яті AVPlayerItem для поточного, наступного та попереднього відео. Створення AVPlayerItem з URL займає час — робимо це на початку скролу до сусіднього елемента, не при завершенні.
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { // Передзавантажуємо AVPlayerItem для цього індексу let item = AVPlayerItem(url: videoURLs[indexPath.row]) playerItemCache[indexPath.row] = item } Згідно з Apple AVFoundation Guide, такий підхід мінімізує затримки.
Як реалізувати запис з мультисегментами?
Камера для запису Reels — кастомний AVCaptureSession. AVCaptureVideoPreviewLayer відображає live preview на весь екран. Запис через AVCaptureMovieFileOutput з обмеженням: maxRecordedDuration = CMTime(seconds: 60, preferredTimescale: 600).
Мультисегментний запис (кілька коротких кліпів в один Reel) — на кожен запис створюємо окремий AVAsset, в кінці об'єднуємо через AVMutableComposition. AVMutableCompositionTrack.insertTimeRange() вставляє сегменти послідовно.
Фільтри в реальному часі — Core Image з CIFilter застосовуємо до CMSampleBuffer в методі делегата captureOutput(_:didOutput:from:). Рендеримо через CIContext з Metal (CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!)). OpenGL для цього завдання — застарілий підхід з гіршою продуктивністю.
Звук, текст, ефекти
Аудіотрек — своя музика з медіатеки через MPMediaPickerController (iOS) або ACTION_PICK з MediaStore.Audio (Android), або треки з каталогу додатка. Аудіо накладаємо на відео через AVMutableComposition з двома треками: відео + аудіо.
Текст поверх відео — не UILabel поверх UIImageView, а запікаємо у відео через AVVideoCompositionCoreAnimationTool + CATextLayer. Це дає коректне відображення тексту при експорті та публікації.
Продуктивність стрічки
UICollectionView.prefetchDataSource на iOS — запитуємо метадані наступних 3 відео при скролі. RecyclerView.setRecycledViewPool() на Android — загальний пул в'ю для перевикористання, знижує кількість inflate-операцій.
Пам'ять: зберігаємо декодовані AVPlayerItem максимум для 5 сусідніх відео. При прокрутці далі — звільняємо AVPlayerItem.asset явно. Без цього при перегляді 20+ Reels пам'ять зростає до 300–500 МБ і iOS надсилає memory warning. Наші інженери сертифіковані Apple і Google, гарантуємо плавну роботу навіть на пристроях з 2 ГБ ОЗП.
| Компонент | Складність | Приблизний час |
|---|---|---|
| Вертикальний paging + shared плеєр | Висока | 2–3 дні |
| Передзавантаження + кеш | Середня | 1–2 дні |
| Запис + мультисегменти | Висока | 3–4 дні |
| Фільтри real-time (Core Image) | Висока | 2–3 дні |
| Текст + аудіо в експорті | Середня | 2 дні |
| Метрика | iOS | Android |
|---|---|---|
| Плавність скролу (fps) | 60 | 60 |
| Затримка перемикання | <100ms | <120ms |
| Споживання пам'яті (5 відео) | ~150 МБ | ~180 МБ |
Що входить в роботу
- Документація архітектури та API.
- Вихідний код з коментарями.
- Налаштування CI/CD та доступів до сторів (App Store Connect, Google Play Console).
- Навчання команди замовника (2–3 сесії).
- Підтримка 30 днів після релізу.
Строки та вартість
Стрічка перегляду з автовідтворенням, передзавантаженням та пагінацією — 1–2 тижні. Повний цикл (запис, редагування, фільтри, публікація, стрічка) — 3–6 тижнів. Вартість розраховується індивідуально залежно від складності інтеграцій та необхідного функціоналу. Зв'яжіться з нами для оцінки вашого проекту.
Покрокова інструкція: як впровадити shared плеєр
- Створіть один
AVPlayer/ExoPlayerу вашій Activity/ViewController. - У кожній комірці використовуйте
playerLayer.player = player/PlayerView.player = player. - У
scrollViewDidEndDeceleratingпризначте плеєр комірці, яка тепер видима. - Для передзавантаження реалізуйте prefetchDataSource (iOS) або addMediaItem (Android).
- Протестуйте на пристроях з 2 ГБ ОЗП.







