В этой серии мы создаем простое музыкальное приложение для Android. Пока что мы представили список песен на устройстве и позволили пользователю выбирать из него, начиная воспроизведение с помощью класса MediaPlayer
классе Service
. В этой заключительной части серии мы позволим пользователю управлять воспроизведением, включая переход к следующей и предыдущей дорожкам, ускоренную перемотку вперед, перемотку назад, воспроизведение, паузу и поиск определенных точек дорожки. Мы также будем отображать уведомление во время воспроизведения, чтобы пользователь мог вернуться к музыкальному проигрывателю после использования других приложений.
Ищете ярлык?
Эта серия учебных пособий знакомит вас с процессом создания музыкального проигрывателя с нуля. Если вам нужно готовое решение, попробуйте Android Music Player , продвинутый музыкальный проигрыватель для устройств Android. Он позволяет просматривать и воспроизводить музыку по альбомам, исполнителям, песням, спискам воспроизведения, папкам и исполнителям альбомов, а также имеет ряд других функций.
Или, если вы хотите, чтобы что-то полностью соответствовало вашим спецификациям, вы можете нанять разработчика Android для Envato Studio, чтобы сделать что угодно, от настроек и исправлений ошибок до создания целого приложения с нуля.
Вступление
Функциональность управления музыкальным проигрывателем будет реализована с использованием класса MediaController
, в котором экземпляр SeekBar
отображает ход воспроизведения, а также позволяет пользователю переходить к определенным местам на дорожке. Мы будем использовать классы Notification
и PendingIntent
чтобы отобразить заголовок текущей воспроизводимой дорожки и позволить пользователю вернуться к приложению.
Вот как должно выглядеть приложение после завершения этого урока:
После этой серии мы также рассмотрим связанные функции, которые вы, возможно, захотите использовать для улучшения приложения музыкального проигрывателя. Это будет включать воспроизведение видео, потоковое воспроизведение мультимедиа, управление аудиофокусом и представление мультимедийных данных различными способами.
1. Создайте контроллер
Шаг 1
Откройте основной класс Activity
и добавьте следующий оператор импорта:
1
|
import android.widget.MediaController.MediaPlayerControl;
|
Расширьте начальную строку объявления класса следующим образом, чтобы мы могли использовать класс Activity
для обеспечения управления воспроизведением:
1
|
public class MainActivity extends Activity implements MediaPlayerControl {
|
Наведите указатель мыши на имя класса и выберите « Добавить не реализованные методы» . Eclipse добавит различные методы управления воспроизведением, которые мы адаптируем по мере продвижения.
Шаг 2
Класс MediaController
представляет стандартный виджет с кнопками воспроизведения / паузы, перемотки назад, ускоренной перемотки вперед и пропуска (предыдущий / следующий). Виджет также содержит панель поиска, которая обновляется по мере воспроизведения песни и содержит текст, указывающий продолжительность песни и текущее положение проигрывателя. Чтобы мы могли настроить детали элемента управления, мы реализуем класс для его расширения. Добавьте новый класс в свой проект, назвав его MusicController . В Eclipse выберите android.widget.MediaController в качестве суперкласса при его создании.
Дайте классу следующее содержание:
1
2
3
4
5
6
7
8
9
|
public class MusicController extends MediaController {
public MusicController(Context c){
super(c);
}
public void hide(){}
}
|
Вы можете адаптировать класс MediaController
различными способами. Все, что мы хотим сделать, это остановить автоматическое скрытие через три секунды, переопределив метод hide
.
Шаг 3
Вернувшись в свой основной класс Activity
, добавьте новую переменную экземпляра:
1
|
private MusicController controller;
|
Мы будем настраивать контроллер более одного раза в жизненном цикле приложения, поэтому давайте сделаем это вспомогательным методом. Добавьте следующий фрагмент кода в свой класс Activity
:
1
2
3
|
private void setController(){
//set the controller up
}
|
Внутри метода создайте экземпляр контроллера:
1
|
controller = new MusicController(this);
|
Вы можете настроить различные аспекты экземпляра MediaController
. Например, нам нужно определить, что произойдет, когда пользователь нажмет предыдущую / следующую кнопку. После создания экземпляра контроллера установите эти прослушиватели щелчков:
01
02
03
04
05
06
07
08
09
10
11
|
controller.setPrevNextListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
playNext();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
playPrev();
}
});
|
Мы реализуем playNext
и playPrev
чуть позже, поэтому пока игнорируем ошибки. По- setController
методе setController
установите контроллер для работы с воспроизведением мультимедиа в приложении, и его привязка к просмотру будет относиться к списку, включенному в макет:
1
2
3
|
controller.setMediaPlayer(this);
controller.setAnchorView(findViewById(R.id.song_list));
controller.setEnabled(true);
|
Вернувшись в onCreate
, вызовите метод:
1
|
setController();
|
Мы также назовем это позже в другом месте в классе.
2. Реализация контроля воспроизведения
Шаг 1
Помните, что воспроизведение мультимедиа происходит в классе Service
, но пользовательский интерфейс происходит из класса Activity
. В предыдущем уроке мы связывали экземпляр Activity
экземпляром Service
, чтобы мы могли управлять воспроизведением из пользовательского интерфейса. Методы в нашем классе Activity
которые мы добавили для реализации интерфейса MediaPlayerControl
будут вызываться, когда пользователь пытается управлять воспроизведением. Нам понадобится класс Service
для работы с этим элементом управления, поэтому откройте свой класс Service
сейчас, чтобы добавить к нему еще несколько методов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public int getPosn(){
return player.getCurrentPosition();
}
public int getDur(){
return player.getDuration();
}
public boolean isPng(){
return player.isPlaying();
}
public void pausePlayer(){
player.pause();
}
public void seek(int posn){
player.seekTo(posn);
}
public void go(){
player.start();
}
|
Все эти методы применяются к стандартным функциям управления воспроизведением, ожидаемым пользователем.
Шаг 2
Теперь давайте добавим методы в класс Service
для пропуска следующего и предыдущего треков. Начните с предыдущей функции:
1
2
3
4
5
|
public void playPrev(){
songPosn—;
if(songPosn<0) songPosn=songs.size()-1;
playSong();
}
|
Мы уменьшаем переменную индекса песни, проверяем, что мы не вышли за пределы диапазона списка, и вызываем метод playSong
который мы добавили. Теперь добавьте метод, чтобы перейти к следующей дорожке:
1
2
3
4
5
6
|
//skip to next
public void playNext(){
songPosn++;
if(songPosn>=songs.size()) songPosn=0;
playSong();
}
|
Это аналогично методу воспроизведения предыдущего трека в данный момент, но мы изменим этот метод позже, чтобы реализовать функцию случайного воспроизведения.
Шаг 3
Теперь переключитесь обратно на ваш класс Activity
чтобы мы могли использовать эти методы. Сначала добавьте методы, которые мы вызывали при настройке контроллера:
01
02
03
04
05
06
07
08
09
10
11
|
//play next
private void playNext(){
musicSrv.playNext();
controller.show(0);
}
//play previous
private void playPrev(){
musicSrv.playPrev();
controller.show(0);
}
|
Мы вызываем методы, которые мы добавили в класс Service
. Мы добавим больше кода к ним позже, чтобы позаботиться о конкретных ситуациях. Теперь давайте обратимся к интерфейсным методам MediaPlayerControl
, которые будут вызываться системой во время воспроизведения и при взаимодействии пользователя с элементами управления. Эти методы уже должны быть в вашем классе Activity
, поэтому мы просто изменим их реализацию.
Начните с метода canPause
, установив для него значение true :
1
2
3
4
|
@Override
public boolean canPause() {
return true;
}
|
Теперь сделайте то же самое для методов canSeekBackward
и canSeekForward
:
1
2
3
4
5
6
7
8
9
|
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
|
Вы можете оставить методы getAudioSessionId
и getBufferPercentage
как они есть. getCurrentPosition
метод getCurrentPosition
следующим образом:
1
2
3
4
5
6
|
@Override
public int getCurrentPosition() {
if(musicSrv!=null && musicBound && musicSrv.isPng())
return musicSrv.getPosn();
else return 0;
}
|
Условные тесты должны избегать различных исключений, которые могут возникнуть при использовании классов MediaPlayer
и MediaController
. Если вы попытаетесь улучшить приложение каким-либо образом, вы, вероятно, обнаружите, что вам нужно предпринять такие шаги, поскольку классы воспроизведения мультимедиа выдают множество исключений. Обратите внимание, что мы вызываем метод getPosn
класса Service
.
Изменить метод getDuration
аналогично:
1
2
3
4
5
6
|
@Override
public int getDuration() {
if(musicSrv!=null && musicBound && musicSrv.isPng())
return musicSrv.getDur();
else return 0;
}
|
isPlaying
метод isPlaying
, вызвав метод isPng
нашего класса Service
:
1
2
3
4
5
6
|
@Override
public boolean isPlaying() {
if(musicSrv!=null && musicBound)
return musicSrv.isPng();
return false;
}
|
Сделайте то же самое для методов pause
, seekTo
и start
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@Override
public void pause() {
musicSrv.pausePlayer();
}
@Override
public void seekTo(int pos) {
musicSrv.seek(pos);
}
@Override
public void start() {
musicSrv.go();
}
|
3. Обработайте навигацию обратно в приложение
Шаг 1
Помните, что мы собираемся продолжить воспроизведение, даже когда пользователь уходит из приложения. Чтобы облегчить это, мы отобразим уведомление, показывающее название воспроизводимой дорожки. Нажатие на уведомление вернет пользователя в приложение. Вернитесь к своему классу Service
и добавьте следующие дополнительные импорты:
1
2
3
|
import java.util.Random;
import android.app.Notification;
import android.app.PendingIntent;
|
Теперь перейдите к методу onPrepared
, в котором мы в настоящее время просто запускаем воспроизведение. После вызова player.start()
добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
Intent notIntent = new Intent(this, MainActivity.class);
notIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendInt = PendingIntent.getActivity(this, 0,
notIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(this);
builder.setContentIntent(pendInt)
.setSmallIcon(R.drawable.play)
.setTicker(songTitle)
.setOngoing(true)
.setContentTitle("Playing")
.setContentText(songTitle);
Notification not = builder.build();
startForeground(NOTIFY_ID, not);
|
Мы добавим отсутствующие переменные дальше. Класс PendingIntent
вернет пользователя в основной класс Activity
при выборе уведомления. Добавьте переменные для названия песни и идентификатора уведомления в верхней части класса:
1
2
|
private String songTitle="";
private static final int NOTIFY_ID=1;
|
Теперь нам нужно установить название песни в методе playSong
после строки, в которой мы получаем песню из списка ( Song playSong = songs.get(songPosn);
):
1
|
songTitle=playSong.getTitle();
|
Поскольку мы вызвали setForeground
в уведомлении, нам нужно убедиться, что мы остановим его, когда экземпляр Service
будет уничтожен. Переопределите следующий метод:
1
2
3
4
|
@Override
public void onDestroy() {
stopForeground(true);
}
|
4. Воспроизведение в случайном порядке
Шаг 1
Помните, что мы добавили кнопку перемешивания, поэтому давайте реализуем это сейчас. Сначала добавьте новые переменные экземпляра в класс Service
:
1
2
|
private boolean shuffle=false;
private Random rand;
|
onCreate
генератор случайных чисел в onCreate
:
1
|
rand=new Random();
|
Теперь добавьте метод для установки флага shuffle:
1
2
3
4
|
public void setShuffle(){
if(shuffle) shuffle=false;
else shuffle=true;
}
|
Мы просто включим и выключим режим перемешивания. Мы будем проверять этот флаг, когда пользователь переходит к следующему треку или когда трек заканчивается и начинается следующий. Изменить метод playNext
следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public void playNext(){
if(shuffle){
int newSong = songPosn;
while(newSong==songPosn){
newSong=rand.nextInt(songs.size());
}
songPosn=newSong;
}
else{
songPosn++;
if(songPosn>=songs.size()) songPosn=0;
}
playSong();
}
|
Если флаг воспроизведения в случайном порядке включен, мы выбираем новую песню из списка случайным образом, не повторяя последнюю сыгранную песню. Вы можете расширить эту функциональность, используя очередь песен и предотвращая повторение любой песни, пока все песни не будут воспроизведены.
Шаг 2
Теперь мы можем позволить пользователю выбрать функцию перемешивания. Вернувшись в свой основной класс Activity
в методе onOptionsItemSelected
, onOptionsItemSelected
раздел для действия shuffle, чтобы вызвать новый метод, который мы добавили в класс Service
:
1
2
3
|
case R.id.action_shuffle:
musicSrv.setShuffle();
break;
|
Теперь пользователь сможет использовать пункт меню для переключения функций перетасовки.
5. Убирать
Шаг 1
Мы почти закончили, но все еще нужно добавить несколько битов обработки, чтобы позаботиться об определенных изменениях, таких как пользователь, покидающий приложение или приостанавливающий воспроизведение. В вашем классе Activity
добавьте еще пару переменных экземпляра:
1
|
private boolean paused=false, playbackPaused=false;
|
Мы будем использовать их для того, чтобы пользователь возвращался к приложению после его выхода и взаимодействовал с элементами управления, когда само воспроизведение приостановлено. Переопределите onPause
чтобы установить один из следующих флагов:
1
2
3
4
5
|
@Override
protected void onPause(){
super.onPause();
paused=true;
}
|
Теперь переопределите onResume
:
1
2
3
4
5
6
7
8
|
@Override
protected void onResume(){
super.onResume();
if(paused){
setController();
paused=false;
}
}
|
Это обеспечит отображение контроллера при возврате пользователя в приложение. Переопределите onStop
чтобы скрыть это:
1
2
3
4
5
|
@Override
protected void onStop() {
controller.hide();
super.onStop();
}
|
Шаг 2
Если пользователь взаимодействует с элементами управления, когда воспроизведение приостановлено, объект MediaPlayer
может вести себя непредсказуемо. Чтобы справиться с этим, мы установим и будем использовать флаг playbackPaused
. Сначала playPrev
методы playNext
и playPrev
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
private void playNext(){
musicSrv.playNext();
if(playbackPaused){
setController();
playbackPaused=false;
}
controller.show(0);
}
private void playPrev(){
musicSrv.playPrev();
if(playbackPaused){
setController();
playbackPaused=false;
}
controller.show(0);
}
|
Мы сбрасываем контроллер и обновляем флаг воспроизведения, когда воспроизведение было приостановлено. Теперь внесите аналогичные изменения в метод playSong
:
1
2
3
4
5
6
7
8
9
|
public void songPicked(View view){
musicSrv.setSong(Integer.parseInt(view.getTag().toString()));
musicSrv.playSong();
if(playbackPaused){
setController();
playbackPaused=false;
}
controller.show(0);
}
|
Теперь установите для playbackPaused
значение true в методе pause
:
1
2
3
4
5
|
@Override
public void pause() {
playbackPaused=true;
musicSrv.pausePlayer();
}
|
MediaController
классами MediaPlayer
и MediaController
, вы обнаружите, что этот тип обработки является необходимым требованием во избежание ошибок. Например, вы иногда обнаружите, что панель поиска контроллера не обновляется, пока пользователь не взаимодействует с ней. Эти ресурсы ведут себя по-разному на разных уровнях API, поэтому тщательное тестирование и настройка необходимы, если вы планируете выпустить свое приложение для общественности. Приложение, которое мы создаем в этой серии, является лишь основой.
Шаг 3
Давайте сделаем несколько последних шагов, чтобы приложение работало согласованно. Вернувшись в класс Service
, onError
метод onError
:
1
2
3
4
5
|
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.reset();
return false;
}
|
Мы просто перезагружаем плеер, но вы, конечно, можете улучшить этот подход.
Метод onCompletion
будет onCompletion
, когда дорожка заканчивается, включая случаи, когда пользователь выбрал новую дорожку или пропустил следующую / предыдущую дорожку, а также когда дорожка достигает конца своего воспроизведения. В последнем случае мы хотим продолжить воспроизведение, проиграв следующий трек. Для этого нам нужно проверить состояние воспроизведения. Изменить метод onCompletion
:
1
2
3
4
5
6
7
|
@Override
public void onCompletion(MediaPlayer mp) {
if(player.getCurrentPosition()>0){
mp.reset();
playNext();
}
}
|
Мы вызываем метод playNext
если текущий трек достиг своего конца.
Service
реализовать интерфейс AudioManager.OnAudioFocusChangeListener
. В методе onCreate
создайте экземпляр класса AudioManager
и вызовите для него requestAudioFocus
. Наконец, onAudioFocusChange
метод onAudioFocusChange
в своем классе, чтобы контролировать, что должно происходить, когда приложение получает или теряет аудио-фокус. Смотрите раздел Audio Focus в Руководстве разработчика для более подробной информации. Это базовое приложение завершено! Тем не менее, вам может потребоваться внести дополнительные усовершенствования, чтобы обеспечить его надежную работу на пользовательских устройствах и уровнях API. Элементы управления должны появляться при каждом взаимодействии с приложением.
Уведомление должно позволить вам вернуться в приложение, пока продолжается воспроизведение.
Вывод
Мы завершили базовый музыкальный плеер для Android. Существует множество способов улучшить приложение, например добавить поддержку потокового мультимедиа, видео, фокусировки звука и предоставить различные методы взаимодействия с музыкальными треками на устройстве. Мы рассмотрим некоторые из этих улучшений в будущих руководствах, в которых рассказывается, как их можно добавить в приложение или другие проекты воспроизведения мультимедиа. А пока посмотрим, сможете ли вы расширить приложение для создания дополнительных функций или повышения надежности на разных устройствах. См. Раздел « Воспроизведение мультимедиа » в Руководстве разработчика Android для получения дополнительной информации.