Best practices in media playback - Google I/O 2016

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
IAN LAKE: Hi everyone. Let's get started. Come on. There's four more seats right here in the front. I'm on stage. I'm not going to bite anymore. It's fine. Hi. My name is Ian Lake. I'm a developer advocate at Google. I focus primarily on the Android framework and support library, but more specifically on the Android media framework. I'm here to talk to you about best practices in media playback. So a very simple goal today, and that's basically just to tell you when to use the right APIs to build the best audio or video playback app possible. So throughout this talk, I'm going to be talking about a lot of these things in terms of certain events in your media playback life cycle. So you can imagine any kind of media app is going to go through all of these phases at some point. So created is obviously kind of our one time initialization step, and then we go into playing. And for playing, what we're going to talk about in playing is actually outputting sound-- playing a video, playing audio. That's the playing state. And on the opposite side is a paused. So pause is going to be our shorthand for basically any temporary state where we're not actually outputting any audio or any video. And at some point in our life cycle, sad as it may be, the user may actually exit out of our app. And at that point, you're going to go into the stopped state. That's kind of the permanent state where the user has moved on from your app to maybe a different app, or they've just stopped playback entirely. And of course, that triggers then onDestroy which would then clean up all of our resources. Now, I was talking about media states. All right. And those are slightly different than our Android lifecycle events. I know. So there's two cases. One, if you're a video playing app-- and how many people here are doing video apps? All right. Quite a few. And audio apps? Nice. Nice. And both? Who's the overachievers? Yeah. You guys. Nicely done. So for video, you're obviously kind of tied to the activity lifecycle. And in this case of course, onCreate is a great place to do our one time initialization onDestroy for our clean up. That makes sense. Things have changed slightly with the introduction of Multi Window for Android N. Here, previously to N, actually the onStop command might not be called immediately after your activity ends. So some might hit the Home button, and you might actually not get onStop for five seconds afterwards. And if you're a video playing app, you may not want to be playing audio and assuming to play video for five seconds while your app is in the background. So in this case, we probably actually want to stop playback in onPause. Right? Because this will be called immediately when you become not visible or at least in the background. And plus, obviously that'd be a disastrous idea because things running side by side, you'll be paused but not stopped. Remember stopped is when you become not visible. So we've actually increased the guarantee that onStop will be called immediately. So that makes it the perfect place to actually stop playback is onStop. Now, for audio apps or if you're doing remote playback via Google Cast, you're going to be in a service. That's kind of where we do background work in Android. And here, we have kind of onCreate for created, onDestroy for on destroy. So in all these cases, these are Android lifecycle events that are going to be triggering a change in your media playback. So you know we didn't have anything in the play and paused states for this? That's because those aren't actually tied to Android lifecycle things. Those are tied to user interactions. That's where we're going to spend the vast majority of our time here today. So wouldn't be much of a media playing app if it didn't actually play media. So everyone went to the ExoPlayer talk. No. The lines was huge. It's fine. I understand. It's OK because everything else in this talk is not going to depend on you knowing ExoPlayer. Actually everything here is player agnostic. So from the Android media framework perspective, it actually doesn't care if you're using ExoPlayer or Media Player or some custom player that you're forced to use. It's fine. They all work. So for example, if we were using Media Player, mapping this onto kind of our states for media playback, it's very simple. onCreate, we're going to then create a new media player. Playing, we're going to prepare pause. Pause, pause. Stop, stop. Destroy, release. Right? So in this case, it's a very simple flow. And it kind of gets us in the idea of what these media events are actually meaning. So woo. It plays. It paused. We're done, right? We can all go home. Well, like I said, everything is player agnostic. So this actually doesn't tell anyone anything about what you're doing. And really, we're trying to do this the right way. This isn't just playing audio. This is making a great user experience for users, right? We want all of our guys to be happy like that guy. We don't want any sad, sunburned faces here, but happy people. So let's do a little bit better. And there's actually a whole bunch of things that we're going to cover today that actually makes for a better user experience. Now, these first two, Audio Focus and action_becoming_noisy, are really important for local playback. So if you're playing a video or audio on the device itself, these things are the type of things that are going to make sure that you're only actually putting out sound when you mean to. Now, the other two, MediaSession and Notifications, are something you should always do. Whether you're playing on a device or if you're using Google Cast, these are the things that are actually going to tell the system what's going on in your world so it can tell other apps and other apps can do the type of controls that Android provides by default. So the first thing is Audio Focus. It's kind of the key to good citizenship in Android when it comes to media playback. And it's really all about having apps not talk over one another. If you could imagine kind of the conch shell that you pass from app to app saying, you shall now speak, and now you can speak. So this is very much a last one wins kind of a model. So the last person who requests audio focus should be the one that the user is interacting with. Now, this is slightly different than actually playing audio. We're actually going to want to hold onto audio focus through both those playing and paused state all the way until we're actually stopped. So really, you think of it more of as an intent to play versus actually playing. A slight difference here. So I'm going to go through a lot of code. I hope you're OK with that. I like code. It's nice and easy. The slides will be available afterwards. So don't worry about taking pictures. It's OK. You know. Pictures are great. I'm OK with pictures. But it'll all be available online. Don't worry. And Audio Focus, pretty straightforward. You call request Audio Focus when you want to request Audio Focus. And then you'll really want to check to make sure it's actually granted. Now, in 98% of the times it will just be granted. And you're fine, and you can go play music, play your videos. But there are a few special cases where it actually isn't granted. So for example, if you are in a phone call, other apps won't be able to get Audio Focus. When you're in a phone call, it's a very specific case where there's generally a two way dialogue going on hopefully the entire time-- no awkward pauses or anything like that. So in that case, you're actually not going to be granted Audio Focus. So make sure you actually check the result at all times. And then of course, when we're stopped, we're going to call Abandon Audio Focus. Now, there's an AudioFocusChangeListener. And the name should be fairly self-explanatory, but it's all of the events that happen around Audio Focus. So this is actually how other apps tell you what's going on. So for example, if another media has requested Audio Focus and said I want to gain permanent Audio Focus, you'll actually get an Audio Focus loss callback. And this is the hammer that says you're done. The user has moved on. They're in a new app. They want to play audio in that app. So in this case, we're just going to stop playback. We're done. We want to respect the user's wish to say, hey, they've moved onto a different app that has said they're requesting Audio Focus. Now, it's not always a permanent loss. Right? There's more transient losses. So in this case, transient loss means that another app wants access to play audio, and they want you to pause. They want you to not play any more media. And just during that time, you can imagine they had something really important to say for a second or the voice search and things like use Loss Transient. So then you have kind of a full dialogue with Google just temporarily without having to have audio being output at the same time as you're trying to give a voice query into Google. Now, the one you're probably most familiar with, particularly from a user perspective, is Transient_Can_Duck. Now, these aren't the quack quack ducks. These are the lowering volume, as in ducking your volume. So in this case, you're actually going to just lower your volume, but you can keep playing. Now, if you're an app that is really important, that you're not missing any words, say a podcast app. You can actually pause. It's OK to pause in these cases-- if it's important, you have spoken words. This is just a suggestion that you can duck. But in any case, whether it's a Loss Transient or Transient_Can_Duck, you'll get an Audio Focus gain when the other app abandons Audio Focus. This kind of brings you back to where you were, where you can start playing audio or video at full speed. Now, for video, things are may be slightly different in that pausing a video every time you get a notification could be weird. So you might consider actually just muting rather than pausing. Kind of depends on your use case. If it's really important that the words and audio are in sync, or if it's OK if it cuts out for that Loss Transient. So a good way to test this is say like, your OK Google, to your device while a video's playing and try and decide what makes more sense there. In a lot of cases, it is pause. But if you're doing something on like Android TV, maybe muting temporarily makes more sense. So the other one is Action_Audio_Becoming_Noisy. And it's probably one of my favorite names from a like totally ridiculous kind of a name but actually is exactly what's happening. So these are when you have headphones plugged in and they become unplugged. And it's becoming noisy. This actually made sense when you think about it, right? All of a sudden we're switching from headphones to basically the speaker on your device. Now, in this case, you would rather not surprise your user. In almost all cases, this maybe isn't an intentional thing. You can imagine someone has it in their pocket, and it gets plugged. It's not necessarily the best idea, especially if it's audio playback. So in this case, we're just going to pause, and then they can hit the Play button if they want. Now in this case, we're creating this broadcast receiver programmatically. It's not in our manifest, and actually we can't use manifest ones for these. And that's OK because audio, the Becoming_Noisy, is actually tied to when you're outputting sound. So this is only in the playing state. In this case, as soon as we pause, we're not actually playing any audio or video. Then it's OK to unregister, right? Unplugging it when it's not actually outputting anything is not something we actually care about. So now we can update our chart. We have a few more items in here. So we can see how the lifecycle is slightly different between Audio Focus, which goes all the way until we're stopped, and Becoming_Noisy, which is only when we're actually outputting sound. Now, at this point, this is everything we need-- well, if we're running on API 7 devices. Who has a 2.1 device? Yeah. Nobody. We have to do a little bit more if you actually want to take advantage of everything that Android's offered since 2.1. And a lot of this is just because user expectations have changed. Android's changed. Things have changed a lot. You can imagine now-- with Android Wear and Android Auto and Bluetooth headsets-- actually taking your phone out of your pocket to pause might actually not be the thing that users are used to. If they're out on a run, it's not something they're going to do. Similarly, if you have controls on the Android TV remote, they want that Play/Pause button to actually work. Similarly, they may not actually want to unlock their device. Aren't they expecting controls on their lockscreen? Similarly, do they even need to enter your app at all? If you have notifications, there's actually little reason to go into your app specifically just to pause or play or skip to the next track. All right. None of this is even counting Google Cast and remote playback which wasn't even close to being a thing in the 2.1 [INAUDIBLE]. So we have to go a little bit farther. And in Lollipop-- despite redoing the whole UI thing, which whatever, it's a minor thing-- also introduced this fancy class called MediaSession. So MediaSession is kind of the one stop shop between your app and the rest of the Android media framework. So it was so useful to have just a single point for both sending commands to your app from, say, Bluetooth headsets, as well as giving information to the system, that we deprecated a whole bunch of APIs. So no more remote control client. Yay. All right. No one's excited about more remote conrol. All right. Maybe it was before your time. It's OK. It's great, but we still want to support older devices pre-Lollipop. So we built a MediaSessionCompact. It Backward compatible back to API 4, before any of this stuff actually existed. You could still use MediaSession, and it does nothing. That's OK because it also does all the translations to the remote control client, which would be the Ice Cream Sandwich through KitKat era of Android. It does all that translation for you. So you get one API to write across all Android versions, which is pretty sweet. That's kind of where we want all of our APIs to be. So when you're actually creating a MediaSessionCompat, it's actually doing a lot more for you than you might think. So it's actually creating what's called a token, which is actually just a parsable wrapper around a binder but allows anyone, whether it's the Android system or Android Wear or Android Auto or even your own app, to build a MediaControllerCompats. And these instances actually allow them to send media buttons to you, to send controls to you, as well as read what kind of metadata or playback state-- like are you actually playing something right now or are you buffering? These are the important things that the system needs to know about and things like Android Wear need to know about to actually get your media everywhere. So this is also something you can use in your own app for building your UI, and we'll get back to that in a little bit. So when someone's actually sending you a command through this token, it's actually going through your MediaSessionCompat and into your Callback class. Now, you can imagine with all these events coming in, the Callback class is kind of a big deal, and it is. It basically has all of the onPlay, onPause, onSkipToNext, onPlay Track_From_Search. It's got all of the methods you'd expect for every one of the actions that could be sent to your app-- as well as even custom actions-- all come in to your Callback class. So it's actually a really great place in your app to encapsulate all of the Media Player calls, all of the ExoPlayer calls, all of the Google Cast calls, can all be put into that one Callback class because you know when you get an onPlay into your callback-- no matter where it came from, whether it was tapping on a button on your device or hitting a Bluetooth headset-- it all is going to go to that one place. This makes it really powerful in a way to actually swap between these things. Say you have to support less than API 16 and you can't live in the beautiful ExoPlayer world, you can actually switch those callbacks or choose at run time what callback you want to add. So that way, you can use Media Player or something separate, pre-API 16 and then ExoPlayer on 16+ and not have to re-architect your whole app around just swapping between these two and swapping out calls. So let's get started with MediaSessionCompact. So we create it, and one thing to keep in mind throughout all of MediaSessionCompact is that it's very cautious. It assumes you support nothing until you actually tell it. So we have to set a few flags. You have to say, yes. I actually do want media buttons from Bluetooth headsets or wired headsets as well as transport controls. So these would be things like Android Wear and Android Auto. So we have to actually tell it every time. Don't forget these two lines or else things will magically fail, and you will not know why. It's these two lines. And similar to Audio Focus, this is an intent to play audio. So in this case, we're actually going to call setActive, which says hey! I actually do want to receive media buttons now when we receive Audio Focus. And then once we've stopped, we can actually say, no. I no longer need any more events. I'm good. I'm not going to playback anything else. So we haven't actually told the system anything, right? Again, very cautious. So we actually do need to tell the system what we're doing. And this comes first across in PlaybackState. Now, PlaybackState is built off of two separate pieces. One is the actual setState itself. So these are like-- I am playing, I am buffering, I am paused. And this is what gives kind of the control to say, oh. By the way, you should display a play button rather than a pause button. Or you should display a buffering circle if your buffering. This all makes a lot of sense to tell the system what's going on. Then the other half of it is setActions. Now, here's where you actually declare, yes. I do solemnly swear I support play/pause or I support skip to next. Not every app needs to have a Skip to Next button. So in this case, you need to call setActions for each and every one of the actions you want. Otherwise, that callback for that specific class won't actually ever be called. So if you never say, I support pausing, there will be no Pause button event sent to you, which is probably not actually what you want. But you have to just make sure that these stay in sync. Now, of course, these are kind of coupled together, which at first, you're like. OK. These are all coupled together for some reason. It turns out that many times when you're changing your state, you're also changing your actions. Fast forwarding while you're buffering probably actually doesn't make a whole lot of sense. Maybe it does. But at any point, you can just create these things together. This is a builder kind of pattern. So you actually curate a PlaybackStateCompact builder. So don't throw away the builder every time. You can just reuse the same builder and change just the one thing you need to. You can just change setState and not call setActions and then just call build and call setState. So with this, all of our media buttons work. Yay! Well, OK. I lied. It works great on API 21+. Turns out it fails completely on pre-API 21 with a nice handy error message saying, MediaButtonReceiver component may not be null. OK. I'll take your word on it. But what is a media button receiver component? And why do I need it? Well, it turns out that media button receivers have a long history in Android, but it's something that's changed a lot. So it's just a BroadcastReceiver in your manifest that receives the media_button action. That's all it does, but how it was used changed. Before Lollipop, it was actually required for basically all of the Android media framework APIs. So if you didn't say I have a media button receiver with a component, then all of the system wouldn't actually route any buttons to you, you wouldn't show up on the lockscreen. You basically weren't using any of the system. Now, technically you could have used PendingIntent on API 18 and higher-- so like 18 and 19 and then it was all gone. But we actually didn't add any CTS tests. So it fails on certain manufacturers. So from MediaSessionCompat's perspective, it'll always need a component in your manifest registered if you want to use MediaSessionCompat prior to API 21. Now, it's actually still kind of useful in Lollipop+ even though we have a MediaSession-- which Lollipop is going to be sending you media buttons directly to your callback. In this case, the actual component in your manifest kind of has a secondary optional aspect where it's only used to restart playback. So if you've ever listened to Google Play Music and then stopped playback and then got into your car and hit the Play button, you'll note that Google Play Music was the one to start. It knew that it would get a start command and actually start playback. And that's because it had registered a media button receiver. So I really strongly suggest if you're doing anything with background playback or something that should restart, you should still register a media button receiver on Lollipop and higher. Because otherwise, it will be Google Play Music that starts up even though it may have been your app that the user was last listening to. So just keep that in mind. So we want to make this really easy. So we built a class for it called MediaButtonReceiver, fancy enough. Again, also in the Support v4 support library. And it has kind of two main purposes. The first is kind of specifically around that background case where if you have a service that's handling your MediaSession-- which is, of course, very common for audio apps-- it will auto forward any media button intents onto your service where you can actually do something with it. And it'll choose between two things. One, if you have a media browser service in your app, it'll choose that first. And if you don't have one of those, you can actually add the same media button intent filter to your service, and MediaButtonReceiver will automatically route it to that. The nice part about this is that you don't actually have to write any MediaButtonReceiver code. You just add it to your manifest, and that's it. You're done. That's pretty nice because who wants to write code? Everyone. OK. But the important part is once you get that intent in your service or in your activity if you're doing video playback-- wherever your actual MediaSession is-- then we have a simple one line command to actually take in your MediaSessionCompat and the intent and then go through all of the process of extracting the key event, figuring out what event that is, and then passing it on to your callback for you. So with just adding it to your manifest and doing one line of handle intent, all of a sudden you can now use your Callback class-- normally something that would be API 21+-- now for all API levels. So whether you're using MediaButtonReceiver on older versions or the native MediaSession stuff on 21+. Now, it's actually more code to enable it on API 21+. So the 23 versions of the support library actually did this for you. But as of the 24 versions of support library, the Android N versions, you'll have to do this manually if you want to do it. It's pretty straightforward. You create a PendingIntent to your action MediaButtonReceiver. Then you call said MediaButtonReceiver fancy enough. So now all of our commands work, but there's more to just commands. There's another part of MediaSessionCompat, and that's around metadata. Now, metadata comes in a lot of different formats, but it's basically the what's playing aspect of telling the system what's going on. So this is how Android Wear gets all of your information on their device or the lockscreen gets the background art. So at the bare minimum, I'd suggest a few fields. First, title, artist, album if you have albums, the duration. If it's a fixed length track, having the duration actually allows it to display how far along you are. And then of course, a bitmap for the art or the album art. And now, this bitmap shouldn't necessarily be too large. This is being sent across processes and to the Android system. So no 4,000 by 4,000 pixel bitmaps please. But it is the only way to get lockscreen backgrounds working. So probably should include a smaller size like 640 by 640 size, somewhere in that kind of range or lower, just to get something on there. We are looking at a future version of Android where the lockscreen stuff won't actually need a bitmap itself, but you'll be able to use the other part, which is a URI. So these allow you to give a content URI to the app. So then instead of having to keep that bitmap and send it around through across multiple processes, you can just send a simple URI. And then this is what things like Android Auto already use to get really high quality artwork onto those devices. Now, while that's really nice for a lockscreen background art, we don't actually have lockscreen controls on Lollipop anymore. That all comes from media notifications. Notifications are the new hotness, guys. And it turns out that you probably should have had a notification all along because they're kind of really useful. Now, they've come a long way since the very beginning. If you can imagine like back in the 2.1 era, you couldn't actually have buttons in your notification. We take that so for granted now that you can actually have multiple actions. But that only was actually added in API 14. So there's a lot of kind of compactness to this where you're like, all right. Well, which versions support what things for media buttons and that type of thing? But you don't have to do any of that because NotificationCompat.MediaStyle does it all for you. Now, it's still more boilerplate than I'd like. So I've created a MediaHelper class that I personally like quite a bit, which takes a context and your MediaSession and then builds out all of the things that you'd need from a notification. I'm going to post this link in these spaces after the talk here. So keep an eye on that if you want the link itself. But it actually is using the getDescription from the MediaMetadata, and this is actually the exact same thing the Android Wear uses to figure out what text to display on your device there. So it's a really nice way of staying in sync and keeps the 27-ish fields that are in MediaMetadata down to a much more manageable number, which you can then use for your notification. And then of course, the other part that's sometimes really tricky for notifications is actually getting the PendingIntents for the Play/Pause button, for the Next button. And we still want to reuse all of the things we've already built. We don't want yet another custom path of PendingIntends to your callbacks. So in this case, we can actually build these intents pretty simply just by using this getActionIntent method which is actually going to reuse our MediaButtonReceiver that we included in our app. We're going to reuse that and kind of fake it so that it's like a media button came in, but its in fact a notification. So this way, we actually get all of the stuff already there. So we create our builder using the MediaStyle helper. And then we use setSmallIcon because we kind of need those for notifications. And then we need to call setColor. Now, I'm not sure if you noticed the Play Music and stuff did not actually use a bright orange for their color because it turns out that on the Lollipop through Marshmallow devices, this was the full background for your notification. So the giant bright pink that you use for your branding for your app, probably not the best color to use here. You'll note in Android N it's actually much more aligned with all of the other styles where it's just used as an accent color, not actually as the giant garish background for the notification. So baby steps. But keep in mind for a color, this might actually be something you want to check your API version on. And it wouldn't be a very useful media notification if it didn't have any actions. So in this case, we're adding a Pause button to our notification and using getActionIntent-- in this case, just passing in the Play/Pause key event, just to say, hey, this is a Play/Pause button. And now this will trigger all of the same logic as if someone had hit the Play/Pause button on their headset so they don't actually need to build any more code to support these actions. And then of course, we want to our actual MediaStyle itself. So MediaStyle has this great property where it supports both an expanded style-- the two line style, where it has larger album art and up to five actions-- as well as the compact view where you could get only a max of two actions on that compact view. But it still works. And so you'll need to actually tell which actions you want to support in that compact view. You can imagine if you had Next Track and then Play/Pause and then Previous Track. You'd probably want to support the Play/Pause action as the highest most action because that's probably the one the users will want to use the most. It's a zero base based on the order you called addAction. Now, the other thing we really-- really important. Don't forget. Again, this is another this will break you if you forget it-- is calling setMediaSession. Now, this adds our SessionToken that we talked about before to the notification. So this is what things like Android Wear are actually using to get a reference to your token. So if you forget this all of a sudden, you'll get things like, well, the notification shows up on Wear because we added the metadata and all that. But then you hit the button, and it doesn't actually Play/Pause, which is probably the most frustrating thing in the world. So don't forget this line again. So at this point, we actually have what I would call the minimum viable product. So this is kind of what I would consider something that I won't yell at you for. So if you actually do all these things, I'll be reasonably happy. And I would say this applies to all apps. Whether you're video or audio, these are things that you're going to need to do. But of course, we're much farther along, and we don't even know how to build a Pause button for our UI. So we have to build a UI. It's kind of a big deal for apps, and we mentioned MediaControllerCompat is actually really useful for this because it has access to all of your metadata and everything else. So we can actually create a media controller compact either from the token or from the session itself, and FragmentActivity actually has some really nice methods that make it easy to set and then get the MediaController at some point. So here, we can use just TransportControls and say pause. And we're using lambdas here. So it looks a little weird, but it's a lot shorter on slides, which is awesome. But the TransportControls is basically the one-to-one with your callbacks. So you can imagine there's a Play, a Pause, and a Skip to Next, which then triggers onPlay, onPause onSkiptoNext. One-to-one. But of course, just having a Pause button doesn't actually do a whole lot for your UI. You want to keep these things in sync. So you can imagine in the old days where you had a service that was doing things and you had to create some custom situation to actually pass information back and forth. Now you don't need to do that anymore because MediaControllerCompat gives you all of the things you'd need. So get metadata to get the metadata, getPlaybackState to get the playback state. But all of these things are one shot. So you probably also want to use a callback. So here, you get a onMetadataChanged onPlaybackStateChanged, and this actually allows you to stay perfectly in sync even across processes. So you'll actually be sent the information immediately when you call setState or when you call setMetadata, it comes across on the other side. Now, all of what I've talked about has been great for video and audio. But if we're doing background playback with a service, that adds some extra complexity because we don't have our MediaSession.Token in our UI. So we need to do a little bit more, and we still need that token. Oh. OK. So we built a class for that to. Actually MediaBrowserServiceCompat does a lot of this stuff for you besides the actual browsing part-- which if you go to any of the Android Auto talks, will be like the key for them. But we can actually do this in our own app. So to actually build a MediaBrowserServiceCompat, it's pretty straightforward. You add an intent action to MediaBrowserService. This is what calls you out as a media browser. And then you extend the class. And then there's one method, which again, if you don't call it, it breaks everything. That's setSessionToken where, again, we kind of give the token to MediaBrowserService. And there's two other methods, onGetRoute and onLoadChildren, which are required to be implemented. These are really important for Android Auto. But for us, we just need to know that you need to return something not null in onGetRoute for any connections to succeed. So in this case, I'm just saying, am I calling myself good? All right. Great. Here's a route that gives you no children back, and we're on with our life. So if MediaBrowserServiceCompat was one side, MediaBrowserCompat is the other side. So this is what you'd actually use in your UI to connect to your service. So in this case, we pass on the component name, and we just call connect. And it's actually binding to your service and doing all that stuff for you. But the important part is when you get that connection callback, it will call onConnected, and it will be able to call getSessionToken and go about our wonderful ways of hooking our whole UI up and getting all of our own information and setting up our callback and on and on and on and on. And like I mentioned, all of this works across processes. So you have not had to touch Android IPC or AIDLs or any of that fun stuff. But it turns out that you can actually support multiple processes. And if you're doing audio playback in a service, it's actually probably a fairly good idea to have a separate process. So when you have very different parts of your life cycle, it's really important to keep the long-term memory usage as low as possible. If someone's going to be playing audio for multiple hours, you don't want to be taking up extra resources. And Android only kills things at the process level. So if they ever open your UI, that UI piece is going to stay in memory as long as your service is. So using a separate service is actually a really good idea for many reasons, including things like the WebView process being updated. If you're using WebView in your UI process, that's going to be killed automatically. And we don't really want to pause our music just because WebView updated. It seems kind of silly. And actually, we never really want to pause our music if it's actually playing. We want it to continue on forever. So to do this on Android, we're using a foreground service. Now, foreground services have one property in that they require notification, but that's OK because we just built our notification. So for media, it actually makes a lot of sense to use these things together. But there were some problems. The notification use for foreground services are ongoing, i.e. they can't be swiped away. So you can imagine how frustrating it is to a user. You're trying to swipe away that notification. I just want to stop playback. It's fine. I'm good. But they can't if you're using a foreground service. And it turns out there's been a bug for like six years where stopForeground(false) didn't actually make it dismissible. Even though that was no longer our foreground service and the notification was still there, it would still be ongoing. And you still couldn't swipe it away. So we fixed it as part of a MediaStyle basically by adding a little x in the corner. Very low tech solution, but it allows users to hit that x button and allows them to actually delete the service. In this case, we're just sending a stop command. Now, on Lollipop and higher, once you call stopForeground(false), you can't actually swipe to dismiss the notification. So this basically does nothing on Lollipop and higher devices. Now, I also mentioned that MediaBrowserService and MediaBrowserCompat are binding to the service, which is great because that means that your service and your process is already up when someone hits the Play button. So you can instantaneously use start playback, but it also means as soon as your UI dies, your service is going to die with it because it'll be unbound when there's no unbound to it, and you're dead. But that's OK. We'll just need to call StartService when we start playback. And that way, we make sure our service will continue even when our activity dies. So you can see our updated lifecycle of media playback specifically for the service case. So in this case, our notifications have been updated slightly to start foreground when we hit Play, stop foreground when we hit Pause because we do want people to be able to swipe it away when we're actually paused. And then on Stop, we're going to remove our notification. And similarly for the actual service itself, we really want to make sure we call setSessionToken. We need to call StartService when we start playback so we live as long as possible. But of course, when the user hits the Stop button or x's out our notification, we actually do want to call StopSelf so the system can clean up our service. So what's next? There is a great example app called UAMP, our Universal Android Music Player, which is up on GitHub, which uses basically all of these technologies all in one including many, many more. It's a great app that you can run on your devices. There's also a whole bunch of other talks which I would suggest-- the ExoPlayer talk, which I believe is already online. If you're free tonight, after an after party. The Android Auto talks-- they're right out this way if you want to get hands on with Android Auto-- as well as Google Cast, the Cast SDK, and Android Wear for Standalone. So thanks for coming. I'll be out here for questions afterwards. Thanks again. [MUSIC PLAYING]
Info
Channel: Android Developers
Views: 19,104
Rating: 4.7767444 out of 5
Keywords: Android, media, playback, audio, media playback, remote playback, app, apps, Android Auto, Android Wear, io16, product: android, Location: MTV, Team: Scalable Advocacy, Type: Live Event, GDS: Full Production, Other: NoGreenScreen
Id: iIKxyDRjecU
Channel Id: undefined
Length: 45min 13sec (2713 seconds)
Published: Thu May 19 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.