165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/* 265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2015 The Android Open Source Project 365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License. 665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at 765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software 1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and 1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License. 1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner.tvinput; 1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentResolver; 2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentUris; 2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentValues; 2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context; 2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.database.Cursor; 2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvContract; 25944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport android.media.tv.TvContract.RecordedPrograms; 2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvInputManager; 2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.net.Uri; 28d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.os.AsyncTask; 2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Handler; 3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.HandlerThread; 3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Message; 3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.IntDef; 3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.MainThread; 3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.Nullable; 35944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport android.support.media.tv.Program; 3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Log; 37633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport android.util.Pair; 38944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.BaseApplication; 3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.recording.RecordingCapability; 40944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.recording.RecordingStorageStatusManager; 41944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.util.CommonUtils; 4265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.DvbDeviceAccessor; 4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData; 44633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.tuner.data.PsipData.EitItem; 4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.TunerChannel; 46633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; 4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor; 4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.SampleExtractor; 4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.buffer.BufferManager; 5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; 51d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.tuner.source.TsDataSource; 52d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.tuner.source.TsDataSourceManager; 53a1589bd48e05abbee991e0cdd27fa402a5dc5001Live Channels Teamimport com.google.android.exoplayer.C; 5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.io.File; 5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.io.IOException; 5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.lang.annotation.Retention; 5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.lang.annotation.RetentionPolicy; 58633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport java.util.ArrayList; 5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List; 6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Locale; 6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Random; 6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.TimeUnit; 6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 6495961816a768da387f0b5523cf4363ace2044089Nick Chalko/** Implements a DVR feature. */ 6595961816a768da387f0b5523cf4363ace2044089Nick Chalkopublic class TunerRecordingSessionWorker 6695961816a768da387f0b5523cf4363ace2044089Nick Chalko implements PlaybackBufferListener, 6795961816a768da387f0b5523cf4363ace2044089Nick Chalko EventDetector.EventListener, 6895961816a768da387f0b5523cf4363ace2044089Nick Chalko SampleExtractor.OnCompletionListener, 6995961816a768da387f0b5523cf4363ace2044089Nick Chalko Handler.Callback { 7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final String TAG = "TunerRecordingSessionW"; 7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final boolean DEBUG = false; 7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 7395961816a768da387f0b5523cf4363ace2044089Nick Chalko private static final String SORT_BY_TIME = 7495961816a768da387f0b5523cf4363ace2044089Nick Chalko TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS 7595961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", " 7695961816a768da387f0b5523cf4363ace2044089Nick Chalko + TvContract.Programs.COLUMN_CHANNEL_ID 7795961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", " 7895961816a768da387f0b5523cf4363ace2044089Nick Chalko + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS; 79633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); 8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); 8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); 82d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final long PREPARE_RECORDER_POLL_MS = 50; 8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final int MSG_TUNE = 1; 8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final int MSG_START_RECORDING = 2; 85d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_PREPARE_RECODER = 3; 86d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_STOP_RECORDING = 4; 87d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_MONITOR_STORAGE_STATUS = 5; 88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_RELEASE = 6; 89633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int MSG_UPDATE_CC_INFO = 7; 9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final RecordingCapability mCapabilities; 9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 92944779887775bd950cf1abf348d2df461593f6abLive Channels Team private static final String[] PROGRAM_PROJECTION = { 93944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_CHANNEL_ID, 94944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_TITLE, 95944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_SEASON_TITLE, 96944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_EPISODE_TITLE, 97944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, 98944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, 99944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_SHORT_DESCRIPTION, 100944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_POSTER_ART_URI, 101944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_THUMBNAIL_URI, 102944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_CANONICAL_GENRE, 103944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_CONTENT_RATING, 104944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, 105944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, 106944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_VIDEO_WIDTH, 107944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_VIDEO_HEIGHT, 108944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA 109944779887775bd950cf1abf348d2df461593f6abLive Channels Team }; 11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 111633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING}) 11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Retention(RetentionPolicy.SOURCE) 11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public @interface DvrSessionState {} 11495961816a768da387f0b5523cf4363ace2044089Nick Chalko 11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final int STATE_IDLE = 1; 116633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int STATE_TUNING = 2; 117633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int STATE_TUNED = 3; 118633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int STATE_RECORDING = 4; 11965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long CHANNEL_ID_NONE = -1; 121633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int MAX_TUNING_RETRY = 6; 12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Context mContext; 12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final ChannelDataManager mChannelDataManager; 125944779887775bd950cf1abf348d2df461593f6abLive Channels Team private final RecordingStorageStatusManager mRecordingStorageStatusManager; 12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Handler mHandler; 127d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final TsDataSourceManager mSourceManager; 12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Random mRandom = new Random(); 12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 130d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private TsDataSource mTunerSource; 13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private TunerChannel mChannel; 13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private File mStorageDir; 13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long mRecordStartTime; 13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long mRecordEndTime; 13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean mRecorderRunning; 13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private SampleExtractor mRecorder; 13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final TunerRecordingSession mSession; 13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @DvrSessionState private int mSessionState = STATE_IDLE; 13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final String mInputId; 14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private Uri mProgramUri; 14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 142633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private PsipData.EitItem mCurrenProgram; 143633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private List<AtscCaptionTrack> mCaptionTracks; 144633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private DvrStorageManager mDvrStorageManager; 145633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 14695961816a768da387f0b5523cf4363ace2044089Nick Chalko public TunerRecordingSessionWorker( 14795961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, 14895961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId, 14995961816a768da387f0b5523cf4363ace2044089Nick Chalko ChannelDataManager dataManager, 15095961816a768da387f0b5523cf4363ace2044089Nick Chalko TunerRecordingSession session) { 15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRandom.setSeed(System.nanoTime()); 15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mContext = context; 15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko HandlerThread handlerThread = new HandlerThread(TAG); 15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko handlerThread.start(); 15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler = new Handler(handlerThread.getLooper(), this); 156944779887775bd950cf1abf348d2df461593f6abLive Channels Team mRecordingStorageStatusManager = 157944779887775bd950cf1abf348d2df461593f6abLive Channels Team BaseApplication.getSingletons(context).getRecordingStorageStatusManager(); 15865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager = dataManager; 15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.checkDataVersion(context); 160d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mSourceManager = TsDataSourceManager.createSourceManager(true); 16165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId); 16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputId = inputId; 16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (DEBUG) Log.d(TAG, mCapabilities.toString()); 16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession = session; 16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // PlaybackBufferListener 16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 16995961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onBufferStartTimeChanged(long startTimeMs) {} 17065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 17295961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onBufferStateChanged(boolean available) {} 17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 17595961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onDiskTooSlow() {} 17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // EventDetector.EventListener 17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { 18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannel == null || mChannel.compareTo(channel) != 0) { 18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); 18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) { 18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannel == null || mChannel.compareTo(channel) != 0) { 18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 191633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget(); 19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.notifyEventDetected(channel, items); 19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChannelScanDone() { 19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // do nothing. 19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // SampleExtractor.OnCompletionListener 20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onCompletion(boolean success, long lastExtractedPositionUs) { 20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onRecordingResult(success, lastExtractedPositionUs); 20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko reset(); 20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 20795961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Tunes to {@code channelUri}. */ 20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void tune(Uri channelUri) { 21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeCallbacksAndMessages(null); 211633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget(); 21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 21495961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Starts recording. */ 21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void startRecording(@Nullable Uri programUri) { 21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget(); 21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 22095961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Stops recording. */ 22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void stopRecording() { 22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.sendEmptyMessage(MSG_STOP_RECORDING); 22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 22695961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Releases all resources. */ 22765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void release() { 22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeCallbacksAndMessages(null); 23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.sendEmptyMessage(MSG_RELEASE); 23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public boolean handleMessage(Message msg) { 23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko switch (msg.what) { 23695961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_TUNE: 23795961816a768da387f0b5523cf4363ace2044089Nick Chalko { 23895961816a768da387f0b5523cf4363ace2044089Nick Chalko Uri channelUri = (Uri) msg.obj; 23995961816a768da387f0b5523cf4363ace2044089Nick Chalko int retryCount = msg.arg1; 24095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Tune to " + channelUri); 24195961816a768da387f0b5523cf4363ace2044089Nick Chalko if (doTune(channelUri)) { 24295961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mSessionState == STATE_TUNED) { 24395961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onTuned(channelUri); 244633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } else { 24595961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); 24695961816a768da387f0b5523cf4363ace2044089Nick Chalko if (retryCount < MAX_TUNING_RETRY) { 24795961816a768da387f0b5523cf4363ace2044089Nick Chalko Message tuneMsg = 24895961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.obtainMessage( 24995961816a768da387f0b5523cf4363ace2044089Nick Chalko MSG_TUNE, retryCount + 1, 0, channelUri); 25095961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS); 25195961816a768da387f0b5523cf4363ace2044089Nick Chalko } else { 25295961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); 25395961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 25495961816a768da387f0b5523cf4363ace2044089Nick Chalko } 255633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 256633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 257d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return true; 258d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 25995961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_START_RECORDING: 26095961816a768da387f0b5523cf4363ace2044089Nick Chalko { 26195961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Start recording"); 26295961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!doStartRecording((Uri) msg.obj)) { 26395961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 264d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26795961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_PREPARE_RECODER: 26895961816a768da387f0b5523cf4363ace2044089Nick Chalko { 26995961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Preparing recorder"); 27095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mRecorderRunning) { 27195961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 27295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 27395961816a768da387f0b5523cf4363ace2044089Nick Chalko try { 27495961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mRecorder.prepare()) { 27595961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendEmptyMessageDelayed( 27695961816a768da387f0b5523cf4363ace2044089Nick Chalko MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS); 27795961816a768da387f0b5523cf4363ace2044089Nick Chalko } 27895961816a768da387f0b5523cf4363ace2044089Nick Chalko } catch (IOException e) { 27995961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor"); 28095961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 28195961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 28295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 28365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28595961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_STOP_RECORDING: 28695961816a768da387f0b5523cf4363ace2044089Nick Chalko { 28795961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Stop recording"); 28895961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mSessionState != STATE_RECORDING) { 28995961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 29095961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 29195961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 29295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorderRunning) { 29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko stopRecorder(); 29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 29695961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 29795961816a768da387f0b5523cf4363ace2044089Nick Chalko } 29895961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_MONITOR_STORAGE_STATUS: 29995961816a768da387f0b5523cf4363ace2044089Nick Chalko { 30095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mSessionState != STATE_RECORDING) { 30195961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 30295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 303944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (!mRecordingStorageStatusManager.isStorageSufficient()) { 30495961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mRecorderRunning) { 30595961816a768da387f0b5523cf4363ace2044089Nick Chalko stopRecorder(); 30695961816a768da387f0b5523cf4363ace2044089Nick Chalko } 30795961816a768da387f0b5523cf4363ace2044089Nick Chalko new DeleteRecordingTask().execute(mStorageDir); 30895961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 30995961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 31095961816a768da387f0b5523cf4363ace2044089Nick Chalko } else { 31195961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendEmptyMessageDelayed( 31295961816a768da387f0b5523cf4363ace2044089Nick Chalko MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); 31395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 31495961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 31595961816a768da387f0b5523cf4363ace2044089Nick Chalko } 31695961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_RELEASE: 31795961816a768da387f0b5523cf4363ace2044089Nick Chalko { 31895961816a768da387f0b5523cf4363ace2044089Nick Chalko // Since release was requested, current recording will be cancelled 31995961816a768da387f0b5523cf4363ace2044089Nick Chalko // without notification. 32065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko reset(); 32195961816a768da387f0b5523cf4363ace2044089Nick Chalko mSourceManager.release(); 32295961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.removeCallbacksAndMessages(null); 32395961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.getLooper().quitSafely(); 32495961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 32595961816a768da387f0b5523cf4363ace2044089Nick Chalko } 32695961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_UPDATE_CC_INFO: 32795961816a768da387f0b5523cf4363ace2044089Nick Chalko { 32895961816a768da387f0b5523cf4363ace2044089Nick Chalko Pair<TunerChannel, List<EitItem>> pair = 32995961816a768da387f0b5523cf4363ace2044089Nick Chalko (Pair<TunerChannel, List<EitItem>>) msg.obj; 33095961816a768da387f0b5523cf4363ace2044089Nick Chalko updateCaptionTracks(pair.first, pair.second); 33195961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 33265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Nullable 33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private TunerChannel getChannel(Uri channelUri) { 33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (channelUri == null) { 34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return null; 34165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long channelId; 34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko try { 34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko channelId = ContentUris.parseId(channelUri); 34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } catch (UnsupportedOperationException | NumberFormatException e) { 34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko channelId = CHANNEL_ID_NONE; 34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId); 34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private String getStorageKey() { 35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long prefix = System.currentTimeMillis(); 35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko int suffix = mRandom.nextInt(); 35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix); 35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void reset() { 35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorder != null) { 35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder.release(); 36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder = null; 36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mTunerSource != null) { 36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSourceManager.releaseDataSource(mTunerSource); 36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTunerSource = null; 36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 366633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mDvrStorageManager = null; 36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSessionState = STATE_IDLE; 36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorderRunning = false; 36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean doTune(Uri channelUri) { 372633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) { 37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Tuning was requested from wrong status."); 37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannel = getChannel(channelUri); 37865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannel == null) { 37965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel); 38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 382633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } else if (mChannel.isRecordingProhibited()) { 383633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 384633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel); 385633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return false; 38665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 387944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (!mRecordingStorageStatusManager.isStorageSufficient()) { 38865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Tuning failed due to insufficient storage."); 39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this); 39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mTunerSource == null) { 394633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko // Retry tuning in this case. 395633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mSessionState = STATE_TUNING; 396633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return true; 39765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSessionState = STATE_TUNED; 39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 40265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean doStartRecording(@Nullable Uri programUri) { 40365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mSessionState != STATE_TUNED) { 40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Recording session status abnormal"); 40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40895961816a768da387f0b5523cf4363ace2044089Nick Chalko mStorageDir = 409944779887775bd950cf1abf348d2df461593f6abLive Channels Team mRecordingStorageStatusManager.isStorageSufficient() 41095961816a768da387f0b5523cf4363ace2044089Nick Chalko ? new File( 411944779887775bd950cf1abf348d2df461593f6abLive Channels Team mRecordingStorageStatusManager.getRecordingRootDataDirectory(), 41295961816a768da387f0b5523cf4363ace2044089Nick Chalko getStorageKey()) 41395961816a768da387f0b5523cf4363ace2044089Nick Chalko : null; 41465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mStorageDir == null) { 41565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 41665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Failed to start recording due to insufficient storage."); 41765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 41865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 41965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Since tuning might be happened a while ago, shifts the start position of tuned source. 42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition()); 42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecordStartTime = System.currentTimeMillis(); 422633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mDvrStorageManager = new DvrStorageManager(mStorageDir, true); 42395961816a768da387f0b5523cf4363ace2044089Nick Chalko mRecorder = 42495961816a768da387f0b5523cf4363ace2044089Nick Chalko new ExoPlayerSampleExtractor( 42595961816a768da387f0b5523cf4363ace2044089Nick Chalko Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true); 42665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder.setOnCompletionListener(this, mHandler); 42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mProgramUri = programUri; 42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSessionState = STATE_RECORDING; 42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorderRunning = true; 430d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mHandler.sendEmptyMessage(MSG_PREPARE_RECODER); 43165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); 43295961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); 43365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void stopRecorder() { 43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Do not change session status. 43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorder != null) { 43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder.release(); 44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecordEndTime = System.currentTimeMillis(); 44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder = null; 44265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 44365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorderRunning = false; 44465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); 44565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.i(TAG, "Recording stopped"); 44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 448633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private void updateCaptionTracks(TunerChannel channel, List<PsipData.EitItem> items) { 44995961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mChannel == null 45095961816a768da387f0b5523cf4363ace2044089Nick Chalko || channel == null 45195961816a768da387f0b5523cf4363ace2044089Nick Chalko || mChannel.compareTo(channel) != 0 45295961816a768da387f0b5523cf4363ace2044089Nick Chalko || items == null 45395961816a768da387f0b5523cf4363ace2044089Nick Chalko || items.isEmpty()) { 454633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return; 455633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 456633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko PsipData.EitItem currentProgram = getCurrentProgram(items); 45795961816a768da387f0b5523cf4363ace2044089Nick Chalko if (currentProgram == null 45895961816a768da387f0b5523cf4363ace2044089Nick Chalko || !currentProgram.hasCaptionTrack() 459a1589bd48e05abbee991e0cdd27fa402a5dc5001Live Channels Team || (mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0)) { 460633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return; 461633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 462633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mCurrenProgram = currentProgram; 463633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks()); 464633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (DEBUG) { 46595961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.d( 46695961816a768da387f0b5523cf4363ace2044089Nick Chalko TAG, 46795961816a768da387f0b5523cf4363ace2044089Nick Chalko "updated " + mCaptionTracks.size() + " caption tracks for " + currentProgram); 468633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 469633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 470633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 471633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private PsipData.EitItem getCurrentProgram(List<PsipData.EitItem> items) { 472633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko for (PsipData.EitItem item : items) { 473633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (mRecordStartTime >= item.getStartTimeUtcMillis() 474633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko && mRecordStartTime < item.getEndTimeUtcMillis()) { 475633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return item; 476633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 477633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 478633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return null; 479633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 480633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 48165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private Program getRecordedProgram() { 48265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ContentResolver resolver = mContext.getContentResolver(); 48365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Uri programUri = mProgramUri; 48465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mProgramUri == null) { 48565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long avg = mRecordStartTime / 2 + mRecordEndTime / 2; 48665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg); 48765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 488944779887775bd950cf1abf348d2df461593f6abLive Channels Team try (Cursor c = resolver.query(programUri, PROGRAM_PROJECTION, null, null, SORT_BY_TIME)) { 489944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (c != null && c.moveToNext()) { 490944779887775bd950cf1abf348d2df461593f6abLive Channels Team Program result = Program.fromCursor(c); 49165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (DEBUG) { 49265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.v(TAG, "Finished query for " + this); 49365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 49465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return result; 49565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 49665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (c == null) { 49765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Unknown query error for " + this); 49865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 499944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (DEBUG) Log.d(TAG, "Can not find program:" + programUri); 50065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return null; 50265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 50695961816a768da387f0b5523cf4363ace2044089Nick Chalko private Uri insertRecordedProgram( 50795961816a768da387f0b5523cf4363ace2044089Nick Chalko Program program, 50895961816a768da387f0b5523cf4363ace2044089Nick Chalko long channelId, 50995961816a768da387f0b5523cf4363ace2044089Nick Chalko String storageUri, 51095961816a768da387f0b5523cf4363ace2044089Nick Chalko long totalBytes, 51195961816a768da387f0b5523cf4363ace2044089Nick Chalko long startTime, 51295961816a768da387f0b5523cf4363ace2044089Nick Chalko long endTime) { 513944779887775bd950cf1abf348d2df461593f6abLive Channels Team ContentValues values = new ContentValues(); 514944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_INPUT_ID, mInputId); 515944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId); 516944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, storageUri); 517944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - startTime); 518944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, totalBytes); 519944779887775bd950cf1abf348d2df461593f6abLive Channels Team // startTime and endTime could be overridden by program's start and end value. 520944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime); 521944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime); 52265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (program != null) { 523944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.putAll(program.toContentValues()); 52465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52595961816a768da387f0b5523cf4363ace2044089Nick Chalko return mContext.getContentResolver() 52695961816a768da387f0b5523cf4363ace2044089Nick Chalko .insert(TvContract.RecordedPrograms.CONTENT_URI, values); 52765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 52965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void onRecordingResult(boolean success, long lastExtractedPositionUs) { 53065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mSessionState != STATE_RECORDING) { 53165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Error notification is not needed. 53265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Recording session status abnormal"); 53365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 53465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorderRunning) { 53665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // In case of recorder not being stopped, because of premature termination of recording. 53765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko stopRecorder(); 53865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53995961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!success 54095961816a768da387f0b5523cf4363ace2044089Nick Chalko && lastExtractedPositionUs 54195961816a768da387f0b5523cf4363ace2044089Nick Chalko < TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) { 542d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new DeleteRecordingTask().execute(mStorageDir); 54365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 54465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Recording failed during recording"); 54565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 54665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 54765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.i(TAG, "recording finished " + (success ? "completely" : "partially")); 548633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko long recordEndTime = 549633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko (lastExtractedPositionUs == C.UNKNOWN_TIME_US) 550633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko ? System.currentTimeMillis() 551633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko : mRecordStartTime + lastExtractedPositionUs / 1000; 552633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Uri uri = 553633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko insertRecordedProgram( 554633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko getRecordedProgram(), 555633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mChannel.getChannelId(), 556633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Uri.fromFile(mStorageDir).toString(), 557633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 1024 * 1024, 558633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mRecordStartTime, 559633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko recordEndTime); 56065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (uri == null) { 561d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new DeleteRecordingTask().execute(mStorageDir); 56265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 56365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Inserting a recording to DB failed"); 56465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 56565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 566633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks); 56765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onRecordFinished(uri); 56865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 569d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 570d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static class DeleteRecordingTask extends AsyncTask<File, Void, Void> { 571d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 572d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @Override 573d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public Void doInBackground(File... files) { 574d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (files == null || files.length == 0) { 575d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return null; 576d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 57795961816a768da387f0b5523cf4363ace2044089Nick Chalko for (File file : files) { 578944779887775bd950cf1abf348d2df461593f6abLive Channels Team CommonUtils.deleteDirOrFile(file); 579d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 580d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return null; 581d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 582d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 58365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko} 584