Background Audio Playback

So far we have concentrated on building applications that are centered around being in the foreground and have their user interface in front of the user. In the last chapter, we looked at how to add audio playback capabilities to those types of applications.

What happens, though, if we want to build an application that plays music or audio books, but we would like the user to be able to do other things with the phone while continuing to listen? We might have some trouble making that happen if we limit ourselves to just building activities. The Android operating system reserves the right to kill activities that aren't in the front and in use by the user. It does this in order to free up memory to make room for other applications to run. If the OS kills an activity that is playing audio, this would stop the audio from playing, making the user experience not so great.

Fortunately, there is a solution. Instead of playing our audio in an activity, we can use a Service.

Services

In order to ensure that the audio continues to play when the application is no longer in the front and its activity is not in use, we need to create a Service. A Service is a component of an Android application that is meant to run tasks in the background without requiring any interaction from the user.

Local vs. Remote Services

There are a couple of different classes of Services in use by Android. The first and what we'll be exploring is called a Local Service. Local Services exist as part of a specific application and are accessed and controlled only by that application. Remote Services are the other type. They can communicate with, be accessed, and be controlled by other applications. As mentioned, we'll be concentrating on using a Local Service to provide audio playback capabilities. Developing Remote Services is a very large topic and is unfortunately out of the scope of this book.

Simple Local Service

To demonstrate a Service, let's go through this very simple example.

First we'll need an activity with an interface that allows us to start and stop the Service.

package com.apress.proandroidmedia.ch06.simpleservice;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class SimpleServiceActivity extends Activity implements OnClickListener {

Our activity will have two Buttons—one for starting the Service and one for stopping it.

    Button startServiceButton;
    Button stopServiceButton;

In order to start or stop the Service, we use a standard intent.

    Intent serviceIntent;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

Our activity implements OnClickListener, so we set the OnClickListener for each of the Buttons to be “this”.

        startServiceButton = (Button) this.findViewById(R.id.StartServiceButton);
        stopServiceButton = (Button) this.findViewById(R.id.StopServiceButton);

        startServiceButton.setOnClickListener(this);
        stopServiceButton.setOnClickListener(this);

When instantiating the intent that will be used to start and stop the Service, we pass in our activity as the Context followed by the Service's class.

        serviceIntent = new Intent(this, SimpleServiceService.class);
    }

    public void onClick(View v) {
        if (v == startServiceButton) {

When the startServiceButton is clicked, we call the startService method, passing in the intent just created to refer to our Service. The startService method is a part of the Context class of which activity is a child.

            startService(serviceIntent);
        }
        else if (v == stopServiceButton) {

When the stopServiceButton is clicked, we call the stopService method, passing in the same intent that refers to our Service. As with startService, stopService is part of the Context class.

            stopService(serviceIntent);
        }
    }
}

Here is our main.xml file that defines the layout for the foregoing activity. It contains the StartService and StopService Buttons as well as a TextView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Simple Service"
    />
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"Image
 android:id="@+id/StartServiceButton" android:text="Start Service"></Button>
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"Image
 android:text="Stop Service" android:id="@+id/StopServiceButton"></Button>
</LinearLayout>

Now we can move on to the code for the Service itself. In this example, we aren't accomplishing anything in the Service, just using Toast to tell us when the Service has started and stopped.

package com.apress.proandroidmedia.ch06.simpleservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

Services extend the android.app.Service class. The Service class is abstract, so in order to extend it, we have to at the very least implement the onBind method. In this very simple example, we aren't going to be “binding” to the Service. Therefore we'll just return null in our onBind method.

public class SimpleServiceService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

The next three methods represent the Service's life cycle. The onCreate method, as in the Activity, is called when the Service is instantiated. It will be the first method called.

    @Override
    public void onCreate() {
        Log.v("SIMPLESERVICE","onCreate");
    }

The onStartCommand method will be called whenever startService is called with an intent that matches this Service. Therefore it may be called more than once. The onStartCommand returns an integer value that represents what the OS should do if it kills the Service. START_STICKY, which we are using here, indicates that the Service will be restarted if killed.

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("SIMPLESERVICE","onStartCommand");
        return START_STICKY;
    }

ONSTARTCOMMAND VS. ONSTART

The onDestroy method is called when the OS is destroying a Service. In this example, it is triggered when the stopService method is called by our activity. This method should be used to do any cleanup that needs to be done when a Service is being shut down.

    public void onDestroy() {
        Log.v("SIMPLESERVICE","onDestroy");
    }
}

Finally, in order to make this example work, we need to add an entry to our manifest file (AndroidManifest.xml) that specifies our Service.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.apress.proandroidmedia.ch06.simpleservice"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".SimpleServiceActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    <service android:name=".SimpleServiceService" />
    </application>
    <uses-sdk android:minSdkVersion="5" />
</manifest>

Of course, this example doesn't do anything other than output to the log indicating when the Service has been started and stopped. Let's move forward and have our Service actually do something.

Local Service plus MediaPlayer

Now that we have created an example Service, we can use it as a template to create an application to play audio files in the background. Here is a Service, and an activity to control the Service that does just that, allows us to play audio files in the background. It works in a similar manner to the custom audio player example from the last chapter, as it is using the same underlying MediaPlayer class that Android makes available to us.

package com.apress.proandroidmedia.ch06.backgroundaudio;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.IBinder;
import android.util.Log;

The Service implements OnCompletionListener so that it can be notified when the MediaPlayer has finished playing an audio file.

public class BackgroundAudioService extends Service implements OnCompletionListener
{

We declare an object of type MediaPlayer. This object will handle the playback of the audio as shown in the custom audio player example in the last chapter.

    MediaPlayer mediaPlayer;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        Log.v("PLAYERSERVICE","onCreate");

In the onCreate method, we instantiate the MediaPlayer. We are passing it a specific reference to an audio file called goodmorningandroid.mp3, which should be placed in the raw resources (res/raw) directory of our project. If this directory doesn't exist, it should be created. Putting the audio file in that location allows us to refer to it by a constant in the generated R class, R.raw.goodmorningandroid. More detail about placing audio in the raw resources directory is available in Chapter 5 in the “Creating a Custom Audio-Playing Application” section.

        mediaPlayer = MediaPlayer.create(this, R.raw.goodmorningandroid);

We also set our Service, this class, to be the OnCompletionListener for the MediaPlayer object.

        mediaPlayer.setOnCompletionListener(this);
    }

When the startService command is issued on this Service, the onStartCommand method will be triggered. In this method, we first check that the MediaPlayer object isn't already playing, as this method may be called multiple times, and if it isn't, we start it. Since we are using the onStartCommand method rather than the onStart method, this example runs only in Android 2.0 and above.

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("PLAYERSERVICE","onStartCommand");

        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
        return START_STICKY;
    }

When the Service is destroyed, the onDestroy method is triggered. Since this doesn't guarantee that the MediaPlayer will stop playing, we invoke its stop method here if it is playing and also call its release method to get rid of any memory usage and or resource locks.

    public void onDestroy() {
        if (mediaPlayer.isPlaying())
        {
            mediaPlayer.stop();
        }
        mediaPlayer.release();
        Log.v("SIMPLESERVICE","onDestroy");
    }

Because we are implementing OnCompletionListener and the Service itself is set to be the MediaPlayer's OnCompletionListener, the following onCompletion method is called when the MediaPlayer has finished playing an audio file. Since this Service is only meant to play one song and that's it, we call stopSelf, which is analogous to calling stopService in our activity.

    public void onCompletion(MediaPlayer _mediaPlayer) {
        stopSelf();
    }
}

Here is the activity that corresponds to the foregoing Service. It has a very simple interface with two buttons to control the starting and stopping of the Service. In each case, it calls finish directly after to illustrate that the Service is not dependent on the activity and runs independently.

