diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 3a18ff8af8..19fbcb6d6a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -246,6 +246,12 @@ public final class DefaultAudioSink implements AudioSink { */ private static final int ERROR_NATIVE_DEAD_OBJECT = -32; + /** + * The duration for which failed attempts to initialize or write to the audio track may be retried + * before throwing an exception, in milliseconds. + */ + private static final int AUDIO_TRACK_RETRY_DURATION_MS = 100; + private static final String TAG = "AudioTrack"; /** @@ -279,6 +285,9 @@ public final class DefaultAudioSink implements AudioSink { private final boolean enableAudioTrackPlaybackParams; private final boolean enableOffload; @MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29; + private final PendingExceptionHolder + initializationExceptionPendingExceptionHolder; + private final PendingExceptionHolder writeExceptionPendingExceptionHolder; @Nullable private Listener listener; /** @@ -425,6 +434,10 @@ public final class DefaultAudioSink implements AudioSink { activeAudioProcessors = new AudioProcessor[0]; outputBuffers = new ByteBuffer[0]; mediaPositionParametersCheckpoints = new ArrayDeque<>(); + initializationExceptionPendingExceptionHolder = + new PendingExceptionHolder<>(AUDIO_TRACK_RETRY_DURATION_MS); + writeExceptionPendingExceptionHolder = + new PendingExceptionHolder<>(AUDIO_TRACK_RETRY_DURATION_MS); } // AudioSink implementation. @@ -710,8 +723,17 @@ public final class DefaultAudioSink implements AudioSink { } if (!isAudioTrackInitialized()) { - initializeAudioTrack(); + try { + initializeAudioTrack(); + } catch (InitializationException e) { + if (e.isRecoverable) { + throw e; // Do not delay the exception if it can be recovered at higher level. + } + initializationExceptionPendingExceptionHolder.throwExceptionIfDeadlineIsReached(e); + return false; + } } + initializationExceptionPendingExceptionHolder.clear(); if (startMediaTimeUsNeedsInit) { startMediaTimeUs = max(0, presentationTimeUs); @@ -929,8 +951,14 @@ public final class DefaultAudioSink implements AudioSink { if (listener != null) { listener.onAudioSinkError(e); } - throw e; + if (e.isRecoverable) { + throw e; // Do not delay the exception if it can be recovered at higher level. + } + writeExceptionPendingExceptionHolder.throwExceptionIfDeadlineIsReached(e); + return; } + writeExceptionPendingExceptionHolder.clear(); + int bytesWritten = bytesWrittenOrError; if (isOffloadedPlayback(audioTrack)) { @@ -1182,6 +1210,8 @@ public final class DefaultAudioSink implements AudioSink { } }.start(); } + writeExceptionPendingExceptionHolder.clear(); + initializationExceptionPendingExceptionHolder.clear(); } @Override @@ -1193,6 +1223,9 @@ public final class DefaultAudioSink implements AudioSink { return; } + writeExceptionPendingExceptionHolder.clear(); + initializationExceptionPendingExceptionHolder.clear(); + if (!isAudioTrackInitialized()) { return; } @@ -2065,4 +2098,37 @@ public final class DefaultAudioSink implements AudioSink { return outputMode == OUTPUT_MODE_OFFLOAD; } } + + private static final class PendingExceptionHolder { + + private final long throwDelayMs; + + @Nullable private T pendingException; + private long throwDeadlineMs; + + public PendingExceptionHolder(long throwDelayMs) { + this.throwDelayMs = throwDelayMs; + } + + public void throwExceptionIfDeadlineIsReached(T exception) throws T { + long nowMs = SystemClock.elapsedRealtime(); + if (pendingException == null) { + pendingException = exception; + throwDeadlineMs = nowMs + throwDelayMs; + } + if (nowMs >= throwDeadlineMs) { + if (pendingException != exception) { + // All retry exception are probably the same, thus only save the last one to save memory. + pendingException.addSuppressed(exception); + } + T pendingException = this.pendingException; + clear(); + throw pendingException; + } + } + + public void clear() { + pendingException = null; + } + } }