Merge branch 'master' of https://github.com/react-native-video/react-native-video into feat/add_new_events_on_tracks_changed

# Conflicts:
#	android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
This commit is contained in:
olivier bouillet
2022-09-12 21:58:27 +02:00
19 changed files with 538 additions and 39 deletions

View File

@@ -33,7 +33,7 @@ repositories {
dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.17.1') {
implementation('com.google.android.exoplayer:exoplayer:2.18.1') {
exclude group: 'com.android.support'
}
@@ -41,8 +41,9 @@ dependencies {
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.core:core:1.1.0"
implementation "androidx.media:media:1.1.0"
implementation 'androidx.activity:activity:1.4.0'
implementation('com.google.android.exoplayer:extension-okhttp:2.17.1') {
implementation('com.google.android.exoplayer:extension-okhttp:2.18.1') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'

View File

@@ -0,0 +1,22 @@
package com.brentvatne;
import com.facebook.react.bridge.ReadableMap;
/*
* This file define static helpers to parse in an easier way input props
*/
public class ReactBridgeUtils {
/*
retrieve key from map as int. fallback is returned if not available
*/
static public int safeGetInt(ReadableMap map, String key, int fallback) {
return map != null && map.hasKey(key) && !map.isNull(key) ? map.getInt(key) : fallback;
}
/*
retrieve key from map as double. fallback is returned if not available
*/
static public double safeGetDouble(ReadableMap map, String key, double fallback) {
return map != null && map.hasKey(key) && !map.isNull(key) ? map.getDouble(key) : fallback;
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import androidx.core.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.SurfaceView;
import android.view.TextureView;
@@ -18,7 +19,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.SubtitleView;
@@ -100,6 +101,16 @@ public final class ExoPlayerView extends FrameLayout {
player.setVideoSurfaceView((SurfaceView) surfaceView);
}
}
public void setSubtitleStyle(SubtitleStyle style) {
// ensure we reset subtile style before reapplying it
subtitleLayout.setUserDefaultStyle();
subtitleLayout.setUserDefaultTextSize();
if (style.getFontSize() > 0) {
subtitleLayout.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, style.getFontSize());
}
subtitleLayout.setPadding(style.getPaddingLeft(), style.getPaddingTop(), style.getPaddingRight(), style.getPaddingBottom());
}
private void updateSurfaceView() {
View view;
@@ -230,7 +241,7 @@ public final class ExoPlayerView extends FrameLayout {
@Override
public void onCues(List<Cue> cues) {
subtitleLayout.onCues(cues);
subtitleLayout.setCues(cues);
}
// ExoPlayer.VideoListener implementation
@@ -284,7 +295,7 @@ public final class ExoPlayerView extends FrameLayout {
}
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
public void onTracksChanged(Tracks tracks) {
updateForCurrentTrackSelections();
}

View File

@@ -0,0 +1,80 @@
package com.brentvatne.exoplayer;
import android.app.Dialog;
import android.content.Context;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import androidx.activity.OnBackPressedCallback;
import com.google.android.exoplayer2.ui.PlayerControlView;
public class FullScreenPlayerView extends Dialog {
private final PlayerControlView playerControlView;
private final ExoPlayerView exoPlayerView;
private ViewGroup parent;
private final FrameLayout containerView;
private final OnBackPressedCallback onBackPressedCallback;
public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, PlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) {
super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
this.playerControlView = playerControlView;
this.exoPlayerView = exoPlayerView;
this.onBackPressedCallback = onBackPressedCallback;
containerView = new FrameLayout(context);
setContentView(containerView, generateDefaultLayoutParams());
}
@Override
public void onBackPressed() {
super.onBackPressed();
onBackPressedCallback.handleOnBackPressed();
}
@Override
protected void onStart() {
parent = (FrameLayout)(exoPlayerView.getParent());
parent.removeView(exoPlayerView);
containerView.addView(exoPlayerView, generateDefaultLayoutParams());
if (playerControlView != null) {
ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
imageButton.setImageResource(com.google.android.exoplayer2.ui.R.drawable.exo_icon_fullscreen_exit);
imageButton.setContentDescription(getContext().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_fullscreen_exit_description));
parent.removeView(playerControlView);
containerView.addView(playerControlView, generateDefaultLayoutParams());
}
super.onStart();
}
@Override
protected void onStop() {
containerView.removeView(exoPlayerView);
parent.addView(exoPlayerView, generateDefaultLayoutParams());
if (playerControlView != null) {
ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
imageButton.setImageResource(com.google.android.exoplayer2.ui.R.drawable.exo_icon_fullscreen_enter);
imageButton.setContentDescription(getContext().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_fullscreen_enter_description));
containerView.removeView(playerControlView);
parent.addView(playerControlView, generateDefaultLayoutParams());
}
parent.requestLayout();
parent = null;
super.onStop();
}
private FrameLayout.LayoutParams generateDefaultLayoutParams() {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
);
layoutParams.setMargins(0, 0, 0, 0);
return layoutParams;
}
}

View File

@@ -18,6 +18,7 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import androidx.annotation.WorkerThread;
import androidx.activity.OnBackPressedCallback;
import com.brentvatne.common.Track;
import com.brentvatne.common.VideoTrack;
@@ -39,8 +40,9 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
@@ -129,6 +131,7 @@ class ReactExoplayerView extends FrameLayout implements
private Player.Listener eventListener;
private ExoPlayerView exoPlayerView;
private FullScreenPlayerView fullScreenPlayerView;
private DataSource.Factory mediaDataSourceFactory;
private ExoPlayer player;
@@ -356,7 +359,6 @@ class ReactExoplayerView extends FrameLayout implements
// Setting the player for the playerControlView
playerControlView.setPlayer(player);
playerControlView.show();
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
// Invoking onClick event for exoplayerView
@@ -388,6 +390,10 @@ class ReactExoplayerView extends FrameLayout implements
}
});
//Handling the fullScreenButton click event
ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen));
// Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player
eventListener = new Player.Listener() {
@Override
@@ -430,6 +436,7 @@ class ReactExoplayerView extends FrameLayout implements
removeViewAt(indexOfPC);
}
addView(playerControlView, 1, layoutParams);
reLayout(playerControlView);
}
/**
@@ -557,7 +564,7 @@ class ReactExoplayerView extends FrameLayout implements
private void initializePlayerCore(ReactExoplayerView self) {
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
self.trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
self.trackSelector = new DefaultTrackSelector(getContext(), videoTrackSelectionFactory);
self.trackSelector.setParameters(trackSelector.buildUponParameters()
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
@@ -653,6 +660,12 @@ class ReactExoplayerView extends FrameLayout implements
setControls(controls);
applyModifiers();
startBufferCheckTimer();
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
setFullscreen(false);
}
});
}
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
@@ -707,6 +720,8 @@ class ReactExoplayerView extends FrameLayout implements
return drmSessionManager;
}
};
} else {
drmProvider = new DefaultDrmSessionManagerProvider();
}
switch (type) {
case C.TYPE_SS:
@@ -813,7 +828,10 @@ class ReactExoplayerView extends FrameLayout implements
player.setPlayWhenReady(true);
}
} else {
player.setPlayWhenReady(false);
// ensure playback is not ENDED, else it will trigger another ended event
if (player.getPlaybackState() != Player.STATE_ENDED) {
player.setPlayWhenReady(false);
}
}
}
@@ -1290,11 +1308,10 @@ class ReactExoplayerView extends FrameLayout implements
}
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
public void onTracksChanged(Tracks tracks) {
eventEmitter.textTracks(getTextTrackInfo());
eventEmitter.audioTracks(getAudioTrackInfo());
eventEmitter.videoTracks(getVideoTrackInfo());
}
@Override
@@ -1605,10 +1622,10 @@ class ReactExoplayerView extends FrameLayout implements
TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks);
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
.buildUpon()
.setRendererDisabled(rendererIndex, false)
.setTrackSelectionOverrides(new TrackSelectionOverrides.Builder().addOverride(selectionOverride).build())
.build();
.buildUpon()
.setRendererDisabled(rendererIndex, false)
.addOverride(selectionOverride)
.build();
trackSelector.setParameters(selectionParameters);
}
@@ -1767,6 +1784,16 @@ class ReactExoplayerView extends FrameLayout implements
if (activity == null) {
return;
}
if (fullScreenPlayerView == null) {
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
setFullscreen(false);
}
});
}
Window window = activity.getWindow();
View decorView = window.getDecorView();
int uiOptions;
@@ -1780,13 +1807,24 @@ class ReactExoplayerView extends FrameLayout implements
| SYSTEM_UI_FLAG_FULLSCREEN;
}
eventEmitter.fullscreenWillPresent();
decorView.setSystemUiVisibility(uiOptions);
eventEmitter.fullscreenDidPresent();
post(() -> {
decorView.setSystemUiVisibility(uiOptions);
if (controls) {
fullScreenPlayerView.show();
}
eventEmitter.fullscreenDidPresent();
});
} else {
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
eventEmitter.fullscreenWillDismiss();
decorView.setSystemUiVisibility(uiOptions);
eventEmitter.fullscreenDidDismiss();
post(() -> {
decorView.setSystemUiVisibility(uiOptions);
if (controls) {
fullScreenPlayerView.dismiss();
reLayout(exoPlayerView);
}
eventEmitter.fullscreenDidDismiss();
});
}
}
@@ -1866,4 +1904,8 @@ class ReactExoplayerView extends FrameLayout implements
}
}
}
public void setSubtitleStyle(SubtitleStyle style) {
exoPlayerView.setSubtitleStyle(style);
}
}

View File

@@ -79,6 +79,8 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
private static final String PROP_CONTROLS = "controls";
private static final String PROP_SUBTITLE_STYLE = "subtitleStyle";
private ReactExoplayerConfig config;
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
@@ -347,6 +349,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setControls(controls);
}
@ReactProp(name = PROP_SUBTITLE_STYLE)
public void setSubtitleStyle(final ReactExoplayerView videoView, @Nullable final ReadableMap src) {
videoView.setSubtitleStyle(SubtitleStyle.parse(src));
}
@ReactProp(name = PROP_BUFFER_CONFIG)
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;

View File

@@ -0,0 +1,38 @@
package com.brentvatne.exoplayer;
import com.brentvatne.ReactBridgeUtils;
import com.facebook.react.bridge.ReadableMap;
/**
* Helper file to parse SubtitleStyle prop and build a dedicated class
*/
public class SubtitleStyle {
private static final String PROP_FONT_SIZE_TRACK = "fontSize";
private static final String PROP_PADDING_BOTTOM = "paddingBottom";
private static final String PROP_PADDING_TOP = "paddingTop";
private static final String PROP_PADDING_LEFT = "paddingLeft";
private static final String PROP_PADDING_RIGHT = "paddingRight";
private int fontSize = -1;
private int paddingLeft = 0;
private int paddingRight = 0;
private int paddingTop = 0;
private int paddingBottom = 0;
private SubtitleStyle() {}
int getFontSize() {return fontSize;}
int getPaddingBottom() {return paddingBottom;}
int getPaddingTop() {return paddingTop;}
int getPaddingLeft() {return paddingLeft;}
int getPaddingRight() {return paddingRight;}
public static SubtitleStyle parse(ReadableMap src) {
SubtitleStyle subtitleStyle = new SubtitleStyle();
subtitleStyle.fontSize = ReactBridgeUtils.safeGetInt(src, PROP_FONT_SIZE_TRACK, -1);
subtitleStyle.paddingBottom = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_BOTTOM, 0);
subtitleStyle.paddingTop = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_TOP, 0);
subtitleStyle.paddingLeft = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_LEFT, 0);
subtitleStyle.paddingRight = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_RIGHT, 0);
return subtitleStyle;
}
}

