Как использовать MediaPlayer для воспроизведения и трекинга позиции

В двух словах: создайте MediaPlayer, подготовьте источник (prepareAsync для стрима/больших файлов), запуск — start(), позиция — getCurrentPosition(), для UI обновлений используйте Handler + Runnable (100–500 мс), а в onDestroy() всегда вызывайте release(). Ниже — компактные примеры и рекомендации, чтобы реализовать рабочий плеер за минуту.

Основы: инициализация и подготовка

MediaPlayer имеет явные состояния — важно не вызывать методы в неверном состоянии. Базовая схема на Kotlin:

mediaPlayer = MediaPlayer().apply {
    setDataSource(context, uri)            // локальный ресурс или URI
    setOnPreparedListener { mp -> mp.start() }
    setOnErrorListener { _, what, extra -> true }
    prepareAsync()                         // не блокировать UI
}

Если файл очень маленький и локальный, можно использовать prepare(), но для любых внешних/стриминговых источников — prepareAsync.

Для локальных raw-ресурсов удобно использовать MediaPlayer.create(context, R.raw.sample), но уязвимо к управлению состояниями — всё равно освобождайте ресурс.

Управление воспроизведением и seek

Стандартные методы:

  • start() — начать/возобновить;
  • pause() — поставить на паузу (позиция сохраняется);
  • stop() — остановить (потребуется подготовка заново);
  • seekTo(ms) — перейти к позиции в миллисекундах;
  • isPlaying — проверить текущее воспроизведение.

Пример обработчиков кнопок:

playBtn.setOnClickListener { if (mediaPlayer?.isPlaying == false) mediaPlayer?.start() }
pauseBtn.setOnClickListener { mediaPlayer?.pause() }
seekTo(positionMs)

Отслеживание позиции для UI (SeekBar / таймер)

Надежный вариант — Handler + Runnable, обновлять каждые 100–500 мс в зависимости от требуемой плавности:

private val handler = Handler(Looper.getMainLooper())
private val update = object : Runnable {
  override fun run() {
    mediaPlayer?.let {
      seekBar.max = it.duration
      seekBar.progress = it.currentPosition
      handler.postDelayed(this, 200)
    }
  }
}

Запускать после подготовки (onPrepared) и удалять колбэки в pause/onDestroy:

handler.post(update)           // старт обновлений
handler.removeCallbacks(update) // остановка

Для взаимодействия пользователя с SeekBar используйте onStartTrackingTouch/onStopTrackingTouch, и при перемещении от пользователя сразу вызывать seekTo(progress).

Не оставляйте Handler запущенным после паузы/уничтожения Activity — это утечка контекста и лишняя нагрузка.

Состояния, колбэки и обработка ошибок

Подпишитесь на:

  • setOnCompletionListener — окончание трека (остановите таймер, обновите UI);
  • setOnErrorListener — лог и корректный cleanup;
  • setOnBufferingUpdateListener — для стриминга показывать прогресс буфера.

Пример:

mediaPlayer?.setOnCompletionListener {
  handler.removeCallbacks(update)
  playBtn.text = "Play"
}

Лучшие практики и lifecycle

  • На фоне (фоновое воспроизведение) переместите логику в Service/Foreground Service с MediaSession.
  • В onPause() — при необходимости pause() + stop "обновления позиции".
  • В onDestroy() — обязательно release() и mediaPlayer = null.
  • Для стриминга и сложных требований рассмотрите ExoPlayer.

Пример lifecycle:

override fun onPause() {
  super.onPause()
  mediaPlayer?.pause()
  handler.removeCallbacks(update)
}
override fun onDestroy() {
  super.onDestroy()
  mediaPlayer?.release()
  mediaPlayer = null
}

Частые ошибки

  • Неправильные вызовы методов вне состояний (например, start перед подготовкой) — проверяйте isPlaying и onPrepared.
  • ANR при использовании prepare() для сетевых источников — используйте prepareAsync().
  • Утечки контекста из-за незакрытых Handler/Listeners.
  • Ошибки типа 1 или -2147483648 — чаще всего неверный путь или повреждённый файл.

FAQ

  • Нужно ли использовать ExoPlayer? Если нужен стриминг, адаптивный буферинг или продвинутая обработка — да. Для простого воспроизведения локальных аудиофайлов MediaPlayer достаточно.
  • Как точно выставить прогресс SeekBar при seekTo? В современных API используйте seekTo(ms, MediaPlayer.SEEK_CLOSEST) или просто seekTo(ms) и обновляйте UI после подтверждения позиционирования.
  • Как синхронизировать визуализацию (анимированная волна) с аудио? Для простоты синхронизации опирайтесь на getCurrentPosition(), для более точной графики используйте AudioTrack/Visualizers.

Статья даёт рабочую схему: prepareAsync → onPrepared start + handler → обновления через getCurrentPosition() → обработка seek → корректный release(). Реализуйте этот поток — и плеер будет устойчивым и отзывчивым.