• 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 androidx.annotation.Nullable;
19 import com.google.android.exoplayer2.source.ClippingMediaPeriod;
20 import com.google.android.exoplayer2.source.EmptySampleStream;
21 import com.google.android.exoplayer2.source.MediaPeriod;
22 import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
23 import com.google.android.exoplayer2.source.SampleStream;
24 import com.google.android.exoplayer2.source.TrackGroupArray;
25 import com.google.android.exoplayer2.trackselection.TrackSelection;
26 import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
27 import com.google.android.exoplayer2.trackselection.TrackSelector;
28 import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
29 import com.google.android.exoplayer2.upstream.Allocator;
30 import com.google.android.exoplayer2.util.Assertions;
31 import com.google.android.exoplayer2.util.Log;
32 import org.checkerframework.checker.nullness.compatqual.NullableType;
33 
34 /** Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */
35 /* package */ final class MediaPeriodHolder {
36 
37   private static final String TAG = "MediaPeriodHolder";
38 
39   /** The {@link MediaPeriod} wrapped by this class. */
40   public final MediaPeriod mediaPeriod;
41   /** The unique timeline period identifier the media period belongs to. */
42   public final Object uid;
43   /**
44    * The sample streams for each renderer associated with this period. May contain null elements.
45    */
46   public final @NullableType SampleStream[] sampleStreams;
47 
48   /** Whether the media period has finished preparing. */
49   public boolean prepared;
50   /** Whether any of the tracks of this media period are enabled. */
51   public boolean hasEnabledTracks;
52   /** {@link MediaPeriodInfo} about this media period. */
53   public MediaPeriodInfo info;
54   /**
55    * Whether all required renderers have been enabled with the {@link #sampleStreams} for this
56    * {@link #mediaPeriod}. This means either {@link Renderer#enable(RendererConfiguration, Format[],
57    * SampleStream, long, boolean, boolean, long)} or {@link Renderer#replaceStream(Format[],
58    * SampleStream, long)} has been called.
59    */
60   public boolean allRenderersEnabled;
61 
62   private final boolean[] mayRetainStreamFlags;
63   private final RendererCapabilities[] rendererCapabilities;
64   private final TrackSelector trackSelector;
65   private final MediaSourceList mediaSourceList;
66 
67   @Nullable private MediaPeriodHolder next;
68   private TrackGroupArray trackGroups;
69   private TrackSelectorResult trackSelectorResult;
70   private long rendererPositionOffsetUs;
71 
72   /**
73    * Creates a new holder with information required to play it as part of a timeline.
74    *
75    * @param rendererCapabilities The renderer capabilities.
76    * @param rendererPositionOffsetUs The renderer time of the start of the period, in microseconds.
77    * @param trackSelector The track selector.
78    * @param allocator The allocator.
79    * @param mediaSourceList The playlist.
80    * @param info Information used to identify this media period in its timeline period.
81    * @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
82    *     renderer.
83    */
MediaPeriodHolder( RendererCapabilities[] rendererCapabilities, long rendererPositionOffsetUs, TrackSelector trackSelector, Allocator allocator, MediaSourceList mediaSourceList, MediaPeriodInfo info, TrackSelectorResult emptyTrackSelectorResult)84   public MediaPeriodHolder(
85       RendererCapabilities[] rendererCapabilities,
86       long rendererPositionOffsetUs,
87       TrackSelector trackSelector,
88       Allocator allocator,
89       MediaSourceList mediaSourceList,
90       MediaPeriodInfo info,
91       TrackSelectorResult emptyTrackSelectorResult) {
92     this.rendererCapabilities = rendererCapabilities;
93     this.rendererPositionOffsetUs = rendererPositionOffsetUs;
94     this.trackSelector = trackSelector;
95     this.mediaSourceList = mediaSourceList;
96     this.uid = info.id.periodUid;
97     this.info = info;
98     this.trackGroups = TrackGroupArray.EMPTY;
99     this.trackSelectorResult = emptyTrackSelectorResult;
100     sampleStreams = new SampleStream[rendererCapabilities.length];
101     mayRetainStreamFlags = new boolean[rendererCapabilities.length];
102     mediaPeriod =
103         createMediaPeriod(
104             info.id, mediaSourceList, allocator, info.startPositionUs, info.endPositionUs);
105   }
106 
107   /**
108    * Converts time relative to the start of the period to the respective renderer time using {@link
109    * #getRendererOffset()}, in microseconds.
110    */
toRendererTime(long periodTimeUs)111   public long toRendererTime(long periodTimeUs) {
112     return periodTimeUs + getRendererOffset();
113   }
114 
115   /**
116    * Converts renderer time to the respective time relative to the start of the period using {@link
117    * #getRendererOffset()}, in microseconds.
118    */
toPeriodTime(long rendererTimeUs)119   public long toPeriodTime(long rendererTimeUs) {
120     return rendererTimeUs - getRendererOffset();
121   }
122 
123   /** Returns the renderer time of the start of the period, in microseconds. */
getRendererOffset()124   public long getRendererOffset() {
125     return rendererPositionOffsetUs;
126   }
127 
128   /**
129    * Sets the renderer time of the start of the period, in microseconds.
130    *
131    * @param rendererPositionOffsetUs The new renderer position offset, in microseconds.
132    */
setRendererOffset(long rendererPositionOffsetUs)133   public void setRendererOffset(long rendererPositionOffsetUs) {
134     this.rendererPositionOffsetUs = rendererPositionOffsetUs;
135   }
136 
137   /** Returns start position of period in renderer time. */
getStartPositionRendererTime()138   public long getStartPositionRendererTime() {
139     return info.startPositionUs + rendererPositionOffsetUs;
140   }
141 
142   /** Returns whether the period is fully buffered. */
isFullyBuffered()143   public boolean isFullyBuffered() {
144     return prepared
145         && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
146   }
147 
148   /**
149    * Returns the buffered position in microseconds. If the period is buffered to the end, then the
150    * period duration is returned.
151    *
152    * @return The buffered position in microseconds.
153    */
getBufferedPositionUs()154   public long getBufferedPositionUs() {
155     if (!prepared) {
156       return info.startPositionUs;
157     }
158     long bufferedPositionUs =
159         hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
160     return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs;
161   }
162 
163   /**
164    * Returns the next load time relative to the start of the period, or {@link C#TIME_END_OF_SOURCE}
165    * if loading has finished.
166    */
getNextLoadPositionUs()167   public long getNextLoadPositionUs() {
168     return !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
169   }
170 
171   /**
172    * Handles period preparation.
173    *
174    * @param playbackSpeed The current playback speed.
175    * @param timeline The current {@link Timeline}.
176    * @throws ExoPlaybackException If an error occurs during track selection.
177    */
handlePrepared(float playbackSpeed, Timeline timeline)178   public void handlePrepared(float playbackSpeed, Timeline timeline) throws ExoPlaybackException {
179     prepared = true;
180     trackGroups = mediaPeriod.getTrackGroups();
181     TrackSelectorResult selectorResult = selectTracks(playbackSpeed, timeline);
182     long requestedStartPositionUs = info.startPositionUs;
183     if (info.durationUs != C.TIME_UNSET && requestedStartPositionUs >= info.durationUs) {
184       // Make sure start position doesn't exceed period duration.
185       requestedStartPositionUs = Math.max(0, info.durationUs - 1);
186     }
187     long newStartPositionUs =
188         applyTrackSelection(
189             selectorResult, requestedStartPositionUs, /* forceRecreateStreams= */ false);
190     rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs;
191     info = info.copyWithStartPositionUs(newStartPositionUs);
192   }
193 
194   /**
195    * Reevaluates the buffer of the media period at the given renderer position. Should only be
196    * called if this is the loading media period.
197    *
198    * @param rendererPositionUs The playing position in renderer time, in microseconds.
199    */
reevaluateBuffer(long rendererPositionUs)200   public void reevaluateBuffer(long rendererPositionUs) {
201     Assertions.checkState(isLoadingMediaPeriod());
202     if (prepared) {
203       mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
204     }
205   }
206 
207   /**
208    * Continues loading the media period at the given renderer position. Should only be called if
209    * this is the loading media period.
210    *
211    * @param rendererPositionUs The load position in renderer time, in microseconds.
212    */
continueLoading(long rendererPositionUs)213   public void continueLoading(long rendererPositionUs) {
214     Assertions.checkState(isLoadingMediaPeriod());
215     long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
216     mediaPeriod.continueLoading(loadingPeriodPositionUs);
217   }
218 
219   /**
220    * Selects tracks for the period. Must only be called if {@link #prepared} is {@code true}.
221    *
222    * <p>The new track selection needs to be applied with {@link
223    * #applyTrackSelection(TrackSelectorResult, long, boolean)} before taking effect.
224    *
225    * @param playbackSpeed The current playback speed.
226    * @param timeline The current {@link Timeline}.
227    * @return The {@link TrackSelectorResult}.
228    * @throws ExoPlaybackException If an error occurs during track selection.
229    */
selectTracks(float playbackSpeed, Timeline timeline)230   public TrackSelectorResult selectTracks(float playbackSpeed, Timeline timeline)
231       throws ExoPlaybackException {
232     TrackSelectorResult selectorResult =
233         trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline);
234     for (TrackSelection trackSelection : selectorResult.selections.getAll()) {
235       if (trackSelection != null) {
236         trackSelection.onPlaybackSpeed(playbackSpeed);
237       }
238     }
239     return selectorResult;
240   }
241 
242   /**
243    * Applies a {@link TrackSelectorResult} to the period.
244    *
245    * @param trackSelectorResult The {@link TrackSelectorResult} to apply.
246    * @param positionUs The position relative to the start of the period at which to apply the new
247    *     track selections, in microseconds.
248    * @param forceRecreateStreams Whether all streams are forced to be recreated.
249    * @return The actual position relative to the start of the period at which the new track
250    *     selections are applied.
251    */
applyTrackSelection( TrackSelectorResult trackSelectorResult, long positionUs, boolean forceRecreateStreams)252   public long applyTrackSelection(
253       TrackSelectorResult trackSelectorResult, long positionUs, boolean forceRecreateStreams) {
254     return applyTrackSelection(
255         trackSelectorResult,
256         positionUs,
257         forceRecreateStreams,
258         new boolean[rendererCapabilities.length]);
259   }
260 
261   /**
262    * Applies a {@link TrackSelectorResult} to the period.
263    *
264    * @param newTrackSelectorResult The {@link TrackSelectorResult} to apply.
265    * @param positionUs The position relative to the start of the period at which to apply the new
266    *     track selections, in microseconds.
267    * @param forceRecreateStreams Whether all streams are forced to be recreated.
268    * @param streamResetFlags Will be populated to indicate which streams have been reset or were
269    *     newly created.
270    * @return The actual position relative to the start of the period at which the new track
271    *     selections are applied.
272    */
applyTrackSelection( TrackSelectorResult newTrackSelectorResult, long positionUs, boolean forceRecreateStreams, boolean[] streamResetFlags)273   public long applyTrackSelection(
274       TrackSelectorResult newTrackSelectorResult,
275       long positionUs,
276       boolean forceRecreateStreams,
277       boolean[] streamResetFlags) {
278     for (int i = 0; i < newTrackSelectorResult.length; i++) {
279       mayRetainStreamFlags[i] =
280           !forceRecreateStreams && newTrackSelectorResult.isEquivalent(trackSelectorResult, i);
281     }
282 
283     // Undo the effect of previous call to associate no-sample renderers with empty tracks
284     // so the mediaPeriod receives back whatever it sent us before.
285     disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams);
286     disableTrackSelectionsInResult();
287     trackSelectorResult = newTrackSelectorResult;
288     enableTrackSelectionsInResult();
289     // Disable streams on the period and get new streams for updated/newly-enabled tracks.
290     TrackSelectionArray trackSelections = newTrackSelectorResult.selections;
291     positionUs =
292         mediaPeriod.selectTracks(
293             trackSelections.getAll(),
294             mayRetainStreamFlags,
295             sampleStreams,
296             streamResetFlags,
297             positionUs);
298     associateNoSampleRenderersWithEmptySampleStream(sampleStreams);
299 
300     // Update whether we have enabled tracks and sanity check the expected streams are non-null.
301     hasEnabledTracks = false;
302     for (int i = 0; i < sampleStreams.length; i++) {
303       if (sampleStreams[i] != null) {
304         Assertions.checkState(newTrackSelectorResult.isRendererEnabled(i));
305         // hasEnabledTracks should be true only when non-empty streams exists.
306         if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) {
307           hasEnabledTracks = true;
308         }
309       } else {
310         Assertions.checkState(trackSelections.get(i) == null);
311       }
312     }
313     return positionUs;
314   }
315 
316   /** Releases the media period. No other method should be called after the release. */
release()317   public void release() {
318     disableTrackSelectionsInResult();
319     releaseMediaPeriod(info.endPositionUs, mediaSourceList, mediaPeriod);
320   }
321 
322   /**
323    * Sets the next media period holder in the queue.
324    *
325    * @param nextMediaPeriodHolder The next holder, or null if this will be the new loading media
326    *     period holder at the end of the queue.
327    */
setNext(@ullable MediaPeriodHolder nextMediaPeriodHolder)328   public void setNext(@Nullable MediaPeriodHolder nextMediaPeriodHolder) {
329     if (nextMediaPeriodHolder == next) {
330       return;
331     }
332     disableTrackSelectionsInResult();
333     next = nextMediaPeriodHolder;
334     enableTrackSelectionsInResult();
335   }
336 
337   /**
338    * Returns the next media period holder in the queue, or null if this is the last media period
339    * (and thus the loading media period).
340    */
341   @Nullable
getNext()342   public MediaPeriodHolder getNext() {
343     return next;
344   }
345 
346   /** Returns the {@link TrackGroupArray} exposed by this media period. */
getTrackGroups()347   public TrackGroupArray getTrackGroups() {
348     return trackGroups;
349   }
350 
351   /** Returns the {@link TrackSelectorResult} which is currently applied. */
getTrackSelectorResult()352   public TrackSelectorResult getTrackSelectorResult() {
353     return trackSelectorResult;
354   }
355 
enableTrackSelectionsInResult()356   private void enableTrackSelectionsInResult() {
357     if (!isLoadingMediaPeriod()) {
358       return;
359     }
360     for (int i = 0; i < trackSelectorResult.length; i++) {
361       boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
362       TrackSelection trackSelection = trackSelectorResult.selections.get(i);
363       if (rendererEnabled && trackSelection != null) {
364         trackSelection.enable();
365       }
366     }
367   }
368 
disableTrackSelectionsInResult()369   private void disableTrackSelectionsInResult() {
370     if (!isLoadingMediaPeriod()) {
371       return;
372     }
373     for (int i = 0; i < trackSelectorResult.length; i++) {
374       boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
375       TrackSelection trackSelection = trackSelectorResult.selections.get(i);
376       if (rendererEnabled && trackSelection != null) {
377         trackSelection.disable();
378       }
379     }
380   }
381 
382   /**
383    * For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy {@link
384    * EmptySampleStream} that was associated with it.
385    */
disassociateNoSampleRenderersWithEmptySampleStream( @ullableType SampleStream[] sampleStreams)386   private void disassociateNoSampleRenderersWithEmptySampleStream(
387       @NullableType SampleStream[] sampleStreams) {
388     for (int i = 0; i < rendererCapabilities.length; i++) {
389       if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE) {
390         sampleStreams[i] = null;
391       }
392     }
393   }
394 
395   /**
396    * For each renderer of type {@link C#TRACK_TYPE_NONE} that was enabled, we will associate it with
397    * a dummy {@link EmptySampleStream}.
398    */
associateNoSampleRenderersWithEmptySampleStream( @ullableType SampleStream[] sampleStreams)399   private void associateNoSampleRenderersWithEmptySampleStream(
400       @NullableType SampleStream[] sampleStreams) {
401     for (int i = 0; i < rendererCapabilities.length; i++) {
402       if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE
403           && trackSelectorResult.isRendererEnabled(i)) {
404         sampleStreams[i] = new EmptySampleStream();
405       }
406     }
407   }
408 
isLoadingMediaPeriod()409   private boolean isLoadingMediaPeriod() {
410     return next == null;
411   }
412 
413   /** Returns a media period corresponding to the given {@code id}. */
createMediaPeriod( MediaPeriodId id, MediaSourceList mediaSourceList, Allocator allocator, long startPositionUs, long endPositionUs)414   private static MediaPeriod createMediaPeriod(
415       MediaPeriodId id,
416       MediaSourceList mediaSourceList,
417       Allocator allocator,
418       long startPositionUs,
419       long endPositionUs) {
420     MediaPeriod mediaPeriod = mediaSourceList.createPeriod(id, allocator, startPositionUs);
421     if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
422       mediaPeriod =
423           new ClippingMediaPeriod(
424               mediaPeriod, /* enableInitialDiscontinuity= */ true, /* startUs= */ 0, endPositionUs);
425     }
426     return mediaPeriod;
427   }
428 
429   /** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
releaseMediaPeriod( long endPositionUs, MediaSourceList mediaSourceList, MediaPeriod mediaPeriod)430   private static void releaseMediaPeriod(
431       long endPositionUs, MediaSourceList mediaSourceList, MediaPeriod mediaPeriod) {
432     try {
433       if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
434         mediaSourceList.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
435       } else {
436         mediaSourceList.releasePeriod(mediaPeriod);
437       }
438     } catch (RuntimeException e) {
439       // There's nothing we can do.
440       Log.e(TAG, "Period release failed.", e);
441     }
442   }
443 }
444