diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 6628bcf6a0..26e6b63274 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -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(); + } } } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index 7999271b33..fcaf562552 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -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 =