• 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.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 currrent playback posistion in msecs. */
getPlaybackPosition()330     public long getPlaybackPosition() {
331         return mTimeShiftCurrentPositionMs;
332     }
333 
334     /** Returns the playback speed currently used. */
getPlaybackSpeed()335     public int getPlaybackSpeed() {
336         return (int) mPlaybackParams.getSpeed();
337     }
338 
339     /** Returns the playback state defined in {@link android.media.session.PlaybackState}. */
getPlaybackState()340     public int getPlaybackState() {
341         return mPlaybackState;
342     }
343 
344     /** Returns the subtitle tracks of the current playback. */
getSubtitleTracks()345     public ArrayList<TvTrackInfo> getSubtitleTracks() {
346         return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE));
347     }
348 
349     /** Returns the audio tracks of the current playback. */
getAudioTracks()350     public ArrayList<TvTrackInfo> getAudioTracks() {
351         List<TvTrackInfo> tracks = mTvView.getTracks(TvTrackInfo.TYPE_AUDIO);
352         return tracks == null ? new ArrayList<>() : new ArrayList<>(tracks);
353     }
354 
355     /** Returns the ID of the selected track of the given type. */
getSelectedTrackId(int trackType)356     public String getSelectedTrackId(int trackType) {
357         if (trackType == TvTrackInfo.TYPE_AUDIO) {
358             return mSelectedAudioTrackId;
359         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
360             return mSelectedSubtitleTrackId;
361         }
362         return null;
363     }
364 
365     /** Returns if playback of the recorded program is started. */
isPlaybackPrepared()366     public boolean isPlaybackPrepared() {
367         return mPlaybackState != PlaybackState.STATE_NONE
368                 && mPlaybackState != PlaybackState.STATE_CONNECTING;
369     }
370 
release()371     public void release() {
372         mTvView.release();
373     }
374 
375     /**
376      * Selects the given track.
377      *
378      * @return ID of the selected track.
379      */
selectTrack(int trackType, TvTrackInfo selectedTrack)380     String selectTrack(int trackType, TvTrackInfo selectedTrack) {
381         String oldSelectedTrackId = getSelectedTrackId(trackType);
382         String newSelectedTrackId = selectedTrack == null ? null : selectedTrack.getId();
383         if (!TextUtils.equals(oldSelectedTrackId, newSelectedTrackId)) {
384             if (selectedTrack == null) {
385                 mTvView.selectTrack(trackType, null);
386                 return null;
387             } else {
388                 List<TvTrackInfo> tracks = mTvView.getTracks(trackType);
389                 if (tracks != null && tracks.contains(selectedTrack)) {
390                     mTvView.selectTrack(trackType, newSelectedTrackId);
391                     return newSelectedTrackId;
392                 } else if (trackType == TvTrackInfo.TYPE_SUBTITLE && oldSelectedTrackId != null) {
393                     // Track not found, disabled closed caption.
394                     mTvView.selectTrack(trackType, null);
395                     return null;
396                 }
397             }
398         }
399         return oldSelectedTrackId;
400     }
401 
setSelectedTrackId(int trackType, String trackId)402     private void setSelectedTrackId(int trackType, String trackId) {
403         if (trackType == TvTrackInfo.TYPE_AUDIO) {
404             mSelectedAudioTrackId = trackId;
405         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
406             mSelectedSubtitleTrackId = trackId;
407         }
408     }
409 
setPlaybackSpeed(int speed)410     private void setPlaybackSpeed(int speed) {
411         mPlaybackParams.setSpeed(speed);
412         mTvView.timeShiftSetPlaybackParams(mPlaybackParams);
413     }
414 
getRealSeekPosition(long seekPositionMs, long endMarginMs)415     private long getRealSeekPosition(long seekPositionMs, long endMarginMs) {
416         return Math.max(0, Math.min(seekPositionMs, mProgram.getDurationMillis() - endMarginMs));
417     }
418 
setTvViewCallbacks()419     private void setTvViewCallbacks() {
420         mTvView.setTimeShiftPositionCallback(
421                 new TvView.TimeShiftPositionCallback() {
422                     @Override
423                     public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
424                         if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs);
425                         mStartPositionMs = timeMs;
426                         if (mTimeShiftPlayAvailable) {
427                             resumeToWatchedPositionIfNeeded();
428                         }
429                     }
430 
431                     @Override
432                     public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
433                         if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs);
434                         if (!mTimeShiftPlayAvailable) {
435                             // Workaround of b/31436263
436                             return;
437                         }
438                         // Workaround of b/32211561, TIF won't report start position when TIS report
439                         // its start position as 0. In that case, we have to do the prework of
440                         // playback
441                         // on the first time we get current position, and the start position should
442                         // be 0
443                         // at that time.
444                         if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) {
445                             mStartPositionMs = 0;
446                             resumeToWatchedPositionIfNeeded();
447                         }
448                         timeMs -= mStartPositionMs;
449                         long bufferedTimeMs =
450                                 System.currentTimeMillis()
451                                         - mProgram.getStartTimeUtcMillis()
452                                         - FORWARD_POSITION_MARGIN_MS;
453                         if ((mPlaybackState == PlaybackState.STATE_REWINDING
454                                         && timeMs <= REWIND_POSITION_MARGIN_MS)
455                                 || (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
456                                         && timeMs > bufferedTimeMs)) {
457                             play();
458                             mCallback.onPlaybackResume();
459                         } else {
460                             mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0);
461                             mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs);
462                             if (timeMs >= mProgram.getDurationMillis()) {
463                                 pause();
464                                 mCallback.onPlaybackEnded();
465                             }
466                         }
467                     }
468                 });
469         mTvView.setCallback(
470                 new TvInputCallbackCompat() {
471                     @Override
472                     public void onTimeShiftStatusChanged(String inputId, int status) {
473                         if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status);
474                         if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
475                                 && mPlaybackState == PlaybackState.STATE_CONNECTING) {
476                             mTimeShiftPlayAvailable = true;
477                             if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
478                                 // onTimeShiftStatusChanged is sometimes called after
479                                 // onTimeShiftStartPositionChanged is called. In this case,
480                                 // resumeToWatchedPositionIfNeeded needs to be called here.
481                                 resumeToWatchedPositionIfNeeded();
482                             }
483                         }
484                     }
485 
486                     @Override
487                     public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
488                         boolean hasClosedCaption =
489                                 !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty();
490                         boolean hasMultiAudio =
491                                 mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1;
492                         if ((hasClosedCaption != mHasClosedCaption
493                                         || hasMultiAudio != mHasMultiAudio)
494                                 && mOnTracksAvailabilityChangedListener != null) {
495                             mOnTracksAvailabilityChangedListener.onTracksAvailabilityChanged(
496                                     hasClosedCaption, hasMultiAudio);
497                         }
498                         mHasClosedCaption = hasClosedCaption;
499                         mHasMultiAudio = hasMultiAudio;
500                     }
501 
502                     @Override
503                     public void onTrackSelected(String inputId, int type, String trackId) {
504                         if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) {
505                             setSelectedTrackId(type, trackId);
506                             OnTrackSelectedListener listener = getOnTrackSelectedListener(type);
507                             if (listener != null) {
508                                 listener.onTrackSelected(trackId);
509                             }
510                         } else if (type == TvTrackInfo.TYPE_VIDEO
511                                 && trackId != null
512                                 && mOnAspectRatioChangedListener != null) {
513                             List<TvTrackInfo> trackInfos =
514                                     mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
515                             if (trackInfos != null) {
516                                 for (TvTrackInfo trackInfo : trackInfos) {
517                                     if (trackInfo.getId().equals(trackId)) {
518                                         float videoAspectRatio;
519                                         int videoWidth = trackInfo.getVideoWidth();
520                                         int videoHeight = trackInfo.getVideoHeight();
521                                         if (videoWidth > 0 && videoHeight > 0) {
522                                             videoAspectRatio =
523                                                     trackInfo.getVideoPixelAspectRatio()
524                                                             * trackInfo.getVideoWidth()
525                                                             / trackInfo.getVideoHeight();
526                                         } else {
527                                             // Aspect ratio is unknown. Pass the message to
528                                             // listeners.
529                                             videoAspectRatio = 0;
530                                         }
531                                         if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
532                                         if (mAspectRatio != videoAspectRatio
533                                                 || videoAspectRatio == 0) {
534                                             mOnAspectRatioChangedListener.onAspectRatioChanged(
535                                                     videoAspectRatio);
536                                             mAspectRatio = videoAspectRatio;
537                                             return;
538                                         }
539                                     }
540                                 }
541                             }
542                         }
543                     }
544 
545                     @Override
546                     public void onContentBlocked(String inputId, TvContentRating rating) {
547                         if (mOnContentBlockedListener != null) {
548                             mOnContentBlockedListener.onContentBlocked(rating);
549                         }
550                     }
551                 });
552     }
553 
resumeToWatchedPositionIfNeeded()554     private void resumeToWatchedPositionIfNeeded() {
555         if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
556             mTvView.timeShiftSeekTo(
557                     getRealSeekPosition(mInitialSeekPositionMs, SEEK_POSITION_MARGIN_MS)
558                             + mStartPositionMs);
559             mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
560         }
561         if (mPauseOnPrepared) {
562             mTvView.timeShiftPause();
563             mPlaybackState = PlaybackState.STATE_PAUSED;
564             mPauseOnPrepared = false;
565         } else {
566             mTvView.timeShiftResume();
567             mPlaybackState = PlaybackState.STATE_PLAYING;
568         }
569         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
570     }
571 }
572