January 13, 2014

Background Audio Streaming with Xamarin.Android

By

Music & Audio is one of the top 10 categories on Android with beautiful applications like Rdio that takes advantage of great native audio features such as Background Streaming to create an amazing user experience. It is extremely easy to get audio streaming in a background service with Xamarin.Android. Using a service to stream audio has many advantages like the ability to play audio while your application is not visible or under the lock screen.

To get started, let’s create our first Android Service with just a tiny amount of code:

[Service]
public class StreamingBackgroundService : Service {
  private MediaPlayer player;
}

I added a MediaPlayer to the service, which is used for audio playback on Android. The MediaPlayer will be used to start and stop playback as well as give us information such as the current position and duration of the audio that is being played. With the service setup we will need a way to communicate with it from our user interface. The best way to do this is to create Intent Filters with custom actions that can be passed from the user interface down to the service:

[Service]
[IntentFilter(new[] { ActionPlay, ActionPause, ActionStop })]
public class StreamingBackgroundService : Service {
  //Actions
  public const string ActionPlay = "com.xamarin.action.PLAY";
  public const string ActionPause = "com.xamarin.action.PAUSE";
  public const string ActionStop = "com.xamarin.action.STOP";
}

I added a few here for the example, but you may want to add other actions to your app such as skip forward/back. When your service receives a start command it will be passed an intent with one of our actions. Here I simply apply a switch case to the action and then call a private method I created for each action:

public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {
  switch (intent.Action) {
    case ActionPlay: Play(); break;
    case ActionStop: Stop(); break;
    case ActionPause: Pause(); break;
  }
  //Set sticky as we are a long running operation
  return StartCommandResult.Sticky;
}

Then to send one of these actions from the user interface, all we have to do is start the service with the desired action:

var intent = new Intent(StreamingBackgroundService.ActionPlay);
StartService(intent);

I also created a simple user interface with buttons that when clicked will pass down these actions to the service:

StreamingMainUI

Starting and Stopping Playback

To start playback in the Play(); method we first initialize the MediaPlayer which allows us to specify what type of audio stream it accepts, and setup event handlers when the player has prepared a file, completed a file, or an error has occurred:

player = new MediaPlayer();
//Tell our player to stream music
player.SetAudioStreamType(Stream.Music);
//When we have prepared the song start playback
player.Prepared += (sender, args) => player.Start();
//When we have reached the end of the song stop ourselves, however you could signal next track here.
player.Completion += (sender, args) => Stop();
player.Error += (sender, args) => {
  //playback error
  Console.WriteLine("Error in playback resetting: " + args.What);
  Stop();//this will clean up and reset properly.
};

After the player has been initialized we simply set the datasource (mp3 url) and prepare the player for playback. After the player has prepared our file the Prepared event will be fired. Then we will start playback:

await player.SetDataSourceAsync(ApplicationContext, Android.Net.Uri.Parse(Mp3));
player.PrepareAsync();

Stopping and pausing is as simple as calling the Stop() and Pause() methods on the player. After stopping playback it is important to also call the Reset() method on the player to set it back to an uninitialized state. Failing to reset the player can lead to exceptions when re-initializing with a new song.

Always be streaming

Even though we are in a service streaming audio Android itself has the ability to shut down or deprioritize features such as WiFi or the CPU. To tell Android we are doing something important we need to do two things. First, set a WakeMode on the player telling Android to keep the CPU alive:

player.SetWakeMode(ApplicationContext, WakeLockFlags.Partial);

and add a WakeLock permission in your AssemblyInfo.cs

[assembly: UsesPermission(Android.Manifest.Permission.WakeLock)]

Next, acquire a WifiLock to ensure that the WiFi be kept alive as well. When audio is playing you should acquire the lock, and when audio is stopped or paused you should release it.

