diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 116c2967c9..41052a0063 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index a98e3475af..49feb74b8b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 20bb920c57..ff613b85a9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -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. /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 7692ec47e2..bf0c8b5d22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -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. + * + *

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 not + * 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. + * + *

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 not + * 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. + * + *

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 not + * 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. + * + *

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 not + * 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. + * + *

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 not + * 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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 82ca6f4b9a..cfd476a421 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -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. + * + *

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 not + * 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. * *

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 not - * 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 not + * 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. * - *

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)); + } + } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java index 12962a786a..f69ffcbb90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java @@ -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); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 1704249c9c..82b65a1e0d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -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) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 2c368ef6fa..f51869ce43 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -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. + * + *

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}. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java index 39fd6d53a9..ac9d88e35a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java @@ -92,11 +92,11 @@ public interface MediaSourceEventListener { * not be called in addition to this method. * *

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 - * not 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 not + * 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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java index 5310bfd624..311f2747fe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java @@ -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); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index ef1825b72a..561055d6a2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -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) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java index aacd8ce8d4..7638aadca8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java @@ -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. + * + *

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 not + * 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)); + } + } } }