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