1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/* 2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project 3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License. 6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at 7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software 11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and 14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License. 15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv; 18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver; 20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context; 21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Handler; 22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Message; 23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.IntDef; 24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.NonNull; 25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.Nullable; 26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.VisibleForTesting; 27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log; 28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Range; 29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 3007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.analytics.Tracker; 3148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.SoftPreconditions; 3207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.common.WeakHandler; 3348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.recording.RecordedProgram; 34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Channel; 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.OnCurrentProgramUpdatedListener; 36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Program; 37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.ProgramDataManager; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.ui.TunableTvView; 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.ui.TunableTvView.TimeShiftListener; 40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.AsyncDbTask; 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils; 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.lang.annotation.Retention; 44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.lang.annotation.RetentionPolicy; 45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList; 46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections; 47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Iterator; 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.LinkedList; 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List; 50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Objects; 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Queue; 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit; 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/** 55516b6a59b79ecf950b32f1286f4d4d288484088fNick Chalko * A class which manages the time shift feature in Live TV. It consists of two parts. 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link PlayController} controls the playback such as play/pause, rewind and fast-forward using 57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link TunableTvView} which communicates with TvInputService through 58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link android.media.tv.TvInputService.Session}. 59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link ProgramManager} loads programs of the current channel in the background. 60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class TimeShiftManager { 62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TAG = "TimeShiftManager"; 63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final boolean DEBUG = false; 64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Retention(RetentionPolicy.SOURCE) 66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @IntDef({PLAY_STATUS_PAUSED, PLAY_STATUS_PLAYING}) 67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public @interface PlayStatus {} 68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_STATUS_PAUSED = 0; 69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_STATUS_PLAYING = 1; 70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Retention(RetentionPolicy.SOURCE) 72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @IntDef({PLAY_SPEED_1X, PLAY_SPEED_2X, PLAY_SPEED_3X, PLAY_SPEED_4X, PLAY_SPEED_5X}) 73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public @interface PlaySpeed{} 74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_SPEED_1X = 1; 75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_SPEED_2X = 2; 76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_SPEED_3X = 3; 77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_SPEED_4X = 4; 78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_SPEED_5X = 5; 79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int SHORT_PROGRAM_THRESHOLD_MILLIS = 46 * 60 * 1000; // 46 mins. 81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int[] SHORT_PROGRAM_SPEED_FACTORS = new int[] {2, 4, 12, 48}; 82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int[] LONG_PROGRAM_SPEED_FACTORS = new int[] {2, 8, 32, 128}; 83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Retention(RetentionPolicy.SOURCE) 85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @IntDef({PLAY_DIRECTION_FORWARD, PLAY_DIRECTION_BACKWARD}) 86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public @interface PlayDirection{} 87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_DIRECTION_FORWARD = 0; 88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int PLAY_DIRECTION_BACKWARD = 1; 89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Retention(RetentionPolicy.SOURCE) 91ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @IntDef(flag = true, value = {TIME_SHIFT_ACTION_ID_PLAY, TIME_SHIFT_ACTION_ID_PAUSE, 92ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko TIME_SHIFT_ACTION_ID_REWIND, TIME_SHIFT_ACTION_ID_FAST_FORWARD, 93ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT}) 94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public @interface TimeShiftActionId{} 95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int TIME_SHIFT_ACTION_ID_PLAY = 1; 96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int TIME_SHIFT_ACTION_ID_PAUSE = 1 << 1; 97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int TIME_SHIFT_ACTION_ID_REWIND = 1 << 2; 98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int TIME_SHIFT_ACTION_ID_FAST_FORWARD = 1 << 3; 99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS = 1 << 4; 100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT = 1 << 5; 101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MSG_GET_CURRENT_POSITION = 1000; 103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MSG_PREFETCH_PROGRAM = 1001; 104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1); 105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30); 106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko static final long INVALID_TIME = -1; 108ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko static final long CURRENT_TIME = -2; 109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long PREFETCH_TIME_OFFSET_FROM_PROGRAM_END = TimeUnit.MINUTES.toMillis(1); 110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long PREFETCH_DURATION_FOR_NEXT = TimeUnit.HOURS.toMillis(2); 111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3); 114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the user presses the {@link android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS} button within 117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * this threshold from the program start time, the play position moves to the start of the 118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * previous program. 119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Otherwise, the play position moves to the start of the current program. 120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * This value is specified in the UX document. 121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long PROGRAM_START_TIME_THRESHOLD = TimeUnit.SECONDS.toMillis(3); 123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the current position enters within this range from the recording start time, rewind action 125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * and jump to previous action is disabled. 126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Similarly, if the current position enters within this range from the current system time, 127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * fast forward action and jump to next action is disabled. 128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least. 129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long DISABLE_ACTION_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL; 131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the current position goes out of this range from the recording start time, rewind action 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * and jump to previous action is enabled. 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Similarly, if the current position goes out of this range from the current system time, 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * fast forward action and jump to next action is enabled. 136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Enable threshold and disable threshold must be different because the current position 137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * does not have the continuous value. It changes every one second. 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long ENABLE_ACTION_THRESHOLD = 140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL; 141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * The current position sent from TIS can not be exactly the same as the current system time 143516b6a59b79ecf950b32f1286f4d4d288484088fNick Chalko * due to the elapsed time to pass the message from TIS to Live TV. 144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * So the boundary threshold is necessary. 145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * The same goes for the recording start time. 146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least. 147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long RECORDING_BOUNDARY_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL; 149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final PlayController mPlayController; 151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final ProgramManager mProgramManager; 15207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private final Tracker mTracker; 153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final CurrentPositionMediator mCurrentPositionMediator = new CurrentPositionMediator(); 155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Listener mListener; 157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener; 158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mEnabledActionIds = TIME_SHIFT_ACTION_ID_PLAY | TIME_SHIFT_ACTION_ID_PAUSE 159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko | TIME_SHIFT_ACTION_ID_REWIND | TIME_SHIFT_ACTION_ID_FAST_FORWARD 160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT; 16107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko @TimeShiftActionId 162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mLastActionId = 0; 163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // TODO: Remove these variables once API level 23 is available. 165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final Context mContext; 166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Program mCurrentProgram; 168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // This variable is used to block notification while changing the availability status. 169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean mNotificationEnabled; 170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private final Handler mHandler = new TimeShiftHandler(this); 172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public TimeShiftManager(Context context, TunableTvView tvView, 17407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko ProgramDataManager programDataManager, Tracker tracker, 175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko OnCurrentProgramUpdatedListener onCurrentProgramUpdatedListener) { 176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mContext = context; 177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController = new PlayController(tvView); 178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramManager = new ProgramManager(programDataManager); 17907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker = tracker; 180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mOnCurrentProgramUpdatedListener = onCurrentProgramUpdatedListener; 181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko tvView.setOnScreenBlockedListener(new TunableTvView.OnScreenBlockingChangedListener() { 182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onScreenBlockingChanged(boolean blocked) { 18448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlayController.onAvailabilityChanged(); 185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }); 187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Sets a listener which will receive events from this class. 191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void setListener(Listener listener) { 193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mListener = listener; 194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Checks if the trick play is available for the current channel. 198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean isAvailable() { 20048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho return mPlayController.mAvailable; 201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the current time position in milliseconds. 205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public long getCurrentPositionMs() { 207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mCurrentPositionMediator.mCurrentPositionMs; 208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void setCurrentPositionMs(long currentTimeMs) { 211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.onCurrentPositionChanged(currentTimeMs); 212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the start time of the recording in milliseconds. 216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public long getRecordStartTimeMs() { 218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long oldestProgramStartTime = mProgramManager.getOldestProgramStartTime(); 219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return oldestProgramStartTime == INVALID_TIME ? INVALID_TIME 220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko : mPlayController.mRecordStartTimeMs; 221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 224ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Returns the end time of the recording in milliseconds. 225ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 226ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public long getRecordEndTimeMs() { 227ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (mPlayController.mRecordEndTimeMs == CURRENT_TIME) { 228ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return System.currentTimeMillis(); 229ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } else { 230ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return mPlayController.mRecordEndTimeMs; 231ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 232ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 233ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 234ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Plays the media. 236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void play() { 240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PLAY)) { 241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 24307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(TIME_SHIFT_ACTION_ID_PLAY); 244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLastActionId = TIME_SHIFT_ACTION_ID_PLAY; 245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.play(); 246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Pauses the playback. 251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void pause() { 255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PAUSE)) { 256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLastActionId = TIME_SHIFT_ACTION_ID_PAUSE; 25907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(mLastActionId); 260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.pause(); 261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Toggles the playing and paused state. 266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void togglePlayPause() { 270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.togglePlayPause(); 271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Plays the media in backward direction. The playback speed is increased by 1x each time 275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * this is called. The range of the speed is from 2x to 5x. 276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the playing position is considered the same as the record start time, it does nothing 277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void rewind() { 281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)) { 282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLastActionId = TIME_SHIFT_ACTION_ID_REWIND; 28507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(mLastActionId); 286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.rewind(); 287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Plays the media in forward direction. The playback speed is increased by 1x each time 292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * this is called. The range of the speed is from 2x to 5x. 293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the playing position is the same as the current time, it does nothing. 294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void fastForward() { 298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)) { 299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLastActionId = TIME_SHIFT_ACTION_ID_FAST_FORWARD; 30207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(mLastActionId); 303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.fastForward(); 304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Jumps to the start of the current program. 309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the currently playing position is within 3 seconds 310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes to 311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * the start of the previous program if exists. 312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the playing position is the same as the record start time, it does nothing. 313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void jumpToPrevious() { 317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) { 318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mProgramManager.getProgramAt( 321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD); 322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (program == null) { 323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long seekPosition = 326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Math.max(program.getStartTimeUtcMillis(), mPlayController.mRecordStartTimeMs); 327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS; 32807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(mLastActionId); 329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.seekTo(seekPosition); 330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.onSeekRequested(seekPosition); 331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Jumps to the start of the next program if exists. 336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If there's no next program, it jumps to the current system time and shows the live TV. 337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the playing position is considered the same as the current time, it does nothing. 338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @throws IllegalStateException if the trick play is not available. 340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void jumpToNext() { 342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) { 343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program currentProgram = mProgramManager.getProgramAt( 346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.mCurrentPositionMs); 347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (currentProgram == null) { 348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program nextProgram = mProgramManager.getProgramAt(currentProgram.getEndTimeUtcMillis()); 351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long currentTimeMs = System.currentTimeMillis(); 352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT; 35307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(mLastActionId); 354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (nextProgram == null || nextProgram.getStartTimeUtcMillis() > currentTimeMs) { 355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.seekTo(currentTimeMs); 356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPlayController.isForwarding()) { 357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The current position will be the current system time from now. 358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.mIsPlayOffsetChanged = false; 359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.initialize(currentTimeMs); 360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The current position would not be the current system time. 362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // So need to wait for the correct time from TIS. 363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.onSeekRequested(currentTimeMs); 364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.seekTo(nextProgram.getStartTimeUtcMillis()); 367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMediator.onSeekRequested(nextProgram.getStartTimeUtcMillis()); 368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING. 374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @PlayStatus public int getPlayStatus() { 376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mPlayController.mPlayStatus; 377816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 380816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the displayed playback speed. The value is one of PLAY_SPEED_1X, PLAY_SPEED_2X, 381816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * PLAY_SPEED_3X, PLAY_SPEED_4X and PLAY_SPEED_5X. 382816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @PlaySpeed public int getDisplayedPlaySpeed() { 384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mPlayController.mDisplayedPlaySpeed; 385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the playback speed. The value is PLAY_DIRECTION_FORWARD or PLAY_DIRECTION_BACKWARD. 389816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @PlayDirection public int getPlayDirection() { 391816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mPlayController.mPlayDirection; 392816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 393816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 394816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 395816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the ID of the last action.. 396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @TimeShiftActionId public int getLastActionId() { 398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mLastActionId; 399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Enables or disables the time-shift actions. 403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void enableAction(@TimeShiftActionId int actionId, boolean enable) { 406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int oldEnabledActionIds = mEnabledActionIds; 407816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (enable) { 408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEnabledActionIds |= actionId; 409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 410816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEnabledActionIds &= ~actionId; 411816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 412816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationEnabled && mListener != null 413816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko && oldEnabledActionIds != mEnabledActionIds) { 414816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mListener.onActionEnabledChanged(actionId, enable); 415816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 418816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean isActionEnabled(@TimeShiftActionId int actionId) { 419816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return (mEnabledActionIds & actionId) == actionId; 420816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 421816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 422816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void updateActions() { 423816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (isAvailable()) { 424816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_PLAY, true); 425816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_PAUSE, true); 426816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Rewind action and jump to previous action. 427816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND) 428816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD; 429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean enabled = mCurrentPositionMediator.mCurrentPositionMs 430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - mPlayController.mRecordStartTimeMs > threshold; 431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_REWIND, enabled); 432816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, enabled); 433816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Fast forward action and jump to next action 434816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD) 435816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD; 436ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko enabled = getRecordEndTimeMs() - mCurrentPositionMediator.mCurrentPositionMs 437816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko > threshold; 438816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled); 439816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled); 440816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 441816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_PLAY, false); 442816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_PAUSE, false); 443816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_REWIND, false); 444816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, false); 445816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, false); 446816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko enableAction(TIME_SHIFT_ACTION_ID_PLAY, false); 447816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 448816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 449816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 450816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void updateCurrentProgram() { 451816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program currentProgram = getProgramAt(mCurrentPositionMediator.mCurrentPositionMs); 452816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!Program.isValid(currentProgram)) { 453816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko currentProgram = null; 454816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 455816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!Objects.equals(mCurrentProgram, currentProgram)) { 456816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "Current program has been updated. " + currentProgram); 457816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentProgram = currentProgram; 458816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationEnabled && mOnCurrentProgramUpdatedListener != null) { 459816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Channel channel = mPlayController.getCurrentChannel(); 460816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (channel != null) { 461816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(channel.getId(), 462816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentProgram); 463816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayController.onCurrentProgramChanged(); 464816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 465816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 466816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 467816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 468816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 469816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 47048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Checks whether the TV is playing the recorded content. 47148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho */ 47248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho public boolean isRecordingPlayback() { 47348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho return mPlayController.mRecordingPlayback; 47448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 47548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho 47648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho /** 477816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns {@code true} if the trick play is available and it's playing to the forward direction 478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * with normal speed, otherwise {@code false}. 479816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean isNormalPlaying() { 48148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho return mPlayController.mAvailable 482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko && mPlayController.mPlayStatus == PLAY_STATUS_PLAYING 483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko && mPlayController.mPlayDirection == PLAY_DIRECTION_FORWARD 484816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko && mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X; 485816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 486816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 487816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 488816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Checks if the trick play is available and it's playback status is paused. 489816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 490816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean isPaused() { 49148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho return mPlayController.mAvailable && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED; 492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the program which airs at the given time. 496816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 497816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @NonNull 498816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public Program getProgramAt(long timeMs) { 499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mProgramManager.getProgramAt(timeMs); 500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (program == null) { 501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Guard just in case when the program prefetch handler doesn't work on time. 502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramManager.addDummyProgramsAt(timeMs); 503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko program = mProgramManager.getProgramAt(timeMs); 504816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 505816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return program; 506816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 507816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 508816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onAvailabilityChanged() { 50948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mProgramManager.onAvailabilityChanged(mPlayController.mAvailable, 51048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlayController.mRecordingPlayback ? null : mPlayController.getCurrentChannel(), 51148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlayController.mRecordStartTimeMs); 512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 513816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Availability change notification should be always sent 514816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // even if mNotificationEnabled is false. 515816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mListener != null) { 516816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mListener.onAvailabilityChanged(); 517816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 518816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 519816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 520ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko void onRecordTimeRangeChanged() { 52148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (mPlayController.mAvailable) { 522ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mProgramManager.onRecordTimeRangeChanged(mPlayController.mRecordStartTimeMs, 523ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mPlayController.mRecordEndTimeMs); 524816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 525816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 526816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationEnabled && mListener != null) { 527ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mListener.onRecordTimeRangeChanged(); 528816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 529816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 530816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 531816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onCurrentPositionChanged() { 532816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateActions(); 533816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateCurrentProgram(); 534816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationEnabled && mListener != null) { 535816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mListener.onCurrentPositionChanged(); 536816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 537816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 538816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 539816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onPlayStatusChanged(@PlayStatus int status) { 540816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationEnabled && mListener != null) { 541816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mListener.onPlayStatusChanged(status); 542816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 543816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 544816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 545816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onProgramInfoChanged() { 546816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko updateCurrentProgram(); 547816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationEnabled && mListener != null) { 548816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mListener.onProgramInfoChanged(); 549816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 550816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 552816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 553816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns the current program which airs right now.<p> 554816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 555816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the program is a dummy program, which means there's no program information, 556816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * returns {@code null}. 557816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 558816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 559816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public Program getCurrentProgram() { 560816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (isAvailable()) { 561816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mCurrentProgram; 562816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 563816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 564816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 565816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 566816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int getPlaybackSpeed() { 567816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int[] playbackSpeedList; 568816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (getCurrentProgram() == null || getCurrentProgram().getEndTimeUtcMillis() 569816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - getCurrentProgram().getStartTimeUtcMillis() > SHORT_PROGRAM_THRESHOLD_MILLIS) { 570816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko playbackSpeedList = LONG_PROGRAM_SPEED_FACTORS; 571816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 572816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko playbackSpeedList = SHORT_PROGRAM_SPEED_FACTORS; 573816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 574816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko switch (mPlayController.mDisplayedPlaySpeed) { 575816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_1X: 576816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return 1; 577816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_2X: 578816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return playbackSpeedList[0]; 579816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_3X: 580816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return playbackSpeedList[1]; 581816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_4X: 582816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return playbackSpeedList[2]; 583816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_5X: 584816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return playbackSpeedList[3]; 585816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko default: 586816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.w(TAG, "Unknown displayed play speed is chosen : " 587816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko + mPlayController.mDisplayedPlaySpeed); 588816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return 1; 589816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 590816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 591816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 592816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 593816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * A class which controls the trick play. 594816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 595816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private class PlayController { 596816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final TunableTvView mTvView; 597816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 598816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private long mRecordStartTimeMs; 599ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private long mRecordEndTimeMs; 600816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 601816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @PlayStatus private int mPlayStatus = PLAY_STATUS_PAUSED; 602816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @PlaySpeed private int mDisplayedPlaySpeed = PLAY_SPEED_1X; 603816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @PlayDirection private int mPlayDirection = PLAY_DIRECTION_FORWARD; 604816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mPlaybackSpeed; 60548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho private boolean mAvailable; 60648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho private boolean mRecordingPlayback; 607816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 608816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 609816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Indicates that the trick play is not playing the current time position. 610816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * It is set true when {@link PlayController#pause}, {@link PlayController#rewind}, 611816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link PlayController#fastForward} and {@link PlayController#seekTo} 612816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * is called. 613816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If it is true, the current time is equal to System.currentTimeMillis(). 614816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 615816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean mIsPlayOffsetChanged; 616816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 617816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko PlayController(TunableTvView tvView) { 618816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView = tvView; 619816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.setTimeShiftListener(new TimeShiftListener() { 620816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 621816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onAvailabilityChanged() { 62248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho PlayController.this.onAvailabilityChanged(); 623816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 624816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 625816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 626816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onRecordStartTimeChanged(long recordStartTimeMs) { 627816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mRecordStartTimeMs == recordStartTimeMs) { 628816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 629816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 630816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecordStartTimeMs = recordStartTimeMs; 631ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko TimeShiftManager.this.onRecordTimeRangeChanged(); 632816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 633816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // According to the UX guidelines, the stream should be resumed if the 634816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // recording buffer fills up while paused, which means that the current time 635816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // position is the same as or before the recording start time. 636816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // But, for this application and the TIS, it's an erroneous and confusing 637816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // situation if the current time position is before the recording start time. 638816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // So, we recommend the TIS to keep the current time position greater than or 639816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // equal to the recording start time. 640816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // And here, we assume that the buffer is full if the current time position 641816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // is nearly equal to the recording start time. 642816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPlayStatus == PLAY_STATUS_PAUSED && 643816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko getCurrentPositionMs() - mRecordStartTimeMs 644816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko < RECORDING_BOUNDARY_THRESHOLD) { 645816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.play(); 646816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 647816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 648816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }); 649816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 650816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 65148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho void onAvailabilityChanged() { 65248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho boolean newAvailable = mTvView.isTimeShiftAvailable() && !mTvView.isScreenBlocked(); 65348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (mAvailable == newAvailable) { 65448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho return; 65548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 65648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mAvailable = newAvailable; 65748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // Do not send the notifications while the availability is changing, 65848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // because the variables are in the intermediate state. 65948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // For example, the current program can be null. 66048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mNotificationEnabled = false; 66148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mDisplayedPlaySpeed = PLAY_SPEED_1X; 66248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlaybackSpeed = 1; 66348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlayDirection = PLAY_DIRECTION_FORWARD; 66448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mRecordingPlayback = mTvView.isRecordingPlayback(); 66548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (mRecordingPlayback) { 66648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho RecordedProgram recordedProgram = mTvView.getPlayingRecordedProgram(); 66748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho SoftPreconditions.checkNotNull(recordedProgram); 66848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mIsPlayOffsetChanged = true; 66948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mRecordStartTimeMs = 0; 67048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mRecordEndTimeMs = recordedProgram.getDurationMillis(); 67148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } else { 67248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mIsPlayOffsetChanged = false; 67348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mRecordStartTimeMs = System.currentTimeMillis(); 67448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mRecordEndTimeMs = CURRENT_TIME; 67548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 67648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mCurrentPositionMediator.initialize(mRecordStartTimeMs); 67748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mHandler.removeMessages(MSG_GET_CURRENT_POSITION); 67848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho 67948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (mAvailable) { 68048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // When the media availability message has come. 68148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlayController.setPlayStatus(PLAY_STATUS_PLAYING); 68248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION, 68348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho REQUEST_CURRENT_POSITION_INTERVAL); 68448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } else { 68548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // When the tune command is sent. 68648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPlayController.setPlayStatus(PLAY_STATUS_PAUSED); 68748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 68848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho TimeShiftManager.this.onAvailabilityChanged(); 68948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mNotificationEnabled = true; 690816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 691816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 692816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void handleGetCurrentPosition() { 693816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mIsPlayOffsetChanged) { 694ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko long currentTimeMs = mRecordEndTimeMs == CURRENT_TIME ? System.currentTimeMillis() 695ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko : mRecordEndTimeMs; 696816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long currentPositionMs = Math.max( 697816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs), 698816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecordStartTimeMs); 699816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean isCurrentTime = 700816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD; 701816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long newCurrentPositionMs; 702816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (isCurrentTime && isForwarding()) { 703816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // It's playing forward and the current playing position reached 704816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // the current system time. i.e. The live stream is played. 705816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Therefore no need to call TvView.timeshiftGetCurrentPositionMs 706816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // any more. 707816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko newCurrentPositionMs = currentTimeMs; 708816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mIsPlayOffsetChanged = false; 709816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mDisplayedPlaySpeed > PLAY_SPEED_1X) { 710816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.play(); 711816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 712816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 713816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko newCurrentPositionMs = currentPositionMs; 714816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean isRecordStartTime = currentPositionMs - mRecordStartTimeMs 715816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko < RECORDING_BOUNDARY_THRESHOLD; 716816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (isRecordStartTime && isRewinding()) { 717816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.play(); 718816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 719816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 720816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setCurrentPositionMs(newCurrentPositionMs); 721816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 722816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setCurrentPositionMs(System.currentTimeMillis()); 723816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onCurrentPositionChanged(); 724816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 725816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Need to send message here just in case there is no or invalid response 726816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // for the current time position request from TIS. 727816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION, 728816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko REQUEST_CURRENT_POSITION_INTERVAL); 729816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 730816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 731816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void play() { 732816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_1X; 733816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlaybackSpeed = 1; 734816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayDirection = PLAY_DIRECTION_FORWARD; 735816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.timeshiftPlay(); 736816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setPlayStatus(PLAY_STATUS_PLAYING); 737816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 738816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 739816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void pause() { 740816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_1X; 741816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlaybackSpeed = 1; 742816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.timeshiftPause(); 743816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setPlayStatus(PLAY_STATUS_PAUSED); 744816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mIsPlayOffsetChanged = true; 745816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 746816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 747816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void togglePlayPause() { 748816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPlayStatus == PLAY_STATUS_PAUSED) { 749816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko play(); 75007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(TIME_SHIFT_ACTION_ID_PLAY); 751816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 752816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko pause(); 75307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mTracker.sendTimeShiftAction(TIME_SHIFT_ACTION_ID_PAUSE); 754816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 755816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 756816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 757816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void rewind() { 758816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPlayDirection == PLAY_DIRECTION_BACKWARD) { 759816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko increaseDisplayedPlaySpeed(); 760816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 761816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_2X; 762816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 763816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayDirection = PLAY_DIRECTION_BACKWARD; 764816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlaybackSpeed = getPlaybackSpeed(); 765816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.timeshiftRewind(mPlaybackSpeed); 766816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setPlayStatus(PLAY_STATUS_PLAYING); 767816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mIsPlayOffsetChanged = true; 768816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 769816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 770816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void fastForward() { 771816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPlayDirection == PLAY_DIRECTION_FORWARD) { 772816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko increaseDisplayedPlaySpeed(); 773816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 774816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_2X; 775816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 776816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayDirection = PLAY_DIRECTION_FORWARD; 777816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlaybackSpeed = getPlaybackSpeed(); 778816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.timeshiftFastForward(mPlaybackSpeed); 779816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setPlayStatus(PLAY_STATUS_PLAYING); 780816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mIsPlayOffsetChanged = true; 781816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 782816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 783816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 784816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Moves to the specified time. 785816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 786816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void seekTo(long timeMs) { 787ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mTvView.timeshiftSeekTo(Math.min(mRecordEndTimeMs == CURRENT_TIME 788ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko ? System.currentTimeMillis() : mRecordEndTimeMs, 789ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Math.max(mRecordStartTimeMs, timeMs))); 790816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mIsPlayOffsetChanged = true; 791816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 792816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 793816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onCurrentProgramChanged() { 794816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Update playback speed 795816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mDisplayedPlaySpeed == PLAY_SPEED_1X) { 796816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 797816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 798816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int playbackSpeed = getPlaybackSpeed(); 799816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (playbackSpeed != mPlaybackSpeed) { 800816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlaybackSpeed = playbackSpeed; 801816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPlayDirection == PLAY_DIRECTION_FORWARD) { 802816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.timeshiftFastForward(mPlaybackSpeed); 803816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 804816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mTvView.timeshiftRewind(mPlaybackSpeed); 805816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 806816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 807816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 808816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 809816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void increaseDisplayedPlaySpeed() { 810816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko switch (mDisplayedPlaySpeed) { 811816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_1X: 812816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_2X; 813816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 814816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_2X: 815816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_3X; 816816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 817816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_3X: 818816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_4X; 819816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 820816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PLAY_SPEED_4X: 821816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDisplayedPlaySpeed = PLAY_SPEED_5X; 822816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 823816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 824816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 825816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 826816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void setPlayStatus(@PlayStatus int status) { 827816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPlayStatus = status; 828816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onPlayStatusChanged(status); 829816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 830816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 831816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean isForwarding() { 832816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_FORWARD; 833816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 834816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 835816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean isRewinding() { 836816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_BACKWARD; 837816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 838816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 839816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Channel getCurrentChannel() { 840816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mTvView.getCurrentChannel(); 841816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 842816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 843816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 844816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private class ProgramManager { 845816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final ProgramDataManager mProgramDataManager; 846816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Channel mChannel; 847816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final List<Program> mPrograms = new ArrayList<>(); 848816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final Queue<Range<Long>> mProgramLoadQueue = new LinkedList<>(); 849816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private LoadProgramsForCurrentChannelTask mProgramLoadTask = null; 85048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho private int mEmptyFetchCount = 0; 851816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 852816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ProgramManager(ProgramDataManager programDataManager) { 853816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramDataManager = programDataManager; 854816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 855816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 856816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) { 85748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (DEBUG) { 85848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho Log.d(TAG, "onAvailabilityChanged(" + available + "+," + channel + ", " 85948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho + currentPositionMs + ")"); 86048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 86148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho 862816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadQueue.clear(); 863816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mProgramLoadTask != null) { 864816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadTask.cancel(true); 865816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 866816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.removeMessages(MSG_PREFETCH_PROGRAM); 867816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.clear(); 86848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mEmptyFetchCount = 0; 869816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel = channel; 870816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (channel == null || channel.isPassthrough()) { 871816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 872816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 873816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (available) { 874816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mProgramDataManager.getCurrentProgram(channel.getId()); 875816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long prefetchStartTimeMs; 876816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (program != null) { 877816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.add(program); 878816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko prefetchStartTimeMs = program.getEndTimeUtcMillis(); 879816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 880816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko prefetchStartTimeMs = Utils.floorTime(currentPositionMs, 881816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MAX_DUMMY_PROGRAM_DURATION); 882816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 883816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Create dummy program 884816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll(createDummyPrograms(prefetchStartTimeMs, 885816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko currentPositionMs + PREFETCH_DURATION_FOR_NEXT)); 886816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko schedulePrefetchPrograms(); 887816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onProgramInfoChanged(); 888816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 889816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 890816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 891ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko void onRecordTimeRangeChanged(long startTimeMs, long endTimeMs) { 892816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mChannel == null || mChannel.isPassthrough()) { 893816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 894816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 895ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (endTimeMs == CURRENT_TIME) { 896ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko endTimeMs = System.currentTimeMillis(); 897ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 898816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 899816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION); 900816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean needToLoad = addDummyPrograms(fetchStartTimeMs, 901ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko endTimeMs + PREFETCH_DURATION_FOR_NEXT); 902816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (needToLoad) { 903ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Range<Long> period = Range.create(fetchStartTimeMs, endTimeMs); 904816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadQueue.add(period); 905816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startTaskIfNeeded(); 906816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 907816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 908816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 909816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void startTaskIfNeeded() { 910816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mProgramLoadQueue.isEmpty()) { 911816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 912816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 913816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mProgramLoadTask == null || mProgramLoadTask.isCancelled()) { 914816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startNext(); 915816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 916816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko switch (mProgramLoadTask.getStatus()) { 917816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case PENDING: 918816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mProgramLoadTask.overlaps(mProgramLoadQueue)) { 919816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mProgramLoadTask.cancel(true)) { 920816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadQueue.add(mProgramLoadTask.getPeriod()); 921816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadTask = null; 922816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startNext(); 923816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 924816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 925816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 926816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case RUNNING: 927816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Remove pending task fully satisfied by the current 928816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Range<Long> current = mProgramLoadTask.getPeriod(); 929816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Iterator<Range<Long>> i = mProgramLoadQueue.iterator(); 930816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while (i.hasNext()) { 931816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Range<Long> r = i.next(); 932816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (current.contains(r)) { 933816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko i.remove(); 934816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 935816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 936816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 937816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko case FINISHED: 938816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The task should have already cleared it self, clear and restart anyways. 939816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.w(TAG, mProgramLoadTask + " is finished, but was not cleared"); 940816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startNext(); 941816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 942816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 943816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 944816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 945816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 946816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void startNext() { 947816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadTask = null; 948816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mProgramLoadQueue.isEmpty()) { 949816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 950816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 951816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 952816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Range<Long> next = mProgramLoadQueue.poll(); 953816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Extend next to include any overlapping Ranges. 954816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Iterator<Range<Long>> i = mProgramLoadQueue.iterator(); 955816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while(i.hasNext()) { 956816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Range<Long> r = i.next(); 957816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if(next.contains(r.getLower()) || next.contains(r.getUpper())){ 958816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko i.remove(); 959816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko next = next.extend(r); 960816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 961816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 962816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mChannel != null) { 963816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadTask = new LoadProgramsForCurrentChannelTask( 964816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mContext.getContentResolver(), next); 965816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadTask.executeOnDbThread(); 966816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 967816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 968816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 969816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void addDummyProgramsAt(long timeMs) { 970816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko addDummyPrograms(timeMs, timeMs + PREFETCH_DURATION_FOR_NEXT); 971816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 972816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 973816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean addDummyPrograms(Range<Long> period) { 974816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return addDummyPrograms(period.getLower(), period.getUpper()); 975816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 976816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 977816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean addDummyPrograms(long startTimeMs, long endTimeMs) { 978816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean added = false; 979816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPrograms.isEmpty()) { 980816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Insert dummy program. 981816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll(createDummyPrograms(startTimeMs, endTimeMs)); 982816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 983816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 984816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Insert dummy program to the head of the list if needed. 985816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program firstProgram = mPrograms.get(0); 986816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (startTimeMs < firstProgram.getStartTimeUtcMillis()) { 987816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!firstProgram.isValid()) { 988816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Already the firstProgram is dummy. 989816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.remove(0); 990816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll(0, 991816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis())); 992816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 993816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll(0, 994816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis())); 995816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 996816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko added = true; 997816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 998816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Insert dummy program to the tail of the list if needed. 999816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program lastProgram = mPrograms.get(mPrograms.size() - 1); 1000816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (endTimeMs > lastProgram.getEndTimeUtcMillis()) { 1001816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!lastProgram.isValid()) { 1002816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Already the lastProgram is dummy. 1003816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.remove(mPrograms.size() - 1); 1004816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll( 1005816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createDummyPrograms(lastProgram.getStartTimeUtcMillis(), endTimeMs)); 1006816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 1007816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll( 1008816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createDummyPrograms(lastProgram.getEndTimeUtcMillis(), endTimeMs)); 1009816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1010816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko added = true; 1011816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1012816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Insert dummy programs if the holes exist in the list. 1013816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 1; i < mPrograms.size(); ++i) { 1014816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long endOfPrevious = mPrograms.get(i - 1).getEndTimeUtcMillis(); 1015816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long startOfCurrent = mPrograms.get(i).getStartTimeUtcMillis(); 1016816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (startOfCurrent > endOfPrevious) { 1017816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Program> dummyPrograms = 1018816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createDummyPrograms(endOfPrevious, startOfCurrent); 1019816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll(i, dummyPrograms); 1020816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko i += dummyPrograms.size(); 1021816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko added = true; 1022816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1023816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1024816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return added; 1025816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1026816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1027816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void removeDummyPrograms() { 1028816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < mPrograms.size(); ++i) { 1029816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mPrograms.get(i); 1030816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!program.isValid()) { 1031816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.remove(i--); 1032816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1033816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1034816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1035816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1036816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void removeOverlappedPrograms(List<Program> loadedPrograms) { 1037816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPrograms.size() == 0) { 1038816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 1039816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1040816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mPrograms.get(0); 1041816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0, j = 0; i < mPrograms.size() && j < loadedPrograms.size(); ++j) { 1042816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program loadedProgram = loadedPrograms.get(j); 1043816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Skip previous programs. 1044816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while (program.getEndTimeUtcMillis() < loadedProgram.getStartTimeUtcMillis()) { 1045816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Reached end of mPrograms. 1046816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (++i == mPrograms.size()) { 1047816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 1048816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1049816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko program = mPrograms.get(i); 1050816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1051816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Remove overlapped programs. 1052816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while (program.getStartTimeUtcMillis() < loadedProgram.getEndTimeUtcMillis() 1053816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko && program.getEndTimeUtcMillis() > loadedProgram.getStartTimeUtcMillis()) { 1054816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.remove(i); 1055816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (i >= mPrograms.size()) { 1056816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 1057816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1058816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko program = mPrograms.get(i); 1059816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1060816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1061816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1062816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1063816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Returns a list of dummy programs. 1064816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The maximum duration of a dummy program is {@link MAX_DUMMY_PROGRAM_DURATION}. 1065816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // So if the duration ({@code endTimeMs}-{@code startTimeMs}) is greater than the duration, 1066816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // we need to create multiple dummy programs. 1067816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The reason of the limitation of the duration is because we want the trick play viewer 1068816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most 1069816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // for a dummy program. 1070816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private List<Program> createDummyPrograms(long startTimeMs, long endTimeMs) { 1071816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (startTimeMs >= endTimeMs) { 1072816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return Collections.emptyList(); 1073816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1074816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Program> programs = new ArrayList<>(); 1075816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long start = startTimeMs; 1076816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION); 1077816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while (end < endTimeMs) { 1078816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko programs.add(new Program.Builder() 1079816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .setStartTimeUtcMillis(start) 1080816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .setEndTimeUtcMillis(end) 1081816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .build()); 1082816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko start = end; 1083816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko end += MAX_DUMMY_PROGRAM_DURATION; 1084816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1085816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko programs.add(new Program.Builder() 1086816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .setStartTimeUtcMillis(start) 1087816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .setEndTimeUtcMillis(endTimeMs) 1088816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .build()); 1089816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return programs; 1090816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1091816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1092816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program getProgramAt(long timeMs) { 1093816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return getProgramAt(timeMs, 0, mPrograms.size() - 1); 1094816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1095816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1096816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Program getProgramAt(long timeMs, int start, int end) { 1097816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (start > end) { 1098816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 1099816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int mid = (start + end) / 2; 1101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mPrograms.get(mid); 1102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (program.getStartTimeUtcMillis() > timeMs) { 1103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return getProgramAt(timeMs, start, mid - 1); 1104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else if (program.getEndTimeUtcMillis() <= timeMs) { 1105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return getProgramAt(timeMs, mid+1, end); 1106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 1107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return program; 1108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private long getOldestProgramStartTime() { 1112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPrograms.isEmpty()) { 1113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return INVALID_TIME; 1114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mPrograms.get(0).getStartTimeUtcMillis(); 1116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Program getLastValidProgram() { 1119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = mPrograms.size() - 1; i >= 0; --i) { 1120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program program = mPrograms.get(i); 1121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (program.isValid()) { 1122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return program; 1123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 1126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void schedulePrefetchPrograms() { 1129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "Scheduling prefetching programs."); 1130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mHandler.hasMessages(MSG_PREFETCH_PROGRAM)) { 1131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 1132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program lastValidProgram = getLastValidProgram(); 1134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram); 113548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho final long delay; 1136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (lastValidProgram != null) { 113748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho delay = lastValidProgram.getEndTimeUtcMillis() 1138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis(); 1139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 114048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // Since there might not be any program data delay the retry 5 seconds, 114148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // then 30 seconds then 5 minutes 114248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho switch (mEmptyFetchCount) { 114348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho case 0: 114448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho delay = 0; 114548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho break; 114648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho case 1: 114748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho delay = TimeUnit.SECONDS.toMillis(5); 114848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho break; 114948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho case 2: 115048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho delay = TimeUnit.SECONDS.toMillis(30); 115148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho break; 115248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho default: 115348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho delay = TimeUnit.MINUTES.toMillis(5); 115448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho break; 115548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 115648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (DEBUG) { 115748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho Log.d(TAG, 115848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho "No last valid program. Already tried " + mEmptyFetchCount + " times"); 115948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 1160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 116148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mHandler.sendEmptyMessageDelayed(MSG_PREFETCH_PROGRAM, delay); 116248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (DEBUG) Log.d(TAG, "Scheduling with " + delay + "(ms) delays."); 1163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 116548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho // Prefecth programs within PREFETCH_DURATION_FOR_NEXT from now. 1166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void prefetchPrograms() { 1167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long startTimeMs; 1168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program lastValidProgram = getLastValidProgram(); 1169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (lastValidProgram == null) { 1170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startTimeMs = System.currentTimeMillis(); 1171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 1172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startTimeMs = lastValidProgram.getEndTimeUtcMillis(); 1173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT; 117548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (startTimeMs <= endTimeMs) { 117648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (DEBUG) { 117748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs) 117848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho + ", endTime=" + Utils.toTimeString(endTimeMs) + "}"); 117948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 118048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs)); 1181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startTaskIfNeeded(); 1183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private class LoadProgramsForCurrentChannelTask 1186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko extends AsyncDbTask.LoadProgramsForChannelTask { 1187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public LoadProgramsForCurrentChannelTask(ContentResolver contentResolver, 1189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Range<Long> period) { 1190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko super(contentResolver, mChannel.getId(), period); 1191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 1194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko protected void onPostExecute(List<Program> programs) { 1195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 1196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.d(TAG, "Programs are loaded {channelId=" + mChannelId + 1197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ", from=" + Utils.toTimeString(mPeriod.getLower()) + 1198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ", to=" + Utils.toTimeString(mPeriod.getUpper()) + 1199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko "}"); 1200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko //remove pending tasks that are fully satisfied by this query. 1202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Iterator<Range<Long>> it = mProgramLoadQueue.iterator(); 1203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while (it.hasNext()) { 1204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Range<Long> r = it.next(); 1205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPeriod.contains(r)) { 1206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko it.remove(); 1207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 120948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (programs == null || programs.isEmpty()) { 121048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mEmptyFetchCount++; 1211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (addDummyPrograms(mPeriod)) { 1212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onProgramInfoChanged(); 1213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko schedulePrefetchPrograms(); 1215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startNextLoadingIfNeeded(); 1216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 1217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 121848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mEmptyFetchCount = 0; 121948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if(!mPrograms.isEmpty()) { 122048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho removeDummyPrograms(); 122148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho removeOverlappedPrograms(programs); 122248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho Program loadedProgram = programs.get(0); 122348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) { 122448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho Program program = mPrograms.get(i); 122548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho while (program.getStartTimeUtcMillis() > loadedProgram 122648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho .getStartTimeUtcMillis()) { 122748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho mPrograms.add(i++, loadedProgram); 122848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho programs.remove(0); 122948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho if (programs.isEmpty()) { 123048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho break; 123148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho } 123248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho loadedProgram = programs.get(0); 1233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mPrograms.addAll(programs); 1237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko addDummyPrograms(mPeriod); 1238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onProgramInfoChanged(); 1239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko schedulePrefetchPrograms(); 1240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startNextLoadingIfNeeded(); 1241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 1244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko protected void onCancelled(List<Program> programs) { 1245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 1246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.d(TAG, "Program loading has been canceled {channelId=" + (mChannel == null 1247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ? "null" : mChannelId) + ", from=" + Utils 1248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .toTimeString(mPeriod.getLower()) + ", to=" + Utils 1249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .toTimeString(mPeriod.getUpper()) + "}"); 1250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startNextLoadingIfNeeded(); 1252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void startNextLoadingIfNeeded() { 1255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mProgramLoadTask = null; 1256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Need to post to handler, because the task is still running. 1257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.post(new Runnable() { 1258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 1259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void run() { 1260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startTaskIfNeeded(); 1261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }); 1263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean overlaps(Queue<Range<Long>> programLoadQueue) { 1266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (Range<Long> r : programLoadQueue) { 1267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mPeriod.contains(r.getLower()) || mPeriod.contains(r.getUpper())) { 1268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 1269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 1272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 1277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final class CurrentPositionMediator { 1278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long mCurrentPositionMs; 1279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long mSeekRequestTimeMs; 1280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void initialize(long timeMs) { 1282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mSeekRequestTimeMs = INVALID_TIME; 1283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMs = timeMs; 1284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onCurrentPositionChanged(); 1285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onSeekRequested(long seekTimeMs) { 1288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mSeekRequestTimeMs = System.currentTimeMillis(); 1289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMs = seekTimeMs; 1290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onCurrentPositionChanged(); 1291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onCurrentPositionChanged(long currentPositionMs) { 1294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mSeekRequestTimeMs == INVALID_TIME) { 1295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMs = currentPositionMs; 1296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onCurrentPositionChanged(); 1297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 1298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long currentTimeMs = System.currentTimeMillis(); 1300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean isValid = Math.abs(currentPositionMs - mCurrentPositionMs) < REQUEST_TIMEOUT_MS; 1301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean isTimeout = currentTimeMs > mSeekRequestTimeMs + REQUEST_TIMEOUT_MS; 1302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (isValid || isTimeout) { 1303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko initialize(currentPositionMs); 1304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 1305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (getPlayStatus() == PLAY_STATUS_PLAYING) { 1306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (getPlayDirection() == PLAY_DIRECTION_FORWARD) { 1307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMs += (currentTimeMs - mSeekRequestTimeMs) 1308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * getPlaybackSpeed(); 1309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 1310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentPositionMs -= (currentTimeMs - mSeekRequestTimeMs) 1311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * getPlaybackSpeed(); 1312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeShiftManager.this.onCurrentPositionChanged(); 1315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 1318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * The listener used to receive the events by the time-shift manager 1321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public interface Listener { 1323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when the availability of the time-shift for the current channel has been changed. 1325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * If the time shift is available, {@link TimeShiftManager#getRecordStartTimeMs} should 1326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * return the valid time. 1327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1328816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onAvailabilityChanged(); 1329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and 1332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link #PLAY_STATUS_PAUSED} 1333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 1334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @param status The new play state. 1335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onPlayStatusChanged(int status); 1337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when the recordStartTime has been changed. 1340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1341ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko void onRecordTimeRangeChanged(); 1342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when the current position is changed. 1345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onCurrentPositionChanged(); 1347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when the program information is updated. 1350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onProgramInfoChanged(); 1352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 1354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when an action becomes enabled or disabled. 1355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 1356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled); 1357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 135807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 135907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private static class TimeShiftHandler extends WeakHandler<TimeShiftManager> { 136007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko public TimeShiftHandler(TimeShiftManager ref) { 136107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko super(ref); 136207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 136307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 136407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko @Override 136507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko public void handleMessage(Message msg, @NonNull TimeShiftManager timeShiftManager) { 136607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko switch (msg.what) { 136707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko case MSG_GET_CURRENT_POSITION: 136807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko timeShiftManager.mPlayController.handleGetCurrentPosition(); 136907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko break; 137007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko case MSG_PREFETCH_PROGRAM: 137107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko timeShiftManager.mProgramManager.prefetchPrograms(); 137207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko break; 137307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 137407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 137507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 1376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 1377