mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
ed71172ade
commit
81e91c25f1
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user