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.
We can play audio in the background if we play it from a Service
instance:
[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; }
StartService()
method:StartService(new Intent(this, typeof(MediaService)));
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:
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(); }
Pause()
method. We do this in our PausePlaying()
method:if (isPrepared) { mediaPlayer.Pause(); }
Stop()
method. This is done in our StopPlaying()
method:if (isPrepared) { mediaPlayer.Stop(); }
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:
SetWakeMode()
method:mediaPlayer.SetWakeMode( ApplicationContext, WakeLockFlags.Partial);
[assembly: UsesPermission(Manifest.Permission.WakeLock)]
WifiLock
instance, which will prevent the Wi-Fi hardware from going to sleep:manager = WifiManager.FromContext(ApplicationContext); wifiLock = manager.CreateWifiLock(WifiMode.Full, "tag");
wifiLock.Release();
Way 2:
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);
StopForeground(false);
StopForeground(true);
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.
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.
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.
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.
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.
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.
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.
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.
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.