Add plumbing for reporting internal decoder errors
PiperOrigin-RevId: 357732695
This commit is contained in:
parent
e009322edd
commit
1a34de5954
@ -9,6 +9,8 @@
|
||||
* Audio:
|
||||
* Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases
|
||||
([#8585](https://github.com/google/ExoPlayer/issues/8585)).
|
||||
* Analytics:
|
||||
* Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`.
|
||||
* Library restructuring:
|
||||
* `DebugTextViewHelper` moved from `ui` package to `util` package.
|
||||
* Spherical UI components moved from `video.spherical` package to
|
||||
|
@ -2098,6 +2098,11 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoCodecError(Exception videoCodecError) {
|
||||
analyticsCollector.onVideoCodecError(videoCodecError);
|
||||
}
|
||||
|
||||
// AudioRendererEventListener implementation
|
||||
|
||||
@Override
|
||||
@ -2156,6 +2161,11 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
analyticsCollector.onAudioSinkError(audioSinkError);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioCodecError(Exception audioCodecError) {
|
||||
analyticsCollector.onAudioCodecError(audioCodecError);
|
||||
}
|
||||
|
||||
// TextOutput implementation
|
||||
|
||||
@Override
|
||||
|
@ -309,6 +309,15 @@ public class AnalyticsCollector
|
||||
listener -> listener.onAudioSinkError(eventTime, audioSinkError));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAudioCodecError(Exception audioCodecError) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
sendEvent(
|
||||
eventTime,
|
||||
AnalyticsListener.EVENT_AUDIO_CODEC_ERROR,
|
||||
listener -> listener.onAudioCodecError(eventTime, audioCodecError));
|
||||
}
|
||||
|
||||
// Additional audio events.
|
||||
|
||||
/**
|
||||
@ -456,6 +465,15 @@ public class AnalyticsCollector
|
||||
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoCodecError(Exception videoCodecError) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
sendEvent(
|
||||
eventTime,
|
||||
AnalyticsListener.EVENT_VIDEO_CODEC_ERROR,
|
||||
listener -> listener.onVideoCodecError(eventTime, videoCodecError));
|
||||
}
|
||||
|
||||
// Additional video events.
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,8 @@ package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.CodecException;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Surface;
|
||||
@ -35,6 +37,7 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.audio.AudioSink;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.LoadEventInfo;
|
||||
@ -198,6 +201,8 @@ public interface AnalyticsListener {
|
||||
EVENT_DRM_KEYS_REMOVED,
|
||||
EVENT_DRM_SESSION_RELEASED,
|
||||
EVENT_PLAYER_RELEASED,
|
||||
EVENT_AUDIO_CODEC_ERROR,
|
||||
EVENT_VIDEO_CODEC_ERROR,
|
||||
})
|
||||
@interface EventFlags {}
|
||||
/** {@link Player#getCurrentTimeline()} changed. */
|
||||
@ -312,6 +317,10 @@ public interface AnalyticsListener {
|
||||
int EVENT_DRM_SESSION_RELEASED = 1035;
|
||||
/** The player was released. */
|
||||
int EVENT_PLAYER_RELEASED = 1036;
|
||||
/** The audio codec encountered an error. */
|
||||
int EVENT_AUDIO_CODEC_ERROR = 1037;
|
||||
/** The video codec encountered an error. */
|
||||
int EVENT_VIDEO_CODEC_ERROR = 1038;
|
||||
|
||||
/** Time information of an event. */
|
||||
final class EventTime {
|
||||
@ -649,8 +658,14 @@ public interface AnalyticsListener {
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
|
||||
|
||||
/**
|
||||
* Called when a media source loading error occurred. These errors are just for informational
|
||||
* purposes and the player may recover.
|
||||
* Called when a media source loading error occurred.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param loadEventInfo The {@link LoadEventInfo} defining the load event.
|
||||
@ -829,8 +844,14 @@ public interface AnalyticsListener {
|
||||
default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {}
|
||||
|
||||
/**
|
||||
* Called when {@link AudioSink} has encountered an error. These errors are just for informational
|
||||
* purposes and the player may recover.
|
||||
* Called when {@link AudioSink} has encountered an error.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param audioSinkError Either a {@link AudioSink.InitializationException} or a {@link
|
||||
@ -838,6 +859,22 @@ public interface AnalyticsListener {
|
||||
*/
|
||||
default void onAudioSinkError(EventTime eventTime, Exception audioSinkError) {}
|
||||
|
||||
/**
|
||||
* Called when an audio decoder encounters an error.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param audioCodecError The error. Typically a {@link CodecException} if the renderer uses
|
||||
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
|
||||
*/
|
||||
default void onAudioCodecError(EventTime eventTime, Exception audioCodecError) {}
|
||||
|
||||
/**
|
||||
* Called when the volume changes.
|
||||
*
|
||||
@ -931,6 +968,22 @@ public interface AnalyticsListener {
|
||||
default void onVideoFrameProcessingOffset(
|
||||
EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {}
|
||||
|
||||
/**
|
||||
* Called when a video decoder encounters an error.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param videoCodecError The error. Typically a {@link CodecException} if the renderer uses
|
||||
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
|
||||
*/
|
||||
default void onVideoCodecError(EventTime eventTime, Exception videoCodecError) {}
|
||||
|
||||
/**
|
||||
* Called when a frame is rendered for the first time since setting the surface, or since the
|
||||
* renderer was reset, or since the stream being rendered was changed.
|
||||
@ -987,8 +1040,14 @@ public interface AnalyticsListener {
|
||||
default void onDrmKeysLoaded(EventTime eventTime) {}
|
||||
|
||||
/**
|
||||
* Called when a drm error occurs. These errors are just for informational purposes and the player
|
||||
* may recover.
|
||||
* Called when a drm error occurs.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param error The error.
|
||||
|
@ -18,15 +18,17 @@ package com.google.android.exoplayer2.audio;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.CodecException;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
@ -113,6 +115,21 @@ public interface AudioRendererEventListener {
|
||||
*/
|
||||
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
|
||||
|
||||
/**
|
||||
* Called when an audio decoder encounters an error.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param audioCodecError The error. Typically a {@link CodecException} if the renderer uses
|
||||
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
|
||||
*/
|
||||
default void onAudioCodecError(Exception audioCodecError) {}
|
||||
|
||||
/**
|
||||
* Called when {@link AudioSink} has encountered an error.
|
||||
*
|
||||
@ -120,18 +137,14 @@ public interface AudioRendererEventListener {
|
||||
* AudioTrack} errors.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error (for example by recreating the AudioTrack,
|
||||
* possibly with different settings) and continue. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry
|
||||
* ({@link Player.EventListener#onPlayerError} is the appropriate place to implement such
|
||||
* behavior). This method is called to provide the application with an opportunity to log the
|
||||
* error if it wishes to do so.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* <p>Fatal errors that cannot be recovered will be reported wrapped in a {@link
|
||||
* ExoPlaybackException} by {@link Player.EventListener#onPlayerError(ExoPlaybackException)}.
|
||||
*
|
||||
* @param audioSinkError Either an {@link AudioSink.InitializationException} or a {@link
|
||||
* AudioSink.WriteException} describing the error.
|
||||
* @param audioSinkError The error that occurred. Typically an {@link
|
||||
* AudioSink.InitializationException} or a {@link AudioSink.WriteException}.
|
||||
*/
|
||||
default void onAudioSinkError(Exception audioSinkError) {}
|
||||
|
||||
@ -230,5 +243,12 @@ public interface AudioRendererEventListener {
|
||||
handler.post(() -> castNonNull(listener).onAudioSinkError(audioSinkError));
|
||||
}
|
||||
}
|
||||
|
||||
/** Invokes {@link AudioRendererEventListener#onAudioCodecError(Exception)}. */
|
||||
public void audioCodecError(Exception audioCodecError) {
|
||||
if (handler != null) {
|
||||
handler.post(() -> castNonNull(listener).onAudioCodecError(audioCodecError));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -300,6 +300,7 @@ public abstract class DecoderAudioRenderer<
|
||||
while (feedInputBuffer()) {}
|
||||
TraceUtil.endSection();
|
||||
} catch (DecoderException e) {
|
||||
eventDispatcher.audioCodecError(e);
|
||||
throw createRendererException(e, inputFormat);
|
||||
} catch (AudioSink.ConfigurationException e) {
|
||||
throw createRendererException(e, e.format);
|
||||
@ -618,7 +619,10 @@ public abstract class DecoderAudioRenderer<
|
||||
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
|
||||
codecInitializedTimestamp - codecInitializingTimestamp);
|
||||
decoderCounters.decoderInitCount++;
|
||||
} catch (DecoderException | OutOfMemoryError e) {
|
||||
} catch (DecoderException e) {
|
||||
eventDispatcher.audioCodecError(e);
|
||||
throw createRendererException(e, inputFormat);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw createRendererException(e, inputFormat);
|
||||
}
|
||||
}
|
||||
|
@ -414,6 +414,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
eventDispatcher.decoderReleased(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCodecError(Exception codecError) {
|
||||
eventDispatcher.audioCodecError(codecError);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
|
@ -848,6 +848,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
decoderCounters.ensureUpdated();
|
||||
} catch (IllegalStateException e) {
|
||||
if (isMediaCodecException(e)) {
|
||||
onCodecError(e);
|
||||
boolean isRecoverable =
|
||||
enableRecoverableCodecExceptionRetries
|
||||
&& Util.SDK_INT >= 21
|
||||
@ -1406,6 +1407,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a codec error has occurred.
|
||||
*
|
||||
* <p>The default implementation is a no-op.
|
||||
*
|
||||
* @param codecError The error.
|
||||
*/
|
||||
protected void onCodecError(Exception codecError) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new {@link Format} is read from the upstream {@link MediaPeriod}.
|
||||
*
|
||||
|
@ -92,11 +92,11 @@ public interface MediaSourceEventListener {
|
||||
* <em>not</em> be called in addition to this method.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error and continue. Hence applications should
|
||||
* <em>not</em> implement this method to display a user visible error or initiate an application
|
||||
* level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement
|
||||
* such behavior). This method is called to provide the application with an opportunity to log the
|
||||
* error if it wishes to do so.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param windowIndex The window index in the timeline of the media source this load belongs to.
|
||||
* @param mediaPeriodId The {@link MediaPeriodId} this load belongs to. Null if the load does not
|
||||
|
@ -206,6 +206,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||
while (feedInputBuffer()) {}
|
||||
TraceUtil.endSection();
|
||||
} catch (DecoderException e) {
|
||||
eventDispatcher.videoCodecError(e);
|
||||
throw createRendererException(e, inputFormat);
|
||||
}
|
||||
decoderCounters.ensureUpdated();
|
||||
@ -707,7 +708,10 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||
decoderInitializedTimestamp,
|
||||
decoderInitializedTimestamp - decoderInitializingTimestamp);
|
||||
decoderCounters.decoderInitCount++;
|
||||
} catch (DecoderException | OutOfMemoryError e) {
|
||||
} catch (DecoderException e) {
|
||||
eventDispatcher.videoCodecError(e);
|
||||
throw createRendererException(e, inputFormat);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw createRendererException(e, inputFormat);
|
||||
}
|
||||
}
|
||||
|
@ -690,6 +690,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
eventDispatcher.decoderReleased(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCodecError(Exception codecError) {
|
||||
eventDispatcher.videoCodecError(codecError);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
|
@ -17,14 +17,18 @@ package com.google.android.exoplayer2.video;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.CodecException;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
@ -147,8 +151,21 @@ public interface VideoRendererEventListener {
|
||||
default void onVideoDisabled(DecoderCounters counters) {}
|
||||
|
||||
/**
|
||||
* Dispatches events to a {@link VideoRendererEventListener}.
|
||||
* Called when a video decoder encounters an error.
|
||||
*
|
||||
* <p>This method being called does not indicate that playback has failed, or that it will fail.
|
||||
* The player may be able to recover from the error. Hence applications should <em>not</em>
|
||||
* implement this method to display a user visible error or initiate an application level retry.
|
||||
* {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
|
||||
* This method is called to provide the application with an opportunity to log the error if it
|
||||
* wishes to do so.
|
||||
*
|
||||
* @param videoCodecError The error. Typically a {@link CodecException} if the renderer uses
|
||||
* {@link MediaCodec}, or a {@link DecoderException} if the renderer uses a software decoder.
|
||||
*/
|
||||
default void onVideoCodecError(Exception videoCodecError) {}
|
||||
|
||||
/** Dispatches events to a {@link VideoRendererEventListener}. */
|
||||
final class EventDispatcher {
|
||||
|
||||
@Nullable private final Handler handler;
|
||||
@ -254,6 +271,12 @@ public interface VideoRendererEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
/** Invokes {@link VideoRendererEventListener#onVideoCodecError(Exception)}. */
|
||||
public void videoCodecError(Exception videoCodecError) {
|
||||
if (handler != null) {
|
||||
handler.post(() -> castNonNull(listener).onVideoCodecError(videoCodecError));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user