private void AquireWifiLock() {
  if (wifiLock == null) {
    wifiLock = wifiManager.CreateWifiLock(WifiMode.Full, "xamarin_wifi_lock");
  }
  wifiLock.Acquire();
}
private void ReleaseWifiLock() {
  if (wifiLock == null)
    return;
  wifiLock.Release();
  wifiLock = null;
}

Play on the Foreground

Our service isn’t a typical short lived task service like checking Twitter or email where users might not be aware that they are running. Audio is playing and there is a good chance that your users will want to interact or at least find out what application is playing the audio. This is where running our service as a foreground service comes in. A foreground service has a higher level of importance inside of Android and it will likely not kill off the service. Running in the foreground also means we can display an active notification and icon in the status bar.

After we start playback we will create our notification to display to our user. When this notification is tapped, it will launch our MainActivity which has our transport controls on it.

var pendingIntent = PendingIntent.GetActivity(ApplicationContext, 0,
                    new Intent(ApplicationContext, typeof(MainActivity)),
                    PendingIntentFlags.UpdateCurrent);
var notification = new Notification {
  TickerText = new Java.Lang.String("Song started!"),
  Icon = Resource.Drawable.ic_stat_av_play_over_video
};
notification.Flags |= NotificationFlags.OngoingEvent;
notification.SetLatestEventInfo(ApplicationContext, "Xamarin Streaming", "Playing music!", pendingIntent);
StartForeground(NotificationId, notification);

When we pause or stop playback, we want to call StopForeground(true); to tell Android to no longer run our service on the foreground and to remove the notification.
StreamingNotification

StreamingMiniNotification

Handling Audio Focus
Since Android is a multitasking environment, there may be several applications fighting for control of playing back audio. Android deals with this by allowing applications to handle audio focus changes that notify them to start, stop, or lower the volume of their audio. Implementing AudioManager.IOnAudioFocusChangeListener in our service allows us to handle this. When audio focus changes we will be notified in the OnAudioFocusChange method. A common implementation of this is below:

public void OnAudioFocusChange(AudioFocus focusChange) {
  switch (focusChange) {
    case AudioFocus.Gain:
      if (player == null)
        IntializePlayer();
      if (!player.IsPlaying){
        player.Start();
        paused = false;
      }
      player.SetVolume(1.0f, 1.0f);//Turn it up!
      break;
    case AudioFocus.Loss:
      //We have lost focus stop!
      Stop();
      break;
    case AudioFocus.LossTransient:
      //We have lost focus for a short time, but likely to resume so pause
      Pause();
      break;
    case AudioFocus.LossTransientCanDuck:
      //We have lost focus but should still play at a lower 10% volume
      if(player.IsPlaying)
        player.SetVolume(.1f, .1f);//turn it down!
      break;
  }
}

It is important to handle Audio Focus to create a great user experience. Even though Android itself does not enforce developers to cooperate in Audio Focus, it is highly encouraged and is what users expect.

Handle AudioBecomingNoisy aka headphones unplugged

Creating a great user experience also means handling some edge cases and stopping audio playback. A very common occurrence is when your users unplug their headphones. Android handles this very differently than anything else we have done yet. You must implement a BroadcastReceiver to handle the AudioManager.ActionAudioBecomingNoisy action. When this is received, we will signal to our background service to stop playing audio using the same method we used to communicate from our user interface.

[BroadcastReceiver]
[IntentFilter(new []{AudioManager.ActionAudioBecomingNoisy})]
public class MusicBroadcastReceiver: BroadcastReceiver {
  public override void OnReceive(Context context, Intent intent){
    if (intent.Action != AudioManager.ActionAudioBecomingNoisy)
      return;
    //signal the service to stop!
    var stopIntent = new Intent(StreamingBackgroundService.ActionStop);
    context.StartService(stopIntent);
  }
}

These are the basics of implementing your very own background audio streaming service. 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 example up on GitHub for you to try.

Discuss this blog post in the Xamarin Forums

TwitterFacebookGoogle+LinkedInEmail