Playing audio in the background

Sometimes, we may want to be able to play audio in the background after the user has closed the app. We may be creating a media player app, or we may just want to allow various media to be played without requiring an activity to be displayed on the screen.

How to do it...

We can play audio in the background if we play it from a Service instance:

  1. First, we will create a service that will be responsible for playing music. Here, we have methods that will be expanded in the following code snippet:
    [Service]
    public class MediaService : Service {
      public const string ActionPlay = "ActionPlay";
      public const string ActionPause = "ActionPause";
      public const string ActionStop = "ActionStop";
    
      private MediaPlayer mediaPlayer;
      private bool isPrepared = false;
    
      public override void OnDestroy() {
        base.OnDestroy();
        CleanUpPlayer();
      }
      public override StartCommandResult OnStartCommand(
      Intent intent, StartCommandFlags flags, int startId) {
        switch (intent.Action) {
          case ActionPlay:
            StartPlaying();
            break;
          case ActionPause:
            PausePlaying();
            break;
          case ActionStop:
            StopPlaying();
            CleanUpPlayer();
            break;
        }
        return StartCommandResult.Sticky;
      }
      public override IBinder OnBind(Intent intent) {
        return null;
      }
  2. When the activity starts, we can launch the service using the StartService() method:
    StartService(new Intent(this, typeof(MediaService))); 
  3. When we want to control the player, we can send intents to the service using the StartService() method with an intent action:
    StartService(new Intent(MediaService.ActionPlay,
      null, this, typeof(MediaService)));
    StartService(new Intent(MediaService.ActionPause,
      null, this, typeof(MediaService)));
    StartService(new Intent(MediaService.ActionStop,
      null, this, typeof(MediaService)));

Once we have our service created, we need to go ahead and implement the various methods that control the underlying MediaPlayer instance:

  1. When we want to play the music, we need to create a MediaPlayer instance. We do this in our StartPlaying() method:
    if (mediaPlayer == null) {
      isPrepared = false;
      mediaPlayer = new MediaPlayer();
      // ... set up the media player ...
      mediaPlayer.Prepared += delegate {
        isPrepared = true;
        mediaPlayer.Start();
      };
      mediaPlayer.PrepareAsync();
    }
    else if (isPrepared && !mediaPlayer.IsPlaying) {
      mediaPlayer.Start();
    }
  2. To pause music, we can just use the Pause() method. We do this in our PausePlaying() method:
    if (isPrepared) {
      mediaPlayer.Pause();
    }
  3. If we want to stop the player, we can also simply invoke the Stop() method. This is done in our StopPlaying() method:
    if (isPrepared) {
      mediaPlayer.Stop();
    }
  4. Finally, we should implement the CleanUpPlayer instance to free resources:
    if (mediaPlayer != null) {
      mediaPlayer.Release();
      mediaPlayer = null;
    }
    isPrepared = false;

The Android system may decide that our service is not important as it is only running in the background, and it may terminate the service. We can avoid this in two ways, wake locks or foreground services.

Way 1:

  1. We can tell the media player to acquire a wake lock while it is playing music, preventing the CPU from going to sleep. We do this through the SetWakeMode() method:
    mediaPlayer.SetWakeMode(
      ApplicationContext, WakeLockFlags.Partial);
  2. In order to use the wake locks, we need to request for a permission to do so:
    [assembly: UsesPermission(Manifest.Permission.WakeLock)]
  3. If we are streaming audio over Wi-Fi, we can also acquire a WifiLock instance, which will prevent the Wi-Fi hardware from going to sleep:
    manager = WifiManager.FromContext(ApplicationContext);
    wifiLock = manager.CreateWifiLock(WifiMode.Full, "tag");
  4. We then release the lock when the playback stops or pauses:
    wifiLock.Release();

Way 2:

  1. Also, we can specify that our service should be treated as a foreground component. For this, we pass a Notification instance to the StartForeground() method:
    var context = ApplicationContext;
    var activity = new Intent(context, typeof(MainActivity));
    var pending = PendingIntent.GetActivity(
      context, 0, activity, PendingIntentFlags.UpdateCurrent);
    var notification = new NotificationCompat.Builder(context)
      .SetSmallIcon(Android.Resource.Drawable.IcMediaPlay)
      .SetContentTitle("Music Player Sample")
      .SetContentText("Playing music...")
      .SetOngoing(true)
      .SetContentIntent(pending)
      .Build();
    StartForeground(MediaNotificationId, notification);
  2. We may want to move the service into the background when the music is paused, but we may not want to remove the notification. This is done using the following line of code:
    StopForeground(false);
  3. When the music stops, we can move the service into the background and remove the notification:
    StopForeground(true);

How it works...

There may be cases where we would like to play audio across activities or fragments, especially if we are creating a media player app. To do this, we can play the audio from a background service and is fairly similar to playing audio in an Activity attribute. This is required if we want the audio to continue even when the user has closed the app or if another app is in use.

Note

A MediaPlayer instance should be released when an Activity attribute is paused. To continue playing audio, even when the app is closed, a Service instance is used.

The main thing that needs to be done is to create a service that will handle requests to control the playback. We inherit from Service and implement the control methods in the StartCommandResult() method and the cleanup in OnDestroy.

In the StartCommandResult() method, we can interact with our MediaPlayer object as we would from an Activity instance. For a MediaPlayer service, we might want to specify Sticky or RedeliverIntent to ensure that the service is restarted if the system decides to stop it. Similar to releasing a player when the activity is paused, we would want to release the MediaPlayer instance in the OnDestroy() method.

As the service actually runs on the main thread, we can start a new thread that does the work. The service will just receive an Intent instance and pass it on to the thread which would control the player. Or, we can take advantage of the asynchronous methods of the media player, such as PrepareAsync() and SetDataSourceAsync(). Setting up a media player in a service is exactly the same as in an activity, except that it is not limited to the life of an activity.

Tip

The service that controls a media player can either create a new thread to prepare the player or make use of the asynchronous methods.

We use Intents to control the media player in the service. The action can specify what we would like to do and then make use of the extras to provide information needed to do it. For example, if we were creating a media player app, we can send the play intent action and provide the track ID in the extras. Another example is if the user has updated the playback type from loop all audio to loop a single track, we can use a more general settings action, but then provide the actual type of action as an extra.

Note

Android may stop background services when the device goes to sleep, so a media player service should be moved to the foreground.

As the service runs as a background process, the Android may decide to stop services when the device goes to sleep. This is a result of an attempt of the Android system to preserve the battery life when not in use. As a media player should not stop playing when the screen is turned off, we need to let the system know that we want our service to be treated as if it was a foreground app even when the device is sleeping.

To do this, we first have to tell the MediaPlayer instance to acquire a wake lock when music is playing. This is easy to do and we just need to let the player know what wake lock to use. As wake locks are a privileged feature, we need to ensure that we requested the WakeLock permission for the app.

Then, to specify that the player should acquire a wake lock, we pass the app context and the wake lock type to the SetWakeMode() method on the player. The wake lock is automatically managed by the player and it will acquire a wake lock when audio begins playing. As soon as the playback stops due to a pause, stop, or when the audio reaches the end, the wake lock is released.

Note

Wake locks are automatically managed by the media player, and only held while there is media playback.

If we are streaming audio over Wi-Fi, we can also acquire a WifiLock to prevent the Wi-Fi hardware from going to sleep and stopping the playback. This is simple to do and we only need the WifiManager instance, which we get from the app context. Once we have the manager, we invoke the CreateWifiLock() method, passing the lock type and a tag used for debugging purposes. When the playback stops or is paused, we can then Release() the lock to conserve battery life.

Note

Unlike wake locks, Wi-Fi locks have to be managed manually.

Even if we hold a wake lock and a Wi-Fi lock, the Android system may still determine that the service can be stopped. This is because although the service holds locks, it is still in the background. This may occur if the user starts some activity that requires a lot of resources, and as the service is in the background, it is considered to be less important than the foreground activity.

Note

Android may stop a background service even if it holds wake locks.

To avoid our service being treated as a background service, we can move it to the foreground. Foreground services require a user-visible notification that is displayed while the service remains in the foreground. Notifications used with foreground services are just normal notifications and are created as we would create any notification. We can also update these notifications to display the service progress. To move the service into the foreground, we pass the notification that will be shown and a notification ID to the StartForeground() method.

Tip

When the service is in the foreground, it will almost never be stopped by the Android system.

To move a service out of the foreground, such as when audio is finished, stopped, or paused, we can use the StopForeground() method. This method requires a bool value. If we specify true, the notification that was passed to StartForeground() will be removed. If we specify false, the notification will remain, but it will be automatically removed when the service is destroyed.

There's more...

When we are playing audio in a service, we must ensure that our app does not interfere with any other app that wants to play audio. When our app is not in the foreground, other apps take priority. To allow other apps to take priority, we make use of the audio focus features of the AudioManager instance.

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

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