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
|
||||
|
||||
### 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)
|
||||
- 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)
|
||||
|
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) => {
|
||||
// Need to hide poster here for windows as onReadyForDisplay is not implemented
|
||||
if (Platform.OS === 'windows') {
|
||||
@ -308,6 +314,7 @@ export default class Video extends Component {
|
||||
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
|
||||
},
|
||||
onVideoLoadStart: this._onLoadStart,
|
||||
onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
|
||||
onVideoLoad: this._onLoad,
|
||||
onVideoError: this._onError,
|
||||
onVideoProgress: this._onProgress,
|
||||
@ -464,6 +471,7 @@ Video.propTypes = {
|
||||
maxBufferMs: PropTypes.number,
|
||||
bufferForPlaybackMs: PropTypes.number,
|
||||
bufferForPlaybackAfterRebufferMs: PropTypes.number,
|
||||
maxHeapAllocationPercent: PropTypes.number,
|
||||
}),
|
||||
stereoPan: PropTypes.number,
|
||||
rate: PropTypes.number,
|
||||
@ -473,7 +481,9 @@ Video.propTypes = {
|
||||
playWhenInactive: PropTypes.bool,
|
||||
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
||||
reportBandwidth: PropTypes.bool,
|
||||
contentStartTime: PropTypes.number,
|
||||
disableFocus: PropTypes.bool,
|
||||
disableBuffering: PropTypes.bool,
|
||||
controls: PropTypes.bool,
|
||||
audioOnly: PropTypes.bool,
|
||||
currentTime: PropTypes.number,
|
||||
@ -481,8 +491,10 @@ Video.propTypes = {
|
||||
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
||||
progressUpdateInterval: PropTypes.number,
|
||||
useTextureView: PropTypes.bool,
|
||||
useSecureView: PropTypes.bool,
|
||||
hideShutterView: PropTypes.bool,
|
||||
onLoadStart: PropTypes.func,
|
||||
onPlaybackStateChanged: PropTypes.func,
|
||||
onLoad: PropTypes.func,
|
||||
onBuffer: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
|
@ -9,16 +9,28 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
public class DefaultReactExoplayerConfig implements ReactExoplayerConfig {
|
||||
|
||||
private final DefaultBandwidthMeter bandwidthMeter;
|
||||
private boolean disableDisconnectError = false;
|
||||
|
||||
public DefaultReactExoplayerConfig(Context context) {
|
||||
this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount) {
|
||||
if (this.disableDisconnectError) {
|
||||
// Use custom error handling policy to prevent throwing an error when losing network connection
|
||||
return new ReactExoplayerLoadErrorHandlingPolicy(minLoadRetryCount);
|
||||
}
|
||||
return new DefaultLoadErrorHandlingPolicy(minLoadRetryCount);
|
||||
}
|
||||
|
||||
public void setDisableDisconnectError(boolean disableDisconnectError) {
|
||||
this.disableDisconnectError = disableDisconnectError;
|
||||
}
|
||||
|
||||
public boolean getDisableDisconnectError() {
|
||||
return this.disableDisconnectError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultBandwidthMeter getBandwidthMeter() {
|
||||
return bandwidthMeter;
|
||||
|
@ -41,6 +41,7 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
private ViewGroup.LayoutParams layoutParams;
|
||||
|
||||
private boolean useTextureView = true;
|
||||
private boolean useSecureView = false;
|
||||
private boolean hideShutterView = false;
|
||||
|
||||
public ExoPlayerView(Context context) {
|
||||
@ -103,7 +104,15 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
this.hideShutterView = hideShutterView;
|
||||
updateShutterViewVisibility();
|
||||
|
@ -9,5 +9,8 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
public interface ReactExoplayerConfig {
|
||||
LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount);
|
||||
|
||||
void setDisableDisconnectError(boolean disableDisconnectError);
|
||||
boolean getDisableDisconnectError();
|
||||
|
||||
DefaultBandwidthMeter getBandwidthMeter();
|
||||
}
|
||||
|
@ -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.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
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.WritableMap;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.util.RNLog;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
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.Format;
|
||||
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.FrameworkMediaDrm;
|
||||
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.mediacodec.MediaCodecInfo;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||
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.DefaultBandwidthMeter;
|
||||
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.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.CookieManager;
|
||||
@ -79,6 +94,16 @@ import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
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")
|
||||
class ReactExoplayerView extends FrameLayout implements
|
||||
@ -90,6 +115,10 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
MetadataOutput,
|
||||
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 CookieManager DEFAULT_COOKIE_MANAGER;
|
||||
@ -128,15 +157,21 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private int minLoadRetryCount = 3;
|
||||
private int maxBitRate = 0;
|
||||
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 maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_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;
|
||||
|
||||
// Props from React
|
||||
private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS;
|
||||
private Uri srcUri;
|
||||
private String extension;
|
||||
private boolean repeat;
|
||||
@ -148,6 +183,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private Dynamic textTrackValue;
|
||||
private ReadableArray textTracks;
|
||||
private boolean disableFocus;
|
||||
private boolean disableBuffering;
|
||||
private long contentStartTime = -1L;
|
||||
private boolean disableDisconnectError;
|
||||
private boolean preventsDisplaySleepDuringVideoPlayback = true;
|
||||
private float mProgressUpdateInterval = 250.0f;
|
||||
private boolean playInBackground = false;
|
||||
@ -388,116 +426,262 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
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() {
|
||||
ReactExoplayerView self = this;
|
||||
Activity activity = themedReactContext.getCurrentActivity();
|
||||
// This ensures all props have been settled, to avoid async racing conditions.
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (player == null) {
|
||||
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
||||
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
||||
try {
|
||||
if (player == null) {
|
||||
// Initialize core configuration and listeners
|
||||
initializePlayerCore(self);
|
||||
}
|
||||
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);
|
||||
DefaultLoadControl.Builder defaultLoadControlBuilder = new DefaultLoadControl.Builder();
|
||||
defaultLoadControlBuilder.setAllocator(allocator);
|
||||
defaultLoadControlBuilder.setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
|
||||
defaultLoadControlBuilder.setTargetBufferBytes(-1);
|
||||
defaultLoadControlBuilder.setPrioritizeTimeOverSizeThresholds(true);
|
||||
DefaultLoadControl defaultLoadControl = defaultLoadControlBuilder.createDefaultLoadControl();
|
||||
DefaultRenderersFactory renderersFactory =
|
||||
new DefaultRenderersFactory(getContext())
|
||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
||||
player = new SimpleExoPlayer.Builder(getContext(), renderersFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setBandwidthMeter(bandwidthMeter)
|
||||
.setLoadControl(defaultLoadControl)
|
||||
.build();
|
||||
player.addListener(self);
|
||||
player.addMetadataOutput(self);
|
||||
exoPlayerView.setPlayer(player);
|
||||
audioBecomingNoisyReceiver.setListener(self);
|
||||
bandwidthMeter.addEventListener(new Handler(), self);
|
||||
setPlayWhenReady(!isPaused);
|
||||
playerNeedsSource = true;
|
||||
// Initialize handler to run on the main thread
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
// Source initialization must run on the main thread
|
||||
initializePlayerSource(self, drmSessionManager);
|
||||
} catch (Exception ex) {
|
||||
self.playerNeedsSource = true;
|
||||
Log.e("ExoPlayer Exception", "Failed to initialize Player!");
|
||||
Log.e("ExoPlayer Exception", ex.toString());
|
||||
self.eventEmitter.error(ex.toString(), ex, "1001");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (srcUri != null) {
|
||||
initializePlayerSource(self, null);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
private DrmSessionManager buildDrmSessionManager(UUID uuid,
|
||||
String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
||||
private void initializePlayerCore(ReactExoplayerView self) {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||
buildHttpDataSourceFactory(false));
|
||||
if (keyRequestPropertiesArray != null) {
|
||||
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
|
||||
keyRequestPropertiesArray[i + 1]);
|
||||
try {
|
||||
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||
buildHttpDataSourceFactory(false));
|
||||
if (keyRequestPropertiesArray != null) {
|
||||
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||
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) {
|
||||
if (uri == null) {
|
||||
throw new IllegalStateException("Invalid video uri");
|
||||
}
|
||||
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
||||
: uri.getLastPathSegment());
|
||||
config.setDisableDisconnectError(this.disableDisconnectError);
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return new SsMediaSource.Factory(
|
||||
@ -617,7 +801,6 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
initializePlayer();
|
||||
}
|
||||
@ -757,6 +940,10 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
onBuffering(false);
|
||||
startProgressHandler();
|
||||
videoLoaded();
|
||||
if (selectTrackWhenReady && isUsingContentResolution) {
|
||||
selectTrackWhenReady = false;
|
||||
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||
}
|
||||
// Setting the visibility for the playerControlView
|
||||
if (playerControlView != null) {
|
||||
playerControlView.show();
|
||||
@ -799,14 +986,34 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
int width = videoFormat != null ? videoFormat.width : 0;
|
||||
int height = videoFormat != null ? videoFormat.height : 0;
|
||||
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() {
|
||||
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();
|
||||
int index = getTrackRendererIndex(C.TRACK_TYPE_AUDIO);
|
||||
if (info == null || index == C.INDEX_UNSET) {
|
||||
@ -827,34 +1034,114 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
TrackGroupArray groups = info.getTrackGroups(index);
|
||||
TrackGroupArray groups = info.getTrackGroups(trackRendererIndex);
|
||||
for (int i = 0; i < groups.length; ++i) {
|
||||
TrackGroup group = groups.get(i);
|
||||
|
||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||
Format format = group.getFormat(trackIndex);
|
||||
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(trackIndex) : format.id);
|
||||
videoTracks.pushMap(videoTrack);
|
||||
if (isFormatSupported(format)) {
|
||||
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(trackIndex) : format.id);
|
||||
videoTracks.pushMap(videoTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
WritableArray textTracks = Arguments.createArray();
|
||||
|
||||
@ -898,12 +1185,30 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
// which they seeked.
|
||||
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
|
||||
// so we need to explicitly detect it.
|
||||
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
|
||||
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
||||
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
|
||||
@ -911,12 +1216,6 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekProcessed() {
|
||||
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
||||
seekTime = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
||||
// Do nothing.
|
||||
@ -937,9 +1236,16 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
eventEmitter.playbackRateChange(params.speed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIsPlayingChanged(boolean isPlaying) {
|
||||
eventEmitter.playbackStateChanged(isPlaying);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException e) {
|
||||
String errorString = "ExoPlaybackException type : " + e.type;
|
||||
String errorCode = "2001"; // Playback error code 2xxx (2001 - unknown playback exception)
|
||||
boolean needsReInitialization = false;
|
||||
Exception ex = e;
|
||||
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
||||
Exception cause = e.getRendererException();
|
||||
@ -950,30 +1256,82 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
if (decoderInitializationException.codecInfo == null
|
||||
|| decoderInitializationException.codecInfo.name == null) {
|
||||
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
|
||||
errorCode = "2011";
|
||||
errorString = getResources().getString(R.string.error_querying_decoders);
|
||||
} else if (decoderInitializationException.secureDecoderRequired) {
|
||||
errorCode = "2012";
|
||||
errorString = getResources().getString(R.string.error_no_secure_decoder,
|
||||
decoderInitializationException.mimeType);
|
||||
} else {
|
||||
errorCode = "2013";
|
||||
errorString = getResources().getString(R.string.error_no_decoder,
|
||||
decoderInitializationException.mimeType);
|
||||
}
|
||||
} else {
|
||||
errorCode = "2014";
|
||||
errorString = getResources().getString(R.string.error_instantiating_decoder,
|
||||
decoderInitializationException.codecInfo.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
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;
|
||||
if (isBehindLiveWindow(e)) {
|
||||
clearResumePosition();
|
||||
initializePlayer();
|
||||
} else {
|
||||
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) {
|
||||
if (uri != null) {
|
||||
boolean isSourceEqual = uri.equals(srcUri);
|
||||
|
||||
hasDrmFailed = false;
|
||||
this.srcUri = uri;
|
||||
this.extension = extension;
|
||||
this.requestHeaders = headers;
|
||||
@ -1051,7 +1409,6 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
public void setRawSrc(final Uri uri, final String extension) {
|
||||
if (uri != null) {
|
||||
boolean isSourceEqual = uri.equals(srcUri);
|
||||
|
||||
this.srcUri = uri;
|
||||
this.extension = extension;
|
||||
this.mediaDataSourceFactory = buildDataSourceFactory(true);
|
||||
@ -1147,14 +1504,51 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
int height = value.asInt();
|
||||
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
||||
TrackGroup group = groups.get(i);
|
||||
Format closestFormat = null;
|
||||
int closestTrackIndex = -1;
|
||||
boolean usingExactMatch = false;
|
||||
for (int j = 0; j < group.length; j++) {
|
||||
Format format = group.getFormat(j);
|
||||
if (format.height == height) {
|
||||
groupIndex = i;
|
||||
tracks[0] = j;
|
||||
closestFormat = null;
|
||||
closestTrackIndex = -1;
|
||||
usingExactMatch = true;
|
||||
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
|
||||
// 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
|
||||
// Add all tracks as valid options for ABR to choose from
|
||||
TrackGroup group = groups.get(0);
|
||||
tracks = new int[group.length];
|
||||
int[] allTracks = new int[group.length];
|
||||
groupIndex = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (groups.length == 0){
|
||||
return C.INDEX_UNSET;
|
||||
@ -1291,6 +1730,32 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
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) {
|
||||
if (fullscreen == isFullscreen) {
|
||||
return; // Avoid generating events when nothing is changing
|
||||
@ -1329,15 +1794,22 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
exoPlayerView.setUseTextureView(finallyUseTextureView);
|
||||
}
|
||||
|
||||
public void useSecureView(boolean useSecureView) {
|
||||
exoPlayerView.useSecureView(useSecureView);
|
||||
}
|
||||
|
||||
public void setHideShutterView(boolean 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;
|
||||
maxBufferMs = newMaxBufferMs;
|
||||
bufferForPlaybackMs = newBufferForPlaybackMs;
|
||||
bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs;
|
||||
maxHeapAllocationPercent = newMaxHeapAllocationPercent;
|
||||
minBackBufferMemoryReservePercent = newMinBackBufferMemoryReservePercent;
|
||||
minBufferMemoryReservePercent = newMinBufferMemoryReservePercent;
|
||||
releasePlayer();
|
||||
initializePlayer();
|
||||
}
|
||||
@ -1363,7 +1835,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
@Override
|
||||
public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) {
|
||||
Log.d("DRM Info", "onDrmSessionManagerError");
|
||||
eventEmitter.error("onDrmSessionManagerError", e);
|
||||
eventEmitter.error("onDrmSessionManagerError", e, "3002");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,11 +49,15 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
private static final String PROP_PAUSED = "paused";
|
||||
private static final String PROP_MUTED = "muted";
|
||||
private static final String PROP_VOLUME = "volume";
|
||||
private static final String PROP_BACK_BUFFER_DURATION_MS = "backBufferDurationMs";
|
||||
private static final String PROP_BUFFER_CONFIG = "bufferConfig";
|
||||
private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs";
|
||||
private static final String PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs";
|
||||
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_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_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
|
||||
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_MAXIMUM_BIT_RATE = "maxBitRate";
|
||||
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_BUFFERING = "disableBuffering";
|
||||
private static final String PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError";
|
||||
private static final String PROP_FULLSCREEN = "fullscreen";
|
||||
private static final String PROP_USE_TEXTURE_VIEW = "useTextureView";
|
||||
private static final String PROP_SECURE_VIEW = "useSecureView";
|
||||
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_VALUE = "value";
|
||||
@ -294,6 +302,26 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
videoView.setDisableFocus(disableFocus);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_BACK_BUFFER_DURATION_MS, defaultInt = 0)
|
||||
public void setBackBufferDurationMs(final ReactExoplayerView videoView, final int backBufferDurationMs) {
|
||||
videoView.setBackBufferDurationMs(backBufferDurationMs);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_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)
|
||||
public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) {
|
||||
videoView.setFullscreen(fullscreen);
|
||||
@ -304,6 +332,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
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)
|
||||
public void setHideShutterView(final ReactExoplayerView videoView, final boolean hideShutterView) {
|
||||
videoView.setHideShutterView(hideShutterView);
|
||||
@ -320,6 +353,10 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||
int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_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) {
|
||||
minBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MS)
|
||||
? 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;
|
||||
bufferForPlaybackAfterRebufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
||||
? 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.RetentionPolicy;
|
||||
import java.io.StringWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
class VideoEventEmitter {
|
||||
|
||||
@ -42,6 +44,7 @@ class VideoEventEmitter {
|
||||
private static final String EVENT_RESUME = "onPlaybackResume";
|
||||
private static final String EVENT_READY = "onReadyForDisplay";
|
||||
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_TIMED_METADATA = "onTimedMetadata";
|
||||
private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy";
|
||||
@ -63,6 +66,8 @@ class VideoEventEmitter {
|
||||
EVENT_RESUME,
|
||||
EVENT_READY,
|
||||
EVENT_BUFFER,
|
||||
EVENT_PLAYBACK_STATE_CHANGED,
|
||||
EVENT_BUFFER_PROGRESS,
|
||||
EVENT_IDLE,
|
||||
EVENT_TIMED_METADATA,
|
||||
EVENT_AUDIO_BECOMING_NOISY,
|
||||
@ -87,6 +92,8 @@ class VideoEventEmitter {
|
||||
EVENT_RESUME,
|
||||
EVENT_READY,
|
||||
EVENT_BUFFER,
|
||||
EVENT_PLAYBACK_STATE_CHANGED,
|
||||
EVENT_BUFFER_PROGRESS,
|
||||
EVENT_IDLE,
|
||||
EVENT_TIMED_METADATA,
|
||||
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_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_PLAYABLE_DURATION = "playableDuration";
|
||||
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_STRING = "errorString";
|
||||
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_BITRATE = "bitrate";
|
||||
|
||||
private static final String EVENT_PROP_IS_PLAYING = "isPlaying";
|
||||
|
||||
void setViewId(int viewId) {
|
||||
this.viewId = viewId;
|
||||
@ -206,6 +218,12 @@ class VideoEventEmitter {
|
||||
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() {
|
||||
receiveEvent(EVENT_IDLE, null);
|
||||
}
|
||||
@ -231,9 +249,25 @@ class VideoEventEmitter {
|
||||
}
|
||||
|
||||
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();
|
||||
error.putString(EVENT_PROP_ERROR_STRING, errorString);
|
||||
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();
|
||||
event.putMap(EVENT_PROP_ERROR, error);
|
||||
receiveEvent(EVENT_ERROR, event);
|
||||
|
Loading…
x
Reference in New Issue
Block a user