Set that any error during pre-warming disables and resets pre-warming

For now, even if a recoverable error occurs during pre-warming, the current process will be that pre-warming is disabled until subsequent media item transition.

PiperOrigin-RevId: 740349517
(cherry picked from commit 6e510c26df0d354312abe480b238afa47abedd3d)
This commit is contained in:
michaelkatz 2025-03-25 08:00:47 -07:00 committed by tonihei
parent 2939bfccbe
commit ef8f72d684
2 changed files with 105 additions and 20 deletions

View File

@ -750,7 +750,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
: readingPeriod.info.id);
}
}
if (e.isRecoverable
if (e.type == ExoPlaybackException.TYPE_RENDERER
&& e.mediaPeriodId != null
&& isRendererPrewarmingMediaPeriod(e.rendererIndex, e.mediaPeriodId)) {
// TODO(b/380273486): Investigate recovery for pre-warming renderer errors
isPrewarmingDisabledUntilNextTransition = true;
disableAndResetPrewarmingRenderers();
// Remove periods from the queue starting at the pre-warming period.
MediaPeriodHolder prewarmingPeriod = queue.getPrewarmingPeriod();
MediaPeriodHolder periodToRemoveAfter = queue.getPlayingPeriod();
if (queue.getPlayingPeriod() != prewarmingPeriod) {
while (periodToRemoveAfter != null && periodToRemoveAfter.getNext() != prewarmingPeriod) {
periodToRemoveAfter = periodToRemoveAfter.getNext();
}
}
queue.removeAfter(periodToRemoveAfter);
if (playbackInfo.playbackState != Player.STATE_ENDED) {
maybeContinueLoading();
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
} else if (e.isRecoverable
&& (pendingRecoverableRendererError == null
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED)) {
@ -768,25 +787,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
// recovered or the player stopped before any other message is handled.
handler.sendMessageAtFrontOfQueue(
handler.obtainMessage(MSG_ATTEMPT_RENDERER_ERROR_RECOVERY, e));
} else if (e.type == ExoPlaybackException.TYPE_RENDERER
&& renderers[e.rendererIndex % renderers.length].isRendererPrewarming(
/* id= */ e.rendererIndex)) {
// TODO(b/380273486): Investigate recovery for pre-warming renderer errors
isPrewarmingDisabledUntilNextTransition = true;
disableAndResetPrewarmingRenderers();
// Remove periods from the queue starting at the pre-warming period.
MediaPeriodHolder prewarmingPeriod = queue.getPrewarmingPeriod();
MediaPeriodHolder periodToRemoveAfter = queue.getPlayingPeriod();
if (queue.getPlayingPeriod() != prewarmingPeriod) {
while (periodToRemoveAfter != null && periodToRemoveAfter.getNext() != prewarmingPeriod) {
periodToRemoveAfter = periodToRemoveAfter.getNext();
}
}
queue.removeAfter(periodToRemoveAfter);
if (playbackInfo.playbackState != Player.STATE_ENDED) {
maybeContinueLoading();
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
} else {
if (pendingRecoverableRendererError != null) {
pendingRecoverableRendererError.addSuppressed(e);

View File

@ -1524,6 +1524,91 @@ public class ExoPlayerWithPrewarmingRenderersTest {
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_ENABLED);
}
@Test
public void
play_recoverableErrorWithPrimaryRendererDuringPrewarming_doesNotResetSecondaryRenderer()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
Player.Listener listener = mock(Player.Listener.class);
AtomicBoolean shouldPrimaryRendererThrowRecoverable = new AtomicBoolean(false);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock) {
@Override
public Renderer[] createRenderers(
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
HandlerWrapper clockAwareHandler =
clock.createHandler(eventHandler.getLooper(), /* callback= */ null);
return new Renderer[] {
new FakeVideoRenderer(clockAwareHandler, videoRendererEventListener) {
@Override
public void render(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
if (!shouldPrimaryRendererThrowRecoverable.get()) {
super.render(positionUs, elapsedRealtimeUs);
} else {
shouldPrimaryRendererThrowRecoverable.set(false);
throw createRendererException(
new MediaCodecRenderer.DecoderInitializationException(
new Format.Builder().build(),
new IllegalArgumentException(),
false,
0),
this.getFormatHolder().format,
true,
PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
}
}
},
new FakeAudioRenderer(clockAwareHandler, audioRendererEventListener)
};
}
})
.build();
player.addListener(listener);
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeBlockingMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is pre-warming.
player.play();
advance(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
advance(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
shouldPrimaryRendererThrowRecoverable.set(true);
advance(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_DISABLED);
@Renderer.State int videoState3 = videoRenderer.getState();
@Renderer.State int secondaryVideoState3 = secondaryVideoRenderer.getState();
player.release();
verify(listener).onPositionDiscontinuity(any(), any(), anyInt());
assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState3).isEqualTo(Renderer.STATE_DISABLED);
assertThat(secondaryVideoState3).isEqualTo(Renderer.STATE_STARTED);
}
/** {@link FakeMediaSource} that prevents any reading of samples off the sample queue. */
private static final class FakeBlockingMediaSource extends FakeMediaSource {