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.content.ComponentName;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.RemoteException;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.Nullable;
25 import android.support.v4.media.MediaBrowserCompat;
26 import android.support.v4.media.MediaBrowserServiceCompat;
27 import android.support.v4.media.MediaMetadataCompat;
28 import android.support.v4.media.session.MediaControllerCompat;
29 import android.support.v4.media.session.MediaControllerCompat.Callback;
30 import android.support.v4.media.session.MediaSessionCompat;
31 import android.support.v4.media.session.PlaybackStateCompat;
32 import android.util.Log;
34 import java.util.ArrayList;
35 import java.util.List;
38 * Helper class for a MediaBrowser that handles connecting, disconnecting,
39 * and basic browsing with simplified callbacks.
41 public class MediaBrowserHelper {
43 private static final String TAG = MediaBrowserHelper.class.getSimpleName();
45 private final Context mContext;
46 private final Class<? extends MediaBrowserServiceCompat> mMediaBrowserServiceClass;
48 private final List<Callback> mCallbackList = new ArrayList<>();
50 private final MediaBrowserConnectionCallback mMediaBrowserConnectionCallback;
51 private final MediaControllerCallback mMediaControllerCallback;
52 private final MediaBrowserSubscriptionCallback mMediaBrowserSubscriptionCallback;
54 private MediaBrowserCompat mMediaBrowser;
57 private MediaControllerCompat mMediaController;
59 public MediaBrowserHelper(Context context,
60 Class<? extends MediaBrowserServiceCompat> serviceClass) {
62 mMediaBrowserServiceClass = serviceClass;
64 mMediaBrowserConnectionCallback = new MediaBrowserConnectionCallback();
65 mMediaControllerCallback = new MediaControllerCallback();
66 mMediaBrowserSubscriptionCallback = new MediaBrowserSubscriptionCallback();
69 public void onStart() {
70 if (mMediaBrowser == null) {
72 new MediaBrowserCompat(
74 new ComponentName(mContext, mMediaBrowserServiceClass),
75 mMediaBrowserConnectionCallback,
77 mMediaBrowser.connect();
79 Log.d(TAG, "onStart: Creating MediaBrowser, and connecting");
82 public void onStop() {
83 if (mMediaController != null) {
84 mMediaController.unregisterCallback(mMediaControllerCallback);
85 mMediaController = null;
87 if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
88 mMediaBrowser.disconnect();
92 Log.d(TAG, "onStop: Releasing MediaController, Disconnecting from MediaBrowser");
96 * Called after connecting with a {@link MediaBrowserServiceCompat}.
98 * Override to perform processing after a connection is established.
100 * @param mediaController {@link MediaControllerCompat} associated with the connected
103 protected void onConnected(@NonNull MediaControllerCompat mediaController) {
107 * Called after loading a browsable {@link MediaBrowserCompat.MediaItem}
109 * @param parentId The media ID of the parent item.
110 * @param children List (possibly empty) of child items.
112 protected void onChildrenLoaded(@NonNull String parentId,
113 @NonNull List<MediaBrowserCompat.MediaItem> children) {
117 * Called when the {@link MediaBrowserServiceCompat} connection is lost.
119 protected void onDisconnected() {
123 protected final MediaControllerCompat getMediaController() {
124 if (mMediaController == null) {
125 throw new IllegalStateException("MediaController is null!");
127 return mMediaController;
131 * The internal state of the app needs to revert to what it looks like when it started before
132 * any connections to the {@link AudiobookService} happens via the {@link MediaSessionCompat}.
134 private void resetState() {
135 performOnAllCallbacks(callback -> callback.onPlaybackStateChanged(null));
136 Log.d(TAG, "resetState: ");
139 public MediaControllerCompat.TransportControls getTransportControls() {
140 if (mMediaController == null) {
141 Log.d(TAG, "getTransportControls: MediaController is null!");
142 throw new IllegalStateException("MediaController is null!");
144 return mMediaController.getTransportControls();
147 public void registerCallback(Callback callback) {
148 if (callback != null) {
149 mCallbackList.add(callback);
151 // Update with the latest metadata/playback state.
152 if (mMediaController != null) {
153 final MediaMetadataCompat metadata = mMediaController.getMetadata();
154 if (metadata != null) {
155 callback.onMetadataChanged(metadata);
158 final PlaybackStateCompat playbackState = mMediaController.getPlaybackState();
159 if (playbackState != null) {
160 callback.onPlaybackStateChanged(playbackState);
166 private void performOnAllCallbacks(@NonNull CallbackCommand command) {
167 for (Callback callback : mCallbackList) {
168 if (callback != null) {
169 command.perform(callback);
175 * Helper for more easily performing operations on all listening clients.
177 private interface CallbackCommand {
178 void perform(@NonNull Callback callback);
181 // Receives callbacks from the MediaBrowser when it has successfully connected to the
182 // MediaBrowserService (AudiobookService).
183 private class MediaBrowserConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
185 // Happens as a result of onStart().
187 public void onConnected() {
189 // Get a MediaController for the MediaSession.
191 new MediaControllerCompat(mContext, mMediaBrowser.getSessionToken());
192 mMediaController.registerCallback(mMediaControllerCallback);
194 // Sync existing MediaSession state to the UI.
195 mMediaControllerCallback.onMetadataChanged(mMediaController.getMetadata());
196 mMediaControllerCallback.onPlaybackStateChanged(
197 mMediaController.getPlaybackState());
199 MediaBrowserHelper.this.onConnected(mMediaController);
200 } catch (RemoteException e) {
201 Log.d(TAG, String.format("onConnected: Problem: %s", e.toString()));
202 throw new RuntimeException(e);
205 mMediaBrowser.subscribe(mMediaBrowser.getRoot(), mMediaBrowserSubscriptionCallback);
209 // Receives callbacks from the MediaBrowser when the MediaBrowserService has loaded new media
210 // that is ready for playback.
211 public class MediaBrowserSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
214 public void onChildrenLoaded(@NonNull String parentId,
215 @NonNull List<MediaBrowserCompat.MediaItem> children) {
216 MediaBrowserHelper.this.onChildrenLoaded(parentId, children);
220 // Receives callbacks from the MediaController and updates the UI state,
221 // i.e.: Which is the current item, whether it's playing or paused, etc.
222 private class MediaControllerCallback extends MediaControllerCompat.Callback {
225 public void onMetadataChanged(final MediaMetadataCompat metadata) {
226 performOnAllCallbacks(callback -> callback.onMetadataChanged(metadata));
230 public void onPlaybackStateChanged(@Nullable final PlaybackStateCompat state) {
231 performOnAllCallbacks(callback -> callback.onPlaybackStateChanged(state));
235 public void onExtrasChanged(Bundle extras) {
236 performOnAllCallbacks(callback -> callback.onExtrasChanged(extras));
239 // This might happen if the AudiobookService is killed while the Activity is in the
240 // foreground and onStart() has been called (but not onStop()).
242 public void onSessionDestroyed() {
244 onPlaybackStateChanged(null);
246 MediaBrowserHelper.this.onDisconnected();