package com.apress.proandroidmedia.ch06.backgroundaudio;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class BackgroundAudioActivity extends Activity implements OnClickListener {

    Button startPlaybackButton, stopPlaybackButton;
    Intent playbackServiceIntent;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        startPlaybackButton = (Button) this.findViewById(R.id.StartPlaybackButton);
        stopPlaybackButton = (Button) this.findViewById(R.id.StopPlaybackButton);

        startPlaybackButton.setOnClickListener(this);
        stopPlaybackButton.setOnClickListener(this);

        playbackServiceIntent = new Intent(this,BackgroundAudioService.class);
    }

    public void onClick(View v) {
        if (v == startPlaybackButton) {
            startService(playbackServiceIntent);
            finish();
        } else if (v == stopPlaybackButton) {
            stopService(playbackServiceIntent);
            finish();
        }
    }
}

Here is the main.xml layout XML file in use by the foregoing activity.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Background Audio Player"
        />
    <Button android:text="Start Playback" android:id="@+id/StartPlaybackButton"Image
     android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
    <Button android:text="Stop Playback" android:id="@+id/StopPlaybackButton"Image
     android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
</LinearLayout>

Finally, here is the AndroidManifest.xml for this project.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.apress.proandroidmedia.ch06.backgroundaudio"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".BackgroundAudioActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".BackgroundAudioService" />
    </application>
    <uses-sdk android:minSdkVersion="5" />
</manifest>

As shown, simply using the MediaPlayer to start and stop audio playback within a Service is very straightforward. Let's now look at how we can take that a step further.

Controlling a MediaPlayer in a Service

Unfortunately, when using a Service, issuing commands to the MediaPlayer from the user-facing activity becomes more complicated.

In order to allow the MediaPlayer to be controlled, we need to bind the activity and Service together. Once we do that, since the activity and Service are running in the same process, we can call methods in the Service directly. If we were creating a remote Service, we would have to take further steps.

Let's add a Button labeled “Have Fun” to the foregoing activity. When this Button is pressed, we'll have the MediaPlayer seek back a few seconds and continuing playing the audio file.

We'll start by adding the Button to the layout XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Background Audio Player"
        />
    <Button android:text="Start Playback" android:id="@+id/StartPlaybackButton"Image
     android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
    <Button android:text="Stop Playback" android:id="@+id/StopPlaybackButton"Image
     android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
    <Button android:text="Have Fun" android:id="@+id/HaveFunButton"Image
     android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
</LinearLayout>

Then in the activity, we'll get a reference to it and set its onClickListener to be the activity itself, just like the existing Buttons.

package com.apress.proandroidmedia.ch06.backgroundaudiobind;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class BackgroundAudioActivity extends Activity implements OnClickListener {

    Button startPlaybackButton, stopPlaybackButton;
    Button haveFunButton;
    Intent playbackServiceIntent;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        startPlaybackButton = (Button) this.findViewById(R.id.StartPlaybackButton);
        stopPlaybackButton = (Button) this.findViewById(R.id.StopPlaybackButton);
        haveFunButton = (Button) this.findViewById(R.id.HaveFunButton);

        startPlaybackButton.setOnClickListener(this);
        stopPlaybackButton.setOnClickListener(this);
        haveFunButton.setOnClickListener(this);

        playbackServiceIntent = new Intent(this,BackgroundAudioService.class);
    }

In order for us to have this Button interact with the MediaPlayer that is running in the Service, we have to bind to the Service. The means to do this is with the bindService method. This method takes in an intent; in fact, we can re-use the playbackServiceIntent that we are using to start the Service, a ServiceConnection object, and some flags for what to do when the Service isn't running.

In the following onClick method in our activity, we bind to the Service right after we start it, when the startPlaybackButton is pressed. We unbind from the Service when the stopPlaybackButton is called.

    public void onClick(View v) {
        if (v == startPlaybackButton) {
            startService(playbackServiceIntent);
            bindService(playbackServiceIntent, serviceConnection,
                        Context.BIND_AUTO_CREATE);
        } else if (v == stopPlaybackButton) {
            unbindService(serviceConnection);
            stopService(playbackServiceIntent);
        }

You'll probably notice that we are using a new object that we haven't defined, serviceConnection. This we'll take care of in a moment.

We also need to finish off the onClick method. Since our new Button has its onClickListener set to be the activity, we should handle that case as well and close out the onClick method.

        else if (v == haveFunButton) {
            baService.haveFun();
        }
    }

