--- /dev/null
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.moiseum.wolnelektury.view.player.service;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AudiobookService extends MediaBrowserServiceCompat {
+
+ private static final String TAG = AudiobookService.class.getSimpleName();
+
+ public static final String ACTION_CLEAR_PLAYLIST = "CommandClear";
+ public static final String EXTRA_PLAYBACK_CURRENT = "PlaybackCurrent";
+ public static final String EXTRA_PLAYBACK_TOTAL = "PlaybackTotal";
+
+ private MediaSessionCompat mSession;
+ private PlayerAdapter mPlayback;
+ private MediaNotificationManager mMediaNotificationManager;
+ private MediaSessionCallback mCallback;
+ private boolean mServiceInStartedState;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Create a new MediaSession.
+ mSession = new MediaSessionCompat(this, "AudiobookService");
+ mCallback = new MediaSessionCallback();
+ mSession.setCallback(mCallback);
+ mSession.setFlags(
+ MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
+ MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS |
+ MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ setSessionToken(mSession.getSessionToken());
+
+ mMediaNotificationManager = new MediaNotificationManager(this);
+
+ mPlayback = new MediaPlayerAdapter(this, new MediaPlayerListener());
+ Log.d(TAG, "onCreate: AudiobookService creating MediaSession, and MediaNotificationManager");
+ }
+
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ stopSelf();
+ }
+
+ @Override
+ public void onDestroy() {
+ mMediaNotificationManager.onDestroy();
+ mPlayback.stop();
+ mSession.release();
+ Log.d(TAG, "onDestroy: MediaPlayerAdapter stopped, and MediaSession released");
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(@NonNull String clientPackageName,
+ int clientUid,
+ Bundle rootHints) {
+ return new BrowserRoot(AudiobookLibrary.getRoot(), null);
+ }
+
+ @Override
+ public void onLoadChildren(
+ @NonNull final String parentMediaId,
+ @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
+ result.sendResult(AudiobookLibrary.getMediaItems());
+ }
+
+ // MediaSession Callback: Transport Controls -> MediaPlayerAdapter
+ public class MediaSessionCallback extends MediaSessionCompat.Callback {
+ private final List<MediaSessionCompat.QueueItem> mPlaylist = new ArrayList<>();
+ private int mQueueIndex = -1;
+ private MediaMetadataCompat mPreparedMedia;
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+ if (ACTION_CLEAR_PLAYLIST.equals(action)) {
+ mPlaylist.clear();
+ mQueueIndex = 0;
+ mSession.setQueue(mPlaylist);
+ }
+ }
+
+ @Override
+ public void onAddQueueItem(MediaDescriptionCompat description) {
+ mPlaylist.add(new MediaSessionCompat.QueueItem(description, description.hashCode()));
+ mQueueIndex = (mQueueIndex == -1) ? 0 : mQueueIndex;
+ mSession.setQueue(mPlaylist);
+ }
+
+ @Override
+ public void onRemoveQueueItem(MediaDescriptionCompat description) {
+ mPlaylist.remove(new MediaSessionCompat.QueueItem(description, description.hashCode()));
+ mQueueIndex = (mPlaylist.isEmpty()) ? -1 : mQueueIndex;
+ mSession.setQueue(mPlaylist);
+ }
+
+ @Override
+ public void onPrepare() {
+ if (mQueueIndex < 0 && mPlaylist.isEmpty()) {
+ // Nothing to play.
+ return;
+ }
+
+ final String mediaId = mPlaylist.get(mQueueIndex).getDescription().getMediaId();
+ mPreparedMedia = AudiobookLibrary.getMetadata(AudiobookService.this, mediaId);
+ mSession.setMetadata(mPreparedMedia);
+
+ if (!mSession.isActive()) {
+ mSession.setActive(true);
+ }
+ }
+
+ @Override
+ public void onPlay() {
+ if (!isReadyToPlay()) {
+ // Nothing to play.
+ return;
+ }
+
+ if (mPreparedMedia == null) {
+ onPrepare();
+ }
+
+ mPlayback.playFromMedia(mPreparedMedia);
+ Log.d(TAG, "onPlayFromMediaId: MediaSession active");
+ }
+
+ @Override
+ public void onPause() {
+ mPlayback.pause();
+ }
+
+ @Override
+ public void onStop() {
+ mPlayback.stop();
+ mSession.setActive(false);
+ }
+
+ @Override
+ public void onSkipToNext() {
+ mQueueIndex = (++mQueueIndex % mPlaylist.size());
+ mPreparedMedia = null;
+ onPlay();
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ mQueueIndex = mQueueIndex > 0 ? mQueueIndex - 1 : mPlaylist.size() - 1;
+ mPreparedMedia = null;
+ onPlay();
+ }
+
+ @Override
+ public void onSkipToQueueItem(long id) {
+ mQueueIndex = (int) id;
+ mPreparedMedia = null;
+ onPlay();
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ mPlayback.seekTo(pos);
+ }
+
+ @Override
+ public void onFastForward() {
+ mPlayback.fastForward();
+ }
+
+ @Override
+ public void onRewind() {
+ mPlayback.rewind();
+ }
+
+ private boolean isReadyToPlay() {
+ return (!mPlaylist.isEmpty());
+ }
+ }
+
+ // MediaPlayerAdapter Callback: MediaPlayerAdapter state -> AudiobookService.
+ public class MediaPlayerListener extends PlaybackInfoListener {
+
+ private final ServiceManager mServiceManager;
+
+ MediaPlayerListener() {
+ mServiceManager = new ServiceManager();
+ }
+
+ @Override
+ public void onPlaybackStateChange(PlaybackStateCompat state) {
+ // Report the state to the MediaSession.
+ mSession.setPlaybackState(state);
+
+ // Manage the started state of this service.
+ switch (state.getState()) {
+ case PlaybackStateCompat.STATE_PLAYING:
+ mServiceManager.moveServiceToStartedState(state);
+ break;
+ case PlaybackStateCompat.STATE_PAUSED:
+ mServiceManager.updateNotificationForPause(state);
+ break;
+ case PlaybackStateCompat.STATE_STOPPED:
+ mServiceManager.moveServiceOutOfStartedState(state);
+ break;
+ }
+ }
+
+ @Override
+ public void onPlaybackProgress(int current) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_PLAYBACK_CURRENT, current);
+ mSession.setExtras(bundle);
+ }
+
+ @Override
+ public void onPlaybackPrepared(int duration) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_PLAYBACK_TOTAL, duration);
+ mSession.setExtras(bundle);
+ }
+
+ class ServiceManager {
+
+ private void moveServiceToStartedState(PlaybackStateCompat state) {
+ Notification notification =
+ mMediaNotificationManager.getNotification(
+ mPlayback.getCurrentMedia(), state, getSessionToken());
+
+ if (!mServiceInStartedState) {
+ ContextCompat.startForegroundService(
+ AudiobookService.this,
+ new Intent(AudiobookService.this, AudiobookService.class));
+ mServiceInStartedState = true;
+ }
+
+ startForeground(MediaNotificationManager.NOTIFICATION_ID, notification);
+ }
+
+ private void updateNotificationForPause(PlaybackStateCompat state) {
+ stopForeground(false);
+ Notification notification =
+ mMediaNotificationManager.getNotification(
+ mPlayback.getCurrentMedia(), state, getSessionToken());
+ mMediaNotificationManager.getNotificationManager()
+ .notify(MediaNotificationManager.NOTIFICATION_ID, notification);
+ }
+
+ private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
+ stopForeground(true);
+ stopSelf();
+ mServiceInStartedState = false;
+ }
+ }
+
+ }
+
+}
\ No newline at end of file