2 * Copyright 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.moiseum.wolnelektury.view.player.service;
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;
32 import java.util.ArrayList;
33 import java.util.List;
35 public class AudiobookService extends MediaBrowserServiceCompat {
37 private static final String TAG = AudiobookService.class.getSimpleName();
39 public static final String ACTION_CLEAR_PLAYLIST = "CommandClear";
40 public static final String EXTRA_PLAYBACK_CURRENT = "PlaybackCurrent";
41 public static final String EXTRA_PLAYBACK_TOTAL = "PlaybackTotal";
43 private MediaSessionCompat mSession;
44 private PlayerAdapter mPlayback;
45 private MediaNotificationManager mMediaNotificationManager;
46 private MediaSessionCallback mCallback;
47 private boolean mServiceInStartedState;
50 public void onCreate() {
53 // Create a new MediaSession.
54 mSession = new MediaSessionCompat(this, "AudiobookService");
55 mCallback = new MediaSessionCallback();
56 mSession.setCallback(mCallback);
58 MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
59 MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS |
60 MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
61 setSessionToken(mSession.getSessionToken());
63 mMediaNotificationManager = new MediaNotificationManager(this);
65 mPlayback = new MediaPlayerAdapter(this, new MediaPlayerListener());
66 Log.d(TAG, "onCreate: AudiobookService creating MediaSession, and MediaNotificationManager");
70 public void onTaskRemoved(Intent rootIntent) {
71 super.onTaskRemoved(rootIntent);
76 public void onDestroy() {
77 mMediaNotificationManager.onDestroy();
80 Log.d(TAG, "onDestroy: MediaPlayerAdapter stopped, and MediaSession released");
84 public BrowserRoot onGetRoot(@NonNull String clientPackageName,
87 return new BrowserRoot(AudiobookLibrary.getRoot(), null);
91 public void onLoadChildren(
92 @NonNull final String parentMediaId,
93 @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
94 result.sendResult(AudiobookLibrary.getMediaItems());
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;
104 public void onCustomAction(String action, Bundle extras) {
105 if (ACTION_CLEAR_PLAYLIST.equals(action)) {
108 mSession.setQueue(mPlaylist);
113 public void onAddQueueItem(MediaDescriptionCompat description) {
114 mPlaylist.add(new MediaSessionCompat.QueueItem(description, description.hashCode()));
115 mQueueIndex = (mQueueIndex == -1) ? 0 : mQueueIndex;
116 mSession.setQueue(mPlaylist);
120 public void onRemoveQueueItem(MediaDescriptionCompat description) {
121 mPlaylist.remove(new MediaSessionCompat.QueueItem(description, description.hashCode()));
122 mQueueIndex = (mPlaylist.isEmpty()) ? -1 : mQueueIndex;
123 mSession.setQueue(mPlaylist);
127 public void onPrepare() {
128 if (mQueueIndex < 0 && mPlaylist.isEmpty()) {
133 final String mediaId = mPlaylist.get(mQueueIndex).getDescription().getMediaId();
134 mPreparedMedia = AudiobookLibrary.getMetadata(AudiobookService.this, mediaId);
135 mSession.setMetadata(mPreparedMedia);
137 if (!mSession.isActive()) {
138 mSession.setActive(true);
143 public void onPlay() {
144 if (!isReadyToPlay()) {
149 if (mPreparedMedia == null) {
153 mPlayback.playFromMedia(mPreparedMedia);
154 Log.d(TAG, "onPlayFromMediaId: MediaSession active");
158 public void onPause() {
163 public void onStop() {
165 mSession.setActive(false);
169 public void onSkipToNext() {
170 mQueueIndex = (++mQueueIndex % mPlaylist.size());
171 mPreparedMedia = null;
176 public void onSkipToPrevious() {
177 mQueueIndex = mQueueIndex > 0 ? mQueueIndex - 1 : mPlaylist.size() - 1;
178 mPreparedMedia = null;
183 public void onSkipToQueueItem(long id) {
184 mQueueIndex = (int) id;
185 mPreparedMedia = null;
190 public void onSeekTo(long pos) {
191 mPlayback.seekTo(pos);
195 public void onFastForward() {
196 mPlayback.fastForward();
200 public void onRewind() {
204 private boolean isReadyToPlay() {
205 return (!mPlaylist.isEmpty());
209 // MediaPlayerAdapter Callback: MediaPlayerAdapter state -> AudiobookService.
210 public class MediaPlayerListener extends PlaybackInfoListener {
212 private final ServiceManager mServiceManager;
214 MediaPlayerListener() {
215 mServiceManager = new ServiceManager();
219 public void onPlaybackStateChange(PlaybackStateCompat state) {
220 // Report the state to the MediaSession.
221 mSession.setPlaybackState(state);
223 // Manage the started state of this service.
224 switch (state.getState()) {
225 case PlaybackStateCompat.STATE_PLAYING:
226 mServiceManager.moveServiceToStartedState(state);
228 case PlaybackStateCompat.STATE_PAUSED:
229 mServiceManager.updateNotificationForPause(state);
231 case PlaybackStateCompat.STATE_STOPPED:
232 mServiceManager.moveServiceOutOfStartedState(state);
238 public void onPlaybackProgress(int current) {
239 Bundle bundle = new Bundle();
240 bundle.putInt(EXTRA_PLAYBACK_CURRENT, current);
241 mSession.setExtras(bundle);
245 public void onPlaybackPrepared(int duration) {
246 Bundle bundle = new Bundle();
247 bundle.putInt(EXTRA_PLAYBACK_TOTAL, duration);
248 mSession.setExtras(bundle);
251 class ServiceManager {
253 private void moveServiceToStartedState(PlaybackStateCompat state) {
254 Notification notification =
255 mMediaNotificationManager.getNotification(
256 mPlayback.getCurrentMedia(), state, getSessionToken());
258 if (!mServiceInStartedState) {
259 ContextCompat.startForegroundService(
260 AudiobookService.this,
261 new Intent(AudiobookService.this, AudiobookService.class));
262 mServiceInStartedState = true;
265 startForeground(MediaNotificationManager.NOTIFICATION_ID, notification);
268 private void updateNotificationForPause(PlaybackStateCompat state) {
269 stopForeground(false);
270 Notification notification =
271 mMediaNotificationManager.getNotification(
272 mPlayback.getCurrentMedia(), state, getSessionToken());
273 mMediaNotificationManager.getNotificationManager()
274 .notify(MediaNotificationManager.NOTIFICATION_ID, notification);
277 private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
278 stopForeground(true);
280 mServiceInStartedState = false;