In the new section, we are using another new object, baService. baService is an object that is of type BackgroundAudioService. We'll declare it now and take care of creating when we create our ServiceConnection object.

    private BackgroundAudioService baService;

As mentioned, we are still missing the declaration and instantiation of an object called serviceConnection. serviceConnection will be an object of type ServiceConnection, which is an Interface for monitoring the state of a bound Service.

Let's take care of creating our serviceConnection now:

    private ServiceConnection serviceConnection = new ServiceConnection() {

The onServiceConnected method shown here will be called when a connection with the Service has been established through a bindService command that names this object as the ServiceConnection (such as we are doing in our bindService call).

One thing that is passed into this method is an IBinder object that is actually created and delivered from the Service itself. In our case, this IBinder object will be of type BackgroundAudioServiceBinder, which we'll create in our Service. It will have a method that returns our Service itself, called getService. The object returned by this can be operated directly on, as we are doing when the haveFunButton is clicked.

        public void onServiceConnected(ComponentName className, IBinder baBinder) {
            baService =
((BackgroundAudioService.BackgroundAudioServiceBinder)baBinder).getService();
        }

We also need an onServiceDisconnected method to handle cases when the Service goes away.

        public void onServiceDisconnected(ComponentName className) {
            baService = null;
        }
    };
}

.

Now we can turn our attention to what we need to change in the Service itself.

package com.apress.proandroidmedia.ch06.backgroundaudiobind;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class BackgroundAudioService extends Service implements OnCompletionListener
{
    MediaPlayer mediaPlayer;

The first change that we'll need to make in our Service is to create an inner class that extends Binder that can return our Service itself when asked.

    public class BackgroundAudioServiceBinder extends Binder {
        BackgroundAudioService getService() {
            return BackgroundAudioService.this;
        }
    }

Following that, we'll instantiate that as an object called basBinder.

    private final IBinder basBinder = new BackgroundAudioServiceBinder();

And override the implementation of onBind to return that.

    @Override
    public IBinder onBind(Intent intent) {
        // Return the BackgroundAudioServiceBinder object
        return basBinder;
    }

That's it for the binding. Now we just need to deal with “Having Fun.”

As mentioned, when the haveFunButton is clicked, we want the MediaPlayer to seek back a few seconds. In this implementation, it will seek back 2,500 milliseconds or 2.5 seconds.

        public void haveFun() {
        if (mediaPlayer.isPlaying()) {
            mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() - 2500);
        }
    }

That's it for updates on the Service. Here is the rest of the code for good measure.

    @Override
    public void onCreate() {
        Log.v("PLAYERSERVICE","onCreate");

        mediaPlayer = MediaPlayer.create(this, R.raw.goodmorningandroid);
        mediaPlayer.setOnCompletionListener(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("PLAYERSERVICE","onStartCommand");

        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
        return START_STICKY;
    }

    public void onDestroy() {
        if (mediaPlayer.isPlaying())
        {
            mediaPlayer.stop();
        }
        mediaPlayer.release();
        Log.v("SIMPLESERVICE","onDestroy");
    }

    public void onCompletion(MediaPlayer _mediaPlayer) {
        stopSelf();
    }

}

Finally, here is the AndroidManifest.xml file that is required by the foregoing example.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      android:versionCode="1"
      android:versionName="1.0" package="com.apress.proandroidmediaImage
.ch06.backgroundaudiobind">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".BackgroundAudioActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".BackgroundAudioService" />
    </application>
    <uses-sdk android:minSdkVersion="5" />
</manifest>

Now that the foundation is in place, we can add whatever functionality we like into the Service and call the various methods such as haveFun directly from our activity by binding to it. Without binding to the Service, we would be unable to do anything more than start and stop the Service.

The foregoing examples should give a good starting point for building an application that plays audio files in the background, allowing users to continue doing other tasks while the audio continues playing. The second example can be extended to build a full-featured audio playback application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.26.217