January 16, 2014

Lock Screen Music Controls in Xamarin.Android

By

Streaming audio in a background service with Xamarin.Android allows developers to create amazing user experiences. In my last post I walked though how to setup the a basic streaming service with a notification that your users could interact with. In Ice Cream Sandwich (API 14) Google introduced the RemoteControlClient, which exposes properties and methods be consumed by remote controls capable of displaying metadata, artwork and media transport control buttons. One of these “remote controls” is built into the lock screen of devices.

IMG_20131108_171118

Since we already have a base streaming music service in place it is actually quite easy to take advantage of the RemoteControlClient to add lock screen music controls to our application.

A RemoteControlClient communicates with our application through Intents, specifically the Intent.ActionMediaButton which will be broadcast when our users press one of the buttons on the lock screen. We will first want to create a new BroadcastReceiver to handle any of these broadcasts to process them and pass the correct command to our background service. Here is what our BroadcastReciver looks like to handle these button presses:

[BroadcastReceiver]
[Android.App.IntentFilter(new []{Intent.ActionMediaButton})]
public class RemoteControlBroadcastReceiver : BroadcastReceiver
{
  public string ComponentName { get { return this.Class.Name; } }
  public override void OnReceive (Context context, Intent intent)
  {
    if (intent.Action != Intent.ActionMediaButton)
      return;
    //The event will fire twice, up and down.
    // we only want to handle the down event though.
    var key = (KeyEvent) intent.GetParcelableExtra(Intent.ExtraKeyEvent);
    if (key.Action != KeyEventActions.Down)
      return;
    var action = StreamingBackgroundService.ActionPlay;
    switch (key.KeyCode) {
      case Keycode.Headsethook:
      case Keycode.MediaPlayPause: action = StreamingBackgroundService.ActionTogglePlayback; break;
      case Keycode.MediaPlay: action = StreamingBackgroundService.ActionPlay; break;
      case Keycode.MediaPause: action = StreamingBackgroundService.ActionPause; break;
      case Keycode.MediaStop: action = StreamingBackgroundService.ActionStop; break;
      case Keycode.MediaNext: action = StreamingBackgroundService.ActionNext; break;
      case Keycode.MediaPrevious: action = StreamingBackgroundService.ActionPrevious; break;
      default: return;
    }
    var remoteIntent = new Intent(action);
    context.StartService(remoteIntent);
  }
}

Once we have our BroadcastReceiver in place, back in our service we are going to create three new methods. The first is to create and register the RemoteControlClient, second is to unregister it, and the last is to update the metadata for the lock screen to display.

First up is our register method, which will register our BroadcastReceiver with the AudioManager to allow it to receive broadcasts and specifies the correct intent to trigger. The last thing the method will do is set the correct RemoteControlFlags that we can process. This method will be called when we start or resume playback.

private ComponentName remoteComponentName;
private void RegisterRemoteClient()
{
  remoteComponentName = new ComponentName(PackageName, new RemoteControlBroadcastReceiver().ComponentName);
  try {
    if(remoteControlClient == null) {
      audioManager.RegisterMediaButtonEventReceiver(remoteComponentName);
      //Create a new pending intent that we want triggered by remote control client
      var mediaButtonIntent = new Intent(Intent.ActionMediaButton);
      mediaButtonIntent.SetComponent(remoteComponentName);
      // Create new pending intent for the intent
      var mediaPendingIntent = PendingIntent.GetBroadcast(this, 0, mediaButtonIntent, 0);
      // Create and register the remote control client
      remoteControlClient = new RemoteControlClient(mediaPendingIntent);
      audioManager.RegisterRemoteControlClient(remoteControlClient);
    }
  //add transport control flags we can to handle
  remoteControlClient.SetTransportControlFlags(RemoteControlFlags.Play |
					       RemoteControlFlags.Pause |
					       RemoteControlFlags.PlayPause |
					       RemoteControlFlags.Stop |
					       RemoteControlFlags.Previous |
					       RemoteControlFlags.Next);
  }catch(Exception ex) {
    Console.WriteLine (ex);
  }
}

Whenever we stop music playback we want to ensure that we unregister the RemoteControlClient as we no longer need to display the lock screen controls.

private void UnregisterRemoteClient()
{
  try{
    audioManager.UnregisterMediaButtonEventReceiver (remoteComponentName);
    audioManager.UnregisterRemoteControlClient (remoteControlClient);
    remoteControlClient.Dispose();
    remoteControlClient = null;
  }catch(Exception ex){
    Console.WriteLine (ex);
  }
}

Lastly, we can update the lock screen with the metadata we want to display. Calling the RemoteControlClient’s EditMetadata method will return a new MetadataEditor that allows us to set a plethora of information to be displayed with MetadataKeys. In this example I am hard coding in the song I am playing back with album artwork located in my drawables, but you might dynamically pass this information down to your service and download the artwork to display.

private void UpdateMetadata()
{
  if (remoteControlClient == null)
    return;
  var metadataEditor = remoteControlClient.EditMetadata(true);
  metadataEditor.PutString (MetadataKey.Album, "Fantastique");
  metadataEditor.PutString (MetadataKey.Artist, "Raw Stiles");
  metadataEditor.PutString (MetadataKey.Albumartist, "Raw Stiles");
  metadataEditor.PutString (MetadataKey.Title, "Rogue");
  var coverArt = BitmapFactory.DecodeResource(Resources, Resource.Drawable.album_art);
  metadataEditor.PutBitmap (BitmapKey.Artwork, coverArt);
  metadataEditor.Apply ();
}

With these methods in place we just have to update our Start, Pause, and Stop logic to call these methods. Additionally, the RemoteControlClient has the SetPlaybackState method that we will set to Playing, Paused, Stopped, or Buffering so the lock screen will display the correct icons for each state. Now when we set the audio to playback we will register the RemoteControlClient, update the state, and set the metadata:

RegisterRemoteClient ();
remoteControlClient.SetPlaybackState(RemoteControlPlayState.Buffering);
UpdateMetadata();

After the song is prepared we will again update the state and ensure the correct metadata is showing:

player.Prepared += (sender, args) => {
  if(remoteControlClient != null)
    remoteControlClient.SetPlaybackState(RemoteControlPlayState.Playing);
  UpdateMetadata ();
  player.Start ();
};

There you have it, a simple way to add great functionality to your audio streaming app. Try this project in Visual Studio, or run this Starter-compatible project in Xamarin Studio on Mac or Windows. I have the full source code available on GitHub, along with my original example of setting up the base streaming service.

Discuss this blog post in the Xamarin Forums

TwitterFacebookGoogle+LinkedInEmail