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

Media Playback the right way

Ian Lake
October 22, 2015

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
Tweet

More Decks by Ian Lake

Other Decks in Programming

Transcript

  1. About me ~5 years of Android Development experience Previously at

    Phunware, Facebook Developer Advocate at Google • Advanced Android Udacity Course • Android Development Patterns
  2. Playing audio MediaPlayer* *Or ExoPlayer: github.com/google/ExoPlayer Created Playing Paused Stopped

    Destroyed MediaPlayer new prepare -> play pause stop release Mission Accomplished!
  3. 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
  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. 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...
  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. 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?
  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 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());
  10. // 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
  11. Lock screen controls Requires an image Set via setMetadata(MediaMetadataCompat) Metadata

    isn’t just for the lock screen - Android Wear also uses it!
  12. 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
  13. 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+?
  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. 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; }
  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. 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
  19. 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)
  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 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
  23. 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