1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.mediasession.service; 18 19 import android.app.Notification; 20 import android.content.Intent; 21 import android.os.Bundle; 22 import android.support.annotation.NonNull; 23 import android.support.v4.content.ContextCompat; 24 import android.support.v4.media.MediaBrowserCompat; 25 import android.support.v4.media.MediaBrowserServiceCompat; 26 import android.support.v4.media.MediaDescriptionCompat; 27 import android.support.v4.media.MediaMetadataCompat; 28 import android.support.v4.media.session.MediaSessionCompat; 29 import android.support.v4.media.session.PlaybackStateCompat; 30 import android.util.Log; 31 32 import com.example.android.mediasession.service.contentcatalogs.MusicLibrary; 33 import com.example.android.mediasession.service.notifications.MediaNotificationManager; 34 import com.example.android.mediasession.service.players.MediaPlayerAdapter; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 public class MusicService extends MediaBrowserServiceCompat { 40 41 private static final String TAG = MusicService.class.getSimpleName(); 42 43 private MediaSessionCompat mSession; 44 private PlayerAdapter mPlayback; 45 private MediaNotificationManager mMediaNotificationManager; 46 private MediaSessionCallback mCallback; 47 private boolean mServiceInStartedState; 48 49 @Override 50 public void onCreate() { 51 super.onCreate(); 52 53 // Create a new MediaSession. 54 mSession = new MediaSessionCompat(this, "MusicService"); 55 mCallback = new MediaSessionCallback(); 56 mSession.setCallback(mCallback); 57 mSession.setFlags( 58 MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | 59 MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS | 60 MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); 61 setSessionToken(mSession.getSessionToken()); 62 63 mMediaNotificationManager = new MediaNotificationManager(this); 64 65 mPlayback = new MediaPlayerAdapter(this, new MediaPlayerListener()); 66 Log.d(TAG, "onCreate: MusicService creating MediaSession, and MediaNotificationManager"); 67 } 68 69 @Override 70 public void onTaskRemoved(Intent rootIntent) { 71 super.onTaskRemoved(rootIntent); 72 stopSelf(); 73 } 74 75 @Override 76 public void onDestroy() { 77 mMediaNotificationManager.onDestroy(); 78 mPlayback.stop(); 79 mSession.release(); 80 Log.d(TAG, "onDestroy: MediaPlayerAdapter stopped, and MediaSession released"); 81 } 82 83 @Override 84 public BrowserRoot onGetRoot(@NonNull String clientPackageName, 85 int clientUid, 86 Bundle rootHints) { 87 return new BrowserRoot(MusicLibrary.getRoot(), null); 88 } 89 90 @Override 91 public void onLoadChildren( 92 @NonNull final String parentMediaId, 93 @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) { 94 result.sendResult(MusicLibrary.getMediaItems()); 95 } 96 97 // MediaSession Callback: Transport Controls -> MediaPlayerAdapter 98 public class MediaSessionCallback extends MediaSessionCompat.Callback { 99 private final List<MediaSessionCompat.QueueItem> mPlaylist = new ArrayList<>(); 100 private int mQueueIndex = -1; 101 private MediaMetadataCompat mPreparedMedia; 102 103 @Override 104 public void onAddQueueItem(MediaDescriptionCompat description) { 105 mPlaylist.add(new MediaSessionCompat.QueueItem(description, description.hashCode())); 106 mQueueIndex = (mQueueIndex == -1) ? 0 : mQueueIndex; 107 mSession.setQueue(mPlaylist); 108 } 109 110 @Override 111 public void onRemoveQueueItem(MediaDescriptionCompat description) { 112 mPlaylist.remove(new MediaSessionCompat.QueueItem(description, description.hashCode())); 113 mQueueIndex = (mPlaylist.isEmpty()) ? -1 : mQueueIndex; 114 mSession.setQueue(mPlaylist); 115 } 116 117 @Override 118 public void onPrepare() { 119 if (mQueueIndex < 0 && mPlaylist.isEmpty()) { 120 // Nothing to play. 121 return; 122 } 123 124 final String mediaId = mPlaylist.get(mQueueIndex).getDescription().getMediaId(); 125 mPreparedMedia = MusicLibrary.getMetadata(MusicService.this, mediaId); 126 mSession.setMetadata(mPreparedMedia); 127 128 if (!mSession.isActive()) { 129 mSession.setActive(true); 130 } 131 } 132 133 @Override 134 public void onPlay() { 135 if (!isReadyToPlay()) { 136 // Nothing to play. 137 return; 138 } 139 140 if (mPreparedMedia == null) { 141 onPrepare(); 142 } 143 144 mPlayback.playFromMedia(mPreparedMedia); 145 Log.d(TAG, "onPlayFromMediaId: MediaSession active"); 146 } 147 148 @Override 149 public void onPause() { 150 mPlayback.pause(); 151 } 152 153 @Override 154 public void onStop() { 155 mPlayback.stop(); 156 mSession.setActive(false); 157 } 158 159 @Override 160 public void onSkipToNext() { 161 mQueueIndex = (++mQueueIndex % mPlaylist.size()); 162 mPreparedMedia = null; 163 onPlay(); 164 } 165 166 @Override 167 public void onSkipToPrevious() { 168 mQueueIndex = mQueueIndex > 0 ? mQueueIndex - 1 : mPlaylist.size() - 1; 169 mPreparedMedia = null; 170 onPlay(); 171 } 172 173 @Override 174 public void onSeekTo(long pos) { 175 mPlayback.seekTo(pos); 176 } 177 178 private boolean isReadyToPlay() { 179 return (!mPlaylist.isEmpty()); 180 } 181 } 182 183 // MediaPlayerAdapter Callback: MediaPlayerAdapter state -> MusicService. 184 public class MediaPlayerListener extends PlaybackInfoListener { 185 186 private final ServiceManager mServiceManager; 187 188 MediaPlayerListener() { 189 mServiceManager = new ServiceManager(); 190 } 191 192 @Override 193 public void onPlaybackStateChange(PlaybackStateCompat state) { 194 // Report the state to the MediaSession. 195 mSession.setPlaybackState(state); 196 197 // Manage the started state of this service. 198 switch (state.getState()) { 199 case PlaybackStateCompat.STATE_PLAYING: 200 mServiceManager.moveServiceToStartedState(state); 201 break; 202 case PlaybackStateCompat.STATE_PAUSED: 203 mServiceManager.updateNotificationForPause(state); 204 break; 205 case PlaybackStateCompat.STATE_STOPPED: 206 mServiceManager.moveServiceOutOfStartedState(state); 207 break; 208 } 209 } 210 211 class ServiceManager { 212 213 private void moveServiceToStartedState(PlaybackStateCompat state) { 214 Notification notification = 215 mMediaNotificationManager.getNotification( 216 mPlayback.getCurrentMedia(), state, getSessionToken()); 217 218 if (!mServiceInStartedState) { 219 ContextCompat.startForegroundService( 220 MusicService.this, 221 new Intent(MusicService.this, MusicService.class)); 222 mServiceInStartedState = true; 223 } 224 225 startForeground(MediaNotificationManager.NOTIFICATION_ID, notification); 226 } 227 228 private void updateNotificationForPause(PlaybackStateCompat state) { 229 stopForeground(false); 230 Notification notification = 231 mMediaNotificationManager.getNotification( 232 mPlayback.getCurrentMedia(), state, getSessionToken()); 233 mMediaNotificationManager.getNotificationManager() 234 .notify(MediaNotificationManager.NOTIFICATION_ID, notification); 235 } 236 237 private void moveServiceOutOfStartedState(PlaybackStateCompat state) { 238 stopForeground(true); 239 stopSelf(); 240 mServiceInStartedState = false; 241 } 242 } 243 244 } 245 246 }