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.content.Context;
20 import android.media.PlaybackParams;
21 import android.media.session.PlaybackState;
22 import android.media.tv.TvContentRating;
23 import android.media.tv.TvInputManager;
24 import android.media.tv.TvTrackInfo;
25 import android.media.tv.TvView;
26 import android.text.TextUtils;
27 import android.util.Log;
28 import com.android.tv.common.compat.TvViewCompat.TvInputCallbackCompat;
29 import com.android.tv.dvr.DvrTvView;
30 import com.android.tv.dvr.data.RecordedProgram;
31 import com.android.tv.ui.AppLayerTvView;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.concurrent.TimeUnit;
35 
36 /** Player for recorded programs. */
37 public class DvrPlayer {
38     private static final String TAG = "DvrPlayer";
39     private static final boolean DEBUG = false;
40 
41     /** The max rewinding speed supported by DVR player. */
42     public static final int MAX_REWIND_SPEED = 256;
43     /** The max fast-forwarding speed supported by DVR player. */
44     public static final int MAX_FAST_FORWARD_SPEED = 256;
45 
46     private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2);
47     private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826
48     private static final long FORWARD_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(5);
49 
50     private RecordedProgram mProgram;
51     private long mInitialSeekPositionMs;
52     private final DvrTvView mTvView;
53     private DvrPlayerCallback mCallback;
54     private OnAspectRatioChangedListener mOnAspectRatioChangedListener;
55     private OnContentBlockedListener mOnContentBlockedListener;
56     private OnTracksAvailabilityChangedListener mOnTracksAvailabilityChangedListener;
57     private OnTrackSelectedListener mOnAudioTrackSelectedListener;
58     private OnTrackSelectedListener mOnSubtitleTrackSelectedListener;
59     private String mSelectedAudioTrackId;
60     private String mSelectedSubtitleTrackId;
61     private float mAspectRatio = Float.NaN;
62     private int mPlaybackState = PlaybackState.STATE_NONE;
63     private long mTimeShiftCurrentPositionMs;
64     private boolean mPauseOnPrepared;
65     private boolean mHasClosedCaption;
66     private boolean mHasMultiAudio;
67     private final PlaybackParams mPlaybackParams = new PlaybackParams();
68     private final DvrPlayerCallback mEmptyCallback = new DvrPlayerCallback();
69     private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
70     private boolean mTimeShiftPlayAvailable;
71 
72     /** Callback of DVR player. */
73     public static class DvrPlayerCallback {
74         /**
75          * Called when the playback position is changed. The normal updating frequency is around 1
76          * sec., which is restricted to the implementation of {@link
77          * android.media.tv.TvInputService}.
78          */
onPlaybackPositionChanged(long positionMs)79         public void onPlaybackPositionChanged(long positionMs) {}
80         /** Called when the playback state or the playback speed is changed. */
onPlaybackStateChanged(int playbackState, int playbackSpeed)81         public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {}
82         /** Called when the playback toward the end. */
onPlaybackEnded()83         public void onPlaybackEnded() {}
84         /** Called when the playback is resumed to live position. */
onPlaybackResume()85         public void onPlaybackResume() {}
86     }
87 
88     /** Listener for aspect ratio changed events. */
89     public interface OnAspectRatioChangedListener {
90         /**
91          * Called when the Video's aspect ratio is changed.
92          *
93          * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios. Listeners
94          *     should handle it carefully.
95          */
onAspectRatioChanged(float videoAspectRatio)96         void onAspectRatioChanged(float videoAspectRatio);
97     }
98 
99     /** Listener for content blocked events. */
100     public interface OnContentBlockedListener {
101         /** Called when the Video's aspect ratio is changed. */
onContentBlocked(TvContentRating rating)102         void onContentBlocked(TvContentRating rating);
103     }
104 
105     /** Listener for tracks availability changed events */
106     public interface OnTracksAvailabilityChangedListener {
107         /** Called when the Video's subtitle or audio tracks are changed. */
onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio)108         void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio);
109     }
110 
111     /** Listener for track selected events */
112     public interface OnTrackSelectedListener {
113         /** Called when certain subtitle or audio track is selected. */
onTrackSelected(String selectedTrackId)114         void onTrackSelected(String selectedTrackId);
115     }
116 
117     /** Constructor of DvrPlayer. */
DvrPlayer(AppLayerTvView tvView, Context context)118     public DvrPlayer(AppLayerTvView tvView, Context context) {
119         mTvView = new DvrTvView(context, tvView, this);
120         mTvView.setCaptionEnabled(true);
121         mPlaybackParams.setSpeed(1.0f);
122         setTvViewCallbacks();
123         setCallback(null);
124         mTvView.init();
125     }
126 
127     /**
128      * Prepares playback.
129      *
130      * @param doPlay indicates DVR player do or do not start playback after media is prepared.
131      */
prepare(boolean doPlay)132     public void prepare(boolean doPlay) throws IllegalStateException {
133         if (DEBUG) Log.d(TAG, "prepare()");
134         if (mProgram == null) {
135             throw new IllegalStateException("Recorded program not set");
136         } else if (mPlaybackState != PlaybackState.STATE_NONE) {
137             throw new IllegalStateException("Playback is already prepared");
138         }
139         mTvView.timeShiftPlay(mProgram.getInputId(), mProgram.getUri());
140         mPlaybackState = PlaybackState.STATE_CONNECTING;
141         mPauseOnPrepared = !doPlay;
142         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
143     }
144 
145     /** Resumes playback. */
play()146     public void play() throws IllegalStateException {
147         if (DEBUG) Log.d(TAG, "play()");
148         if (!isPlaybackPrepared()) {
149             throw new IllegalStateException("Recorded program not set or video not ready yet");
150         }
151         switch (mPlaybackState) {
152             case PlaybackState.STATE_FAST_FORWARDING:
153             case PlaybackState.STATE_REWINDING:
154                 setPlaybackSpeed(1);
155                 break;
156             default:
157                 mTvView.timeShiftResume();
158         }
159         mPlaybackState = PlaybackState.STATE_PLAYING;
160         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
161     }
162 
163     /** Pauses playback. */
pause()164     public void pause() throws IllegalStateException {
165         if (DEBUG) Log.d(TAG, "pause()");
166         if (!isPlaybackPrepared()) {
167             throw new IllegalStateException("Recorded program not set or playback not started yet");
168         }
169         switch (mPlaybackState) {
170             case PlaybackState.STATE_FAST_FORWARDING:
171             case PlaybackState.STATE_REWINDING:
172                 setPlaybackSpeed(1);
173                 // falls through
174             case PlaybackState.STATE_PLAYING:
175                 mTvView.timeShiftPause();
176                 mPlaybackState = PlaybackState.STATE_PAUSED;
177                 break;
178             default:
179                 break;
180         }
181         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
182     }
183 
184     /**
185      * Fast-forwards playback with the given speed. If the given speed is larger than {@value
186      * #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}.
187      */
fastForward(int speed)188     public void fastForward(int speed) throws IllegalStateException {
189         if (DEBUG) Log.d(TAG, "fastForward()");
190         if (!isPlaybackPrepared()) {
191             throw new IllegalStateException("Recorded program not set or playback not started yet");
192         }
193         if (speed <= 0) {
194             throw new IllegalArgumentException("Speed cannot be negative or 0");
195         }
196         if (mTimeShiftCurrentPositionMs >= mProgram.getDurationMillis() - SEEK_POSITION_MARGIN_MS) {
197             return;
198         }
199         speed = Math.min(speed, MAX_FAST_FORWARD_SPEED);
200         if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed);
201         setPlaybackSpeed(speed);
202         mPlaybackState = PlaybackState.STATE_FAST_FORWARDING;
203         mCallback.onPlaybackStateChanged(mPlaybackState, speed);
204     }
205 
206     /**
207      * Rewinds playback with the given speed. If the given speed is larger than {@value
208      * #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}.
209      */
rewind(int speed)210     public void rewind(int speed) throws IllegalStateException {
211         if (DEBUG) Log.d(TAG, "rewind()");
212         if (!isPlaybackPrepared()) {
213             throw new IllegalStateException("Recorded program not set or playback not started yet");
214         }
215         if (speed <= 0) {
216             throw new IllegalArgumentException("Speed cannot be negative or 0");
217         }
218         if (mTimeShiftCurrentPositionMs <= REWIND_POSITION_MARGIN_MS) {
219             return;
220         }
221         speed = Math.min(speed, MAX_REWIND_SPEED);
222         if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed);
223         setPlaybackSpeed(-speed);
224         mPlaybackState = PlaybackState.STATE_REWINDING;
225         mCallback.onPlaybackStateChanged(mPlaybackState, speed);
226     }
227 
228     /** Seeks playback to the specified position. */
seekTo(long positionMs)229     public void seekTo(long positionMs) throws IllegalStateException {
230         if (DEBUG) Log.d(TAG, "seekTo()");
231         if (!isPlaybackPrepared()) {
232             throw new IllegalStateException("Recorded program not set or playback not started yet");
233         }
234         if (mProgram == null || mPlaybackState == PlaybackState.STATE_NONE) {
235             return;
236         }
237         positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS);
238         if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs);
239         mTvView.timeShiftSeekTo(positionMs + mStartPositionMs);
240         if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
241                 || mPlaybackState == PlaybackState.STATE_REWINDING) {
242             mPlaybackState = PlaybackState.STATE_PLAYING;
243             mTvView.timeShiftResume();
244             mCallback.onPlaybackStateChanged(mPlaybackState, 1);
245         }
246     }
247 
248     /** Resets playback. */
reset()249     public void reset() {
250         if (DEBUG) Log.d(TAG, "reset()");
251         mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1);
252         mPlaybackState = PlaybackState.STATE_NONE;
253         mTvView.reset();
254         mTimeShiftPlayAvailable = false;
255         mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
256         mTimeShiftCurrentPositionMs = 0;
257         mPlaybackParams.setSpeed(1.0f);
258         mProgram = null;
259         mSelectedAudioTrackId = null;
260         mSelectedSubtitleTrackId = null;
261     }
262 
263     /** Sets callbacks for playback. */
setCallback(DvrPlayerCallback callback)264     public void setCallback(DvrPlayerCallback callback) {
265         if (callback != null) {
266             mCallback = callback;
267         } else {
268             mCallback = mEmptyCallback;
269         }
270     }
271 
272     /** Sets the listener to aspect ratio changing. */
setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener)273     public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) {
274         mOnAspectRatioChangedListener = listener;
275     }
276 
277     /** Sets the listener to content blocking. */
setOnContentBlockedListener(OnContentBlockedListener listener)278     public void setOnContentBlockedListener(OnContentBlockedListener listener) {
279         mOnContentBlockedListener = listener;
280     }
281 
282     /** Sets the listener to tracks changing. */
setOnTracksAvailabilityChangedListener( OnTracksAvailabilityChangedListener listener)283     public void setOnTracksAvailabilityChangedListener(
284             OnTracksAvailabilityChangedListener listener) {
285         mOnTracksAvailabilityChangedListener = listener;
286     }
287 
288     /**
289      * Sets the listener to tracks of the given type being selected.
290      *
291      * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO} or {@link
292      *     TvTrackInfo#TYPE_SUBTITLE}.
293      */
setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener)294     public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) {
295         if (trackType == TvTrackInfo.TYPE_AUDIO) {
296             mOnAudioTrackSelectedListener = listener;
297         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
298             mOnSubtitleTrackSelectedListener = listener;
299         }
300     }
301 
302     /** Gets the listener to tracks of the given type being selected. */
getOnTrackSelectedListener(int trackType)303     public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) {
304         if (trackType == TvTrackInfo.TYPE_AUDIO) {
305             return mOnAudioTrackSelectedListener;
306         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
307             return mOnSubtitleTrackSelectedListener;
308         }
309         return null;
310     }
311 
312     /** Sets recorded programs for playback. If the player is playing another program, stops it. */
setProgram(RecordedProgram program, long initialSeekPositionMs)313     public void setProgram(RecordedProgram program, long initialSeekPositionMs) {
314         if (mProgram != null && mProgram.equals(program)) {
315             return;
316         }
317         if (mPlaybackState != PlaybackState.STATE_NONE) {
318             reset();
319         }
320         mInitialSeekPositionMs = initialSeekPositionMs;
321         mProgram = program;
322     }
323 
324     /** Returns the recorded program now playing. */
getProgram()325     public RecordedProgram getProgram() {
326         return mProgram;
327     }
328 
329     /** Returns the DVR tv view. */
getView()330     public DvrTvView getView() {
331         return mTvView;
332     }
333 
334     /** Returns the currrent playback posistion in msecs. */
getPlaybackPosition()335     public long getPlaybackPosition() {
336         return mTimeShiftCurrentPositionMs;
337     }
338 
339     /** Returns the playback speed currently used. */
getPlaybackSpeed()340     public int getPlaybackSpeed() {
341         return (int) mPlaybackParams.getSpeed();
342     }
343 
344     /** Returns the playback state defined in {@link android.media.session.PlaybackState}. */
getPlaybackState()345     public int getPlaybackState() {
346         return mPlaybackState;
347     }
348 
349     /** Returns the subtitle tracks of the current playback. */
getSubtitleTracks()350     public ArrayList<TvTrackInfo> getSubtitleTracks() {
351         return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE));
352     }
353 
354     /** Returns the audio tracks of the current playback. */
getAudioTracks()355     public ArrayList<TvTrackInfo> getAudioTracks() {
356         List<TvTrackInfo> tracks = mTvView.getTracks(TvTrackInfo.TYPE_AUDIO);
357         return tracks == null ? new ArrayList<>() : new ArrayList<>(tracks);
358     }
359 
360     /** Returns the ID of the selected track of the given type. */
getSelectedTrackId(int trackType)361     public String getSelectedTrackId(int trackType) {
362         if (trackType == TvTrackInfo.TYPE_AUDIO) {
363             return mSelectedAudioTrackId;
364         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
365             return mSelectedSubtitleTrackId;
366         }
367         return null;
368     }
369 
370     /** Returns if playback of the recorded program is started. */
isPlaybackPrepared()371     public boolean isPlaybackPrepared() {
372         return mPlaybackState != PlaybackState.STATE_NONE
373                 && mPlaybackState != PlaybackState.STATE_CONNECTING;
374     }
375 
release()376     public void release() {
377         mTvView.release();
378     }
379 
380     /**
381      * Selects the given track.
382      *
383      * @return ID of the selected track.
384      */
selectTrack(int trackType, TvTrackInfo selectedTrack)385     String selectTrack(int trackType, TvTrackInfo selectedTrack) {
386         String oldSelectedTrackId = getSelectedTrackId(trackType);
387         String newSelectedTrackId = selectedTrack == null ? null : selectedTrack.getId();
388         if (!TextUtils.equals(oldSelectedTrackId, newSelectedTrackId)) {
389             if (selectedTrack == null) {
390                 mTvView.selectTrack(trackType, null);
391                 return null;
392             } else {
393                 List<TvTrackInfo> tracks = mTvView.getTracks(trackType);
394                 if (tracks != null && tracks.contains(selectedTrack)) {
395                     mTvView.selectTrack(trackType, newSelectedTrackId);
396                     return newSelectedTrackId;
397                 } else if (trackType == TvTrackInfo.TYPE_SUBTITLE && oldSelectedTrackId != null) {
398                     // Track not found, disabled closed caption.
399                     mTvView.selectTrack(trackType, null);
400                     return null;
401                 }
402             }
403         }
404         return oldSelectedTrackId;
405     }
406 
setSelectedTrackId(int trackType, String trackId)407     private void setSelectedTrackId(int trackType, String trackId) {
408         if (trackType == TvTrackInfo.TYPE_AUDIO) {
409             mSelectedAudioTrackId = trackId;
410         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
411             mSelectedSubtitleTrackId = trackId;
412         }
413     }
414 
setPlaybackSpeed(int speed)415     private void setPlaybackSpeed(int speed) {
416         mPlaybackParams.setSpeed(speed);
417         mTvView.timeShiftSetPlaybackParams(mPlaybackParams);
418     }
419 
getRealSeekPosition(long seekPositionMs, long endMarginMs)420     private long getRealSeekPosition(long seekPositionMs, long endMarginMs) {
421         return Math.max(0, Math.min(seekPositionMs, mProgram.getDurationMillis() - endMarginMs));
422     }
423 
setTvViewCallbacks()424     private void setTvViewCallbacks() {
425         mTvView.setTimeShiftPositionCallback(
426                 new TvView.TimeShiftPositionCallback() {
427                     @Override
428                     public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
429                         if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs);
430                         mStartPositionMs = timeMs;
431                         if (mTimeShiftPlayAvailable) {
432                             resumeToWatchedPositionIfNeeded();
433                         }
434                     }
435 
436                     @Override
437                     public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
438                         if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs);
439                         if (!mTimeShiftPlayAvailable) {
440                             // Workaround of b/31436263
441                             return;
442                         }
443                         // Workaround of b/32211561, TIF won't report start position when TIS report
444                         // its start position as 0. In that case, we have to do the prework of
445                         // playback
446                         // on the first time we get current position, and the start position should
447                         // be 0
448                         // at that time.
449                         if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) {
450                             mStartPositionMs = 0;
451                             resumeToWatchedPositionIfNeeded();
452                         }
453                         timeMs -= mStartPositionMs;
454                         long bufferedTimeMs =
455                                 System.currentTimeMillis()
456                                         - mProgram.getStartTimeUtcMillis()
457                                         - FORWARD_POSITION_MARGIN_MS;
458                         if ((mPlaybackState == PlaybackState.STATE_REWINDING
459                                         && timeMs <= REWIND_POSITION_MARGIN_MS)
460                                 || (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
461                                         && timeMs > bufferedTimeMs)) {
462                             play();
463                             mCallback.onPlaybackResume();
464                         } else {
465                             mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0);
466                             mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs);
467                             if (timeMs >= mProgram.getDurationMillis()) {
468                                 pause();
469                                 mCallback.onPlaybackEnded();
470                             }
471                         }
472                     }
473                 });
474         mTvView.setCallback(
475                 new TvInputCallbackCompat() {
476                     @Override
477                     public void onTimeShiftStatusChanged(String inputId, int status) {
478                         if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status);
479                         if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
480                                 && mPlaybackState == PlaybackState.STATE_CONNECTING) {
481                             mTimeShiftPlayAvailable = true;
482                             if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
483                                 // onTimeShiftStatusChanged is sometimes called after
484                                 // onTimeShiftStartPositionChanged is called. In this case,
485                                 // resumeToWatchedPositionIfNeeded needs to be called here.
486                                 resumeToWatchedPositionIfNeeded();
487                             }
488                         }
489                     }
490 
491                     @Override
492                     public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
493                         boolean hasClosedCaption =
494                                 !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty();
495                         boolean hasMultiAudio =
496                                 mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1;
497                         if ((hasClosedCaption != mHasClosedCaption
498                                         || hasMultiAudio != mHasMultiAudio)
499                                 && mOnTracksAvailabilityChangedListener != null) {
500                             mOnTracksAvailabilityChangedListener.onTracksAvailabilityChanged(
501                                     hasClosedCaption, hasMultiAudio);
502                         }
503                         mHasClosedCaption = hasClosedCaption;
504                         mHasMultiAudio = hasMultiAudio;
505                     }
506 
507                     @Override
508                     public void onTrackSelected(String inputId, int type, String trackId) {
509                         if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) {
510                             setSelectedTrackId(type, trackId);
511                             OnTrackSelectedListener listener = getOnTrackSelectedListener(type);
512                             if (listener != null) {
513                                 listener.onTrackSelected(trackId);
514                             }
515                         } else if (type == TvTrackInfo.TYPE_VIDEO
516                                 && trackId != null
517                                 && mOnAspectRatioChangedListener != null) {
518                             List<TvTrackInfo> trackInfos =
519                                     mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
520                             if (trackInfos != null) {
521                                 for (TvTrackInfo trackInfo : trackInfos) {
522                                     if (trackInfo.getId().equals(trackId)) {
523                                         float videoAspectRatio;
524                                         float videoPixelAspectRatio =
525                                                 trackInfo.getVideoPixelAspectRatio();
526                                         int videoWidth = trackInfo.getVideoWidth();
527                                         int videoHeight = trackInfo.getVideoHeight();
528                                         if (videoWidth > 0 && videoHeight > 0) {
529                                             videoAspectRatio =
530                                                     (float) trackInfo.getVideoWidth()
531                                                             / trackInfo.getVideoHeight();
532                                             videoAspectRatio *=
533                                                     videoPixelAspectRatio > 0 ?
534                                                             videoPixelAspectRatio : 1;
535                                         } else {
536                                             // Aspect ratio is unknown. Pass the message to
537                                             // listeners.
538                                             videoAspectRatio = 0;
539                                         }
540                                         if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
541                                         if (mAspectRatio != videoAspectRatio
542                                                 || videoAspectRatio == 0) {
543                                             mOnAspectRatioChangedListener.onAspectRatioChanged(
544                                                     videoAspectRatio);
545                                             mAspectRatio = videoAspectRatio;
546                                             return;
547                                         }
548                                     }
549                                 }
550                             }
551                         }
552                     }
553 
554                     @Override
555                     public void onContentBlocked(String inputId, TvContentRating rating) {
556                         if (mOnContentBlockedListener != null) {
557                             mOnContentBlockedListener.onContentBlocked(rating);
558                         }
559                     }
560                 });
561     }
562 
resumeToWatchedPositionIfNeeded()563     private void resumeToWatchedPositionIfNeeded() {
564         if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
565             mTvView.timeShiftSeekTo(
566                     getRealSeekPosition(mInitialSeekPositionMs, SEEK_POSITION_MARGIN_MS)
567                             + mStartPositionMs);
568             mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
569         }
570         if (mPauseOnPrepared) {
571             mTvView.timeShiftPause();
572             mPlaybackState = PlaybackState.STATE_PAUSED;
573             mPauseOnPrepared = false;
574         } else {
575             mTvView.timeShiftResume();
576             mPlaybackState = PlaybackState.STATE_PLAYING;
577         }
578         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
579     }
580 }
581