View File

@@ -25,7 +25,9 @@ public class ReactVideoPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
return Collections.singletonList(
new VideoDecoderPropertiesModule(reactContext)
);
}
// Deprecated RN 0.47

View File

@@ -0,0 +1,107 @@
package com.brentvatne.react;
import android.annotation.SuppressLint;
import android.media.MediaCodecList;
import android.media.MediaDrm;
import android.media.MediaFormat;
import android.media.UnsupportedSchemeException;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.UUID;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class VideoDecoderPropertiesModule extends ReactContextBaseJavaModule {
ReactApplicationContext reactContext;
@NonNull
@Override
public String getName() {
return "VideoDecoderProperties";
}
@SuppressLint("ObsoleteSdkInt")
@ReactMethod
public void getWidevineLevel(Promise p) {
int widevineLevel = 0;
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
p.resolve(widevineLevel);
return;
}
final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
final String WIDEVINE_SECURITY_LEVEL_1 = "L1";
final String WIDEVINE_SECURITY_LEVEL_2 = "L2";
final String WIDEVINE_SECURITY_LEVEL_3 = "L3";
final String SECURITY_LEVEL_PROPERTY = "securityLevel";
String securityProperty = null;
try {
MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID);
securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY);
} catch (UnsupportedSchemeException e) {
e.printStackTrace();
}
if (securityProperty == null) {
p.resolve(widevineLevel);
return;
}
switch (securityProperty) {
case WIDEVINE_SECURITY_LEVEL_1: {
widevineLevel = 1;
break;
}
case WIDEVINE_SECURITY_LEVEL_2: {
widevineLevel = 2;
break;
}
case WIDEVINE_SECURITY_LEVEL_3: {
widevineLevel = 3;
break;
}
default: {
// widevineLevel 0
break;
}
}
p.resolve(widevineLevel);
}
@SuppressLint("ObsoleteSdkInt")
@ReactMethod
public void isCodecSupported(String mimeType, int width, int height, Promise p) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
p.resolve(false);
return;
}
MediaCodecList mRegularCodecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
String codecName = mRegularCodecs.findDecoderForFormat(format);
if (codecName == null) {
p.resolve(false);
} else {
p.resolve(true);
}
}
@ReactMethod
public void isHEVCSupported(Promise p) {
isCodecSupported("video/hevc", 1920, 1080, p);
}
public VideoDecoderPropertiesModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
}

View File

@@ -71,6 +71,14 @@
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
<ImageButton
android:id="@id/exo_fullscreen"
style="@style/ExoMediaButton.FullScreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="4dp"
android:scaleType="fitCenter" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ExoMediaButton.FullScreen">
<item name="android:src">@drawable/exo_icon_fullscreen_enter</item>
<item name="android:contentDescription">@string/exo_controls_fullscreen_enter_description</item>
</style>
</resources>