1 /* 2 * Copyright (C) 2018 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 androidx.annotation.Nullable; 20 import com.google.android.exoplayer2.Player.RepeatMode; 21 import com.google.android.exoplayer2.source.MediaPeriod; 22 import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; 23 import com.google.android.exoplayer2.trackselection.TrackSelector; 24 import com.google.android.exoplayer2.trackselection.TrackSelectorResult; 25 import com.google.android.exoplayer2.upstream.Allocator; 26 import com.google.android.exoplayer2.util.Assertions; 27 28 /** 29 * Holds a queue of media periods, from the currently playing media period at the front to the 30 * loading media period at the end of the queue, with methods for controlling loading and updating 31 * the queue. Also has a reference to the media period currently being read. 32 */ 33 /* package */ final class MediaPeriodQueue { 34 35 /** 36 * Limits the maximum number of periods to buffer ahead of the current playing period. The 37 * buffering policy normally prevents buffering too far ahead, but the policy could allow too many 38 * small periods to be buffered if the period count were not limited. 39 */ 40 private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100; 41 42 private final Timeline.Period period; 43 private final Timeline.Window window; 44 45 private long nextWindowSequenceNumber; 46 private @RepeatMode int repeatMode; 47 private boolean shuffleModeEnabled; 48 @Nullable private MediaPeriodHolder playing; 49 @Nullable private MediaPeriodHolder reading; 50 @Nullable private MediaPeriodHolder loading; 51 private int length; 52 @Nullable private Object oldFrontPeriodUid; 53 private long oldFrontPeriodWindowSequenceNumber; 54 55 /** Creates a new media period queue. */ MediaPeriodQueue()56 public MediaPeriodQueue() { 57 period = new Timeline.Period(); 58 window = new Timeline.Window(); 59 } 60 61 /** 62 * Sets the {@link RepeatMode} and returns whether the repeat mode change has been fully handled. 63 * If not, it is necessary to seek to the current playback position. 64 * 65 * @param timeline The current timeline. 66 * @param repeatMode The new repeat mode. 67 * @return Whether the repeat mode change has been fully handled. 68 */ updateRepeatMode(Timeline timeline, @RepeatMode int repeatMode)69 public boolean updateRepeatMode(Timeline timeline, @RepeatMode int repeatMode) { 70 this.repeatMode = repeatMode; 71 return updateForPlaybackModeChange(timeline); 72 } 73 74 /** 75 * Sets whether shuffling is enabled and returns whether the shuffle mode change has been fully 76 * handled. If not, it is necessary to seek to the current playback position. 77 * 78 * @param timeline The current timeline. 79 * @param shuffleModeEnabled Whether shuffling mode is enabled. 80 * @return Whether the shuffle mode change has been fully handled. 81 */ updateShuffleModeEnabled(Timeline timeline, boolean shuffleModeEnabled)82 public boolean updateShuffleModeEnabled(Timeline timeline, boolean shuffleModeEnabled) { 83 this.shuffleModeEnabled = shuffleModeEnabled; 84 return updateForPlaybackModeChange(timeline); 85 } 86 87 /** Returns whether {@code mediaPeriod} is the current loading media period. */ isLoading(MediaPeriod mediaPeriod)88 public boolean isLoading(MediaPeriod mediaPeriod) { 89 return loading != null && loading.mediaPeriod == mediaPeriod; 90 } 91 92 /** 93 * If there is a loading period, reevaluates its buffer. 94 * 95 * @param rendererPositionUs The current renderer position. 96 */ reevaluateBuffer(long rendererPositionUs)97 public void reevaluateBuffer(long rendererPositionUs) { 98 if (loading != null) { 99 loading.reevaluateBuffer(rendererPositionUs); 100 } 101 } 102 103 /** Returns whether a new loading media period should be enqueued, if available. */ shouldLoadNextMediaPeriod()104 public boolean shouldLoadNextMediaPeriod() { 105 return loading == null 106 || (!loading.info.isFinal 107 && loading.isFullyBuffered() 108 && loading.info.durationUs != C.TIME_UNSET 109 && length < MAXIMUM_BUFFER_AHEAD_PERIODS); 110 } 111 112 /** 113 * Returns the {@link MediaPeriodInfo} for the next media period to load. 114 * 115 * @param rendererPositionUs The current renderer position. 116 * @param playbackInfo The current playback information. 117 * @return The {@link MediaPeriodInfo} for the next media period to load, or {@code null} if not 118 * yet known. 119 */ 120 @Nullable getNextMediaPeriodInfo( long rendererPositionUs, PlaybackInfo playbackInfo)121 public MediaPeriodInfo getNextMediaPeriodInfo( 122 long rendererPositionUs, PlaybackInfo playbackInfo) { 123 return loading == null 124 ? getFirstMediaPeriodInfo(playbackInfo) 125 : getFollowingMediaPeriodInfo(playbackInfo.timeline, loading, rendererPositionUs); 126 } 127 128 /** 129 * Enqueues a new media period holder based on the specified information as the new loading media 130 * period, and returns it. 131 * 132 * @param rendererCapabilities The renderer capabilities. 133 * @param trackSelector The track selector. 134 * @param allocator The allocator. 135 * @param mediaSourceList The list of media sources. 136 * @param info Information used to identify this media period in its timeline period. 137 * @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each 138 * renderer. 139 */ enqueueNextMediaPeriodHolder( RendererCapabilities[] rendererCapabilities, TrackSelector trackSelector, Allocator allocator, MediaSourceList mediaSourceList, MediaPeriodInfo info, TrackSelectorResult emptyTrackSelectorResult)140 public MediaPeriodHolder enqueueNextMediaPeriodHolder( 141 RendererCapabilities[] rendererCapabilities, 142 TrackSelector trackSelector, 143 Allocator allocator, 144 MediaSourceList mediaSourceList, 145 MediaPeriodInfo info, 146 TrackSelectorResult emptyTrackSelectorResult) { 147 long rendererPositionOffsetUs = 148 loading == null 149 ? (info.id.isAd() && info.requestedContentPositionUs != C.TIME_UNSET 150 ? info.requestedContentPositionUs 151 : 0) 152 : (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs); 153 MediaPeriodHolder newPeriodHolder = 154 new MediaPeriodHolder( 155 rendererCapabilities, 156 rendererPositionOffsetUs, 157 trackSelector, 158 allocator, 159 mediaSourceList, 160 info, 161 emptyTrackSelectorResult); 162 if (loading != null) { 163 loading.setNext(newPeriodHolder); 164 } else { 165 playing = newPeriodHolder; 166 reading = newPeriodHolder; 167 } 168 oldFrontPeriodUid = null; 169 loading = newPeriodHolder; 170 length++; 171 return newPeriodHolder; 172 } 173 174 /** 175 * Returns the loading period holder which is at the end of the queue, or null if the queue is 176 * empty. 177 */ 178 @Nullable getLoadingPeriod()179 public MediaPeriodHolder getLoadingPeriod() { 180 return loading; 181 } 182 183 /** 184 * Returns the playing period holder which is at the front of the queue, or null if the queue is 185 * empty. 186 */ 187 @Nullable getPlayingPeriod()188 public MediaPeriodHolder getPlayingPeriod() { 189 return playing; 190 } 191 192 /** Returns the reading period holder, or null if the queue is empty. */ 193 @Nullable getReadingPeriod()194 public MediaPeriodHolder getReadingPeriod() { 195 return reading; 196 } 197 198 /** 199 * Continues reading from the next period holder in the queue. 200 * 201 * @return The updated reading period holder. 202 */ advanceReadingPeriod()203 public MediaPeriodHolder advanceReadingPeriod() { 204 Assertions.checkState(reading != null && reading.getNext() != null); 205 reading = reading.getNext(); 206 return reading; 207 } 208 209 /** 210 * Dequeues the playing period holder from the front of the queue and advances the playing period 211 * holder to be the next item in the queue. 212 * 213 * @return The updated playing period holder, or null if the queue is or becomes empty. 214 */ 215 @Nullable advancePlayingPeriod()216 public MediaPeriodHolder advancePlayingPeriod() { 217 if (playing == null) { 218 return null; 219 } 220 if (playing == reading) { 221 reading = playing.getNext(); 222 } 223 playing.release(); 224 length--; 225 if (length == 0) { 226 loading = null; 227 oldFrontPeriodUid = playing.uid; 228 oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber; 229 } 230 playing = playing.getNext(); 231 return playing; 232 } 233 234 /** 235 * Removes all period holders after the given period holder. This process may also remove the 236 * currently reading period holder. If that is the case, the reading period holder is set to be 237 * the same as the playing period holder at the front of the queue. 238 * 239 * @param mediaPeriodHolder The media period holder that shall be the new end of the queue. 240 * @return Whether the reading period has been removed. 241 */ removeAfter(MediaPeriodHolder mediaPeriodHolder)242 public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) { 243 Assertions.checkState(mediaPeriodHolder != null); 244 boolean removedReading = false; 245 loading = mediaPeriodHolder; 246 while (mediaPeriodHolder.getNext() != null) { 247 mediaPeriodHolder = mediaPeriodHolder.getNext(); 248 if (mediaPeriodHolder == reading) { 249 reading = playing; 250 removedReading = true; 251 } 252 mediaPeriodHolder.release(); 253 length--; 254 } 255 loading.setNext(null); 256 return removedReading; 257 } 258 259 /** Clears the queue. */ clear()260 public void clear() { 261 MediaPeriodHolder front = playing; 262 if (front != null) { 263 oldFrontPeriodUid = front.uid; 264 oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber; 265 removeAfter(front); 266 front.release(); 267 } 268 playing = null; 269 loading = null; 270 reading = null; 271 length = 0; 272 } 273 274 /** 275 * Updates media periods in the queue to take into account the latest timeline, and returns 276 * whether the timeline change has been fully handled. If not, it is necessary to seek to the 277 * current playback position. The method assumes that the first media period in the queue is still 278 * consistent with the new timeline. 279 * 280 * @param timeline The new timeline. 281 * @param rendererPositionUs The current renderer position in microseconds. 282 * @param maxRendererReadPositionUs The maximum renderer position up to which renderers have read 283 * the current reading media period in microseconds, or {@link C#TIME_END_OF_SOURCE} if they 284 * have read to the end. 285 * @return Whether the timeline change has been handled completely. 286 */ updateQueuedPeriods( Timeline timeline, long rendererPositionUs, long maxRendererReadPositionUs)287 public boolean updateQueuedPeriods( 288 Timeline timeline, long rendererPositionUs, long maxRendererReadPositionUs) { 289 // TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline 290 // is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be 291 // handled here. 292 MediaPeriodHolder previousPeriodHolder = null; 293 MediaPeriodHolder periodHolder = playing; 294 while (periodHolder != null) { 295 MediaPeriodInfo oldPeriodInfo = periodHolder.info; 296 297 // Get period info based on new timeline. 298 MediaPeriodInfo newPeriodInfo; 299 if (previousPeriodHolder == null) { 300 // The id and start position of the first period have already been verified by 301 // ExoPlayerImplInternal.handleSourceInfoRefreshed. Just update duration, isLastInTimeline 302 // and isLastInPeriod flags. 303 newPeriodInfo = getUpdatedMediaPeriodInfo(timeline, oldPeriodInfo); 304 } else { 305 newPeriodInfo = 306 getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs); 307 if (newPeriodInfo == null) { 308 // We've loaded a next media period that is not in the new timeline. 309 return !removeAfter(previousPeriodHolder); 310 } 311 if (!canKeepMediaPeriodHolder(oldPeriodInfo, newPeriodInfo)) { 312 // The new media period has a different id or start position. 313 return !removeAfter(previousPeriodHolder); 314 } 315 } 316 317 // Use the new period info, but keep the old requested content position to avoid overriding it 318 // by the default content position generated in getFollowingMediaPeriodInfo. 319 periodHolder.info = 320 newPeriodInfo.copyWithRequestedContentPositionUs( 321 oldPeriodInfo.requestedContentPositionUs); 322 323 if (!areDurationsCompatible(oldPeriodInfo.durationUs, newPeriodInfo.durationUs)) { 324 // The period duration changed. Remove all subsequent periods and check whether we read 325 // beyond the new duration. 326 long newDurationInRendererTime = 327 newPeriodInfo.durationUs == C.TIME_UNSET 328 ? Long.MAX_VALUE 329 : periodHolder.toRendererTime(newPeriodInfo.durationUs); 330 boolean isReadingAndReadBeyondNewDuration = 331 periodHolder == reading 332 && (maxRendererReadPositionUs == C.TIME_END_OF_SOURCE 333 || maxRendererReadPositionUs >= newDurationInRendererTime); 334 boolean readingPeriodRemoved = removeAfter(periodHolder); 335 return !readingPeriodRemoved && !isReadingAndReadBeyondNewDuration; 336 } 337 338 previousPeriodHolder = periodHolder; 339 periodHolder = periodHolder.getNext(); 340 } 341 return true; 342 } 343 344 /** 345 * Returns new media period info based on specified {@code mediaPeriodInfo} but taking into 346 * account the current timeline. This method must only be called if the period is still part of 347 * the current timeline. 348 * 349 * @param timeline The current timeline used to update the media period. 350 * @param info Media period info for a media period based on an old timeline. 351 * @return The updated media period info for the current timeline. 352 */ getUpdatedMediaPeriodInfo(Timeline timeline, MediaPeriodInfo info)353 public MediaPeriodInfo getUpdatedMediaPeriodInfo(Timeline timeline, MediaPeriodInfo info) { 354 MediaPeriodId id = info.id; 355 boolean isLastInPeriod = isLastInPeriod(id); 356 boolean isLastInWindow = isLastInWindow(timeline, id); 357 boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod); 358 timeline.getPeriodByUid(info.id.periodUid, period); 359 long durationUs = 360 id.isAd() 361 ? period.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup) 362 : (info.endPositionUs == C.TIME_UNSET || info.endPositionUs == C.TIME_END_OF_SOURCE 363 ? period.getDurationUs() 364 : info.endPositionUs); 365 return new MediaPeriodInfo( 366 id, 367 info.startPositionUs, 368 info.requestedContentPositionUs, 369 info.endPositionUs, 370 durationUs, 371 isLastInPeriod, 372 isLastInWindow, 373 isLastInTimeline); 374 } 375 376 /** 377 * Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be 378 * played, returning an identifier for an ad group if one needs to be played before the specified 379 * position, or an identifier for a content media period if not. 380 * 381 * @param timeline The timeline the period is part of. 382 * @param periodUid The uid of the timeline period to play. 383 * @param positionUs The next content position in the period to play. 384 * @return The identifier for the first media period to play, taking into account unplayed ads. 385 */ resolveMediaPeriodIdForAds( Timeline timeline, Object periodUid, long positionUs)386 public MediaPeriodId resolveMediaPeriodIdForAds( 387 Timeline timeline, Object periodUid, long positionUs) { 388 long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid); 389 return resolveMediaPeriodIdForAds( 390 timeline, periodUid, positionUs, windowSequenceNumber, period); 391 } 392 393 // Internal methods. 394 395 /** 396 * Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be 397 * played, returning an identifier for an ad group if one needs to be played before the specified 398 * position, or an identifier for a content media period if not. 399 * 400 * @param timeline The timeline the period is part of. 401 * @param periodUid The uid of the timeline period to play. 402 * @param positionUs The next content position in the period to play. 403 * @param windowSequenceNumber The sequence number of the window in the buffered sequence of 404 * windows this period is part of. 405 * @param period A scratch {@link Timeline.Period}. 406 * @return The identifier for the first media period to play, taking into account unplayed ads. 407 */ resolveMediaPeriodIdForAds( Timeline timeline, Object periodUid, long positionUs, long windowSequenceNumber, Timeline.Period period)408 private static MediaPeriodId resolveMediaPeriodIdForAds( 409 Timeline timeline, 410 Object periodUid, 411 long positionUs, 412 long windowSequenceNumber, 413 Timeline.Period period) { 414 timeline.getPeriodByUid(periodUid, period); 415 int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); 416 if (adGroupIndex == C.INDEX_UNSET) { 417 int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); 418 return new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex); 419 } else { 420 int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); 421 return new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); 422 } 423 } 424 425 /** 426 * Resolves the specified period uid to a corresponding window sequence number. Either by reusing 427 * the window sequence number of an existing matching media period or by creating a new window 428 * sequence number. 429 * 430 * @param timeline The timeline the period is part of. 431 * @param periodUid The uid of the timeline period. 432 * @return A window sequence number for a media period created for this timeline period. 433 */ resolvePeriodIndexToWindowSequenceNumber(Timeline timeline, Object periodUid)434 private long resolvePeriodIndexToWindowSequenceNumber(Timeline timeline, Object periodUid) { 435 int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex; 436 if (oldFrontPeriodUid != null) { 437 int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid); 438 if (oldFrontPeriodIndex != C.INDEX_UNSET) { 439 int oldFrontWindowIndex = timeline.getPeriod(oldFrontPeriodIndex, period).windowIndex; 440 if (oldFrontWindowIndex == windowIndex) { 441 // Try to match old front uid after the queue has been cleared. 442 return oldFrontPeriodWindowSequenceNumber; 443 } 444 } 445 } 446 MediaPeriodHolder mediaPeriodHolder = playing; 447 while (mediaPeriodHolder != null) { 448 if (mediaPeriodHolder.uid.equals(periodUid)) { 449 // Reuse window sequence number of first exact period match. 450 return mediaPeriodHolder.info.id.windowSequenceNumber; 451 } 452 mediaPeriodHolder = mediaPeriodHolder.getNext(); 453 } 454 mediaPeriodHolder = playing; 455 while (mediaPeriodHolder != null) { 456 int indexOfHolderInTimeline = timeline.getIndexOfPeriod(mediaPeriodHolder.uid); 457 if (indexOfHolderInTimeline != C.INDEX_UNSET) { 458 int holderWindowIndex = timeline.getPeriod(indexOfHolderInTimeline, period).windowIndex; 459 if (holderWindowIndex == windowIndex) { 460 // As an alternative, try to match other periods of the same window. 461 return mediaPeriodHolder.info.id.windowSequenceNumber; 462 } 463 } 464 mediaPeriodHolder = mediaPeriodHolder.getNext(); 465 } 466 // If no match is found, create new sequence number. 467 long windowSequenceNumber = nextWindowSequenceNumber++; 468 if (playing == null) { 469 // If the queue is empty, save it as old front uid to allow later reuse. 470 oldFrontPeriodUid = periodUid; 471 oldFrontPeriodWindowSequenceNumber = windowSequenceNumber; 472 } 473 return windowSequenceNumber; 474 } 475 476 /** 477 * Returns whether a period described by {@code oldInfo} can be kept for playing the media period 478 * described by {@code newInfo}. 479 */ canKeepMediaPeriodHolder(MediaPeriodInfo oldInfo, MediaPeriodInfo newInfo)480 private boolean canKeepMediaPeriodHolder(MediaPeriodInfo oldInfo, MediaPeriodInfo newInfo) { 481 return oldInfo.startPositionUs == newInfo.startPositionUs && oldInfo.id.equals(newInfo.id); 482 } 483 484 /** 485 * Returns whether a duration change of a period is compatible with keeping the following periods. 486 */ areDurationsCompatible(long previousDurationUs, long newDurationUs)487 private boolean areDurationsCompatible(long previousDurationUs, long newDurationUs) { 488 return previousDurationUs == C.TIME_UNSET || previousDurationUs == newDurationUs; 489 } 490 491 /** 492 * Updates the queue for any playback mode change, and returns whether the change was fully 493 * handled. If not, it is necessary to seek to the current playback position. 494 * 495 * @param timeline The current timeline. 496 */ updateForPlaybackModeChange(Timeline timeline)497 private boolean updateForPlaybackModeChange(Timeline timeline) { 498 // Find the last existing period holder that matches the new period order. 499 MediaPeriodHolder lastValidPeriodHolder = playing; 500 if (lastValidPeriodHolder == null) { 501 return true; 502 } 503 int currentPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.uid); 504 while (true) { 505 int nextPeriodIndex = 506 timeline.getNextPeriodIndex( 507 currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled); 508 while (lastValidPeriodHolder.getNext() != null 509 && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { 510 lastValidPeriodHolder = lastValidPeriodHolder.getNext(); 511 } 512 513 MediaPeriodHolder nextMediaPeriodHolder = lastValidPeriodHolder.getNext(); 514 if (nextPeriodIndex == C.INDEX_UNSET || nextMediaPeriodHolder == null) { 515 break; 516 } 517 int nextPeriodHolderPeriodIndex = timeline.getIndexOfPeriod(nextMediaPeriodHolder.uid); 518 if (nextPeriodHolderPeriodIndex != nextPeriodIndex) { 519 break; 520 } 521 lastValidPeriodHolder = nextMediaPeriodHolder; 522 currentPeriodIndex = nextPeriodIndex; 523 } 524 525 // Release any period holders that don't match the new period order. 526 boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder); 527 528 // Update the period info for the last holder, as it may now be the last period in the timeline. 529 lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info); 530 531 // If renderers may have read from a period that's been removed, it is necessary to restart. 532 return !readingPeriodRemoved; 533 } 534 535 /** 536 * Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position. 537 */ getFirstMediaPeriodInfo(PlaybackInfo playbackInfo)538 private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) { 539 return getMediaPeriodInfo( 540 playbackInfo.timeline, 541 playbackInfo.periodId, 542 playbackInfo.requestedContentPositionUs, 543 playbackInfo.positionUs); 544 } 545 546 /** 547 * Returns the {@link MediaPeriodInfo} for the media period following {@code mediaPeriodHolder}'s 548 * media period. 549 * 550 * @param timeline The current timeline. 551 * @param mediaPeriodHolder The media period holder. 552 * @param rendererPositionUs The current renderer position in microseconds. 553 * @return The following media period's info, or {@code null} if it is not yet possible to get the 554 * next media period info. 555 */ 556 @Nullable getFollowingMediaPeriodInfo( Timeline timeline, MediaPeriodHolder mediaPeriodHolder, long rendererPositionUs)557 private MediaPeriodInfo getFollowingMediaPeriodInfo( 558 Timeline timeline, MediaPeriodHolder mediaPeriodHolder, long rendererPositionUs) { 559 // TODO: This method is called repeatedly from ExoPlayerImplInternal.maybeUpdateLoadingPeriod 560 // but if the timeline is not ready to provide the next period it can't return a non-null value 561 // until the timeline is updated. Store whether the next timeline period is ready when the 562 // timeline is updated, to avoid repeatedly checking the same timeline. 563 MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; 564 // The expected delay until playback transitions to the new period is equal the duration of 565 // media that's currently buffered (assuming no interruptions). This is used to project forward 566 // the start position for transitions to new windows. 567 long bufferedDurationUs = 568 mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; 569 if (mediaPeriodInfo.isLastInTimelinePeriod) { 570 int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid); 571 int nextPeriodIndex = 572 timeline.getNextPeriodIndex( 573 currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled); 574 if (nextPeriodIndex == C.INDEX_UNSET) { 575 // We can't create a next period yet. 576 return null; 577 } 578 579 long startPositionUs; 580 long contentPositionUs; 581 int nextWindowIndex = 582 timeline.getPeriod(nextPeriodIndex, period, /* setIds= */ true).windowIndex; 583 Object nextPeriodUid = period.uid; 584 long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber; 585 if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) { 586 // We're starting to buffer a new window. When playback transitions to this window we'll 587 // want it to be from its default start position, so project the default start position 588 // forward by the duration of the buffer, and start buffering from this point. 589 contentPositionUs = C.TIME_UNSET; 590 @Nullable 591 Pair<Object, Long> defaultPosition = 592 timeline.getPeriodPosition( 593 window, 594 period, 595 nextWindowIndex, 596 /* windowPositionUs= */ C.TIME_UNSET, 597 /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); 598 if (defaultPosition == null) { 599 return null; 600 } 601 nextPeriodUid = defaultPosition.first; 602 startPositionUs = defaultPosition.second; 603 MediaPeriodHolder nextMediaPeriodHolder = mediaPeriodHolder.getNext(); 604 if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) { 605 windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber; 606 } else { 607 windowSequenceNumber = nextWindowSequenceNumber++; 608 } 609 } else { 610 // We're starting to buffer a new period within the same window. 611 startPositionUs = 0; 612 contentPositionUs = 0; 613 } 614 MediaPeriodId periodId = 615 resolveMediaPeriodIdForAds( 616 timeline, nextPeriodUid, startPositionUs, windowSequenceNumber, period); 617 return getMediaPeriodInfo(timeline, periodId, contentPositionUs, startPositionUs); 618 } 619 620 MediaPeriodId currentPeriodId = mediaPeriodInfo.id; 621 timeline.getPeriodByUid(currentPeriodId.periodUid, period); 622 if (currentPeriodId.isAd()) { 623 int adGroupIndex = currentPeriodId.adGroupIndex; 624 int adCountInCurrentAdGroup = period.getAdCountInAdGroup(adGroupIndex); 625 if (adCountInCurrentAdGroup == C.LENGTH_UNSET) { 626 return null; 627 } 628 int nextAdIndexInAdGroup = 629 period.getNextAdIndexToPlay(adGroupIndex, currentPeriodId.adIndexInAdGroup); 630 if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) { 631 // Play the next ad in the ad group if it's available. 632 return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup) 633 ? null 634 : getMediaPeriodInfoForAd( 635 timeline, 636 currentPeriodId.periodUid, 637 adGroupIndex, 638 nextAdIndexInAdGroup, 639 mediaPeriodInfo.requestedContentPositionUs, 640 currentPeriodId.windowSequenceNumber); 641 } else { 642 // Play content from the ad group position. 643 long startPositionUs = mediaPeriodInfo.requestedContentPositionUs; 644 if (startPositionUs == C.TIME_UNSET) { 645 // If we're transitioning from an ad group to content starting from its default position, 646 // project the start position forward as if this were a transition to a new window. 647 @Nullable 648 Pair<Object, Long> defaultPosition = 649 timeline.getPeriodPosition( 650 window, 651 period, 652 period.windowIndex, 653 /* windowPositionUs= */ C.TIME_UNSET, 654 /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); 655 if (defaultPosition == null) { 656 return null; 657 } 658 startPositionUs = defaultPosition.second; 659 } 660 return getMediaPeriodInfoForContent( 661 timeline, 662 currentPeriodId.periodUid, 663 startPositionUs, 664 mediaPeriodInfo.requestedContentPositionUs, 665 currentPeriodId.windowSequenceNumber); 666 } 667 } else { 668 // Play the next ad group if it's available. 669 int nextAdGroupIndex = period.getAdGroupIndexForPositionUs(mediaPeriodInfo.endPositionUs); 670 if (nextAdGroupIndex == C.INDEX_UNSET) { 671 // The next ad group can't be played. Play content from the previous end position instead. 672 return getMediaPeriodInfoForContent( 673 timeline, 674 currentPeriodId.periodUid, 675 /* startPositionUs= */ mediaPeriodInfo.durationUs, 676 /* requestedContentPositionUs= */ mediaPeriodInfo.durationUs, 677 currentPeriodId.windowSequenceNumber); 678 } 679 int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); 680 return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) 681 ? null 682 : getMediaPeriodInfoForAd( 683 timeline, 684 currentPeriodId.periodUid, 685 nextAdGroupIndex, 686 adIndexInAdGroup, 687 /* contentPositionUs= */ mediaPeriodInfo.durationUs, 688 currentPeriodId.windowSequenceNumber); 689 } 690 } 691 getMediaPeriodInfo( Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs)692 private MediaPeriodInfo getMediaPeriodInfo( 693 Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) { 694 timeline.getPeriodByUid(id.periodUid, period); 695 if (id.isAd()) { 696 if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { 697 return null; 698 } 699 return getMediaPeriodInfoForAd( 700 timeline, 701 id.periodUid, 702 id.adGroupIndex, 703 id.adIndexInAdGroup, 704 requestedContentPositionUs, 705 id.windowSequenceNumber); 706 } else { 707 return getMediaPeriodInfoForContent( 708 timeline, 709 id.periodUid, 710 startPositionUs, 711 requestedContentPositionUs, 712 id.windowSequenceNumber); 713 } 714 } 715 getMediaPeriodInfoForAd( Timeline timeline, Object periodUid, int adGroupIndex, int adIndexInAdGroup, long contentPositionUs, long windowSequenceNumber)716 private MediaPeriodInfo getMediaPeriodInfoForAd( 717 Timeline timeline, 718 Object periodUid, 719 int adGroupIndex, 720 int adIndexInAdGroup, 721 long contentPositionUs, 722 long windowSequenceNumber) { 723 MediaPeriodId id = 724 new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); 725 long durationUs = 726 timeline 727 .getPeriodByUid(id.periodUid, period) 728 .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); 729 long startPositionUs = 730 adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex) 731 ? period.getAdResumePositionUs() 732 : 0; 733 if (durationUs != C.TIME_UNSET && startPositionUs >= durationUs) { 734 // Ensure start position doesn't exceed duration. 735 startPositionUs = Math.max(0, durationUs - 1); 736 } 737 return new MediaPeriodInfo( 738 id, 739 startPositionUs, 740 contentPositionUs, 741 /* endPositionUs= */ C.TIME_UNSET, 742 durationUs, 743 /* isLastInTimelinePeriod= */ false, 744 /* isLastInTimelineWindow= */ false, 745 /* isFinal= */ false); 746 } 747 getMediaPeriodInfoForContent( Timeline timeline, Object periodUid, long startPositionUs, long requestedContentPositionUs, long windowSequenceNumber)748 private MediaPeriodInfo getMediaPeriodInfoForContent( 749 Timeline timeline, 750 Object periodUid, 751 long startPositionUs, 752 long requestedContentPositionUs, 753 long windowSequenceNumber) { 754 timeline.getPeriodByUid(periodUid, period); 755 int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); 756 MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex); 757 boolean isLastInPeriod = isLastInPeriod(id); 758 boolean isLastInWindow = isLastInWindow(timeline, id); 759 boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod); 760 long endPositionUs = 761 nextAdGroupIndex != C.INDEX_UNSET 762 ? period.getAdGroupTimeUs(nextAdGroupIndex) 763 : C.TIME_UNSET; 764 long durationUs = 765 endPositionUs == C.TIME_UNSET || endPositionUs == C.TIME_END_OF_SOURCE 766 ? period.durationUs 767 : endPositionUs; 768 if (durationUs != C.TIME_UNSET && startPositionUs >= durationUs) { 769 // Ensure start position doesn't exceed duration. 770 startPositionUs = Math.max(0, durationUs - 1); 771 } 772 return new MediaPeriodInfo( 773 id, 774 startPositionUs, 775 requestedContentPositionUs, 776 endPositionUs, 777 durationUs, 778 isLastInPeriod, 779 isLastInWindow, 780 isLastInTimeline); 781 } 782 isLastInPeriod(MediaPeriodId id)783 private boolean isLastInPeriod(MediaPeriodId id) { 784 return !id.isAd() && id.nextAdGroupIndex == C.INDEX_UNSET; 785 } 786 isLastInWindow(Timeline timeline, MediaPeriodId id)787 private boolean isLastInWindow(Timeline timeline, MediaPeriodId id) { 788 if (!isLastInPeriod(id)) { 789 return false; 790 } 791 int windowIndex = timeline.getPeriodByUid(id.periodUid, period).windowIndex; 792 int periodIndex = timeline.getIndexOfPeriod(id.periodUid); 793 return timeline.getWindow(windowIndex, window).lastPeriodIndex == periodIndex; 794 } 795 isLastInTimeline( Timeline timeline, MediaPeriodId id, boolean isLastMediaPeriodInPeriod)796 private boolean isLastInTimeline( 797 Timeline timeline, MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { 798 int periodIndex = timeline.getIndexOfPeriod(id.periodUid); 799 int windowIndex = timeline.getPeriod(periodIndex, period).windowIndex; 800 return !timeline.getWindow(windowIndex, window).isDynamic 801 && timeline.isLastPeriod(periodIndex, period, window, repeatMode, shuffleModeEnabled) 802 && isLastMediaPeriodInPeriod; 803 } 804 } 805