Skip to content

Commit

Permalink
Use playing period TrackSelectorResult in track reselection update
Browse files Browse the repository at this point in the history
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
  • Loading branch information
microkatz authored and copybara-github committed Feb 22, 2024
1 parent 0d1b354 commit 4192924
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 1 deletion.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* ExoPlayer:
* Fix issue where `PreloadMediaPeriod` cannot retain the streams when it
is preloaded again.
* Apply the correct corresponding `TrackSelectionResult` to the playing
period in track reselection.
* Transformer:
* Add `audioConversionProcess` and `videoConversionProcess` to
`ExportResult` indicating how the respective track in the output file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1801,12 +1801,17 @@ private void reselectTracksInternal() throws ExoPlaybackException {
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
boolean selectionsChangedForReadPeriod = true;
TrackSelectorResult newTrackSelectorResult;
// Keep playing period result in case of track selection change for reading period only.
TrackSelectorResult newPlayingPeriodTrackSelectorResult = null;
while (true) {
if (periodHolder == null || !periodHolder.prepared) {
// The reselection did not change any prepared periods.
return;
}
newTrackSelectorResult = periodHolder.selectTracks(playbackSpeed, playbackInfo.timeline);
if (periodHolder == queue.getPlayingPeriod()) {
newPlayingPeriodTrackSelectorResult = newTrackSelectorResult;
}
if (!newTrackSelectorResult.isEquivalent(periodHolder.getTrackSelectorResult())) {
// Selected tracks have changed for this period.
break;
Expand All @@ -1826,7 +1831,10 @@ private void reselectTracksInternal() throws ExoPlaybackException {
boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs =
playingPeriodHolder.applyTrackSelection(
newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags);
checkNotNull(newPlayingPeriodTrackSelectorResult),
playbackInfo.positionUs,
recreateStreams,
streamResetFlags);
boolean hasDiscontinuity =
playbackInfo.playbackState != Player.STATE_ENDED
&& periodPositionUs != playbackInfo.positionUs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
import androidx.media3.common.Player.Listener;
import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.StreamKey;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackGroup;
Expand All @@ -129,6 +130,7 @@
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.SystemClock;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.TransferListener;
Expand All @@ -146,12 +148,15 @@
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.MediaSourceEventListener;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.source.WrappingMediaSource;
import androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource;
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.Allocator;
import androidx.media3.exoplayer.upstream.Loader;
Expand Down Expand Up @@ -1410,6 +1415,88 @@ public void allActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreReused()
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
public void dynamicTimelineChangeReason() throws Exception {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 100000));
Expand Down Expand Up @@ -14563,6 +14650,100 @@ public PlaybackParameters getPlaybackParameters() {
}
}

/** 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.
*/
Expand Down

0 comments on commit 4192924

Please sign in to comment.