Реалізація аудіоплеєра в мобільному застосунку
Коли користувач сворачує застосунок, аудіо повинно продовжити грати — та на екрані блокування повинні з'явитися кнопки управління. Це не «додаткова фіча», а очікувана поведінка. Саме тут більшість реалізацій ломається.
Фоновий відтворення
iOS. В Info.plist додаємо UIBackgroundModes: audio. AVAudioSession:
try AVAudioSession.sharedInstance().setCategory(
.playback,
mode: .default,
options: []
)
try AVAudioSession.sharedInstance().setActive(true)
.playback — категорія для плеєра. Без неї iOS приостановит відтворення через 3 секунди після переходу у фон. Але важливіше: AVAudioSession потрібно активувати до початку відтворення, не після.
Android. MediaSessionCompat + MediaBrowserServiceCompat або, для нових проектів, media3 ExoPlayer з MediaSessionService. Сервіс об'являємо в AndroidManifest.xml з android:foregroundServiceType="mediaPlayback". Без foreground service Android 8+ убьє процес.
ExoPlayer vs AVPlayer
ExoPlayer (androidx.media3:media3-exoplayer) — стандарт для Android. Підтримує MP3, AAC, FLAC, OGG, WAV, M4A, OPUS з коробки. Управління через Player.Listener. Плейлист — MediaItem.Builder().setUri(uri).setMediaMetadata(metadata).build().
AVPlayer (iOS) — для одного трека. Для черги — AVQueuePlayer з AVPlayerItem. Додаємо спостереження через AVPlayerItem.status KVO та AVPlayerItemDidPlayToEndTimeNotification.
Перемотка
AVQueuePlayer на iOS: seek(to: CMTime, toleranceBefore: .zero, toleranceAfter: .zero) — точна перемотка. toleranceBefore/After: .zero повільніше, але точно попадає в потрібний кадр. Для слайдера з драгом достатньо CMTime(seconds: 0.5, ...) — швидше.
ExoPlayer: player.seekTo(positionMs). При SEEK_PRECISE стратегії — точно, але дороже по CPU.
Черга треків та метаданні
Метаданні трека (назва, виконавець, обложка) для Lock Screen передаємо через MPNowPlayingInfoCenter (iOS) або MediaSession.setMetadata() (Android).
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyTitle: track.title,
MPMediaItemPropertyArtist: track.artist,
MPMediaItemPropertyPlaybackDuration: track.duration,
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime,
MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: artworkSize) { _ in artworkImage }
]
Без MPMediaItemPropertyArtwork на Lock Screen відображається сірий прямокутник.
Обробка прерывань
Дзвінок телефона, інший застосунок з аудіо — переривання трапляються. iOS: AVAudioSession.interruptionNotification. Android: AudioFocusRequest з OnAudioFocusChangeListener. При AUDIOFOCUS_LOSS_TRANSIENT — пауза з авто-відновленням. При AUDIOFOCUS_LOSS — пауза без відновлення (інший застосунок перехопив фокус надовго).
Crossfade між треками
Плавний перехід між треками — деталь, яку замічають користувачі Spotify та Apple Music. На iOS: створюємо два AVAudioPlayer (або AVPlayerNode в AVAudioEngine), застосовуємо fade out до поточного та fade in до наступного через AVAudioEngine.mainMixerNode.volume з AVAudioTime. На Android: ExoPlayer не підтримує crossfade нативно — реалізуємо через AudioMixer (API 31+) або паралельні ExoPlayer з поступовим зниженням/підвищенням гучності через Handler.postDelayed.
Швидкість відтворення
Подкасти та аудіокниги часто слухають на 1.25x або 1.5x. iOS: AVPlayer.rate = 1.5. Android/ExoPlayer: player.playbackParameters = PlaybackParameters(1.5f). При прискоренні вище 1.5x аудіо без обробки звучить неприродно (ефект чіпманків) — ExoPlayer автоматично застосовує корекцію висоти звуку через SonicAudioProcessor. На iOS при rate > 2.0 потрібно явно виставити AVAudioTimePitchAlgorithm.timeDomain у AVPlayerItem.
Flutter: just_audio
just_audio (pub.dev) — найбільш повнофункціональний аудіоплеєр для Flutter. Підтримує чергу, цикли, перемішування, швидкість, фоновий відтворення через audio_service. AudioPlayer.createProgressiveAudioSource() для прогресивних URL, AudioPlayer.createHlsAudioSource() для HLS-потоків.
Орієнтири за часом
Аудіоплеєр із чергою, фоновим відтворенням та медіаконтролами — 2–3 дні. Користувацький UI з waveform-прогрес-баром та анімацією — плюс 1–2 дні.







