From dbb99fef191ab713d3aa9a24d8283f3c2c146cb1 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 7 Feb 2024 09:54:08 -0800 Subject: [PATCH] Rollback of /~https://github.com/androidx/media/commit/406c0a15be6f0937fe53df0b44097ccd5c8ef9a0 PiperOrigin-RevId: 605015994 (cherry picked from commit 3a7a665d5df758817be637d026664838ef1af2ea) --- .../media3/exoplayer/ClippedPlaybackTest.java | 21 ++- .../source/CompositeSequenceableLoader.java | 117 ++++++++++++++-- .../CompositeSequenceableLoaderFactory.java | 18 ++- ...ultCompositeSequenceableLoaderFactory.java | 17 +++ .../exoplayer/source/MergingMediaPeriod.java | 8 +- .../exoplayer/source/TrackGroupArray.java | 6 + .../CompositeSequenceableLoaderTest.java | 130 ++++++++++++++++-- .../exoplayer/dash/DashMediaPeriod.java | 8 +- .../media3/exoplayer/hls/HlsMediaPeriod.java | 14 +- .../smoothstreaming/SsMediaPeriod.java | 9 +- .../test/utils/FakeAdaptiveMediaPeriod.java | 16 ++- 11 files changed, 322 insertions(+), 42 deletions(-) diff --git a/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/ClippedPlaybackTest.java b/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/ClippedPlaybackTest.java index cea12308bb0..19376fe7938 100644 --- a/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/ClippedPlaybackTest.java +++ b/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/ClippedPlaybackTest.java @@ -34,6 +34,7 @@ import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.junit.runner.RunWith; @@ -94,7 +95,7 @@ public void subtitlesRespectClipping_singlePeriod() throws Exception { player.get().addListener(textCapturer); player.get().setMediaItem(mediaItem); player.get().prepare(); - player.get().play(); + playWhenLoadingIsDone(player.get()); }); textCapturer.block(); @@ -146,6 +147,9 @@ public void subtitlesRespectClipping_multiplePeriods() throws Exception { player.get().addListener(textCapturer); player.get().setMediaItems(mediaItems); player.get().prepare(); + // We don't need playWhenLoadingIsDone here because playback already waits at the end + // of the first period for subtitles to be fully loaded beforetransitioning to the + // second period. player.get().play(); }); @@ -157,6 +161,21 @@ public void subtitlesRespectClipping_multiplePeriods() throws Exception { .isEqualTo("This is the first subtitle."); } + private static void playWhenLoadingIsDone(Player player) { + AtomicBoolean loadingStarted = new AtomicBoolean(false); + player.addListener( + new Player.Listener() { + @Override + public void onEvents(Player player, Player.Events events) { + if (events.contains(Player.EVENT_IS_LOADING_CHANGED) + && loadingStarted.getAndSet(player.isLoading()) + && !player.isLoading()) { + player.play(); + } + } + }); + } + private static class TextCapturingPlaybackListener implements Player.Listener { private final ConditionVariable playbackEnded; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoader.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoader.java index ae8cd9dc713..2fae24973a7 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoader.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoader.java @@ -15,38 +15,88 @@ */ 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 SequenceableLoader[] loaders; + private final ImmutableList loadersWithTrackTypes; + private long lastAudioVideoBufferedPositionUs; + /** + * @deprecated Use {@link CompositeSequenceableLoader#CompositeSequenceableLoader(List, List)} + * instead. + */ + @Deprecated public CompositeSequenceableLoader(SequenceableLoader[] loaders) { - this.loaders = loaders; + this( + ImmutableList.copyOf(loaders), + Collections.nCopies(loaders.length, ImmutableList.of(C.TRACK_TYPE_UNKNOWN))); + } + + public CompositeSequenceableLoader( + List loaders, + List> loaderTrackTypes) { + ImmutableList.Builder 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; - for (SequenceableLoader loader : loaders) { + 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); } } - return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + 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 (SequenceableLoader loader : loaders) { + 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); @@ -57,8 +107,8 @@ public long getNextLoadPositionUs() { @Override public void reevaluateBuffer(long positionUs) { - for (SequenceableLoader loader : loaders) { - loader.reevaluateBuffer(positionUs); + for (int i = 0; i < loadersWithTrackTypes.size(); i++) { + loadersWithTrackTypes.get(i).reevaluateBuffer(positionUs); } } @@ -72,13 +122,13 @@ public boolean continueLoading(LoadingInfo loadingInfo) { if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { break; } - for (SequenceableLoader loader : loaders) { - long loaderNextLoadPositionUs = loader.getNextLoadPositionUs(); + 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 |= loader.continueLoading(loadingInfo); + madeProgressThisIteration |= loadersWithTrackTypes.get(i).continueLoading(loadingInfo); } } madeProgress |= madeProgressThisIteration; @@ -88,11 +138,54 @@ public boolean continueLoading(LoadingInfo loadingInfo) { @Override public boolean isLoading() { - for (SequenceableLoader loader : loaders) { - if (loader.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); + } + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderFactory.java index 3d536922844..2230418ed04 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderFactory.java @@ -15,17 +15,33 @@ */ package androidx.media3.exoplayer.source; +import androidx.media3.common.C; import androidx.media3.common.util.UnstableApi; +import java.util.List; /** A factory to create composite {@link SequenceableLoader}s. */ @UnstableApi public interface CompositeSequenceableLoaderFactory { + /** Returns an empty composite {@link SequenceableLoader}, with no delegate loaders. */ + SequenceableLoader empty(); + + /** + * @deprecated Use {@link #empty()} for an empty composite loader, or {@link #create(List, List)} + * for a non-empty one. + */ + @Deprecated + SequenceableLoader createCompositeSequenceableLoader(SequenceableLoader... loaders); + /** * Creates a composite {@link SequenceableLoader}. * * @param loaders The sub-loaders that make up the {@link SequenceableLoader} to be built. + * @param loaderTrackTypes The track types handled by each entry in {@code loaders}. Must be the + * same as {@code loaders}. * @return A composite {@link SequenceableLoader} that comprises the given loaders. */ - SequenceableLoader createCompositeSequenceableLoader(SequenceableLoader... loaders); + SequenceableLoader create( + List loaders, + List> loaderTrackTypes); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultCompositeSequenceableLoaderFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultCompositeSequenceableLoaderFactory.java index aa342971fe1..7d79aac8da5 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultCompositeSequenceableLoaderFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultCompositeSequenceableLoaderFactory.java @@ -15,7 +15,10 @@ */ package androidx.media3.exoplayer.source; +import androidx.media3.common.C; import androidx.media3.common.util.UnstableApi; +import com.google.common.collect.ImmutableList; +import java.util.List; /** Default implementation of {@link CompositeSequenceableLoaderFactory}. */ @UnstableApi @@ -23,7 +26,21 @@ public final class DefaultCompositeSequenceableLoaderFactory implements CompositeSequenceableLoaderFactory { @Override + public SequenceableLoader empty() { + return new CompositeSequenceableLoader(ImmutableList.of(), ImmutableList.of()); + } + + @Deprecated + @Override + @SuppressWarnings("deprecation") // Calling deprecated constructor public SequenceableLoader createCompositeSequenceableLoader(SequenceableLoader... loaders) { return new CompositeSequenceableLoader(loaders); } + + @Override + public SequenceableLoader create( + List loaders, + List> loaderTrackTypes) { + return new CompositeSequenceableLoader(loaders, loaderTrackTypes); + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/MergingMediaPeriod.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/MergingMediaPeriod.java index 07ff893bfd5..8912f5c8df8 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/MergingMediaPeriod.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/MergingMediaPeriod.java @@ -29,6 +29,7 @@ import androidx.media3.exoplayer.source.chunk.MediaChunk; import androidx.media3.exoplayer.source.chunk.MediaChunkIterator; import androidx.media3.exoplayer.trackselection.ExoTrackSelection; +import com.google.common.collect.Lists; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -58,8 +59,7 @@ public MergingMediaPeriod( this.periods = periods; childrenPendingPreparation = new ArrayList<>(); childTrackGroupByMergedTrackGroup = new HashMap<>(); - compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(); + compositeSequenceableLoader = compositeSequenceableLoaderFactory.empty(); streamPeriodIndices = new IdentityHashMap<>(); enabledPeriods = new MediaPeriod[0]; for (int i = 0; i < periods.length; i++) { @@ -172,7 +172,9 @@ public long selectTracks( // Update the local state. enabledPeriods = enabledPeriodsList.toArray(new MediaPeriod[0]); compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(enabledPeriods); + compositeSequenceableLoaderFactory.create( + enabledPeriodsList, + Lists.transform(enabledPeriodsList, period -> period.getTrackGroups().getTrackTypes())); return positionUs; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/TrackGroupArray.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/TrackGroupArray.java index 2018bb06125..5c7e6c2c0bf 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/TrackGroupArray.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/TrackGroupArray.java @@ -25,6 +25,7 @@ import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.util.List; /** @@ -90,6 +91,11 @@ public boolean isEmpty() { return length == 0; } + /** Returns the {@link TrackGroup#type} of each track group in this array. */ + public ImmutableList<@C.TrackType Integer> getTrackTypes() { + return ImmutableList.copyOf(Lists.transform(trackGroups, t -> t.type)); + } + @Override public int hashCode() { if (hashCode == 0) { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderTest.java index 63b2acb7311..20f6112ac5e 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/CompositeSequenceableLoaderTest.java @@ -22,6 +22,7 @@ import androidx.media3.common.C; import androidx.media3.exoplayer.LoadingInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,7 +42,10 @@ public void getBufferedPositionUsReturnsMinimumLoaderBufferedPosition() { FakeSequenceableLoader loader2 = new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000); assertThat(compositeSequenceableLoader.isLoading()).isTrue(); } @@ -62,7 +66,12 @@ public void getBufferedPositionUsReturnsMinimumNonEndOfSourceLoaderBufferedPosit /* bufferedPositionUs */ C.TIME_END_OF_SOURCE, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2, loader3}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2, loader3), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), + ImmutableList.of(C.TRACK_TYPE_VIDEO), + ImmutableList.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO))); assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000); assertThat(compositeSequenceableLoader.isLoading()).isTrue(); } @@ -83,11 +92,82 @@ public void getBufferedPositionUsReturnsEndOfSourceWhenAllLoaderBufferedTillEndO /* bufferedPositionUs */ C.TIME_END_OF_SOURCE, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE); assertThat(compositeSequenceableLoader.isLoading()).isFalse(); } + /** + * Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns the minimum + * buffered position of loaders with audio or video tracks (if at least one loader has tracks of + * these types). + */ + @Test + public void getBufferedPositionUs_prefersLoadersWithAudioAndVideoTracks() { + FakeSequenceableLoader loaderWithTextOnly = + new FakeSequenceableLoader(/* bufferedPositionUs= */ 999, /* nextLoadPositionUs= */ 2000); + FakeSequenceableLoader loaderWithAudioVideoAndText = + new FakeSequenceableLoader(/* bufferedPositionUs= */ 1000, /* nextLoadPositionUs= */ 2000); + CompositeSequenceableLoader compositeSequenceableLoader = + new CompositeSequenceableLoader( + ImmutableList.of(loaderWithTextOnly, loaderWithAudioVideoAndText), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_TEXT), + ImmutableList.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_TEXT))); + assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000); + assertThat(compositeSequenceableLoader.isLoading()).isTrue(); + } + + /** + * Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} doesn't return {@link + * C#TIME_END_OF_SOURCE} if only the A/V tracks have finished loading and text track is still + * loading. Instead it keeps returning the last 'real' A/V buffered position, to avoid the + * buffered position snapping back to the text track buffered position. + */ + @Test + public void + getBufferedPositionUs_prefersLoadersWithAudioAndVideoTracks_fallsBackToLastAVBufferedPositionWhenLoadingComplete() { + FakeSequenceableLoader loaderWithTextOnly = + new FakeSequenceableLoader(/* bufferedPositionUs= */ 500, /* nextLoadPositionUs= */ 2000); + FakeSequenceableLoader loaderWithAudioVideoAndText = + new FakeSequenceableLoader( + /* bufferedPositionUs= */ 1000, /* nextLoadPositionUs= */ C.TIME_END_OF_SOURCE); + CompositeSequenceableLoader compositeSequenceableLoader = + new CompositeSequenceableLoader( + ImmutableList.of(loaderWithTextOnly, loaderWithAudioVideoAndText), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_TEXT), + ImmutableList.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_TEXT))); + assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000); + loaderWithAudioVideoAndText.continueLoading( + new LoadingInfo.Builder().setPlaybackPositionUs(100).build()); + assertThat(loaderWithAudioVideoAndText.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE); + assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000); + assertThat(compositeSequenceableLoader.isLoading()).isTrue(); + } + + /** + * Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns the minimum + * buffered position of all loaders if no loader has audio or video tracks. + */ + @Test + public void getBufferedPositionUs_considersAllTracksIfNoneAreAudioOrVideo() { + FakeSequenceableLoader loaderWithTextOnly = + new FakeSequenceableLoader(/* bufferedPositionUs= */ 999, /* nextLoadPositionUs= */ 2000); + FakeSequenceableLoader loaderWithMetadataOnly = + new FakeSequenceableLoader(/* bufferedPositionUs= */ 1000, /* nextLoadPositionUs= */ 2000); + CompositeSequenceableLoader compositeSequenceableLoader = + new CompositeSequenceableLoader( + ImmutableList.of(loaderWithTextOnly, loaderWithMetadataOnly), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_TEXT), ImmutableList.of(C.TRACK_TYPE_METADATA))); + assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(999); + assertThat(compositeSequenceableLoader.isLoading()).isTrue(); + } + /** * Tests that {@link CompositeSequenceableLoader#getNextLoadPositionUs()} returns minimum next * load position among all sub-loaders, and is consistent with {@link @@ -100,7 +180,10 @@ public void getNextLoadPositionUsReturnMinimumLoaderNextLoadPositionUs() { FakeSequenceableLoader loader2 = new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2000); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); assertThat(compositeSequenceableLoader.getNextLoadPositionUs()).isEqualTo(2000); assertThat(compositeSequenceableLoader.isLoading()).isTrue(); } @@ -120,7 +203,12 @@ public void getNextLoadPositionUsReturnMinimumNonEndOfSourceLoaderNextLoadPositi new FakeSequenceableLoader( /* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2, loader3}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2, loader3), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), + ImmutableList.of(C.TRACK_TYPE_VIDEO), + ImmutableList.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO))); assertThat(compositeSequenceableLoader.getNextLoadPositionUs()).isEqualTo(2000); assertThat(compositeSequenceableLoader.isLoading()).isTrue(); } @@ -139,7 +227,10 @@ public void getNextLoadPositionUsReturnsEndOfSourceWhenAllLoaderLoadingLastChunk new FakeSequenceableLoader( /* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); assertThat(compositeSequenceableLoader.getNextLoadPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE); assertThat(compositeSequenceableLoader.isLoading()).isFalse(); } @@ -156,7 +247,10 @@ public void continueLoadingOnlyAllowFurthestBehindLoaderToLoadIfNotBehindPlaybac FakeSequenceableLoader loader2 = new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); compositeSequenceableLoader.continueLoading( new LoadingInfo.Builder().setPlaybackPositionUs(100).build()); @@ -177,7 +271,12 @@ public void continueLoadingReturnAllowAllLoadersBehindPlaybackPositionToLoad() { FakeSequenceableLoader loader3 = new FakeSequenceableLoader(/* bufferedPositionUs */ 1002, /* nextLoadPositionUs */ 2002); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2, loader3}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2, loader3), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), + ImmutableList.of(C.TRACK_TYPE_VIDEO), + ImmutableList.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO))); compositeSequenceableLoader.continueLoading( new LoadingInfo.Builder().setPlaybackPositionUs(3000).build()); @@ -199,7 +298,10 @@ public void continueLoadingOnlyNotAllowEndOfSourceLoaderToLoad() { new FakeSequenceableLoader( /* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); compositeSequenceableLoader.continueLoading( new LoadingInfo.Builder().setPlaybackPositionUs(3000).build()); @@ -221,7 +323,10 @@ public void continueLoadingReturnTrueIfFurthestBehindLoaderCanMakeProgress() { loader1.setNextChunkDurationUs(1000); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); assertThat( compositeSequenceableLoader.continueLoading( @@ -244,7 +349,10 @@ public void continueLoadingReturnTrueIfLoaderBehindPlaybackPositionCanMakeProgre loader2.setNextChunkDurationUs(1000); CompositeSequenceableLoader compositeSequenceableLoader = - new CompositeSequenceableLoader(new SequenceableLoader[] {loader1, loader2}); + new CompositeSequenceableLoader( + ImmutableList.of(loader1, loader2), + ImmutableList.of( + ImmutableList.of(C.TRACK_TYPE_AUDIO), ImmutableList.of(C.TRACK_TYPE_VIDEO))); assertThat( compositeSequenceableLoader.continueLoading( diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java index 811c75968f6..5a3e4fb5cad 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java @@ -59,6 +59,7 @@ import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.primitives.Ints; import java.io.IOException; @@ -152,8 +153,7 @@ public DashMediaPeriod( sampleStreams = newSampleStreamArray(0); eventSampleStreams = new EventSampleStream[0]; trackEmsgHandlerBySampleStream = new IdentityHashMap<>(); - compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoader = compositeSequenceableLoaderFactory.empty(); Period period = manifest.getPeriod(periodIndex); eventStreams = period.eventStreams; Pair result = @@ -302,7 +302,9 @@ public long selectTracks( eventSampleStreamList.toArray(eventSampleStreams); compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoaderFactory.create( + sampleStreamList, + Lists.transform(sampleStreamList, s -> ImmutableList.of(s.primaryTrackType))); return positionUs; } diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java index e18220adbf8..d43cea69d58 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java @@ -51,6 +51,8 @@ import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import androidx.media3.extractor.Extractor; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import com.google.common.primitives.Ints; import java.io.IOException; import java.util.ArrayList; @@ -157,8 +159,7 @@ public HlsMediaPeriod( this.playerId = playerId; this.timestampAdjusterInitializationTimeoutMs = timestampAdjusterInitializationTimeoutMs; sampleStreamWrapperCallback = new SampleStreamWrapperCallback(); - compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(); + compositeSequenceableLoader = compositeSequenceableLoaderFactory.empty(); streamWrapperIndices = new IdentityHashMap<>(); timestampAdjusterProvider = new TimestampAdjusterProvider(); sampleStreamWrappers = new HlsSampleStreamWrapper[0]; @@ -372,9 +373,14 @@ public long selectTracks( // Update the local state. enabledSampleStreamWrappers = Util.nullSafeArrayCopy(newEnabledSampleStreamWrappers, newEnabledSampleStreamWrapperCount); + ImmutableList enabledSampleStreamWrappersList = + ImmutableList.copyOf(enabledSampleStreamWrappers); compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader( - enabledSampleStreamWrappers); + compositeSequenceableLoaderFactory.create( + enabledSampleStreamWrappersList, + Lists.transform( + enabledSampleStreamWrappersList, + sampleStreamWrapper -> sampleStreamWrapper.getTrackGroups().getTrackTypes())); return positionUs; } diff --git a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java index e5c757e52f8..6b9f1470177 100644 --- a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java +++ b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java @@ -41,6 +41,8 @@ import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoaderErrorThrower; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -91,8 +93,7 @@ public SsMediaPeriod( this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; trackGroups = buildTrackGroups(manifest, drmSessionManager, chunkSourceFactory); sampleStreams = newSampleStreamArray(0); - compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoader = compositeSequenceableLoaderFactory.empty(); } public void updateManifest(SsManifest manifest) { @@ -158,7 +159,9 @@ public long selectTracks( sampleStreams = newSampleStreamArray(sampleStreamsList.size()); sampleStreamsList.toArray(sampleStreams); compositeSequenceableLoader = - compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); + compositeSequenceableLoaderFactory.create( + sampleStreamsList, + Lists.transform(sampleStreamsList, s -> ImmutableList.of(s.primaryTrackType))); return positionUs; } diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java index 804234c4464..5d3a4f839b6 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java @@ -44,7 +44,9 @@ import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -86,7 +88,9 @@ public FakeAdaptiveMediaPeriod( this.durationUs = durationUs; this.transferListener = transferListener; sampleStreams = new ArrayList<>(); - sequenceableLoader = new CompositeSequenceableLoader(new SequenceableLoader[0]); + sequenceableLoader = + new CompositeSequenceableLoader( + /* loaders= */ ImmutableList.of(), /* loaderTrackTypes= */ ImmutableList.of()); fakePreparationLoadTaskId = LoadEventInfo.getNewId(); } @@ -97,7 +101,9 @@ public void release() { sampleStream.release(); } sampleStreams.clear(); - sequenceableLoader = new CompositeSequenceableLoader(new SequenceableLoader[0]); + sequenceableLoader = + new CompositeSequenceableLoader( + /* loaders= */ ImmutableList.of(), /* loaderTrackTypes= */ ImmutableList.of()); } @Override @@ -143,7 +149,7 @@ public TrackGroupArray getTrackGroups() { return trackGroupArray; } - @SuppressWarnings({"unchecked", "rawtypes"}) // Casting sample streams created by this class. + @SuppressWarnings({"unchecked"}) // Casting sample streams created by this class. @Override public long selectTracks( @NullableType ExoTrackSelection[] selections, @@ -188,7 +194,9 @@ public long selectTracks( } } sequenceableLoader = - new CompositeSequenceableLoader(sampleStreams.toArray(new ChunkSampleStream[0])); + new CompositeSequenceableLoader( + sampleStreams, + Lists.transform(sampleStreams, s -> ImmutableList.of(s.primaryTrackType))); return seekToUs(positionUs); }