Реализация аудиоплеера в мобильном приложении
Когда пользователь сворачивает приложение, аудио должно продолжать играть — и на экране блокировки должны появиться кнопки управления. Это не «дополнительная фича», а ожидаемое поведение. Именно здесь большинство реализаций ломается.
Фоновое воспроизведение
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.
Перемотка (seeking)
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 автоматически применяет pitch correction через 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 дня.







