В этом уроке:
- используем MediaPlayer
MediaPlayer – класс, который позволит вам проигрывать аудио/видео файлы с возможностью сделать паузу и перемотать в нужную позицию. MediaPlayer умеет работать с различными источниками, это может быть: путь к файлу (на SD или в инете), адрес потока, Uri или файл из папки res/raw.
Напишем небольшое приложение аудио-плеер и используем в нем все эти возможности.
Создадим проект:
Project name: P1261_MediaPlayer
Build Target: Android 2.3.3
Application name: MediaPlayer
Package name: ru.startandroid.develop.p1261mediaplayer
Create Activity: MainActivity
Добавляем строки в strings.xml:
<string name="http">HTTP</string> <string name="stream">Stream</string> <string name="sd">SD</string> <string name="uri">Uri</string> <string name="raw">Raw</string> <string name="pause">Pause</string> <string name="resume">Resume</string> <string name="stop">Stop</string> <string name="loop">Loop</string> <string name="backward">Back</string> <string name="forward">Forw</string> <string name="info">Info</string>
layout-файл main.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dp" tools:context=".MainActivity"> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_marginTop="10dp"> <Button android:id="@+id/btnStartHttp" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClickStart" android:text="@string/http" android:textSize="12sp"> </Button> <Button android:id="@+id/btnStartStream" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClickStart" android:text="@string/stream" android:textSize="12sp"> </Button> <Button android:id="@+id/btnStartSD" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClickStart" android:text="@string/sd" android:textSize="12sp"> </Button> <Button android:id="@+id/btnStartUri" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClickStart" android:text="@string/uri" android:textSize="12sp"> </Button> <Button android:id="@+id/btnStartRaw" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClickStart" android:text="@string/raw" android:textSize="12sp"> </Button> </LinearLayout> <LinearLayout android:id="@+id/linearLayout2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_below="@+id/linearLayout1" android:layout_marginTop="30dp"> <Button android:id="@+id/btnPause" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="@string/pause" android:textSize="12sp"> </Button> <Button android:id="@+id/btnResume" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="@string/resume" android:textSize="12sp"> </Button> <Button android:id="@+id/btnStop" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="@string/stop" android:textSize="12sp"> </Button> <CheckBox android:id="@+id/chbLoop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/loop"> </CheckBox> </LinearLayout> <LinearLayout android:id="@+id/linearLayout3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_below="@+id/linearLayout2" android:layout_marginTop="10dp"> <Button android:id="@+id/btnBackward" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="@string/backward" android:textSize="12sp"> </Button> <Button android:id="@+id/btnForward" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="@string/forward" android:textSize="12sp"> </Button> <Button android:id="@+id/btnInfo" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onClick" android:text="@string/info" android:textSize="12sp"> </Button> </LinearLayout> </RelativeLayout>
Так это выглядит на экране
Кнопки верхнего ряда запускают проигрывание треков из различных источников. Кнопки среднего ряда – это пауза, возобновление, стоп и чекбокс повторения трека. А в нижнем ряду кнопки перемотки назад/вперед и вывод в лог текущей информации.
Создайте папку res/raw и положите в нее какой-нить звуковой файл с именем explosion.mp3. Например, его можно скачать здесь - http://dl.dropboxusercontent.com/u/6197740/explosion.mp3.
MainActivity.java:
package ru.startandroid.develop.p1261mediaplayer; import java.io.IOException; import android.app.Activity; import android.content.ContentUris; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; public class MainActivity extends Activity implements OnPreparedListener, OnCompletionListener { final String LOG_TAG = "myLogs"; final String DATA_HTTP = "http://dl.dropboxusercontent.com/u/6197740/explosion.mp3"; final String DATA_STREAM = "http://online.radiorecord.ru:8101/rr_128"; final String DATA_SD = Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + "/music.mp3"; final Uri DATA_URI = ContentUris .withAppendedId( android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 13359); MediaPlayer mediaPlayer; AudioManager am; CheckBox chbLoop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); am = (AudioManager) getSystemService(AUDIO_SERVICE); chbLoop = (CheckBox) findViewById(R.id.chbLoop); chbLoop.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mediaPlayer != null) mediaPlayer.setLooping(isChecked); } }); } public void onClickStart(View view) { releaseMP(); try { switch (view.getId()) { case R.id.btnStartHttp: Log.d(LOG_TAG, "start HTTP"); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(DATA_HTTP); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); Log.d(LOG_TAG, "prepareAsync"); mediaPlayer.setOnPreparedListener(this); mediaPlayer.prepareAsync(); break; case R.id.btnStartStream: Log.d(LOG_TAG, "start Stream"); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(DATA_STREAM); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); Log.d(LOG_TAG, "prepareAsync"); mediaPlayer.setOnPreparedListener(this); mediaPlayer.prepareAsync(); break; case R.id.btnStartSD: Log.d(LOG_TAG, "start SD"); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(DATA_SD); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.prepare(); mediaPlayer.start(); break; case R.id.btnStartUri: Log.d(LOG_TAG, "start Uri"); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(this, DATA_URI); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.prepare(); mediaPlayer.start(); break; case R.id.btnStartRaw: Log.d(LOG_TAG, "start Raw"); mediaPlayer = MediaPlayer.create(this, R.raw.explosion); mediaPlayer.start(); break; } } catch (IOException e) { e.printStackTrace(); } if (mediaPlayer == null) return; mediaPlayer.setLooping(chbLoop.isChecked()); mediaPlayer.setOnCompletionListener(this); } private void releaseMP() { if (mediaPlayer != null) { try { mediaPlayer.release(); mediaPlayer = null; } catch (Exception e) { e.printStackTrace(); } } } public void onClick(View view) { if (mediaPlayer == null) return; switch (view.getId()) { case R.id.btnPause: if (mediaPlayer.isPlaying()) mediaPlayer.pause(); break; case R.id.btnResume: if (!mediaPlayer.isPlaying()) mediaPlayer.start(); break; case R.id.btnStop: mediaPlayer.stop(); break; case R.id.btnBackward: mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() - 3000); break; case R.id.btnForward: mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() + 3000); break; case R.id.btnInfo: Log.d(LOG_TAG, "Playing " + mediaPlayer.isPlaying()); Log.d(LOG_TAG, "Time " + mediaPlayer.getCurrentPosition() + " / " + mediaPlayer.getDuration()); Log.d(LOG_TAG, "Looping " + mediaPlayer.isLooping()); Log.d(LOG_TAG, "Volume " + am.getStreamVolume(AudioManager.STREAM_MUSIC)); break; } } @Override public void onPrepared(MediaPlayer mp) { Log.d(LOG_TAG, "onPrepared"); mp.start(); } @Override public void onCompletion(MediaPlayer mp) { Log.d(LOG_TAG, "onCompletion"); } @Override protected void onDestroy() { super.onDestroy(); releaseMP(); } }
Разбираемся. Сначала создаем константы-пути, которые будет использовать проигрыватель. Это файл в инете (DATA_HTTP), поток в инете (DATA_STREAM), файл на флэшке (DATA_SD) и Uri на мелодию из системы (DATA_URI). Для SD и Uri укажите ваши значения, чтобы такие файлы существовали. (По получению Uri в конце урока есть вспомогательный код)
В onCreate получаем AudioManager, находим на экране чекбокс и настраиваем так, чтобы он включал/выключал режим повтора для плеера.
onClickStart – метод для обработки нажатий на кнопки верхнего ряда. Сначала мы освобождаем ресурсы текущего проигрывателя. Затем в зависимости от нажатой кнопки стартуем проигрывание. Какие методы для этого используются?
setDataSource – задает источник данных для проигрывания
setAudioStreamType – задает аудио-поток, который будет использован для проигрывания. Их существует несколько: STREAM_MUSIC, STREAM_NOTIFICATION и п. Подробнее их можно посмотреть в доках по AudioManager. Предполагаю, что созданы они для того, чтобы можно было задавать разные уровни громкости, например, играм, звонкам и уведомлениям. Этот метод можно и пропустить, если вам не надо явно указывать какой-то поток. Насколько я понял, по умолчанию используется STREAM_MUSIC.
Далее используется метод prepare или prepareAsync (в паре с OnPreparedListener). Эти методы подготавливают плеер к проигрыванию. И, как понятно из названия, prepareAsync делает это асинхронно, и, когда все сделает, сообщит об этом слушателю из метода setOnPreparedListener. А метод prepare работает синхронно. Соотвественно, если хотим прослушать файл из инета, то используем prepareAsync, иначе наше приложение повесится, т.к. заблокируется основной поток, который обслуживает UI.
Ну и метод start запускает проигрывание.
В случае с raw-файлом мы используем метод create. В нем уже будет выполнен метод prepare и нам остается только выполнить start.
Далее мы для плеера включаем/выключаем повтор (setLooping) в зависимости от текущего значения чекбокса. И вешаем слушателя (setOnCompletionListener), который получит уведомление, когда проигрывание закончится.
В методе releaseMP мы выполняем метод release. Он освобождает используемые проигрывателем ресурсы, его рекомендуется вызывать когда вы закончили работу с плеером. Более того, хелп рекомендует вызывать этот метод и при onPause/onStop, если нет острой необходимости держать объект.
В методе onClick мы обрабатываем нажатия на кнопки управления проигрывателем. Какие здесь используются методы?
pause – приостанавливает проигрывание
start – возобновляет проигрывание
stop – останавливает проигрывание
seekTo – переход к определенной позиции трека (в милисекундах)
getCurrentPosition – получить текущую позицию (в милисекундах)
getDuration – общая продолжительность трека
isLooping – включен ли режим повтора
getStreamVolume – получить уровень громкости указанного потока
Далее идут методы
onPrepared – метод слушателя OnPreparedListener. Вызывается, когда плеер готов к проигрыванию.
onCompletion – метод слушателя OnCompletionListener. Вызывается, когда достигнут конец проигрываемого содержимого.
В методе onDestroy обязательно освобождаем ресурсы проигрывателя.
В манифесте добавляем права на интернет - android.permission.INTERNET.
Все сохраняем, запускаем приложение. Дизайн получившегося плеера, конечно, не ахти какой :), но нас сейчас интересует функционал.
Еще раз перечислю возможные действия. Нажимая верхние кнопки, мы запускаем проигрывание из различных источников. Кнопки среднего ряда позволят нам поставить паузу, возобновить/остановить проигрывание и включить режим повтора. Кнопки нижнего ряда перематывают назад/вперед на 3 сек (3000 мсек) и выводят инфу в лог.
Я включу проигрывание файла с SD и выведу инфу в лог (кнопка Info).
start SD
Playing true
Time 4702 / 170588
Looping false
Volume 10
Проигрывание идет, текущая позиция – 4-я секунда из 170, режим повтора выключен, громкость - 10.
Уменьшу громкость (кнопками устройства или эмулятора), включу режим повтора (чекбокс Loop), поставлю паузу (кнопка Pause) и снова выведу инфу в лог:
Playing false
Time 46237 / 170588
Looping true
Volume 6
Видим, что проигрывание остановилось, текущая позиция уже 46 секунд, режим повтора включен, а громкость уменьшилась до 6.
Теперь включу проигрывание потока (кнопка Stream). Смотрим лог:
08:49:13.799: D/myLogs(18805): start Stream
08:49:13.809: D/myLogs(18805): prepareAsync
08:49:27.589: D/myLogs(18805): onPrepared
Обратите внимание, сколько прошло времени с начала (prepareAsync) до завершения (onPrepared) подготовки проигрывателя – 14 секунд. Если бы мы использовали метод prepare, а не prepareAsync, то наше приложение было бы недоступно все это время.
Расскажу еще про несколько методов, которые я не использовал в примере, но о которых стоит знать.
Метод reset – сбрасывает плеер в начальное состояние, после него необходимо снова вызвать setDataSource и prepare. Похож на onRelease, но позволяет продолжить работу с этим же объектом. А вот после onRelease надо создавать новый объект MediaPlayer.
Метод setOnBufferingUpdateListener устанавливает слушателя буферизации проигрываемого потока. По идее слушатель будет получать процент буферизации, но у меня оно как-то странно работает - показывает или 0 или 100.
Метод setOnErrorListener устанавливает слушателя для получения ошибок. Особенно это полезно при методe prepareAsync. Если в ходе этого метода возникнут ошибки, то их можно поймать только так.
Метод setWakeMode позволяет ставить стандартную (PowerManager.WakeLock) блокировку на время проигрывания, указав тип блокировки. Не забудьте в манифесте добавить права на WAKE_LOCK.
По поводу видео. Метод setDisplay позволяет указать плееру, куда выводить изображение. Размещаете на экране компонент SurfaceView (вкладка Advanced), вызываете его метод getHolder и полученный объект передаете в setDisplay. Плеер выведет изображение на этот компонент.
Чтобы узнать размер проигрываемого изображения можно использовать методы getVideoHeight и getVideoWidth.
В хелпе класса MediaPlayer есть хорошая схема состояний плеера. Она кажется запутанной, но если посидеть и поразбираться, то вполне можно все понять. Схема полезная, советую вникнуть.
А здесь можно посмотреть какие форматы поддерживаются системой.
Также хелп любезно предоставляет нам код, который позволит просмотреть существующие медиа-файлы в системе:
ContentResolver contentResolver = getContentResolver(); Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; Cursor cursor = contentResolver.query(uri, null, null, null, null); if (cursor == null) { // query failed, handle error. } else if (!cursor.moveToFirst()) { // no media on the device } else { int titleColumn = cursor .getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE); int idColumn = cursor .getColumnIndex(android.provider.MediaStore.Audio.Media._ID); do { long thisId = cursor.getLong(idColumn); String thisTitle = cursor.getString(titleColumn); // ...process entry... } while (cursor.moveToNext()); }
На следующем уроке:
- работаем с SoundPool
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня