Share
Embed
Media Playback the right way
Talk from Big Android BBQ 2015 on taking your app beyond just playing audio. Learn how the Android Support Library makes it easy to integrate your app with bluetooth controls, lockscreens, notifications, Android Wear, and more with this talk focused on best practices for making the best user experience.
Ian Lake
October 22, 2015
Transcript
39 slides
-
About me ~5 years of Android Development experience Previously at
Phunware, Facebook Developer Advocate at Google • Advanced Android Udacity Course • Android Development Patterns -
Playing audio MediaPlayer* *Or ExoPlayer: github.com/google/ExoPlayer Created Playing Paused Stopped
Destroyed MediaPlayer new prepare -> play pause stop release Mission Accomplished! -
Audio Focus // Request audio focus AudioManager audioManager = (AudioManager)
getSystemService(Context.AUDIO_SERVICE); int result = audioManager.requestAudioFocus( mOnAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Proceed with the playing of glorious music } // On stop audioManager.abandonAudioFocus( mOnAudioFocusChangeListener); Ensure apps don’t talk over one another Hold audio focus until we’ve stopped playback -
OnAudioFocusChangeListener • AUDIOFOCUS_LOSS ◦ Stop Playback • AUDIOFOCUS_LOSS_TRANSIENT ◦ Pause
• AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK ◦ Lower volume (keep playing) • AUDIOFOCUS_GAIN ◦ Play at full volume (if previously paused) -
Lifecycle of media playback Created Playing Paused Stopped Destroyed MediaPlayer
new prepare -> play pause stop release Audio Focus request focus abandon focus Done! Not quite... -
private BroadcastReceiver mNoisyReceiver = new BroadcastReceiver() { @Override public void
onReceive(Context context, Intent intent) { // Pause the music } }; // On Play IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); registerReceiver(mNoisyReceiver, filter); // On Pause unregisterReceiver(mNoisyReceiver); ACTION_AUDIO_BECOMING_NOISY -
Lifecycle of media playback Created Playing Paused Stopped Destroyed MediaPlayer
new prepare -> play pause stop release Audio Focus request focus abandon focus NOISY register unregister Minimum viable product? -
Headphones and bluetooth controls ‘Media buttons’ android.intent.action.MEDIA_BUTTON broadcasts sent by
the system Use Support Library provided MediaButtonReceiver -
<service android:name="com.example.android.MediaPlaybackService" android:exported="false" > <intent-filter> <action android:name="android.intent.action.MEDIA_BUTTON" /> </intent-filter> </service>
<!-- Intents received by MediaButtonReceiver will now be available in MediaPlaybackService’s onStartCommand() --> <!-- AndroidManifest.xml --> <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" > <intent-filter> <action android:name="android.intent.action.MEDIA_BUTTON" /> </intent-filter> </receiver> But … not working? -
Your app <--> media APIs Callbacks MediaSessionCompat // In onCreate()
mMediaSession = new MediaSessionCompat( context, LOG_TAG); mMediaSession.setFlags( MediaSessionCompat. FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat. FLAG_HANDLES_TRANSPORT_CONTROLS); mMediaSession.setCallback( new ExampleCallbacks()); -
// Right after audio focus mMediaSession.setActive(true); // On stop mMediaSession.setActive(false);
// In onStartCommand() MediaButtonReceiver.handleIntent( mMediaSession, intent); setActive(boolean) setActive(boolean) handleIntent() links media buttons to Callback methods MediaSessionCompat -
Lock screen controls Requires an image Set via setMetadata(MediaMetadataCompat) Metadata
isn’t just for the lock screen - Android Wear also uses it! -
Important Metadata Fields Bitmap • METADATA_KEY_ART • METADATA_KEY_ALBUM_ART Uri •
METADATA_KEY_ART_URI • METADATA_KEY_ALBUM_ART_URI Also consider METADATA_KEY_USER_RATING Text • METADATA_KEY_TITLE • METADATA_KEY_ARTIST • METADATA_KEY_ALBUM • METADATA_KEY_ALBUM_ARTIST Long • METADATA_KEY_DURATION -
Lifecycle of media playback Created Playing Paused Stopped Destroyed MediaPlayer
new prepare -> play pause stop release Audio Focus request focus abandon focus NOISY register unregister MediaSession Compat new set flags set callback setActive(true) set metadata set state set state setActive(false) release Lock screen on 5.0+? -
Notifications and media controls NotificationCompat.MediaStyle • <API 14: basic notification
• API 14+: up to 3 actions in collapsed notification • API 16+: up to 5 actions in expanded notification • API 21+: uses framework MediaStyle Note: MediaStyle is part of v7.app.NotificationCompat (which extends v4) -
public static NotificationCompat.Builder from( Context context, MediaSessionCompat mediaSession) { }
public static NotificationCompat.Builder from( Context context, MediaSessionCompat mediaSession) { MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); } public static NotificationCompat.Builder from( Context context, MediaSessionCompat mediaSession) { MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) .setContentIntent(controller.getSessionActivity()) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setDeleteIntent(getActionIntent(context, KeyEvent.KEYCODE_MEDIA_STOP)); return builder; } -
public static PendingIntent getActionIntent( Context context, int mediaKeyEvent) { Intent
intent = new Intent(Intent.ACTION_MEDIA_BUTTON); intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, mediaKeyEvent)); return PendingIntent.getBroadcast(context, mediaKeyEvent, intent, 0); } Full code at gist.github.com/ianhanniballake/47617ec3488e0257325c -
NotificationCompat.Builder builder = MediaStyleHelper.from(this, mediaSession); builder .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(this, R.color.primaryDark)); builder
.addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaStyleHelper.getActionIntent(this, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))); builder.setStyle(new NotificationCompat.MediaStyle() .setShowActionsInCompactView(0) .setMediaSession(mediaSession.getSessionToken())); -
Lifecycle of media playback Created Playing Paused Stopped Destroyed MediaPlayer
new prepare -> play pause stop release Audio Focus request focus abandon focus NOISY register unregister MediaSession Compat new set flags set callback setActive(true) set metadata set state set state setActive(false) release Notification show notification update notification clear notification -
Lifecycle of media playback Created Playing Paused Stopped Destroyed MediaPlayer
new prepare -> play pause stop release Audio Focus request focus abandon focus NOISY register unregister MediaSession Compat new set flags set callback setActive(true) set metadata set state set state setActive(false) release Notification start FG stopFG(false) stopFG(true) -
MediaControllerCompat getMetadata() -> MediaMetadataCompat getPlaybackState() -> PlaybackStateCompat getTransportControls() -> 1:1
with Callback MediaControllerCompat.Callback -
MediaBrowserService / MediaBrowser API to connect and retrieve a Token
New APIs for browsing available media items Required for Android Auto integration Adds browse option on Android Wear notification -
Implementing MediaBrowserService Call setSessionToken() in onCreate() New methods: • onGetRoot()
• onLoadChildren() • onLoadItem() See example Universal Android Music Player (UAMP) github.com/googlesamples/android-UniversalMusicPlayer -
Using MediaControllerCompat Bind to Service, build API to retrieve Token
• Pros: ensures Service will be alive and ready while UI is up • Cons: complicated Static getSessionToken() method in Service, send a local broadcast on change • Pros: simple • Cons: no multi-process, still need to start the Service -
Lifecycle of media playback Created Playing Paused Stopped Destroyed MediaPlayer
new prepare -> play pause stop release Audio Focus request focus abandon focus NOISY register unregister MediaSession Compat new set flags set callback setActive(true) set metadata set state set state setActive(false) release Notification start FG stopFG(false) stopFG(true) MediaBrowser Service set session token