MusicDroid - Audio Player Part II
Introduction
In part one of the MusicDroid tutorial we created a simple MP3 player that will list all of the songs on the SD card and allow the user to select a song to play. Now, we will move the MediaPlayer object into a remote service. This will allow the music to continue in the background while the user is doing other things on their phone.
Click here to download the complete source to reference for this tutorial.
What are services?
Services are components that run in the background and do not display a view for the user to interact with. These components must be listed in the Androidmanifest.xml file with a <service> element. When the Activity that started the service is closed Android will attempt to keep the service running if possible.
To create a new Service you extend the android.app.Service class. Then for your activity to connect to the service it would call either Context.startService() or Context.bindService(). Attempts to connect to services are asynchronous, that means that after you call one of the functions to start your service your code will not wait for it to connect to the service before continuing on. Instead, you must pass in a ServiceConnection object, and then ServiceConnection.onServiceConnected() will be called when the service is connected.
Interacting with services
We need a way to send and receive data from the service to our Activities which are using this service. This is done using an IInterface class. It is quite complicated to create this interface, so Google has made it easy on us. They have created the "Android Interface Definition Language" or more frequently referred to as AIDL. So, to create an Interface for a service you just need to create an AIDL file in your src folder (the same folder with all your other classes), and when you save the .aidl file it will automatically generate a class that extends IInterface based on your AIDL file.
For the MusicDroid project we are going to create a service called MDService (Music Droid Service). We will call our interface for this service MDSInterface. Here is MDSInterface.aidl:
- package com.helloandroid.android.musicdroid2;
- interface MDSInterface {
- void clearPlaylist();
- void playFile( in int position );
- void pause();
- void stop();
- void skipForward();
- void skipBack();
- }
This is just a very basic outline of the functions that we will need to control a media player. Unfortunately, there are still some issues with AIDL and Interfaces, so you are kinda limited on the types of the arguments and return values. For example I would have liked to use a function "void setPlaylist( in List
Notice the that the arguments in the functions say things like "in String song". This signifies the direction of the data, ie you are are reading the value from song, not intending to write the value of song. You can use "out String song" if that was the case.
So when you save this MDSInterface.aidl it will create a file called MDSInterface.java which defines the class MDSInterface. In the class MDSInterface is the is a public abstract class called Stub, and this is what you must create a subclass of in your MDService class.
So, to make this a little easier lets look at the MDService class, with all of the MediaPlayer specific code pulled out for the time being so you can see how the interface is created:
- private List<String> songs = new ArrayList<String>();
- private int currentPosition;
- private NotificationManager nm;
- @Override
- return mBinder;
- }
- try {
- currentPosition = position;
- playSong(MusicDroid.MEDIA_PATH + songs.get(position));
- }
- }
- songs.add(song);
- }
- songs.clear();
- }
- prevSong();
- }
- nextSong();
- }
- nm.notify(NOTIFY_ID, notification);
- mp.pause();
- }
- nm.cancel(NOTIFY_ID);
- mp.stop();
- }
- };
- }
The code above is everything that you need to implement the interface. You'll see first on line 40 the getBinder() function. This will return the mBinder variable that is defined starting on line 86. This mBinder variable is the MDSInterface.Stub class that you must create to define all of those functions in the AIDL file.
Tip: In Eclipse to make overriding all these functions easier you can type in line 86 and hit enter. Then in the empty block starting on line 87 you can right click and goto "Source -> Override / Implement Methods"
So, we are implementing all of these interface functions that we defined earlier in the AIDL file. First in playFile(int) we simply set the currentPosition and call playSong(String) passing in the path to the song. This is very similar to the functionality that was built into the MusicDroid ListActivity in MusicDroid back in Part 1.
Here is the the playSong(String) method:
- try {
- nm.notify(NOTIFY_ID, notification);
- mp.reset();
- mp.setDataSource(file);
- mp.prepare();
- mp.start();
- mp.setOnCompletionListener(new OnCompletionListener() {
- nextSong();
- }
- });
- }
- }
Note that this is almost identical to the playSong function from Part 1. However, those really paying attention will notice a difference on lines 47-49. As you see we are now going to create a notification each time a song plays.
To do this we will need to add 2 files to the res/drawable folder, playbackstart.png and playbackpause.png. Once they are added to the "res/drawable" folder they will be referenced by their int value for their id. The first argument for the Notification constructor in the top status bar icon to use. Since this is the playSong() function we want to use the playbackstart.png, which can be referred to as "R.drawable.playbackstart". For the second and fourth parameter we are passing in the filename, this is the text that will be displayed on the status bar animation.
After we create out Notification object we will use our NotificationManager to initiate the notification. this is done on line 49, with nm.notify(int,Notification). We pass in an int, NOTIFY_ID that we will use refer to this notification icon when we need to modify or remove it, along with the Notification that we created.
The NotificationManager is initialized in our onCreate() function, and we make sure to remove the icon with nm.cancel(int) when the service is destroyed in the onDestroy() function:
- @Override
- protected void onCreate() {
- super.onCreate();
- }
- @Override
- protected void onDestroy() {
- mp.stop();
- mp.release();
- nm.cancel(NOTIFY_ID);
- }
- @Override
- return mBinder;
- }
We must also cancel the notification after playing the last song on the playlist in our very familiar nextSong() function that is moved into our new Service:
- private void nextSong() {
- // Check if last song or not
- if (++currentPosition >= songs.size()) {
- currentPosition = 0;
- nm.cancel(NOTIFY_ID);
- } else {
- playSong(MusicDroid.MEDIA_PATH + songs.get(currentPosition));
- }
- }
One last function that we have created is prevSong(). We saw it called above on line 107 to handle the work in skipBack() from the interface. Here is prevSong():
- private void prevSong() {
- if (mp.getCurrentPosition() < 3000 && currentPosition >= 1) {
- playSong(MusicDroid.MEDIA_PATH + songs.get(--currentPosition));
- } else {
- playSong(MusicDroid.MEDIA_PATH + songs.get(currentPosition));
- }
- }
This function is designed to for the skip back functionality that will be shown in Part 3. The idea here is that if you hit the skip back button in the controls then your song will restart, and if you hit it again then it will go the previous song. So here we go to the previous song if we are less than 3 seconds into the song, and we are not listening to the first song on the list (position 0).
So there you have it, a service to handle everything. On the next page we will look at how to bind to this service, and the changes that we need to make to the MusicDroid class to use the new service...
Comments
What could be great about this example is closing the application leaving the service alive. I've tried this and if I press the "back key" the emulator kills the activity *and the service*. This is not good, I'd like to listen to music after closing the activity! Any idea about how to do it? Thanks
Yeah, I should have called Context.startService() to keep the service running after the activities are done binding to the service. See the third paragraph in my post on the forum:
http://www.helloandroid.com/forum?&c=showthread&ThreadID=24
I'll try to get this added into this tutorial also eventually
I am developping some client server application. I am confused whether i will design the connection class as service or simple java class which takes care of http connection, sending and receiving data.
I dont need the connection to be persistant throughout the application(ie. polling type).
What is advantage of Service over simple java class? whic one will be the good option for my requirement ?
The only reason to create it as a service would be if the connection needs to be used when your application is not running. So if your only using your connection when your app is running just use a normal class and do it in a seperate thread...good luck!
Post new comment