Commit 1a34de59 by olly Committed by kim-vde

Add plumbing for reporting internal decoder errors

PiperOrigin-RevId: 357732695
parent e009322e
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
* Audio: * Audio:
* Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases * Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases
([#8585](https://github.com/google/ExoPlayer/issues/8585)). ([#8585](https://github.com/google/ExoPlayer/issues/8585)).
* Analytics:
* Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`.
* Library restructuring: * Library restructuring:
* `DebugTextViewHelper` moved from `ui` package to `util` package. * `DebugTextViewHelper` moved from `ui` package to `util` package.
* Spherical UI components moved from `video.spherical` package to * Spherical UI components moved from `video.spherical` package to
......
...@@ -2098,6 +2098,11 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -2098,6 +2098,11 @@ public class SimpleExoPlayer extends BasePlayer
analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount); analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount);
} }
@Override
public void onVideoCodecError(Exception videoCodecError) {
analyticsCollector.onVideoCodecError(videoCodecError);
}
// AudioRendererEventListener implementation // AudioRendererEventListener implementation
@Override @Override
...@@ -2156,6 +2161,11 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -2156,6 +2161,11 @@ public class SimpleExoPlayer extends BasePlayer
analyticsCollector.onAudioSinkError(audioSinkError); analyticsCollector.onAudioSinkError(audioSinkError);
} }
@Override
public void onAudioCodecError(Exception audioCodecError) {
analyticsCollector.onAudioCodecError(audioCodecError);
}
// TextOutput implementation // TextOutput implementation
@Override @Override
......
...@@ -309,6 +309,15 @@ public class AnalyticsCollector ...@@ -309,6 +309,15 @@ public class AnalyticsCollector
listener -> listener.onAudioSinkError(eventTime, audioSinkError)); 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. // Additional audio events.
/** /**
...@@ -456,6 +465,15 @@ public class AnalyticsCollector ...@@ -456,6 +465,15 @@ public class AnalyticsCollector
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount)); 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. // Additional video events.
/** /**
......
...@@ -17,6 +17,8 @@ package com.google.android.exoplayer2.analytics; ...@@ -17,6 +17,8 @@ package com.google.android.exoplayer2.analytics;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.Surface; import android.view.Surface;
...@@ -35,6 +37,7 @@ import com.google.android.exoplayer2.Timeline; ...@@ -35,6 +37,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.AudioSink;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.LoadEventInfo;
...@@ -198,6 +201,8 @@ public interface AnalyticsListener { ...@@ -198,6 +201,8 @@ public interface AnalyticsListener {
EVENT_DRM_KEYS_REMOVED, EVENT_DRM_KEYS_REMOVED,
EVENT_DRM_SESSION_RELEASED, EVENT_DRM_SESSION_RELEASED,
EVENT_PLAYER_RELEASED, EVENT_PLAYER_RELEASED,
EVENT_AUDIO_CODEC_ERROR,
EVENT_VIDEO_CODEC_ERROR,
}) })
@interface EventFlags {} @interface EventFlags {}
/** {@link Player#getCurrentTimeline()} changed. */ /** {@link Player#getCurrentTimeline()} changed. */
...@@ -312,6 +317,10 @@ public interface AnalyticsListener { ...@@ -312,6 +317,10 @@ public interface AnalyticsListener {
int EVENT_DRM_SESSION_RELEASED = 1035; int EVENT_DRM_SESSION_RELEASED = 1035;
/** The player was released. */ /** The player was released. */
int EVENT_PLAYER_RELEASED = 1036; 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. */ /** Time information of an event. */
final class EventTime { final class EventTime {
...@@ -649,8 +658,14 @@ public interface AnalyticsListener { ...@@ -649,8 +658,14 @@ public interface AnalyticsListener {
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {} EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
/** /**
* Called when a media source loading error occurred. These errors are just for informational * Called when a media source loading error occurred.
* purposes and the player may recover. *
* <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 eventTime The event time.
* @param loadEventInfo The {@link LoadEventInfo} defining the load event. * @param loadEventInfo The {@link LoadEventInfo} defining the load event.
...@@ -829,8 +844,14 @@ public interface AnalyticsListener { ...@@ -829,8 +844,14 @@ public interface AnalyticsListener {
default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {} default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {}
/** /**
* Called when {@link AudioSink} has encountered an error. These errors are just for informational * Called when {@link AudioSink} has encountered an error.
* purposes and the player may recover. *
* <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 eventTime The event time.
* @param audioSinkError Either a {@link AudioSink.InitializationException} or a {@link * @param audioSinkError Either a {@link AudioSink.InitializationException} or a {@link
...@@ -839,6 +860,22 @@ public interface AnalyticsListener { ...@@ -839,6 +860,22 @@ public interface AnalyticsListener {
default void onAudioSinkError(EventTime eventTime, Exception audioSinkError) {} 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. * Called when the volume changes.
* *
* @param eventTime The event time. * @param eventTime The event time.
...@@ -932,6 +969,22 @@ public interface AnalyticsListener { ...@@ -932,6 +969,22 @@ public interface AnalyticsListener {
EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {} 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 * 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. * renderer was reset, or since the stream being rendered was changed.
* *
...@@ -987,8 +1040,14 @@ public interface AnalyticsListener { ...@@ -987,8 +1040,14 @@ public interface AnalyticsListener {
default void onDrmKeysLoaded(EventTime eventTime) {} default void onDrmKeysLoaded(EventTime eventTime) {}
/** /**
* Called when a drm error occurs. These errors are just for informational purposes and the player * Called when a drm error occurs.
* may recover. *
* <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 eventTime The event time.
* @param error The error. * @param error The error.
......
...@@ -18,15 +18,17 @@ package com.google.android.exoplayer2.audio; ...@@ -18,15 +18,17 @@ package com.google.android.exoplayer2.audio;
import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -114,24 +116,35 @@ public interface AudioRendererEventListener { ...@@ -114,24 +116,35 @@ public interface AudioRendererEventListener {
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {} 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. * Called when {@link AudioSink} has encountered an error.
* *
* <p>If the sink writes to a platform {@link AudioTrack}, this will called for all {@link * <p>If the sink writes to a platform {@link AudioTrack}, this will called for all {@link
* AudioTrack} errors. * AudioTrack} errors.
* *
* <p>This method being called does not indicate that playback has failed, or that it will fail. * <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, * The player may be able to recover from the error. Hence applications should <em>not</em>
* 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.
* 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.
* ({@link Player.EventListener#onPlayerError} is the appropriate place to implement such * This method is called to provide the application with an opportunity to log the error if it
* behavior). This method is called to provide the application with an opportunity to log the * wishes to do so.
* error if it wishes to do so. *
* * @param audioSinkError The error that occurred. Typically an {@link
* <p>Fatal errors that cannot be recovered will be reported wrapped in a {@link * AudioSink.InitializationException} or a {@link AudioSink.WriteException}.
* ExoPlaybackException} by {@link Player.EventListener#onPlayerError(ExoPlaybackException)}.
*
* @param audioSinkError Either an {@link AudioSink.InitializationException} or a {@link
* AudioSink.WriteException} describing the error.
*/ */
default void onAudioSinkError(Exception audioSinkError) {} default void onAudioSinkError(Exception audioSinkError) {}
...@@ -230,5 +243,12 @@ public interface AudioRendererEventListener { ...@@ -230,5 +243,12 @@ public interface AudioRendererEventListener {
handler.post(() -> castNonNull(listener).onAudioSinkError(audioSinkError)); 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< ...@@ -300,6 +300,7 @@ public abstract class DecoderAudioRenderer<
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
TraceUtil.endSection(); TraceUtil.endSection();
} catch (DecoderException e) { } catch (DecoderException e) {
eventDispatcher.audioCodecError(e);
throw createRendererException(e, inputFormat); throw createRendererException(e, inputFormat);
} catch (AudioSink.ConfigurationException e) { } catch (AudioSink.ConfigurationException e) {
throw createRendererException(e, e.format); throw createRendererException(e, e.format);
...@@ -618,7 +619,10 @@ public abstract class DecoderAudioRenderer< ...@@ -618,7 +619,10 @@ public abstract class DecoderAudioRenderer<
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp); codecInitializedTimestamp - codecInitializingTimestamp);
decoderCounters.decoderInitCount++; decoderCounters.decoderInitCount++;
} catch (DecoderException | OutOfMemoryError e) { } catch (DecoderException e) {
eventDispatcher.audioCodecError(e);
throw createRendererException(e, inputFormat);
} catch (OutOfMemoryError e) {
throw createRendererException(e, inputFormat); throw createRendererException(e, inputFormat);
} }
} }
......
...@@ -415,6 +415,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -415,6 +415,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onCodecError(Exception codecError) {
eventDispatcher.audioCodecError(codecError);
}
@Override
@Nullable @Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
throws ExoPlaybackException { throws ExoPlaybackException {
......
...@@ -848,6 +848,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -848,6 +848,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
if (isMediaCodecException(e)) { if (isMediaCodecException(e)) {
onCodecError(e);
boolean isRecoverable = boolean isRecoverable =
enableRecoverableCodecExceptionRetries enableRecoverableCodecExceptionRetries
&& Util.SDK_INT >= 21 && Util.SDK_INT >= 21
...@@ -1407,6 +1408,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1407,6 +1408,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* 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}. * Called when a new {@link Format} is read from the upstream {@link MediaPeriod}.
* *
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}. * @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
......
...@@ -92,11 +92,11 @@ public interface MediaSourceEventListener { ...@@ -92,11 +92,11 @@ public interface MediaSourceEventListener {
* <em>not</em> be called in addition to this method. * <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. * <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 * The player may be able to recover from the error. Hence applications should <em>not</em>
* <em>not</em> implement this method to display a user visible error or initiate an application * implement this method to display a user visible error or initiate an application level retry.
* level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement * {@link Player.EventListener#onPlayerError} is the appropriate place to implement such behavior.
* such behavior). This method is called to provide the application with an opportunity to log the * This method is called to provide the application with an opportunity to log the error if it
* error if it wishes to do so. * wishes to do so.
* *
* @param windowIndex The window index in the timeline of the media source this load belongs to. * @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 * @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 { ...@@ -206,6 +206,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
TraceUtil.endSection(); TraceUtil.endSection();
} catch (DecoderException e) { } catch (DecoderException e) {
eventDispatcher.videoCodecError(e);
throw createRendererException(e, inputFormat); throw createRendererException(e, inputFormat);
} }
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
...@@ -707,7 +708,10 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -707,7 +708,10 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
decoderInitializedTimestamp, decoderInitializedTimestamp,
decoderInitializedTimestamp - decoderInitializingTimestamp); decoderInitializedTimestamp - decoderInitializingTimestamp);
decoderCounters.decoderInitCount++; decoderCounters.decoderInitCount++;
} catch (DecoderException | OutOfMemoryError e) { } catch (DecoderException e) {
eventDispatcher.videoCodecError(e);
throw createRendererException(e, inputFormat);
} catch (OutOfMemoryError e) {
throw createRendererException(e, inputFormat); throw createRendererException(e, inputFormat);
} }
} }
......
...@@ -691,6 +691,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -691,6 +691,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
@Override @Override
protected void onCodecError(Exception codecError) {
eventDispatcher.videoCodecError(codecError);
}
@Override
@Nullable @Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
throws ExoPlaybackException { throws ExoPlaybackException {
......
...@@ -17,14 +17,18 @@ package com.google.android.exoplayer2.video; ...@@ -17,14 +17,18 @@ package com.google.android.exoplayer2.video;
import static com.google.android.exoplayer2.util.Util.castNonNull; 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.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
import android.view.TextureView; import android.view.TextureView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -147,8 +151,21 @@ public interface VideoRendererEventListener { ...@@ -147,8 +151,21 @@ public interface VideoRendererEventListener {
default void onVideoDisabled(DecoderCounters counters) {} 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 { final class EventDispatcher {
@Nullable private final Handler handler; @Nullable private final Handler handler;
...@@ -254,6 +271,12 @@ public interface VideoRendererEventListener { ...@@ -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));
}
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment