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