Как использовать 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(). Реализуйте этот поток — и плеер будет устойчивым и отзывчивым.