Merge pull request #2689 from crunchyroll/cr-android-improvements
Crunchyroll Android Improvements
This commit is contained in:
commit
bf11bb95ed
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,7 +1,23 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
### Version 6.0.0-alpha1
|
### Version 6.0.0-alpha1
|
||||||
|
- Support disabling buffering [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Fix AudioFocus bug that could cause the player to stop responding to play/pause in some instances. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Fix player crashing when it is being cleared. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Add support for customising back buffer duration and handle network errors gracefully to prevent releasing the player when network is lost. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Allow player to be init before source is provided, and later update once a source is provided. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Adds handling for providing a empty source in order to stop playback and clear out any existing content [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Add support for detecting if format is supported and exclude unsupported resolutions from auto quality selection and video track info in RN. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Improve error handling [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Add support for L1 to L3 Widevine fallback if playback fails initially. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Reduce buffer size based on available heap [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Force garbage collection when there is no available memory [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Improve memory usage [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Support disabling screen recording [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Improved error capturing [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Fix DRM init crashes [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Improve progress reporting [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
|
- Fix progress loss when network connection is regained [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||||
- Add Google's maven repository to avoid build error [#2552](https://github.com/react-native-video/react-native-video/pull/2552)
|
- Add Google's maven repository to avoid build error [#2552](https://github.com/react-native-video/react-native-video/pull/2552)
|
||||||
- Fix iOS 15.4 HLS playback race condition [#2633](https://github.com/react-native-video/react-native-video/pull/2633)
|
- Fix iOS 15.4 HLS playback race condition [#2633](https://github.com/react-native-video/react-native-video/pull/2633)
|
||||||
- Fix app crash from NPE in Exoplayer error handler [#2575](https://github.com/react-native-video/react-native-video/pull/2575)
|
- Fix app crash from NPE in Exoplayer error handler [#2575](https://github.com/react-native-video/react-native-video/pull/2575)
|
||||||
|
12
Video.js
12
Video.js
@ -100,6 +100,12 @@ export default class Video extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onPlaybackStateChanged = (event) => {
|
||||||
|
if (this.props.onPlaybackStateChanged) {
|
||||||
|
this.props.onPlaybackStateChanged(event.nativeEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_onLoad = (event) => {
|
_onLoad = (event) => {
|
||||||
// Need to hide poster here for windows as onReadyForDisplay is not implemented
|
// Need to hide poster here for windows as onReadyForDisplay is not implemented
|
||||||
if (Platform.OS === 'windows') {
|
if (Platform.OS === 'windows') {
|
||||||
@ -308,6 +314,7 @@ export default class Video extends Component {
|
|||||||
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
|
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
|
||||||
},
|
},
|
||||||
onVideoLoadStart: this._onLoadStart,
|
onVideoLoadStart: this._onLoadStart,
|
||||||
|
onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
|
||||||
onVideoLoad: this._onLoad,
|
onVideoLoad: this._onLoad,
|
||||||
onVideoError: this._onError,
|
onVideoError: this._onError,
|
||||||
onVideoProgress: this._onProgress,
|
onVideoProgress: this._onProgress,
|
||||||
@ -464,6 +471,7 @@ Video.propTypes = {
|
|||||||
maxBufferMs: PropTypes.number,
|
maxBufferMs: PropTypes.number,
|
||||||
bufferForPlaybackMs: PropTypes.number,
|
bufferForPlaybackMs: PropTypes.number,
|
||||||
bufferForPlaybackAfterRebufferMs: PropTypes.number,
|
bufferForPlaybackAfterRebufferMs: PropTypes.number,
|
||||||
|
maxHeapAllocationPercent: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
stereoPan: PropTypes.number,
|
stereoPan: PropTypes.number,
|
||||||
rate: PropTypes.number,
|
rate: PropTypes.number,
|
||||||
@ -473,7 +481,9 @@ Video.propTypes = {
|
|||||||
playWhenInactive: PropTypes.bool,
|
playWhenInactive: PropTypes.bool,
|
||||||
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
||||||
reportBandwidth: PropTypes.bool,
|
reportBandwidth: PropTypes.bool,
|
||||||
|
contentStartTime: PropTypes.number,
|
||||||
disableFocus: PropTypes.bool,
|
disableFocus: PropTypes.bool,
|
||||||
|
disableBuffering: PropTypes.bool,
|
||||||
controls: PropTypes.bool,
|
controls: PropTypes.bool,
|
||||||
audioOnly: PropTypes.bool,
|
audioOnly: PropTypes.bool,
|
||||||
currentTime: PropTypes.number,
|
currentTime: PropTypes.number,
|
||||||
@ -481,8 +491,10 @@ Video.propTypes = {
|
|||||||
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
||||||
progressUpdateInterval: PropTypes.number,
|
progressUpdateInterval: PropTypes.number,
|
||||||
useTextureView: PropTypes.bool,
|
useTextureView: PropTypes.bool,
|
||||||
|
useSecureView: PropTypes.bool,
|
||||||
hideShutterView: PropTypes.bool,
|
hideShutterView: PropTypes.bool,
|
||||||
onLoadStart: PropTypes.func,
|
onLoadStart: PropTypes.func,
|
||||||
|
onPlaybackStateChanged: PropTypes.func,
|
||||||
onLoad: PropTypes.func,
|
onLoad: PropTypes.func,
|
||||||
onBuffer: PropTypes.func,
|
onBuffer: PropTypes.func,
|
||||||
onError: PropTypes.func,
|
onError: PropTypes.func,
|
||||||
|
@ -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;
|
||||||
|
@ -41,6 +41,7 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
private ViewGroup.LayoutParams layoutParams;
|
private ViewGroup.LayoutParams layoutParams;
|
||||||
|
|
||||||
private boolean useTextureView = true;
|
private boolean useTextureView = true;
|
||||||
|
private boolean useSecureView = false;
|
||||||
private boolean hideShutterView = false;
|
private boolean hideShutterView = false;
|
||||||
|
|
||||||
public ExoPlayerView(Context context) {
|
public ExoPlayerView(Context context) {
|
||||||
@ -103,7 +104,15 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateSurfaceView() {
|
private void updateSurfaceView() {
|
||||||
View view = useTextureView ? new TextureView(context) : new SurfaceView(context);
|
View view;
|
||||||
|
if (!useTextureView || useSecureView) {
|
||||||
|
view = new SurfaceView(context);
|
||||||
|
if (useSecureView) {
|
||||||
|
((SurfaceView)view).setSecure(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view = new TextureView(context);
|
||||||
|
}
|
||||||
view.setLayoutParams(layoutParams);
|
view.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
surfaceView = view;
|
surfaceView = view;
|
||||||
@ -178,6 +187,13 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void useSecureView(boolean useSecureView) {
|
||||||
|
if (useSecureView != this.useSecureView) {
|
||||||
|
this.useSecureView = useSecureView;
|
||||||
|
updateSurfaceView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setHideShutterView(boolean hideShutterView) {
|
public void setHideShutterView(boolean hideShutterView) {
|
||||||
this.hideShutterView = hideShutterView;
|
this.hideShutterView = hideShutterView;
|
||||||
updateShutterViewVisibility();
|
updateShutterViewVisibility();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
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 &&
|
||||||
|
(loadErrorInfo.exception.getMessage() == "Unable to connect" || loadErrorInfo.exception.getMessage() == "Software caused connection abort")
|
||||||
|
) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,12 @@ package com.brentvatne.exoplayer;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -26,10 +28,13 @@ import com.facebook.react.bridge.ReadableMap;
|
|||||||
import com.facebook.react.bridge.WritableArray;
|
import com.facebook.react.bridge.WritableArray;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.uimanager.ThemedReactContext;
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
import com.facebook.react.util.RNLog;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.drm.MediaDrmCallbackException;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
@ -42,7 +47,9 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
|
|||||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
|
import com.google.android.exoplayer2.drm.MediaDrmCallbackException;
|
||||||
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||||
|
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
@ -70,7 +77,15 @@ import com.google.android.exoplayer2.upstream.DataSource;
|
|||||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.source.dash.DashUtil;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Descriptor;
|
||||||
|
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
@ -79,6 +94,16 @@ import java.util.ArrayList;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.List;
|
||||||
|
import java.lang.Thread;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.lang.Integer;
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class ReactExoplayerView extends FrameLayout implements
|
class ReactExoplayerView extends FrameLayout implements
|
||||||
@ -90,6 +115,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
MetadataOutput,
|
MetadataOutput,
|
||||||
DrmSessionEventListener {
|
DrmSessionEventListener {
|
||||||
|
|
||||||
|
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1;
|
||||||
|
public static final double DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE = 0;
|
||||||
|
public static final double DEFAULT_MIN_BUFFER_MEMORY_RESERVE = 0;
|
||||||
|
|
||||||
private static final String TAG = "ReactExoplayerView";
|
private static final String TAG = "ReactExoplayerView";
|
||||||
|
|
||||||
private static final CookieManager DEFAULT_COOKIE_MANAGER;
|
private static final CookieManager DEFAULT_COOKIE_MANAGER;
|
||||||
@ -128,15 +157,21 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private int minLoadRetryCount = 3;
|
private int minLoadRetryCount = 3;
|
||||||
private int maxBitRate = 0;
|
private int maxBitRate = 0;
|
||||||
private long seekTime = C.TIME_UNSET;
|
private long seekTime = C.TIME_UNSET;
|
||||||
|
private boolean hasDrmFailed = false;
|
||||||
|
private boolean isUsingContentResolution = false;
|
||||||
|
private boolean selectTrackWhenReady = false;
|
||||||
|
|
||||||
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||||
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||||
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
||||||
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
||||||
|
private double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT;
|
||||||
|
private double minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE;
|
||||||
|
private double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE;
|
||||||
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;
|
||||||
@ -148,6 +183,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private Dynamic textTrackValue;
|
private Dynamic textTrackValue;
|
||||||
private ReadableArray textTracks;
|
private ReadableArray textTracks;
|
||||||
private boolean disableFocus;
|
private boolean disableFocus;
|
||||||
|
private boolean disableBuffering;
|
||||||
|
private long contentStartTime = -1L;
|
||||||
|
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;
|
||||||
@ -388,116 +426,262 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
|
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RNVLoadControl extends DefaultLoadControl {
|
||||||
|
private int availableHeapInBytes = 0;
|
||||||
|
private Runtime runtime;
|
||||||
|
public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, int backBufferDurationMs, boolean retainBackBufferFromKeyframe) {
|
||||||
|
super(allocator,
|
||||||
|
minBufferMs,
|
||||||
|
maxBufferMs,
|
||||||
|
bufferForPlaybackMs,
|
||||||
|
bufferForPlaybackAfterRebufferMs,
|
||||||
|
targetBufferBytes,
|
||||||
|
prioritizeTimeOverSizeThresholds,
|
||||||
|
backBufferDurationMs,
|
||||||
|
retainBackBufferFromKeyframe);
|
||||||
|
runtime = Runtime.getRuntime();
|
||||||
|
ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(themedReactContext.ACTIVITY_SERVICE);
|
||||||
|
availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
|
||||||
|
if (ReactExoplayerView.this.disableBuffering) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int loadedBytes = getAllocator().getTotalBytesAllocated();
|
||||||
|
boolean isHeapReached = availableHeapInBytes > 0 && loadedBytes >= availableHeapInBytes;
|
||||||
|
if (isHeapReached) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
|
long freeMemory = runtime.maxMemory() - usedMemory;
|
||||||
|
long reserveMemory = (long)minBufferMemoryReservePercent * runtime.maxMemory();
|
||||||
|
long bufferedMs = bufferedDurationUs / (long)1000;
|
||||||
|
if (reserveMemory > freeMemory && bufferedMs > 2000) {
|
||||||
|
// We don't have enough memory in reserve so we stop buffering to allow other components to use it instead
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (runtime.freeMemory() == 0) {
|
||||||
|
Log.w("ExoPlayer Warning", "Free memory reached 0, forcing garbage collection");
|
||||||
|
runtime.gc();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.shouldContinueLoading(playbackPositionUs, bufferedDurationUs, playbackSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startBufferCheckTimer() {
|
||||||
|
SimpleExoPlayer player = this.player;
|
||||||
|
VideoEventEmitter eventEmitter = this.eventEmitter;
|
||||||
|
Handler mainHandler = this.mainHandler;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void initializePlayer() {
|
private void initializePlayer() {
|
||||||
ReactExoplayerView self = this;
|
ReactExoplayerView self = this;
|
||||||
|
Activity activity = themedReactContext.getCurrentActivity();
|
||||||
// This ensures all props have been settled, to avoid async racing conditions.
|
// This ensures all props have been settled, to avoid async racing conditions.
|
||||||
new Handler().postDelayed(new Runnable() {
|
new Handler().postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (player == null) {
|
try {
|
||||||
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
if (player == null) {
|
||||||
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
// Initialize core configuration and listeners
|
||||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
initializePlayerCore(self);
|
||||||
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
}
|
||||||
|
if (playerNeedsSource && srcUri != null) {
|
||||||
|
exoPlayerView.invalidateAspectRatio();
|
||||||
|
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
|
||||||
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
|
es.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// DRM initialization must run on a different thread
|
||||||
|
DrmSessionManager drmSessionManager = initializePlayerDrm(self);
|
||||||
|
if (drmSessionManager == null && self.drmUUID != null) {
|
||||||
|
// Failed to intialize DRM session manager - cannot continue
|
||||||
|
Log.e("ExoPlayer Exception", "Failed to initialize DRM Session Manager Framework!");
|
||||||
|
eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
// Initialize handler to run on the main thread
|
||||||
DefaultLoadControl.Builder defaultLoadControlBuilder = new DefaultLoadControl.Builder();
|
activity.runOnUiThread(new Runnable() {
|
||||||
defaultLoadControlBuilder.setAllocator(allocator);
|
public void run() {
|
||||||
defaultLoadControlBuilder.setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
|
try {
|
||||||
defaultLoadControlBuilder.setTargetBufferBytes(-1);
|
// Source initialization must run on the main thread
|
||||||
defaultLoadControlBuilder.setPrioritizeTimeOverSizeThresholds(true);
|
initializePlayerSource(self, drmSessionManager);
|
||||||
DefaultLoadControl defaultLoadControl = defaultLoadControlBuilder.createDefaultLoadControl();
|
} catch (Exception ex) {
|
||||||
DefaultRenderersFactory renderersFactory =
|
self.playerNeedsSource = true;
|
||||||
new DefaultRenderersFactory(getContext())
|
Log.e("ExoPlayer Exception", "Failed to initialize Player!");
|
||||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
Log.e("ExoPlayer Exception", ex.toString());
|
||||||
player = new SimpleExoPlayer.Builder(getContext(), renderersFactory)
|
self.eventEmitter.error(ex.toString(), ex, "1001");
|
||||||
.setTrackSelector(trackSelector)
|
}
|
||||||
.setBandwidthMeter(bandwidthMeter)
|
}
|
||||||
.setLoadControl(defaultLoadControl)
|
});
|
||||||
.build();
|
}
|
||||||
player.addListener(self);
|
});
|
||||||
player.addMetadataOutput(self);
|
} else if (srcUri != null) {
|
||||||
exoPlayerView.setPlayer(player);
|
initializePlayerSource(self, null);
|
||||||
audioBecomingNoisyReceiver.setListener(self);
|
}
|
||||||
bandwidthMeter.addEventListener(new Handler(), self);
|
|
||||||
setPlayWhenReady(!isPaused);
|
|
||||||
playerNeedsSource = true;
|
|
||||||
|
|
||||||
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
|
||||||
player.setPlaybackParameters(params);
|
} catch (Exception ex) {
|
||||||
|
self.playerNeedsSource = true;
|
||||||
|
Log.e("ExoPlayer Exception", "Failed to initialize Player!");
|
||||||
|
Log.e("ExoPlayer Exception", ex.toString());
|
||||||
|
eventEmitter.error(ex.toString(), ex, "1001");
|
||||||
}
|
}
|
||||||
if (playerNeedsSource && srcUri != null) {
|
|
||||||
exoPlayerView.invalidateAspectRatio();
|
|
||||||
|
|
||||||
// DRM
|
|
||||||
DrmSessionManager drmSessionManager = null;
|
|
||||||
if (self.drmUUID != null) {
|
|
||||||
try {
|
|
||||||
drmSessionManager = buildDrmSessionManager(self.drmUUID, self.drmLicenseUrl,
|
|
||||||
self.drmLicenseHeader);
|
|
||||||
} catch (UnsupportedDrmException e) {
|
|
||||||
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
|
||||||
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
|
||||||
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
|
||||||
eventEmitter.error(getResources().getString(errorStringId), e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End DRM
|
|
||||||
|
|
||||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
|
||||||
MediaSource videoSource = buildMediaSource(srcUri, extension, drmSessionManager);
|
|
||||||
MediaSource mediaSource;
|
|
||||||
if (mediaSourceList.size() == 0) {
|
|
||||||
mediaSource = videoSource;
|
|
||||||
} else {
|
|
||||||
mediaSourceList.add(0, videoSource);
|
|
||||||
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
|
||||||
new MediaSource[mediaSourceList.size()]
|
|
||||||
);
|
|
||||||
mediaSource = new MergingMediaSource(textSourceArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
|
||||||
if (haveResumePosition) {
|
|
||||||
player.seekTo(resumeWindow, resumePosition);
|
|
||||||
}
|
|
||||||
player.prepare(mediaSource, !haveResumePosition, false);
|
|
||||||
playerNeedsSource = false;
|
|
||||||
|
|
||||||
reLayout(exoPlayerView);
|
|
||||||
eventEmitter.loadStart();
|
|
||||||
loadVideoStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializing the playerControlView
|
|
||||||
initializePlayerControl();
|
|
||||||
setControls(controls);
|
|
||||||
applyModifiers();
|
|
||||||
}
|
}
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid,
|
private void initializePlayerCore(ReactExoplayerView self) {
|
||||||
String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
||||||
|
self.trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||||
|
self.trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
|
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
||||||
|
|
||||||
|
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
||||||
|
RNVLoadControl loadControl = new RNVLoadControl(
|
||||||
|
allocator,
|
||||||
|
minBufferMs,
|
||||||
|
maxBufferMs,
|
||||||
|
bufferForPlaybackMs,
|
||||||
|
bufferForPlaybackAfterRebufferMs,
|
||||||
|
-1,
|
||||||
|
true,
|
||||||
|
backBufferDurationMs,
|
||||||
|
DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME
|
||||||
|
);
|
||||||
|
DefaultRenderersFactory renderersFactory =
|
||||||
|
new DefaultRenderersFactory(getContext())
|
||||||
|
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
||||||
|
player = new SimpleExoPlayer.Builder(getContext(), renderersFactory)
|
||||||
|
.setTrackSelector(self.trackSelector)
|
||||||
|
.setBandwidthMeter(bandwidthMeter)
|
||||||
|
.setLoadControl(loadControl)
|
||||||
|
.build();
|
||||||
|
player.addListener(self);
|
||||||
|
player.addMetadataOutput(self);
|
||||||
|
exoPlayerView.setPlayer(player);
|
||||||
|
audioBecomingNoisyReceiver.setListener(self);
|
||||||
|
bandwidthMeter.addEventListener(new Handler(), self);
|
||||||
|
setPlayWhenReady(!isPaused);
|
||||||
|
playerNeedsSource = true;
|
||||||
|
|
||||||
|
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
||||||
|
player.setPlaybackParameters(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrmSessionManager initializePlayerDrm(ReactExoplayerView self) {
|
||||||
|
DrmSessionManager drmSessionManager = null;
|
||||||
|
if (self.drmUUID != null) {
|
||||||
|
try {
|
||||||
|
drmSessionManager = self.buildDrmSessionManager(self.drmUUID, self.drmLicenseUrl,
|
||||||
|
self.drmLicenseHeader);
|
||||||
|
} catch (UnsupportedDrmException e) {
|
||||||
|
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
||||||
|
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
||||||
|
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
||||||
|
eventEmitter.error(getResources().getString(errorStringId), e, "3003");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drmSessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) {
|
||||||
|
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
||||||
|
MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager);
|
||||||
|
MediaSource mediaSource;
|
||||||
|
if (mediaSourceList.size() == 0) {
|
||||||
|
mediaSource = videoSource;
|
||||||
|
} else {
|
||||||
|
mediaSourceList.add(0, videoSource);
|
||||||
|
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
||||||
|
new MediaSource[mediaSourceList.size()]
|
||||||
|
);
|
||||||
|
mediaSource = new MergingMediaSource(textSourceArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for player to be set
|
||||||
|
while (player == null) {
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
Log.e("ExoPlayer Exception", ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
||||||
|
if (haveResumePosition) {
|
||||||
|
player.seekTo(resumeWindow, resumePosition);
|
||||||
|
}
|
||||||
|
player.prepare(mediaSource, !haveResumePosition, false);
|
||||||
|
playerNeedsSource = false;
|
||||||
|
|
||||||
|
reLayout(exoPlayerView);
|
||||||
|
eventEmitter.loadStart();
|
||||||
|
loadVideoStarted = true;
|
||||||
|
|
||||||
|
finishPlayerInitialization();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishPlayerInitialization() {
|
||||||
|
// Initializing the playerControlView
|
||||||
|
initializePlayerControl();
|
||||||
|
setControls(controls);
|
||||||
|
applyModifiers();
|
||||||
|
startBufferCheckTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
||||||
|
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, int retryCount) throws UnsupportedDrmException {
|
||||||
if (Util.SDK_INT < 18) {
|
if (Util.SDK_INT < 18) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
try {
|
||||||
buildHttpDataSourceFactory(false));
|
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||||
if (keyRequestPropertiesArray != null) {
|
buildHttpDataSourceFactory(false));
|
||||||
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
if (keyRequestPropertiesArray != null) {
|
||||||
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
|
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||||
keyRequestPropertiesArray[i + 1]);
|
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
FrameworkMediaDrm mediaDrm = FrameworkMediaDrm.newInstance(uuid);
|
||||||
|
if (hasDrmFailed) {
|
||||||
|
// When DRM fails using L1 we want to switch to L3
|
||||||
|
mediaDrm.setPropertyString("securityLevel", "L3");
|
||||||
|
}
|
||||||
|
return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, false, 3);
|
||||||
|
} catch(UnsupportedDrmException ex) {
|
||||||
|
// Unsupported DRM exceptions are handled by the calling method
|
||||||
|
throw ex;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (retryCount < 3) {
|
||||||
|
// Attempt retry 3 times in case where the OS Media DRM Framework fails for whatever reason
|
||||||
|
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, ++retryCount);
|
||||||
|
}
|
||||||
|
// Handle the unknow exception and emit to JS
|
||||||
|
eventEmitter.error(ex.toString(), ex, "3006");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return new DefaultDrmSessionManager(uuid,
|
|
||||||
FrameworkMediaDrm.newInstance(uuid), drmCallback, null, false, 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) {
|
private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) {
|
||||||
|
if (uri == null) {
|
||||||
|
throw new IllegalStateException("Invalid video uri");
|
||||||
|
}
|
||||||
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(
|
||||||
@ -617,7 +801,6 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
}
|
}
|
||||||
@ -757,6 +940,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
onBuffering(false);
|
onBuffering(false);
|
||||||
startProgressHandler();
|
startProgressHandler();
|
||||||
videoLoaded();
|
videoLoaded();
|
||||||
|
if (selectTrackWhenReady && isUsingContentResolution) {
|
||||||
|
selectTrackWhenReady = false;
|
||||||
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
|
}
|
||||||
// Setting the visibility for the playerControlView
|
// Setting the visibility for the playerControlView
|
||||||
if (playerControlView != null) {
|
if (playerControlView != null) {
|
||||||
playerControlView.show();
|
playerControlView.show();
|
||||||
@ -799,14 +986,34 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
int width = videoFormat != null ? videoFormat.width : 0;
|
int width = videoFormat != null ? videoFormat.width : 0;
|
||||||
int height = videoFormat != null ? videoFormat.height : 0;
|
int height = videoFormat != null ? videoFormat.height : 0;
|
||||||
String trackId = videoFormat != null ? videoFormat.id : "-1";
|
String trackId = videoFormat != null ? videoFormat.id : "-1";
|
||||||
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
|
|
||||||
getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo(), trackId);
|
// Properties that must be accessed on the main thread
|
||||||
|
long duration = player.getDuration();
|
||||||
|
long currentPosition = player.getCurrentPosition();
|
||||||
|
WritableArray audioTrackInfo = getAudioTrackInfo();
|
||||||
|
WritableArray textTrackInfo = getTextTrackInfo();
|
||||||
|
int trackRendererIndex = getTrackRendererIndex(C.TRACK_TYPE_VIDEO);
|
||||||
|
|
||||||
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
|
es.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done
|
||||||
|
eventEmitter.load(duration, currentPosition, width, height,
|
||||||
|
audioTrackInfo, textTrackInfo, getVideoTrackInfo(trackRendererIndex), trackId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private WritableArray getAudioTrackInfo() {
|
private WritableArray getAudioTrackInfo() {
|
||||||
WritableArray audioTracks = Arguments.createArray();
|
WritableArray audioTracks = Arguments.createArray();
|
||||||
|
|
||||||
|
if (trackSelector == null) {
|
||||||
|
// Likely player is unmounting so no audio tracks are available anymore
|
||||||
|
return audioTracks;
|
||||||
|
}
|
||||||
|
|
||||||
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
||||||
int index = getTrackRendererIndex(C.TRACK_TYPE_AUDIO);
|
int index = getTrackRendererIndex(C.TRACK_TYPE_AUDIO);
|
||||||
if (info == null || index == C.INDEX_UNSET) {
|
if (info == null || index == C.INDEX_UNSET) {
|
||||||
@ -827,34 +1034,114 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
return audioTracks;
|
return audioTracks;
|
||||||
}
|
}
|
||||||
private WritableArray getVideoTrackInfo() {
|
private WritableArray getVideoTrackInfo(int trackRendererIndex) {
|
||||||
|
|
||||||
|
if (this.contentStartTime != -1L) {
|
||||||
|
WritableArray contentVideoTracks = this.getVideoTrackInfoFromManifest();
|
||||||
|
if (contentVideoTracks != null) {
|
||||||
|
isUsingContentResolution = true;
|
||||||
|
return contentVideoTracks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WritableArray videoTracks = Arguments.createArray();
|
WritableArray videoTracks = Arguments.createArray();
|
||||||
|
|
||||||
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
||||||
int index = getTrackRendererIndex(C.TRACK_TYPE_VIDEO);
|
|
||||||
if (info == null || index == C.INDEX_UNSET) {
|
if (info == null || trackRendererIndex == C.INDEX_UNSET) {
|
||||||
return videoTracks;
|
return videoTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackGroupArray groups = info.getTrackGroups(index);
|
TrackGroupArray groups = info.getTrackGroups(trackRendererIndex);
|
||||||
for (int i = 0; i < groups.length; ++i) {
|
for (int i = 0; i < groups.length; ++i) {
|
||||||
TrackGroup group = groups.get(i);
|
TrackGroup group = groups.get(i);
|
||||||
|
|
||||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||||
Format format = group.getFormat(trackIndex);
|
Format format = group.getFormat(trackIndex);
|
||||||
WritableMap videoTrack = Arguments.createMap();
|
if (isFormatSupported(format)) {
|
||||||
videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width);
|
WritableMap videoTrack = Arguments.createMap();
|
||||||
videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height);
|
videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width);
|
||||||
videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
|
videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height);
|
||||||
videoTrack.putString("codecs", format.codecs != null ? format.codecs : "");
|
videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
|
||||||
videoTrack.putString("trackId",
|
videoTrack.putString("codecs", format.codecs != null ? format.codecs : "");
|
||||||
format.id == null ? String.valueOf(trackIndex) : format.id);
|
videoTrack.putString("trackId", format.id == null ? String.valueOf(trackIndex) : format.id);
|
||||||
videoTracks.pushMap(videoTrack);
|
videoTracks.pushMap(videoTrack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoTracks;
|
return videoTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WritableArray getVideoTrackInfoFromManifest() {
|
||||||
|
return this.getVideoTrackInfoFromManifest(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need retry count to in case where minefest request fails from poor network conditions
|
||||||
|
private WritableArray getVideoTrackInfoFromManifest(int retryCount) {
|
||||||
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
|
final DataSource dataSource = this.mediaDataSourceFactory.createDataSource();
|
||||||
|
final Uri sourceUri = this.srcUri;
|
||||||
|
final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset
|
||||||
|
|
||||||
|
Future<WritableArray> result = es.submit(new Callable<WritableArray>() {
|
||||||
|
DataSource ds = dataSource;
|
||||||
|
Uri uri = sourceUri;
|
||||||
|
long startTimeUs = startTime * 1000; // ms -> us
|
||||||
|
|
||||||
|
public WritableArray call() throws Exception {
|
||||||
|
WritableArray videoTracks = Arguments.createArray();
|
||||||
|
try {
|
||||||
|
DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri);
|
||||||
|
int periodCount = manifest.getPeriodCount();
|
||||||
|
for (int i = 0; i < periodCount; i++) {
|
||||||
|
Period period = manifest.getPeriod(i);
|
||||||
|
for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets.size(); adaptationIndex++) {
|
||||||
|
AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex);
|
||||||
|
if (adaptation.type != C.TRACK_TYPE_VIDEO) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean hasFoundContentPeriod = false;
|
||||||
|
for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) {
|
||||||
|
Representation representation = adaptation.representations.get(representationIndex);
|
||||||
|
Format format = representation.format;
|
||||||
|
if (representation.presentationTimeOffsetUs <= startTimeUs) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasFoundContentPeriod = true;
|
||||||
|
WritableMap videoTrack = Arguments.createMap();
|
||||||
|
videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width);
|
||||||
|
videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height);
|
||||||
|
videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
|
||||||
|
videoTrack.putString("codecs", format.codecs != null ? format.codecs : "");
|
||||||
|
videoTrack.putString("trackId",
|
||||||
|
format.id == null ? String.valueOf(representationIndex) : format.id);
|
||||||
|
if (isFormatSupported(format)) {
|
||||||
|
videoTracks.pushMap(videoTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasFoundContentPeriod) {
|
||||||
|
return videoTracks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
WritableArray results = result.get(3000, TimeUnit.MILLISECONDS);
|
||||||
|
if (results == null && retryCount < 1) {
|
||||||
|
return this.getVideoTrackInfoFromManifest(++retryCount);
|
||||||
|
}
|
||||||
|
es.shutdown();
|
||||||
|
return results;
|
||||||
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private WritableArray getTextTrackInfo() {
|
private WritableArray getTextTrackInfo() {
|
||||||
WritableArray textTracks = Arguments.createArray();
|
WritableArray textTracks = Arguments.createArray();
|
||||||
|
|
||||||
@ -898,12 +1185,30 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
// which they seeked.
|
// which they seeked.
|
||||||
updateResumePosition();
|
updateResumePosition();
|
||||||
}
|
}
|
||||||
|
if (isUsingContentResolution) {
|
||||||
|
// Discontinuity events might have a different track list so we update the selected track
|
||||||
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
|
selectTrackWhenReady = true;
|
||||||
|
}
|
||||||
// When repeat is turned on, reaching the end of the video will not cause a state change
|
// When repeat is turned on, reaching the end of the video will not cause a state change
|
||||||
// so we need to explicitly detect it.
|
// so we need to explicitly detect it.
|
||||||
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
|
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
|
||||||
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
||||||
eventEmitter.end();
|
eventEmitter.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackStateChanged(int playbackState) {
|
||||||
|
if (playbackState == Player.STATE_READY && seekTime != C.TIME_UNSET) {
|
||||||
|
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
||||||
|
seekTime = C.TIME_UNSET;
|
||||||
|
if (isUsingContentResolution) {
|
||||||
|
// We need to update the selected track to make sure that it still matches user selection if track list has changed in this period
|
||||||
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -911,12 +1216,6 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSeekProcessed() {
|
|
||||||
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
|
||||||
seekTime = C.TIME_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
@ -937,9 +1236,16 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
eventEmitter.playbackRateChange(params.speed);
|
eventEmitter.playbackRateChange(params.speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIsPlayingChanged(boolean isPlaying) {
|
||||||
|
eventEmitter.playbackStateChanged(isPlaying);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException e) {
|
public void onPlayerError(ExoPlaybackException e) {
|
||||||
String errorString = "ExoPlaybackException type : " + e.type;
|
String errorString = "ExoPlaybackException type : " + e.type;
|
||||||
|
String errorCode = "2001"; // Playback error code 2xxx (2001 - unknown playback exception)
|
||||||
|
boolean needsReInitialization = false;
|
||||||
Exception ex = e;
|
Exception ex = e;
|
||||||
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
||||||
Exception cause = e.getRendererException();
|
Exception cause = e.getRendererException();
|
||||||
@ -950,30 +1256,82 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (decoderInitializationException.codecInfo == null
|
if (decoderInitializationException.codecInfo == null
|
||||||
|| decoderInitializationException.codecInfo.name == null) {
|
|| decoderInitializationException.codecInfo.name == null) {
|
||||||
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
|
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
|
||||||
|
errorCode = "2011";
|
||||||
errorString = getResources().getString(R.string.error_querying_decoders);
|
errorString = getResources().getString(R.string.error_querying_decoders);
|
||||||
} else if (decoderInitializationException.secureDecoderRequired) {
|
} else if (decoderInitializationException.secureDecoderRequired) {
|
||||||
|
errorCode = "2012";
|
||||||
errorString = getResources().getString(R.string.error_no_secure_decoder,
|
errorString = getResources().getString(R.string.error_no_secure_decoder,
|
||||||
decoderInitializationException.mimeType);
|
decoderInitializationException.mimeType);
|
||||||
} else {
|
} else {
|
||||||
|
errorCode = "2013";
|
||||||
errorString = getResources().getString(R.string.error_no_decoder,
|
errorString = getResources().getString(R.string.error_no_decoder,
|
||||||
decoderInitializationException.mimeType);
|
decoderInitializationException.mimeType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
errorCode = "2014";
|
||||||
errorString = getResources().getString(R.string.error_instantiating_decoder,
|
errorString = getResources().getString(R.string.error_instantiating_decoder,
|
||||||
decoderInitializationException.codecInfo.name);
|
decoderInitializationException.codecInfo.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
|
else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
|
||||||
|
// Re-initialization improves recovery speed and properly resumes
|
||||||
|
needsReInitialization = true;
|
||||||
errorString = getResources().getString(R.string.unrecognized_media_format);
|
errorString = getResources().getString(R.string.unrecognized_media_format);
|
||||||
|
Exception cause = e.getSourceException();
|
||||||
|
if (cause instanceof DefaultDrmSessionManager.MissingSchemeDataException) {
|
||||||
|
errorCode = "3004";
|
||||||
|
errorString = getResources().getString(R.string.unrecognized_media_format);
|
||||||
|
} else if(cause instanceof MediaDrmCallbackException || cause instanceof DrmSessionException) {
|
||||||
|
errorCode = "3005";
|
||||||
|
errorString = getResources().getString(R.string.unrecognized_media_format);
|
||||||
|
// DrmSessionExceptions can be caused by a lot internal reasons for failure, in most cases they can be safely retried and playback will recover
|
||||||
|
if (!hasDrmFailed || cause instanceof DrmSessionException) {
|
||||||
|
// When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time
|
||||||
|
hasDrmFailed = true;
|
||||||
|
playerNeedsSource = true;
|
||||||
|
updateResumePosition();
|
||||||
|
initializePlayer();
|
||||||
|
setPlayWhenReady(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (cause instanceof HttpDataSource.HttpDataSourceException) {
|
||||||
|
// this exception happens when connectivity is lost
|
||||||
|
updateResumePosition();
|
||||||
|
initializePlayer();
|
||||||
|
setPlayWhenReady(true);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
errorCode = "2021";
|
||||||
|
errorString = getResources().getString(R.string.unrecognized_media_format);
|
||||||
|
}
|
||||||
|
if (cause != null) {
|
||||||
|
Throwable rootCause = cause.getCause();
|
||||||
|
if (rootCause instanceof MediaDrmCallbackException) {
|
||||||
|
errorCode = "3005";
|
||||||
|
errorString = getResources().getString(R.string.unrecognized_media_format);
|
||||||
|
if (!hasDrmFailed) {
|
||||||
|
// When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time
|
||||||
|
hasDrmFailed = true;
|
||||||
|
playerNeedsSource = true;
|
||||||
|
updateResumePosition();
|
||||||
|
initializePlayer();
|
||||||
|
setPlayWhenReady(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
eventEmitter.error(errorString, ex);
|
eventEmitter.error(errorString, ex, errorCode);
|
||||||
playerNeedsSource = true;
|
playerNeedsSource = true;
|
||||||
if (isBehindLiveWindow(e)) {
|
if (isBehindLiveWindow(e)) {
|
||||||
clearResumePosition();
|
clearResumePosition();
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
} else {
|
} else {
|
||||||
updateResumePosition();
|
updateResumePosition();
|
||||||
|
if (needsReInitialization) {
|
||||||
|
initializePlayer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1015,7 +1373,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
public void setSrc(final Uri uri, final String extension, Map<String, String> headers) {
|
public void setSrc(final Uri uri, final String extension, Map<String, String> headers) {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
boolean isSourceEqual = uri.equals(srcUri);
|
boolean isSourceEqual = uri.equals(srcUri);
|
||||||
|
hasDrmFailed = false;
|
||||||
this.srcUri = uri;
|
this.srcUri = uri;
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
this.requestHeaders = headers;
|
this.requestHeaders = headers;
|
||||||
@ -1051,7 +1409,6 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
public void setRawSrc(final Uri uri, final String extension) {
|
public void setRawSrc(final Uri uri, final String extension) {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
boolean isSourceEqual = uri.equals(srcUri);
|
boolean isSourceEqual = uri.equals(srcUri);
|
||||||
|
|
||||||
this.srcUri = uri;
|
this.srcUri = uri;
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
this.mediaDataSourceFactory = buildDataSourceFactory(true);
|
this.mediaDataSourceFactory = buildDataSourceFactory(true);
|
||||||
@ -1147,14 +1504,51 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
int height = value.asInt();
|
int height = value.asInt();
|
||||||
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
||||||
TrackGroup group = groups.get(i);
|
TrackGroup group = groups.get(i);
|
||||||
|
Format closestFormat = null;
|
||||||
|
int closestTrackIndex = -1;
|
||||||
|
boolean usingExactMatch = false;
|
||||||
for (int j = 0; j < group.length; j++) {
|
for (int j = 0; j < group.length; j++) {
|
||||||
Format format = group.getFormat(j);
|
Format format = group.getFormat(j);
|
||||||
if (format.height == height) {
|
if (format.height == height) {
|
||||||
groupIndex = i;
|
groupIndex = i;
|
||||||
tracks[0] = j;
|
tracks[0] = j;
|
||||||
|
closestFormat = null;
|
||||||
|
closestTrackIndex = -1;
|
||||||
|
usingExactMatch = true;
|
||||||
break;
|
break;
|
||||||
|
} else if (isUsingContentResolution) {
|
||||||
|
// When using content resolution rather than ads, we need to try and find the closest match if there is no exact match
|
||||||
|
if (closestFormat != null) {
|
||||||
|
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) {
|
||||||
|
// Higher quality match
|
||||||
|
closestFormat = format;
|
||||||
|
closestTrackIndex = j;
|
||||||
|
}
|
||||||
|
} else if(format.height < height) {
|
||||||
|
closestFormat = format;
|
||||||
|
closestTrackIndex = j;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This is a fallback if the new period contains only higher resolutions than the user has selected
|
||||||
|
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
|
||||||
|
// No close match found - so we pick the lowest quality
|
||||||
|
int minHeight = Integer.MAX_VALUE;
|
||||||
|
for (int j = 0; j < group.length; j++) {
|
||||||
|
Format format = group.getFormat(j);
|
||||||
|
if (format.height < minHeight) {
|
||||||
|
minHeight = format.height;
|
||||||
|
groupIndex = i;
|
||||||
|
tracks[0] = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Selecting the closest match found
|
||||||
|
if (closestFormat != null && closestTrackIndex != -1) {
|
||||||
|
// We found the closest match instead of an exact one
|
||||||
|
groupIndex = i;
|
||||||
|
tracks[0] = closestTrackIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
|
} else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
|
||||||
// Use system settings if possible
|
// Use system settings if possible
|
||||||
@ -1170,10 +1564,36 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (groupIndex == C.INDEX_UNSET && trackType == C.TRACK_TYPE_VIDEO && groups.length != 0) { // Video auto
|
if (groupIndex == C.INDEX_UNSET && trackType == C.TRACK_TYPE_VIDEO && groups.length != 0) { // Video auto
|
||||||
// Add all tracks as valid options for ABR to choose from
|
// Add all tracks as valid options for ABR to choose from
|
||||||
TrackGroup group = groups.get(0);
|
TrackGroup group = groups.get(0);
|
||||||
tracks = new int[group.length];
|
int[] allTracks = new int[group.length];
|
||||||
groupIndex = 0;
|
groupIndex = 0;
|
||||||
|
|
||||||
for (int j = 0; j < group.length; j++) {
|
for (int j = 0; j < group.length; j++) {
|
||||||
tracks[j] = j;
|
allTracks[j] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valiate list of all tracks and add only supported formats
|
||||||
|
int supportedFormatLength = 0;
|
||||||
|
ArrayList<Integer> supportedTrackList = new ArrayList<Integer>();
|
||||||
|
for (int g = 0; g < allTracks.length; g++) {
|
||||||
|
Format format = group.getFormat(g);
|
||||||
|
if (isFormatSupported(format)) {
|
||||||
|
supportedFormatLength++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allTracks.length == 1) {
|
||||||
|
// With only one tracks we can't remove any tracks so attempt to play it anyway
|
||||||
|
tracks = allTracks;
|
||||||
|
} else {
|
||||||
|
tracks = new int[supportedFormatLength + 1];
|
||||||
|
int o = 0;
|
||||||
|
for (int k = 0; k < allTracks.length; k++) {
|
||||||
|
Format format = group.getFormat(k);
|
||||||
|
if (isFormatSupported(format)) {
|
||||||
|
tracks[o] = allTracks[k];
|
||||||
|
supportedTrackList.add(allTracks[k]);
|
||||||
|
o++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1191,6 +1611,25 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
trackSelector.setParameters(selectionParameters);
|
trackSelector.setParameters(selectionParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isFormatSupported(Format format) {
|
||||||
|
int width = format.width == Format.NO_VALUE ? 0 : format.width;
|
||||||
|
int height = format.height == Format.NO_VALUE ? 0 : format.height;
|
||||||
|
float frameRate = format.frameRate == Format.NO_VALUE ? 0 : format.frameRate;
|
||||||
|
String mimeType = format.sampleMimeType;
|
||||||
|
if (mimeType == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
boolean isSupported = false;
|
||||||
|
try {
|
||||||
|
MediaCodecInfo codecInfo = MediaCodecUtil.getDecoderInfo(mimeType, false, false);
|
||||||
|
isSupported = codecInfo.isVideoSizeAndRateSupportedV21(width, height, frameRate);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Failed to get decoder info - assume it is supported
|
||||||
|
isSupported = true;
|
||||||
|
}
|
||||||
|
return isSupported;
|
||||||
|
}
|
||||||
|
|
||||||
private int getGroupIndexForDefaultLocale(TrackGroupArray groups) {
|
private int getGroupIndexForDefaultLocale(TrackGroupArray groups) {
|
||||||
if (groups.length == 0){
|
if (groups.length == 0){
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
@ -1291,6 +1730,32 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.disableFocus = disableFocus;
|
this.disableFocus = disableFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBackBufferDurationMs(int backBufferDurationMs) {
|
||||||
|
Runtime runtime = Runtime.getRuntime();
|
||||||
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
|
long freeMemory = runtime.maxMemory() - usedMemory;
|
||||||
|
long reserveMemory = (long)minBackBufferMemoryReservePercent * runtime.maxMemory();
|
||||||
|
if (reserveMemory > freeMemory) {
|
||||||
|
// We don't have enough memory in reserve so we will
|
||||||
|
Log.w("ExoPlayer Warning", "Not enough reserve memory, setting back buffer to 0ms to reduce memory pressure!");
|
||||||
|
this.backBufferDurationMs = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.backBufferDurationMs = backBufferDurationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentStartTime(int contentStartTime) {
|
||||||
|
this.contentStartTime = (long)contentStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisableBuffering(boolean 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
|
||||||
@ -1329,15 +1794,22 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
exoPlayerView.setUseTextureView(finallyUseTextureView);
|
exoPlayerView.setUseTextureView(finallyUseTextureView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void useSecureView(boolean useSecureView) {
|
||||||
|
exoPlayerView.useSecureView(useSecureView);
|
||||||
|
}
|
||||||
|
|
||||||
public void setHideShutterView(boolean hideShutterView) {
|
public void setHideShutterView(boolean hideShutterView) {
|
||||||
exoPlayerView.setHideShutterView(hideShutterView);
|
exoPlayerView.setHideShutterView(hideShutterView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) {
|
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs, double newMaxHeapAllocationPercent, double newMinBackBufferMemoryReservePercent, double newMinBufferMemoryReservePercent) {
|
||||||
minBufferMs = newMinBufferMs;
|
minBufferMs = newMinBufferMs;
|
||||||
maxBufferMs = newMaxBufferMs;
|
maxBufferMs = newMaxBufferMs;
|
||||||
bufferForPlaybackMs = newBufferForPlaybackMs;
|
bufferForPlaybackMs = newBufferForPlaybackMs;
|
||||||
bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs;
|
bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs;
|
||||||
|
maxHeapAllocationPercent = newMaxHeapAllocationPercent;
|
||||||
|
minBackBufferMemoryReservePercent = newMinBackBufferMemoryReservePercent;
|
||||||
|
minBufferMemoryReservePercent = newMinBufferMemoryReservePercent;
|
||||||
releasePlayer();
|
releasePlayer();
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
}
|
}
|
||||||
@ -1363,7 +1835,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
@Override
|
@Override
|
||||||
public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) {
|
public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) {
|
||||||
Log.d("DRM Info", "onDrmSessionManagerError");
|
Log.d("DRM Info", "onDrmSessionManagerError");
|
||||||
eventEmitter.error("onDrmSessionManagerError", e);
|
eventEmitter.error("onDrmSessionManagerError", e, "3002");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,11 +49,15 @@ 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";
|
||||||
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs";
|
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs";
|
||||||
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs";
|
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs";
|
||||||
|
private static final String PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent";
|
||||||
|
private static final String PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT = "minBackBufferMemoryReservePercent";
|
||||||
|
private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT = "minBufferMemoryReservePercent";
|
||||||
private static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback";
|
private static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback";
|
||||||
private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
|
private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
|
||||||
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
|
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
|
||||||
@ -62,9 +66,13 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount";
|
private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount";
|
||||||
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
|
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
|
||||||
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
|
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
|
||||||
|
private static final String PROP_CONTENT_START_TIME = "contentStartTime";
|
||||||
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_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_SECURE_VIEW = "useSecureView";
|
||||||
private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack";
|
private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack";
|
||||||
private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type";
|
private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type";
|
||||||
private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value";
|
private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value";
|
||||||
@ -294,6 +302,26 @@ 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_CONTENT_START_TIME, defaultInt = 0)
|
||||||
|
public void setContentStartTime(final ReactExoplayerView videoView, final int contentStartTime) {
|
||||||
|
videoView.setContentStartTime(contentStartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
@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);
|
||||||
@ -304,6 +332,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
videoView.setUseTextureView(useTextureView);
|
videoView.setUseTextureView(useTextureView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = PROP_SECURE_VIEW, defaultBoolean = true)
|
||||||
|
public void useSecureView(final ReactExoplayerView videoView, final boolean useSecureView) {
|
||||||
|
videoView.useSecureView(useSecureView);
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_HIDE_SHUTTER_VIEW, defaultBoolean = false)
|
@ReactProp(name = PROP_HIDE_SHUTTER_VIEW, defaultBoolean = false)
|
||||||
public void setHideShutterView(final ReactExoplayerView videoView, final boolean hideShutterView) {
|
public void setHideShutterView(final ReactExoplayerView videoView, final boolean hideShutterView) {
|
||||||
videoView.setHideShutterView(hideShutterView);
|
videoView.setHideShutterView(hideShutterView);
|
||||||
@ -320,6 +353,10 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||||
int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
||||||
int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
||||||
|
double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT;
|
||||||
|
double minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE;
|
||||||
|
double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE;
|
||||||
|
|
||||||
if (bufferConfig != null) {
|
if (bufferConfig != null) {
|
||||||
minBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MS)
|
minBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MS)
|
||||||
? bufferConfig.getInt(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) : minBufferMs;
|
? bufferConfig.getInt(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) : minBufferMs;
|
||||||
@ -329,7 +366,13 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) : bufferForPlaybackMs;
|
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) : bufferForPlaybackMs;
|
||||||
bufferForPlaybackAfterRebufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
bufferForPlaybackAfterRebufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
||||||
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) : bufferForPlaybackAfterRebufferMs;
|
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) : bufferForPlaybackAfterRebufferMs;
|
||||||
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
|
maxHeapAllocationPercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT)
|
||||||
|
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) : maxHeapAllocationPercent;
|
||||||
|
minBackBufferMemoryReservePercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT)
|
||||||
|
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) : minBackBufferMemoryReservePercent;
|
||||||
|
minBufferMemoryReservePercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT)
|
||||||
|
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT) : minBufferMemoryReservePercent;
|
||||||
|
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
|||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
class VideoEventEmitter {
|
class VideoEventEmitter {
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ class VideoEventEmitter {
|
|||||||
private static final String EVENT_RESUME = "onPlaybackResume";
|
private static final String EVENT_RESUME = "onPlaybackResume";
|
||||||
private static final String EVENT_READY = "onReadyForDisplay";
|
private static final String EVENT_READY = "onReadyForDisplay";
|
||||||
private static final String EVENT_BUFFER = "onVideoBuffer";
|
private static final String EVENT_BUFFER = "onVideoBuffer";
|
||||||
|
private static final String EVENT_PLAYBACK_STATE_CHANGED = "onVideoPlaybackStateChanged";
|
||||||
private static final String EVENT_IDLE = "onVideoIdle";
|
private static final String EVENT_IDLE = "onVideoIdle";
|
||||||
private static final String EVENT_TIMED_METADATA = "onTimedMetadata";
|
private static final String EVENT_TIMED_METADATA = "onTimedMetadata";
|
||||||
private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy";
|
private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy";
|
||||||
@ -63,6 +66,8 @@ class VideoEventEmitter {
|
|||||||
EVENT_RESUME,
|
EVENT_RESUME,
|
||||||
EVENT_READY,
|
EVENT_READY,
|
||||||
EVENT_BUFFER,
|
EVENT_BUFFER,
|
||||||
|
EVENT_PLAYBACK_STATE_CHANGED,
|
||||||
|
EVENT_BUFFER_PROGRESS,
|
||||||
EVENT_IDLE,
|
EVENT_IDLE,
|
||||||
EVENT_TIMED_METADATA,
|
EVENT_TIMED_METADATA,
|
||||||
EVENT_AUDIO_BECOMING_NOISY,
|
EVENT_AUDIO_BECOMING_NOISY,
|
||||||
@ -87,6 +92,8 @@ class VideoEventEmitter {
|
|||||||
EVENT_RESUME,
|
EVENT_RESUME,
|
||||||
EVENT_READY,
|
EVENT_READY,
|
||||||
EVENT_BUFFER,
|
EVENT_BUFFER,
|
||||||
|
EVENT_PLAYBACK_STATE_CHANGED,
|
||||||
|
EVENT_BUFFER_PROGRESS,
|
||||||
EVENT_IDLE,
|
EVENT_IDLE,
|
||||||
EVENT_TIMED_METADATA,
|
EVENT_TIMED_METADATA,
|
||||||
EVENT_AUDIO_BECOMING_NOISY,
|
EVENT_AUDIO_BECOMING_NOISY,
|
||||||
@ -104,6 +111,8 @@ class VideoEventEmitter {
|
|||||||
private static final String EVENT_PROP_STEP_FORWARD = "canStepForward";
|
private static final String EVENT_PROP_STEP_FORWARD = "canStepForward";
|
||||||
private static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward";
|
private static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward";
|
||||||
|
|
||||||
|
private static final String EVENT_PROP_BUFFER_START = "bufferStart";
|
||||||
|
private static final String EVENT_PROP_BUFFER_END = "bufferEnd";
|
||||||
private static final String EVENT_PROP_DURATION = "duration";
|
private static final String EVENT_PROP_DURATION = "duration";
|
||||||
private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
|
private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
|
||||||
private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration";
|
private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration";
|
||||||
@ -125,11 +134,14 @@ class VideoEventEmitter {
|
|||||||
private static final String EVENT_PROP_ERROR = "error";
|
private static final String EVENT_PROP_ERROR = "error";
|
||||||
private static final String EVENT_PROP_ERROR_STRING = "errorString";
|
private static final String EVENT_PROP_ERROR_STRING = "errorString";
|
||||||
private static final String EVENT_PROP_ERROR_EXCEPTION = "errorException";
|
private static final String EVENT_PROP_ERROR_EXCEPTION = "errorException";
|
||||||
|
private static final String EVENT_PROP_ERROR_TRACE = "errorStackTrace";
|
||||||
|
private static final String EVENT_PROP_ERROR_CODE = "errorCode";
|
||||||
|
|
||||||
private static final String EVENT_PROP_TIMED_METADATA = "metadata";
|
private static final String EVENT_PROP_TIMED_METADATA = "metadata";
|
||||||
|
|
||||||
private static final String EVENT_PROP_BITRATE = "bitrate";
|
private static final String EVENT_PROP_BITRATE = "bitrate";
|
||||||
|
|
||||||
|
private static final String EVENT_PROP_IS_PLAYING = "isPlaying";
|
||||||
|
|
||||||
void setViewId(int viewId) {
|
void setViewId(int viewId) {
|
||||||
this.viewId = viewId;
|
this.viewId = viewId;
|
||||||
@ -206,6 +218,12 @@ class VideoEventEmitter {
|
|||||||
receiveEvent(EVENT_BUFFER, map);
|
receiveEvent(EVENT_BUFFER, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void playbackStateChanged(boolean isPlaying) {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putBoolean(EVENT_PROP_IS_PLAYING, isPlaying);
|
||||||
|
receiveEvent(EVENT_PLAYBACK_STATE_CHANGED, map);
|
||||||
|
}
|
||||||
|
|
||||||
void idle() {
|
void idle() {
|
||||||
receiveEvent(EVENT_IDLE, null);
|
receiveEvent(EVENT_IDLE, null);
|
||||||
}
|
}
|
||||||
@ -231,9 +249,25 @@ class VideoEventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void error(String errorString, Exception exception) {
|
void error(String errorString, Exception exception) {
|
||||||
|
_error(errorString, exception, "0001");
|
||||||
|
}
|
||||||
|
|
||||||
|
void error(String errorString, Exception exception, String errorCode) {
|
||||||
|
_error(errorString, exception, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _error(String errorString, Exception exception, String errorCode) {
|
||||||
|
// Prepare stack trace
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
exception.printStackTrace(pw);
|
||||||
|
String stackTrace = sw.toString();
|
||||||
|
|
||||||
WritableMap error = Arguments.createMap();
|
WritableMap error = Arguments.createMap();
|
||||||
error.putString(EVENT_PROP_ERROR_STRING, errorString);
|
error.putString(EVENT_PROP_ERROR_STRING, errorString);
|
||||||
error.putString(EVENT_PROP_ERROR_EXCEPTION, exception.toString());
|
error.putString(EVENT_PROP_ERROR_EXCEPTION, exception.toString());
|
||||||
|
error.putString(EVENT_PROP_ERROR_CODE, errorCode);
|
||||||
|
error.putString(EVENT_PROP_ERROR_TRACE, stackTrace);
|
||||||
WritableMap event = Arguments.createMap();
|
WritableMap event = Arguments.createMap();
|
||||||
event.putMap(EVENT_PROP_ERROR, error);
|
event.putMap(EVENT_PROP_ERROR, error);
|
||||||
receiveEvent(EVENT_ERROR, event);
|
receiveEvent(EVENT_ERROR, event);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user