• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.support.v17.leanback.media;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.support.annotation.CallSuper;
22 import android.support.v17.leanback.widget.Action;
23 import android.support.v17.leanback.widget.ArrayObjectAdapter;
24 import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
25 import android.support.v17.leanback.widget.OnActionClickedListener;
26 import android.support.v17.leanback.widget.PlaybackControlsRow;
27 import android.support.v17.leanback.widget.PlaybackRowPresenter;
28 import android.support.v17.leanback.widget.PlaybackTransportRowPresenter;
29 import android.support.v17.leanback.widget.Presenter;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.view.KeyEvent;
33 import android.view.View;
34 
35 import java.util.List;
36 
37 /**
38  * A base abstract class for managing a {@link PlaybackControlsRow} being displayed in
39  * {@link PlaybackGlueHost}. It supports standard playback control actions play/pause and
40  * skip next/previous. This helper class is a glue layer that manages interaction between the
41  * leanback UI components {@link PlaybackControlsRow} {@link PlaybackRowPresenter}
42  * and a functional {@link PlayerAdapter} which represents the underlying
43  * media player.
44  *
45  * <p>The app must pass a {@link PlayerAdapter} in constructor for a specific
46  * implementation e.g. a {@link MediaPlayerAdapter}.
47  * </p>
48  *
49  * <p>The glue has two action bars: primary action bars and secondary action bars. Apps
50  * can provide additional actions by overriding {@link #onCreatePrimaryActions} and / or
51  * {@link #onCreateSecondaryActions} and respond to actions by overriding
52  * {@link #onActionClicked(Action)}.
53  * </p>
54  *
55  * <p>The subclass is responsible for implementing the "repeat mode" in
56  * {@link #onPlayCompleted()}.
57  * </p>
58  *
59  * @param <T> Type of {@link PlayerAdapter} passed in constructor.
60  */
61 public abstract class PlaybackBaseControlGlue<T extends PlayerAdapter> extends PlaybackGlue
62         implements OnActionClickedListener, View.OnKeyListener {
63 
64     static final String TAG = "PlaybackTransportGlue";
65     static final boolean DEBUG = false;
66 
67     final T mPlayerAdapter;
68     PlaybackControlsRow mControlsRow;
69     PlaybackRowPresenter mControlsRowPresenter;
70     PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
71     boolean mIsPlaying = false;
72     boolean mFadeWhenPlaying = true;
73 
74     CharSequence mSubtitle;
75     CharSequence mTitle;
76     Drawable mCover;
77 
78     PlaybackGlueHost.PlayerCallback mPlayerCallback;
79     boolean mBuffering = false;
80     int mVideoWidth = 0;
81     int mVideoHeight = 0;
82     boolean mErrorSet = false;
83     int mErrorCode;
84     String mErrorMessage;
85 
86     final PlayerAdapter.Callback mAdapterCallback = new PlayerAdapter
87             .Callback() {
88 
89         @Override
90         public void onPlayStateChanged(PlayerAdapter wrapper) {
91             if (DEBUG) Log.v(TAG, "onPlayStateChanged");
92             PlaybackBaseControlGlue.this.onPlayStateChanged();
93         }
94 
95         @Override
96         public void onCurrentPositionChanged(PlayerAdapter wrapper) {
97             if (DEBUG) Log.v(TAG, "onCurrentPositionChanged");
98             PlaybackBaseControlGlue.this.onUpdateProgress();
99         }
100 
101         @Override
102         public void onBufferedPositionChanged(PlayerAdapter wrapper) {
103             if (DEBUG) Log.v(TAG, "onBufferedPositionChanged");
104             PlaybackBaseControlGlue.this.onUpdateBufferedProgress();
105         }
106 
107         @Override
108         public void onDurationChanged(PlayerAdapter wrapper) {
109             if (DEBUG) Log.v(TAG, "onDurationChanged");
110             PlaybackBaseControlGlue.this.onUpdateDuration();
111         }
112 
113         @Override
114         public void onPlayCompleted(PlayerAdapter wrapper) {
115             if (DEBUG) Log.v(TAG, "onPlayCompleted");
116             PlaybackBaseControlGlue.this.onPlayCompleted();
117         }
118 
119         @Override
120         public void onPreparedStateChanged(PlayerAdapter wrapper) {
121             if (DEBUG) Log.v(TAG, "onPreparedStateChanged");
122             PlaybackBaseControlGlue.this.onPreparedStateChanged();
123         }
124 
125         @Override
126         public void onVideoSizeChanged(PlayerAdapter wrapper, int width, int height) {
127             mVideoWidth = width;
128             mVideoHeight = height;
129             if (mPlayerCallback != null) {
130                 mPlayerCallback.onVideoSizeChanged(width, height);
131             }
132         }
133 
134         @Override
135         public void onError(PlayerAdapter wrapper, int errorCode, String errorMessage) {
136             mErrorSet = true;
137             mErrorCode = errorCode;
138             mErrorMessage = errorMessage;
139             if (mPlayerCallback != null) {
140                 mPlayerCallback.onError(errorCode, errorMessage);
141             }
142         }
143 
144         @Override
145         public void onBufferingStateChanged(PlayerAdapter wrapper, boolean start) {
146             mBuffering = start;
147             if (mPlayerCallback != null) {
148                 mPlayerCallback.onBufferingStateChanged(start);
149             }
150         }
151     };
152 
153     /**
154      * Constructor for the glue.
155      *
156      * @param context
157      * @param impl Implementation to underlying media player.
158      */
PlaybackBaseControlGlue(Context context, T impl)159     public PlaybackBaseControlGlue(Context context, T impl) {
160         super(context);
161         mPlayerAdapter = impl;
162         mPlayerAdapter.setCallback(mAdapterCallback);
163     }
164 
getPlayerAdapter()165     public final T getPlayerAdapter() {
166         return mPlayerAdapter;
167     }
168 
169     @Override
onAttachedToHost(PlaybackGlueHost host)170     protected void onAttachedToHost(PlaybackGlueHost host) {
171         super.onAttachedToHost(host);
172         host.setOnKeyInterceptListener(this);
173         host.setOnActionClickedListener(this);
174         onCreateDefaultControlsRow();
175         onCreateDefaultRowPresenter();
176         host.setPlaybackRowPresenter(getPlaybackRowPresenter());
177         host.setPlaybackRow(getControlsRow());
178 
179         mPlayerCallback = host.getPlayerCallback();
180         onAttachHostCallback();
181         mPlayerAdapter.onAttachedToHost(host);
182     }
183 
onAttachHostCallback()184     void onAttachHostCallback() {
185         if (mPlayerCallback != null) {
186             if (mVideoWidth != 0 && mVideoHeight != 0) {
187                 mPlayerCallback.onVideoSizeChanged(mVideoWidth, mVideoHeight);
188             }
189             if (mErrorSet) {
190                 mPlayerCallback.onError(mErrorCode, mErrorMessage);
191             }
192             mPlayerCallback.onBufferingStateChanged(mBuffering);
193         }
194     }
195 
onDetachHostCallback()196     void onDetachHostCallback() {
197         mErrorSet = false;
198         mErrorCode = 0;
199         mErrorMessage = null;
200         if (mPlayerCallback != null) {
201             mPlayerCallback.onBufferingStateChanged(false);
202         }
203     }
204 
205     @Override
onHostStart()206     protected void onHostStart() {
207         mPlayerAdapter.setProgressUpdatingEnabled(true);
208     }
209 
210     @Override
onHostStop()211     protected void onHostStop() {
212         mPlayerAdapter.setProgressUpdatingEnabled(false);
213     }
214 
215     @Override
onDetachedFromHost()216     protected void onDetachedFromHost() {
217         onDetachHostCallback();
218         mPlayerCallback = null;
219         mPlayerAdapter.onDetachedFromHost();
220         mPlayerAdapter.setProgressUpdatingEnabled(false);
221         super.onDetachedFromHost();
222     }
223 
onCreateDefaultControlsRow()224     void onCreateDefaultControlsRow() {
225         if (mControlsRow == null) {
226             PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
227             setControlsRow(controlsRow);
228         }
229     }
230 
onCreateDefaultRowPresenter()231     void onCreateDefaultRowPresenter() {
232         if (mControlsRowPresenter == null) {
233             setPlaybackRowPresenter(onCreateRowPresenter());
234         }
235     }
236 
onCreateRowPresenter()237     protected abstract PlaybackRowPresenter onCreateRowPresenter();
238 
239     /**
240      * Sets the controls to auto hide after a timeout when media is playing.
241      * @param enable True to enable auto hide after a timeout when media is playing.
242      * @see PlaybackGlueHost#setControlsOverlayAutoHideEnabled(boolean)
243      */
setControlsOverlayAutoHideEnabled(boolean enable)244     public void setControlsOverlayAutoHideEnabled(boolean enable) {
245         mFadeWhenPlaying = enable;
246         if (!mFadeWhenPlaying && getHost() != null) {
247             getHost().setControlsOverlayAutoHideEnabled(false);
248         }
249     }
250 
251     /**
252      * Returns true if the controls auto hides after a timeout when media is playing.
253      * @see PlaybackGlueHost#isControlsOverlayAutoHideEnabled()
254      */
isControlsOverlayAutoHideEnabled()255     public boolean isControlsOverlayAutoHideEnabled() {
256         return mFadeWhenPlaying;
257     }
258 
259     /**
260      * Sets the controls row to be managed by the glue layer. If
261      * {@link PlaybackControlsRow#getPrimaryActionsAdapter()} is not provided, a default
262      * {@link ArrayObjectAdapter} will be created and initialized in
263      * {@link #onCreatePrimaryActions(ArrayObjectAdapter)}. If
264      * {@link PlaybackControlsRow#getSecondaryActionsAdapter()} is not provided, a default
265      * {@link ArrayObjectAdapter} will be created and initialized in
266      * {@link #onCreateSecondaryActions(ArrayObjectAdapter)}.
267      * The primary actions and playback state related aspects of the row
268      * are updated by the glue.
269      */
setControlsRow(PlaybackControlsRow controlsRow)270     public void setControlsRow(PlaybackControlsRow controlsRow) {
271         mControlsRow = controlsRow;
272         mControlsRow.setCurrentPosition(-1);
273         mControlsRow.setDuration(-1);
274         mControlsRow.setBufferedPosition(-1);
275         if (mControlsRow.getPrimaryActionsAdapter() == null) {
276             ArrayObjectAdapter adapter = new ArrayObjectAdapter(
277                     new ControlButtonPresenterSelector());
278             onCreatePrimaryActions(adapter);
279             mControlsRow.setPrimaryActionsAdapter(adapter);
280         }
281         // Add secondary actions
282         if (mControlsRow.getSecondaryActionsAdapter() == null) {
283             ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
284                     new ControlButtonPresenterSelector());
285             onCreateSecondaryActions(secondaryActions);
286             getControlsRow().setSecondaryActionsAdapter(secondaryActions);
287         }
288         updateControlsRow();
289     }
290 
291     /**
292      * Sets the controls row Presenter to be managed by the glue layer.
293      */
setPlaybackRowPresenter(PlaybackRowPresenter presenter)294     public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
295         mControlsRowPresenter = presenter;
296     }
297 
298     /**
299      * Returns the playback controls row managed by the glue layer.
300      */
getControlsRow()301     public PlaybackControlsRow getControlsRow() {
302         return mControlsRow;
303     }
304 
305     /**
306      * Returns the playback controls row Presenter managed by the glue layer.
307      */
getPlaybackRowPresenter()308     public PlaybackRowPresenter getPlaybackRowPresenter() {
309         return mControlsRowPresenter;
310     }
311 
312     /**
313      * Handles action clicks.  A subclass may override this add support for additional actions.
314      */
315     @Override
onActionClicked(Action action)316     public abstract void onActionClicked(Action action);
317 
318     /**
319      * Handles key events and returns true if handled.  A subclass may override this to provide
320      * additional support.
321      */
322     @Override
onKey(View v, int keyCode, KeyEvent event)323     public abstract boolean onKey(View v, int keyCode, KeyEvent event);
324 
updateControlsRow()325     private void updateControlsRow() {
326         onMetadataChanged();
327     }
328 
329     @Override
isPlaying()330     public final boolean isPlaying() {
331         return mPlayerAdapter.isPlaying();
332     }
333 
334     @Override
play()335     public void play() {
336         mPlayerAdapter.play();
337     }
338 
339     @Override
pause()340     public void pause() {
341         mPlayerAdapter.pause();
342     }
343 
notifyItemChanged(ArrayObjectAdapter adapter, Object object)344     protected static void notifyItemChanged(ArrayObjectAdapter adapter, Object object) {
345         int index = adapter.indexOf(object);
346         if (index >= 0) {
347             adapter.notifyArrayItemRangeChanged(index, 1);
348         }
349     }
350 
351     /**
352      * May be overridden to add primary actions to the adapter. Default implementation add
353      * {@link PlaybackControlsRow.PlayPauseAction}.
354      *
355      * @param primaryActionsAdapter The adapter to add primary {@link Action}s.
356      */
onCreatePrimaryActions(ArrayObjectAdapter primaryActionsAdapter)357     protected void onCreatePrimaryActions(ArrayObjectAdapter primaryActionsAdapter) {
358     }
359 
360     /**
361      * May be overridden to add secondary actions to the adapter.
362      *
363      * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
364      */
onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter)365     protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
366     }
367 
onUpdateProgress()368     void onUpdateProgress() {
369         if (mControlsRow != null) {
370             mControlsRow.setCurrentPosition(mPlayerAdapter.isPrepared()
371                     ? getCurrentPosition() : -1);
372         }
373     }
374 
onUpdateBufferedProgress()375     void onUpdateBufferedProgress() {
376         if (mControlsRow != null) {
377             mControlsRow.setBufferedPosition(mPlayerAdapter.getBufferedPosition());
378         }
379     }
380 
onUpdateDuration()381     void onUpdateDuration() {
382         if (mControlsRow != null) {
383             mControlsRow.setDuration(
384                     mPlayerAdapter.isPrepared() ? mPlayerAdapter.getDuration() : -1);
385         }
386     }
387 
388     /**
389      * @return The duration of the media item in milliseconds.
390      */
getDuration()391     public final long getDuration() {
392         return mPlayerAdapter.getDuration();
393     }
394 
395     /**
396      * @return The current position of the media item in milliseconds.
397      */
getCurrentPosition()398     public long getCurrentPosition() {
399         return mPlayerAdapter.getCurrentPosition();
400     }
401 
402     /**
403      * @return The current buffered position of the media item in milliseconds.
404      */
getBufferedPosition()405     public final long getBufferedPosition() {
406         return mPlayerAdapter.getBufferedPosition();
407     }
408 
409     @Override
isPrepared()410     public final boolean isPrepared() {
411         return mPlayerAdapter.isPrepared();
412     }
413 
414     /**
415      * Event when ready state for play changes.
416      */
417     @CallSuper
onPreparedStateChanged()418     protected void onPreparedStateChanged() {
419         onUpdateDuration();
420         List<PlayerCallback> callbacks = getPlayerCallbacks();
421         if (callbacks != null) {
422             for (int i = 0, size = callbacks.size(); i < size; i++) {
423                 callbacks.get(i).onPreparedStateChanged(this);
424             }
425         }
426     }
427 
428     /**
429      * Sets the drawable representing cover image. The drawable will be rendered by default
430      * description presenter in
431      * {@link PlaybackTransportRowPresenter#setDescriptionPresenter(Presenter)}.
432      * @param cover The drawable representing cover image.
433      */
setArt(Drawable cover)434     public void setArt(Drawable cover) {
435         if (mCover == cover) {
436             return;
437         }
438         this.mCover = cover;
439         mControlsRow.setImageDrawable(mCover);
440         if (getHost() != null) {
441             getHost().notifyPlaybackRowChanged();
442         }
443     }
444 
445     /**
446      * @return The drawable representing cover image.
447      */
getArt()448     public Drawable getArt() {
449         return mCover;
450     }
451 
452     /**
453      * Sets the media subtitle. The subtitle will be rendered by default description presenter
454      * {@link PlaybackTransportRowPresenter#setDescriptionPresenter(Presenter)}.
455      * @param subtitle Subtitle to set.
456      */
setSubtitle(CharSequence subtitle)457     public void setSubtitle(CharSequence subtitle) {
458         if (TextUtils.equals(subtitle, mSubtitle)) {
459             return;
460         }
461         mSubtitle = subtitle;
462         if (getHost() != null) {
463             getHost().notifyPlaybackRowChanged();
464         }
465     }
466 
467     /**
468      * Return The media subtitle.
469      */
getSubtitle()470     public CharSequence getSubtitle() {
471         return mSubtitle;
472     }
473 
474     /**
475      * Sets the media title. The title will be rendered by default description presenter
476      * {@link PlaybackTransportRowPresenter#setDescriptionPresenter(Presenter)}.
477      */
setTitle(CharSequence title)478     public void setTitle(CharSequence title) {
479         if (TextUtils.equals(title, mTitle)) {
480             return;
481         }
482         mTitle = title;
483         if (getHost() != null) {
484             getHost().notifyPlaybackRowChanged();
485         }
486     }
487 
488     /**
489      * Returns the title of the media item.
490      */
getTitle()491     public CharSequence getTitle() {
492         return mTitle;
493     }
494 
495     /**
496      * Event when metadata changed
497      */
onMetadataChanged()498     void onMetadataChanged() {
499         if (mControlsRow == null) {
500             return;
501         }
502 
503         if (DEBUG) Log.v(TAG, "updateRowMetadata");
504 
505         mControlsRow.setImageDrawable(getArt());
506         mControlsRow.setDuration(mPlayerAdapter.getDuration());
507         mControlsRow.setCurrentPosition(getCurrentPosition());
508 
509         if (getHost() != null) {
510             getHost().notifyPlaybackRowChanged();
511         }
512     }
513 
514     /**
515      * Event when play state changed.
516      */
517     @CallSuper
onPlayStateChanged()518     protected void onPlayStateChanged() {
519         List<PlayerCallback> callbacks = getPlayerCallbacks();
520         if (callbacks != null) {
521             for (int i = 0, size = callbacks.size(); i < size; i++) {
522                 callbacks.get(i).onPlayStateChanged(this);
523             }
524         }
525     }
526 
527     /**
528      * Event when play finishes, subclass may handling repeat mode here.
529      */
530     @CallSuper
onPlayCompleted()531     protected void onPlayCompleted() {
532         List<PlayerCallback> callbacks = getPlayerCallbacks();
533         if (callbacks != null) {
534             for (int i = 0, size = callbacks.size(); i < size; i++) {
535                 callbacks.get(i).onPlayCompleted(this);
536             }
537         }
538     }
539 
540     /**
541      * Seek media to a new position.
542      * @param position New position.
543      */
seekTo(long position)544     public final void seekTo(long position) {
545         mPlayerAdapter.seekTo(position);
546     }
547 
548 }
549