Added Android code
[wl-app.git] / Android / app / src / main / java / com / moiseum / wolnelektury / view / player / service / AudiobookService.java
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.moiseum.wolnelektury.view.player.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 java.util.ArrayList;
33 import java.util.List;
34
35 public class AudiobookService extends MediaBrowserServiceCompat {
36
37     private static final String TAG = AudiobookService.class.getSimpleName();
38
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";
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, "AudiobookService");
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: AudiobookService 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(AudiobookLibrary.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(AudiobookLibrary.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 onCustomAction(String action, Bundle extras) {
105                     if (ACTION_CLEAR_PLAYLIST.equals(action)) {
106                             mPlaylist.clear();
107                             mQueueIndex = 0;
108                             mSession.setQueue(mPlaylist);
109                     }
110             }
111
112             @Override
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);
117         }
118
119         @Override
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);
124         }
125
126         @Override
127         public void onPrepare() {
128             if (mQueueIndex < 0 && mPlaylist.isEmpty()) {
129                 // Nothing to play.
130                 return;
131             }
132
133             final String mediaId = mPlaylist.get(mQueueIndex).getDescription().getMediaId();
134             mPreparedMedia = AudiobookLibrary.getMetadata(AudiobookService.this, mediaId);
135             mSession.setMetadata(mPreparedMedia);
136
137             if (!mSession.isActive()) {
138                 mSession.setActive(true);
139             }
140         }
141
142         @Override
143         public void onPlay() {
144             if (!isReadyToPlay()) {
145                 // Nothing to play.
146                 return;
147             }
148
149             if (mPreparedMedia == null) {
150                 onPrepare();
151             }
152
153             mPlayback.playFromMedia(mPreparedMedia);
154             Log.d(TAG, "onPlayFromMediaId: MediaSession active");
155         }
156
157         @Override
158         public void onPause() {
159             mPlayback.pause();
160         }
161
162         @Override
163         public void onStop() {
164             mPlayback.stop();
165             mSession.setActive(false);
166         }
167
168         @Override
169         public void onSkipToNext() {
170             mQueueIndex = (++mQueueIndex % mPlaylist.size());
171             mPreparedMedia = null;
172             onPlay();
173         }
174
175         @Override
176         public void onSkipToPrevious() {
177             mQueueIndex = mQueueIndex > 0 ? mQueueIndex - 1 : mPlaylist.size() - 1;
178             mPreparedMedia = null;
179             onPlay();
180         }
181
182         @Override
183         public void onSkipToQueueItem(long id) {
184             mQueueIndex = (int) id;
185             mPreparedMedia = null;
186             onPlay();
187         }
188
189         @Override
190         public void onSeekTo(long pos) {
191             mPlayback.seekTo(pos);
192         }
193
194             @Override
195             public void onFastForward() {
196                     mPlayback.fastForward();
197             }
198
199             @Override
200             public void onRewind() {
201                     mPlayback.rewind();
202             }
203
204             private boolean isReadyToPlay() {
205             return (!mPlaylist.isEmpty());
206         }
207     }
208
209     // MediaPlayerAdapter Callback: MediaPlayerAdapter state -> AudiobookService.
210     public class MediaPlayerListener extends PlaybackInfoListener {
211
212         private final ServiceManager mServiceManager;
213
214         MediaPlayerListener() {
215             mServiceManager = new ServiceManager();
216         }
217
218         @Override
219         public void onPlaybackStateChange(PlaybackStateCompat state) {
220             // Report the state to the MediaSession.
221             mSession.setPlaybackState(state);
222
223             // Manage the started state of this service.
224             switch (state.getState()) {
225                 case PlaybackStateCompat.STATE_PLAYING:
226                     mServiceManager.moveServiceToStartedState(state);
227                     break;
228                 case PlaybackStateCompat.STATE_PAUSED:
229                     mServiceManager.updateNotificationForPause(state);
230                     break;
231                 case PlaybackStateCompat.STATE_STOPPED:
232                     mServiceManager.moveServiceOutOfStartedState(state);
233                     break;
234             }
235         }
236
237             @Override
238             public void onPlaybackProgress(int current) {
239                     Bundle bundle = new Bundle();
240                     bundle.putInt(EXTRA_PLAYBACK_CURRENT, current);
241                     mSession.setExtras(bundle);
242             }
243
244             @Override
245             public void onPlaybackPrepared(int duration) {
246                     Bundle bundle = new Bundle();
247                     bundle.putInt(EXTRA_PLAYBACK_TOTAL, duration);
248                     mSession.setExtras(bundle);
249             }
250
251             class ServiceManager {
252
253             private void moveServiceToStartedState(PlaybackStateCompat state) {
254                 Notification notification =
255                         mMediaNotificationManager.getNotification(
256                                 mPlayback.getCurrentMedia(), state, getSessionToken());
257
258                 if (!mServiceInStartedState) {
259                     ContextCompat.startForegroundService(
260                             AudiobookService.this,
261                             new Intent(AudiobookService.this, AudiobookService.class));
262                     mServiceInStartedState = true;
263                 }
264
265                 startForeground(MediaNotificationManager.NOTIFICATION_ID, notification);
266             }
267
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);
275             }
276
277             private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
278                 stopForeground(true);
279                 stopSelf();
280                 mServiceInStartedState = false;
281             }
282         }
283
284     }
285
286 }