diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 993357ac4..5bf239a86 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -2517,23 +2517,11 @@ public final class Player implements Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); saveStreamProgressState(); - - // create error notification - final ErrorInfo errorInfo; - if (currentMetadata == null) { - errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, - "Player error[type=" + error.type + "] occurred, currentMetadata is null"); - } else { - errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, - "Player error[type=" + error.type + "] occurred while playing " - + currentMetadata.getMetadata().getUrl(), - currentMetadata.getMetadata()); - } - ErrorUtil.createNotification(context, errorInfo); + boolean isCatchableException = false; switch (error.type) { case ExoPlaybackException.TYPE_SOURCE: - processSourceError(error.getSourceException()); + isCatchableException = processSourceError(error.getSourceException()); break; case ExoPlaybackException.TYPE_UNEXPECTED: setRecovery(); @@ -2546,22 +2534,60 @@ public final class Player implements break; } + if (isCatchableException) { + return; + } + + createErrorNotification(error); + if (fragmentListener != null) { fragmentListener.onPlayerError(error); } } - private void processSourceError(final IOException error) { - if (exoPlayerIsNull() || playQueue == null) { - return; + private void createErrorNotification(@NonNull final ExoPlaybackException error) { + final ErrorInfo errorInfo; + if (currentMetadata == null) { + errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, + "Player error[type=" + error.type + "] occurred, currentMetadata is null"); + } else { + errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, + "Player error[type=" + error.type + "] occurred while playing " + + currentMetadata.getMetadata().getUrl(), + currentMetadata.getMetadata()); } + ErrorUtil.createNotification(context, errorInfo); + } + + /** + * Process an {@link IOException} returned by {@link ExoPlaybackException#getSourceException()} + * for {@link ExoPlaybackException#TYPE_SOURCE} exceptions. + * + *

+ * This method sets the recovery position and sends an error message to the play queue if the + * exception is not a {@link BehindLiveWindowException}. + *

+ * @param error the source error which was thrown by ExoPlayer + * @return whether the exception thrown is a {@link BehindLiveWindowException} ({@code false} + * is always returned if ExoPlayer or the play queue is null) + */ + private boolean processSourceError(final IOException error) { + if (exoPlayerIsNull() || playQueue == null) { + return false; + } + setRecovery(); if (error instanceof BehindLiveWindowException) { - reloadPlayQueueManager(); - } else { - playQueue.error(); + simpleExoPlayer.seekToDefaultPosition(); + simpleExoPlayer.prepare(); + // Inform the user that we are reloading the stream by switching to the buffering state + onBuffering(); + return true; } + + playQueue.error(); + return false; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 708b72ff2..a2f0d7149 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -9,6 +9,7 @@ import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker; import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.upstream.DataSource; @@ -17,9 +18,18 @@ import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; public class PlayerDataSource { + + public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000; + + /** + * An approximately 4.3 times greater value than the + * {@link DefaultHlsPlaylistTracker#DEFAULT_PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT default} + * to ensure that (very) low latency livestreams which got stuck for a moment don't crash too + * early. + */ + private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 15; private static final int MANIFEST_MINIMUM_RETRY = 5; private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE; - public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000; private final DataSource.Factory cacheDataSourceFactory; private final DataSource.Factory cachelessDataSourceFactory; @@ -44,8 +54,13 @@ public class PlayerDataSource { public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() { return new HlsMediaSource.Factory(cachelessDataSourceFactory) .setAllowChunklessPreparation(true) - .setLoadErrorHandlingPolicy( - new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); + .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy( + MANIFEST_MINIMUM_RETRY)) + .setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy, + playlistParserFactory) -> + new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy, + playlistParserFactory, PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) + ); } public DashMediaSource.Factory getLiveDashMediaSourceFactory() {