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