mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Retry after offload playback failure
Do that by adding a recoverable state to the ExoPlaybackException marking when it is needed to recreate the renderers. PiperOrigin-RevId: 333507849
This commit is contained in:
parent
cf30ee504e
commit
d97af76280
8 changed files with 166 additions and 47 deletions
|
|
@ -17,6 +17,8 @@
|
||||||
([#7866](https://github.com/google/ExoPlayer/issues/7866)).
|
([#7866](https://github.com/google/ExoPlayer/issues/7866)).
|
||||||
* Text:
|
* Text:
|
||||||
* Add support for `\h` SSA/ASS style override code (non-breaking space).
|
* Add support for `\h` SSA/ASS style override code (non-breaking space).
|
||||||
|
* Audio:
|
||||||
|
* Retry playback after some types of `AudioTrack` error.
|
||||||
|
|
||||||
### 2.12.0 (2020-09-11) ###
|
### 2.12.0 (2020-09-11) ###
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,19 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
||||||
*/
|
*/
|
||||||
protected final ExoPlaybackException createRendererException(
|
protected final ExoPlaybackException createRendererException(
|
||||||
Exception cause, @Nullable Format format) {
|
Exception cause, @Nullable Format format) {
|
||||||
|
return createRendererException(cause, format, /* isRecoverable= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link ExoPlaybackException} of type {@link ExoPlaybackException#TYPE_RENDERER} for
|
||||||
|
* this renderer.
|
||||||
|
*
|
||||||
|
* @param cause The cause of the exception.
|
||||||
|
* @param format The current format used by the renderer. May be null.
|
||||||
|
* @param isRecoverable If the error is recoverable by disabling and re-enabling the renderer.
|
||||||
|
*/
|
||||||
|
protected final ExoPlaybackException createRendererException(
|
||||||
|
Exception cause, @Nullable Format format, boolean isRecoverable) {
|
||||||
@FormatSupport int formatSupport = RendererCapabilities.FORMAT_HANDLED;
|
@FormatSupport int formatSupport = RendererCapabilities.FORMAT_HANDLED;
|
||||||
if (format != null && !throwRendererExceptionIsExecuting) {
|
if (format != null && !throwRendererExceptionIsExecuting) {
|
||||||
// Prevent recursive re-entry from subclass supportsFormat implementations.
|
// Prevent recursive re-entry from subclass supportsFormat implementations.
|
||||||
|
|
@ -354,7 +367,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ExoPlaybackException.createForRenderer(
|
return ExoPlaybackException.createForRenderer(
|
||||||
cause, getName(), getIndex(), format, formatSupport);
|
cause, getName(), getIndex(), format, formatSupport, isRecoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
/**
|
/** Thrown when a non locally recoverable playback failure occurs. */
|
||||||
* Thrown when a non-recoverable playback failure occurs.
|
|
||||||
*/
|
|
||||||
public final class ExoPlaybackException extends Exception {
|
public final class ExoPlaybackException extends Exception {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,6 +136,17 @@ public final class ExoPlaybackException extends Exception {
|
||||||
*/
|
*/
|
||||||
@Nullable public final MediaSource.MediaPeriodId mediaPeriodId;
|
@Nullable public final MediaSource.MediaPeriodId mediaPeriodId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the error may be recoverable.
|
||||||
|
*
|
||||||
|
* <p>This is only used internally by ExoPlayer to try to recover from some errors and should not
|
||||||
|
* be used by apps.
|
||||||
|
*
|
||||||
|
* <p>If the {@link #type} is {@link #TYPE_RENDERER}, it may be possible to recover from the error
|
||||||
|
* by disabling and re-enabling the renderers.
|
||||||
|
*/
|
||||||
|
/* package */ final boolean isRecoverable;
|
||||||
|
|
||||||
@Nullable private final Throwable cause;
|
@Nullable private final Throwable cause;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -167,6 +176,34 @@ public final class ExoPlaybackException extends Exception {
|
||||||
int rendererIndex,
|
int rendererIndex,
|
||||||
@Nullable Format rendererFormat,
|
@Nullable Format rendererFormat,
|
||||||
@FormatSupport int rendererFormatSupport) {
|
@FormatSupport int rendererFormatSupport) {
|
||||||
|
return createForRenderer(
|
||||||
|
cause,
|
||||||
|
rendererName,
|
||||||
|
rendererIndex,
|
||||||
|
rendererFormat,
|
||||||
|
rendererFormatSupport,
|
||||||
|
/* isRecoverable= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of type {@link #TYPE_RENDERER}.
|
||||||
|
*
|
||||||
|
* @param cause The cause of the failure.
|
||||||
|
* @param rendererIndex The index of the renderer in which the failure occurred.
|
||||||
|
* @param rendererFormat The {@link Format} the renderer was using at the time of the exception,
|
||||||
|
* or null if the renderer wasn't using a {@link Format}.
|
||||||
|
* @param rendererFormatSupport The {@link FormatSupport} of the renderer for {@code
|
||||||
|
* rendererFormat}. Ignored if {@code rendererFormat} is null.
|
||||||
|
* @param isRecoverable If the failure can be recovered by disabling and re-enabling the renderer.
|
||||||
|
* @return The created instance.
|
||||||
|
*/
|
||||||
|
public static ExoPlaybackException createForRenderer(
|
||||||
|
Exception cause,
|
||||||
|
String rendererName,
|
||||||
|
int rendererIndex,
|
||||||
|
@Nullable Format rendererFormat,
|
||||||
|
@FormatSupport int rendererFormatSupport,
|
||||||
|
boolean isRecoverable) {
|
||||||
return new ExoPlaybackException(
|
return new ExoPlaybackException(
|
||||||
TYPE_RENDERER,
|
TYPE_RENDERER,
|
||||||
cause,
|
cause,
|
||||||
|
|
@ -175,7 +212,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
rendererIndex,
|
rendererIndex,
|
||||||
rendererFormat,
|
rendererFormat,
|
||||||
rendererFormat == null ? RendererCapabilities.FORMAT_HANDLED : rendererFormatSupport,
|
rendererFormat == null ? RendererCapabilities.FORMAT_HANDLED : rendererFormatSupport,
|
||||||
TIMEOUT_OPERATION_UNDEFINED);
|
TIMEOUT_OPERATION_UNDEFINED,
|
||||||
|
isRecoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -225,7 +263,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
/* rendererIndex= */ C.INDEX_UNSET,
|
/* rendererIndex= */ C.INDEX_UNSET,
|
||||||
/* rendererFormat= */ null,
|
/* rendererFormat= */ null,
|
||||||
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED,
|
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED,
|
||||||
timeoutOperation);
|
timeoutOperation,
|
||||||
|
/* isRecoverable= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExoPlaybackException(@Type int type, Throwable cause) {
|
private ExoPlaybackException(@Type int type, Throwable cause) {
|
||||||
|
|
@ -237,7 +276,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
/* rendererIndex= */ C.INDEX_UNSET,
|
/* rendererIndex= */ C.INDEX_UNSET,
|
||||||
/* rendererFormat= */ null,
|
/* rendererFormat= */ null,
|
||||||
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED,
|
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED,
|
||||||
TIMEOUT_OPERATION_UNDEFINED);
|
TIMEOUT_OPERATION_UNDEFINED,
|
||||||
|
/* isRecoverable= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExoPlaybackException(@Type int type, String message) {
|
private ExoPlaybackException(@Type int type, String message) {
|
||||||
|
|
@ -249,7 +289,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
/* rendererIndex= */ C.INDEX_UNSET,
|
/* rendererIndex= */ C.INDEX_UNSET,
|
||||||
/* rendererFormat= */ null,
|
/* rendererFormat= */ null,
|
||||||
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED,
|
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED,
|
||||||
/* timeoutOperation= */ TIMEOUT_OPERATION_UNDEFINED);
|
/* timeoutOperation= */ TIMEOUT_OPERATION_UNDEFINED,
|
||||||
|
/* isRecoverable= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExoPlaybackException(
|
private ExoPlaybackException(
|
||||||
|
|
@ -260,7 +301,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
int rendererIndex,
|
int rendererIndex,
|
||||||
@Nullable Format rendererFormat,
|
@Nullable Format rendererFormat,
|
||||||
@FormatSupport int rendererFormatSupport,
|
@FormatSupport int rendererFormatSupport,
|
||||||
@TimeoutOperation int timeoutOperation) {
|
@TimeoutOperation int timeoutOperation,
|
||||||
|
boolean isRecoverable) {
|
||||||
this(
|
this(
|
||||||
deriveMessage(
|
deriveMessage(
|
||||||
type,
|
type,
|
||||||
|
|
@ -277,7 +319,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
rendererFormatSupport,
|
rendererFormatSupport,
|
||||||
/* mediaPeriodId= */ null,
|
/* mediaPeriodId= */ null,
|
||||||
timeoutOperation,
|
timeoutOperation,
|
||||||
/* timestampMs= */ SystemClock.elapsedRealtime());
|
/* timestampMs= */ SystemClock.elapsedRealtime(),
|
||||||
|
isRecoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExoPlaybackException(
|
private ExoPlaybackException(
|
||||||
|
|
@ -290,7 +333,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
@FormatSupport int rendererFormatSupport,
|
@FormatSupport int rendererFormatSupport,
|
||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
@TimeoutOperation int timeoutOperation,
|
@TimeoutOperation int timeoutOperation,
|
||||||
long timestampMs) {
|
long timestampMs,
|
||||||
|
boolean isRecoverable) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
|
|
@ -301,6 +345,7 @@ public final class ExoPlaybackException extends Exception {
|
||||||
this.mediaPeriodId = mediaPeriodId;
|
this.mediaPeriodId = mediaPeriodId;
|
||||||
this.timeoutOperation = timeoutOperation;
|
this.timeoutOperation = timeoutOperation;
|
||||||
this.timestampMs = timestampMs;
|
this.timestampMs = timestampMs;
|
||||||
|
this.isRecoverable = isRecoverable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -360,7 +405,7 @@ public final class ExoPlaybackException extends Exception {
|
||||||
* @return The copied exception.
|
* @return The copied exception.
|
||||||
*/
|
*/
|
||||||
@CheckResult
|
@CheckResult
|
||||||
/* package= */ ExoPlaybackException copyWithMediaPeriodId(
|
/* package */ ExoPlaybackException copyWithMediaPeriodId(
|
||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
return new ExoPlaybackException(
|
return new ExoPlaybackException(
|
||||||
getMessage(),
|
getMessage(),
|
||||||
|
|
@ -372,7 +417,8 @@ public final class ExoPlaybackException extends Exception {
|
||||||
rendererFormatSupport,
|
rendererFormatSupport,
|
||||||
mediaPeriodId,
|
mediaPeriodId,
|
||||||
timeoutOperation,
|
timeoutOperation,
|
||||||
timestampMs);
|
timestampMs,
|
||||||
|
isRecoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
private static final int MSG_PLAYLIST_UPDATE_REQUESTED = 22;
|
private static final int MSG_PLAYLIST_UPDATE_REQUESTED = 22;
|
||||||
private static final int MSG_SET_PAUSE_AT_END_OF_WINDOW = 23;
|
private static final int MSG_SET_PAUSE_AT_END_OF_WINDOW = 23;
|
||||||
private static final int MSG_SET_OFFLOAD_SCHEDULING_ENABLED = 24;
|
private static final int MSG_SET_OFFLOAD_SCHEDULING_ENABLED = 24;
|
||||||
|
private static final int MSG_ATTEMPT_ERROR_RECOVERY = 25;
|
||||||
|
|
||||||
private static final int ACTIVE_INTERVAL_MS = 10;
|
private static final int ACTIVE_INTERVAL_MS = 10;
|
||||||
private static final int IDLE_INTERVAL_MS = 1000;
|
private static final int IDLE_INTERVAL_MS = 1000;
|
||||||
|
|
@ -196,6 +197,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
private long rendererPositionUs;
|
private long rendererPositionUs;
|
||||||
private int nextPendingMessageIndexHint;
|
private int nextPendingMessageIndexHint;
|
||||||
private boolean deliverPendingMessageAtStartPositionRequired;
|
private boolean deliverPendingMessageAtStartPositionRequired;
|
||||||
|
@Nullable private ExoPlaybackException pendingRecoverableError;
|
||||||
|
|
||||||
private long releaseTimeoutMs;
|
private long releaseTimeoutMs;
|
||||||
private boolean throwWhenStuckBuffering;
|
private boolean throwWhenStuckBuffering;
|
||||||
|
|
@ -525,6 +527,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
case MSG_SET_OFFLOAD_SCHEDULING_ENABLED:
|
case MSG_SET_OFFLOAD_SCHEDULING_ENABLED:
|
||||||
setOffloadSchedulingEnabledInternal(msg.arg1 == 1);
|
setOffloadSchedulingEnabledInternal(msg.arg1 == 1);
|
||||||
break;
|
break;
|
||||||
|
case MSG_ATTEMPT_ERROR_RECOVERY:
|
||||||
|
attemptErrorRecovery((ExoPlaybackException) msg.obj);
|
||||||
|
break;
|
||||||
case MSG_RELEASE:
|
case MSG_RELEASE:
|
||||||
releaseInternal();
|
releaseInternal();
|
||||||
// Return immediately to not send playback info updates after release.
|
// Return immediately to not send playback info updates after release.
|
||||||
|
|
@ -542,9 +547,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
e = e.copyWithMediaPeriodId(readingPeriod.info.id);
|
e = e.copyWithMediaPeriodId(readingPeriod.info.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.e(TAG, "Playback error", e);
|
if (e.isRecoverable && pendingRecoverableError == null) {
|
||||||
stopInternal(/* forceResetRenderers= */ true, /* acknowledgeStop= */ false);
|
Log.w(TAG, "Recoverable playback error", e);
|
||||||
playbackInfo = playbackInfo.copyWithPlaybackError(e);
|
pendingRecoverableError = e;
|
||||||
|
Message message = handler.obtainMessage(MSG_ATTEMPT_ERROR_RECOVERY, e);
|
||||||
|
// Given that the player is now in an unhandled exception state, the error needs to be
|
||||||
|
// recovered or the player stopped before any other message is handled.
|
||||||
|
message.getTarget().sendMessageAtFrontOfQueue(message);
|
||||||
|
} else {
|
||||||
|
if (pendingRecoverableError != null) {
|
||||||
|
e.addSuppressed(pendingRecoverableError);
|
||||||
|
pendingRecoverableError = null;
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Playback error", e);
|
||||||
|
stopInternal(/* forceResetRenderers= */ true, /* acknowledgeStop= */ false);
|
||||||
|
playbackInfo = playbackInfo.copyWithPlaybackError(e);
|
||||||
|
}
|
||||||
maybeNotifyPlaybackInfoChanged();
|
maybeNotifyPlaybackInfoChanged();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ExoPlaybackException error = ExoPlaybackException.createForSource(e);
|
ExoPlaybackException error = ExoPlaybackException.createForSource(e);
|
||||||
|
|
@ -572,6 +590,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
|
private void attemptErrorRecovery(ExoPlaybackException exceptionToRecoverFrom)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
Assertions.checkArgument(
|
||||||
|
exceptionToRecoverFrom.isRecoverable
|
||||||
|
&& exceptionToRecoverFrom.type == ExoPlaybackException.TYPE_RENDERER);
|
||||||
|
try {
|
||||||
|
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
exceptionToRecoverFrom.addSuppressed(e);
|
||||||
|
throw exceptionToRecoverFrom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks the current thread until a condition becomes true.
|
* Blocks the current thread until a condition becomes true.
|
||||||
*
|
*
|
||||||
|
|
@ -929,6 +960,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING
|
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING
|
||||||
&& shouldTransitionToReadyState(renderersAllowPlayback)) {
|
&& shouldTransitionToReadyState(renderersAllowPlayback)) {
|
||||||
setState(Player.STATE_READY);
|
setState(Player.STATE_READY);
|
||||||
|
pendingRecoverableError = null; // Any pending error was successfully recovered from.
|
||||||
if (shouldPlayWhenReady()) {
|
if (shouldPlayWhenReady()) {
|
||||||
startRenderers();
|
startRenderers();
|
||||||
}
|
}
|
||||||
|
|
@ -1318,6 +1350,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
if (releaseMediaSourceList) {
|
if (releaseMediaSourceList) {
|
||||||
mediaSourceList.release();
|
mediaSourceList.release();
|
||||||
}
|
}
|
||||||
|
pendingRecoverableError = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<MediaPeriodId, Long> getPlaceholderFirstMediaPeriodPosition(Timeline timeline) {
|
private Pair<MediaPeriodId, Long> getPlaceholderFirstMediaPeriodPosition(Timeline timeline) {
|
||||||
|
|
|
||||||
|
|
@ -136,34 +136,41 @@ public interface AudioSink {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Thrown when a failure occurs initializing the sink. */
|
||||||
* Thrown when a failure occurs initializing the sink.
|
|
||||||
*/
|
|
||||||
final class InitializationException extends Exception {
|
final class InitializationException extends Exception {
|
||||||
|
|
||||||
/**
|
/** The underlying {@link AudioTrack}'s state. */
|
||||||
* The underlying {@link AudioTrack}'s state, if applicable.
|
|
||||||
*/
|
|
||||||
public final int audioTrackState;
|
public final int audioTrackState;
|
||||||
|
/** If the exception can be recovered by recreating the sink. */
|
||||||
|
public final boolean isRecoverable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param audioTrackState The underlying {@link AudioTrack}'s state, if applicable.
|
|
||||||
* @param sampleRate The requested sample rate in Hz.
|
* @param sampleRate The requested sample rate in Hz.
|
||||||
* @param channelConfig The requested channel configuration.
|
* @param channelConfig The requested channel configuration.
|
||||||
* @param bufferSize The requested buffer size in bytes.
|
* @param bufferSize The requested buffer size in bytes.
|
||||||
|
* @param audioTrackException Exception thrown during the creation of the {@link AudioTrack}.
|
||||||
*/
|
*/
|
||||||
public InitializationException(int audioTrackState, int sampleRate, int channelConfig,
|
public InitializationException(
|
||||||
int bufferSize) {
|
int audioTrackState,
|
||||||
super("AudioTrack init failed: " + audioTrackState + ", Config(" + sampleRate + ", "
|
int sampleRate,
|
||||||
+ channelConfig + ", " + bufferSize + ")");
|
int channelConfig,
|
||||||
|
int bufferSize,
|
||||||
|
boolean isRecoverable,
|
||||||
|
@Nullable Exception audioTrackException) {
|
||||||
|
super(
|
||||||
|
"AudioTrack init failed "
|
||||||
|
+ audioTrackState
|
||||||
|
+ " "
|
||||||
|
+ ("Config(" + sampleRate + ", " + channelConfig + ", " + bufferSize + ")")
|
||||||
|
+ (isRecoverable ? " (recoverable)" : ""),
|
||||||
|
audioTrackException);
|
||||||
this.audioTrackState = audioTrackState;
|
this.audioTrackState = audioTrackState;
|
||||||
|
this.isRecoverable = isRecoverable;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Thrown when a failure occurs writing to the sink. */
|
||||||
* Thrown when a failure occurs writing to the sink.
|
|
||||||
*/
|
|
||||||
final class WriteException extends Exception {
|
final class WriteException extends Exception {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -173,12 +180,13 @@ public interface AudioSink {
|
||||||
* Otherwise, the meaning of the error code depends on the sink implementation.
|
* Otherwise, the meaning of the error code depends on the sink implementation.
|
||||||
*/
|
*/
|
||||||
public final int errorCode;
|
public final int errorCode;
|
||||||
|
/** If the exception can be recovered by recreating the sink. */
|
||||||
|
public final boolean isRecoverable;
|
||||||
|
|
||||||
/**
|
/** @param errorCode The error value returned from the sink implementation. */
|
||||||
* @param errorCode The error value returned from the sink implementation.
|
public WriteException(int errorCode, boolean isRecoverable) {
|
||||||
*/
|
|
||||||
public WriteException(int errorCode) {
|
|
||||||
super("AudioTrack write failed: " + errorCode);
|
super("AudioTrack write failed: " + errorCode);
|
||||||
|
this.isRecoverable = isRecoverable;
|
||||||
this.errorCode = errorCode;
|
this.errorCode = errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ public abstract class DecoderAudioRenderer<
|
||||||
try {
|
try {
|
||||||
audioSink.playToEndOfStream();
|
audioSink.playToEndOfStream();
|
||||||
} catch (AudioSink.WriteException e) {
|
} catch (AudioSink.WriteException e) {
|
||||||
throw createRendererException(e, inputFormat);
|
throw createRendererException(e, inputFormat, e.isRecoverable);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -296,11 +296,12 @@ public abstract class DecoderAudioRenderer<
|
||||||
while (drainOutputBuffer()) {}
|
while (drainOutputBuffer()) {}
|
||||||
while (feedInputBuffer()) {}
|
while (feedInputBuffer()) {}
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
} catch (DecoderException
|
} catch (DecoderException | AudioSink.ConfigurationException e) {
|
||||||
| AudioSink.ConfigurationException
|
|
||||||
| AudioSink.InitializationException
|
|
||||||
| AudioSink.WriteException e) {
|
|
||||||
throw createRendererException(e, inputFormat);
|
throw createRendererException(e, inputFormat);
|
||||||
|
} catch (AudioSink.InitializationException e) {
|
||||||
|
throw createRendererException(e, inputFormat, e.isRecoverable);
|
||||||
|
} catch (AudioSink.WriteException e) {
|
||||||
|
throw createRendererException(e, inputFormat, e.isRecoverable);
|
||||||
}
|
}
|
||||||
decoderCounters.ensureUpdated();
|
decoderCounters.ensureUpdated();
|
||||||
}
|
}
|
||||||
|
|
@ -383,7 +384,7 @@ public abstract class DecoderAudioRenderer<
|
||||||
try {
|
try {
|
||||||
processEndOfStream();
|
processEndOfStream();
|
||||||
} catch (AudioSink.WriteException e) {
|
} catch (AudioSink.WriteException e) {
|
||||||
throw createRendererException(e, getOutputFormat(decoder));
|
throw createRendererException(e, getOutputFormat(decoder), e.isRecoverable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -908,7 +908,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
if (isRecoverable) {
|
if (isRecoverable) {
|
||||||
maybeDisableOffload();
|
maybeDisableOffload();
|
||||||
}
|
}
|
||||||
throw new WriteException(bytesWritten);
|
throw new WriteException(bytesWritten, isRecoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOffloadedPlayback(audioTrack)) {
|
if (isOffloadedPlayback(audioTrack)) {
|
||||||
|
|
@ -1883,9 +1883,14 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
AudioTrack audioTrack;
|
AudioTrack audioTrack;
|
||||||
try {
|
try {
|
||||||
audioTrack = createAudioTrack(tunneling, audioAttributes, audioSessionId);
|
audioTrack = createAudioTrack(tunneling, audioAttributes, audioSessionId);
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException | IllegalArgumentException e) {
|
||||||
throw new InitializationException(
|
throw new InitializationException(
|
||||||
AudioTrack.STATE_UNINITIALIZED, outputSampleRate, outputChannelConfig, bufferSize);
|
AudioTrack.STATE_UNINITIALIZED,
|
||||||
|
outputSampleRate,
|
||||||
|
outputChannelConfig,
|
||||||
|
bufferSize,
|
||||||
|
/* isRecoverable= */ outputModeIsOffload(),
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
|
|
||||||
int state = audioTrack.getState();
|
int state = audioTrack.getState();
|
||||||
|
|
@ -1896,7 +1901,13 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
// The track has already failed to initialize, so it wouldn't be that surprising if
|
// The track has already failed to initialize, so it wouldn't be that surprising if
|
||||||
// release were to fail too. Swallow the exception.
|
// release were to fail too. Swallow the exception.
|
||||||
}
|
}
|
||||||
throw new InitializationException(state, outputSampleRate, outputChannelConfig, bufferSize);
|
throw new InitializationException(
|
||||||
|
state,
|
||||||
|
outputSampleRate,
|
||||||
|
outputChannelConfig,
|
||||||
|
bufferSize,
|
||||||
|
/* isRecoverable= */ outputModeIsOffload(),
|
||||||
|
/* audioTrackException= */ null);
|
||||||
}
|
}
|
||||||
return audioTrack;
|
return audioTrack;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.PlayerMessage.Target;
|
import com.google.android.exoplayer2.PlayerMessage.Target;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
|
||||||
|
import com.google.android.exoplayer2.audio.AudioSink.InitializationException;
|
||||||
|
import com.google.android.exoplayer2.audio.AudioSink.WriteException;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||||
|
|
@ -616,8 +618,10 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
boolean fullyConsumed;
|
boolean fullyConsumed;
|
||||||
try {
|
try {
|
||||||
fullyConsumed = audioSink.handleBuffer(buffer, bufferPresentationTimeUs, sampleCount);
|
fullyConsumed = audioSink.handleBuffer(buffer, bufferPresentationTimeUs, sampleCount);
|
||||||
} catch (AudioSink.InitializationException | AudioSink.WriteException e) {
|
} catch (InitializationException e) {
|
||||||
throw createRendererException(e, format);
|
throw createRendererException(e, format, e.isRecoverable);
|
||||||
|
} catch (WriteException e) {
|
||||||
|
throw createRendererException(e, format, e.isRecoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullyConsumed) {
|
if (fullyConsumed) {
|
||||||
|
|
@ -637,7 +641,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
audioSink.playToEndOfStream();
|
audioSink.playToEndOfStream();
|
||||||
} catch (AudioSink.WriteException e) {
|
} catch (AudioSink.WriteException e) {
|
||||||
@Nullable Format outputFormat = getOutputFormat();
|
@Nullable Format outputFormat = getOutputFormat();
|
||||||
throw createRendererException(e, outputFormat != null ? outputFormat : getInputFormat());
|
throw createRendererException(
|
||||||
|
e, outputFormat != null ? outputFormat : getInputFormat(), e.isRecoverable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue