Статьи

Создание музыкального проигрывателя на Android: элементы управления пользователя

В этой серии мы создаем простое музыкальное приложение для Android. Пока что мы представили список песен на устройстве и позволили пользователю выбирать из него, начиная воспроизведение с помощью класса MediaPlayer классе Service . В этой заключительной части серии мы позволим пользователю управлять воспроизведением, включая переход к следующей и предыдущей дорожкам, ускоренную перемотку вперед, перемотку назад, воспроизведение, паузу и поиск определенных точек дорожки. Мы также будем отображать уведомление во время воспроизведения, чтобы пользователь мог вернуться к музыкальному проигрывателю после использования других приложений.

Эта серия учебных пособий знакомит вас с процессом создания музыкального проигрывателя с нуля. Если вам нужно готовое решение, попробуйте Android Music Player , продвинутый музыкальный проигрыватель для устройств Android. Он позволяет просматривать и воспроизводить музыку по альбомам, исполнителям, песням, спискам воспроизведения, папкам и исполнителям альбомов, а также имеет ряд других функций.

Музыкальный проигрыватель Android
Музыкальный проигрыватель Android

Или, если вы хотите, чтобы что-то полностью соответствовало вашим спецификациям, вы можете нанять разработчика Android для Envato Studio, чтобы сделать что угодно, от настроек и исправлений ошибок до создания целого приложения с нуля.

Функциональность управления музыкальным проигрывателем будет реализована с использованием класса MediaController , в котором экземпляр SeekBar отображает ход воспроизведения, а также позволяет пользователю переходить к определенным местам на дорожке. Мы будем использовать классы Notification и PendingIntent чтобы отобразить заголовок текущей воспроизводимой дорожки и позволить пользователю вернуться к приложению.

Вот как должно выглядеть приложение после завершения этого урока:

Музыкальный проигрыватель Android

После этой серии мы также рассмотрим связанные функции, которые вы, возможно, захотите использовать для улучшения приложения музыкального проигрывателя. Это будет включать воспроизведение видео, потоковое воспроизведение мультимедиа, управление аудиофокусом и представление мультимедийных данных различными способами.

Откройте основной класс Activity и добавьте следующий оператор импорта:

1
import android.widget.MediaController.MediaPlayerControl;

Расширьте начальную строку объявления класса следующим образом, чтобы мы могли использовать класс Activity для обеспечения управления воспроизведением:

1
public class MainActivity extends Activity implements MediaPlayerControl {

Наведите указатель мыши на имя класса и выберите « Добавить не реализованные методы» . Eclipse добавит различные методы управления воспроизведением, которые мы адаптируем по мере продвижения.

Класс 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 .

Совет: вам может понадобиться настроить тему, используемую вашим приложением, чтобы обеспечить четкое отображение текста контроллера мультимедиа.

Вернувшись в свой основной класс 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();

Мы также назовем это позже в другом месте в классе.

Помните, что воспроизведение мультимедиа происходит в классе 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();
}

Все эти методы применяются к стандартным функциям управления воспроизведением, ожидаемым пользователем.

Теперь давайте добавим методы в класс 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();
}

Это аналогично методу воспроизведения предыдущего трека в данный момент, но мы изменим этот метод позже, чтобы реализовать функцию случайного воспроизведения.

Теперь переключитесь обратно на ваш класс 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();
}

Помните, что мы собираемся продолжить воспроизведение, даже когда пользователь уходит из приложения. Чтобы облегчить это, мы отобразим уведомление, показывающее название воспроизводимой дорожки. Нажатие на уведомление вернет пользователя в приложение. Вернитесь к своему классу 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);
}

Помните, что мы добавили кнопку перемешивания, поэтому давайте реализуем это сейчас. Сначала добавьте новые переменные экземпляра в класс 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();
}

Если флаг воспроизведения в случайном порядке включен, мы выбираем новую песню из списка случайным образом, не повторяя последнюю сыгранную песню. Вы можете расширить эту функциональность, используя очередь песен и предотвращая повторение любой песни, пока все песни не будут воспроизведены.

Теперь мы можем позволить пользователю выбрать функцию перемешивания. Вернувшись в свой основной класс Activity в методе onOptionsItemSelected , onOptionsItemSelected раздел для действия shuffle, чтобы вызвать новый метод, который мы добавили в класс Service :

1
2
3
case R.id.action_shuffle:
 musicSrv.setShuffle();
 break;

Теперь пользователь сможет использовать пункт меню для переключения функций перетасовки.

Мы почти закончили, но все еще нужно добавить несколько битов обработки, чтобы позаботиться об определенных изменениях, таких как пользователь, покидающий приложение или приостанавливающий воспроизведение. В вашем классе 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();
}

Если пользователь взаимодействует с элементами управления, когда воспроизведение приостановлено, объект 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, поэтому тщательное тестирование и настройка необходимы, если вы планируете выпустить свое приложение для общественности. Приложение, которое мы создаем в этой серии, является лишь основой.

Давайте сделаем несколько последних шагов, чтобы приложение работало согласованно. Вернувшись в класс 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

Мы завершили базовый музыкальный плеер для Android. Существует множество способов улучшить приложение, например добавить поддержку потокового мультимедиа, видео, фокусировки звука и предоставить различные методы взаимодействия с музыкальными треками на устройстве. Мы рассмотрим некоторые из этих улучшений в будущих руководствах, в которых рассказывается, как их можно добавить в приложение или другие проекты воспроизведения мультимедиа. А пока посмотрим, сможете ли вы расширить приложение для создания дополнительных функций или повышения надежности на разных устройствах. См. Раздел « Воспроизведение мультимедиа » в Руководстве разработчика Android для получения дополнительной информации.