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