1/*
2 * Copyright (C) 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
17package androidx.leanback.media;
18
19import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_FAST_FORWARD;
20import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_PLAY_PAUSE;
21import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_REPEAT;
22import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_REWIND;
23import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SHUFFLE;
24import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_NEXT;
25import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_PREVIOUS;
26
27import android.content.Context;
28import android.graphics.Bitmap;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.Drawable;
31import android.os.Handler;
32import android.support.v4.media.MediaMetadataCompat;
33import android.support.v4.media.session.MediaControllerCompat;
34import android.support.v4.media.session.PlaybackStateCompat;
35import android.util.Log;
36
37import androidx.leanback.widget.PlaybackControlsRow;
38
39/**
40 * A helper class for implementing a adapter layer for {@link MediaControllerCompat}.
41 */
42public class MediaControllerAdapter extends PlayerAdapter {
43
44    private static final String TAG = "MediaControllerAdapter";
45    private static final boolean DEBUG = false;
46
47    private MediaControllerCompat mController;
48    private Handler mHandler = new Handler();
49
50    // Runnable object to update current media's playing position.
51    private final Runnable mPositionUpdaterRunnable = new Runnable() {
52        @Override
53        public void run() {
54            getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
55            mHandler.postDelayed(this, getUpdatePeriod());
56        }
57    };
58
59    // Update period to post runnable.
60    private int getUpdatePeriod() {
61        return 16;
62    }
63
64    private boolean mIsBuffering = false;
65
66    MediaControllerCompat.Callback mMediaControllerCallback =
67            new MediaControllerCompat.Callback() {
68                @Override
69                public void onPlaybackStateChanged(PlaybackStateCompat state) {
70                    if (mIsBuffering && state.getState() != PlaybackStateCompat.STATE_BUFFERING) {
71                        getCallback().onBufferingStateChanged(MediaControllerAdapter.this, false);
72                        getCallback().onBufferedPositionChanged(MediaControllerAdapter.this);
73                        mIsBuffering = false;
74                    }
75                    if (state.getState() == PlaybackStateCompat.STATE_NONE) {
76                        // The STATE_NONE playback state will only occurs when initialize the player
77                        // at first time.
78                        if (DEBUG) {
79                            Log.d(TAG, "Playback state is none");
80                        }
81                    } else if (state.getState() == PlaybackStateCompat.STATE_STOPPED) {
82                        // STATE_STOPPED is associated with onPlayCompleted() callback.
83                        // STATE_STOPPED playback state will only occurs when the last item in
84                        // play list is finished. And repeat mode is not enabled.
85                        getCallback().onPlayCompleted(MediaControllerAdapter.this);
86                    } else if (state.getState() == PlaybackStateCompat.STATE_PAUSED) {
87                        getCallback().onPlayStateChanged(MediaControllerAdapter.this);
88                        getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
89                    } else if (state.getState() == PlaybackStateCompat.STATE_PLAYING) {
90                        getCallback().onPlayStateChanged(MediaControllerAdapter.this);
91                        getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
92                    } else if (state.getState() == PlaybackStateCompat.STATE_BUFFERING) {
93                        mIsBuffering = true;
94                        getCallback().onBufferingStateChanged(MediaControllerAdapter.this, true);
95                        getCallback().onBufferedPositionChanged(MediaControllerAdapter.this);
96                    } else if (state.getState() == PlaybackStateCompat.STATE_ERROR) {
97                        CharSequence errorMessage = state.getErrorMessage();
98                        if (errorMessage == null) {
99                            getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(),
100                                    "");
101                        } else {
102                            getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(),
103                                    state.getErrorMessage().toString());
104                        }
105                    } else if (state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING) {
106                        getCallback().onPlayStateChanged(MediaControllerAdapter.this);
107                        getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
108                    } else if (state.getState() == PlaybackStateCompat.STATE_REWINDING) {
109                        getCallback().onPlayStateChanged(MediaControllerAdapter.this);
110                        getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
111                    }
112                }
113
114                @Override
115                public void onMetadataChanged(MediaMetadataCompat metadata) {
116                    getCallback().onMetadataChanged(MediaControllerAdapter.this);
117                }
118            };
119
120    /**
121     * Constructor for the adapter using {@link MediaControllerCompat}.
122     *
123     * @param controller Object of MediaControllerCompat..
124     */
125    public MediaControllerAdapter(MediaControllerCompat controller) {
126        if (controller == null) {
127            throw new NullPointerException("Object of MediaControllerCompat is null");
128        }
129        mController = controller;
130    }
131
132    /**
133     * Return the object of {@link MediaControllerCompat} from this class.
134     *
135     * @return Media Controller Compat object owned by this class.
136     */
137    public MediaControllerCompat getMediaController() {
138        return mController;
139    }
140
141    @Override
142    public void play() {
143        mController.getTransportControls().play();
144    }
145
146    @Override
147    public void pause() {
148        mController.getTransportControls().pause();
149    }
150
151    @Override
152    public void seekTo(long positionInMs) {
153        mController.getTransportControls().seekTo(positionInMs);
154    }
155
156    @Override
157    public void next() {
158        mController.getTransportControls().skipToNext();
159    }
160
161    @Override
162    public void previous() {
163        mController.getTransportControls().skipToPrevious();
164    }
165
166    @Override
167    public void fastForward() {
168        mController.getTransportControls().fastForward();
169    }
170
171    @Override
172    public void rewind() {
173        mController.getTransportControls().rewind();
174    }
175
176    @Override
177    public void setRepeatAction(int repeatActionIndex) {
178        int repeatMode = mapRepeatActionToRepeatMode(repeatActionIndex);
179        mController.getTransportControls().setRepeatMode(repeatMode);
180    }
181
182    @Override
183    public void setShuffleAction(int shuffleActionIndex) {
184        int shuffleMode = mapShuffleActionToShuffleMode(shuffleActionIndex);
185        mController.getTransportControls().setShuffleMode(shuffleMode);
186    }
187
188    @Override
189    public boolean isPlaying() {
190        if (mController.getPlaybackState() == null) {
191            return false;
192        }
193        return mController.getPlaybackState().getState()
194                == PlaybackStateCompat.STATE_PLAYING
195                || mController.getPlaybackState().getState()
196                == PlaybackStateCompat.STATE_FAST_FORWARDING
197                || mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_REWINDING;
198    }
199
200    @Override
201    public long getCurrentPosition() {
202        if (mController.getPlaybackState() == null) {
203            return 0;
204        }
205        return mController.getPlaybackState().getPosition();
206    }
207
208    @Override
209    public long getBufferedPosition() {
210        if (mController.getPlaybackState() == null) {
211            return 0;
212        }
213        return mController.getPlaybackState().getBufferedPosition();
214    }
215
216    /**
217     * Get current media's title.
218     *
219     * @return Title of current media.
220     */
221    public CharSequence getMediaTitle() {
222        if (mController.getMetadata() == null) {
223            return "";
224        }
225        return mController.getMetadata().getDescription().getTitle();
226    }
227
228    /**
229     * Get current media's subtitle.
230     *
231     * @return Subtitle of current media.
232     */
233    public CharSequence getMediaSubtitle() {
234        if (mController.getMetadata() == null) {
235            return "";
236        }
237        return mController.getMetadata().getDescription().getSubtitle();
238    }
239
240    /**
241     * Get current media's drawable art.
242     *
243     * @return Drawable art of current media.
244     */
245    public Drawable getMediaArt(Context context) {
246        if (mController.getMetadata() == null) {
247            return null;
248        }
249        Bitmap bitmap = mController.getMetadata().getDescription().getIconBitmap();
250        return bitmap == null ? null : new BitmapDrawable(context.getResources(), bitmap);
251    }
252
253    @Override
254    public long getDuration() {
255        if (mController.getMetadata() == null) {
256            return 0;
257        }
258        return (int) mController.getMetadata().getLong(
259                MediaMetadataCompat.METADATA_KEY_DURATION);
260    }
261
262    @Override
263    public void onAttachedToHost(PlaybackGlueHost host) {
264        mController.registerCallback(mMediaControllerCallback);
265    }
266
267    @Override
268    public void onDetachedFromHost() {
269        mController.unregisterCallback(mMediaControllerCallback);
270    }
271
272    @Override
273    public void setProgressUpdatingEnabled(boolean enabled) {
274        mHandler.removeCallbacks(mPositionUpdaterRunnable);
275        if (!enabled) {
276            return;
277        }
278        mHandler.postDelayed(mPositionUpdaterRunnable, getUpdatePeriod());
279    }
280
281    @Override
282    public long getSupportedActions() {
283        long supportedActions = 0;
284        if (mController.getPlaybackState() == null) {
285            return supportedActions;
286        }
287        long actionsFromController = mController.getPlaybackState().getActions();
288        // Translation.
289        if ((actionsFromController & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
290            supportedActions |= ACTION_PLAY_PAUSE;
291        }
292        if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
293            supportedActions |= ACTION_SKIP_TO_NEXT;
294        }
295        if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
296            supportedActions |= ACTION_SKIP_TO_PREVIOUS;
297        }
298        if ((actionsFromController & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
299            supportedActions |= ACTION_FAST_FORWARD;
300        }
301        if ((actionsFromController & PlaybackStateCompat.ACTION_REWIND) != 0) {
302            supportedActions |= ACTION_REWIND;
303        }
304        if ((actionsFromController & PlaybackStateCompat.ACTION_SET_REPEAT_MODE) != 0) {
305            supportedActions |= ACTION_REPEAT;
306        }
307        if ((actionsFromController & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE) != 0) {
308            supportedActions |= ACTION_SHUFFLE;
309        }
310        return supportedActions;
311    }
312
313    /**
314     * This function will translate the index of RepeatAction in PlaybackControlsRow to
315     * the repeat mode which is defined by PlaybackStateCompat.
316     *
317     * @param repeatActionIndex Index of RepeatAction in PlaybackControlsRow.
318     * @return Repeat Mode in playback state.
319     */
320    private int mapRepeatActionToRepeatMode(int repeatActionIndex) {
321        switch (repeatActionIndex) {
322            case PlaybackControlsRow.RepeatAction.INDEX_NONE:
323                return PlaybackStateCompat.REPEAT_MODE_NONE;
324            case PlaybackControlsRow.RepeatAction.INDEX_ALL:
325                return PlaybackStateCompat.REPEAT_MODE_ALL;
326            case PlaybackControlsRow.RepeatAction.INDEX_ONE:
327                return PlaybackStateCompat.REPEAT_MODE_ONE;
328        }
329        return -1;
330    }
331
332    /**
333     * This function will translate the index of RepeatAction in PlaybackControlsRow to
334     * the repeat mode which is defined by PlaybackStateCompat.
335     *
336     * @param shuffleActionIndex Index of RepeatAction in PlaybackControlsRow.
337     * @return Repeat Mode in playback state.
338     */
339    private int mapShuffleActionToShuffleMode(int shuffleActionIndex) {
340        switch (shuffleActionIndex) {
341            case PlaybackControlsRow.ShuffleAction.INDEX_OFF:
342                return PlaybackStateCompat.SHUFFLE_MODE_NONE;
343            case PlaybackControlsRow.ShuffleAction.INDEX_ON:
344                return PlaybackStateCompat.SHUFFLE_MODE_ALL;
345        }
346        return -1;
347    }
348}
349
350