1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2; 17 18 import android.util.Pair; 19 import com.google.android.exoplayer2.source.ShuffleOrder; 20 import com.google.android.exoplayer2.util.Assertions; 21 22 /** Abstract base class for the concatenation of one or more {@link Timeline}s. */ 23 public abstract class AbstractConcatenatedTimeline extends Timeline { 24 25 private final int childCount; 26 private final ShuffleOrder shuffleOrder; 27 private final boolean isAtomic; 28 29 /** 30 * Returns UID of child timeline from a concatenated period UID. 31 * 32 * @param concatenatedUid UID of a period in a concatenated timeline. 33 * @return UID of the child timeline this period belongs to. 34 */ 35 @SuppressWarnings("nullness:return.type.incompatible") getChildTimelineUidFromConcatenatedUid(Object concatenatedUid)36 public static Object getChildTimelineUidFromConcatenatedUid(Object concatenatedUid) { 37 return ((Pair<?, ?>) concatenatedUid).first; 38 } 39 40 /** 41 * Returns UID of the period in the child timeline from a concatenated period UID. 42 * 43 * @param concatenatedUid UID of a period in a concatenated timeline. 44 * @return UID of the period in the child timeline. 45 */ 46 @SuppressWarnings("nullness:return.type.incompatible") getChildPeriodUidFromConcatenatedUid(Object concatenatedUid)47 public static Object getChildPeriodUidFromConcatenatedUid(Object concatenatedUid) { 48 return ((Pair<?, ?>) concatenatedUid).second; 49 } 50 51 /** 52 * Returns a concatenated UID for a period or window in a child timeline. 53 * 54 * @param childTimelineUid UID of the child timeline this period or window belongs to. 55 * @param childPeriodOrWindowUid UID of the period or window in the child timeline. 56 * @return UID of the period or window in the concatenated timeline. 57 */ getConcatenatedUid(Object childTimelineUid, Object childPeriodOrWindowUid)58 public static Object getConcatenatedUid(Object childTimelineUid, Object childPeriodOrWindowUid) { 59 return Pair.create(childTimelineUid, childPeriodOrWindowUid); 60 } 61 62 /** 63 * Sets up a concatenated timeline with a shuffle order of child timelines. 64 * 65 * @param isAtomic Whether the child timelines shall be treated as atomic, i.e., treated as a 66 * single item for repeating and shuffling. 67 * @param shuffleOrder A shuffle order of child timelines. The number of child timelines must 68 * match the number of elements in the shuffle order. 69 */ AbstractConcatenatedTimeline(boolean isAtomic, ShuffleOrder shuffleOrder)70 public AbstractConcatenatedTimeline(boolean isAtomic, ShuffleOrder shuffleOrder) { 71 this.isAtomic = isAtomic; 72 this.shuffleOrder = shuffleOrder; 73 this.childCount = shuffleOrder.getLength(); 74 } 75 76 @Override getNextWindowIndex( int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)77 public int getNextWindowIndex( 78 int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) { 79 if (isAtomic) { 80 // Adapt repeat and shuffle mode to atomic concatenation. 81 repeatMode = repeatMode == Player.REPEAT_MODE_ONE ? Player.REPEAT_MODE_ALL : repeatMode; 82 shuffleModeEnabled = false; 83 } 84 // Find next window within current child. 85 int childIndex = getChildIndexByWindowIndex(windowIndex); 86 int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); 87 int nextWindowIndexInChild = 88 getTimelineByChildIndex(childIndex) 89 .getNextWindowIndex( 90 windowIndex - firstWindowIndexInChild, 91 repeatMode == Player.REPEAT_MODE_ALL ? Player.REPEAT_MODE_OFF : repeatMode, 92 shuffleModeEnabled); 93 if (nextWindowIndexInChild != C.INDEX_UNSET) { 94 return firstWindowIndexInChild + nextWindowIndexInChild; 95 } 96 // If not found, find first window of next non-empty child. 97 int nextChildIndex = getNextChildIndex(childIndex, shuffleModeEnabled); 98 while (nextChildIndex != C.INDEX_UNSET && getTimelineByChildIndex(nextChildIndex).isEmpty()) { 99 nextChildIndex = getNextChildIndex(nextChildIndex, shuffleModeEnabled); 100 } 101 if (nextChildIndex != C.INDEX_UNSET) { 102 return getFirstWindowIndexByChildIndex(nextChildIndex) 103 + getTimelineByChildIndex(nextChildIndex).getFirstWindowIndex(shuffleModeEnabled); 104 } 105 // If not found, this is the last window. 106 if (repeatMode == Player.REPEAT_MODE_ALL) { 107 return getFirstWindowIndex(shuffleModeEnabled); 108 } 109 return C.INDEX_UNSET; 110 } 111 112 @Override getPreviousWindowIndex( int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)113 public int getPreviousWindowIndex( 114 int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) { 115 if (isAtomic) { 116 // Adapt repeat and shuffle mode to atomic concatenation. 117 repeatMode = repeatMode == Player.REPEAT_MODE_ONE ? Player.REPEAT_MODE_ALL : repeatMode; 118 shuffleModeEnabled = false; 119 } 120 // Find previous window within current child. 121 int childIndex = getChildIndexByWindowIndex(windowIndex); 122 int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); 123 int previousWindowIndexInChild = 124 getTimelineByChildIndex(childIndex) 125 .getPreviousWindowIndex( 126 windowIndex - firstWindowIndexInChild, 127 repeatMode == Player.REPEAT_MODE_ALL ? Player.REPEAT_MODE_OFF : repeatMode, 128 shuffleModeEnabled); 129 if (previousWindowIndexInChild != C.INDEX_UNSET) { 130 return firstWindowIndexInChild + previousWindowIndexInChild; 131 } 132 // If not found, find last window of previous non-empty child. 133 int previousChildIndex = getPreviousChildIndex(childIndex, shuffleModeEnabled); 134 while (previousChildIndex != C.INDEX_UNSET 135 && getTimelineByChildIndex(previousChildIndex).isEmpty()) { 136 previousChildIndex = getPreviousChildIndex(previousChildIndex, shuffleModeEnabled); 137 } 138 if (previousChildIndex != C.INDEX_UNSET) { 139 return getFirstWindowIndexByChildIndex(previousChildIndex) 140 + getTimelineByChildIndex(previousChildIndex).getLastWindowIndex(shuffleModeEnabled); 141 } 142 // If not found, this is the first window. 143 if (repeatMode == Player.REPEAT_MODE_ALL) { 144 return getLastWindowIndex(shuffleModeEnabled); 145 } 146 return C.INDEX_UNSET; 147 } 148 149 @Override getLastWindowIndex(boolean shuffleModeEnabled)150 public int getLastWindowIndex(boolean shuffleModeEnabled) { 151 if (childCount == 0) { 152 return C.INDEX_UNSET; 153 } 154 if (isAtomic) { 155 shuffleModeEnabled = false; 156 } 157 // Find last non-empty child. 158 int lastChildIndex = shuffleModeEnabled ? shuffleOrder.getLastIndex() : childCount - 1; 159 while (getTimelineByChildIndex(lastChildIndex).isEmpty()) { 160 lastChildIndex = getPreviousChildIndex(lastChildIndex, shuffleModeEnabled); 161 if (lastChildIndex == C.INDEX_UNSET) { 162 // All children are empty. 163 return C.INDEX_UNSET; 164 } 165 } 166 return getFirstWindowIndexByChildIndex(lastChildIndex) 167 + getTimelineByChildIndex(lastChildIndex).getLastWindowIndex(shuffleModeEnabled); 168 } 169 170 @Override getFirstWindowIndex(boolean shuffleModeEnabled)171 public int getFirstWindowIndex(boolean shuffleModeEnabled) { 172 if (childCount == 0) { 173 return C.INDEX_UNSET; 174 } 175 if (isAtomic) { 176 shuffleModeEnabled = false; 177 } 178 // Find first non-empty child. 179 int firstChildIndex = shuffleModeEnabled ? shuffleOrder.getFirstIndex() : 0; 180 while (getTimelineByChildIndex(firstChildIndex).isEmpty()) { 181 firstChildIndex = getNextChildIndex(firstChildIndex, shuffleModeEnabled); 182 if (firstChildIndex == C.INDEX_UNSET) { 183 // All children are empty. 184 return C.INDEX_UNSET; 185 } 186 } 187 return getFirstWindowIndexByChildIndex(firstChildIndex) 188 + getTimelineByChildIndex(firstChildIndex).getFirstWindowIndex(shuffleModeEnabled); 189 } 190 191 @Override getWindow(int windowIndex, Window window, long defaultPositionProjectionUs)192 public final Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { 193 int childIndex = getChildIndexByWindowIndex(windowIndex); 194 int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); 195 int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); 196 getTimelineByChildIndex(childIndex) 197 .getWindow(windowIndex - firstWindowIndexInChild, window, defaultPositionProjectionUs); 198 Object childUid = getChildUidByChildIndex(childIndex); 199 // Don't create new objects if the child is using SINGLE_WINDOW_UID. 200 window.uid = 201 Window.SINGLE_WINDOW_UID.equals(window.uid) 202 ? childUid 203 : getConcatenatedUid(childUid, window.uid); 204 window.firstPeriodIndex += firstPeriodIndexInChild; 205 window.lastPeriodIndex += firstPeriodIndexInChild; 206 return window; 207 } 208 209 @Override getPeriodByUid(Object uid, Period period)210 public final Period getPeriodByUid(Object uid, Period period) { 211 Object childUid = getChildTimelineUidFromConcatenatedUid(uid); 212 Object periodUid = getChildPeriodUidFromConcatenatedUid(uid); 213 int childIndex = getChildIndexByChildUid(childUid); 214 int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); 215 getTimelineByChildIndex(childIndex).getPeriodByUid(periodUid, period); 216 period.windowIndex += firstWindowIndexInChild; 217 period.uid = uid; 218 return period; 219 } 220 221 @Override getPeriod(int periodIndex, Period period, boolean setIds)222 public final Period getPeriod(int periodIndex, Period period, boolean setIds) { 223 int childIndex = getChildIndexByPeriodIndex(periodIndex); 224 int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); 225 int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); 226 getTimelineByChildIndex(childIndex) 227 .getPeriod(periodIndex - firstPeriodIndexInChild, period, setIds); 228 period.windowIndex += firstWindowIndexInChild; 229 if (setIds) { 230 period.uid = 231 getConcatenatedUid( 232 getChildUidByChildIndex(childIndex), Assertions.checkNotNull(period.uid)); 233 } 234 return period; 235 } 236 237 @Override getIndexOfPeriod(Object uid)238 public final int getIndexOfPeriod(Object uid) { 239 if (!(uid instanceof Pair)) { 240 return C.INDEX_UNSET; 241 } 242 Object childUid = getChildTimelineUidFromConcatenatedUid(uid); 243 Object periodUid = getChildPeriodUidFromConcatenatedUid(uid); 244 int childIndex = getChildIndexByChildUid(childUid); 245 if (childIndex == C.INDEX_UNSET) { 246 return C.INDEX_UNSET; 247 } 248 int periodIndexInChild = getTimelineByChildIndex(childIndex).getIndexOfPeriod(periodUid); 249 return periodIndexInChild == C.INDEX_UNSET 250 ? C.INDEX_UNSET 251 : getFirstPeriodIndexByChildIndex(childIndex) + periodIndexInChild; 252 } 253 254 @Override getUidOfPeriod(int periodIndex)255 public final Object getUidOfPeriod(int periodIndex) { 256 int childIndex = getChildIndexByPeriodIndex(periodIndex); 257 int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); 258 Object periodUidInChild = 259 getTimelineByChildIndex(childIndex).getUidOfPeriod(periodIndex - firstPeriodIndexInChild); 260 return getConcatenatedUid(getChildUidByChildIndex(childIndex), periodUidInChild); 261 } 262 263 /** 264 * Returns the index of the child timeline containing the given period index. 265 * 266 * @param periodIndex A valid period index within the bounds of the timeline. 267 */ getChildIndexByPeriodIndex(int periodIndex)268 protected abstract int getChildIndexByPeriodIndex(int periodIndex); 269 270 /** 271 * Returns the index of the child timeline containing the given window index. 272 * 273 * @param windowIndex A valid window index within the bounds of the timeline. 274 */ getChildIndexByWindowIndex(int windowIndex)275 protected abstract int getChildIndexByWindowIndex(int windowIndex); 276 277 /** 278 * Returns the index of the child timeline with the given UID or {@link C#INDEX_UNSET} if not 279 * found. 280 * 281 * @param childUid A child UID. 282 * @return Index of child timeline or {@link C#INDEX_UNSET} if UID was not found. 283 */ getChildIndexByChildUid(Object childUid)284 protected abstract int getChildIndexByChildUid(Object childUid); 285 286 /** 287 * Returns the child timeline for the child with the given index. 288 * 289 * @param childIndex A valid child index within the bounds of the timeline. 290 */ getTimelineByChildIndex(int childIndex)291 protected abstract Timeline getTimelineByChildIndex(int childIndex); 292 293 /** 294 * Returns the first period index belonging to the child timeline with the given index. 295 * 296 * @param childIndex A valid child index within the bounds of the timeline. 297 */ getFirstPeriodIndexByChildIndex(int childIndex)298 protected abstract int getFirstPeriodIndexByChildIndex(int childIndex); 299 300 /** 301 * Returns the first window index belonging to the child timeline with the given index. 302 * 303 * @param childIndex A valid child index within the bounds of the timeline. 304 */ getFirstWindowIndexByChildIndex(int childIndex)305 protected abstract int getFirstWindowIndexByChildIndex(int childIndex); 306 307 /** 308 * Returns the UID of the child timeline with the given index. 309 * 310 * @param childIndex A valid child index within the bounds of the timeline. 311 */ getChildUidByChildIndex(int childIndex)312 protected abstract Object getChildUidByChildIndex(int childIndex); 313 getNextChildIndex(int childIndex, boolean shuffleModeEnabled)314 private int getNextChildIndex(int childIndex, boolean shuffleModeEnabled) { 315 return shuffleModeEnabled 316 ? shuffleOrder.getNextIndex(childIndex) 317 : childIndex < childCount - 1 ? childIndex + 1 : C.INDEX_UNSET; 318 } 319 getPreviousChildIndex(int childIndex, boolean shuffleModeEnabled)320 private int getPreviousChildIndex(int childIndex, boolean shuffleModeEnabled) { 321 return shuffleModeEnabled 322 ? shuffleOrder.getPreviousIndex(childIndex) 323 : childIndex > 0 ? childIndex - 1 : C.INDEX_UNSET; 324 } 325 } 326