Issue
I want to stream an mp3 file that has a length of about 1 hour. I want to play the file in background. I saw many tutorial videos. Some of them used service and some used asyntask. I dont know what can i choose between these too. Which is the better one?
Solution
You'll definitely want to use a service, and not an AsyncTask. The main reason for this being that, if you want the music to run even when the app has been suspended/put into the background, like when the user moves to another app, only a service will do this. AsyncTasks will not run in the background in that way.
To include some background information about background services, they use events from app contexts such as activities and foreground services to notify them of when to do work. This work is handled via the service's onStartCommand() function. More can be read about services in the Android docs https://developer.android.com/guide/components/services
That being said, a service will allow running in the background, but it can still be preempted if the OS needs to complete another task. Therefore, for the music to play reliably, and restart shortly after the OS has preempted the service for any reason, you will need to specify START_STICKY as the return value from the service's onStartCommand() function. But, like with everything Android, prefer the compatibility version, START_STICKY_COMPATIBILITY, to the not compatible version. START_STICKY/START_STICKY_COMPATIBILITY is appropriate to return in the case of the PLAY command. I.e. if the event the service is receiving is PLAY.
Returning START_STICKY or START_STICKY_COMPATIBILITY from the onStartCommand() in every case will wind you up with a service that never dies, thus consuming processing power and battery life from the phone running it. This could cause processor consumption and a drain on battery. That is why it is important to return START_NOT_STICKY from the onStartCommand if the user is attempting to pause. I.e. if the event the service is receiving is PAUSE.
Here is a stripped down version of what you might want your onStartCommand of your service to look like:
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(ACTION_PLAY)) {
...
return START_STICKY_COMPATIBILITY;
} else { // i.e. action is ACTION_PAUSE
...
return START_NOT_STICKY;
}
}
Edit: To caveot this and the remainder of this answer - In an attempt to simplify the post, I excluded considerations for waiting for the mediaplayer to prepare. As a note, the service will likely also need to handle waiting for the mediaplayer to prepare with either a separate event, or from within the handling of the PLAY event. The could possibly also be handled from within the activity before starting the service but this may be more or less complicated. Explaining the rest of the issues/considerations in this answer is much easier without talking about this aspect of the problem, although it will have to be considered to make a functional music player app.
Provisions for when the device is locked are also required so that some hardware peripherals don't shut off. Consider adding the following in response to the PLAY event in the service's onStartCommand to account for this:
// Setup Wake Mode Wake Lock so the music can play while device screen is locked
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.FULL_WAKE_LOCK);
// Setup wifi lock so wifi can stay on while device is locked and trying to save power
wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
Another issue is that your user will ideally be able to kill the service if it is running. If they kill the app, it will not kill the service, as intended, and the music will keep playing. So the user should be able to control the service via a notification with controls to pause and play the music. This can be done using a foreground service. If you want to add the foreground service layer, you can add a call to startForeground() in the onStartCommand() of the service in response to the broadcast event for PLAY. Here is the stripped down onStartCommand() with the foreground logic added:
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(ACTION_PLAY)) {
...
Notification notification = setupNotification(); // where setupNotification is your own function
startForeground(1, notification);
return START_STICKY_COMPATIBILITY;
} else { // i.e. action is ACTION_PAUSE
...
stopForeground(false);
// NotificationManagerCompat needed here for swipeable notification b/c service will be killed
Notification notification = setupNotification();
NotificationManagerCompat nmc = NotificationManagerCompat.from(this);
nmc.notify(1, notification);
return START_NOT_STICKY;
}
}
The startForeground() functions takes an id and a Notification object as its params. The notification can be created with the NotificationCompat.Builder with code that looks something like this (noting that some variables here will need to be subbed out for your respective application):
Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher_round_large);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("Music is now playing") // change to paused on paused
.setTicker("Music Playing")
.setSmallIcon(R.drawable.status_bar_icon_xhdpi_48px)
.setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
.setContentIntent(pendingTapIntent)
.setDeleteIntent(pendingSwipeIntent)
.addAction(iconId, buttonText, pendingButtonIntent)
.setWhen(System.currentTimeMillis());
Note the pending intents in the code above. These are created with the PendingIntent class. I.e. create a pending intent for a play button on the notification like so (where "this" is the background service, assuming you are creating this intent from within the background service)
Intent playIntent = new Intent(this, MyService.class);
playIntent.setAction(ACTION_PLAY);
PendingIntent pendingPlayIntent = PendingIntent.getService(this, 0, playIntent, 0);
Likewise, create a pending intent for when the user taps on the notification so that it opens the app with the following code (again, where "this" is the background service, assuming you are creating this intent from within the background service):
Intent notificationIntent = new Intent(this, StreamActivity.class);
notificationIntent.setAction("ACTION_MAIN");
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Furthermore, create the pending intent for swipe action on the notification to kill the background service like this (again, where "this" is the background service, assuming you are creating this intent from within the background service):
Intent swipeIntent = new Intent(this, MyService.class);
swipeIntent.setAction(ACTION_END_SERVICE);
PendingIntent pendingSwipeIntent = PendingIntent.getService(this, 0, swipeIntent, 0);
Hopefully this covers enough to get you going, but I would recommend starting this process without the foreground activity layer of complexity. Then add it once you see the music play from within the app is working.
Answered By - caitcoo0odes
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.