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