Allow audio output via earpiece

This commit is contained in:
Craig Martin 2023-04-02 14:02:56 -04:00
parent 05864dc64a
commit daabb91475
8 changed files with 331 additions and 198 deletions

8
API.md
View File

@ -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

View File

@ -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)

View File

@ -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']),

View File

@ -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
* *
* This is a workaround for the open bug in react-native: https://github.com/facebook/react-native/issues/17968 * @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
*/ */
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;

View File

@ -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)

View File

@ -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
} }

View File

@ -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

View File

@ -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);