aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/tuner/tvinput/TunerSessionWorker.java')
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSessionWorker.java457
1 files changed, 314 insertions, 143 deletions
diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
index 5230298e..e7eb017e 100644
--- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
+++ b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
@@ -27,6 +27,7 @@ import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
@@ -35,6 +36,7 @@ import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.WorkerThread;
import android.text.Html;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -45,7 +47,10 @@ import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.ExoPlayer;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.TvContentRatingCache;
+import com.android.tv.customization.TvCustomizationManager;
+import com.android.tv.customization.TvCustomizationManager.TRICKPLAY_MODE;
import com.android.tv.tuner.TunerPreferences;
+import com.android.tv.tuner.TunerPreferences.TrickplaySetting;
import com.android.tv.tuner.data.Cea708Data;
import com.android.tv.tuner.data.PsipData.EitItem;
import com.android.tv.tuner.data.PsipData.TvTracksInterface;
@@ -55,20 +60,23 @@ import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager;
import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
import com.android.tv.tuner.exoplayer.MpegTsPlayer;
+import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
+import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient;
import com.android.tv.tuner.source.TsDataSource;
import com.android.tv.tuner.source.TsDataSourceManager;
import com.android.tv.tuner.util.StatusTextUtils;
+import com.android.tv.tuner.util.SystemPropertiesProxy;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
/**
* {@link TunerSessionWorker} implements a handler thread which processes TV input jobs
@@ -82,6 +90,9 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private static final boolean DEBUG = false;
private static final boolean ENABLE_PROFILER = true;
private static final String PLAY_FROM_CHANNEL = "channel";
+ private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes";
+ private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB
+ private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB
// Public messages
public static final int MSG_SELECT_TRACK = 1;
@@ -93,6 +104,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7;
public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8;
public static final int MSG_UNBLOCKED_RATING = 9;
+ public static final int MSG_TUNER_PREFERENCES_CHANGED = 10;
// Private messages
private static final int MSG_TUNE = 1000;
@@ -147,10 +159,20 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500;
private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20;
private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250;
+ private static final int RELEASE_WAIT_INTERVAL_MS = 50;
+ private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14);
+
+ // Since release() is done asynchronously, synchronization between multiple TunerSessionWorker
+ // creation/release is required.
+ // This is used to guarantee that at most one active TunerSessionWorker exists at any give time.
+ private static Semaphore sActiveSessionSemaphore = new Semaphore(1);
private final Context mContext;
private final ChannelDataManager mChannelDataManager;
private final TsDataSourceManager mSourceManager;
+ private final int mMaxTrickplayBufferSizeMb;
+ private final File mTrickplayBufferDir;
+ private final @TRICKPLAY_MODE int mTrickplayModeCustomization;
private volatile Surface mSurface;
private volatile float mVolume = 1.0f;
private volatile boolean mCaptionEnabled;
@@ -159,6 +181,9 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private volatile Long mRecordingDuration;
private volatile long mRecordStartTimeMs;
private volatile long mBufferStartTimeMs;
+ private volatile boolean mTrickplayDisabledByStorageIssue;
+ private @TrickplaySetting int mTrickplaySetting;
+ private long mTrickplayExpiredMs;
private String mRecordingId;
private final Handler mHandler;
private int mRetryCount;
@@ -177,19 +202,20 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private TvContentRating mUnblockedContentRating;
private long mLastPositionMs;
private AudioCapabilities mAudioCapabilities;
- private final CountDownLatch mReleaseLatch = new CountDownLatch(1);
private long mLastLimitInBytes;
- private long mLastPositionInBytes;
- private final BufferManager mBufferManager;
private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
private final TunerSession mSession;
+ private final boolean mHasSoftwareAudioDecoder;
private int mPlayerState = ExoPlayer.STATE_IDLE;
private long mPreparingStartTimeMs;
private long mBufferingStartTimeMs;
private long mReadyStartTimeMs;
+ private boolean mIsActiveSession;
+ private boolean mReleaseRequested; // Guarded by mReleaseLock
+ private final Object mReleaseLock = new Object();
public TunerSessionWorker(Context context, ChannelDataManager channelDataManager,
- BufferManager bufferManager, TunerSession tunerSession) {
+ TunerSession tunerSession) {
if (DEBUG) Log.d(TAG, "TunerSessionWorker created");
mContext = context;
@@ -211,10 +237,39 @@ public class TunerSessionWorker implements PlaybackBufferListener,
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
mCaptionEnabled = captioningManager.isEnabled();
mPlaybackParams.setSpeed(1.0f);
- mBufferManager = bufferManager;
+ mMaxTrickplayBufferSizeMb =
+ SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
+ mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context);
+ if (mTrickplayModeCustomization ==
+ TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
+ boolean useExternalStorage =
+ Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
+ Environment.isExternalStorageRemovable();
+ mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null;
+ } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) {
+ mTrickplayBufferDir = context.getCacheDir();
+ } else {
+ mTrickplayBufferDir = null;
+ }
+ mTrickplayDisabledByStorageIssue = mTrickplayBufferDir == null;
+ mTrickplaySetting = TunerPreferences.getTrickplaySetting(context);
+ if (mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_NOT_SET
+ && mTrickplayModeCustomization
+ == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
+ // Consider the case of Customization package updates the value of trickplay mode
+ // to TRICKPLAY_MODE_USE_EXTERNAL_STORAGE after install.
+ mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_NOT_SET;
+ TunerPreferences.setTrickplaySetting(context, mTrickplaySetting);
+ TunerPreferences.setTrickplayExpiredMs(context, 0);
+ }
+ mTrickplayExpiredMs = TunerPreferences.getTrickplayExpiredMs(context);
mPreparingStartTimeMs = INVALID_TIME;
mBufferingStartTimeMs = INVALID_TIME;
mReadyStartTimeMs = INVALID_TIME;
+ // NOTE: We assume that TunerSessionWorker instance will be at most one.
+ // Only one TunerSessionWorker can be connected to FfmpegDecoderClient at any given time.
+ // connect() will return false, if there is a connected TunerSessionWorker already.
+ mHasSoftwareAudioDecoder = FfmpegDecoderClient.connect(context);
}
// Public methods
@@ -285,24 +340,21 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
private Long getDurationForRecording(String recordingId) {
- try {
- DvrStorageManager storageManager =
+ DvrStorageManager storageManager =
new DvrStorageManager(new File(getRecordingPath()), false);
- Pair<String, MediaFormat> trackInfo = null;
- try {
- trackInfo = storageManager.readTrackInfoFile(false);
- } catch (FileNotFoundException e) {
- }
- if (trackInfo == null) {
- trackInfo = storageManager.readTrackInfoFile(true);
- }
- Long durationUs = trackInfo.second.getLong(MediaFormat.KEY_DURATION);
+ List<BufferManager.TrackFormat> trackFormatList =
+ storageManager.readTrackInfoFiles(false);
+ if (trackFormatList.isEmpty()) {
+ trackFormatList = storageManager.readTrackInfoFiles(true);
+ }
+ if (!trackFormatList.isEmpty()) {
+ BufferManager.TrackFormat trackFormat = trackFormatList.get(0);
+ Long durationUs = trackFormat.format.getLong(MediaFormat.KEY_DURATION);
// we need duration by milli for trickplay notification.
return durationUs != null ? durationUs / 1000 : null;
- } catch (IOException e) {
- Log.e(TAG, "meta file for recording was not found: " + recordingId);
- return null;
}
+ Log.e(TAG, "meta file for recording was not found: " + recordingId);
+ return null;
}
@MainThread
@@ -341,16 +393,15 @@ public class TunerSessionWorker implements PlaybackBufferListener,
@MainThread
public void release() {
if (DEBUG) Log.d(TAG, "release()");
+ synchronized (mReleaseLock) {
+ mReleaseRequested = true;
+ }
+ if (mHasSoftwareAudioDecoder) {
+ FfmpegDecoderClient.disconnect(mContext);
+ }
mChannelDataManager.setListener(null);
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessage(MSG_RELEASE);
- try {
- mReleaseLatch.await();
- } catch (InterruptedException e) {
- Log.e(TAG, "Couldn't wait for finish of MSG_RELEASE", e);
- } finally {
- mHandler.getLooper().quitSafely();
- }
}
// MpegTsPlayer.Listener
@@ -367,7 +418,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (playbackState == ExoPlayer.STATE_READY) {
if (DEBUG) Log.d(TAG, "ExoPlayer ready");
if (!mPlayerStarted) {
- sendMessage(MSG_START_PLAYBACK, mPlayer);
+ sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer));
}
mReadyStartTimeMs = SystemClock.elapsedRealtime();
} else if (playbackState == ExoPlayer.STATE_PREPARING) {
@@ -379,7 +430,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards.
Log.i(TAG, "Player ended: end of stream");
if (mChannel != null) {
- sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
+ sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
}
}
mPlayerState = playbackState;
@@ -397,7 +448,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// If we are playing live stream, retrying playback maybe helpful. But for recorded stream,
// retrying playback is not helpful.
if (mChannel != null) {
- mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer).sendToTarget();
+ mHandler.obtainMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer))
+ .sendToTarget();
}
}
@@ -415,8 +467,12 @@ public class TunerSessionWorker implements PlaybackBufferListener,
public void onDrawnToSurface(MpegTsPlayer player, Surface surface) {
if (mSurface != null && mPlayerStarted) {
if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE");
- mBufferStartTimeMs = mRecordStartTimeMs =
- (mRecordingId != null) ? 0 : System.currentTimeMillis();
+ if (mRecordingId != null) {
+ // Workaround of b/33298048: set it to 1 instead of 0.
+ mBufferStartTimeMs = mRecordStartTimeMs = 1;
+ } else {
+ mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
+ }
notifyVideoAvailable();
mReportedDrawnToSurface = true;
@@ -461,6 +517,11 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
@Override
+ public void onClearCaptionEvent() {
+ mSession.sendUiMessage(TunerSession.MSG_UI_CLEAR_CAPTION_RENDERER);
+ }
+
+ @Override
public void onDiscoverCaptionServiceNumber(int serviceNumber) {
sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber);
}
@@ -499,7 +560,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
@Override
public void onDiskTooSlow() {
- sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
+ mTrickplayDisabledByStorageIssue = true;
+ sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
}
// EventDetector.EventListener
@@ -602,6 +664,28 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return true;
}
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
+ if (!mIsActiveSession) {
+ // Wait until release is finished if there is a pending release.
+ try {
+ while (!sActiveSessionSemaphore.tryAcquire(
+ RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) {
+ synchronized (mReleaseLock) {
+ if (mReleaseRequested) {
+ return true;
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ synchronized (mReleaseLock) {
+ if (mReleaseRequested) {
+ sActiveSessionSemaphore.release();
+ return true;
+ }
+ }
+ mIsActiveSession = true;
+ }
Uri channelUri = (Uri) msg.obj;
String recording = null;
long channelId = parseChannel(channelUri);
@@ -616,7 +700,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
return true;
}
- mHandler.removeCallbacksAndMessages(null);
+ clearCallbacksAndMessagesSafely();
+ mChannelDataManager.removeAllCallbacksAndMessages();
if (channel != null) {
mChannelDataManager.requestProgramsData(channel);
}
@@ -624,8 +709,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// TODO: Need to refactor. notifyContentAllowed() should not be called if parental
// control is turned on.
mSession.notifyContentAllowed();
- resetPlayback();
resetTvTracks();
+ resetPlayback();
mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
return true;
@@ -633,7 +718,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
case MSG_STOP_TUNE: {
if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE");
mChannel = null;
- stopPlayback();
+ stopPlayback(true);
stopCaptionTrack();
resetTvTracks();
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
@@ -642,14 +727,17 @@ public class TunerSessionWorker implements PlaybackBufferListener,
case MSG_RELEASE: {
if (DEBUG) Log.d(TAG, "MSG_RELEASE");
mHandler.removeCallbacksAndMessages(null);
- stopPlayback();
+ stopPlayback(true);
stopCaptionTrack();
mSourceManager.release();
- mReleaseLatch.countDown();
+ mHandler.getLooper().quitSafely();
+ if (mIsActiveSession) {
+ sActiveSessionSemaphore.release();
+ }
return true;
}
case MSG_RETRY_PLAYBACK: {
- if (mPlayer == msg.obj) {
+ if (System.identityHashCode(mPlayer) == (int) msg.obj) {
Log.i(TAG, "Retrying the playback for channel: " + mChannel);
mHandler.removeMessages(MSG_RETRY_PLAYBACK);
// When there is a request of retrying playback, don't reuse TunerHal.
@@ -658,16 +746,18 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (DEBUG) {
Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount);
}
+ mChannelDataManager.removeAllCallbacksAndMessages();
if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) {
resetPlayback();
} else {
// When it reaches this point, it may be due to an error that occurred in
// the tuner device. Calling stopPlayback() resets the tuner device
// to recover from the error.
- stopPlayback();
+ stopPlayback(false);
stopCaptionTrack();
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
+ Log.i(TAG, "Notify weak signal since fail to retry playback");
// After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen
// value before recovering the playback.
@@ -679,13 +769,14 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
case MSG_RESET_PLAYBACK: {
if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK");
+ mChannelDataManager.removeAllCallbacksAndMessages();
resetPlayback();
return true;
}
case MSG_START_PLAYBACK: {
if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK");
if (mChannel != null || mRecordingId != null) {
- startPlayback(msg.obj);
+ startPlayback((int) msg.obj);
}
return true;
}
@@ -790,7 +881,11 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return true;
}
case MSG_RESCHEDULE_PROGRAMS: {
- doReschedulePrograms();
+ if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) {
+ mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS);
+ } else {
+ doReschedulePrograms();
+ }
return true;
}
case MSG_PARENTAL_CONTROLS: {
@@ -814,11 +909,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return true;
}
case MSG_SELECT_TRACK: {
- if (mChannel != null) {
+ if (mChannel != null || mRecordingId != null) {
doSelectTrack(msg.arg1, (String) msg.obj);
- } else if (mRecordingId != null) {
- // TODO : mChannel == null && mRecordingId != null
- Log.d(TAG, "track selected for recording");
}
return true;
}
@@ -835,6 +927,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (mPlayer == null) {
return true;
}
+ setTrickplayEnabledIfNeeded();
doTimeShiftPause();
return true;
}
@@ -843,6 +936,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (mPlayer == null) {
return true;
}
+ setTrickplayEnabledIfNeeded();
doTimeShiftResume();
return true;
}
@@ -852,6 +946,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (mPlayer == null) {
return true;
}
+ setTrickplayEnabledIfNeeded();
doTimeShiftSeekTo(position);
return true;
}
@@ -859,6 +954,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (mPlayer == null) {
return true;
}
+ setTrickplayEnabledIfNeeded();
doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj);
return true;
}
@@ -883,6 +979,22 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
return true;
}
+ case MSG_TUNER_PREFERENCES_CHANGED: {
+ mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED);
+ @TrickplaySetting int trickplaySetting =
+ TunerPreferences.getTrickplaySetting(mContext);
+ if (trickplaySetting != mTrickplaySetting) {
+ boolean wasTrcikplayEnabled =
+ mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
+ boolean isTrickplayEnabled =
+ trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
+ mTrickplaySetting = trickplaySetting;
+ if (isTrickplayEnabled != wasTrcikplayEnabled) {
+ sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer));
+ }
+ }
+ return true;
+ }
case MSG_BUFFER_START_TIME_CHANGED: {
if (mPlayer == null) {
return true;
@@ -891,7 +1003,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (!hasEnoughBackwardBuffer()
&& (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) {
mPlayer.setPlayWhenReady(true);
- mPlayer.setAudioTrack(true);
+ mPlayer.setAudioTrackAndClosedCaption(true);
mPlaybackParams.setSpeed(1.0f);
}
return true;
@@ -909,7 +1021,6 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
TsDataSource source = mPlayer.getDataSource();
long limitInBytes = source != null ? source.getBufferedPosition() : 0L;
- long positionInBytes = source != null ? source.getLastReadPosition() : 0L;
if (TunerDebug.ENABLED) {
TunerDebug.calculateDiff();
mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT,
@@ -927,32 +1038,36 @@ public class TunerSessionWorker implements PlaybackBufferListener,
TunerDebug.getVideoPtsUsRate()
)));
}
- if (DEBUG) {
- Log.d(TAG, String.format("MSG_CHECK_SIGNAL position: %d, limit: %d",
- positionInBytes, limitInBytes));
- }
mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
long currentTime = SystemClock.elapsedRealtime();
- boolean noBufferRead = positionInBytes == mLastPositionInBytes
- && limitInBytes == mLastLimitInBytes;
- boolean isBufferingTooLong = mBufferingStartTimeMs != INVALID_TIME
- && currentTime - mBufferingStartTimeMs
- > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- boolean isPreparingTooLong = mPreparingStartTimeMs != INVALID_TIME
- && currentTime - mPreparingStartTimeMs
- > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
+ long bufferingTimeMs = mBufferingStartTimeMs != INVALID_TIME
+ ? currentTime - mBufferingStartTimeMs : mBufferingStartTimeMs;
+ long preparingTimeMs = mPreparingStartTimeMs != INVALID_TIME
+ ? currentTime - mPreparingStartTimeMs : mPreparingStartTimeMs;
+ boolean isBufferingTooLong =
+ bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
+ boolean isPreparingTooLong =
+ preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
boolean isWeakSignal = source != null
- && mChannel.getType() == Channel.TYPE_TUNER
- && (noBufferRead || isBufferingTooLong || isPreparingTooLong);
+ && mChannel.getType() != Channel.TYPE_FILE
+ && (isBufferingTooLong || isPreparingTooLong);
if (isWeakSignal && !mReportedWeakSignal) {
if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_RETRY_PLAYBACK, mPlayer), PLAYBACK_RETRY_DELAY_MS);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
+ System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
}
if (mPlayer != null) {
- mPlayer.setAudioTrack(false);
+ mPlayer.setAudioTrackAndClosedCaption(false);
}
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
+ Log.i(TAG, "Notify weak signal due to signal check, " + String.format(
+ "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " +
+ "videoFrameDrop:%d",
+ (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE,
+ bufferingTimeMs,
+ preparingTimeMs,
+ TunerDebug.getVideoFrameDrop()
+ ));
} else if (!isWeakSignal && mReportedWeakSignal) {
boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME
&& currentTime - mReadyStartTimeMs
@@ -962,11 +1077,10 @@ public class TunerSessionWorker implements PlaybackBufferListener,
} else if (mReportedDrawnToSurface) {
mHandler.removeMessages(MSG_RETRY_PLAYBACK);
notifyVideoAvailable();
- mPlayer.setAudioTrack(true);
+ mPlayer.setAudioTrackAndClosedCaption(true);
}
}
mLastLimitInBytes = limitInBytes;
- mLastPositionInBytes = positionInBytes;
mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS);
return true;
}
@@ -999,15 +1113,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (trackId == null) {
return;
}
- AtscAudioTrack audioTrack = mAudioTrackMap.get(numTrackId);
- if (audioTrack == null) {
- return;
- }
- int oldAudioPid = mChannel.getAudioPid();
- mChannel.selectAudioTrack(audioTrack.index);
- int newAudioPid = mChannel.getAudioPid();
- if (oldAudioPid != newAudioPid) {
- mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, audioTrack.index);
+ if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) {
+ mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId);
}
mSession.notifyTrackSelected(type, trackId);
} else if (type == TvTrackInfo.TYPE_SUBTITLE) {
@@ -1030,11 +1137,49 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
- private MpegTsPlayer createPlayer(AudioCapabilities capabilities, BufferManager bufferManager) {
+ private void setTrickplayEnabledIfNeeded() {
+ if (mChannel == null ||
+ mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) {
+ return;
+ }
+ if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) {
+ mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED;
+ TunerPreferences.setTrickplaySetting(
+ mContext, mTrickplaySetting);
+ }
+ }
+
+ private MpegTsPlayer createPlayer(AudioCapabilities capabilities) {
if (capabilities == null) {
Log.w(TAG, "No Audio Capabilities");
}
-
+ long now = System.currentTimeMillis();
+ if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED
+ && mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) {
+ if (mTrickplayExpiredMs == 0) {
+ mTrickplayExpiredMs = now + TRICKPLAY_OFF_DURATION_MS;
+ TunerPreferences.setTrickplayExpiredMs(mContext, mTrickplayExpiredMs);
+ } else {
+ if (mTrickplayExpiredMs < now) {
+ mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_DISABLED;
+ TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting);
+ }
+ }
+ }
+ BufferManager bufferManager = null;
+ if (mRecordingId != null) {
+ StorageManager storageManager =
+ new DvrStorageManager(new File(getRecordingPath()), false);
+ bufferManager = new BufferManager(storageManager);
+ updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles());
+ } else if (!mTrickplayDisabledByStorageIssue
+ && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED
+ && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) {
+ bufferManager = new BufferManager(new TrickplayStorageManager(mContext,
+ mTrickplayBufferDir, 1024L * 1024 * mMaxTrickplayBufferSizeMb));
+ } else {
+ Log.w(TAG, "Trickplay is disabled.");
+ }
MpegTsPlayer player = new MpegTsPlayer(
new MpegTsRendererBuilder(mContext, bufferManager, this),
mHandler, mSourceManager, capabilities, this);
@@ -1069,24 +1214,26 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) {
- if (DEBUG) {
- Log.d(TAG, "UpdateTvTracks " + tvTracksInterface);
- }
- List<AtscAudioTrack> audioTracks = tvTracksInterface.getAudioTracks();
- List<AtscCaptionTrack> captionTracks = tvTracksInterface.getCaptionTracks();
- // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio
- // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio
- // track info in PMT more and use info in EIT only when we have nothing.
- if (audioTracks != null && !audioTracks.isEmpty()
- && (mChannel.getAudioTracks() == null || fromPmt)) {
- updateAudioTracks(audioTracks);
- }
- if (captionTracks == null || captionTracks.isEmpty()) {
- if (tvTracksInterface.hasCaptionTrack()) {
+ synchronized (tvTracksInterface) {
+ if (DEBUG) {
+ Log.d(TAG, "UpdateTvTracks " + tvTracksInterface);
+ }
+ List<AtscAudioTrack> audioTracks = tvTracksInterface.getAudioTracks();
+ List<AtscCaptionTrack> captionTracks = tvTracksInterface.getCaptionTracks();
+ // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio
+ // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio
+ // track info in PMT more and use info in EIT only when we have nothing.
+ if (audioTracks != null && !audioTracks.isEmpty()
+ && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) {
+ updateAudioTracks(audioTracks);
+ }
+ if (captionTracks == null || captionTracks.isEmpty()) {
+ if (tvTracksInterface.hasCaptionTrack()) {
+ updateCaptionTracks(captionTracks);
+ }
+ } else {
updateCaptionTracks(captionTracks);
}
- } else {
- updateCaptionTracks(captionTracks);
}
}
@@ -1132,25 +1279,24 @@ public class TunerSessionWorker implements PlaybackBufferListener,
int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO);
removeTvTracks(TvTrackInfo.TYPE_AUDIO);
for (int i = 0; i < audioTrackCount; i++) {
- AtscAudioTrack audioTrack = mAudioTrackMap.get(i);
- if (audioTrack == null) {
- continue;
- }
- String language = audioTrack.language;
- if (language == null && mChannel.getAudioTracks() != null
- && mChannel.getAudioTracks().size() == mAudioTrackMap.size()) {
- // If a language is not present, use a language field in PMT section parsed.
- language = mChannel.getAudioTracks().get(i).language;
- }
- // Save the index to the audio track.
- // Later, when an audio track is selected, both the audio pid and its audio stream
- // type reside in the selected index position of the tuner channel's audio data.
- audioTrack.index = i;
+ // We use language information from EIT/VCT only when the player does not provide
+ // languages.
+ com.google.android.exoplayer.MediaFormat infoFromPlayer =
+ mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i);
+ AtscAudioTrack infoFromEit = mAudioTrackMap.get(i);
+ AtscAudioTrack infoFromVct = (mChannel != null
+ && mChannel.getAudioTracks().size() == mAudioTrackMap.size()
+ && i < mChannel.getAudioTracks().size())
+ ? mChannel.getAudioTracks().get(i) : null;
+ String language = !TextUtils.isEmpty(infoFromPlayer.language) ? infoFromPlayer.language
+ : (infoFromEit != null && infoFromEit.language != null) ? infoFromEit.language
+ : (infoFromVct != null && infoFromVct.language != null)
+ ? infoFromVct.language : null;
TvTrackInfo.Builder builder = new TvTrackInfo.Builder(
TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
builder.setLanguage(language);
- builder.setAudioChannelCount(audioTrack.channelCount);
- builder.setAudioSampleRate(audioTrack.sampleRate);
+ builder.setAudioChannelCount(infoFromPlayer.channelCount);
+ builder.setAudioSampleRate(infoFromPlayer.sampleRate);
TvTrackInfo track = builder.build();
mTvTracks.add(track);
}
@@ -1226,8 +1372,10 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
- private void stopPlayback() {
- mChannelDataManager.removeAllCallbacksAndMessages();
+ private void stopPlayback(boolean removeChannelDataCallbacks) {
+ if (removeChannelDataCallbacks) {
+ mChannelDataManager.removeAllCallbacksAndMessages();
+ }
if (mPlayer != null) {
mPlayer.setPlayWhenReady(false);
mPlayer.release();
@@ -1239,14 +1387,15 @@ public class TunerSessionWorker implements PlaybackBufferListener,
mPreparingStartTimeMs = INVALID_TIME;
mBufferingStartTimeMs = INVALID_TIME;
mReadyStartTimeMs = INVALID_TIME;
+ mLastLimitInBytes = 0L;
mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE);
mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
}
}
- private void startPlayback(Object playerObj) {
+ private void startPlayback(int playerHashCode) {
// TODO: provide hasAudio()/hasVideo() for play recordings.
- if (mPlayer == null || mPlayer != playerObj) {
+ if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) {
return;
}
if (mChannel != null && !mChannel.hasAudio()) {
@@ -1257,9 +1406,12 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return;
}
if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio())
- || (mChannel.hasVideo() && !mPlayer.hasVideo()))) {
- // Tracks haven't been detected in the extractor. Try again.
- sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
+ || (mChannel.hasVideo() && !mPlayer.hasVideo()))
+ && mChannel.getType() != Channel.TYPE_NETWORK) {
+ // If the channel is from network, skip this part since the video and audio tracks
+ // information for channels from network are more reliable in the extractor. Otherwise,
+ // tracks haven't been detected in the extractor. Try again.
+ sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
return;
}
// Since mSurface is volatile, we define a local variable surface to keep the same value
@@ -1269,7 +1421,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
mPlayer.setSurface(surface);
mPlayer.setPlayWhenReady(true);
mPlayer.setVolume(mVolume);
- if (mChannel != null && !mChannel.hasVideo() && mChannel.hasAudio()) {
+ if (mChannel != null && mPlayer.hasAudio() && !mPlayer.hasVideo()) {
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY);
} else if (!mReportedWeakSignal) {
// Doesn't show buffering during weak signal.
@@ -1286,22 +1438,21 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return;
}
mSourceManager.setKeepTuneStatus(true);
- BufferManager bufferManager = mChannel != null ? mBufferManager : new BufferManager(
- new DvrStorageManager(new File(getRecordingPath()), false));
- MpegTsPlayer player = createPlayer(mAudioCapabilities, bufferManager);
+ MpegTsPlayer player = createPlayer(mAudioCapabilities);
player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
player.setVideoEventListener(this);
player.setCaptionServiceNumber(mCaptionTrack != null ?
mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER);
- if (!player.prepare(mContext, mChannel, this)) {
+ if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) {
mSourceManager.setKeepTuneStatus(false);
player.release();
if (!mHandler.hasMessages(MSG_TUNE)) {
// When prepare failed, there may be some errors related to hardware. In that
// case, retry playback immediately may not help.
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer),
- PLAYBACK_RETRY_DELAY_MS);
+ Log.i(TAG, "Notify weak signal due to player preparation failure");
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
+ System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
}
} else {
mPlayer = player;
@@ -1314,7 +1465,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private void resetPlayback() {
long timestamp, oldTimestamp;
timestamp = SystemClock.elapsedRealtime();
- stopPlayback();
+ stopPlayback(false);
stopCaptionTrack();
if (ENABLE_PROFILER) {
oldTimestamp = timestamp;
@@ -1336,8 +1487,12 @@ public class TunerSessionWorker implements PlaybackBufferListener,
mRecordingDuration = recording != null ? getDurationForRecording(recording) : null;
mProgram = null;
mPrograms = null;
- mBufferStartTimeMs = mRecordStartTimeMs =
- (mRecordingId != null) ? 0 : System.currentTimeMillis();
+ if (mRecordingId != null) {
+ // Workaround of b/33298048: set it to 1 instead of 0.
+ mBufferStartTimeMs = mRecordStartTimeMs = 1;
+ } else {
+ mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
+ }
mLastPositionMs = 0;
mCaptionTrack = null;
mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
@@ -1385,14 +1540,19 @@ public class TunerSessionWorker implements PlaybackBufferListener,
} else {
mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs);
mPlaybackParams.setSpeed(1.0f);
- mPlayer.setAudioTrack(true);
+ mPlayer.setAudioTrackAndClosedCaption(true);
return;
}
} else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) {
- mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setAudioTrack(true);
- return;
+ // Stops trickplay when FF requested the position later than current position.
+ // If RW trickplay requested the position later than current position,
+ // continue trickplay.
+ if (mPlaybackParams.getSpeed() > 0.0f) {
+ mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs);
+ mPlaybackParams.setSpeed(1.0f);
+ mPlayer.setAudioTrackAndClosedCaption(true);
+ return;
+ }
}
long delayForNextSeek = getTrickPlaySeekIntervalMs();
@@ -1414,7 +1574,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
mPlaybackParams.setSpeed(1.0f);
mPlayer.setPlayWhenReady(false);
- mPlayer.setAudioTrack(true);
+ mPlayer.setAudioTrackAndClosedCaption(true);
}
private void doTimeShiftResume() {
@@ -1422,7 +1582,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
mPlaybackParams.setSpeed(1.0f);
mPlayer.setPlayWhenReady(true);
- mPlayer.setAudioTrack(true);
+ mPlayer.setAudioTrackAndClosedCaption(true);
}
private void doTimeShiftSeekTo(long timeMs) {
@@ -1443,14 +1603,14 @@ public class TunerSessionWorker implements PlaybackBufferListener,
doTimeShiftResume();
} else if (mPlayer.supportSmoothTrickPlay(speed)) {
mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlayer.setAudioTrack(false);
+ mPlayer.setAudioTrackAndClosedCaption(false);
mPlayer.startSmoothTrickplay(mPlaybackParams);
mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
TRICKPLAY_MONITOR_INTERVAL_MS);
} else {
mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) {
- mPlayer.setAudioTrack(false);
+ mPlayer.setAudioTrackAndClosedCaption(false);
mPlayer.setPlayWhenReady(false);
// Initiate trickplay
mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK,
@@ -1525,8 +1685,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
TvContentRating[] ratings = mTvContentRatingCache
.getRatings(currentProgram.getContentRating());
- if (ratings == null) {
- return null;
+ if (ratings == null || ratings.length == 0) {
+ ratings = new TvContentRating[] {TvContentRating.UNRATED};
}
for (TvContentRating rating : ratings) {
if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager
@@ -1544,15 +1704,15 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
mChannelBlocked = channelBlocked;
if (mChannelBlocked) {
- mHandler.removeCallbacksAndMessages(null);
- stopPlayback();
+ clearCallbacksAndMessagesSafely();
+ stopPlayback(true);
resetTvTracks();
if (contentRating != null) {
mSession.notifyContentBlocked(contentRating);
}
mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS);
} else {
- mHandler.removeCallbacksAndMessages(null);
+ clearCallbacksAndMessagesSafely();
resetPlayback();
mSession.notifyContentAllowed();
mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
@@ -1562,6 +1722,17 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
+ @WorkerThread
+ private void clearCallbacksAndMessagesSafely() {
+ // If MSG_RELEASE is removed, TunerSessionWorker will hang forever.
+ // Do not remove messages, after release is requested from MainThread.
+ synchronized (mReleaseLock) {
+ if (!mReleaseRequested) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ }
+ }
+
private boolean hasEnoughBackwardBuffer() {
return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS
>= mBufferStartTimeMs - mRecordStartTimeMs;