Allow audio output via earpiece
This commit is contained in:
parent
05864dc64a
commit
daabb91475
8
API.md
8
API.md
@ -294,6 +294,7 @@ var styles = StyleSheet.create({
|
|||||||
| [adTagUrl](#adTagUrl) | Android, iOS |
|
| [adTagUrl](#adTagUrl) | Android, iOS |
|
||||||
| [allowsExternalPlayback](#allowsexternalplayback) | iOS |
|
| [allowsExternalPlayback](#allowsexternalplayback) | iOS |
|
||||||
| [audioOnly](#audioonly) | All |
|
| [audioOnly](#audioonly) | All |
|
||||||
|
| [audioOutput](#audioOutput) | Android, iOS |
|
||||||
| [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS |
|
| [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS |
|
||||||
| [backBufferDurationMs](#backBufferDurationMs) | Android |
|
| [backBufferDurationMs](#backBufferDurationMs) | Android |
|
||||||
| [bufferConfig](#bufferconfig) | Android |
|
| [bufferConfig](#bufferconfig) | Android |
|
||||||
@ -417,6 +418,13 @@ For this to work, the poster prop must be set.
|
|||||||
|
|
||||||
Platforms: all
|
Platforms: all
|
||||||
|
|
||||||
|
#### audioOutput
|
||||||
|
Changes the audio output.
|
||||||
|
* **speaker (default)** - plays through speaker
|
||||||
|
* **earpiece** - plays through earpiece
|
||||||
|
|
||||||
|
Platforms: Android, iOS
|
||||||
|
|
||||||
#### automaticallyWaitsToMinimizeStalling
|
#### automaticallyWaitsToMinimizeStalling
|
||||||
A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling. For clients linked against iOS 10.0 and later
|
A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling. For clients linked against iOS 10.0 and later
|
||||||
* **false** - Immediately starts playback
|
* **false** - Immediately starts playback
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
- Feature: playing audio over earpiece [#2887](https://github.com/react-native-video/react-native-video/issues/2887)
|
||||||
|
|
||||||
### Version 6.0.0-alpha.5
|
### Version 6.0.0-alpha.5
|
||||||
|
|
||||||
- iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017)
|
- iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017)
|
||||||
|
1
Video.js
1
Video.js
@ -520,6 +520,7 @@ Video.propTypes = {
|
|||||||
disableBuffering: PropTypes.bool,
|
disableBuffering: PropTypes.bool,
|
||||||
controls: PropTypes.bool,
|
controls: PropTypes.bool,
|
||||||
audioOnly: PropTypes.bool,
|
audioOnly: PropTypes.bool,
|
||||||
|
audioOutput: PropTypes.oneOf(['earpiece', 'speaker']),
|
||||||
currentTime: PropTypes.number,
|
currentTime: PropTypes.number,
|
||||||
fullscreenAutorotate: PropTypes.bool,
|
fullscreenAutorotate: PropTypes.bool,
|
||||||
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
||||||
|
@ -47,6 +47,7 @@ import com.google.android.exoplayer2.Player;
|
|||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.Tracks;
|
import com.google.android.exoplayer2.Tracks;
|
||||||
|
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
@ -135,6 +136,33 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
public enum AudioOutput {
|
||||||
|
SPEAKER("speaker", C.STREAM_TYPE_MUSIC),
|
||||||
|
EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL);
|
||||||
|
|
||||||
|
private final int streamType;
|
||||||
|
private final String mName;
|
||||||
|
|
||||||
|
AudioOutput(final String name, int stream) {
|
||||||
|
mName = name;
|
||||||
|
streamType = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AudioOutput get(String name) {
|
||||||
|
for (AudioOutput d : values()) {
|
||||||
|
if (d.mName.equalsIgnoreCase(name))
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return SPEAKER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "(" + this.mName + ", " + streamType + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final VideoEventEmitter eventEmitter;
|
private final VideoEventEmitter eventEmitter;
|
||||||
private final ReactExoplayerConfig config;
|
private final ReactExoplayerConfig config;
|
||||||
private final DefaultBandwidthMeter bandwidthMeter;
|
private final DefaultBandwidthMeter bandwidthMeter;
|
||||||
@ -161,6 +189,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private boolean muted = false;
|
private boolean muted = false;
|
||||||
private boolean hasAudioFocus = false;
|
private boolean hasAudioFocus = false;
|
||||||
private float rate = 1f;
|
private float rate = 1f;
|
||||||
|
private AudioOutput audioOutput = AudioOutput.SPEAKER;
|
||||||
private float audioVolume = 1f;
|
private float audioVolume = 1f;
|
||||||
private int minLoadRetryCount = 3;
|
private int minLoadRetryCount = 3;
|
||||||
private int maxBitRate = 0;
|
private int maxBitRate = 0;
|
||||||
@ -236,7 +265,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
lastPos = pos;
|
lastPos = pos;
|
||||||
lastBufferDuration = bufferedDuration;
|
lastBufferDuration = bufferedDuration;
|
||||||
lastDuration = duration;
|
lastDuration = duration;
|
||||||
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos));
|
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(),
|
||||||
|
getPositionInFirstPeriodMsForCurrentWindow(pos));
|
||||||
}
|
}
|
||||||
msg = obtainMessage(SHOW_PROGRESS);
|
msg = obtainMessage(SHOW_PROGRESS);
|
||||||
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
|
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
|
||||||
@ -248,7 +278,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) {
|
public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) {
|
||||||
Timeline.Window window = new Timeline.Window();
|
Timeline.Window window = new Timeline.Window();
|
||||||
if(!player.getCurrentTimeline().isEmpty()) {
|
if (!player.getCurrentTimeline().isEmpty()) {
|
||||||
player.getCurrentTimeline().getWindow(player.getCurrentMediaItemIndex(), window);
|
player.getCurrentTimeline().getWindow(player.getCurrentMediaItemIndex(), window);
|
||||||
}
|
}
|
||||||
return window.windowStartTimeMs + currentPosition;
|
return window.windowStartTimeMs + currentPosition;
|
||||||
@ -307,7 +337,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow() {
|
protected void onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
/* We want to be able to continue playing audio when switching tabs.
|
/*
|
||||||
|
* We want to be able to continue playing audio when switching tabs.
|
||||||
* Leave this here in case it causes issues.
|
* Leave this here in case it causes issues.
|
||||||
*/
|
*/
|
||||||
// stopPlayback();
|
// stopPlayback();
|
||||||
@ -341,7 +372,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
stopPlayback();
|
stopPlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
//BandwidthMeter.EventListener implementation
|
// BandwidthMeter.EventListener implementation
|
||||||
@Override
|
@Override
|
||||||
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
|
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
|
||||||
if (mReportBandwidth) {
|
if (mReportBandwidth) {
|
||||||
@ -363,7 +394,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
* Toggling the visibility of the player control view
|
* Toggling the visibility of the player control view
|
||||||
*/
|
*/
|
||||||
private void togglePlayerControlVisibility() {
|
private void togglePlayerControlVisibility() {
|
||||||
if(player == null) return;
|
if (player == null)
|
||||||
|
return;
|
||||||
reLayout(playerControlView);
|
reLayout(playerControlView);
|
||||||
if (playerControlView.isVisible()) {
|
if (playerControlView.isVisible()) {
|
||||||
playerControlView.hide();
|
playerControlView.hide();
|
||||||
@ -381,12 +413,13 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fullScreenPlayerView == null) {
|
if (fullScreenPlayerView == null) {
|
||||||
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) {
|
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView,
|
||||||
@Override
|
new OnBackPressedCallback(true) {
|
||||||
public void handleOnBackPressed() {
|
@Override
|
||||||
setFullscreen(false);
|
public void handleOnBackPressed() {
|
||||||
}
|
setFullscreen(false);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting the player for the playerControlView
|
// Setting the player for the playerControlView
|
||||||
@ -403,7 +436,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Handling the playButton click event
|
// Handling the playButton click event
|
||||||
ImageButton playButton = playerControlView.findViewById(R.id.exo_play);
|
ImageButton playButton = playerControlView.findViewById(R.id.exo_play);
|
||||||
playButton.setOnClickListener(new View.OnClickListener() {
|
playButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -415,7 +448,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Handling the pauseButton click event
|
// Handling the pauseButton click event
|
||||||
ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
|
ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
|
||||||
pauseButton.setOnClickListener(new View.OnClickListener() {
|
pauseButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -424,7 +457,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Handling the fullScreenButton click event
|
// Handling the fullScreenButton click event
|
||||||
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
|
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
|
||||||
fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen));
|
fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen));
|
||||||
updateFullScreenButtonVisbility();
|
updateFullScreenButtonVisbility();
|
||||||
@ -442,14 +475,16 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
pauseButton.setVisibility(INVISIBLE);
|
pauseButton.setVisibility(INVISIBLE);
|
||||||
}
|
}
|
||||||
reLayout(playPauseControlContainer);
|
reLayout(playPauseControlContainer);
|
||||||
//Remove this eventListener once its executed. since UI will work fine once after the reLayout is done
|
// Remove this eventListener once its executed. since UI will work fine once
|
||||||
|
// after the reLayout is done
|
||||||
player.removeListener(eventListener);
|
player.removeListener(eventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
|
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
|
||||||
reLayout(playPauseControlContainer);
|
reLayout(playPauseControlContainer);
|
||||||
//Remove this eventListener once its executed. since UI will work fine once after the reLayout is done
|
// Remove this eventListener once its executed. since UI will work fine once
|
||||||
|
// after the reLayout is done
|
||||||
player.removeListener(eventListener);
|
player.removeListener(eventListener);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -460,7 +495,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
* Adding Player control to the frame layout
|
* Adding Player control to the frame layout
|
||||||
*/
|
*/
|
||||||
private void addPlayerControl() {
|
private void addPlayerControl() {
|
||||||
if(playerControlView == null) return;
|
if (playerControlView == null)
|
||||||
|
return;
|
||||||
LayoutParams layoutParams = new LayoutParams(
|
LayoutParams layoutParams = new LayoutParams(
|
||||||
LayoutParams.MATCH_PARENT,
|
LayoutParams.MATCH_PARENT,
|
||||||
LayoutParams.MATCH_PARENT);
|
LayoutParams.MATCH_PARENT);
|
||||||
@ -475,12 +511,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the layout
|
* Update the layout
|
||||||
* @param view view needs to update layout
|
*
|
||||||
|
* @param view view needs to update layout
|
||||||
*
|
*
|
||||||
* This is a workaround for the open bug in react-native: https://github.com/facebook/react-native/issues/17968
|
* This is a workaround for the open bug in react-native:
|
||||||
|
* https://github.com/facebook/react-native/issues/17968
|
||||||
*/
|
*/
|
||||||
private void reLayout(View view) {
|
private void reLayout(View view) {
|
||||||
if (view == null) return;
|
if (view == null)
|
||||||
|
return;
|
||||||
view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
|
view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
|
||||||
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
|
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
|
||||||
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
|
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||||
@ -489,7 +528,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private class RNVLoadControl extends DefaultLoadControl {
|
private class RNVLoadControl extends DefaultLoadControl {
|
||||||
private int availableHeapInBytes = 0;
|
private int availableHeapInBytes = 0;
|
||||||
private Runtime runtime;
|
private Runtime runtime;
|
||||||
public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, int backBufferDurationMs, boolean retainBackBufferFromKeyframe) {
|
|
||||||
|
public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs,
|
||||||
|
int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds,
|
||||||
|
int backBufferDurationMs, boolean retainBackBufferFromKeyframe) {
|
||||||
super(allocator,
|
super(allocator,
|
||||||
minBufferMs,
|
minBufferMs,
|
||||||
maxBufferMs,
|
maxBufferMs,
|
||||||
@ -500,8 +542,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
backBufferDurationMs,
|
backBufferDurationMs,
|
||||||
retainBackBufferFromKeyframe);
|
retainBackBufferFromKeyframe);
|
||||||
runtime = Runtime.getRuntime();
|
runtime = Runtime.getRuntime();
|
||||||
ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(themedReactContext.ACTIVITY_SERVICE);
|
ActivityManager activityManager = (ActivityManager) themedReactContext
|
||||||
availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024);
|
.getSystemService(themedReactContext.ACTIVITY_SERVICE);
|
||||||
|
availableHeapInBytes = (int) Math
|
||||||
|
.floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -516,10 +560,11 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
long freeMemory = runtime.maxMemory() - usedMemory;
|
long freeMemory = runtime.maxMemory() - usedMemory;
|
||||||
long reserveMemory = (long)minBufferMemoryReservePercent * runtime.maxMemory();
|
long reserveMemory = (long) minBufferMemoryReservePercent * runtime.maxMemory();
|
||||||
long bufferedMs = bufferedDurationUs / (long)1000;
|
long bufferedMs = bufferedDurationUs / (long) 1000;
|
||||||
if (reserveMemory > freeMemory && bufferedMs > 2000) {
|
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
|
// We don't have enough memory in reserve so we stop buffering to allow other
|
||||||
|
// components to use it instead
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (runtime.freeMemory() == 0) {
|
if (runtime.freeMemory() == 0) {
|
||||||
@ -552,7 +597,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
if (playerNeedsSource && srcUri != null) {
|
if (playerNeedsSource && srcUri != null) {
|
||||||
exoPlayerView.invalidateAspectRatio();
|
exoPlayerView.invalidateAspectRatio();
|
||||||
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
|
// DRM session manager creation must be done on a different thread to prevent
|
||||||
|
// crashes so we start a new thread
|
||||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
es.execute(new Runnable() {
|
es.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -562,13 +608,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (drmSessionManager == null && self.drmUUID != null) {
|
if (drmSessionManager == null && self.drmUUID != null) {
|
||||||
// Failed to intialize DRM session manager - cannot continue
|
// Failed to intialize DRM session manager - cannot continue
|
||||||
Log.e("ExoPlayer Exception", "Failed to initialize DRM Session Manager Framework!");
|
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");
|
eventEmitter.error("Failed to initialize DRM Session Manager Framework!",
|
||||||
|
new Exception("DRM Session Manager Framework failure!"), "3003");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
Log.e("ExoPlayer Exception", "Failed to initialize Player!");
|
Log.e("ExoPlayer Exception", "Failed to initialize Player!");
|
||||||
eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001");
|
eventEmitter.error("Failed to initialize Player!",
|
||||||
|
new Exception("Current Activity is null!"), "1001");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,24 +666,22 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
-1,
|
-1,
|
||||||
true,
|
true,
|
||||||
backBufferDurationMs,
|
backBufferDurationMs,
|
||||||
DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME
|
DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME);
|
||||||
);
|
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext())
|
||||||
DefaultRenderersFactory renderersFactory =
|
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
||||||
new DefaultRenderersFactory(getContext())
|
|
||||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
|
||||||
|
|
||||||
// Create an AdsLoader.
|
// Create an AdsLoader.
|
||||||
adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build();
|
adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build();
|
||||||
|
|
||||||
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
||||||
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
||||||
|
|
||||||
player = new ExoPlayer.Builder(getContext(), renderersFactory)
|
player = new ExoPlayer.Builder(getContext(), renderersFactory)
|
||||||
.setTrackSelector(self.trackSelector)
|
.setTrackSelector(self.trackSelector)
|
||||||
.setBandwidthMeter(bandwidthMeter)
|
.setBandwidthMeter(bandwidthMeter)
|
||||||
.setLoadControl(loadControl)
|
.setLoadControl(loadControl)
|
||||||
.setMediaSourceFactory(mediaSourceFactory)
|
.setMediaSourceFactory(mediaSourceFactory)
|
||||||
.build();
|
.build();
|
||||||
player.addListener(self);
|
player.addListener(self);
|
||||||
exoPlayerView.setPlayer(player);
|
exoPlayerView.setPlayer(player);
|
||||||
if (adsLoader != null) {
|
if (adsLoader != null) {
|
||||||
@ -659,7 +705,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
} catch (UnsupportedDrmException e) {
|
} catch (UnsupportedDrmException e) {
|
||||||
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
||||||
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
||||||
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
? R.string.error_drm_unsupported_scheme
|
||||||
|
: R.string.error_drm_unknown);
|
||||||
eventEmitter.error(getResources().getString(errorStringId), e, "3003");
|
eventEmitter.error(getResources().getString(errorStringId), e, "3003");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -675,7 +722,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
||||||
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
||||||
DataSpec adTagDataSpec = new DataSpec(adTagUrl);
|
DataSpec adTagDataSpec = new DataSpec(adTagUrl);
|
||||||
mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView);
|
mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl),
|
||||||
|
mediaSourceFactory, adsLoader, exoPlayerView);
|
||||||
}
|
}
|
||||||
MediaSource mediaSource;
|
MediaSource mediaSource;
|
||||||
if (mediaSourceList.size() == 0) {
|
if (mediaSourceList.size() == 0) {
|
||||||
@ -691,8 +739,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
mediaSourceList.add(0, videoSource);
|
mediaSourceList.add(0, videoSource);
|
||||||
}
|
}
|
||||||
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
||||||
new MediaSource[mediaSourceList.size()]
|
new MediaSource[mediaSourceList.size()]);
|
||||||
);
|
|
||||||
mediaSource = new MergingMediaSource(textSourceArray);
|
mediaSource = new MergingMediaSource(textSourceArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,11 +775,13 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
startBufferCheckTimer();
|
startBufferCheckTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray)
|
||||||
|
throws UnsupportedDrmException {
|
||||||
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, 0);
|
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, int retryCount) throws UnsupportedDrmException {
|
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;
|
||||||
}
|
}
|
||||||
@ -750,12 +799,13 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
mediaDrm.setPropertyString("securityLevel", "L3");
|
mediaDrm.setPropertyString("securityLevel", "L3");
|
||||||
}
|
}
|
||||||
return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, false, 3);
|
return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, false, 3);
|
||||||
} catch(UnsupportedDrmException ex) {
|
} catch (UnsupportedDrmException ex) {
|
||||||
// Unsupported DRM exceptions are handled by the calling method
|
// Unsupported DRM exceptions are handled by the calling method
|
||||||
throw ex;
|
throw ex;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (retryCount < 3) {
|
if (retryCount < 3) {
|
||||||
// Attempt retry 3 times in case where the OS Media DRM Framework fails for whatever reason
|
// Attempt retry 3 times in case where the OS Media DRM Framework fails for
|
||||||
|
// whatever reason
|
||||||
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, ++retryCount);
|
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, ++retryCount);
|
||||||
}
|
}
|
||||||
// Handle the unknow exception and emit to JS
|
// Handle the unknow exception and emit to JS
|
||||||
@ -776,8 +826,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
if (adTagUrl != null) {
|
if (adTagUrl != null) {
|
||||||
mediaItemBuilder.setAdsConfiguration(
|
mediaItemBuilder.setAdsConfiguration(
|
||||||
new MediaItem.AdsConfiguration.Builder(adTagUrl).build()
|
new MediaItem.AdsConfiguration.Builder(adTagUrl).build());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaItem mediaItem = mediaItemBuilder.build();
|
MediaItem mediaItem = mediaItemBuilder.build();
|
||||||
@ -797,33 +846,29 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
case CONTENT_TYPE_SS:
|
case CONTENT_TYPE_SS:
|
||||||
return new SsMediaSource.Factory(
|
return new SsMediaSource.Factory(
|
||||||
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
|
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
|
||||||
buildDataSourceFactory(false)
|
buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider)
|
||||||
).setDrmSessionManagerProvider(drmProvider)
|
.setLoadErrorHandlingPolicy(
|
||||||
.setLoadErrorHandlingPolicy(
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
|
.createMediaSource(mediaItem);
|
||||||
).createMediaSource(mediaItem);
|
|
||||||
case CONTENT_TYPE_DASH:
|
case CONTENT_TYPE_DASH:
|
||||||
return new DashMediaSource.Factory(
|
return new DashMediaSource.Factory(
|
||||||
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
|
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
|
||||||
buildDataSourceFactory(false)
|
buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider)
|
||||||
).setDrmSessionManagerProvider(drmProvider)
|
.setLoadErrorHandlingPolicy(
|
||||||
.setLoadErrorHandlingPolicy(
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
|
.createMediaSource(mediaItem);
|
||||||
).createMediaSource(mediaItem);
|
|
||||||
case CONTENT_TYPE_HLS:
|
case CONTENT_TYPE_HLS:
|
||||||
return new HlsMediaSource.Factory(
|
return new HlsMediaSource.Factory(
|
||||||
mediaDataSourceFactory
|
mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider)
|
||||||
).setDrmSessionManagerProvider(drmProvider)
|
.setLoadErrorHandlingPolicy(
|
||||||
.setLoadErrorHandlingPolicy(
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
|
.createMediaSource(mediaItem);
|
||||||
).createMediaSource(mediaItem);
|
|
||||||
case CONTENT_TYPE_OTHER:
|
case CONTENT_TYPE_OTHER:
|
||||||
return new ProgressiveMediaSource.Factory(
|
return new ProgressiveMediaSource.Factory(
|
||||||
mediaDataSourceFactory
|
mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider)
|
||||||
).setDrmSessionManagerProvider(drmProvider)
|
.setLoadErrorHandlingPolicy(
|
||||||
.setLoadErrorHandlingPolicy(
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
|
.createMediaSource(mediaItem);
|
||||||
).createMediaSource(mediaItem);
|
|
||||||
default: {
|
default: {
|
||||||
throw new IllegalStateException("Unsupported type: " + type);
|
throw new IllegalStateException("Unsupported type: " + type);
|
||||||
}
|
}
|
||||||
@ -840,7 +885,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
ReadableMap textTrack = textTracks.getMap(i);
|
ReadableMap textTrack = textTracks.getMap(i);
|
||||||
String language = textTrack.getString("language");
|
String language = textTrack.getString("language");
|
||||||
String title = textTrack.hasKey("title")
|
String title = textTrack.hasKey("title")
|
||||||
? textTrack.getString("title") : language + " " + i;
|
? textTrack.getString("title")
|
||||||
|
: language + " " + i;
|
||||||
Uri uri = Uri.parse(textTrack.getString("uri"));
|
Uri uri = Uri.parse(textTrack.getString("uri"));
|
||||||
MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"),
|
MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"),
|
||||||
language);
|
language);
|
||||||
@ -971,7 +1017,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
/**
|
/**
|
||||||
* Returns a new DataSource factory.
|
* Returns a new DataSource factory.
|
||||||
*
|
*
|
||||||
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
|
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener
|
||||||
|
* to the new
|
||||||
* DataSource factory.
|
* DataSource factory.
|
||||||
* @return A new DataSource factory.
|
* @return A new DataSource factory.
|
||||||
*/
|
*/
|
||||||
@ -983,15 +1030,16 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
/**
|
/**
|
||||||
* Returns a new HttpDataSource factory.
|
* Returns a new HttpDataSource factory.
|
||||||
*
|
*
|
||||||
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
|
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener
|
||||||
* DataSource factory.
|
* to the new
|
||||||
|
* DataSource factory.
|
||||||
* @return A new HttpDataSource factory.
|
* @return A new HttpDataSource factory.
|
||||||
*/
|
*/
|
||||||
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
||||||
return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
|
return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext,
|
||||||
|
useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AudioManager.OnAudioFocusChangeListener implementation
|
// AudioManager.OnAudioFocusChangeListener implementation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1045,7 +1093,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvents(Player player, Player.Events events) {
|
public void onEvents(Player player, Player.Events events) {
|
||||||
if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
|
if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
|
||||||
|
|| events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
|
||||||
int playbackState = player.getPlaybackState();
|
int playbackState = player.getPlaybackState();
|
||||||
boolean playWhenReady = player.getPlayWhenReady();
|
boolean playWhenReady = player.getPlayWhenReady();
|
||||||
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
|
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
|
||||||
@ -1059,38 +1108,38 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
setKeepScreenOn(false);
|
setKeepScreenOn(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Player.STATE_BUFFERING:
|
case Player.STATE_BUFFERING:
|
||||||
text += "buffering";
|
text += "buffering";
|
||||||
onBuffering(true);
|
onBuffering(true);
|
||||||
clearProgressMessageHandler();
|
clearProgressMessageHandler();
|
||||||
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);
|
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);
|
||||||
break;
|
break;
|
||||||
case Player.STATE_READY:
|
case Player.STATE_READY:
|
||||||
text += "ready";
|
text += "ready";
|
||||||
eventEmitter.ready();
|
eventEmitter.ready();
|
||||||
onBuffering(false);
|
onBuffering(false);
|
||||||
clearProgressMessageHandler(); // ensure there is no other message
|
clearProgressMessageHandler(); // ensure there is no other message
|
||||||
startProgressHandler();
|
startProgressHandler();
|
||||||
videoLoaded();
|
videoLoaded();
|
||||||
if (selectTrackWhenReady && isUsingContentResolution) {
|
if (selectTrackWhenReady && isUsingContentResolution) {
|
||||||
selectTrackWhenReady = false;
|
selectTrackWhenReady = false;
|
||||||
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
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();
|
||||||
}
|
}
|
||||||
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);
|
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);
|
||||||
break;
|
break;
|
||||||
case Player.STATE_ENDED:
|
case Player.STATE_ENDED:
|
||||||
text += "ended";
|
text += "ended";
|
||||||
eventEmitter.end();
|
eventEmitter.end();
|
||||||
onStopPlayback();
|
onStopPlayback();
|
||||||
setKeepScreenOn(false);
|
setKeepScreenOn(false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
text += "unknown";
|
text += "unknown";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1100,12 +1149,14 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The progress message handler will duplicate recursions of the onProgressMessage handler
|
* The progress message handler will duplicate recursions of the
|
||||||
on change of player state from any state to STATE_READY with playWhenReady is true (when
|
* onProgressMessage handler
|
||||||
the video is not paused). This clears all existing messages.
|
* on change of player state from any state to STATE_READY with playWhenReady is
|
||||||
|
* true (when
|
||||||
|
* the video is not paused). This clears all existing messages.
|
||||||
*/
|
*/
|
||||||
private void clearProgressMessageHandler() {
|
private void clearProgressMessageHandler() {
|
||||||
progressHandler.removeMessages(SHOW_PROGRESS);
|
progressHandler.removeMessages(SHOW_PROGRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void videoLoaded() {
|
private void videoLoaded() {
|
||||||
@ -1129,20 +1180,21 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
long duration = player.getDuration();
|
long duration = player.getDuration();
|
||||||
long currentPosition = player.getCurrentPosition();
|
long currentPosition = player.getCurrentPosition();
|
||||||
ArrayList<Track> audioTracks = getAudioTrackInfo();
|
ArrayList<Track> audioTracks = getAudioTrackInfo();
|
||||||
ArrayList<Track> textTracks = getTextTrackInfo();
|
ArrayList<Track> textTracks = getTextTrackInfo();
|
||||||
|
|
||||||
if (this.contentStartTime != -1L) {
|
if (this.contentStartTime != -1L) {
|
||||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
es.execute(new Runnable() {
|
es.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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
|
// To prevent ANRs caused by getVideoTrackInfo we run this on a different thread
|
||||||
|
// and notify the player only when we're done
|
||||||
ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
|
ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
|
||||||
if (videoTracks != null) {
|
if (videoTracks != null) {
|
||||||
isUsingContentResolution = true;
|
isUsingContentResolution = true;
|
||||||
}
|
}
|
||||||
eventEmitter.load(duration, currentPosition, width, height,
|
eventEmitter.load(duration, currentPosition, width, height,
|
||||||
audioTracks, textTracks, videoTracks, trackId );
|
audioTracks, textTracks, videoTracks, trackId);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1157,9 +1209,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTrackSelected(TrackSelection selection, TrackGroup group,
|
private static boolean isTrackSelected(TrackSelection selection, TrackGroup group,
|
||||||
int trackIndex){
|
int trackIndex) {
|
||||||
return selection != null && selection.getTrackGroup() == group
|
return selection != null && selection.getTrackGroup() == group
|
||||||
&& selection.indexOf( trackIndex ) != C.INDEX_UNSET;
|
&& selection.indexOf(trackIndex) != C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<Track> getAudioTrackInfo() {
|
private ArrayList<Track> getAudioTrackInfo() {
|
||||||
@ -1176,7 +1228,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
TrackGroupArray groups = info.getTrackGroups(index);
|
TrackGroupArray groups = info.getTrackGroups(index);
|
||||||
TrackSelectionArray selectionArray = player.getCurrentTrackSelections();
|
TrackSelectionArray selectionArray = player.getCurrentTrackSelections();
|
||||||
TrackSelection selection = selectionArray.get( C.TRACK_TYPE_AUDIO );
|
TrackSelection selection = selectionArray.get(C.TRACK_TYPE_AUDIO);
|
||||||
|
|
||||||
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);
|
||||||
@ -1187,7 +1239,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
audioTrack.m_mimeType = format.sampleMimeType;
|
audioTrack.m_mimeType = format.sampleMimeType;
|
||||||
audioTrack.m_language = format.language != null ? format.language : "";
|
audioTrack.m_language = format.language != null ? format.language : "";
|
||||||
audioTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate;
|
audioTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate;
|
||||||
audioTrack.m_isSelected = isTrackSelected(selection, group, 0 );
|
audioTrack.m_isSelected = isTrackSelected(selection, group, 0);
|
||||||
audioTracks.add(audioTrack);
|
audioTracks.add(audioTrack);
|
||||||
}
|
}
|
||||||
return audioTracks;
|
return audioTracks;
|
||||||
@ -1229,7 +1281,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
return this.getVideoTrackInfoFromManifest(0);
|
return this.getVideoTrackInfoFromManifest(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need retry count to in case where minefest request fails from poor network conditions
|
// We need retry count to in case where minefest request fails from poor network
|
||||||
|
// conditions
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private ArrayList<VideoTrack> getVideoTrackInfoFromManifest(int retryCount) {
|
private ArrayList<VideoTrack> getVideoTrackInfoFromManifest(int retryCount) {
|
||||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
@ -1244,18 +1297,20 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
public ArrayList<VideoTrack> call() throws Exception {
|
public ArrayList<VideoTrack> call() throws Exception {
|
||||||
ArrayList<VideoTrack> videoTracks = new ArrayList<>();
|
ArrayList<VideoTrack> videoTracks = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri);
|
DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri);
|
||||||
int periodCount = manifest.getPeriodCount();
|
int periodCount = manifest.getPeriodCount();
|
||||||
for (int i = 0; i < periodCount; i++) {
|
for (int i = 0; i < periodCount; i++) {
|
||||||
Period period = manifest.getPeriod(i);
|
Period period = manifest.getPeriod(i);
|
||||||
for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets.size(); adaptationIndex++) {
|
for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets
|
||||||
|
.size(); adaptationIndex++) {
|
||||||
AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex);
|
AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex);
|
||||||
if (adaptation.type != C.TRACK_TYPE_VIDEO) {
|
if (adaptation.type != C.TRACK_TYPE_VIDEO) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean hasFoundContentPeriod = false;
|
boolean hasFoundContentPeriod = false;
|
||||||
for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) {
|
for (int representationIndex = 0; representationIndex < adaptation.representations
|
||||||
|
.size(); representationIndex++) {
|
||||||
Representation representation = adaptation.representations.get(representationIndex);
|
Representation representation = adaptation.representations.get(representationIndex);
|
||||||
Format format = representation.format;
|
Format format = representation.format;
|
||||||
if (isFormatSupported(format)) {
|
if (isFormatSupported(format)) {
|
||||||
@ -1268,7 +1323,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
videoTrack.m_height = format.height == Format.NO_VALUE ? 0 : format.height;
|
videoTrack.m_height = format.height == Format.NO_VALUE ? 0 : format.height;
|
||||||
videoTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate;
|
videoTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate;
|
||||||
videoTrack.m_codecs = format.codecs != null ? format.codecs : "";
|
videoTrack.m_codecs = format.codecs != null ? format.codecs : "";
|
||||||
videoTrack.m_trackId = format.id == null ? String.valueOf(representationIndex) : format.id;
|
videoTrack.m_trackId = format.id == null ? String.valueOf(representationIndex)
|
||||||
|
: format.id;
|
||||||
videoTracks.add(videoTrack);
|
videoTracks.add(videoTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1277,7 +1333,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {}
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1289,7 +1346,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
es.shutdown();
|
es.shutdown();
|
||||||
return results;
|
return results;
|
||||||
} catch (Exception e) {}
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1305,7 +1363,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
return textTracks;
|
return textTracks;
|
||||||
}
|
}
|
||||||
TrackSelectionArray selectionArray = player.getCurrentTrackSelections();
|
TrackSelectionArray selectionArray = player.getCurrentTrackSelections();
|
||||||
TrackSelection selection = selectionArray.get( C.TRACK_TYPE_VIDEO );
|
TrackSelection selection = selectionArray.get(C.TRACK_TYPE_VIDEO);
|
||||||
TrackGroupArray groups = info.getTrackGroups(index);
|
TrackGroupArray groups = info.getTrackGroups(index);
|
||||||
|
|
||||||
for (int i = 0; i < groups.length; ++i) {
|
for (int i = 0; i < groups.length; ++i) {
|
||||||
@ -1317,7 +1375,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
textTrack.m_title = format.id != null ? format.id : "";
|
textTrack.m_title = format.id != null ? format.id : "";
|
||||||
textTrack.m_mimeType = format.sampleMimeType;
|
textTrack.m_mimeType = format.sampleMimeType;
|
||||||
textTrack.m_language = format.language != null ? format.language : "";
|
textTrack.m_language = format.language != null ? format.language : "";
|
||||||
textTrack.m_isSelected = isTrackSelected(selection, group, 0 );
|
textTrack.m_isSelected = isTrackSelected(selection, group, 0);
|
||||||
textTracks.add(textTrack);
|
textTracks.add(textTrack);
|
||||||
}
|
}
|
||||||
return textTracks;
|
return textTracks;
|
||||||
@ -1339,17 +1397,21 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
@Override
|
@Override
|
||||||
public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
|
public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
|
||||||
if (playerNeedsSource) {
|
if (playerNeedsSource) {
|
||||||
// This will only occur if the user has performed a seek whilst in the error state. Update the
|
// This will only occur if the user has performed a seek whilst in the error
|
||||||
// resume position so that if the user then retries, playback will resume from the position to
|
// state. Update the
|
||||||
|
// resume position so that if the user then retries, playback will resume from
|
||||||
|
// the position to
|
||||||
// which they seeked.
|
// which they seeked.
|
||||||
updateResumePosition();
|
updateResumePosition();
|
||||||
}
|
}
|
||||||
if (isUsingContentResolution) {
|
if (isUsingContentResolution) {
|
||||||
// Discontinuity events might have a different track list so we update the selected track
|
// Discontinuity events might have a different track list so we update the
|
||||||
|
// selected track
|
||||||
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
selectTrackWhenReady = true;
|
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_AUTO_TRANSITION
|
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
|
||||||
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
||||||
@ -1369,7 +1431,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
||||||
seekTime = C.TIME_UNSET;
|
seekTime = C.TIME_UNSET;
|
||||||
if (isUsingContentResolution) {
|
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
|
// 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);
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1410,14 +1473,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
String errorString = "ExoPlaybackException: " + PlaybackException.getErrorCodeName(e.errorCode);
|
String errorString = "ExoPlaybackException: " + PlaybackException.getErrorCodeName(e.errorCode);
|
||||||
String errorCode = "2" + String.valueOf(e.errorCode);
|
String errorCode = "2" + String.valueOf(e.errorCode);
|
||||||
boolean needsReInitialization = false;
|
boolean needsReInitialization = false;
|
||||||
switch(e.errorCode) {
|
switch (e.errorCode) {
|
||||||
case PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED:
|
case PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED:
|
||||||
case PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED:
|
case PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED:
|
||||||
case PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED:
|
case PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED:
|
||||||
case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR:
|
case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR:
|
||||||
case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED:
|
case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED:
|
||||||
if (!hasDrmFailed) {
|
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
|
// 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;
|
hasDrmFailed = true;
|
||||||
playerNeedsSource = true;
|
playerNeedsSource = true;
|
||||||
updateResumePosition();
|
updateResumePosition();
|
||||||
@ -1472,9 +1536,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.srcUri = uri;
|
this.srcUri = uri;
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
this.requestHeaders = headers;
|
this.requestHeaders = headers;
|
||||||
this.mediaDataSourceFactory =
|
this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext,
|
||||||
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter,
|
bandwidthMeter,
|
||||||
this.requestHeaders);
|
this.requestHeaders);
|
||||||
|
|
||||||
if (!isSourceEqual) {
|
if (!isSourceEqual) {
|
||||||
reloadSource();
|
reloadSource();
|
||||||
@ -1562,7 +1626,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedTrack(int trackType, String type, Dynamic value) {
|
public void setSelectedTrack(int trackType, String type, Dynamic value) {
|
||||||
if (player == null) return;
|
if (player == null)
|
||||||
|
return;
|
||||||
int rendererIndex = getTrackRendererIndex(trackType);
|
int rendererIndex = getTrackRendererIndex(trackType);
|
||||||
if (rendererIndex == C.INDEX_UNSET) {
|
if (rendererIndex == C.INDEX_UNSET) {
|
||||||
return;
|
return;
|
||||||
@ -1621,20 +1686,23 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
usingExactMatch = true;
|
usingExactMatch = true;
|
||||||
break;
|
break;
|
||||||
} else if (isUsingContentResolution) {
|
} 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
|
// 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 (closestFormat != null) {
|
||||||
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) {
|
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height)
|
||||||
|
&& format.height < height) {
|
||||||
// Higher quality match
|
// Higher quality match
|
||||||
closestFormat = format;
|
closestFormat = format;
|
||||||
closestTrackIndex = j;
|
closestTrackIndex = j;
|
||||||
}
|
}
|
||||||
} else if(format.height < height) {
|
} else if (format.height < height) {
|
||||||
closestFormat = format;
|
closestFormat = format;
|
||||||
closestTrackIndex = j;
|
closestTrackIndex = j;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This is a fallback if the new period contains only higher resolutions than the user has selected
|
// This is a fallback if the new period contains only higher resolutions than
|
||||||
|
// the user has selected
|
||||||
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
|
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
|
||||||
// No close match found - so we pick the lowest quality
|
// No close match found - so we pick the lowest quality
|
||||||
int minHeight = Integer.MAX_VALUE;
|
int minHeight = Integer.MAX_VALUE;
|
||||||
@ -1656,8 +1724,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
} 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
|
||||||
CaptioningManager captioningManager
|
CaptioningManager captioningManager = (CaptioningManager) themedReactContext
|
||||||
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
|
.getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
if (captioningManager != null && captioningManager.isEnabled()) {
|
if (captioningManager != null && captioningManager.isEnabled()) {
|
||||||
groupIndex = getGroupIndexForDefaultLocale(groups);
|
groupIndex = getGroupIndexForDefaultLocale(groups);
|
||||||
}
|
}
|
||||||
@ -1688,7 +1756,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
// With only one tracks we can't remove any tracks so attempt to play it anyway
|
// With only one tracks we can't remove any tracks so attempt to play it anyway
|
||||||
tracks = allTracks;
|
tracks = allTracks;
|
||||||
} else {
|
} else {
|
||||||
tracks = new ArrayList<>(supportedFormatLength + 1);
|
tracks = new ArrayList<>(supportedFormatLength + 1);
|
||||||
for (int k = 0; k < allTracks.size(); k++) {
|
for (int k = 0; k < allTracks.size(); k++) {
|
||||||
Format format = group.getFormat(k);
|
Format format = group.getFormat(k);
|
||||||
if (isFormatSupported(format)) {
|
if (isFormatSupported(format)) {
|
||||||
@ -1707,11 +1775,11 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks);
|
TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks);
|
||||||
|
|
||||||
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
|
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.setRendererDisabled(rendererIndex, false)
|
.setRendererDisabled(rendererIndex, false)
|
||||||
.clearOverridesOfType(selectionOverride.getType())
|
.clearOverridesOfType(selectionOverride.getType())
|
||||||
.addOverride(selectionOverride)
|
.addOverride(selectionOverride)
|
||||||
.build();
|
.build();
|
||||||
trackSelector.setParameters(selectionParameters);
|
trackSelector.setParameters(selectionParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1735,7 +1803,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1789,6 +1857,21 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAudioOutput(AudioOutput output) {
|
||||||
|
if (audioOutput != output && player != null) {
|
||||||
|
this.audioOutput = output;
|
||||||
|
int usage = Util.getAudioUsageForStreamType(audioOutput.streamType);
|
||||||
|
int contentType = Util.getAudioContentTypeForStreamType(audioOutput.streamType);
|
||||||
|
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(usage).setContentType(contentType)
|
||||||
|
.build();
|
||||||
|
player.setAudioAttributes(audioAttributes, false);
|
||||||
|
AudioManager audioManager = (AudioManager) themedReactContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
audioManager.setMode(
|
||||||
|
audioOutput == AudioOutput.SPEAKER ? AudioManager.MODE_NORMAL : AudioManager.MODE_IN_COMMUNICATION);
|
||||||
|
audioManager.setSpeakerphoneOn(audioOutput == AudioOutput.SPEAKER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setVolumeModifier(float volume) {
|
public void setVolumeModifier(float volume) {
|
||||||
audioVolume = volume;
|
audioVolume = volume;
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
@ -1843,10 +1926,11 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
Runtime runtime = Runtime.getRuntime();
|
Runtime runtime = Runtime.getRuntime();
|
||||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
long freeMemory = runtime.maxMemory() - usedMemory;
|
long freeMemory = runtime.maxMemory() - usedMemory;
|
||||||
long reserveMemory = (long)minBackBufferMemoryReservePercent * runtime.maxMemory();
|
long reserveMemory = (long) minBackBufferMemoryReservePercent * runtime.maxMemory();
|
||||||
if (reserveMemory > freeMemory) {
|
if (reserveMemory > freeMemory) {
|
||||||
// We don't have enough memory in reserve so we will
|
// 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!");
|
Log.w("ExoPlayer Warning",
|
||||||
|
"Not enough reserve memory, setting back buffer to 0ms to reduce memory pressure!");
|
||||||
this.backBufferDurationMs = 0;
|
this.backBufferDurationMs = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1854,7 +1938,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setContentStartTime(int contentStartTime) {
|
public void setContentStartTime(int contentStartTime) {
|
||||||
this.contentStartTime = (long)contentStartTime;
|
this.contentStartTime = (long) contentStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDisableBuffering(boolean disableBuffering) {
|
public void setDisableBuffering(boolean disableBuffering) {
|
||||||
@ -1865,7 +1949,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (playerControlView != null) {
|
if (playerControlView != null) {
|
||||||
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
|
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
//Handling the fullScreenButton click event
|
// Handling the fullScreenButton click event
|
||||||
if (isFullscreen && fullScreenPlayerView != null && !fullScreenPlayerView.isShowing()) {
|
if (isFullscreen && fullScreenPlayerView != null && !fullScreenPlayerView.isShowing()) {
|
||||||
fullScreenButton.setVisibility(GONE);
|
fullScreenButton.setVisibility(GONE);
|
||||||
} else {
|
} else {
|
||||||
@ -1924,7 +2008,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
eventEmitter.fullscreenDidDismiss();
|
eventEmitter.fullscreenDidDismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// need to be done at the end to avoid hiding fullscreen control button when fullScreenPlayerView is shown
|
// need to be done at the end to avoid hiding fullscreen control button when
|
||||||
|
// fullScreenPlayerView is shown
|
||||||
updateFullScreenButtonVisbility();
|
updateFullScreenButtonVisbility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1941,7 +2026,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
exoPlayerView.setHideShutterView(hideShutterView);
|
exoPlayerView.setHideShutterView(hideShutterView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs, double newMaxHeapAllocationPercent, double newMinBackBufferMemoryReservePercent, double newMinBufferMemoryReservePercent) {
|
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;
|
||||||
@ -1957,15 +2044,14 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.drmUUID = drmType;
|
this.drmUUID = drmType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDrmLicenseUrl(String licenseUrl){
|
public void setDrmLicenseUrl(String licenseUrl) {
|
||||||
this.drmLicenseUrl = licenseUrl;
|
this.drmLicenseUrl = licenseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDrmLicenseHeader(String[] header){
|
public void setDrmLicenseHeader(String[] header) {
|
||||||
this.drmLicenseHeader = header;
|
this.drmLicenseHeader = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
|
public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
Log.d("DRM Info", "onDrmKeysLoaded");
|
Log.d("DRM Info", "onDrmKeysLoaded");
|
||||||
@ -1990,7 +2076,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
/**
|
/**
|
||||||
* Handling controls prop
|
* Handling controls prop
|
||||||
*
|
*
|
||||||
* @param controls Controls prop, if true enable controls, if false disable them
|
* @param controls Controls prop, if true enable controls, if false disable them
|
||||||
*/
|
*/
|
||||||
public void setControls(boolean controls) {
|
public void setControls(boolean controls) {
|
||||||
this.controls = controls;
|
this.controls = controls;
|
||||||
|
@ -49,6 +49,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_TEXT_TRACKS = "textTracks";
|
private static final String PROP_TEXT_TRACKS = "textTracks";
|
||||||
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_AUDIO_OUTPUT = "audioOutput";
|
||||||
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_BACK_BUFFER_DURATION_MS = "backBufferDurationMs";
|
||||||
private static final String PROP_BUFFER_CONFIG = "bufferConfig";
|
private static final String PROP_BUFFER_CONFIG = "bufferConfig";
|
||||||
@ -119,8 +120,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
"ScaleNone", Integer.toString(ResizeMode.RESIZE_MODE_FIT),
|
"ScaleNone", Integer.toString(ResizeMode.RESIZE_MODE_FIT),
|
||||||
"ScaleAspectFit", Integer.toString(ResizeMode.RESIZE_MODE_FIT),
|
"ScaleAspectFit", Integer.toString(ResizeMode.RESIZE_MODE_FIT),
|
||||||
"ScaleToFill", Integer.toString(ResizeMode.RESIZE_MODE_FILL),
|
"ScaleToFill", Integer.toString(ResizeMode.RESIZE_MODE_FILL),
|
||||||
"ScaleAspectFill", Integer.toString(ResizeMode.RESIZE_MODE_CENTER_CROP)
|
"ScaleAspectFill", Integer.toString(ResizeMode.RESIZE_MODE_CENTER_CROP));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_DRM)
|
@ReactProp(name = PROP_DRM)
|
||||||
@ -168,16 +168,14 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int identifier = context.getResources().getIdentifier(
|
int identifier = context.getResources().getIdentifier(
|
||||||
uriString,
|
uriString,
|
||||||
"drawable",
|
"drawable",
|
||||||
context.getPackageName()
|
context.getPackageName());
|
||||||
);
|
|
||||||
if (identifier == 0) {
|
if (identifier == 0) {
|
||||||
identifier = context.getResources().getIdentifier(
|
identifier = context.getResources().getIdentifier(
|
||||||
uriString,
|
uriString,
|
||||||
"raw",
|
"raw",
|
||||||
context.getPackageName()
|
context.getPackageName());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (identifier > 0) {
|
if (identifier > 0) {
|
||||||
Uri srcUri = RawResourceDataSource.buildRawResourceUri(identifier);
|
Uri srcUri = RawResourceDataSource.buildRawResourceUri(identifier);
|
||||||
@ -201,7 +199,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
videoView.setAdTagUrl(adTagUrl);
|
videoView.setAdTagUrl(adTagUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ReactProp(name = PROP_RESIZE_MODE)
|
@ReactProp(name = PROP_RESIZE_MODE)
|
||||||
public void setResizeMode(final ReactExoplayerView videoView, final String resizeModeOrdinalString) {
|
public void setResizeMode(final ReactExoplayerView videoView, final String resizeModeOrdinalString) {
|
||||||
videoView.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString));
|
videoView.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString));
|
||||||
@ -213,55 +210,62 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK, defaultBoolean = false)
|
@ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK, defaultBoolean = false)
|
||||||
public void setPreventsDisplaySleepDuringVideoPlayback(final ReactExoplayerView videoView, final boolean preventsSleep) {
|
public void setPreventsDisplaySleepDuringVideoPlayback(final ReactExoplayerView videoView,
|
||||||
|
final boolean preventsSleep) {
|
||||||
videoView.setPreventsDisplaySleepDuringVideoPlayback(preventsSleep);
|
videoView.setPreventsDisplaySleepDuringVideoPlayback(preventsSleep);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_SELECTED_VIDEO_TRACK)
|
@ReactProp(name = PROP_SELECTED_VIDEO_TRACK)
|
||||||
public void setSelectedVideoTrack(final ReactExoplayerView videoView,
|
public void setSelectedVideoTrack(final ReactExoplayerView videoView,
|
||||||
@Nullable ReadableMap selectedVideoTrack) {
|
@Nullable ReadableMap selectedVideoTrack) {
|
||||||
String typeString = null;
|
String typeString = null;
|
||||||
Dynamic value = null;
|
Dynamic value = null;
|
||||||
if (selectedVideoTrack != null) {
|
if (selectedVideoTrack != null) {
|
||||||
typeString = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_TYPE)
|
typeString = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_TYPE)
|
||||||
? selectedVideoTrack.getString(PROP_SELECTED_VIDEO_TRACK_TYPE) : null;
|
? selectedVideoTrack.getString(PROP_SELECTED_VIDEO_TRACK_TYPE)
|
||||||
|
: null;
|
||||||
value = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_VALUE)
|
value = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_VALUE)
|
||||||
? selectedVideoTrack.getDynamic(PROP_SELECTED_VIDEO_TRACK_VALUE) : null;
|
? selectedVideoTrack.getDynamic(PROP_SELECTED_VIDEO_TRACK_VALUE)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
videoView.setSelectedVideoTrack(typeString, value);
|
videoView.setSelectedVideoTrack(typeString, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_SELECTED_AUDIO_TRACK)
|
@ReactProp(name = PROP_SELECTED_AUDIO_TRACK)
|
||||||
public void setSelectedAudioTrack(final ReactExoplayerView videoView,
|
public void setSelectedAudioTrack(final ReactExoplayerView videoView,
|
||||||
@Nullable ReadableMap selectedAudioTrack) {
|
@Nullable ReadableMap selectedAudioTrack) {
|
||||||
String typeString = null;
|
String typeString = null;
|
||||||
Dynamic value = null;
|
Dynamic value = null;
|
||||||
if (selectedAudioTrack != null) {
|
if (selectedAudioTrack != null) {
|
||||||
typeString = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_TYPE)
|
typeString = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_TYPE)
|
||||||
? selectedAudioTrack.getString(PROP_SELECTED_AUDIO_TRACK_TYPE) : null;
|
? selectedAudioTrack.getString(PROP_SELECTED_AUDIO_TRACK_TYPE)
|
||||||
|
: null;
|
||||||
value = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_VALUE)
|
value = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_VALUE)
|
||||||
? selectedAudioTrack.getDynamic(PROP_SELECTED_AUDIO_TRACK_VALUE) : null;
|
? selectedAudioTrack.getDynamic(PROP_SELECTED_AUDIO_TRACK_VALUE)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
videoView.setSelectedAudioTrack(typeString, value);
|
videoView.setSelectedAudioTrack(typeString, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_SELECTED_TEXT_TRACK)
|
@ReactProp(name = PROP_SELECTED_TEXT_TRACK)
|
||||||
public void setSelectedTextTrack(final ReactExoplayerView videoView,
|
public void setSelectedTextTrack(final ReactExoplayerView videoView,
|
||||||
@Nullable ReadableMap selectedTextTrack) {
|
@Nullable ReadableMap selectedTextTrack) {
|
||||||
String typeString = null;
|
String typeString = null;
|
||||||
Dynamic value = null;
|
Dynamic value = null;
|
||||||
if (selectedTextTrack != null) {
|
if (selectedTextTrack != null) {
|
||||||
typeString = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_TYPE)
|
typeString = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_TYPE)
|
||||||
? selectedTextTrack.getString(PROP_SELECTED_TEXT_TRACK_TYPE) : null;
|
? selectedTextTrack.getString(PROP_SELECTED_TEXT_TRACK_TYPE)
|
||||||
|
: null;
|
||||||
value = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_VALUE)
|
value = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_VALUE)
|
||||||
? selectedTextTrack.getDynamic(PROP_SELECTED_TEXT_TRACK_VALUE) : null;
|
? selectedTextTrack.getDynamic(PROP_SELECTED_TEXT_TRACK_VALUE)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
videoView.setSelectedTextTrack(typeString, value);
|
videoView.setSelectedTextTrack(typeString, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_TEXT_TRACKS)
|
@ReactProp(name = PROP_TEXT_TRACKS)
|
||||||
public void setPropTextTracks(final ReactExoplayerView videoView,
|
public void setPropTextTracks(final ReactExoplayerView videoView,
|
||||||
@Nullable ReadableArray textTracks) {
|
@Nullable ReadableArray textTracks) {
|
||||||
videoView.setTextTracks(textTracks);
|
videoView.setTextTracks(textTracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +279,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
videoView.setMutedModifier(muted);
|
videoView.setMutedModifier(muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = PROP_AUDIO_OUTPUT)
|
||||||
|
public void setAudioOutput(final ReactExoplayerView videoView, final String audioOutput) {
|
||||||
|
videoView.setAudioOutput(ReactExoplayerView.AudioOutput.get(audioOutput));
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f)
|
@ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f)
|
||||||
public void setVolume(final ReactExoplayerView videoView, final float volume) {
|
public void setVolume(final ReactExoplayerView videoView, final float volume) {
|
||||||
videoView.setVolumeModifier(volume);
|
videoView.setVolumeModifier(volume);
|
||||||
@ -387,20 +396,30 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
|
|
||||||
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;
|
||||||
maxBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_BUFFER_MS)
|
maxBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_BUFFER_MS)
|
||||||
? bufferConfig.getInt(PROP_BUFFER_CONFIG_MAX_BUFFER_MS) : maxBufferMs;
|
? bufferConfig.getInt(PROP_BUFFER_CONFIG_MAX_BUFFER_MS)
|
||||||
|
: maxBufferMs;
|
||||||
bufferForPlaybackMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS)
|
bufferForPlaybackMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS)
|
||||||
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) : bufferForPlaybackMs;
|
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS)
|
||||||
bufferForPlaybackAfterRebufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
: bufferForPlaybackMs;
|
||||||
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) : bufferForPlaybackAfterRebufferMs;
|
bufferForPlaybackAfterRebufferMs = bufferConfig
|
||||||
|
.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
||||||
|
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
|
||||||
|
: bufferForPlaybackAfterRebufferMs;
|
||||||
maxHeapAllocationPercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT)
|
maxHeapAllocationPercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT)
|
||||||
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) : maxHeapAllocationPercent;
|
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT)
|
||||||
minBackBufferMemoryReservePercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT)
|
: maxHeapAllocationPercent;
|
||||||
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) : minBackBufferMemoryReservePercent;
|
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)
|
minBufferMemoryReservePercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT)
|
||||||
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT) : minBufferMemoryReservePercent;
|
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT)
|
||||||
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent);
|
: minBufferMemoryReservePercent;
|
||||||
|
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs,
|
||||||
|
maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,7 +445,8 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
*
|
*
|
||||||
* @param readableMap The ReadableMap to be conveted.
|
* @param readableMap The ReadableMap to be conveted.
|
||||||
* @return A HashMap containing the data that was in the ReadableMap.
|
* @return A HashMap containing the data that was in the ReadableMap.
|
||||||
* @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java'
|
* @see 'Adapted from
|
||||||
|
* https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java'
|
||||||
*/
|
*/
|
||||||
public static Map<String, String> toStringMap(@Nullable ReadableMap readableMap) {
|
public static Map<String, String> toStringMap(@Nullable ReadableMap readableMap) {
|
||||||
if (readableMap == null)
|
if (readableMap == null)
|
||||||
|
@ -197,7 +197,7 @@ enum RCTPlayerOperations {
|
|||||||
var options:AVAudioSession.CategoryOptions? = nil
|
var options:AVAudioSession.CategoryOptions? = nil
|
||||||
|
|
||||||
if (ignoreSilentSwitch == "ignore") {
|
if (ignoreSilentSwitch == "ignore") {
|
||||||
category = AVAudioSession.Category.playback
|
category = AVAudioSession.Category.playAndRecord
|
||||||
} else if (ignoreSilentSwitch == "obey") {
|
} else if (ignoreSilentSwitch == "obey") {
|
||||||
category = AVAudioSession.Category.ambient
|
category = AVAudioSession.Category.ambient
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
private var _controls:Bool = false
|
private var _controls:Bool = false
|
||||||
|
|
||||||
/* Keep track of any modifiers, need to be applied after each play */
|
/* Keep track of any modifiers, need to be applied after each play */
|
||||||
|
private var _audioOutput: String = "speaker"
|
||||||
private var _volume:Float = 1.0
|
private var _volume:Float = 1.0
|
||||||
private var _rate:Float = 1.0
|
private var _rate:Float = 1.0
|
||||||
private var _maxBitRate:Float?
|
private var _maxBitRate:Float?
|
||||||
@ -515,6 +516,20 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
applyModifiers()
|
applyModifiers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func setAudioOutput(_ audioOutput:String) {
|
||||||
|
_audioOutput = audioOutput
|
||||||
|
do {
|
||||||
|
if audioOutput == "speaker" {
|
||||||
|
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
|
||||||
|
} else if audioOutput == "earpiece" {
|
||||||
|
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error occurred: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func setVolume(_ volume:Float) {
|
func setVolume(_ volume:Float) {
|
||||||
_volume = volume
|
_volume = volume
|
||||||
|
@ -17,6 +17,7 @@ RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(audioOutput, NSString);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(volume, float);
|
RCT_EXPORT_VIEW_PROPERTY(volume, float);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(preventsDisplaySleepDuringVideoPlayback, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(preventsDisplaySleepDuringVideoPlayback, BOOL);
|
||||||
|
Loading…
Reference in New Issue
Block a user