Share
Embed
State of Media Playback
Talk from Android Dev Summit 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
November 24, 2015
Transcript
39 slides
-
~5 years of Android Development experience Previously at Phunware, Facebook
Developer Advocate at Google • Advanced Android Udacity Course • Android Development Patterns About me -
Created Playing Paused Stopped Destroyed MediaPlayer new prepare -> play
pause stop release Playing audio Media Player* Mission Accomplished! *Or ExoPlayer: github.com/google/ExoPlayer -
// 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); Audio Focus 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) -
Created Playing Paused Stopped Destroyed MediaPlayer new prepare -> play
pause stop release Audio Focus request focus abandon focus Lifecycle of media playback 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 -
Created Playing Paused Stopped Destroyed MediaPlayer new prepare -> play
pause stop release Audio Focus request focus abandon focus NOISY register unregister Lifecycle of media playback 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 // In onCreate() mMediaSession
= new MediaSessionCompat(context, LOG_TAG); mMediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mMediaSession.setCallback(new ExampleCallbacks()); MediaSessionCompat -
// In onStartCommand() MediaButtonReceiver.handleIntent( mMediaSession, intent); setActive(boolean) MediaSessionCompat handleIntent() links
media buttons to Callback methods // Right after audio focus mMediaSession.setActive(true); // On stop mMediaSession.setActive(false); -
Lock screen controls & Metadata Requires an image Set via
setMetadata(MediaMetadataCompat) Metadata isn’t just for the lock screen - Android Wear also uses it! -
Important Metadata Fields Text ➔ METADATA_KEY_TITLE ➔ METADATA_KEY_ARTIST ➔ METADATA_KEY_ALBUM
➔ METADATA_KEY_ALBUM_ARTIST Long ➔ METADATA_KEY_DURATION Bitmap ➔ METADATA_KEY_ART ➔ METADATA_KEY_ALBUM_ART Uri ➔ METADATA_KEY_ART_URI ➔ METADATA_KEY_ALBUM_ART_URI Also consider METADATA_KEY_USER_RATING -
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 Lifecycle of media playback 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) -
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; } MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); public static NotificationCompat.Builder from( Context context, MediaSessionCompat mediaSession) { -
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())); -
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) Lifecycle of media playback -
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 Static getSessionToken() method in Service, send a local
broadcast on change • Pros: simple • Cons: no multi-process, still need to start the Service Bind to Service, build API to retrieve Token ➔ Pros: ensures Service will be alive and ready while UI is up ➔ Cons: complicated -
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 Lifecycle of media playback