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) * [allowsExternalPlayback](#allowsexternalplayback)
* [audioOnly](#audioonly) * [audioOnly](#audioonly)
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) * [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [backBufferDurationMs](#backBufferDurationMs)
* [bufferConfig](#bufferconfig) * [bufferConfig](#bufferconfig)
* [controls](#controls) * [controls](#controls)
* [currentPlaybackTime](#currentPlaybackTime) * [currentPlaybackTime](#currentPlaybackTime)
* [disableFocus](#disableFocus) * [disableFocus](#disableFocus)
* [disableDisconnectError](#disableDisconnectError)
* [filter](#filter) * [filter](#filter)
* [filterEnabled](#filterEnabled) * [filterEnabled](#filterEnabled)
* [fullscreen](#fullscreen) * [fullscreen](#fullscreen)
@ -367,6 +369,11 @@ A Boolean value that indicates whether the player should automatically delay pla
Platforms: iOS 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 #### bufferConfig
Adjust the buffer settings. This prop takes an object with one or more of the properties listed below. 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 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 ### DRM
To setup DRM please follow [this guide](./DRM.md) 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 { public class DefaultReactExoplayerConfig implements ReactExoplayerConfig {
private final DefaultBandwidthMeter bandwidthMeter; private final DefaultBandwidthMeter bandwidthMeter;
private boolean disableDisconnectError = false;
public DefaultReactExoplayerConfig(Context context) { public DefaultReactExoplayerConfig(Context context) {
this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
} }
@Override
public LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount) { 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); return new DefaultLoadErrorHandlingPolicy(minLoadRetryCount);
} }
public void setDisableDisconnectError(boolean disableDisconnectError) {
this.disableDisconnectError = disableDisconnectError;
}
public boolean getDisableDisconnectError() {
return this.disableDisconnectError;
}
@Override @Override
public DefaultBandwidthMeter getBandwidthMeter() { public DefaultBandwidthMeter getBandwidthMeter() {
return bandwidthMeter; return bandwidthMeter;

View File

@ -9,5 +9,8 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
public interface ReactExoplayerConfig { public interface ReactExoplayerConfig {
LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount); LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount);
void setDisableDisconnectError(boolean disableDisconnectError);
boolean getDisableDisconnectError();
DefaultBandwidthMeter getBandwidthMeter(); 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; private Handler mainHandler;
// Props from React // Props from React
private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS;
private Uri srcUri; private Uri srcUri;
private String extension; private String extension;
private boolean repeat; private boolean repeat;
@ -151,6 +152,7 @@ class ReactExoplayerView extends FrameLayout implements
private ReadableArray textTracks; private ReadableArray textTracks;
private boolean disableFocus; private boolean disableFocus;
private boolean disableBuffering; private boolean disableBuffering;
private boolean disableDisconnectError;
private boolean preventsDisplaySleepDuringVideoPlayback = true; private boolean preventsDisplaySleepDuringVideoPlayback = true;
private float mProgressUpdateInterval = 250.0f; private float mProgressUpdateInterval = 250.0f;
private boolean playInBackground = false; private boolean playInBackground = false;
@ -434,7 +436,7 @@ class ReactExoplayerView extends FrameLayout implements
bufferForPlaybackAfterRebufferMs, bufferForPlaybackAfterRebufferMs,
-1, -1,
true, true,
DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS, backBufferDurationMs,
DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME
); );
DefaultRenderersFactory renderersFactory = DefaultRenderersFactory renderersFactory =
@ -527,6 +529,7 @@ class ReactExoplayerView extends FrameLayout implements
private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) { private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) {
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
: uri.getLastPathSegment()); : uri.getLastPathSegment());
config.setDisableDisconnectError(this.disableDisconnectError);
switch (type) { switch (type) {
case C.TYPE_SS: case C.TYPE_SS:
return new SsMediaSource.Factory( return new SsMediaSource.Factory(
@ -1312,10 +1315,18 @@ class ReactExoplayerView extends FrameLayout implements
this.disableFocus = disableFocus; this.disableFocus = disableFocus;
} }
public void setBackBufferDurationMs(int backBufferDurationMs) {
this.backBufferDurationMs = backBufferDurationMs;
}
public void setDisableBuffering(boolean disableBuffering) { public void setDisableBuffering(boolean disableBuffering) {
this.disableBuffering = disableBuffering; this.disableBuffering = disableBuffering;
} }
public void setDisableDisconnectError(boolean disableDisconnectError) {
this.disableDisconnectError = disableDisconnectError;
}
public void setFullscreen(boolean fullscreen) { public void setFullscreen(boolean fullscreen) {
if (fullscreen == isFullscreen) { if (fullscreen == isFullscreen) {
return; // Avoid generating events when nothing is changing 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_PAUSED = "paused";
private static final String PROP_MUTED = "muted"; private static final String PROP_MUTED = "muted";
private static final String PROP_VOLUME = "volume"; 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 = "bufferConfig";
private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs"; private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs";
private static final String PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs"; 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_PLAY_IN_BACKGROUND = "playInBackground";
private static final String PROP_DISABLE_FOCUS = "disableFocus"; private static final String PROP_DISABLE_FOCUS = "disableFocus";
private static final String PROP_DISABLE_BUFFERING = "disableBuffering"; 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_FULLSCREEN = "fullscreen";
private static final String PROP_USE_TEXTURE_VIEW = "useTextureView"; private static final String PROP_USE_TEXTURE_VIEW = "useTextureView";
private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack"; private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack";
@ -294,11 +296,21 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setDisableFocus(disableFocus); 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) @ReactProp(name = PROP_DISABLE_BUFFERING, defaultBoolean = false)
public void setDisableBuffering(final ReactExoplayerView videoView, final boolean disableBuffering) { public void setDisableBuffering(final ReactExoplayerView videoView, final boolean disableBuffering) {
videoView.setDisableBuffering(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) @ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false)
public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) { public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) {
videoView.setFullscreen(fullscreen); videoView.setFullscreen(fullscreen);