Merge pull request #7661 from TiA4f8R/livestreams-improvements

Increase playlist stuck target duration coefficient and catch BehindLiveWindowExceptions properly
This commit is contained in:
Robin 2022-02-01 11:38:12 +01:00 committed by GitHub
commit e865c4350e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 23 deletions

View File

@ -2517,23 +2517,11 @@ public final class Player implements
Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error);
saveStreamProgressState(); saveStreamProgressState();
boolean isCatchableException = false;
// 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);
switch (error.type) { switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE: case ExoPlaybackException.TYPE_SOURCE:
processSourceError(error.getSourceException()); isCatchableException = processSourceError(error.getSourceException());
break; break;
case ExoPlaybackException.TYPE_UNEXPECTED: case ExoPlaybackException.TYPE_UNEXPECTED:
setRecovery(); setRecovery();
@ -2546,22 +2534,60 @@ public final class Player implements
break; break;
} }
if (isCatchableException) {
return;
}
createErrorNotification(error);
if (fragmentListener != null) { if (fragmentListener != null) {
fragmentListener.onPlayerError(error); fragmentListener.onPlayerError(error);
} }
} }
private void processSourceError(final IOException error) { private void createErrorNotification(@NonNull final ExoPlaybackException error) {
if (exoPlayerIsNull() || playQueue == null) { final ErrorInfo errorInfo;
return; 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.
*
* <p>
* This method sets the recovery position and sends an error message to the play queue if the
* exception is not a {@link BehindLiveWindowException}.
* </p>
* @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(); setRecovery();
if (error instanceof BehindLiveWindowException) { if (error instanceof BehindLiveWindowException) {
reloadPlayQueueManager(); simpleExoPlayer.seekToDefaultPosition();
} else { simpleExoPlayer.prepare();
playQueue.error(); // Inform the user that we are reloading the stream by switching to the buffering state
onBuffering();
return true;
} }
playQueue.error();
return false;
} }
//endregion //endregion

View File

@ -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.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; 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.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource; 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; import com.google.android.exoplayer2.upstream.TransferListener;
public class PlayerDataSource { 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 MANIFEST_MINIMUM_RETRY = 5;
private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE; 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 cacheDataSourceFactory;
private final DataSource.Factory cachelessDataSourceFactory; private final DataSource.Factory cachelessDataSourceFactory;
@ -44,8 +54,13 @@ public class PlayerDataSource {
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() { public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(cachelessDataSourceFactory) return new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true) .setAllowChunklessPreparation(true)
.setLoadErrorHandlingPolicy( .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); MANIFEST_MINIMUM_RETRY))
.setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory) ->
new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory, PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT)
);
} }
public DashMediaSource.Factory getLiveDashMediaSourceFactory() { public DashMediaSource.Factory getLiveDashMediaSourceFactory() {