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.
Introduction
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:
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.
1. Create a Service
Step 1
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:
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:
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:
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;
Step 2
Add the following instance variables to the new Service
class:
//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
:
public void onCreate(){ //create the service }
Inside onCreate
, call the superclass method, instantiating the position and MediaPlayer
instance variables:
//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:
public void initMusicPlayer(){ //set player properties }
Inside this method, we configure the music player by setting some of its properties as shown below:
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:
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
:
initMusicPlayer();
Step 3
It's time to add a method to the Service
class to pass the list of songs from the Activity
:
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:
public class MusicBinder extends Binder { MusicService getService() { return MusicService.this; } }
We will also access this from the Activity
class.
2. Start the Service
Step 1
Back in your app's main Activity
class, you will need to add the following additional imports:
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:
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:
//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:
import com.example.musicplayer.MusicService.MusicBinder;
Don't forget to alter the package and class names to suit your own if necessary.
Step 2
We will want to start the Service
instance when the Activity
instance starts, so override the onStart
method:
@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:
private final IBinder musicBind = new MusicBinder();
Now amend the onBind
method to return this object:
@Override public IBinder onBind(Intent intent) { return musicBind; }
Add the onUnbind
method to release resources when the Service
instance is unbound:
@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.
3. Begin Playback
Step 1
Let's now set the app up to play a track. In your Service
class, add the following method:
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:
player.reset();
Next, get the song from the list, extract the ID for it using its Song
object, and model this as a URI:
//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:
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:
player.prepareAsync();
Step 2
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:
@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:
public void setSong(int songIndex){ songPosn=songIndex; }
We will call this when the user picks a song from the list.
Step 3
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:
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:
@Override public boolean onOptionsItemSelected(MenuItem item) { //menu item selected }
Inside the method, add a switch statement for the actions:
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:
@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.
Conclusion
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.