Use playing period TrackSelectorResult in track reselection update

If the reading period has already advanced and a track reselection procs that only affects the reading period media, then ExoPlayer may try and apply the reading period's track selection incorrectly unto the playing period. ExoPlayer should apply the playing period's track selection to the playing period instead.

PiperOrigin-RevId: 609375077
(cherry picked from commit 41929246222e9fdb9aa552db526c1b41d26bdb90)
This commit is contained in:
michaelkatz 2024-02-22 08:05:55 -08:00 committed by SheenaChhabra
parent ed71172ade
commit 81e91c25f1
3 changed files with 192 additions and 1 deletions

View File

@ -5,6 +5,8 @@
* ExoPlayer: * ExoPlayer:
* Fix issue where `PreloadMediaPeriod` cannot retain the streams when it * Fix issue where `PreloadMediaPeriod` cannot retain the streams when it
is preloaded again. is preloaded again.
* Apply the correct corresponding `TrackSelectionResult` to the playing
period in track reselection.
* Transformer: * Transformer:
* Add workaround for exception thrown due to `MediaMuxer` not supporting * Add workaround for exception thrown due to `MediaMuxer` not supporting
negative presentation timestamps before API 30. negative presentation timestamps before API 30.

View File

@ -1801,12 +1801,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
boolean selectionsChangedForReadPeriod = true; boolean selectionsChangedForReadPeriod = true;
TrackSelectorResult newTrackSelectorResult; TrackSelectorResult newTrackSelectorResult;
// Keep playing period result in case of track selection change for reading period only.
TrackSelectorResult newPlayingPeriodTrackSelectorResult = null;
while (true) { while (true) {
if (periodHolder == null || !periodHolder.prepared) { if (periodHolder == null || !periodHolder.prepared) {
// The reselection did not change any prepared periods. // The reselection did not change any prepared periods.
return; return;
} }
newTrackSelectorResult = periodHolder.selectTracks(playbackSpeed, playbackInfo.timeline); newTrackSelectorResult = periodHolder.selectTracks(playbackSpeed, playbackInfo.timeline);
if (periodHolder == queue.getPlayingPeriod()) {
newPlayingPeriodTrackSelectorResult = newTrackSelectorResult;
}
if (!newTrackSelectorResult.isEquivalent(periodHolder.getTrackSelectorResult())) { if (!newTrackSelectorResult.isEquivalent(periodHolder.getTrackSelectorResult())) {
// Selected tracks have changed for this period. // Selected tracks have changed for this period.
break; break;
@ -1826,7 +1831,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
boolean[] streamResetFlags = new boolean[renderers.length]; boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs = long periodPositionUs =
playingPeriodHolder.applyTrackSelection( playingPeriodHolder.applyTrackSelection(
newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags); checkNotNull(newPlayingPeriodTrackSelectorResult),
playbackInfo.positionUs,
recreateStreams,
streamResetFlags);
boolean hasDiscontinuity = boolean hasDiscontinuity =
playbackInfo.playbackState != Player.STATE_ENDED playbackInfo.playbackState != Player.STATE_ENDED
&& periodPositionUs != playbackInfo.positionUs; && periodPositionUs != playbackInfo.positionUs;

View File

@ -119,6 +119,7 @@ import androidx.media3.common.Player.DiscontinuityReason;
import androidx.media3.common.Player.Listener; import androidx.media3.common.Player.Listener;
import androidx.media3.common.Player.PlayWhenReadyChangeReason; import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.Player.PositionInfo; import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.StreamKey;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window; import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroup;
@ -129,6 +130,7 @@ import androidx.media3.common.VideoSize;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.SystemClock; import androidx.media3.common.util.SystemClock;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.TransferListener; import androidx.media3.datasource.TransferListener;
@ -146,12 +148,15 @@ import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.MediaSourceEventListener; import androidx.media3.exoplayer.source.MediaSourceEventListener;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.source.ShuffleOrder; import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.SinglePeriodTimeline; import androidx.media3.exoplayer.source.SinglePeriodTimeline;
import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.source.WrappingMediaSource; import androidx.media3.exoplayer.source.WrappingMediaSource;
import androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource; import androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource;
import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.Allocation; import androidx.media3.exoplayer.upstream.Allocation;
import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.Loader; import androidx.media3.exoplayer.upstream.Loader;
@ -1410,6 +1415,88 @@ public final class ExoPlayerTest {
assertThat(numSelectionsEnabled).isEqualTo(3); assertThat(numSelectionsEnabled).isEqualTo(3);
} }
@Test
public void setTrackSelectionParameters_onlyAffectingReadingPeriodMediaItem_selectsCorrectTracks()
throws Exception {
Format audioFormat1 =
ExoPlayerTestRunner.AUDIO_FORMAT.buildUpon().setAverageBitrate(50_000).build();
Format audioFormat2 = audioFormat1.buildUpon().setAverageBitrate(100_000).build();
Format audioFormat3 = audioFormat1.buildUpon().setAverageBitrate(60_000).build();
DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(context);
defaultTrackSelector.setParameters(
defaultTrackSelector
.buildUponParameters()
.setExceedAudioConstraintsIfNecessary(false)
.build());
Timeline timeline = new FakeTimeline();
AtomicInteger createMediaPeriodCounter = new AtomicInteger();
AtomicReference<ExoTrackSelection[]> exoTrackSelectionAtomicReferenceMediaItem1 =
new AtomicReference<>();
AtomicReference<ExoTrackSelection[]> exoTrackSelectionAtomicReferenceMediaItem2 =
new AtomicReference<>();
ExoPlayer player =
new TestExoPlayerBuilder(context).setTrackSelector(defaultTrackSelector).build();
player.addMediaSources(
ImmutableList.of(
new FakeMediaSource(timeline, audioFormat1) {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
return new ForwardingMediaPeriod(
super.createPeriod(id, allocator, startPositionUs),
exoTrackSelectionAtomicReferenceMediaItem1);
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
if (mediaPeriod instanceof ForwardingMediaPeriod) {
super.releasePeriod(((ForwardingMediaPeriod) mediaPeriod).mediaPeriod);
} else {
super.releasePeriod(mediaPeriod);
}
}
},
new FakeMediaSource(timeline, audioFormat2, audioFormat3) {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
createMediaPeriodCounter.getAndIncrement();
return new ForwardingMediaPeriod(
super.createPeriod(id, allocator, startPositionUs),
exoTrackSelectionAtomicReferenceMediaItem2);
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
if (mediaPeriod instanceof ForwardingMediaPeriod) {
super.releasePeriod(((ForwardingMediaPeriod) mediaPeriod).mediaPeriod);
} else {
super.releasePeriod(mediaPeriod);
}
}
}));
player.prepare();
TestPlayerRunHelper.playUntilPosition(
player, /* mediaItemIndex= */ 0, /* positionMs= */ 5 * C.MICROS_PER_SECOND);
assertThat(exoTrackSelectionAtomicReferenceMediaItem2.get()[1]).isNotNull();
assertThat(exoTrackSelectionAtomicReferenceMediaItem2.get()[1].getFormat(0))
.isEqualTo(audioFormat2);
// Alter track selection parameters to invalidate track selection on second media item only.
player.setTrackSelectionParameters(
player.getTrackSelectionParameters().buildUpon().setMaxAudioBitrate(70_000).build());
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
assertThat(createMediaPeriodCounter.get()).isEqualTo(2);
assertThat(exoTrackSelectionAtomicReferenceMediaItem1.get()[1]).isNotNull();
assertThat(exoTrackSelectionAtomicReferenceMediaItem1.get()[1].getFormat(0))
.isEqualTo(audioFormat1);
assertThat(exoTrackSelectionAtomicReferenceMediaItem2.get()[1]).isNotNull();
assertThat(exoTrackSelectionAtomicReferenceMediaItem2.get()[1].getFormat(0))
.isEqualTo(audioFormat3);
}
@Test @Test
public void dynamicTimelineChangeReason() throws Exception { public void dynamicTimelineChangeReason() throws Exception {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 100000)); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 100000));
@ -14563,6 +14650,100 @@ public final class ExoPlayerTest {
} }
} }
/** Forwarding {@link MediaPeriod} class with tracked reference for {@link #selectTracks}. */
private static final class ForwardingMediaPeriod implements MediaPeriod {
/** The wrapped {@link MediaPeriod} that method calls are forwarded to. */
public final MediaPeriod mediaPeriod;
/** Reference to last tracks selected through {@linkplain #selectTracks}. */
public final AtomicReference<ExoTrackSelection[]> exoTrackSelectionReferenceList;
public ForwardingMediaPeriod(
MediaPeriod mediaPeriod,
AtomicReference<ExoTrackSelection[]> exoTrackSelectionReferenceList) {
this.mediaPeriod = mediaPeriod;
this.exoTrackSelectionReferenceList = exoTrackSelectionReferenceList;
}
@Override
public void prepare(Callback callback, long positionUs) {
mediaPeriod.prepare(callback, positionUs);
}
@Override
public void maybeThrowPrepareError() throws IOException {
mediaPeriod.maybeThrowPrepareError();
}
@Override
public TrackGroupArray getTrackGroups() {
return mediaPeriod.getTrackGroups();
}
@Override
public List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
return mediaPeriod.getStreamKeys(trackSelections);
}
@Override
public long selectTracks(
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
long positionUs) {
exoTrackSelectionReferenceList.set(selections);
return mediaPeriod.selectTracks(
selections, mayRetainStreamFlags, streams, streamResetFlags, positionUs);
}
@Override
public void discardBuffer(long positionUs, boolean toKeyframe) {
mediaPeriod.discardBuffer(positionUs, toKeyframe);
}
@Override
public long readDiscontinuity() {
return mediaPeriod.readDiscontinuity();
}
@Override
public long seekToUs(long positionUs) {
return mediaPeriod.seekToUs(positionUs);
}
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
return mediaPeriod.getAdjustedSeekPositionUs(positionUs, seekParameters);
}
@Override
public long getBufferedPositionUs() {
return mediaPeriod.getBufferedPositionUs();
}
@Override
public long getNextLoadPositionUs() {
return mediaPeriod.getNextLoadPositionUs();
}
@Override
public boolean continueLoading(LoadingInfo loadingInfo) {
return mediaPeriod.continueLoading(loadingInfo);
}
@Override
public boolean isLoading() {
return mediaPeriod.isLoading();
}
@Override
public void reevaluateBuffer(long positionUs) {
mediaPeriod.reevaluateBuffer(positionUs);
}
}
/** /**
* Returns an argument matcher for {@link Timeline} instances that ignores period and window uids. * Returns an argument matcher for {@link Timeline} instances that ignores period and window uids.
*/ */