1 /* 2 * Copyright (C) 2015 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.tuner.tvinput; 18 19 import android.content.ContentResolver; 20 import android.content.ContentUris; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.media.MediaFormat; 24 import android.media.PlaybackParams; 25 import android.media.tv.TvContentRating; 26 import android.media.tv.TvContract; 27 import android.media.tv.TvInputManager; 28 import android.media.tv.TvTrackInfo; 29 import android.net.Uri; 30 import android.os.Environment; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.Message; 34 import android.os.SystemClock; 35 import android.support.annotation.AnyThread; 36 import android.support.annotation.MainThread; 37 import android.support.annotation.WorkerThread; 38 import android.text.Html; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.util.Pair; 42 import android.util.SparseArray; 43 import android.view.Surface; 44 import android.view.accessibility.CaptioningManager; 45 46 import com.google.android.exoplayer.audio.AudioCapabilities; 47 import com.google.android.exoplayer.ExoPlayer; 48 import com.android.tv.common.SoftPreconditions; 49 import com.android.tv.common.TvContentRatingCache; 50 import com.android.tv.customization.TvCustomizationManager; 51 import com.android.tv.customization.TvCustomizationManager.TRICKPLAY_MODE; 52 import com.android.tv.tuner.TunerPreferences; 53 import com.android.tv.tuner.TunerPreferences.TrickplaySetting; 54 import com.android.tv.tuner.data.Cea708Data; 55 import com.android.tv.tuner.data.PsipData.EitItem; 56 import com.android.tv.tuner.data.PsipData.TvTracksInterface; 57 import com.android.tv.tuner.data.TunerChannel; 58 import com.android.tv.tuner.data.nano.Channel; 59 import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; 60 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; 61 import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder; 62 import com.android.tv.tuner.exoplayer.buffer.BufferManager; 63 import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager; 64 import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; 65 import com.android.tv.tuner.exoplayer.MpegTsPlayer; 66 import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; 67 import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; 68 import com.android.tv.tuner.source.TsDataSource; 69 import com.android.tv.tuner.source.TsDataSourceManager; 70 import com.android.tv.tuner.util.StatusTextUtils; 71 import com.android.tv.tuner.util.SystemPropertiesProxy; 72 73 import java.io.File; 74 import java.util.ArrayList; 75 import java.util.Iterator; 76 import java.util.List; 77 import java.util.Objects; 78 import java.util.concurrent.Semaphore; 79 import java.util.concurrent.TimeUnit; 80 81 /** 82 * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs 83 * such as handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on. 84 */ 85 @WorkerThread 86 public class TunerSessionWorker implements PlaybackBufferListener, 87 MpegTsPlayer.VideoEventListener, MpegTsPlayer.Listener, EventDetector.EventListener, 88 ChannelDataManager.ProgramInfoListener, Handler.Callback { 89 private static final String TAG = "TunerSessionWorker"; 90 private static final boolean DEBUG = false; 91 private static final boolean ENABLE_PROFILER = true; 92 private static final String PLAY_FROM_CHANNEL = "channel"; 93 private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes"; 94 private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB 95 private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB 96 97 // Public messages 98 public static final int MSG_SELECT_TRACK = 1; 99 public static final int MSG_UPDATE_CAPTION_TRACK = 2; 100 public static final int MSG_SET_STREAM_VOLUME = 3; 101 public static final int MSG_TIMESHIFT_PAUSE = 4; 102 public static final int MSG_TIMESHIFT_RESUME = 5; 103 public static final int MSG_TIMESHIFT_SEEK_TO = 6; 104 public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7; 105 public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8; 106 public static final int MSG_UNBLOCKED_RATING = 9; 107 public static final int MSG_TUNER_PREFERENCES_CHANGED = 10; 108 109 // Private messages 110 private static final int MSG_TUNE = 1000; 111 private static final int MSG_RELEASE = 1001; 112 private static final int MSG_RETRY_PLAYBACK = 1002; 113 private static final int MSG_START_PLAYBACK = 1003; 114 private static final int MSG_UPDATE_PROGRAM = 1008; 115 private static final int MSG_SCHEDULE_OF_PROGRAMS = 1009; 116 private static final int MSG_UPDATE_CHANNEL_INFO = 1010; 117 private static final int MSG_TRICKPLAY_BY_SEEK = 1011; 118 private static final int MSG_SMOOTH_TRICKPLAY_MONITOR = 1012; 119 private static final int MSG_PARENTAL_CONTROLS = 1015; 120 private static final int MSG_RESCHEDULE_PROGRAMS = 1016; 121 private static final int MSG_BUFFER_START_TIME_CHANGED = 1017; 122 private static final int MSG_CHECK_SIGNAL = 1018; 123 private static final int MSG_DISCOVER_CAPTION_SERVICE_NUMBER = 1019; 124 private static final int MSG_RESET_PLAYBACK = 1020; 125 private static final int MSG_BUFFER_STATE_CHANGED = 1021; 126 private static final int MSG_PROGRAM_DATA_RESULT = 1022; 127 private static final int MSG_STOP_TUNE = 1023; 128 private static final int MSG_SET_SURFACE = 1024; 129 private static final int MSG_NOTIFY_AUDIO_TRACK_UPDATED = 1025; 130 131 private static final int TS_PACKET_SIZE = 188; 132 private static final int CHECK_NO_SIGNAL_INITIAL_DELAY_MS = 4000; 133 private static final int CHECK_NO_SIGNAL_PERIOD_MS = 500; 134 private static final int RECOVER_STOPPED_PLAYBACK_PERIOD_MS = 2500; 135 private static final int PARENTAL_CONTROLS_INTERVAL_MS = 5000; 136 private static final int RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS = 4000; 137 private static final int RESCHEDULE_PROGRAMS_INTERVAL_MS = 10000; 138 private static final int RESCHEDULE_PROGRAMS_TOLERANCE_MS = 2000; 139 // The following 3s is defined empirically. This should be larger than 2s considering video 140 // key frame interval in the TS stream. 141 private static final int PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS = 3000; 142 private static final int PLAYBACK_RETRY_DELAY_MS = 5000; 143 private static final int MAX_IMMEDIATE_RETRY_COUNT = 5; 144 private static final long INVALID_TIME = -1; 145 146 // Some examples of the track ids of the audio tracks, "a0", "a1", "a2". 147 // The number after prefix is being used for indicating a index of the given audio track. 148 private static final String AUDIO_TRACK_PREFIX = "a"; 149 150 // Some examples of the tracks id of the caption tracks, "s1", "s2", "s3". 151 // The number after prefix is being used for indicating a index of a caption service number 152 // of the given caption track. 153 private static final String SUBTITLE_TRACK_PREFIX = "s"; 154 private static final int TRACK_PREFIX_SIZE = 1; 155 private static final String VIDEO_TRACK_ID = "v"; 156 private static final long BUFFER_UNDERFLOW_BUFFER_MS = 5000; 157 158 // Actual interval would be divided by the speed. 159 private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500; 160 private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20; 161 private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250; 162 private static final int RELEASE_WAIT_INTERVAL_MS = 50; 163 private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14); 164 165 // Since release() is done asynchronously, synchronization between multiple TunerSessionWorker 166 // creation/release is required. 167 // This is used to guarantee that at most one active TunerSessionWorker exists at any give time. 168 private static Semaphore sActiveSessionSemaphore = new Semaphore(1); 169 170 private final Context mContext; 171 private final ChannelDataManager mChannelDataManager; 172 private final TsDataSourceManager mSourceManager; 173 private final int mMaxTrickplayBufferSizeMb; 174 private final File mTrickplayBufferDir; 175 private final @TRICKPLAY_MODE int mTrickplayModeCustomization; 176 private volatile Surface mSurface; 177 private volatile float mVolume = 1.0f; 178 private volatile boolean mCaptionEnabled; 179 private volatile MpegTsPlayer mPlayer; 180 private volatile TunerChannel mChannel; 181 private volatile Long mRecordingDuration; 182 private volatile long mRecordStartTimeMs; 183 private volatile long mBufferStartTimeMs; 184 private volatile boolean mTrickplayDisabledByStorageIssue; 185 private @TrickplaySetting int mTrickplaySetting; 186 private long mTrickplayExpiredMs; 187 private String mRecordingId; 188 private final Handler mHandler; 189 private int mRetryCount; 190 private final ArrayList<TvTrackInfo> mTvTracks; 191 private final SparseArray<AtscAudioTrack> mAudioTrackMap; 192 private final SparseArray<AtscCaptionTrack> mCaptionTrackMap; 193 private AtscCaptionTrack mCaptionTrack; 194 private PlaybackParams mPlaybackParams = new PlaybackParams(); 195 private boolean mPlayerStarted = false; 196 private boolean mReportedDrawnToSurface = false; 197 private boolean mReportedWeakSignal = false; 198 private EitItem mProgram; 199 private List<EitItem> mPrograms; 200 private final TvInputManager mTvInputManager; 201 private boolean mChannelBlocked; 202 private TvContentRating mUnblockedContentRating; 203 private long mLastPositionMs; 204 private AudioCapabilities mAudioCapabilities; 205 private long mLastLimitInBytes; 206 private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance(); 207 private final TunerSession mSession; 208 private final boolean mHasSoftwareAudioDecoder; 209 private int mPlayerState = ExoPlayer.STATE_IDLE; 210 private long mPreparingStartTimeMs; 211 private long mBufferingStartTimeMs; 212 private long mReadyStartTimeMs; 213 private boolean mIsActiveSession; 214 private boolean mReleaseRequested; // Guarded by mReleaseLock 215 private final Object mReleaseLock = new Object(); 216 TunerSessionWorker(Context context, ChannelDataManager channelDataManager, TunerSession tunerSession)217 public TunerSessionWorker(Context context, ChannelDataManager channelDataManager, 218 TunerSession tunerSession) { 219 if (DEBUG) Log.d(TAG, "TunerSessionWorker created"); 220 mContext = context; 221 222 // HandlerThread should be set up before it is registered as a listener in the all other 223 // components. 224 HandlerThread handlerThread = new HandlerThread(TAG); 225 handlerThread.start(); 226 mHandler = new Handler(handlerThread.getLooper(), this); 227 mSession = tunerSession; 228 mChannelDataManager = channelDataManager; 229 mChannelDataManager.setListener(this); 230 mChannelDataManager.checkDataVersion(mContext); 231 mSourceManager = TsDataSourceManager.createSourceManager(false); 232 mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); 233 mTvTracks = new ArrayList<>(); 234 mAudioTrackMap = new SparseArray<>(); 235 mCaptionTrackMap = new SparseArray<>(); 236 CaptioningManager captioningManager = 237 (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); 238 mCaptionEnabled = captioningManager.isEnabled(); 239 mPlaybackParams.setSpeed(1.0f); 240 mMaxTrickplayBufferSizeMb = 241 SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); 242 mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context); 243 if (mTrickplayModeCustomization == 244 TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { 245 boolean useExternalStorage = 246 Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && 247 Environment.isExternalStorageRemovable(); 248 mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null; 249 } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { 250 mTrickplayBufferDir = context.getCacheDir(); 251 } else { 252 mTrickplayBufferDir = null; 253 } 254 mTrickplayDisabledByStorageIssue = mTrickplayBufferDir == null; 255 mTrickplaySetting = TunerPreferences.getTrickplaySetting(context); 256 if (mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_NOT_SET 257 && mTrickplayModeCustomization 258 == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { 259 // Consider the case of Customization package updates the value of trickplay mode 260 // to TRICKPLAY_MODE_USE_EXTERNAL_STORAGE after install. 261 mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_NOT_SET; 262 TunerPreferences.setTrickplaySetting(context, mTrickplaySetting); 263 TunerPreferences.setTrickplayExpiredMs(context, 0); 264 } 265 mTrickplayExpiredMs = TunerPreferences.getTrickplayExpiredMs(context); 266 mPreparingStartTimeMs = INVALID_TIME; 267 mBufferingStartTimeMs = INVALID_TIME; 268 mReadyStartTimeMs = INVALID_TIME; 269 // NOTE: We assume that TunerSessionWorker instance will be at most one. 270 // Only one TunerSessionWorker can be connected to FfmpegDecoderClient at any given time. 271 // connect() will return false, if there is a connected TunerSessionWorker already. 272 mHasSoftwareAudioDecoder = FfmpegDecoderClient.connect(context); 273 } 274 275 // Public methods 276 @MainThread tune(Uri channelUri)277 public void tune(Uri channelUri) { 278 mHandler.removeCallbacksAndMessages(null); 279 mSourceManager.setHasPendingTune(); 280 sendMessage(MSG_TUNE, channelUri); 281 } 282 283 @MainThread stopTune()284 public void stopTune() { 285 mHandler.removeCallbacksAndMessages(null); 286 sendMessage(MSG_STOP_TUNE); 287 } 288 289 /** 290 * Sets {@link Surface}. 291 */ 292 @MainThread setSurface(Surface surface)293 public void setSurface(Surface surface) { 294 if (surface != null && !surface.isValid()) { 295 Log.w(TAG, "Ignoring invalid surface."); 296 return; 297 } 298 // mSurface is kept even when tune is called right after. But, messages can be deleted by 299 // tune or updateChannelBlockStatus. So mSurface should be stored here, not through message. 300 mSurface = surface; 301 mHandler.sendEmptyMessage(MSG_SET_SURFACE); 302 } 303 304 /** 305 * Sets volume. 306 */ 307 @MainThread setStreamVolume(float volume)308 public void setStreamVolume(float volume) { 309 // mVolume is kept even when tune is called right after. But, messages can be deleted by 310 // tune or updateChannelBlockStatus. So mVolume is stored here and mPlayer.setVolume will be 311 // called in MSG_SET_STREAM_VOLUME. 312 mVolume = volume; 313 mHandler.sendEmptyMessage(MSG_SET_STREAM_VOLUME); 314 } 315 316 /** 317 * Sets if caption is enabled or disabled. 318 */ 319 @MainThread setCaptionEnabled(boolean captionEnabled)320 public void setCaptionEnabled(boolean captionEnabled) { 321 // mCaptionEnabled is kept even when tune is called right after. But, messages can be 322 // deleted by tune or updateChannelBlockStatus. So mCaptionEnabled is stored here and 323 // start/stopCaptionTrack will be called in MSG_UPDATE_CAPTION_STATUS. 324 mCaptionEnabled = captionEnabled; 325 mHandler.sendEmptyMessage(MSG_UPDATE_CAPTION_TRACK); 326 } 327 getCurrentChannel()328 public TunerChannel getCurrentChannel() { 329 return mChannel; 330 } 331 332 @MainThread getStartPosition()333 public long getStartPosition() { 334 return mBufferStartTimeMs; 335 } 336 337 getRecordingPath()338 private String getRecordingPath() { 339 return Uri.parse(mRecordingId).getPath(); 340 } 341 getDurationForRecording(String recordingId)342 private Long getDurationForRecording(String recordingId) { 343 DvrStorageManager storageManager = 344 new DvrStorageManager(new File(getRecordingPath()), false); 345 List<BufferManager.TrackFormat> trackFormatList = 346 storageManager.readTrackInfoFiles(false); 347 if (trackFormatList.isEmpty()) { 348 trackFormatList = storageManager.readTrackInfoFiles(true); 349 } 350 if (!trackFormatList.isEmpty()) { 351 BufferManager.TrackFormat trackFormat = trackFormatList.get(0); 352 Long durationUs = trackFormat.format.getLong(MediaFormat.KEY_DURATION); 353 // we need duration by milli for trickplay notification. 354 return durationUs != null ? durationUs / 1000 : null; 355 } 356 Log.e(TAG, "meta file for recording was not found: " + recordingId); 357 return null; 358 } 359 360 @MainThread getCurrentPosition()361 public long getCurrentPosition() { 362 // TODO: More precise time may be necessary. 363 MpegTsPlayer mpegTsPlayer = mPlayer; 364 long currentTime = mpegTsPlayer != null 365 ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() : mRecordStartTimeMs; 366 if (mChannel == null && mPlayerState == ExoPlayer.STATE_ENDED) { 367 currentTime = mRecordingDuration + mRecordStartTimeMs; 368 } 369 if (DEBUG) { 370 long systemCurrentTime = System.currentTimeMillis(); 371 Log.d(TAG, "currentTime = " + currentTime 372 + " ; System.currentTimeMillis() = " + systemCurrentTime 373 + " ; diff = " + (currentTime - systemCurrentTime)); 374 } 375 return currentTime; 376 } 377 378 @AnyThread sendMessage(int messageType)379 public void sendMessage(int messageType) { 380 mHandler.sendEmptyMessage(messageType); 381 } 382 383 @AnyThread sendMessage(int messageType, Object object)384 public void sendMessage(int messageType, Object object) { 385 mHandler.obtainMessage(messageType, object).sendToTarget(); 386 } 387 388 @AnyThread sendMessage(int messageType, int arg1, int arg2, Object object)389 public void sendMessage(int messageType, int arg1, int arg2, Object object) { 390 mHandler.obtainMessage(messageType, arg1, arg2, object).sendToTarget(); 391 } 392 393 @MainThread release()394 public void release() { 395 if (DEBUG) Log.d(TAG, "release()"); 396 synchronized (mReleaseLock) { 397 mReleaseRequested = true; 398 } 399 if (mHasSoftwareAudioDecoder) { 400 FfmpegDecoderClient.disconnect(mContext); 401 } 402 mChannelDataManager.setListener(null); 403 mHandler.removeCallbacksAndMessages(null); 404 mHandler.sendEmptyMessage(MSG_RELEASE); 405 } 406 407 // MpegTsPlayer.Listener 408 // Called in the same thread as mHandler. 409 @Override onStateChanged(boolean playWhenReady, int playbackState)410 public void onStateChanged(boolean playWhenReady, int playbackState) { 411 if (DEBUG) Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady); 412 if (playbackState == mPlayerState) { 413 return; 414 } 415 mReadyStartTimeMs = INVALID_TIME; 416 mPreparingStartTimeMs = INVALID_TIME; 417 mBufferingStartTimeMs = INVALID_TIME; 418 if (playbackState == ExoPlayer.STATE_READY) { 419 if (DEBUG) Log.d(TAG, "ExoPlayer ready"); 420 if (!mPlayerStarted) { 421 sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer)); 422 } 423 mReadyStartTimeMs = SystemClock.elapsedRealtime(); 424 } else if (playbackState == ExoPlayer.STATE_PREPARING) { 425 mPreparingStartTimeMs = SystemClock.elapsedRealtime(); 426 } else if (playbackState == ExoPlayer.STATE_BUFFERING) { 427 mBufferingStartTimeMs = SystemClock.elapsedRealtime(); 428 } else if (playbackState == ExoPlayer.STATE_ENDED) { 429 // Final status 430 // notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards. 431 Log.i(TAG, "Player ended: end of stream"); 432 if (mChannel != null) { 433 sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); 434 } 435 } 436 mPlayerState = playbackState; 437 } 438 439 @Override onError(Exception e)440 public void onError(Exception e) { 441 if (TunerPreferences.getStoreTsStream(mContext)) { 442 // Crash intentionally to capture the error causing TS file. 443 Log.e(TAG, "Crash intentionally to capture the error causing TS file. " 444 + e.getMessage()); 445 SoftPreconditions.checkState(false); 446 } 447 // There maybe some errors that finally raise ExoPlaybackException and will be handled here. 448 // If we are playing live stream, retrying playback maybe helpful. But for recorded stream, 449 // retrying playback is not helpful. 450 if (mChannel != null) { 451 mHandler.obtainMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)) 452 .sendToTarget(); 453 } 454 } 455 456 @Override onVideoSizeChanged(int width, int height, float pixelWidthHeight)457 public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) { 458 if (mChannel != null && mChannel.hasVideo()) { 459 updateVideoTrack(width, height); 460 } 461 if (mRecordingId != null) { 462 updateVideoTrack(width, height); 463 } 464 } 465 466 @Override onDrawnToSurface(MpegTsPlayer player, Surface surface)467 public void onDrawnToSurface(MpegTsPlayer player, Surface surface) { 468 if (mSurface != null && mPlayerStarted) { 469 if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE"); 470 if (mRecordingId != null) { 471 // Workaround of b/33298048: set it to 1 instead of 0. 472 mBufferStartTimeMs = mRecordStartTimeMs = 1; 473 } else { 474 mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis(); 475 } 476 notifyVideoAvailable(); 477 mReportedDrawnToSurface = true; 478 479 // If surface is drawn successfully, it means that the playback was brought back 480 // to normal and therefore, the playback recovery status will be reset through 481 // setting a zero value to the retry count. 482 // TODO: Consider audio only channels for detecting playback status changes to 483 // be normal. 484 mRetryCount = 0; 485 if (mCaptionEnabled && mCaptionTrack != null) { 486 startCaptionTrack(); 487 } else { 488 stopCaptionTrack(); 489 } 490 mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED); 491 } 492 } 493 494 @Override onSmoothTrickplayForceStopped()495 public void onSmoothTrickplayForceStopped() { 496 if (mPlayer == null || !mHandler.hasMessages(MSG_SMOOTH_TRICKPLAY_MONITOR)) { 497 return; 498 } 499 mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); 500 doTrickplayBySeek((int) mPlayer.getCurrentPosition()); 501 } 502 503 @Override onAudioUnplayable()504 public void onAudioUnplayable() { 505 if (mPlayer == null) { 506 return; 507 } 508 Log.i(TAG, "AC3 audio cannot be played due to device limitation"); 509 mSession.sendUiMessage( 510 TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE); 511 } 512 513 // MpegTsPlayer.VideoEventListener 514 @Override onEmitCaptionEvent(Cea708Data.CaptionEvent event)515 public void onEmitCaptionEvent(Cea708Data.CaptionEvent event) { 516 mSession.sendUiMessage(TunerSession.MSG_UI_PROCESS_CAPTION_TRACK, event); 517 } 518 519 @Override onClearCaptionEvent()520 public void onClearCaptionEvent() { 521 mSession.sendUiMessage(TunerSession.MSG_UI_CLEAR_CAPTION_RENDERER); 522 } 523 524 @Override onDiscoverCaptionServiceNumber(int serviceNumber)525 public void onDiscoverCaptionServiceNumber(int serviceNumber) { 526 sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber); 527 } 528 529 // ChannelDataManager.ProgramInfoListener 530 @Override onProgramsArrived(TunerChannel channel, List<EitItem> programs)531 public void onProgramsArrived(TunerChannel channel, List<EitItem> programs) { 532 sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs)); 533 } 534 535 @Override onChannelArrived(TunerChannel channel)536 public void onChannelArrived(TunerChannel channel) { 537 sendMessage(MSG_UPDATE_CHANNEL_INFO, channel); 538 } 539 540 @Override onRescanNeeded()541 public void onRescanNeeded() { 542 mSession.sendUiMessage(TunerSession.MSG_UI_TOAST_RESCAN_NEEDED); 543 } 544 545 @Override onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs)546 public void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs) { 547 sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs)); 548 } 549 550 // PlaybackBufferListener 551 @Override onBufferStartTimeChanged(long startTimeMs)552 public void onBufferStartTimeChanged(long startTimeMs) { 553 sendMessage(MSG_BUFFER_START_TIME_CHANGED, startTimeMs); 554 } 555 556 @Override onBufferStateChanged(boolean available)557 public void onBufferStateChanged(boolean available) { 558 sendMessage(MSG_BUFFER_STATE_CHANGED, available); 559 } 560 561 @Override onDiskTooSlow()562 public void onDiskTooSlow() { 563 mTrickplayDisabledByStorageIssue = true; 564 sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); 565 } 566 567 // EventDetector.EventListener 568 @Override onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime)569 public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { 570 mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); 571 } 572 573 @Override onEventDetected(TunerChannel channel, List<EitItem> items)574 public void onEventDetected(TunerChannel channel, List<EitItem> items) { 575 mChannelDataManager.notifyEventDetected(channel, items); 576 } 577 578 @Override onChannelScanDone()579 public void onChannelScanDone() { 580 // do nothing. 581 } 582 parseChannel(Uri uri)583 private long parseChannel(Uri uri) { 584 try { 585 List<String> paths = uri.getPathSegments(); 586 if (paths.size() > 1 && paths.get(0).equals(PLAY_FROM_CHANNEL)) { 587 return ContentUris.parseId(uri); 588 } 589 } catch (UnsupportedOperationException | NumberFormatException e) { 590 } 591 return -1; 592 } 593 594 private static class RecordedProgram { 595 private final long mChannelId; 596 private final String mDataUri; 597 598 private static final String[] PROJECTION = { 599 TvContract.Programs.COLUMN_CHANNEL_ID, 600 TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI, 601 }; 602 RecordedProgram(Cursor cursor)603 public RecordedProgram(Cursor cursor) { 604 int index = 0; 605 mChannelId = cursor.getLong(index++); 606 mDataUri = cursor.getString(index++); 607 } 608 RecordedProgram(long channelId, String dataUri)609 public RecordedProgram(long channelId, String dataUri) { 610 mChannelId = channelId; 611 mDataUri = dataUri; 612 } 613 onQuery(Cursor c)614 public static RecordedProgram onQuery(Cursor c) { 615 RecordedProgram recording = null; 616 if (c != null && c.moveToNext()) { 617 recording = new RecordedProgram(c); 618 } 619 return recording; 620 } 621 getDataUri()622 public String getDataUri() { 623 return mDataUri; 624 } 625 } 626 getRecordedProgram(Uri recordedUri)627 private RecordedProgram getRecordedProgram(Uri recordedUri) { 628 ContentResolver resolver = mContext.getContentResolver(); 629 try(Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) { 630 if (c != null) { 631 RecordedProgram result = RecordedProgram.onQuery(c); 632 if (DEBUG) { 633 Log.d(TAG, "Finished query for " + this); 634 } 635 return result; 636 } else { 637 if (c == null) { 638 Log.e(TAG, "Unknown query error for " + this); 639 } else { 640 if (DEBUG) Log.d(TAG, "Canceled query for " + this); 641 } 642 return null; 643 } 644 } 645 } 646 parseRecording(Uri uri)647 private String parseRecording(Uri uri) { 648 RecordedProgram recording = getRecordedProgram(uri); 649 if (recording != null) { 650 return recording.getDataUri(); 651 } 652 return null; 653 } 654 655 @Override handleMessage(Message msg)656 public boolean handleMessage(Message msg) { 657 switch (msg.what) { 658 case MSG_TUNE: { 659 if (DEBUG) Log.d(TAG, "MSG_TUNE"); 660 661 // When sequential tuning messages arrived, it skips middle tuning messages in order 662 // to change to the last requested channel quickly. 663 if (mHandler.hasMessages(MSG_TUNE)) { 664 return true; 665 } 666 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); 667 if (!mIsActiveSession) { 668 // Wait until release is finished if there is a pending release. 669 try { 670 while (!sActiveSessionSemaphore.tryAcquire( 671 RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) { 672 synchronized (mReleaseLock) { 673 if (mReleaseRequested) { 674 return true; 675 } 676 } 677 } 678 } catch (InterruptedException e) { 679 Thread.currentThread().interrupt(); 680 } 681 synchronized (mReleaseLock) { 682 if (mReleaseRequested) { 683 sActiveSessionSemaphore.release(); 684 return true; 685 } 686 } 687 mIsActiveSession = true; 688 } 689 Uri channelUri = (Uri) msg.obj; 690 String recording = null; 691 long channelId = parseChannel(channelUri); 692 TunerChannel channel = (channelId == -1) ? null 693 : mChannelDataManager.getChannel(channelId); 694 if (channelId == -1) { 695 recording = parseRecording(channelUri); 696 } 697 if (channel == null && recording == null) { 698 Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri); 699 stopTune(); 700 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); 701 return true; 702 } 703 clearCallbacksAndMessagesSafely(); 704 mChannelDataManager.removeAllCallbacksAndMessages(); 705 if (channel != null) { 706 mChannelDataManager.requestProgramsData(channel); 707 } 708 prepareTune(channel, recording); 709 // TODO: Need to refactor. notifyContentAllowed() should not be called if parental 710 // control is turned on. 711 mSession.notifyContentAllowed(); 712 resetTvTracks(); 713 resetPlayback(); 714 mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, 715 RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); 716 return true; 717 } 718 case MSG_STOP_TUNE: { 719 if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE"); 720 mChannel = null; 721 stopPlayback(true); 722 stopCaptionTrack(); 723 resetTvTracks(); 724 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); 725 return true; 726 } 727 case MSG_RELEASE: { 728 if (DEBUG) Log.d(TAG, "MSG_RELEASE"); 729 mHandler.removeCallbacksAndMessages(null); 730 stopPlayback(true); 731 stopCaptionTrack(); 732 mSourceManager.release(); 733 mHandler.getLooper().quitSafely(); 734 if (mIsActiveSession) { 735 sActiveSessionSemaphore.release(); 736 } 737 return true; 738 } 739 case MSG_RETRY_PLAYBACK: { 740 if (System.identityHashCode(mPlayer) == (int) msg.obj) { 741 Log.i(TAG, "Retrying the playback for channel: " + mChannel); 742 mHandler.removeMessages(MSG_RETRY_PLAYBACK); 743 // When there is a request of retrying playback, don't reuse TunerHal. 744 mSourceManager.setKeepTuneStatus(false); 745 mRetryCount++; 746 if (DEBUG) { 747 Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount); 748 } 749 mChannelDataManager.removeAllCallbacksAndMessages(); 750 if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) { 751 resetPlayback(); 752 } else { 753 // When it reaches this point, it may be due to an error that occurred in 754 // the tuner device. Calling stopPlayback() resets the tuner device 755 // to recover from the error. 756 stopPlayback(false); 757 stopCaptionTrack(); 758 759 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); 760 Log.i(TAG, "Notify weak signal since fail to retry playback"); 761 762 // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen 763 // value before recovering the playback. 764 mHandler.sendEmptyMessageDelayed(MSG_RESET_PLAYBACK, 765 RECOVER_STOPPED_PLAYBACK_PERIOD_MS); 766 } 767 } 768 return true; 769 } 770 case MSG_RESET_PLAYBACK: { 771 if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK"); 772 mChannelDataManager.removeAllCallbacksAndMessages(); 773 resetPlayback(); 774 return true; 775 } 776 case MSG_START_PLAYBACK: { 777 if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK"); 778 if (mChannel != null || mRecordingId != null) { 779 startPlayback((int) msg.obj); 780 } 781 return true; 782 } 783 case MSG_UPDATE_PROGRAM: { 784 if (mChannel != null) { 785 EitItem program = (EitItem) msg.obj; 786 updateTvTracks(program, false); 787 mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); 788 } 789 return true; 790 } 791 case MSG_SCHEDULE_OF_PROGRAMS: { 792 mHandler.removeMessages(MSG_UPDATE_PROGRAM); 793 Pair<TunerChannel, List<EitItem>> pair = 794 (Pair<TunerChannel, List<EitItem>>) msg.obj; 795 TunerChannel channel = pair.first; 796 if (mChannel == null) { 797 return true; 798 } 799 if (mChannel != null && mChannel.compareTo(channel) != 0) { 800 return true; 801 } 802 mPrograms = pair.second; 803 EitItem currentProgram = getCurrentProgram(); 804 if (currentProgram == null) { 805 mProgram = null; 806 } 807 long currentTimeMs = getCurrentPosition(); 808 if (mPrograms != null) { 809 for (EitItem item : mPrograms) { 810 if (currentProgram != null && currentProgram.compareTo(item) == 0) { 811 if (DEBUG) { 812 Log.d(TAG, "Update current TvTracks " + item); 813 } 814 if (mProgram != null && mProgram.compareTo(item) == 0) { 815 continue; 816 } 817 mProgram = item; 818 updateTvTracks(item, false); 819 } else if (item.getStartTimeUtcMillis() > currentTimeMs) { 820 if (DEBUG) { 821 Log.d(TAG, "Update next TvTracks " + item + " " 822 + (item.getStartTimeUtcMillis() - currentTimeMs)); 823 } 824 mHandler.sendMessageDelayed( 825 mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item), 826 item.getStartTimeUtcMillis() - currentTimeMs); 827 } 828 } 829 } 830 mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); 831 return true; 832 } 833 case MSG_UPDATE_CHANNEL_INFO: { 834 TunerChannel channel = (TunerChannel) msg.obj; 835 if (mChannel != null && mChannel.compareTo(channel) == 0) { 836 updateChannelInfo(channel); 837 } 838 return true; 839 } 840 case MSG_PROGRAM_DATA_RESULT: { 841 TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first; 842 843 // If there already exists, skip it since real-time data is a top priority, 844 if (mChannel != null && mChannel.compareTo(channel) == 0 845 && mPrograms == null && mProgram == null) { 846 sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj); 847 } 848 return true; 849 } 850 case MSG_TRICKPLAY_BY_SEEK: { 851 if (mPlayer == null) { 852 return true; 853 } 854 doTrickplayBySeek(msg.arg1); 855 return true; 856 } 857 case MSG_SMOOTH_TRICKPLAY_MONITOR: { 858 if (mPlayer == null) { 859 return true; 860 } 861 long systemCurrentTime = System.currentTimeMillis(); 862 long position = getCurrentPosition(); 863 if (mRecordingId == null) { 864 // Checks if the position exceeds the upper bound when forwarding, 865 // or exceed the lower bound when rewinding. 866 // If the direction is not checked, there can be some issues. 867 // (See b/29939781 for more details.) 868 if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L) 869 || (position < mBufferStartTimeMs && mPlaybackParams.getSpeed() < 0L)) { 870 doTimeShiftResume(); 871 return true; 872 } 873 } else { 874 if (position > mRecordingDuration || position < 0) { 875 doTimeShiftPause(); 876 return true; 877 } 878 } 879 mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR, 880 TRICKPLAY_MONITOR_INTERVAL_MS); 881 return true; 882 } 883 case MSG_RESCHEDULE_PROGRAMS: { 884 if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) { 885 mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS); 886 } else { 887 doReschedulePrograms(); 888 } 889 return true; 890 } 891 case MSG_PARENTAL_CONTROLS: { 892 doParentalControls(); 893 mHandler.removeMessages(MSG_PARENTAL_CONTROLS); 894 mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, 895 PARENTAL_CONTROLS_INTERVAL_MS); 896 return true; 897 } 898 case MSG_UNBLOCKED_RATING: { 899 mUnblockedContentRating = (TvContentRating) msg.obj; 900 doParentalControls(); 901 mHandler.removeMessages(MSG_PARENTAL_CONTROLS); 902 mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, 903 PARENTAL_CONTROLS_INTERVAL_MS); 904 return true; 905 } 906 case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: { 907 int serviceNumber = (int) msg.obj; 908 doDiscoverCaptionServiceNumber(serviceNumber); 909 return true; 910 } 911 case MSG_SELECT_TRACK: { 912 if (mChannel != null || mRecordingId != null) { 913 doSelectTrack(msg.arg1, (String) msg.obj); 914 } 915 return true; 916 } 917 case MSG_UPDATE_CAPTION_TRACK: { 918 if (mCaptionEnabled) { 919 startCaptionTrack(); 920 } else { 921 stopCaptionTrack(); 922 } 923 return true; 924 } 925 case MSG_TIMESHIFT_PAUSE: { 926 if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE"); 927 if (mPlayer == null) { 928 return true; 929 } 930 setTrickplayEnabledIfNeeded(); 931 doTimeShiftPause(); 932 return true; 933 } 934 case MSG_TIMESHIFT_RESUME: { 935 if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME"); 936 if (mPlayer == null) { 937 return true; 938 } 939 setTrickplayEnabledIfNeeded(); 940 doTimeShiftResume(); 941 return true; 942 } 943 case MSG_TIMESHIFT_SEEK_TO: { 944 long position = (long) msg.obj; 945 if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")"); 946 if (mPlayer == null) { 947 return true; 948 } 949 setTrickplayEnabledIfNeeded(); 950 doTimeShiftSeekTo(position); 951 return true; 952 } 953 case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: { 954 if (mPlayer == null) { 955 return true; 956 } 957 setTrickplayEnabledIfNeeded(); 958 doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj); 959 return true; 960 } 961 case MSG_AUDIO_CAPABILITIES_CHANGED: { 962 AudioCapabilities capabilities = (AudioCapabilities) msg.obj; 963 if (DEBUG) { 964 Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities); 965 } 966 if (capabilities == null) { 967 return true; 968 } 969 if (!capabilities.equals(mAudioCapabilities)) { 970 // HDMI supported encodings are changed. restart player. 971 mAudioCapabilities = capabilities; 972 resetPlayback(); 973 } 974 return true; 975 } 976 case MSG_SET_STREAM_VOLUME: { 977 if (mPlayer != null && mPlayer.isPlaying()) { 978 mPlayer.setVolume(mVolume); 979 } 980 return true; 981 } 982 case MSG_TUNER_PREFERENCES_CHANGED: { 983 mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED); 984 @TrickplaySetting int trickplaySetting = 985 TunerPreferences.getTrickplaySetting(mContext); 986 if (trickplaySetting != mTrickplaySetting) { 987 boolean wasTrcikplayEnabled = 988 mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; 989 boolean isTrickplayEnabled = 990 trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; 991 mTrickplaySetting = trickplaySetting; 992 if (isTrickplayEnabled != wasTrcikplayEnabled) { 993 sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer)); 994 } 995 } 996 return true; 997 } 998 case MSG_BUFFER_START_TIME_CHANGED: { 999 if (mPlayer == null) { 1000 return true; 1001 } 1002 mBufferStartTimeMs = (long) msg.obj; 1003 if (!hasEnoughBackwardBuffer() 1004 && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) { 1005 mPlayer.setPlayWhenReady(true); 1006 mPlayer.setAudioTrackAndClosedCaption(true); 1007 mPlaybackParams.setSpeed(1.0f); 1008 } 1009 return true; 1010 } 1011 case MSG_BUFFER_STATE_CHANGED: { 1012 boolean available = (boolean) msg.obj; 1013 mSession.notifyTimeShiftStatusChanged(available 1014 ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE 1015 : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); 1016 return true; 1017 } 1018 case MSG_CHECK_SIGNAL: { 1019 if (mChannel == null || mPlayer == null) { 1020 return true; 1021 } 1022 TsDataSource source = mPlayer.getDataSource(); 1023 long limitInBytes = source != null ? source.getBufferedPosition() : 0L; 1024 if (TunerDebug.ENABLED) { 1025 TunerDebug.calculateDiff(); 1026 mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT, 1027 Html.fromHtml( 1028 StatusTextUtils.getStatusWarningInHTML( 1029 (limitInBytes - mLastLimitInBytes) 1030 / TS_PACKET_SIZE, 1031 TunerDebug.getVideoFrameDrop(), 1032 TunerDebug.getBytesInQueue(), 1033 TunerDebug.getAudioPositionUs(), 1034 TunerDebug.getAudioPositionUsRate(), 1035 TunerDebug.getAudioPtsUs(), 1036 TunerDebug.getAudioPtsUsRate(), 1037 TunerDebug.getVideoPtsUs(), 1038 TunerDebug.getVideoPtsUsRate() 1039 ))); 1040 } 1041 mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); 1042 long currentTime = SystemClock.elapsedRealtime(); 1043 long bufferingTimeMs = mBufferingStartTimeMs != INVALID_TIME 1044 ? currentTime - mBufferingStartTimeMs : mBufferingStartTimeMs; 1045 long preparingTimeMs = mPreparingStartTimeMs != INVALID_TIME 1046 ? currentTime - mPreparingStartTimeMs : mPreparingStartTimeMs; 1047 boolean isBufferingTooLong = 1048 bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; 1049 boolean isPreparingTooLong = 1050 preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; 1051 boolean isWeakSignal = source != null 1052 && mChannel.getType() != Channel.TYPE_FILE 1053 && (isBufferingTooLong || isPreparingTooLong); 1054 if (isWeakSignal && !mReportedWeakSignal) { 1055 if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) { 1056 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, 1057 System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); 1058 } 1059 if (mPlayer != null) { 1060 mPlayer.setAudioTrackAndClosedCaption(false); 1061 } 1062 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); 1063 Log.i(TAG, "Notify weak signal due to signal check, " + String.format( 1064 "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " + 1065 "videoFrameDrop:%d", 1066 (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, 1067 bufferingTimeMs, 1068 preparingTimeMs, 1069 TunerDebug.getVideoFrameDrop() 1070 )); 1071 } else if (!isWeakSignal && mReportedWeakSignal) { 1072 boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME 1073 && currentTime - mReadyStartTimeMs 1074 > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; 1075 if (!isPlaybackStable) { 1076 // Wait until playback becomes stable. 1077 } else if (mReportedDrawnToSurface) { 1078 mHandler.removeMessages(MSG_RETRY_PLAYBACK); 1079 notifyVideoAvailable(); 1080 mPlayer.setAudioTrackAndClosedCaption(true); 1081 } 1082 } 1083 mLastLimitInBytes = limitInBytes; 1084 mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS); 1085 return true; 1086 } 1087 case MSG_SET_SURFACE: { 1088 if (mPlayer != null) { 1089 mPlayer.setSurface(mSurface); 1090 } else { 1091 // TODO: Since surface is dynamically set, we can remove the dependency of 1092 // playback start on mSurface nullity. 1093 resetPlayback(); 1094 } 1095 return true; 1096 } 1097 case MSG_NOTIFY_AUDIO_TRACK_UPDATED: { 1098 notifyAudioTracksUpdated(); 1099 return true; 1100 } 1101 default: { 1102 Log.w(TAG, "Unhandled message code: " + msg.what); 1103 return false; 1104 } 1105 } 1106 } 1107 1108 // Private methods doSelectTrack(int type, String trackId)1109 private void doSelectTrack(int type, String trackId) { 1110 int numTrackId = trackId != null 1111 ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1; 1112 if (type == TvTrackInfo.TYPE_AUDIO) { 1113 if (trackId == null) { 1114 return; 1115 } 1116 if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) { 1117 mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId); 1118 } 1119 mSession.notifyTrackSelected(type, trackId); 1120 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1121 if (trackId == null) { 1122 mSession.notifyTrackSelected(type, null); 1123 mCaptionTrack = null; 1124 stopCaptionTrack(); 1125 return; 1126 } 1127 for (TvTrackInfo track : mTvTracks) { 1128 if (track.getId().equals(trackId)) { 1129 // The service number of the caption service is used for track id of a 1130 // subtitle track. Passes the following track id on to TsParser. 1131 mSession.notifyTrackSelected(type, trackId); 1132 mCaptionTrack = mCaptionTrackMap.get(numTrackId); 1133 startCaptionTrack(); 1134 return; 1135 } 1136 } 1137 } 1138 } 1139 setTrickplayEnabledIfNeeded()1140 private void setTrickplayEnabledIfNeeded() { 1141 if (mChannel == null || 1142 mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { 1143 return; 1144 } 1145 if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { 1146 mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED; 1147 TunerPreferences.setTrickplaySetting( 1148 mContext, mTrickplaySetting); 1149 } 1150 } 1151 createPlayer(AudioCapabilities capabilities)1152 private MpegTsPlayer createPlayer(AudioCapabilities capabilities) { 1153 if (capabilities == null) { 1154 Log.w(TAG, "No Audio Capabilities"); 1155 } 1156 long now = System.currentTimeMillis(); 1157 if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED 1158 && mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { 1159 if (mTrickplayExpiredMs == 0) { 1160 mTrickplayExpiredMs = now + TRICKPLAY_OFF_DURATION_MS; 1161 TunerPreferences.setTrickplayExpiredMs(mContext, mTrickplayExpiredMs); 1162 } else { 1163 if (mTrickplayExpiredMs < now) { 1164 mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_DISABLED; 1165 TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting); 1166 } 1167 } 1168 } 1169 BufferManager bufferManager = null; 1170 if (mRecordingId != null) { 1171 StorageManager storageManager = 1172 new DvrStorageManager(new File(getRecordingPath()), false); 1173 bufferManager = new BufferManager(storageManager); 1174 updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles()); 1175 } else if (!mTrickplayDisabledByStorageIssue 1176 && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED 1177 && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) { 1178 bufferManager = new BufferManager(new TrickplayStorageManager(mContext, 1179 mTrickplayBufferDir, 1024L * 1024 * mMaxTrickplayBufferSizeMb)); 1180 } else { 1181 Log.w(TAG, "Trickplay is disabled."); 1182 } 1183 MpegTsPlayer player = new MpegTsPlayer( 1184 new MpegTsRendererBuilder(mContext, bufferManager, this), 1185 mHandler, mSourceManager, capabilities, this); 1186 Log.i(TAG, "Passthrough AC3 renderer"); 1187 if (DEBUG) Log.d(TAG, "ExoPlayer created"); 1188 return player; 1189 } 1190 startCaptionTrack()1191 private void startCaptionTrack() { 1192 if (mCaptionEnabled && mCaptionTrack != null) { 1193 mSession.sendUiMessage( 1194 TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack); 1195 if (mPlayer != null) { 1196 mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber); 1197 } 1198 } 1199 } 1200 stopCaptionTrack()1201 private void stopCaptionTrack() { 1202 if (mPlayer != null) { 1203 mPlayer.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); 1204 } 1205 mSession.sendUiMessage(TunerSession.MSG_UI_STOP_CAPTION_TRACK); 1206 } 1207 resetTvTracks()1208 private void resetTvTracks() { 1209 mTvTracks.clear(); 1210 mAudioTrackMap.clear(); 1211 mCaptionTrackMap.clear(); 1212 mSession.sendUiMessage(TunerSession.MSG_UI_RESET_CAPTION_TRACK); 1213 mSession.notifyTracksChanged(mTvTracks); 1214 } 1215 updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt)1216 private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) { 1217 synchronized (tvTracksInterface) { 1218 if (DEBUG) { 1219 Log.d(TAG, "UpdateTvTracks " + tvTracksInterface); 1220 } 1221 List<AtscAudioTrack> audioTracks = tvTracksInterface.getAudioTracks(); 1222 List<AtscCaptionTrack> captionTracks = tvTracksInterface.getCaptionTracks(); 1223 // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio 1224 // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio 1225 // track info in PMT more and use info in EIT only when we have nothing. 1226 if (audioTracks != null && !audioTracks.isEmpty() 1227 && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) { 1228 updateAudioTracks(audioTracks); 1229 } 1230 if (captionTracks == null || captionTracks.isEmpty()) { 1231 if (tvTracksInterface.hasCaptionTrack()) { 1232 updateCaptionTracks(captionTracks); 1233 } 1234 } else { 1235 updateCaptionTracks(captionTracks); 1236 } 1237 } 1238 } 1239 removeTvTracks(int trackType)1240 private void removeTvTracks(int trackType) { 1241 Iterator<TvTrackInfo> iterator = mTvTracks.iterator(); 1242 while (iterator.hasNext()) { 1243 TvTrackInfo tvTrackInfo = iterator.next(); 1244 if (tvTrackInfo.getType() == trackType) { 1245 iterator.remove(); 1246 } 1247 } 1248 } 1249 updateVideoTrack(int width, int height)1250 private void updateVideoTrack(int width, int height) { 1251 removeTvTracks(TvTrackInfo.TYPE_VIDEO); 1252 mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID) 1253 .setVideoWidth(width).setVideoHeight(height).build()); 1254 mSession.notifyTracksChanged(mTvTracks); 1255 mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID); 1256 } 1257 updateAudioTracks(List<AtscAudioTrack> audioTracks)1258 private void updateAudioTracks(List<AtscAudioTrack> audioTracks) { 1259 if (DEBUG) { 1260 Log.d(TAG, "Update AudioTracks " + audioTracks); 1261 } 1262 mAudioTrackMap.clear(); 1263 if (audioTracks != null) { 1264 int index = 0; 1265 for (AtscAudioTrack audioTrack : audioTracks) { 1266 audioTrack.index = index; 1267 mAudioTrackMap.put(index, audioTrack); 1268 ++index; 1269 } 1270 } 1271 mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED); 1272 } 1273 notifyAudioTracksUpdated()1274 private void notifyAudioTracksUpdated() { 1275 if (mPlayer == null) { 1276 // Audio tracks will be updated later once player initialization is done. 1277 return; 1278 } 1279 int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO); 1280 removeTvTracks(TvTrackInfo.TYPE_AUDIO); 1281 for (int i = 0; i < audioTrackCount; i++) { 1282 // We use language information from EIT/VCT only when the player does not provide 1283 // languages. 1284 com.google.android.exoplayer.MediaFormat infoFromPlayer = 1285 mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i); 1286 AtscAudioTrack infoFromEit = mAudioTrackMap.get(i); 1287 AtscAudioTrack infoFromVct = (mChannel != null 1288 && mChannel.getAudioTracks().size() == mAudioTrackMap.size() 1289 && i < mChannel.getAudioTracks().size()) 1290 ? mChannel.getAudioTracks().get(i) : null; 1291 String language = !TextUtils.isEmpty(infoFromPlayer.language) ? infoFromPlayer.language 1292 : (infoFromEit != null && infoFromEit.language != null) ? infoFromEit.language 1293 : (infoFromVct != null && infoFromVct.language != null) 1294 ? infoFromVct.language : null; 1295 TvTrackInfo.Builder builder = new TvTrackInfo.Builder( 1296 TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i); 1297 builder.setLanguage(language); 1298 builder.setAudioChannelCount(infoFromPlayer.channelCount); 1299 builder.setAudioSampleRate(infoFromPlayer.sampleRate); 1300 TvTrackInfo track = builder.build(); 1301 mTvTracks.add(track); 1302 } 1303 mSession.notifyTracksChanged(mTvTracks); 1304 } 1305 updateCaptionTracks(List<AtscCaptionTrack> captionTracks)1306 private void updateCaptionTracks(List<AtscCaptionTrack> captionTracks) { 1307 if (DEBUG) { 1308 Log.d(TAG, "Update CaptionTrack " + captionTracks); 1309 } 1310 removeTvTracks(TvTrackInfo.TYPE_SUBTITLE); 1311 mCaptionTrackMap.clear(); 1312 if (captionTracks != null) { 1313 for (AtscCaptionTrack captionTrack : captionTracks) { 1314 if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) { 1315 continue; 1316 } 1317 String language = captionTrack.language; 1318 1319 // The service number of the caption service is used for track id of a subtitle. 1320 // Later, when a subtitle is chosen, track id will be passed on to TsParser. 1321 TvTrackInfo.Builder builder = 1322 new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, 1323 SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber); 1324 builder.setLanguage(language); 1325 mTvTracks.add(builder.build()); 1326 mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack); 1327 } 1328 } 1329 mSession.notifyTracksChanged(mTvTracks); 1330 } 1331 updateChannelInfo(TunerChannel channel)1332 private void updateChannelInfo(TunerChannel channel) { 1333 if (DEBUG) { 1334 Log.d(TAG, String.format("Channel Info (old) videoPid: %d audioPid: %d " + 1335 "audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(), 1336 mChannel.getAudioPids().size())); 1337 } 1338 1339 // The list of the audio tracks resided in a channel is often changed depending on a 1340 // program being on the air. So, we should update the streaming PIDs and types of the 1341 // tuned channel according to the newly received channel data. 1342 int oldVideoPid = mChannel.getVideoPid(); 1343 int oldAudioPid = mChannel.getAudioPid(); 1344 List<Integer> audioPids = channel.getAudioPids(); 1345 List<Integer> audioStreamTypes = channel.getAudioStreamTypes(); 1346 int size = audioPids.size(); 1347 mChannel.setVideoPid(channel.getVideoPid()); 1348 mChannel.setAudioPids(audioPids); 1349 mChannel.setAudioStreamTypes(audioStreamTypes); 1350 updateTvTracks(channel, true); 1351 int index = audioPids.isEmpty() ? -1 : 0; 1352 for (int i = 0; i < size; ++i) { 1353 if (audioPids.get(i) == oldAudioPid) { 1354 index = i; 1355 break; 1356 } 1357 } 1358 mChannel.selectAudioTrack(index); 1359 mSession.notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, 1360 index == -1 ? null : AUDIO_TRACK_PREFIX + index); 1361 1362 // Reset playback if there is a change in the listening streaming PIDs. 1363 if (oldVideoPid != mChannel.getVideoPid() 1364 || oldAudioPid != mChannel.getAudioPid()) { 1365 // TODO: Implement a switching between tracks more smoothly. 1366 resetPlayback(); 1367 } 1368 if (DEBUG) { 1369 Log.d(TAG, String.format("Channel Info (new) videoPid: %d audioPid: %d " + 1370 " audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(), 1371 mChannel.getAudioPids().size())); 1372 } 1373 } 1374 stopPlayback(boolean removeChannelDataCallbacks)1375 private void stopPlayback(boolean removeChannelDataCallbacks) { 1376 if (removeChannelDataCallbacks) { 1377 mChannelDataManager.removeAllCallbacksAndMessages(); 1378 } 1379 if (mPlayer != null) { 1380 mPlayer.setPlayWhenReady(false); 1381 mPlayer.release(); 1382 mPlayer = null; 1383 mPlayerState = ExoPlayer.STATE_IDLE; 1384 mPlaybackParams.setSpeed(1.0f); 1385 mPlayerStarted = false; 1386 mReportedDrawnToSurface = false; 1387 mPreparingStartTimeMs = INVALID_TIME; 1388 mBufferingStartTimeMs = INVALID_TIME; 1389 mReadyStartTimeMs = INVALID_TIME; 1390 mLastLimitInBytes = 0L; 1391 mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE); 1392 mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); 1393 } 1394 } 1395 startPlayback(int playerHashCode)1396 private void startPlayback(int playerHashCode) { 1397 // TODO: provide hasAudio()/hasVideo() for play recordings. 1398 if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) { 1399 return; 1400 } 1401 if (mChannel != null && !mChannel.hasAudio()) { 1402 if (DEBUG) Log.d(TAG, "Channel " + mChannel + " does not have audio."); 1403 // Playbacks with video-only stream have not been tested yet. 1404 // No video-only channel has been found. 1405 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); 1406 return; 1407 } 1408 if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio()) 1409 || (mChannel.hasVideo() && !mPlayer.hasVideo())) 1410 && mChannel.getType() != Channel.TYPE_NETWORK) { 1411 // If the channel is from network, skip this part since the video and audio tracks 1412 // information for channels from network are more reliable in the extractor. Otherwise, 1413 // tracks haven't been detected in the extractor. Try again. 1414 sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); 1415 return; 1416 } 1417 // Since mSurface is volatile, we define a local variable surface to keep the same value 1418 // inside this method. 1419 Surface surface = mSurface; 1420 if (surface != null && !mPlayerStarted) { 1421 mPlayer.setSurface(surface); 1422 mPlayer.setPlayWhenReady(true); 1423 mPlayer.setVolume(mVolume); 1424 if (mChannel != null && mPlayer.hasAudio() && !mPlayer.hasVideo()) { 1425 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY); 1426 } else if (!mReportedWeakSignal) { 1427 // Doesn't show buffering during weak signal. 1428 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING); 1429 } 1430 mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); 1431 mPlayerStarted = true; 1432 } 1433 } 1434 preparePlayback()1435 private void preparePlayback() { 1436 SoftPreconditions.checkState(mPlayer == null); 1437 if (mChannel == null && mRecordingId == null) { 1438 return; 1439 } 1440 mSourceManager.setKeepTuneStatus(true); 1441 MpegTsPlayer player = createPlayer(mAudioCapabilities); 1442 player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); 1443 player.setVideoEventListener(this); 1444 player.setCaptionServiceNumber(mCaptionTrack != null ? 1445 mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER); 1446 if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) { 1447 mSourceManager.setKeepTuneStatus(false); 1448 player.release(); 1449 if (!mHandler.hasMessages(MSG_TUNE)) { 1450 // When prepare failed, there may be some errors related to hardware. In that 1451 // case, retry playback immediately may not help. 1452 notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); 1453 Log.i(TAG, "Notify weak signal due to player preparation failure"); 1454 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, 1455 System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); 1456 } 1457 } else { 1458 mPlayer = player; 1459 mPlayerStarted = false; 1460 mHandler.removeMessages(MSG_CHECK_SIGNAL); 1461 mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS); 1462 } 1463 } 1464 resetPlayback()1465 private void resetPlayback() { 1466 long timestamp, oldTimestamp; 1467 timestamp = SystemClock.elapsedRealtime(); 1468 stopPlayback(false); 1469 stopCaptionTrack(); 1470 if (ENABLE_PROFILER) { 1471 oldTimestamp = timestamp; 1472 timestamp = SystemClock.elapsedRealtime(); 1473 Log.i(TAG, "[Profiler] stopPlayback() takes " + (timestamp - oldTimestamp) + " ms"); 1474 } 1475 if (mChannelBlocked || mSurface == null) { 1476 return; 1477 } 1478 preparePlayback(); 1479 } 1480 prepareTune(TunerChannel channel, String recording)1481 private void prepareTune(TunerChannel channel, String recording) { 1482 mChannelBlocked = false; 1483 mUnblockedContentRating = null; 1484 mRetryCount = 0; 1485 mChannel = channel; 1486 mRecordingId = recording; 1487 mRecordingDuration = recording != null ? getDurationForRecording(recording) : null; 1488 mProgram = null; 1489 mPrograms = null; 1490 if (mRecordingId != null) { 1491 // Workaround of b/33298048: set it to 1 instead of 0. 1492 mBufferStartTimeMs = mRecordStartTimeMs = 1; 1493 } else { 1494 mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis(); 1495 } 1496 mLastPositionMs = 0; 1497 mCaptionTrack = null; 1498 mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); 1499 } 1500 doReschedulePrograms()1501 private void doReschedulePrograms() { 1502 long currentPositionMs = getCurrentPosition(); 1503 long forwardDifference = Math.abs(currentPositionMs - mLastPositionMs 1504 - RESCHEDULE_PROGRAMS_INTERVAL_MS); 1505 mLastPositionMs = currentPositionMs; 1506 1507 // A gap is measured as the time difference between previous and next current position 1508 // periodically. If the gap has a significant difference with an interval of a period, 1509 // this means that there is a change of playback status and the programs of the current 1510 // channel should be rescheduled to new playback timeline. 1511 if (forwardDifference > RESCHEDULE_PROGRAMS_TOLERANCE_MS) { 1512 if (DEBUG) { 1513 Log.d(TAG, "reschedule programs size:" 1514 + (mPrograms != null ? mPrograms.size() : 0) + " current program: " 1515 + getCurrentProgram()); 1516 } 1517 mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms)) 1518 .sendToTarget(); 1519 } 1520 mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS); 1521 mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, 1522 RESCHEDULE_PROGRAMS_INTERVAL_MS); 1523 } 1524 getTrickPlaySeekIntervalMs()1525 private int getTrickPlaySeekIntervalMs() { 1526 return Math.max(EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()), 1527 MIN_TRICKPLAY_SEEK_INTERVAL_MS); 1528 } 1529 doTrickplayBySeek(int seekPositionMs)1530 private void doTrickplayBySeek(int seekPositionMs) { 1531 mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); 1532 if (mPlaybackParams.getSpeed() == 1.0f || !mPlayer.isPrepared()) { 1533 return; 1534 } 1535 if (seekPositionMs < mBufferStartTimeMs - mRecordStartTimeMs) { 1536 if (mPlaybackParams.getSpeed() > 1.0f) { 1537 // If fast forwarding, the seekPositionMs can be out of the buffered range 1538 // because of chuck evictions. 1539 seekPositionMs = (int) (mBufferStartTimeMs - mRecordStartTimeMs); 1540 } else { 1541 mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs); 1542 mPlaybackParams.setSpeed(1.0f); 1543 mPlayer.setAudioTrackAndClosedCaption(true); 1544 return; 1545 } 1546 } else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) { 1547 // Stops trickplay when FF requested the position later than current position. 1548 // If RW trickplay requested the position later than current position, 1549 // continue trickplay. 1550 if (mPlaybackParams.getSpeed() > 0.0f) { 1551 mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs); 1552 mPlaybackParams.setSpeed(1.0f); 1553 mPlayer.setAudioTrackAndClosedCaption(true); 1554 return; 1555 } 1556 } 1557 1558 long delayForNextSeek = getTrickPlaySeekIntervalMs(); 1559 if (!mPlayer.isBuffering()) { 1560 mPlayer.seekTo(seekPositionMs); 1561 } else { 1562 delayForNextSeek = MIN_TRICKPLAY_SEEK_INTERVAL_MS; 1563 } 1564 seekPositionMs += mPlaybackParams.getSpeed() * delayForNextSeek; 1565 mHandler.sendMessageDelayed(mHandler.obtainMessage( 1566 MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek); 1567 } 1568 doTimeShiftPause()1569 private void doTimeShiftPause() { 1570 mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); 1571 mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); 1572 if (!hasEnoughBackwardBuffer()) { 1573 return; 1574 } 1575 mPlaybackParams.setSpeed(1.0f); 1576 mPlayer.setPlayWhenReady(false); 1577 mPlayer.setAudioTrackAndClosedCaption(true); 1578 } 1579 doTimeShiftResume()1580 private void doTimeShiftResume() { 1581 mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); 1582 mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); 1583 mPlaybackParams.setSpeed(1.0f); 1584 mPlayer.setPlayWhenReady(true); 1585 mPlayer.setAudioTrackAndClosedCaption(true); 1586 } 1587 doTimeShiftSeekTo(long timeMs)1588 private void doTimeShiftSeekTo(long timeMs) { 1589 mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); 1590 mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); 1591 mPlayer.seekTo((int) (timeMs - mRecordStartTimeMs)); 1592 } 1593 doTimeShiftSetPlaybackParams(PlaybackParams params)1594 private void doTimeShiftSetPlaybackParams(PlaybackParams params) { 1595 if (!hasEnoughBackwardBuffer() && params.getSpeed() < 1.0f) { 1596 return; 1597 } 1598 mPlaybackParams = params; 1599 float speed = mPlaybackParams.getSpeed(); 1600 if (speed == 1.0f) { 1601 mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); 1602 mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); 1603 doTimeShiftResume(); 1604 } else if (mPlayer.supportSmoothTrickPlay(speed)) { 1605 mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); 1606 mPlayer.setAudioTrackAndClosedCaption(false); 1607 mPlayer.startSmoothTrickplay(mPlaybackParams); 1608 mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR, 1609 TRICKPLAY_MONITOR_INTERVAL_MS); 1610 } else { 1611 mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); 1612 if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) { 1613 mPlayer.setAudioTrackAndClosedCaption(false); 1614 mPlayer.setPlayWhenReady(false); 1615 // Initiate trickplay 1616 mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK, 1617 (int) (mPlayer.getCurrentPosition() 1618 + speed * getTrickPlaySeekIntervalMs()), 0)); 1619 } 1620 } 1621 } 1622 getCurrentProgram()1623 private EitItem getCurrentProgram() { 1624 if (mPrograms == null || mPrograms.isEmpty()) { 1625 return null; 1626 } 1627 if (mChannel.getType() == Channel.TYPE_FILE) { 1628 // For the playback from the local file, we use the first one from the given program. 1629 EitItem first = mPrograms.get(0); 1630 if (first != null && (mProgram == null 1631 || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) { 1632 return first; 1633 } 1634 return null; 1635 } 1636 long currentTimeMs = getCurrentPosition(); 1637 for (EitItem item : mPrograms) { 1638 if (item.getStartTimeUtcMillis() <= currentTimeMs 1639 && item.getEndTimeUtcMillis() >= currentTimeMs) { 1640 return item; 1641 } 1642 } 1643 return null; 1644 } 1645 doParentalControls()1646 private void doParentalControls() { 1647 boolean isParentalControlsEnabled = mTvInputManager.isParentalControlsEnabled(); 1648 if (isParentalControlsEnabled) { 1649 TvContentRating blockContentRating = getContentRatingOfCurrentProgramBlocked(); 1650 if (DEBUG) { 1651 if (blockContentRating != null) { 1652 Log.d(TAG, "Check parental controls: blocked by content rating - " 1653 + blockContentRating); 1654 } else { 1655 Log.d(TAG, "Check parental controls: available"); 1656 } 1657 } 1658 updateChannelBlockStatus(blockContentRating != null, blockContentRating); 1659 } else { 1660 if (DEBUG) { 1661 Log.d(TAG, "Check parental controls: available"); 1662 } 1663 updateChannelBlockStatus(false, null); 1664 } 1665 } 1666 doDiscoverCaptionServiceNumber(int serviceNumber)1667 private void doDiscoverCaptionServiceNumber(int serviceNumber) { 1668 int index = mCaptionTrackMap.indexOfKey(serviceNumber); 1669 if (index < 0) { 1670 AtscCaptionTrack captionTrack = new AtscCaptionTrack(); 1671 captionTrack.serviceNumber = serviceNumber; 1672 captionTrack.wideAspectRatio = false; 1673 captionTrack.easyReader = false; 1674 mCaptionTrackMap.put(serviceNumber, captionTrack); 1675 mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, 1676 SUBTITLE_TRACK_PREFIX + serviceNumber).build()); 1677 mSession.notifyTracksChanged(mTvTracks); 1678 } 1679 } 1680 getContentRatingOfCurrentProgramBlocked()1681 private TvContentRating getContentRatingOfCurrentProgramBlocked() { 1682 EitItem currentProgram = getCurrentProgram(); 1683 if (currentProgram == null) { 1684 return null; 1685 } 1686 TvContentRating[] ratings = mTvContentRatingCache 1687 .getRatings(currentProgram.getContentRating()); 1688 if (ratings == null || ratings.length == 0) { 1689 ratings = new TvContentRating[] {TvContentRating.UNRATED}; 1690 } 1691 for (TvContentRating rating : ratings) { 1692 if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager 1693 .isRatingBlocked(rating)) { 1694 return rating; 1695 } 1696 } 1697 return null; 1698 } 1699 updateChannelBlockStatus(boolean channelBlocked, TvContentRating contentRating)1700 private void updateChannelBlockStatus(boolean channelBlocked, 1701 TvContentRating contentRating) { 1702 if (mChannelBlocked == channelBlocked) { 1703 return; 1704 } 1705 mChannelBlocked = channelBlocked; 1706 if (mChannelBlocked) { 1707 clearCallbacksAndMessagesSafely(); 1708 stopPlayback(true); 1709 resetTvTracks(); 1710 if (contentRating != null) { 1711 mSession.notifyContentBlocked(contentRating); 1712 } 1713 mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); 1714 } else { 1715 clearCallbacksAndMessagesSafely(); 1716 resetPlayback(); 1717 mSession.notifyContentAllowed(); 1718 mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, 1719 RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); 1720 mHandler.removeMessages(MSG_CHECK_SIGNAL); 1721 mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS); 1722 } 1723 } 1724 1725 @WorkerThread clearCallbacksAndMessagesSafely()1726 private void clearCallbacksAndMessagesSafely() { 1727 // If MSG_RELEASE is removed, TunerSessionWorker will hang forever. 1728 // Do not remove messages, after release is requested from MainThread. 1729 synchronized (mReleaseLock) { 1730 if (!mReleaseRequested) { 1731 mHandler.removeCallbacksAndMessages(null); 1732 } 1733 } 1734 } 1735 hasEnoughBackwardBuffer()1736 private boolean hasEnoughBackwardBuffer() { 1737 return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS 1738 >= mBufferStartTimeMs - mRecordStartTimeMs; 1739 } 1740 notifyVideoUnavailable(final int reason)1741 private void notifyVideoUnavailable(final int reason) { 1742 mReportedWeakSignal = (reason == TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); 1743 if (mSession != null) { 1744 mSession.notifyVideoUnavailable(reason); 1745 } 1746 } 1747 notifyVideoAvailable()1748 private void notifyVideoAvailable() { 1749 mReportedWeakSignal = false; 1750 if (mSession != null) { 1751 mSession.notifyVideoAvailable(); 1752 } 1753 } 1754 } 1755