Fix ArrayIndexOutOfBoundIndex when re-preparing after exception

When an app tried to re-prepare a live streeam with server side inserted
ad after a playback exception, the player tried to find the ad group by
its index in the ad playback state of the next timeline when creating
the first period.

If a source that supports server side ad, has removed the ad playback
state when the source has been removed, this causes a crash. For live
streams this is a reasonable thing to do given the exception could be
caused by an invalid ad playback state.

This change removes the ad metadata from the current period for live
streams and the timeline. In case the ad playback state is not reset
by the source, the first timeline refresh would ad the metadata again.

PiperOrigin-RevId: 541959628
This commit is contained in:
bachinger 2023-06-20 16:55:22 +00:00 committed by Tofunmi Adigun-Hameed
parent 63ca2595be
commit 4604f0cde6
2 changed files with 43 additions and 3 deletions

View File

@ -1494,11 +1494,21 @@ import java.util.concurrent.atomic.AtomicBoolean;
Timeline timeline = playbackInfo.timeline;
if (releaseMediaSourceList && timeline instanceof PlaylistTimeline) {
// Wrap the current live timeline to make sure the current period is marked as a placeholder
// to force resolving the default start position with the next timeline refresh.
// Wrap the current timeline to make sure the current period is marked as a placeholder to
// force resolving the default start position with the next timeline refresh.
timeline =
((PlaylistTimeline) playbackInfo.timeline)
.copyWithPlaceholderTimeline(mediaSourceList.getShuffleOrder());
if (mediaPeriodId.adGroupIndex != C.INDEX_UNSET) {
timeline.getPeriodByUid(mediaPeriodId.periodUid, period);
if (timeline.getWindow(period.windowIndex, window).isLive()) {
// Drop ad metadata to allow live streams to reset the ad playback state. In case the ad
// playback state is not reset by the source, the first timeline refresh after
// re-preparation will add the ad metadata to the period again.
mediaPeriodId =
new MediaPeriodId(mediaPeriodId.periodUid, mediaPeriodId.windowSequenceNumber);
}
}
}
playbackInfo =
new PlaybackInfo(

View File

@ -15,7 +15,9 @@
*/
package androidx.media3.exoplayer;
import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.ForwardingTimeline;
@ -117,15 +119,43 @@ import java.util.List;
return periodCount;
}
/**
* Creates a copy of the timeline and wraps each child timeline with a {@link ForwardingTimeline}
* that overrides {@link Timeline#getPeriod(int, Period, boolean)} to set the {@link
* Period#isPlaceholder} flag.
*
* <p>For periods of a live window, the {@link AdPlaybackState} is set to {@link
* AdPlaybackState#NONE} to allow a live source with ad support to drop the ad playback state.
*
* <p>This method should be used when the player is reset (for instance when a playback error
* occurs or {@link Player#stop()} is called) to make the player resolve the start position like
* when prepared initially. In this state, each source needs to be prepared again at which point
* the first timeline delivered by the source will replace the wrapped source to continue
* playback.
*/
public PlaylistTimeline copyWithPlaceholderTimeline(ShuffleOrder shuffleOrder) {
Timeline[] newTimelines = new Timeline[timelines.length];
for (int i = 0; i < timelines.length; i++) {
newTimelines[i] =
new ForwardingTimeline(timelines[i]) {
private final Window window = new Window();
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
Period superPeriod = super.getPeriod(periodIndex, period, setIds);
superPeriod.isPlaceholder = true;
if (super.getWindow(superPeriod.windowIndex, window).isLive()) {
// Reset the ad playback state for placeholder period of a live streams.
superPeriod.set(
period.id,
period.uid,
period.windowIndex,
period.durationUs,
period.positionInWindowUs,
AdPlaybackState.NONE,
/* isPlaceholder= */ true);
} else {
superPeriod.isPlaceholder = true;
}
return superPeriod;
}
};