CompositeSequenceableLoader.java
/*
* Copyright (C) 2016 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 androidx.media3.exoplayer.source;
import static androidx.media3.common.util.Assertions.checkArgument;
import static java.lang.Math.min;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.LoadingInfo;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
/** A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s. */
@UnstableApi
public final class CompositeSequenceableLoader implements SequenceableLoader {
private final ImmutableList<SequenceableLoaderWithTrackTypes> loadersWithTrackTypes;
private long lastAudioVideoBufferedPositionUs;
/**
* @deprecated Use {@link CompositeSequenceableLoader#CompositeSequenceableLoader(List, List)}
* instead.
*/
@Deprecated
public CompositeSequenceableLoader(SequenceableLoader[] loaders) {
this(
ImmutableList.copyOf(loaders),
Collections.nCopies(loaders.length, ImmutableList.of(C.TRACK_TYPE_UNKNOWN)));
}
public CompositeSequenceableLoader(
List<? extends SequenceableLoader> loaders,
List<List<@C.TrackType Integer>> loaderTrackTypes) {
ImmutableList.Builder<SequenceableLoaderWithTrackTypes> loaderAndTrackTypes =
ImmutableList.builder();
checkArgument(loaders.size() == loaderTrackTypes.size());
for (int i = 0; i < loaders.size(); i++) {
loaderAndTrackTypes.add(
new SequenceableLoaderWithTrackTypes(loaders.get(i), loaderTrackTypes.get(i)));
}
this.loadersWithTrackTypes = loaderAndTrackTypes.build();
this.lastAudioVideoBufferedPositionUs = C.TIME_UNSET;
}
@Override
public long getBufferedPositionUs() {
long bufferedPositionUs = Long.MAX_VALUE;
long bufferedPositionAudioVideoUs = Long.MAX_VALUE;
for (int i = 0; i < loadersWithTrackTypes.size(); i++) {
SequenceableLoaderWithTrackTypes loader = loadersWithTrackTypes.get(i);
long loaderBufferedPositionUs = loader.getBufferedPositionUs();
if (loader.getTrackTypes().contains(C.TRACK_TYPE_AUDIO)
|| loader.getTrackTypes().contains(C.TRACK_TYPE_VIDEO)
|| loader.getTrackTypes().contains(C.TRACK_TYPE_IMAGE)) {
if (loaderBufferedPositionUs != C.TIME_END_OF_SOURCE) {
bufferedPositionAudioVideoUs =
min(bufferedPositionAudioVideoUs, loaderBufferedPositionUs);
}
}
if (loaderBufferedPositionUs != C.TIME_END_OF_SOURCE) {
bufferedPositionUs = min(bufferedPositionUs, loaderBufferedPositionUs);
}
}
if (bufferedPositionAudioVideoUs != Long.MAX_VALUE) {
lastAudioVideoBufferedPositionUs = bufferedPositionAudioVideoUs;
return bufferedPositionAudioVideoUs;
} else if (bufferedPositionUs != Long.MAX_VALUE) {
// If lastAudioVideoBufferedPositionUs != C.TIME_UNSET, then we know there's at least one a/v
// track (because this is the only time we end up assigning lastAudioVideoBufferedPositionUs
// on a previous invocation).
return lastAudioVideoBufferedPositionUs != C.TIME_UNSET
? lastAudioVideoBufferedPositionUs
: bufferedPositionUs;
} else {
return C.TIME_END_OF_SOURCE;
}
}
@Override
public long getNextLoadPositionUs() {
long nextLoadPositionUs = Long.MAX_VALUE;
for (int i = 0; i < loadersWithTrackTypes.size(); i++) {
SequenceableLoaderWithTrackTypes loader = loadersWithTrackTypes.get(i);
long loaderNextLoadPositionUs = loader.getNextLoadPositionUs();
if (loaderNextLoadPositionUs != C.TIME_END_OF_SOURCE) {
nextLoadPositionUs = min(nextLoadPositionUs, loaderNextLoadPositionUs);
}
}
return nextLoadPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : nextLoadPositionUs;
}
@Override
public void reevaluateBuffer(long positionUs) {
for (int i = 0; i < loadersWithTrackTypes.size(); i++) {
loadersWithTrackTypes.get(i).reevaluateBuffer(positionUs);
}
}
@Override
public boolean continueLoading(LoadingInfo loadingInfo) {
boolean madeProgress = false;
boolean madeProgressThisIteration;
do {
madeProgressThisIteration = false;
long nextLoadPositionUs = getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
break;
}
for (int i = 0; i < loadersWithTrackTypes.size(); i++) {
long loaderNextLoadPositionUs = loadersWithTrackTypes.get(i).getNextLoadPositionUs();
boolean isLoaderBehind =
loaderNextLoadPositionUs != C.TIME_END_OF_SOURCE
&& loaderNextLoadPositionUs <= loadingInfo.playbackPositionUs;
if (loaderNextLoadPositionUs == nextLoadPositionUs || isLoaderBehind) {
madeProgressThisIteration |= loadersWithTrackTypes.get(i).continueLoading(loadingInfo);
}
}
madeProgress |= madeProgressThisIteration;
} while (madeProgressThisIteration);
return madeProgress;
}
@Override
public boolean isLoading() {
for (int i = 0; i < loadersWithTrackTypes.size(); i++) {
if (loadersWithTrackTypes.get(i).isLoading()) {
return true;
}
}
return false;
}
private static final class SequenceableLoaderWithTrackTypes implements SequenceableLoader {
private final SequenceableLoader loader;
private final ImmutableList<@C.TrackType Integer> trackTypes;
public SequenceableLoaderWithTrackTypes(
SequenceableLoader loader, List<@C.TrackType Integer> trackTypes) {
this.loader = loader;
this.trackTypes = ImmutableList.copyOf(trackTypes);
}
public ImmutableList<@C.TrackType Integer> getTrackTypes() {
return trackTypes;
}
// SequenceableLoader implementation
@Override
public long getBufferedPositionUs() {
return loader.getBufferedPositionUs();
}
@Override
public long getNextLoadPositionUs() {
return loader.getNextLoadPositionUs();
}
@Override
public boolean continueLoading(LoadingInfo loadingInfo) {
return loader.continueLoading(loadingInfo);
}
@Override
public boolean isLoading() {
return loader.isLoading();
}
@Override
public void reevaluateBuffer(long positionUs) {
loader.reevaluateBuffer(positionUs);
}
}
}