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