TunerRecordingSessionWorker.java revision a1589bd48e05abbee991e0cdd27fa402a5dc5001
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. */ 65944779887775bd950cf1abf348d2df461593f6abLive Channels Team@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed 6695961816a768da387f0b5523cf4363ace2044089Nick Chalkopublic class TunerRecordingSessionWorker 6795961816a768da387f0b5523cf4363ace2044089Nick Chalko implements PlaybackBufferListener, 6895961816a768da387f0b5523cf4363ace2044089Nick Chalko EventDetector.EventListener, 6995961816a768da387f0b5523cf4363ace2044089Nick Chalko SampleExtractor.OnCompletionListener, 7095961816a768da387f0b5523cf4363ace2044089Nick Chalko Handler.Callback { 7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final String TAG = "TunerRecordingSessionW"; 7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final boolean DEBUG = false; 7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 7495961816a768da387f0b5523cf4363ace2044089Nick Chalko private static final String SORT_BY_TIME = 7595961816a768da387f0b5523cf4363ace2044089Nick Chalko TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS 7695961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", " 7795961816a768da387f0b5523cf4363ace2044089Nick Chalko + TvContract.Programs.COLUMN_CHANNEL_ID 7895961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", " 7995961816a768da387f0b5523cf4363ace2044089Nick Chalko + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS; 80633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); 8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); 8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); 83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final long PREPARE_RECORDER_POLL_MS = 50; 8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final int MSG_TUNE = 1; 8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final int MSG_START_RECORDING = 2; 86d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_PREPARE_RECODER = 3; 87d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_STOP_RECORDING = 4; 88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_MONITOR_STORAGE_STATUS = 5; 89d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final int MSG_RELEASE = 6; 90633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int MSG_UPDATE_CC_INFO = 7; 9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final RecordingCapability mCapabilities; 9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 93944779887775bd950cf1abf348d2df461593f6abLive Channels Team private static final String[] PROGRAM_PROJECTION = { 94944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_CHANNEL_ID, 95944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_TITLE, 96944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_SEASON_TITLE, 97944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_EPISODE_TITLE, 98944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, 99944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, 100944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_SHORT_DESCRIPTION, 101944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_POSTER_ART_URI, 102944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_THUMBNAIL_URI, 103944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_CANONICAL_GENRE, 104944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_CONTENT_RATING, 105944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, 106944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, 107944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_VIDEO_WIDTH, 108944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_VIDEO_HEIGHT, 109944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA 110944779887775bd950cf1abf348d2df461593f6abLive Channels Team }; 11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 112633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING}) 11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Retention(RetentionPolicy.SOURCE) 11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public @interface DvrSessionState {} 11595961816a768da387f0b5523cf4363ace2044089Nick Chalko 11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final int STATE_IDLE = 1; 117633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int STATE_TUNING = 2; 118633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int STATE_TUNED = 3; 119633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int STATE_RECORDING = 4; 12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long CHANNEL_ID_NONE = -1; 122633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static final int MAX_TUNING_RETRY = 6; 12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Context mContext; 12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final ChannelDataManager mChannelDataManager; 126944779887775bd950cf1abf348d2df461593f6abLive Channels Team private final RecordingStorageStatusManager mRecordingStorageStatusManager; 12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Handler mHandler; 128d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final TsDataSourceManager mSourceManager; 12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Random mRandom = new Random(); 13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 131d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private TsDataSource mTunerSource; 13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private TunerChannel mChannel; 13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private File mStorageDir; 13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long mRecordStartTime; 13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long mRecordEndTime; 13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean mRecorderRunning; 13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private SampleExtractor mRecorder; 13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final TunerRecordingSession mSession; 13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @DvrSessionState private int mSessionState = STATE_IDLE; 14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final String mInputId; 14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private Uri mProgramUri; 14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 143633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private PsipData.EitItem mCurrenProgram; 144633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private List<AtscCaptionTrack> mCaptionTracks; 145633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private DvrStorageManager mDvrStorageManager; 146633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 14795961816a768da387f0b5523cf4363ace2044089Nick Chalko public TunerRecordingSessionWorker( 14895961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, 14995961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId, 15095961816a768da387f0b5523cf4363ace2044089Nick Chalko ChannelDataManager dataManager, 15195961816a768da387f0b5523cf4363ace2044089Nick Chalko TunerRecordingSession session) { 15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRandom.setSeed(System.nanoTime()); 15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mContext = context; 15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko HandlerThread handlerThread = new HandlerThread(TAG); 15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko handlerThread.start(); 15665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler = new Handler(handlerThread.getLooper(), this); 157944779887775bd950cf1abf348d2df461593f6abLive Channels Team mRecordingStorageStatusManager = 158944779887775bd950cf1abf348d2df461593f6abLive Channels Team BaseApplication.getSingletons(context).getRecordingStorageStatusManager(); 15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager = dataManager; 16065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.checkDataVersion(context); 161d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mSourceManager = TsDataSourceManager.createSourceManager(true); 16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId); 16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputId = inputId; 16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (DEBUG) Log.d(TAG, mCapabilities.toString()); 16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession = session; 16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // PlaybackBufferListener 16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 17095961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onBufferStartTimeChanged(long startTimeMs) {} 17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 17265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 17395961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onBufferStateChanged(boolean available) {} 17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 17565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 17695961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onDiskTooSlow() {} 17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // EventDetector.EventListener 17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { 18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannel == null || mChannel.compareTo(channel) != 0) { 18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); 18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) { 18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannel == null || mChannel.compareTo(channel) != 0) { 19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 192633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget(); 19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.notifyEventDetected(channel, items); 19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChannelScanDone() { 19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // do nothing. 19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // SampleExtractor.OnCompletionListener 20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onCompletion(boolean success, long lastExtractedPositionUs) { 20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onRecordingResult(success, lastExtractedPositionUs); 20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko reset(); 20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 20895961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Tunes to {@code channelUri}. */ 20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void tune(Uri channelUri) { 21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeCallbacksAndMessages(null); 212633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget(); 21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 21595961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Starts recording. */ 21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void startRecording(@Nullable Uri programUri) { 21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget(); 21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 22195961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Stops recording. */ 22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void stopRecording() { 22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.sendEmptyMessage(MSG_STOP_RECORDING); 22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 22665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 22795961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Releases all resources. */ 22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @MainThread 22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void release() { 23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeCallbacksAndMessages(null); 23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.sendEmptyMessage(MSG_RELEASE); 23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public boolean handleMessage(Message msg) { 23665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko switch (msg.what) { 23795961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_TUNE: 23895961816a768da387f0b5523cf4363ace2044089Nick Chalko { 23995961816a768da387f0b5523cf4363ace2044089Nick Chalko Uri channelUri = (Uri) msg.obj; 24095961816a768da387f0b5523cf4363ace2044089Nick Chalko int retryCount = msg.arg1; 24195961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Tune to " + channelUri); 24295961816a768da387f0b5523cf4363ace2044089Nick Chalko if (doTune(channelUri)) { 24395961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mSessionState == STATE_TUNED) { 24495961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onTuned(channelUri); 245633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } else { 24695961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); 24795961816a768da387f0b5523cf4363ace2044089Nick Chalko if (retryCount < MAX_TUNING_RETRY) { 24895961816a768da387f0b5523cf4363ace2044089Nick Chalko Message tuneMsg = 24995961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.obtainMessage( 25095961816a768da387f0b5523cf4363ace2044089Nick Chalko MSG_TUNE, retryCount + 1, 0, channelUri); 25195961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS); 25295961816a768da387f0b5523cf4363ace2044089Nick Chalko } else { 25395961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); 25495961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 25595961816a768da387f0b5523cf4363ace2044089Nick Chalko } 256633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 257633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 258d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return true; 259d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 26095961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_START_RECORDING: 26195961816a768da387f0b5523cf4363ace2044089Nick Chalko { 26295961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Start recording"); 26395961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!doStartRecording((Uri) msg.obj)) { 26495961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 265d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26895961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_PREPARE_RECODER: 26995961816a768da387f0b5523cf4363ace2044089Nick Chalko { 27095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Preparing recorder"); 27195961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mRecorderRunning) { 27295961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 27395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 27495961816a768da387f0b5523cf4363ace2044089Nick Chalko try { 27595961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mRecorder.prepare()) { 27695961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendEmptyMessageDelayed( 27795961816a768da387f0b5523cf4363ace2044089Nick Chalko MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS); 27895961816a768da387f0b5523cf4363ace2044089Nick Chalko } 27995961816a768da387f0b5523cf4363ace2044089Nick Chalko } catch (IOException e) { 28095961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor"); 28195961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 28295961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 28395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 28565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28695961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_STOP_RECORDING: 28795961816a768da387f0b5523cf4363ace2044089Nick Chalko { 28895961816a768da387f0b5523cf4363ace2044089Nick Chalko if (DEBUG) Log.d(TAG, "Stop recording"); 28995961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mSessionState != STATE_RECORDING) { 29095961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 29195961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 29295961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 29395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorderRunning) { 29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko stopRecorder(); 29665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 29795961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 29895961816a768da387f0b5523cf4363ace2044089Nick Chalko } 29995961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_MONITOR_STORAGE_STATUS: 30095961816a768da387f0b5523cf4363ace2044089Nick Chalko { 30195961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mSessionState != STATE_RECORDING) { 30295961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 30395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 304944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (!mRecordingStorageStatusManager.isStorageSufficient()) { 30595961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mRecorderRunning) { 30695961816a768da387f0b5523cf4363ace2044089Nick Chalko stopRecorder(); 30795961816a768da387f0b5523cf4363ace2044089Nick Chalko } 30895961816a768da387f0b5523cf4363ace2044089Nick Chalko new DeleteRecordingTask().execute(mStorageDir); 30995961816a768da387f0b5523cf4363ace2044089Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 31095961816a768da387f0b5523cf4363ace2044089Nick Chalko reset(); 31195961816a768da387f0b5523cf4363ace2044089Nick Chalko } else { 31295961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendEmptyMessageDelayed( 31395961816a768da387f0b5523cf4363ace2044089Nick Chalko MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); 31495961816a768da387f0b5523cf4363ace2044089Nick Chalko } 31595961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 31695961816a768da387f0b5523cf4363ace2044089Nick Chalko } 31795961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_RELEASE: 31895961816a768da387f0b5523cf4363ace2044089Nick Chalko { 31995961816a768da387f0b5523cf4363ace2044089Nick Chalko // Since release was requested, current recording will be cancelled 32095961816a768da387f0b5523cf4363ace2044089Nick Chalko // without notification. 32165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko reset(); 32295961816a768da387f0b5523cf4363ace2044089Nick Chalko mSourceManager.release(); 32395961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.removeCallbacksAndMessages(null); 32495961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.getLooper().quitSafely(); 32595961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 32695961816a768da387f0b5523cf4363ace2044089Nick Chalko } 32795961816a768da387f0b5523cf4363ace2044089Nick Chalko case MSG_UPDATE_CC_INFO: 32895961816a768da387f0b5523cf4363ace2044089Nick Chalko { 32995961816a768da387f0b5523cf4363ace2044089Nick Chalko Pair<TunerChannel, List<EitItem>> pair = 33095961816a768da387f0b5523cf4363ace2044089Nick Chalko (Pair<TunerChannel, List<EitItem>>) msg.obj; 33195961816a768da387f0b5523cf4363ace2044089Nick Chalko updateCaptionTracks(pair.first, pair.second); 33295961816a768da387f0b5523cf4363ace2044089Nick Chalko return true; 33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Nullable 33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private TunerChannel getChannel(Uri channelUri) { 34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (channelUri == null) { 34165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return null; 34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long channelId; 34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko try { 34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko channelId = ContentUris.parseId(channelUri); 34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } catch (UnsupportedOperationException | NumberFormatException e) { 34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko channelId = CHANNEL_ID_NONE; 34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId); 35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private String getStorageKey() { 35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long prefix = System.currentTimeMillis(); 35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko int suffix = mRandom.nextInt(); 35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix); 35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void reset() { 35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorder != null) { 36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder.release(); 36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder = null; 36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mTunerSource != null) { 36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSourceManager.releaseDataSource(mTunerSource); 36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTunerSource = null; 36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 367633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mDvrStorageManager = null; 36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSessionState = STATE_IDLE; 36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorderRunning = false; 37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean doTune(Uri channelUri) { 373633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) { 37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Tuning was requested from wrong status."); 37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannel = getChannel(channelUri); 37965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannel == null) { 38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel); 38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 383633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } else if (mChannel.isRecordingProhibited()) { 384633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 385633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel); 386633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return false; 38765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 388944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (!mRecordingStorageStatusManager.isStorageSufficient()) { 38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Tuning failed due to insufficient storage."); 39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this); 39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mTunerSource == null) { 395633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko // Retry tuning in this case. 396633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mSessionState = STATE_TUNING; 397633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return true; 39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSessionState = STATE_TUNED; 40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 40165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 40365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean doStartRecording(@Nullable Uri programUri) { 40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mSessionState != STATE_TUNED) { 40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Recording session status abnormal"); 40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 40865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40995961816a768da387f0b5523cf4363ace2044089Nick Chalko mStorageDir = 410944779887775bd950cf1abf348d2df461593f6abLive Channels Team mRecordingStorageStatusManager.isStorageSufficient() 41195961816a768da387f0b5523cf4363ace2044089Nick Chalko ? new File( 412944779887775bd950cf1abf348d2df461593f6abLive Channels Team mRecordingStorageStatusManager.getRecordingRootDataDirectory(), 41395961816a768da387f0b5523cf4363ace2044089Nick Chalko getStorageKey()) 41495961816a768da387f0b5523cf4363ace2044089Nick Chalko : null; 41565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mStorageDir == null) { 41665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 41765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Failed to start recording due to insufficient storage."); 41865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 41965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Since tuning might be happened a while ago, shifts the start position of tuned source. 42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition()); 42265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecordStartTime = System.currentTimeMillis(); 423633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mDvrStorageManager = new DvrStorageManager(mStorageDir, true); 42495961816a768da387f0b5523cf4363ace2044089Nick Chalko mRecorder = 42595961816a768da387f0b5523cf4363ace2044089Nick Chalko new ExoPlayerSampleExtractor( 42695961816a768da387f0b5523cf4363ace2044089Nick Chalko Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true); 42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder.setOnCompletionListener(this, mHandler); 42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mProgramUri = programUri; 42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSessionState = STATE_RECORDING; 43065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorderRunning = true; 431d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mHandler.sendEmptyMessage(MSG_PREPARE_RECODER); 43265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); 43395961816a768da387f0b5523cf4363ace2044089Nick Chalko mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); 43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return true; 43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void stopRecorder() { 43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Do not change session status. 43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorder != null) { 44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder.release(); 44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecordEndTime = System.currentTimeMillis(); 44265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorder = null; 44365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 44465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mRecorderRunning = false; 44565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); 44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.i(TAG, "Recording stopped"); 44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 44865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 449633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private void updateCaptionTracks(TunerChannel channel, List<PsipData.EitItem> items) { 45095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mChannel == null 45195961816a768da387f0b5523cf4363ace2044089Nick Chalko || channel == null 45295961816a768da387f0b5523cf4363ace2044089Nick Chalko || mChannel.compareTo(channel) != 0 45395961816a768da387f0b5523cf4363ace2044089Nick Chalko || items == null 45495961816a768da387f0b5523cf4363ace2044089Nick Chalko || items.isEmpty()) { 455633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return; 456633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 457633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko PsipData.EitItem currentProgram = getCurrentProgram(items); 45895961816a768da387f0b5523cf4363ace2044089Nick Chalko if (currentProgram == null 45995961816a768da387f0b5523cf4363ace2044089Nick Chalko || !currentProgram.hasCaptionTrack() 460a1589bd48e05abbee991e0cdd27fa402a5dc5001Live Channels Team || (mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0)) { 461633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return; 462633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 463633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mCurrenProgram = currentProgram; 464633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks()); 465633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (DEBUG) { 46695961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.d( 46795961816a768da387f0b5523cf4363ace2044089Nick Chalko TAG, 46895961816a768da387f0b5523cf4363ace2044089Nick Chalko "updated " + mCaptionTracks.size() + " caption tracks for " + currentProgram); 469633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 470633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 471633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 472633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private PsipData.EitItem getCurrentProgram(List<PsipData.EitItem> items) { 473633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko for (PsipData.EitItem item : items) { 474633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (mRecordStartTime >= item.getStartTimeUtcMillis() 475633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko && mRecordStartTime < item.getEndTimeUtcMillis()) { 476633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return item; 477633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 478633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 479633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return null; 480633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 481633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 48265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private Program getRecordedProgram() { 48365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ContentResolver resolver = mContext.getContentResolver(); 48465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Uri programUri = mProgramUri; 48565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mProgramUri == null) { 48665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long avg = mRecordStartTime / 2 + mRecordEndTime / 2; 48765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg); 48865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 489944779887775bd950cf1abf348d2df461593f6abLive Channels Team try (Cursor c = resolver.query(programUri, PROGRAM_PROJECTION, null, null, SORT_BY_TIME)) { 490944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (c != null && c.moveToNext()) { 491944779887775bd950cf1abf348d2df461593f6abLive Channels Team Program result = Program.fromCursor(c); 49265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (DEBUG) { 49365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.v(TAG, "Finished query for " + this); 49465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 49565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return result; 49665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 49765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (c == null) { 49865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Unknown query error for " + this); 49965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 500944779887775bd950cf1abf348d2df461593f6abLive Channels Team if (DEBUG) Log.d(TAG, "Can not find program:" + programUri); 50165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return null; 50365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 50795961816a768da387f0b5523cf4363ace2044089Nick Chalko private Uri insertRecordedProgram( 50895961816a768da387f0b5523cf4363ace2044089Nick Chalko Program program, 50995961816a768da387f0b5523cf4363ace2044089Nick Chalko long channelId, 51095961816a768da387f0b5523cf4363ace2044089Nick Chalko String storageUri, 51195961816a768da387f0b5523cf4363ace2044089Nick Chalko long totalBytes, 51295961816a768da387f0b5523cf4363ace2044089Nick Chalko long startTime, 51395961816a768da387f0b5523cf4363ace2044089Nick Chalko long endTime) { 514944779887775bd950cf1abf348d2df461593f6abLive Channels Team ContentValues values = new ContentValues(); 515944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_INPUT_ID, mInputId); 516944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId); 517944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, storageUri); 518944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - startTime); 519944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, totalBytes); 520944779887775bd950cf1abf348d2df461593f6abLive Channels Team // startTime and endTime could be overridden by program's start and end value. 521944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime); 522944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime); 52365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (program != null) { 524944779887775bd950cf1abf348d2df461593f6abLive Channels Team values.putAll(program.toContentValues()); 52565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52695961816a768da387f0b5523cf4363ace2044089Nick Chalko return mContext.getContentResolver() 52795961816a768da387f0b5523cf4363ace2044089Nick Chalko .insert(TvContract.RecordedPrograms.CONTENT_URI, values); 52865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 53065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void onRecordingResult(boolean success, long lastExtractedPositionUs) { 53165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mSessionState != STATE_RECORDING) { 53265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Error notification is not needed. 53365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Recording session status abnormal"); 53465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 53565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mRecorderRunning) { 53765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // In case of recorder not being stopped, because of premature termination of recording. 53865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko stopRecorder(); 53965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 54095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!success 54195961816a768da387f0b5523cf4363ace2044089Nick Chalko && lastExtractedPositionUs 54295961816a768da387f0b5523cf4363ace2044089Nick Chalko < TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) { 543d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new DeleteRecordingTask().execute(mStorageDir); 54465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 54565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.w(TAG, "Recording failed during recording"); 54665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 54765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 54865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.i(TAG, "recording finished " + (success ? "completely" : "partially")); 549633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko long recordEndTime = 550633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko (lastExtractedPositionUs == C.UNKNOWN_TIME_US) 551633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko ? System.currentTimeMillis() 552633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko : mRecordStartTime + lastExtractedPositionUs / 1000; 553633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Uri uri = 554633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko insertRecordedProgram( 555633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko getRecordedProgram(), 556633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mChannel.getChannelId(), 557633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Uri.fromFile(mStorageDir).toString(), 558633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 1024 * 1024, 559633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mRecordStartTime, 560633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko recordEndTime); 56165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (uri == null) { 562d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new DeleteRecordingTask().execute(mStorageDir); 56365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); 56465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Log.e(TAG, "Inserting a recording to DB failed"); 56565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 56665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 567633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks); 56865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mSession.onRecordFinished(uri); 56965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 570d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 571d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static class DeleteRecordingTask extends AsyncTask<File, Void, Void> { 572d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 573d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @Override 574d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public Void doInBackground(File... files) { 575d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (files == null || files.length == 0) { 576d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return null; 577d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 57895961816a768da387f0b5523cf4363ace2044089Nick Chalko for (File file : files) { 579944779887775bd950cf1abf348d2df461593f6abLive Channels Team CommonUtils.deleteDirOrFile(file); 580d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 581d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return null; 582d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 583d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 58465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko} 585