ExoPlayerImplInternal.releaseInternal(): unblock the app thread

This change makes ExoPlayerImplInternal.releaseInternal() unblock the
app thread if a runtime exception is thrown while releasing components
from the playback thread.

Before this change, if a runtime exception occurred during releasing
components in the playback thread, ExoPlayer.release() would wait for
`releaseTimeoutMs` and then raise a player error. With this change,
the player error is reported only when the playback thread is blocked
but if there is a runtime exception, the application thread is
unblocked.

The impact of this change is potentially fewer ANRs on
ExoPlayer.release() at the expense of less error reporting.

PiperOrigin-RevId: 609702549
This commit is contained in:
christosts 2024-02-23 05:51:18 -08:00 committed by Copybara-Service
parent 601d6ed587
commit 0480eff6a1
2 changed files with 51 additions and 14 deletions

View file

@ -1478,20 +1478,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void releaseInternal() {
resetInternal(
/* resetRenderers= */ true,
/* resetPosition= */ false,
/* releaseMediaSourceList= */ true,
/* resetError= */ false);
releaseRenderers();
loadControl.onReleased();
setState(Player.STATE_IDLE);
if (internalPlaybackThread != null) {
internalPlaybackThread.quit();
}
synchronized (this) {
released = true;
notifyAll();
try {
resetInternal(
/* resetRenderers= */ true,
/* resetPosition= */ false,
/* releaseMediaSourceList= */ true,
/* resetError= */ false);
releaseRenderers();
loadControl.onReleased();
setState(Player.STATE_IDLE);
} finally {
if (internalPlaybackThread != null) {
internalPlaybackThread.quit();
}
synchronized (this) {
released = true;
notifyAll();
}
}
}

View file

@ -85,6 +85,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -12934,6 +12935,39 @@ public final class ExoPlayerTest {
verify(listener).onVolumeChanged(anyFloat());
}
/**
* This test verifies that {@link ExoPlayer#release()} will return without a timeout reported when
* there is a {@link RuntimeException} thrown on the playback thread during releasing the internal
* components.
*/
@Test
public void release_internalFailure_noTimeoutError() {
Player.Listener listener = mock(Player.Listener.class);
LoadControl loadControl =
spy(
new DefaultLoadControl() {
@Override
public void onReleased() {
// Emulate a failure during player release.
throw new RuntimeException();
}
});
ExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
.setLoadControl(loadControl)
.build();
player.addListener(listener);
// Ensure load control has not thrown the exception yet.
verify(loadControl, never()).onReleased();
player.release();
ShadowLooper.idleMainLooper();
// Verify load control threw the exception.
verify(loadControl).onReleased();
verify(listener, never()).onPlayerError(any());
}
@Test
public void releaseAfterVolumeChanges_triggerPendingDeviceVolumeEventsInListener() {
ExoPlayer player =