• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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