17e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer/* 27e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * Copyright (c) 2016, The Android Open Source Project 37e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * 47e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * Licensed under the Apache License, Version 2.0 (the "License"); 57e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * you may not use this file except in compliance with the License. 67e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * You may obtain a copy of the License at 77e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * 87e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * http://www.apache.org/licenses/LICENSE-2.0 97e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * 107e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * Unless required by applicable law or agreed to in writing, software 117e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * distributed under the License is distributed on an "AS IS" BASIS, 127e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * See the License for the specific language governing permissions and 147e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * limitations under the License. 157e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer */ 167e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerpackage com.android.car.media.localmediaplayer; 177e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 18f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyerimport android.app.Notification; 19f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyerimport android.app.NotificationManager; 20f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyerimport android.app.PendingIntent; 217e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.content.Context; 22f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyerimport android.content.Intent; 23a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyerimport android.content.SharedPreferences; 247e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.AudioManager; 257e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.AudioManager.OnAudioFocusChangeListener; 267e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.MediaDescription; 277e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.MediaMetadata; 287e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.MediaPlayer; 297e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.MediaPlayer.OnCompletionListener; 307e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.session.MediaSession; 317e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.session.MediaSession.QueueItem; 327e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.media.session.PlaybackState; 33d47e834a2872587d83e727d8001815476713e297Rakesh Iyerimport android.media.session.PlaybackState.CustomAction; 347e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.os.Bundle; 357e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport android.util.Log; 367e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 37f304f0a68ff116f402523d2f35f4382797e33d68Tamas Berghammerimport com.android.car.media.localmediaplayer.nano.Proto.Playlist; 38f304f0a68ff116f402523d2f35f4382797e33d68Tamas Berghammerimport com.android.car.media.localmediaplayer.nano.Proto.Song; 39a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 40a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer// Proto should be available in AOSP. 41a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyerimport com.google.protobuf.nano.MessageNano; 42a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyerimport com.google.protobuf.nano.InvalidProtocolBufferNanoException; 43a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 447e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport java.io.IOException; 45a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyerimport java.io.File; 46a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyerimport java.util.ArrayList; 47a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyerimport java.util.Base64; 48d47e834a2872587d83e727d8001815476713e297Rakesh Iyerimport java.util.Collections; 497e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerimport java.util.List; 507e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 517e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer/** 527e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer * TODO: Consider doing all content provider accesses and player operations asynchronously. 537e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer */ 547e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyerpublic class Player extends MediaSession.Callback { 557e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final String TAG = "LMPlayer"; 56a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer private static final String SHARED_PREFS_NAME = "com.android.car.media.localmediaplayer.prefs"; 57a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer private static final String CURRENT_PLAYLIST_KEY = "__CURRENT_PLAYLIST_KEY__"; 58f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private static final int NOTIFICATION_ID = 42; 59f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private static final int REQUEST_CODE = 94043; 607e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 617e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final float PLAYBACK_SPEED = 1.0f; 627e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final float PLAYBACK_SPEED_STOPPED = 1.0f; 637e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final long PLAYBACK_POSITION_STOPPED = 0; 647e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 657e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // Note: Queues loop around so next/previous are always available. 667e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final long PLAYING_ACTIONS = PlaybackState.ACTION_PAUSE 677e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_SKIP_TO_NEXT 687e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer | PlaybackState.ACTION_SKIP_TO_PREVIOUS | PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM; 697e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 707e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final long PAUSED_ACTIONS = PlaybackState.ACTION_PLAY 717e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_SKIP_TO_NEXT 727e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer | PlaybackState.ACTION_SKIP_TO_PREVIOUS; 737e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 747e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private static final long STOPPED_ACTIONS = PlaybackState.ACTION_PLAY 757e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_SKIP_TO_NEXT 767e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer | PlaybackState.ACTION_SKIP_TO_PREVIOUS; 777e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 78d47e834a2872587d83e727d8001815476713e297Rakesh Iyer private static final String SHUFFLE = "android.car.media.localmediaplayer.shuffle"; 79d47e834a2872587d83e727d8001815476713e297Rakesh Iyer 807e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private final Context mContext; 817e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private final MediaSession mSession; 827e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private final AudioManager mAudioManager; 837e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private final PlaybackState mErrorState; 847e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private final DataModel mDataModel; 85d47e834a2872587d83e727d8001815476713e297Rakesh Iyer private final CustomAction mShuffle; 867e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 877e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private List<QueueItem> mQueue; 887e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private int mCurrentQueueIdx = 0; 89a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer private final SharedPreferences mSharedPrefs; 907e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 91f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private NotificationManager mNotificationManager; 92f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private Notification.Builder mPlayingNotificationBuilder; 93f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private Notification.Builder mPausedNotificationBuilder; 94f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 957e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // TODO: Use multiple media players for gapless playback. 967e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private final MediaPlayer mMediaPlayer; 977e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 987e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public Player(Context context, MediaSession session, DataModel dataModel) { 997e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mContext = context; 1007e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mDataModel = dataModel; 1017e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1027e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession = session; 103a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer mSharedPrefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); 1047e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 105d47e834a2872587d83e727d8001815476713e297Rakesh Iyer mShuffle = new CustomAction.Builder(SHUFFLE, context.getString(R.string.shuffle), 106d47e834a2872587d83e727d8001815476713e297Rakesh Iyer R.drawable.shuffle).build(); 107d47e834a2872587d83e727d8001815476713e297Rakesh Iyer 1087e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer = new MediaPlayer(); 1097e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.reset(); 1107e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.setOnCompletionListener(mOnCompletionListener); 1117e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mErrorState = new PlaybackState.Builder() 1127e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setState(PlaybackState.STATE_ERROR, 0, 0) 1137e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setErrorMessage(context.getString(R.string.playback_error)) 1147e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .build(); 115f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 116f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer mNotificationManager = 117f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 118f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 119f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // There are 2 forms of the media notification, when playing it needs to show the controls 120f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // to pause & skip whereas when paused it needs to show controls to play & skip. Setup 121f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // pre-populated builders for both of these up front. 122f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer Notification.Action prevAction = makeNotificationAction( 123f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer LocalMediaBrowserService.ACTION_PREV, R.drawable.ic_prev, R.string.prev); 124f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer Notification.Action nextAction = makeNotificationAction( 125f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer LocalMediaBrowserService.ACTION_NEXT, R.drawable.ic_next, R.string.next); 126f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer Notification.Action playAction = makeNotificationAction( 127f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer LocalMediaBrowserService.ACTION_PLAY, R.drawable.ic_play, R.string.play); 128f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer Notification.Action pauseAction = makeNotificationAction( 129f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer LocalMediaBrowserService.ACTION_PAUSE, R.drawable.ic_pause, R.string.pause); 130f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 131f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // While playing, you need prev, pause, next. 132f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer mPlayingNotificationBuilder = new Notification.Builder(context) 133f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setVisibility(Notification.VISIBILITY_PUBLIC) 134f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setSmallIcon(R.drawable.ic_sd_storage_black) 135f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .addAction(prevAction) 136f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .addAction(pauseAction) 137f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .addAction(nextAction); 138f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 139f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // While paused, you need prev, play, next. 140f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer mPausedNotificationBuilder = new Notification.Builder(context) 141f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setVisibility(Notification.VISIBILITY_PUBLIC) 142f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setSmallIcon(R.drawable.ic_sd_storage_black) 143f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .addAction(prevAction) 144f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .addAction(playAction) 145f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .addAction(nextAction); 146f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer } 147f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 148f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private Notification.Action makeNotificationAction(String action, int iconId, int stringId) { 149f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer PendingIntent intent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, 150f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer new Intent(action), PendingIntent.FLAG_UPDATE_CURRENT); 151f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer Notification.Action notificationAction = new Notification.Action.Builder(iconId, 152f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer mContext.getString(stringId), intent) 153f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .build(); 154f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer return notificationAction; 1557e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 1567e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 1574f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer private boolean requestAudioFocus(Runnable onSuccess) { 1584f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer int result = mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC, 1594f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer AudioManager.AUDIOFOCUS_GAIN); 1604f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 1614f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer onSuccess.run(); 1624f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer return true; 1634f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer } 1644f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer Log.e(TAG, "Failed to acquire audio focus"); 1654f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer return false; 1664f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer } 1674f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer 1687e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 1697e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onPlay() { 1707e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer super.onPlay(); 1717e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 1727e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "onPlay"); 1737e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 1744f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer requestAudioFocus(() -> resumePlayback()); 1757e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 1767e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 1777e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 1787e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onPause() { 1797e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer super.onPause(); 1807e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 1817e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "onPause"); 1827e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 1837e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer pausePlayback(); 1847e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mAudioManager.abandonAudioFocus(mAudioFocusListener); 1857e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 1867e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 1877e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void destroy() { 1887e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer stopPlayback(); 189f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer mNotificationManager.cancelAll(); 1907e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mAudioManager.abandonAudioFocus(mAudioFocusListener); 1917e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.release(); 1927e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 1937e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 194a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer public void saveState() { 195a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer if (mQueue == null || mQueue.isEmpty()) { 196a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return; 197a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 198a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 199a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer Playlist playlist = new Playlist(); 200a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer playlist.songs = new Song[mQueue.size()]; 201a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 202a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer int idx = 0; 203a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer for (QueueItem item : mQueue) { 204a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer Song song = new Song(); 205a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer song.queueId = item.getQueueId(); 206a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer MediaDescription description = item.getDescription(); 207a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer song.mediaId = description.getMediaId(); 208a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer song.title = description.getTitle().toString(); 209a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer song.subtitle = description.getSubtitle().toString(); 210a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer song.path = description.getExtras().getString(DataModel.PATH_KEY); 211a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 212a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer playlist.songs[idx] = song; 213a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer idx++; 214a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 215a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer playlist.currentQueueId = mQueue.get(mCurrentQueueIdx).getQueueId(); 216a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer playlist.name = CURRENT_PLAYLIST_KEY; 217a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 218a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // Go to Base64 to ensure that we can actually store the string in a sharedpref. This is 219a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // slightly wasteful because of the fact that base64 expands the size a bit but it's a 220a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // lot less riskier than abusing the java string to directly store bytes coming out of 221a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // proto encoding. 222a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer String serialized = Base64.getEncoder().encodeToString(MessageNano.toByteArray(playlist)); 223a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer SharedPreferences.Editor editor = mSharedPrefs.edit(); 224a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer editor.putString(CURRENT_PLAYLIST_KEY, serialized); 225a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer editor.commit(); 226a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 227a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 228a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer private boolean maybeRebuildQueue(Playlist playlist) { 229a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer List<QueueItem> queue = new ArrayList<>(); 230a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer int foundIdx = 0; 231a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // You need to check if the playlist actually is still valid because the user could have 232a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // deleted files or taken out the sd card between runs so we might as well check this ahead 233a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // of time before we load up the playlist. 234a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer for (Song song : playlist.songs) { 235a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer File tmp = new File(song.path); 236a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer if (!tmp.exists()) { 237a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer continue; 238a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 239a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 240a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer if (playlist.currentQueueId == song.queueId) { 241a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer foundIdx = queue.size(); 242a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 243a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 244a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer Bundle bundle = new Bundle(); 245a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer bundle.putString(DataModel.PATH_KEY, song.path); 246a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer MediaDescription description = new MediaDescription.Builder() 247a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer .setMediaId(song.mediaId) 248a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer .setTitle(song.title) 249a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer .setSubtitle(song.subtitle) 250a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer .setExtras(bundle) 251a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer .build(); 252a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer queue.add(new QueueItem(description, song.queueId)); 253a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 254a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 255a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer if (queue.isEmpty()) { 256a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return false; 257a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 258a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 259a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer mQueue = queue; 260a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer mCurrentQueueIdx = foundIdx; // Resumes from beginning if last playing song was not found. 261a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 262a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return true; 263a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 264a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 265a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer public boolean maybeRestoreState() { 266a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer String serialized = mSharedPrefs.getString(CURRENT_PLAYLIST_KEY, null); 267a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer if (serialized == null) { 268a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return false; 269a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 270a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 271a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer try { 272a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer Playlist playlist = Playlist.parseFrom(Base64.getDecoder().decode(serialized)); 273a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer if (!maybeRebuildQueue(playlist)) { 274a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return false; 275a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 276a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer updateSessionQueueState(); 2774f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer 2784f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer requestAudioFocus(() -> { 2794f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer try { 2804f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer updatePlaybackStatePlaying(); 2814f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer playCurrentQueueIndex(); 2824f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer } catch (IOException e) { 2834f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer Log.e(TAG, "Restored queue, but couldn't resume playback."); 2844f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer } 2854f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer }); 286a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } catch (IllegalArgumentException | InvalidProtocolBufferNanoException e) { 287a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer // Couldn't restore the playlist. Not the end of the world. 288a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return false; 289a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 290a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 291a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer return true; 292a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 293a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 294a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer private void updateSessionQueueState() { 295a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer mSession.setQueueTitle(mContext.getString(R.string.playlist)); 296a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer mSession.setQueue(mQueue); 297a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer } 298a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer 2997e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void startPlayback(String key) { 3007e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 3017e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "startPlayback()"); 3027e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3037e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3047e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer List<QueueItem> queue = mDataModel.getQueue(); 3057e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer int idx = 0; 3067e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer int foundIdx = -1; 3077e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer for (QueueItem item : queue) { 3087e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (item.getDescription().getMediaId().equals(key)) { 3097e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer foundIdx = idx; 3107e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer break; 3117e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3127e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer idx++; 3137e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3147e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3157e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (foundIdx == -1) { 3167e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(mErrorState); 3177e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer return; 3187e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3197e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3207e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mQueue = queue; 3217e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mCurrentQueueIdx = foundIdx; 3227e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer QueueItem current = mQueue.get(mCurrentQueueIdx); 3237e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer String path = current.getDescription().getExtras().getString(DataModel.PATH_KEY); 3247e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer MediaMetadata metadata = mDataModel.getMetadata(current.getDescription().getMediaId()); 325a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer updateSessionQueueState(); 3267e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3277e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer try { 3287e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer play(path, metadata); 3297e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } catch (IOException e) { 3307e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.e(TAG, "Playback failed.", e); 3317e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(mErrorState); 3327e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3337e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3347e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3357e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void resumePlayback() { 3367e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 3377e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "resumePlayback()"); 3387e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3397e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3407e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer updatePlaybackStatePlaying(); 3417e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3427e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (!mMediaPlayer.isPlaying()) { 3437e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.start(); 3447e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3457e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3467e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 347f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer private void postMediaNotification(Notification.Builder builder) { 348f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer if (mQueue == null) { 349f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer return; 350f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer } 351f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 352f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer MediaDescription current = mQueue.get(mCurrentQueueIdx).getDescription(); 353f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer Notification notification = builder 354f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setStyle(new Notification.MediaStyle().setMediaSession(mSession.getSessionToken())) 355f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setContentTitle(current.getTitle()) 356f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .setContentText(current.getSubtitle()) 357f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer .build(); 358f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer notification.flags |= Notification.FLAG_NO_CLEAR; 359f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer mNotificationManager.notify(NOTIFICATION_ID, notification); 360f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer } 361f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 3627e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void updatePlaybackStatePlaying() { 3637e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (!mSession.isActive()) { 3647e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setActive(true); 3657e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3667e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 367f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // Update the state in the media session. 3687e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer PlaybackState state = new PlaybackState.Builder() 3697e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setState(PlaybackState.STATE_PLAYING, 3707e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.getCurrentPosition(), PLAYBACK_SPEED) 3717e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setActions(PLAYING_ACTIONS) 372d47e834a2872587d83e727d8001815476713e297Rakesh Iyer .addCustomAction(mShuffle) 373d47e834a2872587d83e727d8001815476713e297Rakesh Iyer .setActiveQueueItemId(mQueue.get(mCurrentQueueIdx).getQueueId()) 3747e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .build(); 3757e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(state); 376f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 377f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // Update the media styled notification. 378f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer postMediaNotification(mPlayingNotificationBuilder); 3797e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3807e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3817e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void pausePlayback() { 3827e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 3837e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "pausePlayback()"); 3847e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3857e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3867e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer long currentPosition = 0; 3877e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (mMediaPlayer.isPlaying()) { 3887e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer currentPosition = mMediaPlayer.getCurrentPosition(); 3897e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.pause(); 3907e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 3917e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 3927e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer PlaybackState state = new PlaybackState.Builder() 3937e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setState(PlaybackState.STATE_PAUSED, currentPosition, PLAYBACK_SPEED_STOPPED) 3947e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setActions(PAUSED_ACTIONS) 395d47e834a2872587d83e727d8001815476713e297Rakesh Iyer .addCustomAction(mShuffle) 3967e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .build(); 3977e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(state); 398f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer 399f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer // Update the media styled notification. 400f4a24e8cfd616016b1d64c0a31f57d4268431ad4Rakesh Iyer postMediaNotification(mPausedNotificationBuilder); 4017e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4027e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4037e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void stopPlayback() { 4047e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 4057e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "stopPlayback()"); 4067e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4077e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4087e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (mMediaPlayer.isPlaying()) { 4097e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.stop(); 4107e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4117e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4127e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer PlaybackState state = new PlaybackState.Builder() 4137e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setState(PlaybackState.STATE_STOPPED, PLAYBACK_POSITION_STOPPED, 4147e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer PLAYBACK_SPEED_STOPPED) 4157e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .setActions(STOPPED_ACTIONS) 4167e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer .build(); 4177e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(state); 4187e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4197e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4207e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void advance() throws IOException { 4217e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 4227e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "advance()"); 4237e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4247e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // Go to the next song if one exists. Note that if you were to support gapless 4257e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // playback, you would have to change this code such that you had a currently 4267e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // playing and a loading MediaPlayer and juggled between them while also calling 4277e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // setNextMediaPlayer. 4287e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4297e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (mQueue != null) { 4307e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // Keep looping around when we run off the end of our current queue. 4317e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mCurrentQueueIdx = (mCurrentQueueIdx + 1) % mQueue.size(); 4327e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer playCurrentQueueIndex(); 4337e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } else { 4347e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer stopPlayback(); 4357e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4367e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4377e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4387e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void retreat() throws IOException { 4397e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 4407e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "retreat()"); 4417e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4427e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // Go to the next song if one exists. Note that if you were to support gapless 4437e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // playback, you would have to change this code such that you had a currently 4447e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // playing and a loading MediaPlayer and juggled between them while also calling 4457e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // setNextMediaPlayer. 4467e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (mQueue != null) { 4477e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer // Keep looping around when we run off the end of our current queue. 4487e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mCurrentQueueIdx--; 4497e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (mCurrentQueueIdx < 0) { 4507e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mCurrentQueueIdx = mQueue.size() - 1; 4517e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4527e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer playCurrentQueueIndex(); 4537e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } else { 4547e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer stopPlayback(); 4557e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4567e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4577e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4587e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void playCurrentQueueIndex() throws IOException { 4597e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer MediaDescription next = mQueue.get(mCurrentQueueIdx).getDescription(); 4607e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer String path = next.getExtras().getString(DataModel.PATH_KEY); 4617e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer MediaMetadata metadata = mDataModel.getMetadata(next.getMediaId()); 4627e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4637e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer play(path, metadata); 4647e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4657e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4667e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void play(String path, MediaMetadata metadata) throws IOException { 4677e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 4687e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "play path=" + path + " metadata=" + metadata); 4697e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4707e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4717e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.reset(); 4727e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.setDataSource(path); 4737e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.prepare(); 4747e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mMediaPlayer.start(); 4757e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4767e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (metadata != null) { 4777e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setMetadata(metadata); 4787e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4797e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer updatePlaybackStatePlaying(); 4807e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4817e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4827e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void safeAdvance() { 4837e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer try { 4847e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer advance(); 4857e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } catch (IOException e) { 4867e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.e(TAG, "Failed to advance.", e); 4877e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(mErrorState); 4887e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4897e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4907e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 4917e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private void safeRetreat() { 4927e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer try { 4937e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer retreat(); 4947e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } catch (IOException e) { 4957e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.e(TAG, "Failed to advance.", e); 4967e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(mErrorState); 4977e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4987e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 4997e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 500d47e834a2872587d83e727d8001815476713e297Rakesh Iyer /** 501d47e834a2872587d83e727d8001815476713e297Rakesh Iyer * This is a naive implementation of shuffle, previously played songs may repeat after the 502d47e834a2872587d83e727d8001815476713e297Rakesh Iyer * shuffle operation. Only call this from the main thread. 503d47e834a2872587d83e727d8001815476713e297Rakesh Iyer */ 504d47e834a2872587d83e727d8001815476713e297Rakesh Iyer private void shuffle() { 505d47e834a2872587d83e727d8001815476713e297Rakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 506d47e834a2872587d83e727d8001815476713e297Rakesh Iyer Log.d(TAG, "Shuffling"); 507d47e834a2872587d83e727d8001815476713e297Rakesh Iyer } 508d47e834a2872587d83e727d8001815476713e297Rakesh Iyer 509d47e834a2872587d83e727d8001815476713e297Rakesh Iyer if (mQueue != null) { 510d47e834a2872587d83e727d8001815476713e297Rakesh Iyer QueueItem current = mQueue.remove(mCurrentQueueIdx); 511d47e834a2872587d83e727d8001815476713e297Rakesh Iyer Collections.shuffle(mQueue); 512d47e834a2872587d83e727d8001815476713e297Rakesh Iyer mQueue.add(0, current); 513d47e834a2872587d83e727d8001815476713e297Rakesh Iyer mCurrentQueueIdx = 0; 514a48989c04282c7041b7f17b58cb2d0753075722eRakesh Iyer updateSessionQueueState(); 515d47e834a2872587d83e727d8001815476713e297Rakesh Iyer updatePlaybackStatePlaying(); 516d47e834a2872587d83e727d8001815476713e297Rakesh Iyer } 517d47e834a2872587d83e727d8001815476713e297Rakesh Iyer } 518d47e834a2872587d83e727d8001815476713e297Rakesh Iyer 5197e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 5207e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onPlayFromMediaId(String mediaId, Bundle extras) { 5217e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer super.onPlayFromMediaId(mediaId, extras); 5227e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 5237e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "onPlayFromMediaId mediaId" + mediaId + " extras=" + extras); 5247e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5257e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5264f47437963a9271cda42fea8d57d29f30823b5a7Rakesh Iyer requestAudioFocus(() -> startPlayback(mediaId)); 5277e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5287e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5297e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 5307e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onSkipToNext() { 5317e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 5327e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "onSkipToNext()"); 5337e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5347e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer safeAdvance(); 5357e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5367e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5377e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 5387e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onSkipToPrevious() { 5397e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 5407e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "onSkipToPrevious()"); 5417e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5427e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer safeRetreat(); 5437e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5447e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5457e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 5467e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onSkipToQueueItem(long id) { 5477e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer int idx = (int) id; 5487e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer MediaSession.QueueItem item = mQueue.get(idx); 5497e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer MediaDescription description = item.getDescription(); 5507e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5517e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer String path = description.getExtras().getString(DataModel.PATH_KEY); 5527e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer MediaMetadata metadata = mDataModel.getMetadata(description.getMediaId()); 5537e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5547e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer try { 5557e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer play(path, metadata); 5567e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mCurrentQueueIdx = idx; 5577e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } catch (IOException e) { 5587e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.e(TAG, "Failed to play.", e); 5597e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer mSession.setPlaybackState(mErrorState); 5607e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5617e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5627e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 563d47e834a2872587d83e727d8001815476713e297Rakesh Iyer @Override 564d47e834a2872587d83e727d8001815476713e297Rakesh Iyer public void onCustomAction(String action, Bundle extras) { 565d47e834a2872587d83e727d8001815476713e297Rakesh Iyer switch (action) { 566d47e834a2872587d83e727d8001815476713e297Rakesh Iyer case SHUFFLE: 567d47e834a2872587d83e727d8001815476713e297Rakesh Iyer shuffle(); 568d47e834a2872587d83e727d8001815476713e297Rakesh Iyer break; 569d47e834a2872587d83e727d8001815476713e297Rakesh Iyer default: 570d47e834a2872587d83e727d8001815476713e297Rakesh Iyer Log.e(TAG, "Unhandled custom action: " + action); 571d47e834a2872587d83e727d8001815476713e297Rakesh Iyer } 572d47e834a2872587d83e727d8001815476713e297Rakesh Iyer } 573d47e834a2872587d83e727d8001815476713e297Rakesh Iyer 5747e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() { 5757e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 5767e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onAudioFocusChange(int focus) { 5777e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer switch (focus) { 5787e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer case AudioManager.AUDIOFOCUS_GAIN: 5797e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer resumePlayback(); 5807e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer break; 5817e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer case AudioManager.AUDIOFOCUS_LOSS: 5827e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 5837e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 5847e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer pausePlayback(); 5857e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer break; 586d47e834a2872587d83e727d8001815476713e297Rakesh Iyer default: 587d47e834a2872587d83e727d8001815476713e297Rakesh Iyer Log.e(TAG, "Unhandled audio focus type: " + focus); 5887e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5897e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5907e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer }; 5917e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer 5927e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer private OnCompletionListener mOnCompletionListener = new OnCompletionListener() { 5937e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer @Override 5947e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer public void onCompletion(MediaPlayer mediaPlayer) { 5957e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer if (Log.isLoggable(TAG, Log.DEBUG)) { 5967e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer Log.d(TAG, "onCompletion()"); 5977e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 5987e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer safeAdvance(); 5997e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer } 6007e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer }; 6017e37ae05a9c7c9ecf8569a5bfe752a54f660a6baRakesh Iyer} 602