Upgrade to Pro — share decks privately, control downloads, hide ads and more …

State of Media Playback

Ian Lake
November 24, 2015

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
Tweet

More Decks by Ian Lake

Other Decks in Programming

Transcript

  1. ~5 years of Android Development experience Previously at Phunware, Facebook

    Developer Advocate at Google • Advanced Android Udacity Course • Android Development Patterns About me
  2. Created Playing Paused Stopped Destroyed MediaPlayer new prepare -> play

    pause stop release Playing audio Media Player* Mission Accomplished! *Or ExoPlayer: github.com/google/ExoPlayer
  3. // 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
  4. 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)
  5. 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...
  6. 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
  7. 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?
  8. <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?
  9. 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
  10. // 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);
  11. Lock screen controls & Metadata Requires an image Set via

    setMetadata(MediaMetadataCompat) Metadata isn’t just for the lock screen - Android Wear also uses it!
  12. 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
  13. 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+?
  14. 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)
  15. 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) {
  16. 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
  17. 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()));
  18. 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
  19. 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
  20. 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
  21. Implementing MediaBrowserService Call setSessionToken() in onCreate() New methods: ➔ onGetRoot()

    ➔ onLoadChildren() ➔ onLoadItem() See example Universal Android Music Player (UAMP) github.com/googlesamples/android-UniversalMusicPlayer
  22. 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
  23. 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