mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Move MediaPeriodHolder and MediaPeriodHolderQueue to top level
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=183065262
This commit is contained in:
parent
c8298e70b1
commit
b8bb1f642b
3 changed files with 430 additions and 382 deletions
|
|
@ -28,14 +28,11 @@ import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
|
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
|
||||||
import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo;
|
import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo;
|
||||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||||
import com.google.android.exoplayer2.source.ClippingMediaPeriod;
|
|
||||||
import com.google.android.exoplayer2.source.EmptySampleStream;
|
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -1688,385 +1685,6 @@ import java.util.Collections;
|
||||||
return formats;
|
return formats;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds a queue of {@link MediaPeriodHolder}s from the currently playing period holder at the
|
|
||||||
* front to the loading period holder at the end of the queue. Also has a reference to the reading
|
|
||||||
* period holder.
|
|
||||||
*/
|
|
||||||
private static final class MediaPeriodHolderQueue {
|
|
||||||
|
|
||||||
private MediaPeriodHolder playing;
|
|
||||||
private MediaPeriodHolder reading;
|
|
||||||
private MediaPeriodHolder loading;
|
|
||||||
private int length;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the loading period holder which is at the end of the queue, or null if the queue is
|
|
||||||
* empty.
|
|
||||||
*/
|
|
||||||
public MediaPeriodHolder getLoadingPeriod() {
|
|
||||||
return loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the playing period holder which is at the front of the queue, or null if the queue is
|
|
||||||
* empty or hasn't started playing.
|
|
||||||
*/
|
|
||||||
public MediaPeriodHolder getPlayingPeriod() {
|
|
||||||
return playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the reading period holder, or null if the queue is empty or the player hasn't started
|
|
||||||
* reading.
|
|
||||||
*/
|
|
||||||
public MediaPeriodHolder getReadingPeriod() {
|
|
||||||
return reading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the period holder in the front of the queue which is the playing period holder when
|
|
||||||
* playing, or null if the queue is empty.
|
|
||||||
*/
|
|
||||||
public MediaPeriodHolder getFrontPeriod() {
|
|
||||||
return hasPlayingPeriod() ? playing : loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the current length of the queue. */
|
|
||||||
public int getLength() {
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns whether the reading and playing period holders are set. */
|
|
||||||
public boolean hasPlayingPeriod() {
|
|
||||||
return playing != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Continues reading from the next period holder in the queue.
|
|
||||||
*
|
|
||||||
* @return The updated reading period holder.
|
|
||||||
*/
|
|
||||||
public MediaPeriodHolder advanceReadingPeriod() {
|
|
||||||
Assertions.checkState(reading != null && reading.next != null);
|
|
||||||
reading = reading.next;
|
|
||||||
return reading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Enqueues a new period holder at the end, which becomes the new loading period holder. */
|
|
||||||
public void enqueueLoadingPeriod(MediaPeriodHolder mediaPeriodHolder) {
|
|
||||||
Assertions.checkState(mediaPeriodHolder != null);
|
|
||||||
if (loading != null) {
|
|
||||||
Assertions.checkState(hasPlayingPeriod());
|
|
||||||
loading.next = mediaPeriodHolder;
|
|
||||||
}
|
|
||||||
loading = mediaPeriodHolder;
|
|
||||||
length++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dequeues the playing period holder from the front of the queue and advances the playing
|
|
||||||
* period holder to be the next item in the queue. If the playing period holder is unset, set it
|
|
||||||
* to the item in the front of the queue.
|
|
||||||
*
|
|
||||||
* @return The updated playing period holder, or null if the queue is or becomes empty.
|
|
||||||
*/
|
|
||||||
public MediaPeriodHolder advancePlayingPeriod() {
|
|
||||||
if (playing != null) {
|
|
||||||
if (playing == reading) {
|
|
||||||
reading = playing.next;
|
|
||||||
}
|
|
||||||
playing.release();
|
|
||||||
playing = playing.next;
|
|
||||||
length--;
|
|
||||||
if (length == 0) {
|
|
||||||
loading = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
playing = loading;
|
|
||||||
reading = loading;
|
|
||||||
}
|
|
||||||
return playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all period holders after the given period holder. This process may also remove the
|
|
||||||
* currently reading period holder. If that is the case, the reading period holder is set to be
|
|
||||||
* the same as the playing period holder at the front of the queue.
|
|
||||||
*
|
|
||||||
* @param mediaPeriodHolder The media period holder that shall be the new end of the queue.
|
|
||||||
* @return Whether the reading period has been removed.
|
|
||||||
*/
|
|
||||||
public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) {
|
|
||||||
Assertions.checkState(mediaPeriodHolder != null);
|
|
||||||
boolean removedReading = false;
|
|
||||||
loading = mediaPeriodHolder;
|
|
||||||
while (mediaPeriodHolder.next != null) {
|
|
||||||
mediaPeriodHolder = mediaPeriodHolder.next;
|
|
||||||
if (mediaPeriodHolder == reading) {
|
|
||||||
reading = playing;
|
|
||||||
removedReading = true;
|
|
||||||
}
|
|
||||||
mediaPeriodHolder.release();
|
|
||||||
length--;
|
|
||||||
}
|
|
||||||
loading.next = null;
|
|
||||||
return removedReading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clears the queue. */
|
|
||||||
public void clear() {
|
|
||||||
MediaPeriodHolder front = getFrontPeriod();
|
|
||||||
if (front != null) {
|
|
||||||
front.release();
|
|
||||||
removeAfter(front);
|
|
||||||
}
|
|
||||||
playing = null;
|
|
||||||
loading = null;
|
|
||||||
reading = null;
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
|
|
||||||
*/
|
|
||||||
private static final class MediaPeriodHolder {
|
|
||||||
|
|
||||||
public final MediaPeriod mediaPeriod;
|
|
||||||
public final Object uid;
|
|
||||||
public final SampleStream[] sampleStreams;
|
|
||||||
public final boolean[] mayRetainStreamFlags;
|
|
||||||
|
|
||||||
public long rendererPositionOffsetUs;
|
|
||||||
public MediaPeriodInfo info;
|
|
||||||
public boolean prepared;
|
|
||||||
public boolean hasEnabledTracks;
|
|
||||||
public MediaPeriodHolder next;
|
|
||||||
public TrackSelectorResult trackSelectorResult;
|
|
||||||
|
|
||||||
private final Renderer[] renderers;
|
|
||||||
private final RendererCapabilities[] rendererCapabilities;
|
|
||||||
private final TrackSelector trackSelector;
|
|
||||||
private final LoadControl loadControl;
|
|
||||||
private final MediaSource mediaSource;
|
|
||||||
|
|
||||||
private TrackSelectorResult periodTrackSelectorResult;
|
|
||||||
|
|
||||||
public MediaPeriodHolder(
|
|
||||||
Renderer[] renderers,
|
|
||||||
RendererCapabilities[] rendererCapabilities,
|
|
||||||
long rendererPositionOffsetUs,
|
|
||||||
TrackSelector trackSelector,
|
|
||||||
LoadControl loadControl,
|
|
||||||
MediaSource mediaSource,
|
|
||||||
Object periodUid,
|
|
||||||
MediaPeriodInfo info) {
|
|
||||||
this.renderers = renderers;
|
|
||||||
this.rendererCapabilities = rendererCapabilities;
|
|
||||||
this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs;
|
|
||||||
this.trackSelector = trackSelector;
|
|
||||||
this.loadControl = loadControl;
|
|
||||||
this.mediaSource = mediaSource;
|
|
||||||
this.uid = Assertions.checkNotNull(periodUid);
|
|
||||||
this.info = info;
|
|
||||||
sampleStreams = new SampleStream[renderers.length];
|
|
||||||
mayRetainStreamFlags = new boolean[renderers.length];
|
|
||||||
MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, loadControl.getAllocator());
|
|
||||||
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
|
|
||||||
ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true);
|
|
||||||
clippingMediaPeriod.setClipping(0, info.endPositionUs);
|
|
||||||
mediaPeriod = clippingMediaPeriod;
|
|
||||||
}
|
|
||||||
this.mediaPeriod = mediaPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long toRendererTime(long periodTimeUs) {
|
|
||||||
return periodTimeUs + getRendererOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long toPeriodTime(long rendererTimeUs) {
|
|
||||||
return rendererTimeUs - getRendererOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRendererOffset() {
|
|
||||||
return rendererPositionOffsetUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFullyBuffered() {
|
|
||||||
return prepared
|
|
||||||
&& (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean haveSufficientBuffer(long rendererPositionUs, float playbackSpeed,
|
|
||||||
boolean rebuffering) {
|
|
||||||
long bufferedPositionUs = !prepared ? info.startPositionUs
|
|
||||||
: mediaPeriod.getBufferedPositionUs();
|
|
||||||
if (bufferedPositionUs == C.TIME_END_OF_SOURCE) {
|
|
||||||
if (info.isFinal) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bufferedPositionUs = info.durationUs;
|
|
||||||
}
|
|
||||||
return loadControl.shouldStartPlayback(bufferedPositionUs - toPeriodTime(rendererPositionUs),
|
|
||||||
playbackSpeed, rebuffering);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handlePrepared(float playbackSpeed) throws ExoPlaybackException {
|
|
||||||
prepared = true;
|
|
||||||
selectTracks(playbackSpeed);
|
|
||||||
long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false);
|
|
||||||
rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs;
|
|
||||||
info = info.copyWithStartPositionUs(newStartPositionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reevaluateBuffer(long rendererPositionUs) {
|
|
||||||
if (prepared) {
|
|
||||||
mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) {
|
|
||||||
long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
|
|
||||||
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
long bufferedDurationUs = nextLoadPositionUs - toPeriodTime(rendererPositionUs);
|
|
||||||
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void continueLoading(long rendererPositionUs) {
|
|
||||||
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
|
|
||||||
mediaPeriod.continueLoading(loadingPeriodPositionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean selectTracks(float playbackSpeed) throws ExoPlaybackException {
|
|
||||||
TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities,
|
|
||||||
mediaPeriod.getTrackGroups());
|
|
||||||
if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
trackSelectorResult = selectorResult;
|
|
||||||
for (TrackSelection trackSelection : trackSelectorResult.selections.getAll()) {
|
|
||||||
if (trackSelection != null) {
|
|
||||||
trackSelection.onPlaybackSpeed(playbackSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams) {
|
|
||||||
return updatePeriodTrackSelection(positionUs, forceRecreateStreams,
|
|
||||||
new boolean[renderers.length]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams,
|
|
||||||
boolean[] streamResetFlags) {
|
|
||||||
TrackSelectionArray trackSelections = trackSelectorResult.selections;
|
|
||||||
for (int i = 0; i < trackSelections.length; i++) {
|
|
||||||
mayRetainStreamFlags[i] = !forceRecreateStreams
|
|
||||||
&& trackSelectorResult.isEquivalent(periodTrackSelectorResult, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Undo the effect of previous call to associate no-sample renderers with empty tracks
|
|
||||||
// so the mediaPeriod receives back whatever it sent us before.
|
|
||||||
disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams);
|
|
||||||
updatePeriodTrackSelectorResult(trackSelectorResult);
|
|
||||||
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
|
|
||||||
positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags,
|
|
||||||
sampleStreams, streamResetFlags, positionUs);
|
|
||||||
associateNoSampleRenderersWithEmptySampleStream(sampleStreams);
|
|
||||||
|
|
||||||
// Update whether we have enabled tracks and sanity check the expected streams are non-null.
|
|
||||||
hasEnabledTracks = false;
|
|
||||||
for (int i = 0; i < sampleStreams.length; i++) {
|
|
||||||
if (sampleStreams[i] != null) {
|
|
||||||
Assertions.checkState(trackSelectorResult.renderersEnabled[i]);
|
|
||||||
// hasEnabledTracks should be true only when non-empty streams exists.
|
|
||||||
if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) {
|
|
||||||
hasEnabledTracks = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Assertions.checkState(trackSelections.get(i) == null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The track selection has changed.
|
|
||||||
loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
|
|
||||||
return positionUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void release() {
|
|
||||||
updatePeriodTrackSelectorResult(null);
|
|
||||||
try {
|
|
||||||
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
|
|
||||||
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
|
|
||||||
} else {
|
|
||||||
mediaSource.releasePeriod(mediaPeriod);
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
// There's nothing we can do.
|
|
||||||
Log.e(TAG, "Period release failed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePeriodTrackSelectorResult(TrackSelectorResult trackSelectorResult) {
|
|
||||||
if (periodTrackSelectorResult != null) {
|
|
||||||
disableTrackSelectionsInResult(periodTrackSelectorResult);
|
|
||||||
}
|
|
||||||
periodTrackSelectorResult = trackSelectorResult;
|
|
||||||
if (periodTrackSelectorResult != null) {
|
|
||||||
enableTrackSelectionsInResult(periodTrackSelectorResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) {
|
|
||||||
for (int i = 0; i < trackSelectorResult.renderersEnabled.length; i++) {
|
|
||||||
boolean rendererEnabled = trackSelectorResult.renderersEnabled[i];
|
|
||||||
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
|
|
||||||
if (rendererEnabled && trackSelection != null) {
|
|
||||||
trackSelection.enable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) {
|
|
||||||
for (int i = 0; i < trackSelectorResult.renderersEnabled.length; i++) {
|
|
||||||
boolean rendererEnabled = trackSelectorResult.renderersEnabled[i];
|
|
||||||
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
|
|
||||||
if (rendererEnabled && trackSelection != null) {
|
|
||||||
trackSelection.disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy
|
|
||||||
* {@link EmptySampleStream} that was associated with it.
|
|
||||||
*/
|
|
||||||
private void disassociateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) {
|
|
||||||
for (int i = 0; i < rendererCapabilities.length; i++) {
|
|
||||||
if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE) {
|
|
||||||
sampleStreams[i] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each renderer of type {@link C#TRACK_TYPE_NONE} that was enabled, we will
|
|
||||||
* associate it with a dummy {@link EmptySampleStream}.
|
|
||||||
*/
|
|
||||||
private void associateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) {
|
|
||||||
for (int i = 0; i < rendererCapabilities.length; i++) {
|
|
||||||
if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE
|
|
||||||
&& trackSelectorResult.renderersEnabled[i]) {
|
|
||||||
sampleStreams[i] = new EmptySampleStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class SeekPosition {
|
private static final class SeekPosition {
|
||||||
|
|
||||||
public final Timeline timeline;
|
public final Timeline timeline;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo;
|
||||||
|
import com.google.android.exoplayer2.source.ClippingMediaPeriod;
|
||||||
|
import com.google.android.exoplayer2.source.EmptySampleStream;
|
||||||
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
/** Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */
|
||||||
|
/* package */ final class MediaPeriodHolder {
|
||||||
|
|
||||||
|
private static final String TAG = "MediaPeriodHolder";
|
||||||
|
|
||||||
|
public final MediaPeriod mediaPeriod;
|
||||||
|
public final Object uid;
|
||||||
|
public final SampleStream[] sampleStreams;
|
||||||
|
public final boolean[] mayRetainStreamFlags;
|
||||||
|
|
||||||
|
public long rendererPositionOffsetUs;
|
||||||
|
public MediaPeriodInfo info;
|
||||||
|
public boolean prepared;
|
||||||
|
public boolean hasEnabledTracks;
|
||||||
|
public MediaPeriodHolder next;
|
||||||
|
public TrackSelectorResult trackSelectorResult;
|
||||||
|
|
||||||
|
private final Renderer[] renderers;
|
||||||
|
private final RendererCapabilities[] rendererCapabilities;
|
||||||
|
private final TrackSelector trackSelector;
|
||||||
|
private final LoadControl loadControl;
|
||||||
|
private final MediaSource mediaSource;
|
||||||
|
|
||||||
|
private TrackSelectorResult periodTrackSelectorResult;
|
||||||
|
|
||||||
|
public MediaPeriodHolder(
|
||||||
|
Renderer[] renderers,
|
||||||
|
RendererCapabilities[] rendererCapabilities,
|
||||||
|
long rendererPositionOffsetUs,
|
||||||
|
TrackSelector trackSelector,
|
||||||
|
LoadControl loadControl,
|
||||||
|
MediaSource mediaSource,
|
||||||
|
Object periodUid,
|
||||||
|
MediaPeriodInfo info) {
|
||||||
|
this.renderers = renderers;
|
||||||
|
this.rendererCapabilities = rendererCapabilities;
|
||||||
|
this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs;
|
||||||
|
this.trackSelector = trackSelector;
|
||||||
|
this.loadControl = loadControl;
|
||||||
|
this.mediaSource = mediaSource;
|
||||||
|
this.uid = Assertions.checkNotNull(periodUid);
|
||||||
|
this.info = info;
|
||||||
|
sampleStreams = new SampleStream[renderers.length];
|
||||||
|
mayRetainStreamFlags = new boolean[renderers.length];
|
||||||
|
MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, loadControl.getAllocator());
|
||||||
|
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
|
||||||
|
ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true);
|
||||||
|
clippingMediaPeriod.setClipping(0, info.endPositionUs);
|
||||||
|
mediaPeriod = clippingMediaPeriod;
|
||||||
|
}
|
||||||
|
this.mediaPeriod = mediaPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long toRendererTime(long periodTimeUs) {
|
||||||
|
return periodTimeUs + getRendererOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long toPeriodTime(long rendererTimeUs) {
|
||||||
|
return rendererTimeUs - getRendererOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRendererOffset() {
|
||||||
|
return rendererPositionOffsetUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullyBuffered() {
|
||||||
|
return prepared
|
||||||
|
&& (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean haveSufficientBuffer(
|
||||||
|
long rendererPositionUs, float playbackSpeed, boolean rebuffering) {
|
||||||
|
long bufferedPositionUs =
|
||||||
|
!prepared ? info.startPositionUs : mediaPeriod.getBufferedPositionUs();
|
||||||
|
if (bufferedPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
|
if (info.isFinal) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bufferedPositionUs = info.durationUs;
|
||||||
|
}
|
||||||
|
return loadControl.shouldStartPlayback(
|
||||||
|
bufferedPositionUs - toPeriodTime(rendererPositionUs), playbackSpeed, rebuffering);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePrepared(float playbackSpeed) throws ExoPlaybackException {
|
||||||
|
prepared = true;
|
||||||
|
selectTracks(playbackSpeed);
|
||||||
|
long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false);
|
||||||
|
rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs;
|
||||||
|
info = info.copyWithStartPositionUs(newStartPositionUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reevaluateBuffer(long rendererPositionUs) {
|
||||||
|
if (prepared) {
|
||||||
|
mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) {
|
||||||
|
long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
|
||||||
|
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
long bufferedDurationUs = nextLoadPositionUs - toPeriodTime(rendererPositionUs);
|
||||||
|
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void continueLoading(long rendererPositionUs) {
|
||||||
|
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
|
||||||
|
mediaPeriod.continueLoading(loadingPeriodPositionUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean selectTracks(float playbackSpeed) throws ExoPlaybackException {
|
||||||
|
TrackSelectorResult selectorResult =
|
||||||
|
trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups());
|
||||||
|
if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
trackSelectorResult = selectorResult;
|
||||||
|
for (TrackSelection trackSelection : trackSelectorResult.selections.getAll()) {
|
||||||
|
if (trackSelection != null) {
|
||||||
|
trackSelection.onPlaybackSpeed(playbackSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams) {
|
||||||
|
return updatePeriodTrackSelection(
|
||||||
|
positionUs, forceRecreateStreams, new boolean[renderers.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long updatePeriodTrackSelection(
|
||||||
|
long positionUs, boolean forceRecreateStreams, boolean[] streamResetFlags) {
|
||||||
|
TrackSelectionArray trackSelections = trackSelectorResult.selections;
|
||||||
|
for (int i = 0; i < trackSelections.length; i++) {
|
||||||
|
mayRetainStreamFlags[i] =
|
||||||
|
!forceRecreateStreams && trackSelectorResult.isEquivalent(periodTrackSelectorResult, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo the effect of previous call to associate no-sample renderers with empty tracks
|
||||||
|
// so the mediaPeriod receives back whatever it sent us before.
|
||||||
|
disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams);
|
||||||
|
updatePeriodTrackSelectorResult(trackSelectorResult);
|
||||||
|
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
|
||||||
|
positionUs =
|
||||||
|
mediaPeriod.selectTracks(
|
||||||
|
trackSelections.getAll(),
|
||||||
|
mayRetainStreamFlags,
|
||||||
|
sampleStreams,
|
||||||
|
streamResetFlags,
|
||||||
|
positionUs);
|
||||||
|
associateNoSampleRenderersWithEmptySampleStream(sampleStreams);
|
||||||
|
|
||||||
|
// Update whether we have enabled tracks and sanity check the expected streams are non-null.
|
||||||
|
hasEnabledTracks = false;
|
||||||
|
for (int i = 0; i < sampleStreams.length; i++) {
|
||||||
|
if (sampleStreams[i] != null) {
|
||||||
|
Assertions.checkState(trackSelectorResult.renderersEnabled[i]);
|
||||||
|
// hasEnabledTracks should be true only when non-empty streams exists.
|
||||||
|
if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) {
|
||||||
|
hasEnabledTracks = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Assertions.checkState(trackSelections.get(i) == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The track selection has changed.
|
||||||
|
loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
|
||||||
|
return positionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
updatePeriodTrackSelectorResult(null);
|
||||||
|
try {
|
||||||
|
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
|
||||||
|
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
|
||||||
|
} else {
|
||||||
|
mediaSource.releasePeriod(mediaPeriod);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// There's nothing we can do.
|
||||||
|
Log.e(TAG, "Period release failed.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePeriodTrackSelectorResult(TrackSelectorResult trackSelectorResult) {
|
||||||
|
if (periodTrackSelectorResult != null) {
|
||||||
|
disableTrackSelectionsInResult(periodTrackSelectorResult);
|
||||||
|
}
|
||||||
|
periodTrackSelectorResult = trackSelectorResult;
|
||||||
|
if (periodTrackSelectorResult != null) {
|
||||||
|
enableTrackSelectionsInResult(periodTrackSelectorResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) {
|
||||||
|
for (int i = 0; i < trackSelectorResult.renderersEnabled.length; i++) {
|
||||||
|
boolean rendererEnabled = trackSelectorResult.renderersEnabled[i];
|
||||||
|
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
|
||||||
|
if (rendererEnabled && trackSelection != null) {
|
||||||
|
trackSelection.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) {
|
||||||
|
for (int i = 0; i < trackSelectorResult.renderersEnabled.length; i++) {
|
||||||
|
boolean rendererEnabled = trackSelectorResult.renderersEnabled[i];
|
||||||
|
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
|
||||||
|
if (rendererEnabled && trackSelection != null) {
|
||||||
|
trackSelection.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy {@link
|
||||||
|
* EmptySampleStream} that was associated with it.
|
||||||
|
*/
|
||||||
|
private void disassociateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) {
|
||||||
|
for (int i = 0; i < rendererCapabilities.length; i++) {
|
||||||
|
if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE) {
|
||||||
|
sampleStreams[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each renderer of type {@link C#TRACK_TYPE_NONE} that was enabled, we will associate it with
|
||||||
|
* a dummy {@link EmptySampleStream}.
|
||||||
|
*/
|
||||||
|
private void associateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) {
|
||||||
|
for (int i = 0; i < rendererCapabilities.length; i++) {
|
||||||
|
if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE
|
||||||
|
&& trackSelectorResult.renderersEnabled[i]) {
|
||||||
|
sampleStreams[i] = new EmptySampleStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds a queue of {@link MediaPeriodHolder}s from the currently playing period holder at the front
|
||||||
|
* to the loading period holder at the end of the queue. Also has a reference to the reading period
|
||||||
|
* holder.
|
||||||
|
*/
|
||||||
|
/* package */ final class MediaPeriodHolderQueue {
|
||||||
|
|
||||||
|
private MediaPeriodHolder playing;
|
||||||
|
private MediaPeriodHolder reading;
|
||||||
|
private MediaPeriodHolder loading;
|
||||||
|
private int length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the loading period holder which is at the end of the queue, or null if the queue is
|
||||||
|
* empty.
|
||||||
|
*/
|
||||||
|
public MediaPeriodHolder getLoadingPeriod() {
|
||||||
|
return loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the playing period holder which is at the front of the queue, or null if the queue is
|
||||||
|
* empty or hasn't started playing.
|
||||||
|
*/
|
||||||
|
public MediaPeriodHolder getPlayingPeriod() {
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reading period holder, or null if the queue is empty or the player hasn't started
|
||||||
|
* reading.
|
||||||
|
*/
|
||||||
|
public MediaPeriodHolder getReadingPeriod() {
|
||||||
|
return reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the period holder in the front of the queue which is the playing period holder when
|
||||||
|
* playing, or null if the queue is empty.
|
||||||
|
*/
|
||||||
|
public MediaPeriodHolder getFrontPeriod() {
|
||||||
|
return hasPlayingPeriod() ? playing : loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the current length of the queue. */
|
||||||
|
public int getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the reading and playing period holders are set. */
|
||||||
|
public boolean hasPlayingPeriod() {
|
||||||
|
return playing != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continues reading from the next period holder in the queue.
|
||||||
|
*
|
||||||
|
* @return The updated reading period holder.
|
||||||
|
*/
|
||||||
|
public MediaPeriodHolder advanceReadingPeriod() {
|
||||||
|
Assertions.checkState(reading != null && reading.next != null);
|
||||||
|
reading = reading.next;
|
||||||
|
return reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enqueues a new period holder at the end, which becomes the new loading period holder. */
|
||||||
|
public void enqueueLoadingPeriod(MediaPeriodHolder mediaPeriodHolder) {
|
||||||
|
Assertions.checkState(mediaPeriodHolder != null);
|
||||||
|
if (loading != null) {
|
||||||
|
Assertions.checkState(hasPlayingPeriod());
|
||||||
|
loading.next = mediaPeriodHolder;
|
||||||
|
}
|
||||||
|
loading = mediaPeriodHolder;
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dequeues the playing period holder from the front of the queue and advances the playing period
|
||||||
|
* holder to be the next item in the queue. If the playing period holder is unset, set it to the
|
||||||
|
* item in the front of the queue.
|
||||||
|
*
|
||||||
|
* @return The updated playing period holder, or null if the queue is or becomes empty.
|
||||||
|
*/
|
||||||
|
public MediaPeriodHolder advancePlayingPeriod() {
|
||||||
|
if (playing != null) {
|
||||||
|
if (playing == reading) {
|
||||||
|
reading = playing.next;
|
||||||
|
}
|
||||||
|
playing.release();
|
||||||
|
playing = playing.next;
|
||||||
|
length--;
|
||||||
|
if (length == 0) {
|
||||||
|
loading = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playing = loading;
|
||||||
|
reading = loading;
|
||||||
|
}
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all period holders after the given period holder. This process may also remove the
|
||||||
|
* currently reading period holder. If that is the case, the reading period holder is set to be
|
||||||
|
* the same as the playing period holder at the front of the queue.
|
||||||
|
*
|
||||||
|
* @param mediaPeriodHolder The media period holder that shall be the new end of the queue.
|
||||||
|
* @return Whether the reading period has been removed.
|
||||||
|
*/
|
||||||
|
public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) {
|
||||||
|
Assertions.checkState(mediaPeriodHolder != null);
|
||||||
|
boolean removedReading = false;
|
||||||
|
loading = mediaPeriodHolder;
|
||||||
|
while (mediaPeriodHolder.next != null) {
|
||||||
|
mediaPeriodHolder = mediaPeriodHolder.next;
|
||||||
|
if (mediaPeriodHolder == reading) {
|
||||||
|
reading = playing;
|
||||||
|
removedReading = true;
|
||||||
|
}
|
||||||
|
mediaPeriodHolder.release();
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
loading.next = null;
|
||||||
|
return removedReading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the queue. */
|
||||||
|
public void clear() {
|
||||||
|
MediaPeriodHolder front = getFrontPeriod();
|
||||||
|
if (front != null) {
|
||||||
|
front.release();
|
||||||
|
removeAfter(front);
|
||||||
|
}
|
||||||
|
playing = null;
|
||||||
|
loading = null;
|
||||||
|
reading = null;
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue