1 /*
2  * Copyright (C) 2016 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.os.SystemClock;
19 import android.util.Pair;
20 import androidx.annotation.Nullable;
21 import com.google.android.exoplayer2.source.ads.AdPlaybackState;
22 import com.google.android.exoplayer2.util.Assertions;
23 import com.google.android.exoplayer2.util.Util;
24 
25 /**
26  * A flexible representation of the structure of media. A timeline is able to represent the
27  * structure of a wide variety of media, from simple cases like a single media file through to
28  * complex compositions of media such as playlists and streams with inserted ads. Instances are
29  * immutable. For cases where media is changing dynamically (e.g. live streams), a timeline provides
30  * a snapshot of the current state.
31  *
32  * <p>A timeline consists of {@link Window Windows} and {@link Period Periods}.
33  *
34  * <ul>
35  *   <li>A {@link Window} usually corresponds to one playlist item. It may span one or more periods
36  *       and it defines the region within those periods that's currently available for playback. The
37  *       window also provides additional information such as whether seeking is supported within the
38  *       window and the default position, which is the position from which playback will start when
39  *       the player starts playing the window.
40  *   <li>A {@link Period} defines a single logical piece of media, for example a media file. It may
41  *       also define groups of ads inserted into the media, along with information about whether
42  *       those ads have been loaded and played.
43  * </ul>
44  *
45  * <p>The following examples illustrate timelines for various use cases.
46  *
47  * <h3 id="single-file">Single media file or on-demand stream</h3>
48  *
49  * <p style="align:center"><img src="doc-files/timeline-single-file.svg" alt="Example timeline for a
50  * single file"> A timeline for a single media file or on-demand stream consists of a single period
51  * and window. The window spans the whole period, indicating that all parts of the media are
52  * available for playback. The window's default position is typically at the start of the period
53  * (indicated by the black dot in the figure above).
54  *
55  * <h3>Playlist of media files or on-demand streams</h3>
56  *
57  * <p style="align:center"><img src="doc-files/timeline-playlist.svg" alt="Example timeline for a
58  * playlist of files"> A timeline for a playlist of media files or on-demand streams consists of
59  * multiple periods, each with its own window. Each window spans the whole of the corresponding
60  * period, and typically has a default position at the start of the period. The properties of the
61  * periods and windows (e.g. their durations and whether the window is seekable) will often only
62  * become known when the player starts buffering the corresponding file or stream.
63  *
64  * <h3 id="live-limited">Live stream with limited availability</h3>
65  *
66  * <p style="align:center"><img src="doc-files/timeline-live-limited.svg" alt="Example timeline for
67  * a live stream with limited availability"> A timeline for a live stream consists of a period whose
68  * duration is unknown, since it's continually extending as more content is broadcast. If content
69  * only remains available for a limited period of time then the window may start at a non-zero
70  * position, defining the region of content that can still be played. The window will have {@link
71  * Window#isLive} set to true to indicate it's a live stream and {@link Window#isDynamic} set to
72  * true as long as we expect changes to the live window. Its default position is typically near to
73  * the live edge (indicated by the black dot in the figure above).
74  *
75  * <h3>Live stream with indefinite availability</h3>
76  *
77  * <p style="align:center"><img src="doc-files/timeline-live-indefinite.svg" alt="Example timeline
78  * for a live stream with indefinite availability"> A timeline for a live stream with indefinite
79  * availability is similar to the <a href="#live-limited">Live stream with limited availability</a>
80  * case, except that the window starts at the beginning of the period to indicate that all of the
81  * previously broadcast content can still be played.
82  *
83  * <h3 id="live-multi-period">Live stream with multiple periods</h3>
84  *
85  * <p style="align:center"><img src="doc-files/timeline-live-multi-period.svg" alt="Example timeline
86  * for a live stream with multiple periods"> This case arises when a live stream is explicitly
87  * divided into separate periods, for example at content boundaries. This case is similar to the <a
88  * href="#live-limited">Live stream with limited availability</a> case, except that the window may
89  * span more than one period. Multiple periods are also possible in the indefinite availability
90  * case.
91  *
92  * <h3>On-demand stream followed by live stream</h3>
93  *
94  * <p style="align:center"><img src="doc-files/timeline-advanced.svg" alt="Example timeline for an
95  * on-demand stream followed by a live stream"> This case is the concatenation of the <a
96  * href="#single-file">Single media file or on-demand stream</a> and <a href="#multi-period">Live
97  * stream with multiple periods</a> cases. When playback of the on-demand stream ends, playback of
98  * the live stream will start from its default position near the live edge.
99  *
100  * <h3 id="single-file-midrolls">On-demand stream with mid-roll ads</h3>
101  *
102  * <p style="align:center"><img src="doc-files/timeline-single-file-midrolls.svg" alt="Example
103  * timeline for an on-demand stream with mid-roll ad groups"> This case includes mid-roll ad groups,
104  * which are defined as part of the timeline's single period. The period can be queried for
105  * information about the ad groups and the ads they contain.
106  */
107 public abstract class Timeline {
108 
109   /**
110    * Holds information about a window in a {@link Timeline}. A window usually corresponds to one
111    * playlist item and defines a region of media currently available for playback along with
112    * additional information such as whether seeking is supported within the window. The figure below
113    * shows some of the information defined by a window, as well as how this information relates to
114    * corresponding {@link Period Periods} in the timeline.
115    *
116    * <p style="align:center"><img src="doc-files/timeline-window.svg" alt="Information defined by a
117    * timeline window">
118    */
119   public static final class Window {
120 
121     /**
122      * A {@link #uid} for a window that must be used for single-window {@link Timeline Timelines}.
123      */
124     public static final Object SINGLE_WINDOW_UID = new Object();
125 
126     /**
127      * A unique identifier for the window. Single-window {@link Timeline Timelines} must use {@link
128      * #SINGLE_WINDOW_UID}.
129      */
130     public Object uid;
131 
132     /** A tag for the window. Not necessarily unique. */
133     @Nullable public Object tag;
134 
135     /** The manifest of the window. May be {@code null}. */
136     @Nullable public Object manifest;
137 
138     /**
139      * The start time of the presentation to which this window belongs in milliseconds since the
140      * Unix epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes
141      * only.
142      */
143     public long presentationStartTimeMs;
144 
145     /**
146      * The window's start time in milliseconds since the Unix epoch, or {@link C#TIME_UNSET} if
147      * unknown or not applicable. For informational purposes only.
148      */
149     public long windowStartTimeMs;
150 
151     /**
152      * The offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix epoch
153      * according to the clock of the media origin server, or {@link C#TIME_UNSET} if unknown or not
154      * applicable.
155      *
156      * <p>Note that the current Unix time can be retrieved using {@link #getCurrentUnixTimeMs()} and
157      * is calculated as {@code SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs}.
158      */
159     public long elapsedRealtimeEpochOffsetMs;
160 
161     /** Whether it's possible to seek within this window. */
162     public boolean isSeekable;
163 
164     // TODO: Split this to better describe which parts of the window might change. For example it
165     // should be possible to individually determine whether the start and end positions of the
166     // window may change relative to the underlying periods. For an example of where it's useful to
167     // know that the end position is fixed whilst the start position may still change, see:
168     // https://github.com/google/ExoPlayer/issues/4780.
169     /** Whether this window may change when the timeline is updated. */
170     public boolean isDynamic;
171 
172     /**
173      * Whether the media in this window is live. For informational purposes only.
174      *
175      * <p>Check {@link #isDynamic} to know whether this window may still change.
176      */
177     public boolean isLive;
178 
179     /**
180      * Whether this window contains placeholder information because the real information has yet to
181      * be loaded.
182      */
183     public boolean isPlaceholder;
184 
185     /** The index of the first period that belongs to this window. */
186     public int firstPeriodIndex;
187 
188     /**
189      * The index of the last period that belongs to this window.
190      */
191     public int lastPeriodIndex;
192 
193     /**
194      * The default position relative to the start of the window at which to begin playback, in
195      * microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
196      * non-zero default position projection, and if the specified projection cannot be performed
197      * whilst remaining within the bounds of the window.
198      */
199     public long defaultPositionUs;
200 
201     /**
202      * The duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown.
203      */
204     public long durationUs;
205 
206     /**
207      * The position of the start of this window relative to the start of the first period belonging
208      * to it, in microseconds.
209      */
210     public long positionInFirstPeriodUs;
211 
212     /** Creates window. */
Window()213     public Window() {
214       uid = SINGLE_WINDOW_UID;
215     }
216 
217     /** Sets the data held by this window. */
set( Object uid, @Nullable Object tag, @Nullable Object manifest, long presentationStartTimeMs, long windowStartTimeMs, long elapsedRealtimeEpochOffsetMs, boolean isSeekable, boolean isDynamic, boolean isLive, long defaultPositionUs, long durationUs, int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs)218     public Window set(
219         Object uid,
220         @Nullable Object tag,
221         @Nullable Object manifest,
222         long presentationStartTimeMs,
223         long windowStartTimeMs,
224         long elapsedRealtimeEpochOffsetMs,
225         boolean isSeekable,
226         boolean isDynamic,
227         boolean isLive,
228         long defaultPositionUs,
229         long durationUs,
230         int firstPeriodIndex,
231         int lastPeriodIndex,
232         long positionInFirstPeriodUs) {
233       this.uid = uid;
234       this.tag = tag;
235       this.manifest = manifest;
236       this.presentationStartTimeMs = presentationStartTimeMs;
237       this.windowStartTimeMs = windowStartTimeMs;
238       this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
239       this.isSeekable = isSeekable;
240       this.isDynamic = isDynamic;
241       this.isLive = isLive;
242       this.defaultPositionUs = defaultPositionUs;
243       this.durationUs = durationUs;
244       this.firstPeriodIndex = firstPeriodIndex;
245       this.lastPeriodIndex = lastPeriodIndex;
246       this.positionInFirstPeriodUs = positionInFirstPeriodUs;
247       this.isPlaceholder = false;
248       return this;
249     }
250 
251     /**
252      * Returns the default position relative to the start of the window at which to begin playback,
253      * in milliseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
254      * non-zero default position projection, and if the specified projection cannot be performed
255      * whilst remaining within the bounds of the window.
256      */
getDefaultPositionMs()257     public long getDefaultPositionMs() {
258       return C.usToMs(defaultPositionUs);
259     }
260 
261     /**
262      * Returns the default position relative to the start of the window at which to begin playback,
263      * in microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
264      * non-zero default position projection, and if the specified projection cannot be performed
265      * whilst remaining within the bounds of the window.
266      */
getDefaultPositionUs()267     public long getDefaultPositionUs() {
268       return defaultPositionUs;
269     }
270 
271     /**
272      * Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown.
273      */
getDurationMs()274     public long getDurationMs() {
275       return C.usToMs(durationUs);
276     }
277 
278     /**
279      * Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown.
280      */
getDurationUs()281     public long getDurationUs() {
282       return durationUs;
283     }
284 
285     /**
286      * Returns the position of the start of this window relative to the start of the first period
287      * belonging to it, in milliseconds.
288      */
getPositionInFirstPeriodMs()289     public long getPositionInFirstPeriodMs() {
290       return C.usToMs(positionInFirstPeriodUs);
291     }
292 
293     /**
294      * Returns the position of the start of this window relative to the start of the first period
295      * belonging to it, in microseconds.
296      */
getPositionInFirstPeriodUs()297     public long getPositionInFirstPeriodUs() {
298       return positionInFirstPeriodUs;
299     }
300 
301     /**
302      * Returns the current time in milliseconds since the Unix epoch.
303      *
304      * <p>This method applies {@link #elapsedRealtimeEpochOffsetMs known corrections} made available
305      * by the media such that this time corresponds to the clock of the media origin server.
306      */
getCurrentUnixTimeMs()307     public long getCurrentUnixTimeMs() {
308       return Util.getNowUnixTimeMs(elapsedRealtimeEpochOffsetMs);
309     }
310 
311     @Override
equals(@ullable Object obj)312     public boolean equals(@Nullable Object obj) {
313       if (this == obj) {
314         return true;
315       }
316       if (obj == null || !getClass().equals(obj.getClass())) {
317         return false;
318       }
319       Window that = (Window) obj;
320       return Util.areEqual(uid, that.uid)
321           && Util.areEqual(tag, that.tag)
322           && Util.areEqual(manifest, that.manifest)
323           && presentationStartTimeMs == that.presentationStartTimeMs
324           && windowStartTimeMs == that.windowStartTimeMs
325           && elapsedRealtimeEpochOffsetMs == that.elapsedRealtimeEpochOffsetMs
326           && isSeekable == that.isSeekable
327           && isDynamic == that.isDynamic
328           && isLive == that.isLive
329           && isPlaceholder == that.isPlaceholder
330           && defaultPositionUs == that.defaultPositionUs
331           && durationUs == that.durationUs
332           && firstPeriodIndex == that.firstPeriodIndex
333           && lastPeriodIndex == that.lastPeriodIndex
334           && positionInFirstPeriodUs == that.positionInFirstPeriodUs;
335     }
336 
337     @Override
hashCode()338     public int hashCode() {
339       int result = 7;
340       result = 31 * result + uid.hashCode();
341       result = 31 * result + (tag == null ? 0 : tag.hashCode());
342       result = 31 * result + (manifest == null ? 0 : manifest.hashCode());
343       result = 31 * result + (int) (presentationStartTimeMs ^ (presentationStartTimeMs >>> 32));
344       result = 31 * result + (int) (windowStartTimeMs ^ (windowStartTimeMs >>> 32));
345       result =
346           31 * result
347               + (int) (elapsedRealtimeEpochOffsetMs ^ (elapsedRealtimeEpochOffsetMs >>> 32));
348       result = 31 * result + (isSeekable ? 1 : 0);
349       result = 31 * result + (isDynamic ? 1 : 0);
350       result = 31 * result + (isLive ? 1 : 0);
351       result = 31 * result + (isPlaceholder ? 1 : 0);
352       result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32));
353       result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
354       result = 31 * result + firstPeriodIndex;
355       result = 31 * result + lastPeriodIndex;
356       result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32));
357       return result;
358     }
359   }
360 
361   /**
362    * Holds information about a period in a {@link Timeline}. A period defines a single logical piece
363    * of media, for example a media file. It may also define groups of ads inserted into the media,
364    * along with information about whether those ads have been loaded and played.
365    *
366    * <p>The figure below shows some of the information defined by a period, as well as how this
367    * information relates to a corresponding {@link Window} in the timeline.
368    *
369    * <p style="align:center"><img src="doc-files/timeline-period.svg" alt="Information defined by a
370    * period">
371    */
372   public static final class Period {
373 
374     /**
375      * An identifier for the period. Not necessarily unique. May be null if the ids of the period
376      * are not required.
377      */
378     @Nullable public Object id;
379 
380     /**
381      * A unique identifier for the period. May be null if the ids of the period are not required.
382      */
383     @Nullable public Object uid;
384 
385     /**
386      * The index of the window to which this period belongs.
387      */
388     public int windowIndex;
389 
390     /**
391      * The duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown.
392      */
393     public long durationUs;
394 
395     private long positionInWindowUs;
396     private AdPlaybackState adPlaybackState;
397 
398     /** Creates a new instance with no ad playback state. */
Period()399     public Period() {
400       adPlaybackState = AdPlaybackState.NONE;
401     }
402 
403     /**
404      * Sets the data held by this period.
405      *
406      * @param id An identifier for the period. Not necessarily unique. May be null if the ids of the
407      *     period are not required.
408      * @param uid A unique identifier for the period. May be null if the ids of the period are not
409      *     required.
410      * @param windowIndex The index of the window to which this period belongs.
411      * @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if
412      *     unknown.
413      * @param positionInWindowUs The position of the start of this period relative to the start of
414      *     the window to which it belongs, in milliseconds. May be negative if the start of the
415      *     period is not within the window.
416      * @return This period, for convenience.
417      */
set( @ullable Object id, @Nullable Object uid, int windowIndex, long durationUs, long positionInWindowUs)418     public Period set(
419         @Nullable Object id,
420         @Nullable Object uid,
421         int windowIndex,
422         long durationUs,
423         long positionInWindowUs) {
424       return set(id, uid, windowIndex, durationUs, positionInWindowUs, AdPlaybackState.NONE);
425     }
426 
427     /**
428      * Sets the data held by this period.
429      *
430      * @param id An identifier for the period. Not necessarily unique. May be null if the ids of the
431      *     period are not required.
432      * @param uid A unique identifier for the period. May be null if the ids of the period are not
433      *     required.
434      * @param windowIndex The index of the window to which this period belongs.
435      * @param durationUs The duration of this period in microseconds, or {@link C#TIME_UNSET} if
436      *     unknown.
437      * @param positionInWindowUs The position of the start of this period relative to the start of
438      *     the window to which it belongs, in milliseconds. May be negative if the start of the
439      *     period is not within the window.
440      * @param adPlaybackState The state of the period's ads, or {@link AdPlaybackState#NONE} if
441      *     there are no ads.
442      * @return This period, for convenience.
443      */
set( @ullable Object id, @Nullable Object uid, int windowIndex, long durationUs, long positionInWindowUs, AdPlaybackState adPlaybackState)444     public Period set(
445         @Nullable Object id,
446         @Nullable Object uid,
447         int windowIndex,
448         long durationUs,
449         long positionInWindowUs,
450         AdPlaybackState adPlaybackState) {
451       this.id = id;
452       this.uid = uid;
453       this.windowIndex = windowIndex;
454       this.durationUs = durationUs;
455       this.positionInWindowUs = positionInWindowUs;
456       this.adPlaybackState = adPlaybackState;
457       return this;
458     }
459 
460     /**
461      * Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown.
462      */
getDurationMs()463     public long getDurationMs() {
464       return C.usToMs(durationUs);
465     }
466 
467     /**
468      * Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown.
469      */
getDurationUs()470     public long getDurationUs() {
471       return durationUs;
472     }
473 
474     /**
475      * Returns the position of the start of this period relative to the start of the window to which
476      * it belongs, in milliseconds. May be negative if the start of the period is not within the
477      * window.
478      */
getPositionInWindowMs()479     public long getPositionInWindowMs() {
480       return C.usToMs(positionInWindowUs);
481     }
482 
483     /**
484      * Returns the position of the start of this period relative to the start of the window to which
485      * it belongs, in microseconds. May be negative if the start of the period is not within the
486      * window.
487      */
getPositionInWindowUs()488     public long getPositionInWindowUs() {
489       return positionInWindowUs;
490     }
491 
492     /**
493      * Returns the number of ad groups in the period.
494      */
getAdGroupCount()495     public int getAdGroupCount() {
496       return adPlaybackState.adGroupCount;
497     }
498 
499     /**
500      * Returns the time of the ad group at index {@code adGroupIndex} in the period, in
501      * microseconds.
502      *
503      * @param adGroupIndex The ad group index.
504      * @return The time of the ad group at the index relative to the start of the enclosing {@link
505      *     Period}, in microseconds, or {@link C#TIME_END_OF_SOURCE} for a post-roll ad group.
506      */
getAdGroupTimeUs(int adGroupIndex)507     public long getAdGroupTimeUs(int adGroupIndex) {
508       return adPlaybackState.adGroupTimesUs[adGroupIndex];
509     }
510 
511     /**
512      * Returns the index of the first ad in the specified ad group that should be played, or the
513      * number of ads in the ad group if no ads should be played.
514      *
515      * @param adGroupIndex The ad group index.
516      * @return The index of the first ad that should be played, or the number of ads in the ad group
517      *     if no ads should be played.
518      */
getFirstAdIndexToPlay(int adGroupIndex)519     public int getFirstAdIndexToPlay(int adGroupIndex) {
520       return adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay();
521     }
522 
523     /**
524      * Returns the index of the next ad in the specified ad group that should be played after
525      * playing {@code adIndexInAdGroup}, or the number of ads in the ad group if no later ads should
526      * be played.
527      *
528      * @param adGroupIndex The ad group index.
529      * @param lastPlayedAdIndex The last played ad index in the ad group.
530      * @return The index of the next ad that should be played, or the number of ads in the ad group
531      *     if the ad group does not have any ads remaining to play.
532      */
getNextAdIndexToPlay(int adGroupIndex, int lastPlayedAdIndex)533     public int getNextAdIndexToPlay(int adGroupIndex, int lastPlayedAdIndex) {
534       return adPlaybackState.adGroups[adGroupIndex].getNextAdIndexToPlay(lastPlayedAdIndex);
535     }
536 
537     /**
538      * Returns whether the ad group at index {@code adGroupIndex} has been played.
539      *
540      * @param adGroupIndex The ad group index.
541      * @return Whether the ad group at index {@code adGroupIndex} has been played.
542      */
hasPlayedAdGroup(int adGroupIndex)543     public boolean hasPlayedAdGroup(int adGroupIndex) {
544       return !adPlaybackState.adGroups[adGroupIndex].hasUnplayedAds();
545     }
546 
547     /**
548      * Returns the index of the ad group at or before {@code positionUs} in the period, if that ad
549      * group is unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code
550      * positionUs} has no ads remaining to be played, or if there is no such ad group.
551      *
552      * @param positionUs The period position at or before which to find an ad group, in
553      *     microseconds.
554      * @return The index of the ad group, or {@link C#INDEX_UNSET}.
555      */
getAdGroupIndexForPositionUs(long positionUs)556     public int getAdGroupIndexForPositionUs(long positionUs) {
557       return adPlaybackState.getAdGroupIndexForPositionUs(positionUs, durationUs);
558     }
559 
560     /**
561      * Returns the index of the next ad group after {@code positionUs} in the period that has ads
562      * remaining to be played. Returns {@link C#INDEX_UNSET} if there is no such ad group.
563      *
564      * @param positionUs The period position after which to find an ad group, in microseconds.
565      * @return The index of the ad group, or {@link C#INDEX_UNSET}.
566      */
getAdGroupIndexAfterPositionUs(long positionUs)567     public int getAdGroupIndexAfterPositionUs(long positionUs) {
568       return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs, durationUs);
569     }
570 
571     /**
572      * Returns the number of ads in the ad group at index {@code adGroupIndex}, or
573      * {@link C#LENGTH_UNSET} if not yet known.
574      *
575      * @param adGroupIndex The ad group index.
576      * @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known.
577      */
getAdCountInAdGroup(int adGroupIndex)578     public int getAdCountInAdGroup(int adGroupIndex) {
579       return adPlaybackState.adGroups[adGroupIndex].count;
580     }
581 
582     /**
583      * Returns whether the URL for the specified ad is known.
584      *
585      * @param adGroupIndex The ad group index.
586      * @param adIndexInAdGroup The ad index in the ad group.
587      * @return Whether the URL for the specified ad is known.
588      */
isAdAvailable(int adGroupIndex, int adIndexInAdGroup)589     public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) {
590       AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
591       return adGroup.count != C.LENGTH_UNSET
592           && adGroup.states[adIndexInAdGroup] != AdPlaybackState.AD_STATE_UNAVAILABLE;
593     }
594 
595     /**
596      * Returns the duration of the ad at index {@code adIndexInAdGroup} in the ad group at
597      * {@code adGroupIndex}, in microseconds, or {@link C#TIME_UNSET} if not yet known.
598      *
599      * @param adGroupIndex The ad group index.
600      * @param adIndexInAdGroup The ad index in the ad group.
601      * @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known.
602      */
getAdDurationUs(int adGroupIndex, int adIndexInAdGroup)603     public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) {
604       AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
605       return adGroup.count != C.LENGTH_UNSET ? adGroup.durationsUs[adIndexInAdGroup] : C.TIME_UNSET;
606     }
607 
608     /**
609      * Returns the position offset in the first unplayed ad at which to begin playback, in
610      * microseconds.
611      */
getAdResumePositionUs()612     public long getAdResumePositionUs() {
613       return adPlaybackState.adResumePositionUs;
614     }
615 
616     @Override
equals(@ullable Object obj)617     public boolean equals(@Nullable Object obj) {
618       if (this == obj) {
619         return true;
620       }
621       if (obj == null || !getClass().equals(obj.getClass())) {
622         return false;
623       }
624       Period that = (Period) obj;
625       return Util.areEqual(id, that.id)
626           && Util.areEqual(uid, that.uid)
627           && windowIndex == that.windowIndex
628           && durationUs == that.durationUs
629           && positionInWindowUs == that.positionInWindowUs
630           && Util.areEqual(adPlaybackState, that.adPlaybackState);
631     }
632 
633     @Override
hashCode()634     public int hashCode() {
635       int result = 7;
636       result = 31 * result + (id == null ? 0 : id.hashCode());
637       result = 31 * result + (uid == null ? 0 : uid.hashCode());
638       result = 31 * result + windowIndex;
639       result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
640       result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32));
641       result = 31 * result + (adPlaybackState == null ? 0 : adPlaybackState.hashCode());
642       return result;
643     }
644   }
645 
646   /** An empty timeline. */
647   public static final Timeline EMPTY =
648       new Timeline() {
649 
650         @Override
651         public int getWindowCount() {
652           return 0;
653         }
654 
655         @Override
656         public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
657           throw new IndexOutOfBoundsException();
658         }
659 
660         @Override
661         public int getPeriodCount() {
662           return 0;
663         }
664 
665         @Override
666         public Period getPeriod(int periodIndex, Period period, boolean setIds) {
667           throw new IndexOutOfBoundsException();
668         }
669 
670         @Override
671         public int getIndexOfPeriod(Object uid) {
672           return C.INDEX_UNSET;
673         }
674 
675         @Override
676         public Object getUidOfPeriod(int periodIndex) {
677           throw new IndexOutOfBoundsException();
678         }
679       };
680 
681   /**
682    * Returns whether the timeline is empty.
683    */
isEmpty()684   public final boolean isEmpty() {
685     return getWindowCount() == 0;
686   }
687 
688   /**
689    * Returns the number of windows in the timeline.
690    */
getWindowCount()691   public abstract int getWindowCount();
692 
693   /**
694    * Returns the index of the window after the window at index {@code windowIndex} depending on the
695    * {@code repeatMode} and whether shuffling is enabled.
696    *
697    * @param windowIndex Index of a window in the timeline.
698    * @param repeatMode A repeat mode.
699    * @param shuffleModeEnabled Whether shuffling is enabled.
700    * @return The index of the next window, or {@link C#INDEX_UNSET} if this is the last window.
701    */
getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)702   public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
703       boolean shuffleModeEnabled) {
704     switch (repeatMode) {
705       case Player.REPEAT_MODE_OFF:
706         return windowIndex == getLastWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
707             : windowIndex + 1;
708       case Player.REPEAT_MODE_ONE:
709         return windowIndex;
710       case Player.REPEAT_MODE_ALL:
711         return windowIndex == getLastWindowIndex(shuffleModeEnabled)
712             ? getFirstWindowIndex(shuffleModeEnabled) : windowIndex + 1;
713       default:
714         throw new IllegalStateException();
715     }
716   }
717 
718   /**
719    * Returns the index of the window before the window at index {@code windowIndex} depending on the
720    * {@code repeatMode} and whether shuffling is enabled.
721    *
722    * @param windowIndex Index of a window in the timeline.
723    * @param repeatMode A repeat mode.
724    * @param shuffleModeEnabled Whether shuffling is enabled.
725    * @return The index of the previous window, or {@link C#INDEX_UNSET} if this is the first window.
726    */
getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)727   public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
728       boolean shuffleModeEnabled) {
729     switch (repeatMode) {
730       case Player.REPEAT_MODE_OFF:
731         return windowIndex == getFirstWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
732             : windowIndex - 1;
733       case Player.REPEAT_MODE_ONE:
734         return windowIndex;
735       case Player.REPEAT_MODE_ALL:
736         return windowIndex == getFirstWindowIndex(shuffleModeEnabled)
737             ? getLastWindowIndex(shuffleModeEnabled) : windowIndex - 1;
738       default:
739         throw new IllegalStateException();
740     }
741   }
742 
743   /**
744    * Returns the index of the last window in the playback order depending on whether shuffling is
745    * enabled.
746    *
747    * @param shuffleModeEnabled Whether shuffling is enabled.
748    * @return The index of the last window in the playback order, or {@link C#INDEX_UNSET} if the
749    *     timeline is empty.
750    */
getLastWindowIndex(boolean shuffleModeEnabled)751   public int getLastWindowIndex(boolean shuffleModeEnabled) {
752     return isEmpty() ? C.INDEX_UNSET : getWindowCount() - 1;
753   }
754 
755   /**
756    * Returns the index of the first window in the playback order depending on whether shuffling is
757    * enabled.
758    *
759    * @param shuffleModeEnabled Whether shuffling is enabled.
760    * @return The index of the first window in the playback order, or {@link C#INDEX_UNSET} if the
761    *     timeline is empty.
762    */
getFirstWindowIndex(boolean shuffleModeEnabled)763   public int getFirstWindowIndex(boolean shuffleModeEnabled) {
764     return isEmpty() ? C.INDEX_UNSET : 0;
765   }
766 
767   /**
768    * Populates a {@link Window} with data for the window at the specified index.
769    *
770    * @param windowIndex The index of the window.
771    * @param window The {@link Window} to populate. Must not be null.
772    * @return The populated {@link Window}, for convenience.
773    */
getWindow(int windowIndex, Window window)774   public final Window getWindow(int windowIndex, Window window) {
775     return getWindow(windowIndex, window, /* defaultPositionProjectionUs= */ 0);
776   }
777 
778   /** @deprecated Use {@link #getWindow(int, Window)} instead. Tags will always be set. */
779   @Deprecated
getWindow(int windowIndex, Window window, boolean setTag)780   public final Window getWindow(int windowIndex, Window window, boolean setTag) {
781     return getWindow(windowIndex, window, /* defaultPositionProjectionUs= */ 0);
782   }
783 
784   /**
785    * Populates a {@link Window} with data for the window at the specified index.
786    *
787    * @param windowIndex The index of the window.
788    * @param window The {@link Window} to populate. Must not be null.
789    * @param defaultPositionProjectionUs A duration into the future that the populated window's
790    *     default start position should be projected.
791    * @return The populated {@link Window}, for convenience.
792    */
getWindow( int windowIndex, Window window, long defaultPositionProjectionUs)793   public abstract Window getWindow(
794       int windowIndex, Window window, long defaultPositionProjectionUs);
795 
796   /**
797    * Returns the number of periods in the timeline.
798    */
getPeriodCount()799   public abstract int getPeriodCount();
800 
801   /**
802    * Returns the index of the period after the period at index {@code periodIndex} depending on the
803    * {@code repeatMode} and whether shuffling is enabled.
804    *
805    * @param periodIndex Index of a period in the timeline.
806    * @param period A {@link Period} to be used internally. Must not be null.
807    * @param window A {@link Window} to be used internally. Must not be null.
808    * @param repeatMode A repeat mode.
809    * @param shuffleModeEnabled Whether shuffling is enabled.
810    * @return The index of the next period, or {@link C#INDEX_UNSET} if this is the last period.
811    */
getNextPeriodIndex(int periodIndex, Period period, Window window, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)812   public final int getNextPeriodIndex(int periodIndex, Period period, Window window,
813       @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
814     int windowIndex = getPeriod(periodIndex, period).windowIndex;
815     if (getWindow(windowIndex, window).lastPeriodIndex == periodIndex) {
816       int nextWindowIndex = getNextWindowIndex(windowIndex, repeatMode, shuffleModeEnabled);
817       if (nextWindowIndex == C.INDEX_UNSET) {
818         return C.INDEX_UNSET;
819       }
820       return getWindow(nextWindowIndex, window).firstPeriodIndex;
821     }
822     return periodIndex + 1;
823   }
824 
825   /**
826    * Returns whether the given period is the last period of the timeline depending on the
827    * {@code repeatMode} and whether shuffling is enabled.
828    *
829    * @param periodIndex A period index.
830    * @param period A {@link Period} to be used internally. Must not be null.
831    * @param window A {@link Window} to be used internally. Must not be null.
832    * @param repeatMode A repeat mode.
833    * @param shuffleModeEnabled Whether shuffling is enabled.
834    * @return Whether the period of the given index is the last period of the timeline.
835    */
isLastPeriod(int periodIndex, Period period, Window window, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled)836   public final boolean isLastPeriod(int periodIndex, Period period, Window window,
837       @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
838     return getNextPeriodIndex(periodIndex, period, window, repeatMode, shuffleModeEnabled)
839         == C.INDEX_UNSET;
840   }
841 
842   /**
843    * Calls {@link #getPeriodPosition(Window, Period, int, long, long)} with a zero default position
844    * projection.
845    */
getPeriodPosition( Window window, Period period, int windowIndex, long windowPositionUs)846   public final Pair<Object, Long> getPeriodPosition(
847       Window window, Period period, int windowIndex, long windowPositionUs) {
848     return Assertions.checkNotNull(
849         getPeriodPosition(
850             window, period, windowIndex, windowPositionUs, /* defaultPositionProjectionUs= */ 0));
851   }
852 
853   /**
854    * Converts (windowIndex, windowPositionUs) to the corresponding (periodUid, periodPositionUs).
855    *
856    * @param window A {@link Window} that may be overwritten.
857    * @param period A {@link Period} that may be overwritten.
858    * @param windowIndex The window index.
859    * @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default
860    *     start position.
861    * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the
862    *     duration into the future by which the window's position should be projected.
863    * @return The corresponding (periodUid, periodPositionUs), or null if {@code #windowPositionUs}
864    *     is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's
865    *     position could not be projected by {@code defaultPositionProjectionUs}.
866    */
867   @Nullable
getPeriodPosition( Window window, Period period, int windowIndex, long windowPositionUs, long defaultPositionProjectionUs)868   public final Pair<Object, Long> getPeriodPosition(
869       Window window,
870       Period period,
871       int windowIndex,
872       long windowPositionUs,
873       long defaultPositionProjectionUs) {
874     Assertions.checkIndex(windowIndex, 0, getWindowCount());
875     getWindow(windowIndex, window, defaultPositionProjectionUs);
876     if (windowPositionUs == C.TIME_UNSET) {
877       windowPositionUs = window.getDefaultPositionUs();
878       if (windowPositionUs == C.TIME_UNSET) {
879         return null;
880       }
881     }
882     int periodIndex = window.firstPeriodIndex;
883     long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs;
884     long periodDurationUs = getPeriod(periodIndex, period, /* setIds= */ true).getDurationUs();
885     while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
886         && periodIndex < window.lastPeriodIndex) {
887       periodPositionUs -= periodDurationUs;
888       periodDurationUs = getPeriod(++periodIndex, period, /* setIds= */ true).getDurationUs();
889     }
890     return Pair.create(Assertions.checkNotNull(period.uid), periodPositionUs);
891   }
892 
893   /**
894    * Populates a {@link Period} with data for the period with the specified unique identifier.
895    *
896    * @param periodUid The unique identifier of the period.
897    * @param period The {@link Period} to populate. Must not be null.
898    * @return The populated {@link Period}, for convenience.
899    */
getPeriodByUid(Object periodUid, Period period)900   public Period getPeriodByUid(Object periodUid, Period period) {
901     return getPeriod(getIndexOfPeriod(periodUid), period, /* setIds= */ true);
902   }
903 
904   /**
905    * Populates a {@link Period} with data for the period at the specified index. {@link Period#id}
906    * and {@link Period#uid} will be set to null.
907    *
908    * @param periodIndex The index of the period.
909    * @param period The {@link Period} to populate. Must not be null.
910    * @return The populated {@link Period}, for convenience.
911    */
getPeriod(int periodIndex, Period period)912   public final Period getPeriod(int periodIndex, Period period) {
913     return getPeriod(periodIndex, period, false);
914   }
915 
916   /**
917    * Populates a {@link Period} with data for the period at the specified index.
918    *
919    * @param periodIndex The index of the period.
920    * @param period The {@link Period} to populate. Must not be null.
921    * @param setIds Whether {@link Period#id} and {@link Period#uid} should be populated. If false,
922    *     the fields will be set to null. The caller should pass false for efficiency reasons unless
923    *     the fields are required.
924    * @return The populated {@link Period}, for convenience.
925    */
getPeriod(int periodIndex, Period period, boolean setIds)926   public abstract Period getPeriod(int periodIndex, Period period, boolean setIds);
927 
928   /**
929    * Returns the index of the period identified by its unique {@link Period#uid}, or {@link
930    * C#INDEX_UNSET} if the period is not in the timeline.
931    *
932    * @param uid A unique identifier for a period.
933    * @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found.
934    */
getIndexOfPeriod(Object uid)935   public abstract int getIndexOfPeriod(Object uid);
936 
937   /**
938    * Returns the unique id of the period identified by its index in the timeline.
939    *
940    * @param periodIndex The index of the period.
941    * @return The unique id of the period.
942    */
getUidOfPeriod(int periodIndex)943   public abstract Object getUidOfPeriod(int periodIndex);
944 
945   @Override
equals(@ullable Object obj)946   public boolean equals(@Nullable Object obj) {
947     if (this == obj) {
948       return true;
949     }
950     if (!(obj instanceof Timeline)) {
951       return false;
952     }
953     Timeline other = (Timeline) obj;
954     if (other.getWindowCount() != getWindowCount() || other.getPeriodCount() != getPeriodCount()) {
955       return false;
956     }
957     Timeline.Window window = new Timeline.Window();
958     Timeline.Period period = new Timeline.Period();
959     Timeline.Window otherWindow = new Timeline.Window();
960     Timeline.Period otherPeriod = new Timeline.Period();
961     for (int i = 0; i < getWindowCount(); i++) {
962       if (!getWindow(i, window).equals(other.getWindow(i, otherWindow))) {
963         return false;
964       }
965     }
966     for (int i = 0; i < getPeriodCount(); i++) {
967       if (!getPeriod(i, period, /* setIds= */ true)
968           .equals(other.getPeriod(i, otherPeriod, /* setIds= */ true))) {
969         return false;
970       }
971     }
972     return true;
973   }
974 
975   @Override
hashCode()976   public int hashCode() {
977     Window window = new Window();
978     Period period = new Period();
979     int result = 7;
980     result = 31 * result + getWindowCount();
981     for (int i = 0; i < getWindowCount(); i++) {
982       result = 31 * result + getWindow(i, window).hashCode();
983     }
984     result = 31 * result + getPeriodCount();
985     for (int i = 0; i < getPeriodCount(); i++) {
986       result = 31 * result + getPeriod(i, period, /* setIds= */ true).hashCode();
987     }
988     return result;
989   }
990 }
991