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