Sunday, 21 December 2014

Create a Music Player on Android: Song Playback

In this series, we are creating a music player on Android using the MediaPlayer and MediaController classes. In the first part, we created the app and prepared the user interface for playback. We presented the list of songs on the user device and specified a method to execute when the user makes a selection. In this part of the series, we will implement a Service class to execute music playback continuously, even when the user is not directly interacting with the application.

We will need the app to bind to the music playing Service in order to interact with playback, so you will learn some of the basic aspects of the Service life cycle in this tutorial if you haven't explored them before. Here is a preview of the final result that we're working towards:
Android Music Player
In the final installment of this series, we'll add user control over the playback and we'll also make sure the app will continue to function in various application states. Later, we will follow the series up with additional enhancements that you may wish to add, such as audio focus control, video and streaming media playback, and alternate ways of presenting the media files.
Add a new class to your app, naming it MusicService or another name of your choice. Make sure it matches the name you listed in the Manifest. When creating the class in Eclipse, choose android.app.Service as its superclass. Eclipse should enter an outline:
1
2
3
4
5
6
7
8
9
public class MusicService extends Service {
 
  @Override
  public IBinder onBind(Intent arg0) {
    // TODO Auto-generated method stub
    return null;
  }
 
}
Extend the opening line of the class declaration to implement some interfaces we will use for music playback:
1
2
3
public class MusicService extends Service implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {
Eclipse will display an error over the class name. Hover over the error and choose Add unimplemented methods. We will add code to the methods in a few moments. The interfaces we are implementing will aid the process of interacting with the MediaPlayer class.
Your class will also need the following additional imports:
1
2
3
4
5
6
7
8
import java.util.ArrayList;
import android.content.ContentUris;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.PowerManager;
import android.util.Log;
Add the following instance variables to the new Service class:
1
2
3
4
5
6
//media player
private MediaPlayer player;
//song list
private ArrayList<Song> songs;
//current position
private int songPosn;
We will pass the list of songs into the Service class, playing from it using the MediaPlayer class and keeping track of the position of the current song using the songPosn instance variable. Now, implement the onCreate method for the Service:
1
2
3
public void onCreate(){
  //create the service
}
Inside onCreate, call the superclass method, instantiating the position and MediaPlayer instance variables:
1
2
3
4
5
6
//create the service
super.onCreate();
//initialize position
songPosn=0;
//create player
player = new MediaPlayer();
Next, let's add a method to initialize the MediaPlayer class, after the onCreate method:
1
2
3
public void initMusicPlayer(){
  //set player properties
}
Inside this method, we configure the music player by setting some of its properties as shown below:
1
2
3
player.setWakeMode(getApplicationContext(),
  PowerManager.PARTIAL_WAKE_LOCK);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
The wake lock will let playback continue when the device becomes idle and we set the stream type to music. Set the class as listener for (1) when the MediaPlayer instance is prepared, (2) when a song has completed playback, and when (3) an error is thrown:
1
2
3
player.setOnPreparedListener(this);
player.setOnCompletionListener(this);
player.setOnErrorListener(this);
Notice that these correspond to the interfaces we implemented. We will be adding code to the onPrepared, onCompletion, and onError methods to respond to these events. Back in onCreate, invoke initMusicPlayer:
1
initMusicPlayer();
It's time to add a method to the Service class to pass the list of songs from the Activity:
1
2
3
public void setList(ArrayList<Song> theSongs){
  songs=theSongs;
}
We will call this method later in the tutorial. This will form part of the interaction between the Activity and Service classes, for which we also need a Binder instance. Add the following snippet to the Service class after the setList method:
1
2
3
4
5
public class MusicBinder extends Binder {
  MusicService getService() {
    return MusicService.this;
  }
}
We will also access this from the Activity class.
Back in your app's main Activity class, you will need to add the following additional imports:
1
2
3
4
5
6
7
import android.os.IBinder;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.view.MenuItem;
import android.view.View;
And you'll also need to declare three new instance variables:
1
2
3
private MusicService musicSrv;
private Intent playIntent;
private boolean musicBound=false;
We are going to play the music in the Service class, but control it from the Activity class, where the application's user interface operates. To accomplish this, we will have to bind to the Service class. The above instance variables represent the Service class and Intent, as well as a flag to keep track of whether the Activity class is bound to the Service class or not. Add the following to your Activity class, after the onCreate method:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//connect to the service
private ServiceConnection musicConnection = new ServiceConnection(){
 
  @Override
  public void onServiceConnected(ComponentName name, IBinder service) {
    MusicBinder binder = (MusicBinder)service;
    //get service
    musicSrv = binder.getService();
    //pass list
    musicSrv.setList(songList);
    musicBound = true;
  }
 
  @Override
  public void onServiceDisconnected(ComponentName name) {
    musicBound = false;
  }
};
The callback methods will inform the class when the Activity instance has successfully connected to the Service instance. When that happens, we get a reference to the Service instance so that the Activity can interact with it. It starts by calling the method to pass the song list. We set the boolean flag to keep track of the binding status. You will need to import the Binder class we added to the Service class at the top of your Activity class:
1
import com.example.musicplayer.MusicService.MusicBinder;
Don't forget to alter the package and class names to suit your own if necessary.
We will want to start the Service instance when the Activity instance starts, so override the onStart method:
1
2
3
4
5
6
7
8
9
@Override
protected void onStart() {
  super.onStart();
  if(playIntent==null){
    playIntent = new Intent(this, MusicService.class);
    bindService(playIntent, musicConnection, Context.BIND_AUTO_CREATE);
    startService(playIntent);
  }
}
When the Activity instance starts, we create the Intent object if it doesn't exist yet, bind to it, and start it. Alter the code if you chose a different name for the Service class. Notice that we use the connection object we created so that when the connection to the bound Service instance is made, we pass the song list. We will also be able to interact with the Service instance in order to control playback later.
Return to your Service class to complete this binding process. Add an instance variable representing the inner Binder class we added:
1
private final IBinder musicBind = new MusicBinder();
Now amend the onBind method to return this object:
1
2
3
4
@Override
public IBinder onBind(Intent intent) {
  return musicBind;
}
Add the onUnbind method to release resources when the Service instance is unbound:
1
2
3
4
5
6
@Override
public boolean onUnbind(Intent intent){
  player.stop();
  player.release();
  return false;
}
This will execute when the user exits the app, at which point we will stop the service.
Let's now set the app up to play a track. In your Service class, add the following method:
1
2
3
public void playSong(){
  //play a song
}
Inside the method, start by resetting the MediaPlayer since we will also use this code when the user is playing subsequent songs:
1
player.reset();
Next, get the song from the list, extract the ID for it using its Song object, and model this as a URI:
1
2
3
4
5
6
7
8
//get song
Song playSong = songs.get(songPosn);
//get id
long currSong = playSong.getID();
//set uri
Uri trackUri = ContentUris.withAppendedId(
  android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  currSong);
We can now try setting this URI as the data source for the MediaPlayer instance, but an exception may be thrown if an error pops up so we use a try/catch block:
1
2
3
4
5
6
try{
  player.setDataSource(getApplicationContext(), trackUri);
}
catch(Exception e){
  Log.e("MUSIC SERVICE", "Error setting data source", e);
}
After the catch block, complete the playSong method by calling the asynchronous method of the MediaPlayer to prepare it:
1
player.prepareAsync();
When the MediaPlayer is prepared, the onPrepared method will be executed. Eclipse should have inserted it in your Service class. Inside this method, start the playback:
1
2
3
4
5
@Override
public void onPrepared(MediaPlayer mp) {
  //start playback
  mp.start();
}
In order for the user to select songs, we also need a method in the Service class to set the current song. Add it now:
1
2
3
public void setSong(int songIndex){
  songPosn=songIndex;
}
We will call this when the user picks a song from the list.
Advertisement
Remember that we added an onClick attribute to the layout for each item in the song list. Add that method to the main Activity class:
1
2
3
4
public void songPicked(View view){
  musicSrv.setSong(Integer.parseInt(view.getTag().toString()));
  musicSrv.playSong();
}
We set the song position as the tag for each item in the list view when we defined the Adapter class. We retrieve it here and pass it to the Service instance before calling the method to start the playback.
Before you run your app, implement the end button we added to the main menu. In your main Activity class, add the method to respond to menu item selection:
1
2
3
4
@Override
public boolean onOptionsItemSelected(MenuItem item) {
  //menu item selected
}
Inside the method, add a switch statement for the actions:
01
02
03
04
05
06
07
08
09
10
11
switch (item.getItemId()) {
case R.id.action_shuffle:
  //shuffle
  break;
case R.id.action_end:
  stopService(playIntent);
  musicSrv=null;
  System.exit(0);
  break;
}
return super.onOptionsItemSelected(item);
We will implement the shuffle function in the next tutorial. For the end button, we stop the Service instance and exit the app. Pressing the back button will not exit the app, since we will assume that the user wants playback to continue unless they select the end button. Use the same process if the the app is destroyed, overriding the activity's onDestroy method:
1
2
3
4
5
6
@Override
protected void onDestroy() {
  stopService(playIntent);
  musicSrv=null;
  super.onDestroy();
}
When you run the app at this point, you will be able to play songs by selecting them from the list and you can also exit the app using the end button.
We have now implemented basic playback of music tracks selected from the user's list of music files. In the final part of this series, we will add a media controller through which the user will be able to control playback. We will add a notification to let the user return to the app after navigating away from it and we will carry out some housekeeping to make the app cope with a variety of user actions.

No comments:

Post a Comment