VEX-4579: Network loss handling (#5)

Add support for customizing back buffer duration and handle network errors gracefully to prevent releasing the player when network is lost.
This commit is contained in:
Armands Malejev 2021-05-17 13:09:09 +03:00 committed by GitHub
parent f6cce0d819
commit 80873102a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 2 deletions

View File

@ -276,10 +276,12 @@ var styles = StyleSheet.create({
* [allowsExternalPlayback](#allowsexternalplayback)
* [audioOnly](#audioonly)
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [backBufferDurationMs](#backBufferDurationMs)
* [bufferConfig](#bufferconfig)
* [controls](#controls)
* [currentPlaybackTime](#currentPlaybackTime)
* [disableFocus](#disableFocus)
* [disableDisconnectError](#disableDisconnectError)
* [filter](#filter)
* [filterEnabled](#filterEnabled)
* [fullscreen](#fullscreen)
@ -367,6 +369,11 @@ A Boolean value that indicates whether the player should automatically delay pla
Platforms: iOS
#### backBufferDurationMs
The number of milliseconds of buffer to keep before the current position. This allows rewinding without rebuffering within that duration.
Platforms: Android ExoPlayer
#### bufferConfig
Adjust the buffer settings. This prop takes an object with one or more of the properties listed below.
@ -416,6 +423,13 @@ Determines whether video audio should override background music/audio in Android
Platforms: Android Exoplayer
#### disableDisconnectError
Determines if the player needs to throw an error when connection is lost or not
* **false (default)** - Player will throw an error when connection is lost
* **true** - Player will keep trying to buffer when network connect is lost
Platforms: Android Exoplayer
### DRM
To setup DRM please follow [this guide](./DRM.md)

View File

@ -9,16 +9,28 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
public class DefaultReactExoplayerConfig implements ReactExoplayerConfig {
private final DefaultBandwidthMeter bandwidthMeter;
private boolean disableDisconnectError = false;
public DefaultReactExoplayerConfig(Context context) {
this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
}
@Override
public LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount) {
if (this.disableDisconnectError) {
// Use custom error handling policy to prevent throwing an error when losing network connection
return new ReactExoplayerLoadErrorHandlingPolicy(minLoadRetryCount);
}
return new DefaultLoadErrorHandlingPolicy(minLoadRetryCount);
}
public void setDisableDisconnectError(boolean disableDisconnectError) {
this.disableDisconnectError = disableDisconnectError;
}
public boolean getDisableDisconnectError() {
return this.disableDisconnectError;
}
@Override
public DefaultBandwidthMeter getBandwidthMeter() {
return bandwidthMeter;

View File

@ -9,5 +9,8 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
public interface ReactExoplayerConfig {
LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount);
void setDisableDisconnectError(boolean disableDisconnectError);
boolean getDisableDisconnectError();
DefaultBandwidthMeter getBandwidthMeter();
}

View File

@ -0,0 +1,33 @@
package com.brentvatne.exoplayer;
import java.io.IOException;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
import com.google.android.exoplayer2.C;
public final class ReactExoplayerLoadErrorHandlingPolicy extends DefaultLoadErrorHandlingPolicy {
private int minLoadRetryCount = Integer.MAX_VALUE;
public ReactExoplayerLoadErrorHandlingPolicy(int minLoadRetryCount) {
super(minLoadRetryCount);
this.minLoadRetryCount = minLoadRetryCount;
}
@Override
public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
if (loadErrorInfo.exception instanceof HttpDataSourceException) {
// Capture the error we get when there is no network connectivity and keep retrying it
return 1000; // Retry every second
} else if(loadErrorInfo.errorCount < this.minLoadRetryCount) {
return Math.min((loadErrorInfo.errorCount - 1) * 1000, 5000); // Default timeout handling
} else {
return C.TIME_UNSET; // Done retrying and will return the error immediately
}
}
@Override
public int getMinimumLoadableRetryCount(int dataType) {
return Integer.MAX_VALUE;
}
}

View File

@ -139,6 +139,7 @@ class ReactExoplayerView extends FrameLayout implements
private Handler mainHandler;
// Props from React
private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS;
private Uri srcUri;
private String extension;
private boolean repeat;
@ -151,6 +152,7 @@ class ReactExoplayerView extends FrameLayout implements
private ReadableArray textTracks;
private boolean disableFocus;
private boolean disableBuffering;
private boolean disableDisconnectError;
private boolean preventsDisplaySleepDuringVideoPlayback = true;
private float mProgressUpdateInterval = 250.0f;
private boolean playInBackground = false;
@ -434,7 +436,7 @@ class ReactExoplayerView extends FrameLayout implements
bufferForPlaybackAfterRebufferMs,
-1,
true,
DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS,
backBufferDurationMs,
DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME
);
DefaultRenderersFactory renderersFactory =
@ -527,6 +529,7 @@ class ReactExoplayerView extends FrameLayout implements
private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) {
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
: uri.getLastPathSegment());
config.setDisableDisconnectError(this.disableDisconnectError);
switch (type) {
case C.TYPE_SS:
return new SsMediaSource.Factory(
@ -1312,10 +1315,18 @@ class ReactExoplayerView extends FrameLayout implements
this.disableFocus = disableFocus;
}
public void setBackBufferDurationMs(int backBufferDurationMs) {
this.backBufferDurationMs = backBufferDurationMs;
}
public void setDisableBuffering(boolean disableBuffering) {
this.disableBuffering = disableBuffering;
}
public void setDisableDisconnectError(boolean disableDisconnectError) {
this.disableDisconnectError = disableDisconnectError;
}
public void setFullscreen(boolean fullscreen) {
if (fullscreen == isFullscreen) {
return; // Avoid generating events when nothing is changing

View File

@ -49,6 +49,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_PAUSED = "paused";
private static final String PROP_MUTED = "muted";
private static final String PROP_VOLUME = "volume";
private static final String PROP_BACK_BUFFER_DURATION_MS = "backBufferDurationMs";
private static final String PROP_BUFFER_CONFIG = "bufferConfig";
private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs";
private static final String PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs";
@ -64,6 +65,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
private static final String PROP_DISABLE_FOCUS = "disableFocus";
private static final String PROP_DISABLE_BUFFERING = "disableBuffering";
private static final String PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError";
private static final String PROP_FULLSCREEN = "fullscreen";
private static final String PROP_USE_TEXTURE_VIEW = "useTextureView";
private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack";
@ -294,11 +296,21 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setDisableFocus(disableFocus);
}
@ReactProp(name = PROP_BACK_BUFFER_DURATION_MS, defaultInt = 0)
public void setBackBufferDurationMs(final ReactExoplayerView videoView, final int backBufferDurationMs) {
videoView.setBackBufferDurationMs(backBufferDurationMs);
}
@ReactProp(name = PROP_DISABLE_BUFFERING, defaultBoolean = false)
public void setDisableBuffering(final ReactExoplayerView videoView, final boolean disableBuffering) {
videoView.setDisableBuffering(disableBuffering);
}
@ReactProp(name = PROP_DISABLE_DISCONNECT_ERROR, defaultBoolean = false)
public void setDisableDisconnectError(final ReactExoplayerView videoView, final boolean disableDisconnectError) {
videoView.setDisableDisconnectError(disableDisconnectError);
}
@ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false)
public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) {
videoView.setFullscreen(fullscreen);