• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.android.tv.dvr.ui.playback;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.graphics.drawable.Drawable;
22 import android.media.MediaMetadata;
23 import android.media.session.MediaController;
24 import android.media.session.MediaController.TransportControls;
25 import android.media.session.PlaybackState;
26 import android.media.tv.TvTrackInfo;
27 import android.os.Bundle;
28 import android.support.annotation.Nullable;
29 import android.support.v17.leanback.media.PlaybackControlGlue;
30 import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
31 import android.support.v17.leanback.widget.Action;
32 import android.support.v17.leanback.widget.ArrayObjectAdapter;
33 import android.support.v17.leanback.widget.PlaybackControlsRow;
34 import android.support.v17.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
35 import android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction;
36 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
37 import android.support.v17.leanback.widget.RowPresenter;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.KeyEvent;
41 import android.view.View;
42 import com.android.tv.R;
43 import com.android.tv.util.TimeShiftUtils;
44 import java.util.ArrayList;
45 
46 /**
47  * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and send
48  * command to the media controller. It also helps to update playback states displayed in the
49  * fragment according to information the media session provides.
50  */
51 class DvrPlaybackControlHelper extends PlaybackControlGlue {
52     private static final String TAG = "DvrPlaybackControlHelpr";
53     private static final boolean DEBUG = false;
54 
55     private static final int AUDIO_ACTION_ID = 1001;
56 
57     private int mPlaybackState = PlaybackState.STATE_NONE;
58     private int mPlaybackSpeedLevel;
59     private int mPlaybackSpeedId;
60     private boolean mReadyToControl;
61 
62     private final DvrPlaybackOverlayFragment mFragment;
63     private final MediaController mMediaController;
64     private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
65     private final TransportControls mTransportControls;
66     private final int mExtraPaddingTopForNoDescription;
67     private final MultiAction mClosedCaptioningAction;
68     private final MultiAction mMultiAudioAction;
69     private ArrayObjectAdapter mSecondaryActionsAdapter;
70 
DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment)71     DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
72         super(activity, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
73         mFragment = overlayFragment;
74         mMediaController = activity.getMediaController();
75         mMediaController.registerCallback(mMediaControllerCallback);
76         mTransportControls = mMediaController.getTransportControls();
77         mExtraPaddingTopForNoDescription =
78                 activity.getResources()
79                         .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
80         mClosedCaptioningAction = new ClosedCaptioningAction(activity);
81         mMultiAudioAction = new MultiAudioAction(activity);
82         createControlsRowPresenter();
83     }
84 
createControlsRow()85     void createControlsRow() {
86         PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
87         setControlsRow(controlsRow);
88         mSecondaryActionsAdapter = (ArrayObjectAdapter) controlsRow.getSecondaryActionsAdapter();
89     }
90 
createControlsRowPresenter()91     private void createControlsRowPresenter() {
92         AbstractDetailsDescriptionPresenter detailsPresenter =
93                 new AbstractDetailsDescriptionPresenter() {
94                     @Override
95                     protected void onBindDescription(
96                             AbstractDetailsDescriptionPresenter.ViewHolder viewHolder,
97                             Object object) {
98                         PlaybackControlGlue glue = (PlaybackControlGlue) object;
99                         if (glue.hasValidMedia()) {
100                             viewHolder.getTitle().setText(glue.getMediaTitle());
101                             viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
102                         } else {
103                             viewHolder.getTitle().setText("");
104                             viewHolder.getSubtitle().setText("");
105                         }
106                         if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) {
107                             viewHolder.view.setPadding(
108                                     viewHolder.view.getPaddingLeft(),
109                                     mExtraPaddingTopForNoDescription,
110                                     viewHolder.view.getPaddingRight(),
111                                     viewHolder.view.getPaddingBottom());
112                         }
113                     }
114                 };
115         PlaybackControlsRowPresenter presenter =
116                 new PlaybackControlsRowPresenter(detailsPresenter) {
117                     @Override
118                     protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
119                         super.onBindRowViewHolder(vh, item);
120                         vh.setOnKeyListener(DvrPlaybackControlHelper.this);
121                     }
122 
123                     @Override
124                     protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
125                         super.onUnbindRowViewHolder(vh);
126                         vh.setOnKeyListener(null);
127                     }
128                 };
129         presenter.setProgressColor(
130                 getContext().getResources().getColor(R.color.play_controls_progress_bar_watched));
131         presenter.setBackgroundColor(
132                 getContext()
133                         .getResources()
134                         .getColor(R.color.play_controls_body_background_enabled));
135         setControlsRowPresenter(presenter);
136     }
137 
138     @Override
onActionClicked(Action action)139     public void onActionClicked(Action action) {
140         if (mReadyToControl) {
141             int trackType;
142             if (action.getId() == mClosedCaptioningAction.getId()) {
143                 trackType = TvTrackInfo.TYPE_SUBTITLE;
144             } else if (action.getId() == AUDIO_ACTION_ID) {
145                 trackType = TvTrackInfo.TYPE_AUDIO;
146             } else {
147                 super.onActionClicked(action);
148                 return;
149             }
150             ArrayList<TvTrackInfo> trackInfos = mFragment.getTracks(trackType);
151             if (!trackInfos.isEmpty()) {
152                 showSideFragment(trackInfos, mFragment.getSelectedTrackId(trackType));
153             }
154         }
155     }
156 
157     @Override
onKey(View v, int keyCode, KeyEvent event)158     public boolean onKey(View v, int keyCode, KeyEvent event) {
159         return mReadyToControl && super.onKey(v, keyCode, event);
160     }
161 
162     @Override
hasValidMedia()163     public boolean hasValidMedia() {
164         PlaybackState playbackState = mMediaController.getPlaybackState();
165         return playbackState != null;
166     }
167 
168     @Override
isMediaPlaying()169     public boolean isMediaPlaying() {
170         PlaybackState playbackState = mMediaController.getPlaybackState();
171         if (playbackState == null) {
172             return false;
173         }
174         int state = playbackState.getState();
175         return state != PlaybackState.STATE_NONE
176                 && state != PlaybackState.STATE_CONNECTING
177                 && state != PlaybackState.STATE_PAUSED;
178     }
179 
180     /** Returns the ID of the media under playback. */
getMediaId()181     public String getMediaId() {
182         MediaMetadata mediaMetadata = mMediaController.getMetadata();
183         return mediaMetadata == null
184                 ? null
185                 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
186     }
187 
188     @Override
getMediaTitle()189     public CharSequence getMediaTitle() {
190         MediaMetadata mediaMetadata = mMediaController.getMetadata();
191         return mediaMetadata == null
192                 ? ""
193                 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
194     }
195 
196     @Override
getMediaSubtitle()197     public CharSequence getMediaSubtitle() {
198         MediaMetadata mediaMetadata = mMediaController.getMetadata();
199         return mediaMetadata == null
200                 ? ""
201                 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE);
202     }
203 
204     @Override
getMediaDuration()205     public int getMediaDuration() {
206         MediaMetadata mediaMetadata = mMediaController.getMetadata();
207         return mediaMetadata == null
208                 ? 0
209                 : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
210     }
211 
212     @Override
getMediaArt()213     public Drawable getMediaArt() {
214         // Do not show the poster art on control row.
215         return null;
216     }
217 
218     @Override
getSupportedActions()219     public long getSupportedActions() {
220         return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
221     }
222 
223     @Override
getCurrentSpeedId()224     public int getCurrentSpeedId() {
225         return mPlaybackSpeedId;
226     }
227 
228     @Override
getCurrentPosition()229     public int getCurrentPosition() {
230         PlaybackState playbackState = mMediaController.getPlaybackState();
231         if (playbackState == null) {
232             return 0;
233         }
234         return (int) playbackState.getPosition();
235     }
236 
237     /** Unregister media controller's callback. */
unregisterCallback()238     void unregisterCallback() {
239         mMediaController.unregisterCallback(mMediaControllerCallback);
240     }
241 
242     /**
243      * Update the secondary controls row.
244      *
245      * @param hasClosedCaption {@code true} to show the closed caption selection button, {@code
246      *     false} to hide it.
247      * @param hasMultiAudio {@code true} to show the audio track selection button, {@code false} to
248      *     hide it.
249      */
updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio)250     void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) {
251         if (hasClosedCaption) {
252             if (mSecondaryActionsAdapter.indexOf(mClosedCaptioningAction) < 0) {
253                 mSecondaryActionsAdapter.add(0, mClosedCaptioningAction);
254             }
255         } else {
256             mSecondaryActionsAdapter.remove(mClosedCaptioningAction);
257         }
258         if (hasMultiAudio) {
259             if (mSecondaryActionsAdapter.indexOf(mMultiAudioAction) < 0) {
260                 mSecondaryActionsAdapter.add(mMultiAudioAction);
261             }
262         } else {
263             mSecondaryActionsAdapter.remove(mMultiAudioAction);
264         }
265         getHost().notifyPlaybackRowChanged();
266     }
267 
268     @Nullable
hasSecondaryRow()269     Boolean hasSecondaryRow() {
270         if (mSecondaryActionsAdapter == null) {
271             return null;
272         }
273         return mSecondaryActionsAdapter.size() != 0;
274     }
275 
276     @Override
play(int speedId)277     public void play(int speedId) {
278         if (getCurrentSpeedId() == speedId) {
279             return;
280         }
281         if (speedId == PLAYBACK_SPEED_NORMAL) {
282             mTransportControls.play();
283         } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) {
284             mTransportControls.rewind();
285         } else if (speedId >= PLAYBACK_SPEED_FAST_L0) {
286             mTransportControls.fastForward();
287         }
288     }
289 
290     @Override
pause()291     public void pause() {
292         mTransportControls.pause();
293     }
294 
295     /** Notifies closed caption being enabled/disabled to update related UI. */
onSubtitleTrackStateChanged(boolean enabled)296     void onSubtitleTrackStateChanged(boolean enabled) {
297         mClosedCaptioningAction.setIndex(
298                 enabled ? ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
299     }
300 
onStateChanged(int state, long positionMs, int speedLevel)301     private void onStateChanged(int state, long positionMs, int speedLevel) {
302         if (DEBUG) Log.d(TAG, "onStateChanged");
303         getControlsRow().setCurrentTime((int) positionMs);
304         if (state == mPlaybackState && mPlaybackSpeedLevel == speedLevel) {
305             // Only position is changed, no need to update controls row
306             return;
307         }
308         // NOTICE: The below two variables should only be used in this method.
309         // The only usage of them is to confirm if the state is changed or not.
310         mPlaybackState = state;
311         mPlaybackSpeedLevel = speedLevel;
312         switch (state) {
313             case PlaybackState.STATE_PLAYING:
314                 mPlaybackSpeedId = PLAYBACK_SPEED_NORMAL;
315                 setFadingEnabled(true);
316                 mReadyToControl = true;
317                 break;
318             case PlaybackState.STATE_PAUSED:
319                 mPlaybackSpeedId = PLAYBACK_SPEED_PAUSED;
320                 setFadingEnabled(true);
321                 mReadyToControl = true;
322                 break;
323             case PlaybackState.STATE_FAST_FORWARDING:
324                 mPlaybackSpeedId = PLAYBACK_SPEED_FAST_L0 + speedLevel;
325                 setFadingEnabled(false);
326                 mReadyToControl = true;
327                 break;
328             case PlaybackState.STATE_REWINDING:
329                 mPlaybackSpeedId = -PLAYBACK_SPEED_FAST_L0 - speedLevel;
330                 setFadingEnabled(false);
331                 mReadyToControl = true;
332                 break;
333             case PlaybackState.STATE_CONNECTING:
334                 setFadingEnabled(false);
335                 mReadyToControl = false;
336                 break;
337             case PlaybackState.STATE_NONE:
338                 mReadyToControl = false;
339                 break;
340             default:
341                 setFadingEnabled(true);
342                 break;
343         }
344         onStateChanged();
345     }
346 
showSideFragment(ArrayList<TvTrackInfo> trackInfos, String selectedTrackId)347     private void showSideFragment(ArrayList<TvTrackInfo> trackInfos, String selectedTrackId) {
348         Bundle args = new Bundle();
349         args.putParcelableArrayList(DvrPlaybackSideFragment.TRACK_INFOS, trackInfos);
350         args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId);
351         DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment();
352         sideFragment.setArguments(args);
353         mFragment
354                 .getFragmentManager()
355                 .beginTransaction()
356                 .hide(mFragment)
357                 .replace(R.id.dvr_playback_side_fragment, sideFragment)
358                 .addToBackStack(null)
359                 .commit();
360     }
361 
362     private class MediaControllerCallback extends MediaController.Callback {
363         @Override
onPlaybackStateChanged(PlaybackState state)364         public void onPlaybackStateChanged(PlaybackState state) {
365             if (DEBUG) Log.d(TAG, "Playback state changed: " + state.getState());
366             onStateChanged(state.getState(), state.getPosition(), (int) state.getPlaybackSpeed());
367         }
368 
369         @Override
onMetadataChanged(MediaMetadata metadata)370         public void onMetadataChanged(MediaMetadata metadata) {
371             DvrPlaybackControlHelper.this.onMetadataChanged();
372         }
373     }
374 
375     private static class MultiAudioAction extends MultiAction {
MultiAudioAction(Context context)376         MultiAudioAction(Context context) {
377             super(AUDIO_ACTION_ID);
378             setDrawables(new Drawable[] {context.getDrawable(R.drawable.ic_tvoption_multi_track)});
379         }
380     }
381 }
382