Реалізація потокового відтворення аудіо в мобільному додатку
Ми часто стикаємося з проектами, де користувач хоче слухати радіо, подкасти або музику у фоні, перемикатися між додатками і не втрачати позицію. Головна технічна складність — плеєр повинен витримувати перехід в інший додаток і повернення без перезавантаження, залишаючись синхронізованим з UI. Наприклад, в одному з проектів інтернет-радіо вимагалося, щоб після 10 перемикань між додатками відтворення продовжувалося без збоїв, а медіа-контроли на lock screen оновлювалися миттєво. У цій статті розбираємо технічні деталі: від вибору плеєра до кешування чанків та обробки втрати мережі, спираючись на досвід понад 20 реалізованих аудіорішень.
Як реалізувати архітектуру плеєра для потокового аудіо?
Досягти того, щоб плеєр переживав перестворення Activity на Android та ViewController на iOS, можна за допомогою підходу сервісного шару. Розглянемо на прикладі реальних проектів.
Android. Сучасний метод — media3 MediaSessionService. Плеєр живе в окремому сервісі, Activity лише відображає стан. MediaController зв'язує UI з сервісом через Binder/IPC. При знищенні Activity плеєр продовжує працювати. Порівняно з застарілим MediaPlayer, MediaSessionService забезпечує вдвічі менше споживання пам'яті.
// У MediaSessionService val player = ExoPlayer.Builder(this).build() val mediaSession = MediaSession.Builder(this, player).build() override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession iOS. AVAudioSession.sharedInstance().setCategory(.playback) + UIBackgroundModes: audio в Info.plist. Плеєр створюється в AppDelegate або окремому синглтоні, переживає перестворення ViewController. Не забудьте активувати сесію: try? AVAudioSession.sharedInstance().setActive(true), інакше плеєр зупиниться при блокуванні екрану.
Пошагова інструкція з реалізації фонового відтворення
- Налаштуйте
UIBackgroundModesвключаючиaudioдля iOS та сервіс для Android. - Створіть екземпляр плеєра (ExoPlayer/AVPlayer) у сервісному шарі, а не в UI-компонентах.
- Прив'яжіть медіа-сесію (MediaSession/AVAudioSession) до плеєра.
- Реалізуйте обробку команд з медіа-контролів (play/pause/next).
- Протестуйте перемикання між додатками та блокування екрану.
Як налаштувати буферизацію та кешування для стабільного стрімінгу?
ExoPlayer буферизує вперед автоматично. Управління через DefaultLoadControl:
val loadControl = DefaultLoadControl.Builder() .setBufferDurationsMs( 15_000, // minBufferMs 50_000, // maxBufferMs 2_500, // bufferForPlaybackMs 5_000 // bufferForPlaybackAfterRebufferMs ) .build() minBufferMs = 15000 — плеєр починає відтворення після накопичення 2.5 с буфера, тримає в пам'яті до 50 с. При втраті мережі — продовжує грати з буфера 50 с, потім пауза з індикатором завантаження. Такий підхід знижує кількість переривань на 30% порівняно з налаштуваннями за замовчуванням.
Деталі налаштування буфера
Ці параметри адаптовані під live-стріми. Для подкастів можна збільшити maxBufferMs до 120 000 с. Для економії трафіку — зменшити до 15 000. ExoPlayer дозволяє змінювати LoadControl динамічно.
Для кешування на диск (щоб не перезавантажувати при поверненні до треку):
val cache = SimpleCache(cacheDir, LeastRecentlyUsedCacheEvictor(100 * 1024 * 1024)) val cacheDataSourceFactory = CacheDataSource.Factory() .setCache(cache) .setUpstreamDataSourceFactory(DefaultHttpDataSource.Factory()) iOS. AVURLAsset не кешує на диск нативно. Для кешування — AVAssetResourceLoader з кастомним AVAssetResourceLoadingDelegate, пишемо дані у файл при завантаженні. Або URLCache для HTTP-сегментів при HLS. ExoPlayer SimpleCache на 40% швидше, ніж кастомний ресурс-лоадер на iOS.
Порівняння підходів до кешування
| Підхід | Android | iOS | Складність |
|---|---|---|---|
| Вбудований кеш | ExoPlayer SimpleCache | URLCache | Низька |
| Кастомний ресурс-лоадер | Не потрібен | AVAssetResourceLoader | Середня |
| Проксі-сервер | LocalCacheDataSource | Нестандартно | Висока |
Які протоколи потокового аудіо використовуються?
| Протокол | Затримка | Застосування |
|---|---|---|
| HTTP progressive | нема | подкасти, один файл |
| HLS | 3–30 с | музичний стрімінг |
| Icecast/Shoutcast (MP3/AAC stream) | < 1 с | інтернет-радіо |
| OPUS over WebRTC | < 0.2 с | голосові чати |
Icecast-стріми (Content-Type: audio/mpeg з нескінченним тілом) — ExoPlayer обробляє як ProgressiveMediaSource. На iOS — AVPlayer справляється нативно через http:// URL потоку.
Що робити при втраті мережі?
Стрімінг — нестабільне середовище. При втраті з'єднання плеєр повинен автоматично спробувати перепідключитися, а не просто зупинитися.
ExoPlayer: LoadControl.getBackBufferDurationUs() зберігає вже відтворені дані в пам'яті. При перепідключенні — буфер не втрачається, відтворення продовжується з того місця де зупинилося. Для радіостріму (live) перепідключення означає отримання актуального фрагмента, а не того, що було до обриву.
На iOS: AVPlayer.automaticallyWaitsToMinimizeStalling = true — плеєр сам вирішує, коли накопичити достатньо буфера. При обриві HLS-стріму підписуємося на AVPlayerItem.status KVO, при .failed з NSURLErrorNetworkConnectionLost — replaceCurrentItem(with:) з новим AVPlayerItem від того ж URL через 3–5 секунд.
Метадані IcyCast
Радіостанції передають метадані (назву треку) прямо в потоці через ICY-заголовки. ExoPlayer IcyDecoder читає їх автоматично, отримуємо через Player.Listener.onMediaMetadataChanged. На iOS нативно не підтримується — потрібен кастомний AVAssetResourceLoadingDelegate з парсингом ICY.
Що входить в реалізацію
- Архітектурна схема — вибір стратегії кешування, сервісний шар плеєра.
- Налаштування MediaSession/AVAudioSession з коректними категоріями та обробкою аудіорозривів.
- Інтеграція буферизації та кешування — конфігурація ExoPlayer/AVURLAsset, кастомні лоадери.
- Тестування на реальних умовах мережі — імітація втрати сигналу, перемикання Wi-Fi/4G.
- Документація та код-рев'ю — розгорнуті коментарі в коді, архітектурний опис.
Типові помилки та їх уникнення
-
Не забути активувати AVAudioSession — без
setActive(true)плеєр зупиняється при блокуванні екрану. - Використання плеєра в Activity/ViewController — при перестворенні втрачається стан. Завжди виносите плеєр у сервісний шар.
- Не налаштований LoadControl — за замовчуванням буфер малий, що призводить до частих переривань на слабкій мережі.
- Ігнорування ICY-метаданих — на iOS потрібен кастомний делегат, інакше назва треку не відображатиметься.
Терміни та досвід
Базовий аудіо-стрімінг з фоновим відтворенням та медіаконтролами — від 2 днів. Кешування чанків на диск, обробка Icecast-метаданих та офлайн-режим — від 3–4 днів. Все залежить від складності: якщо потрібна кастомна логіка перепідключення або інтеграція з Firebase, терміни можуть збільшитися на 1–2 дні.
Наша команда має понад 10 років досвіду в мобільній розробці та понад 20 реалізованих аудіопроектів. Ми гарантуємо, що плеєр буде стабільно працювати в умовах поганої мережі та після багаторазових перезапусків додатку.
Готові взятися за ваш проект? Оцінимо задачу за один робочий день — розкажіть про специфіку свого додатку, і ми запропонуємо оптимальне архітектурне рішення. Зв'яжіться з нами для консультації, і ми підберемо оптимальну стратегію для вашого проекту. Отримайте оцінку вашого проекту вже сьогодні.







