diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e23bf592a5..847746d425 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,11 @@ # Release notes # +### 2.11.4 (not yet released) ### + +* Text: Catch-and-log all fatal exceptions in `TextRenderer` instead of + re-throwing, allowing playback to continue even if subtitles fail + ([#6885](https://github.com/google/ExoPlayer/issues/6885)). + ### 2.11.3 (2020-02-19) ### * SmoothStreaming: Fix regression that broke playback in 2.11.2 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java index 03aabecb0e..4eef1ea32d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java @@ -149,6 +149,7 @@ public abstract class SimpleDecoder< while (!queuedOutputBuffers.isEmpty()) { queuedOutputBuffers.removeFirst().release(); } + exception = null; } } @@ -225,6 +226,7 @@ public abstract class SimpleDecoder< if (inputBuffer.isDecodeOnly()) { outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); } + @Nullable E exception; try { exception = decode(inputBuffer, outputBuffer, resetDecoder); } catch (RuntimeException e) { @@ -238,8 +240,9 @@ public abstract class SimpleDecoder< exception = createUnexpectedDecodeException(e); } if (exception != null) { - // Memory barrier to ensure that the decoder exception is visible from the playback thread. - synchronized (lock) {} + synchronized (lock) { + this.exception = exception; + } return false; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java index 058b1c4526..46c26db122 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java @@ -23,11 +23,11 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; @@ -45,6 +45,8 @@ import java.util.List; */ public final class TextRenderer extends BaseRenderer implements Callback { + private static final String TAG = "TextRenderer"; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -143,19 +145,13 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override protected void onPositionReset(long positionUs, boolean joining) { - clearOutput(); inputStreamEnded = false; outputStreamEnded = false; - if (decoderReplacementState != REPLACEMENT_STATE_NONE) { - replaceDecoder(); - } else { - releaseBuffers(); - decoder.flush(); - } + resetOutputAndDecoder(); } @Override - public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + public void render(long positionUs, long elapsedRealtimeUs) { if (outputStreamEnded) { return; } @@ -165,7 +161,8 @@ public final class TextRenderer extends BaseRenderer implements Callback { try { nextSubtitle = decoder.dequeueOutputBuffer(); } catch (SubtitleDecoderException e) { - throw createRendererException(e, streamFormat); + handleDecoderError(e); + return; } } @@ -247,7 +244,8 @@ public final class TextRenderer extends BaseRenderer implements Callback { } } } catch (SubtitleDecoderException e) { - throw createRendererException(e, streamFormat); + handleDecoderError(e); + return; } } @@ -329,4 +327,24 @@ public final class TextRenderer extends BaseRenderer implements Callback { output.onCues(cues); } + /** + * Called when {@link #decoder} throws an exception, so it can be logged and playback can + * continue. + * + *
Logs {@code e} and resets state to allow decoding the next sample. + */ + private void handleDecoderError(SubtitleDecoderException e) { + Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e); + resetOutputAndDecoder(); + } + + private void resetOutputAndDecoder() { + clearOutput(); + if (decoderReplacementState != REPLACEMENT_STATE_NONE) { + replaceDecoder(); + } else { + releaseBuffers(); + decoder.flush(); + } + } }