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

# Conflicts:
#	API.md
#	android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
#	android/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java
#	examples/basic/yarn.lock
This commit is contained in:
olivier bouillet
2023-01-02 22:59:10 +01:00
86 changed files with 6030 additions and 5373 deletions

View File

@@ -45,5 +45,7 @@ dependencies {
implementation('com.google.android.exoplayer:extension-okhttp:2.18.1') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
implementation 'com.google.android.exoplayer:extension-ima:2.18.1'
implementation "com.squareup.okhttp3:okhttp:" + '$OKHTTP_VERSION'
}

View File

@@ -4,7 +4,6 @@ import android.annotation.TargetApi;
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;
@@ -22,13 +21,14 @@ import com.google.android.exoplayer2.Timeline;
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.AdViewProvider;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.video.VideoSize;
import java.util.List;
@TargetApi(16)
public final class ExoPlayerView extends FrameLayout {
public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
private View surfaceView;
private final View shutterView;
@@ -38,6 +38,7 @@ public final class ExoPlayerView extends FrameLayout {
private ExoPlayer player;
private Context context;
private ViewGroup.LayoutParams layoutParams;
private final FrameLayout adOverlayFrameLayout;
private boolean useTextureView = true;
private boolean useSecureView = false;
@@ -80,8 +81,11 @@ public final class ExoPlayerView extends FrameLayout {
updateSurfaceView();
adOverlayFrameLayout = new FrameLayout(context);
layout.addView(shutterView, 1, layoutParams);
layout.addView(subtitleLayout, 2, layoutParams);
layout.addView(adOverlayFrameLayout, 3, layoutParams);
addViewInLayout(layout, 0, aspectRatioParams);
}
@@ -139,6 +143,19 @@ public final class ExoPlayerView extends FrameLayout {
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
}
@Override
public void requestLayout() {
super.requestLayout();
post(measureAndLayout);
}
// AdsLoader.AdViewProvider implementation.
@Override
public ViewGroup getAdViewGroup() {
return Assertions.checkNotNull(adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
}
/**
* Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the
* player will be called and previous

View File

@@ -1,5 +1,10 @@
package com.brentvatne.exoplayer;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_DASH;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_HLS;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_OTHER;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_SS;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
@@ -30,6 +35,7 @@ import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.google.ads.interactivemedia.v3.api.AdEvent;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
@@ -68,11 +74,11 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides;
import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource;
@@ -84,6 +90,11 @@ import com.google.android.exoplayer2.source.dash.manifest.Period;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.common.collect.ImmutableList;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
@@ -107,7 +118,8 @@ class ReactExoplayerView extends FrameLayout implements
BandwidthMeter.EventListener,
BecomingNoisyListener,
AudioManager.OnAudioFocusChangeListener,
DrmSessionEventListener {
DrmSessionEventListener,
AdEvent.AdEventListener {
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1;
public static final double DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE = 0;
@@ -132,6 +144,7 @@ class ReactExoplayerView extends FrameLayout implements
private ExoPlayerView exoPlayerView;
private FullScreenPlayerView fullScreenPlayerView;
private ImaAdsLoader adsLoader;
private DataSource.Factory mediaDataSourceFactory;
private ExoPlayer player;
@@ -191,6 +204,7 @@ class ReactExoplayerView extends FrameLayout implements
private String drmLicenseUrl = null;
private String[] drmLicenseHeader = null;
private boolean controls;
private Uri adTagUrl;
// \ End props
// React
@@ -209,6 +223,9 @@ class ReactExoplayerView extends FrameLayout implements
switch (msg.what) {
case SHOW_PROGRESS:
if (player != null) {
if (playerControlView != null && isPlayingAd() && controls) {
playerControlView.hide();
}
long pos = player.getCurrentPosition();
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
long duration = player.getDuration();
@@ -251,6 +268,9 @@ class ReactExoplayerView extends FrameLayout implements
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
}
private boolean isPlayingAd() {
return player != null && player.isPlayingAd();
}
@Override
public void setId(int id) {
@@ -360,6 +380,15 @@ class ReactExoplayerView extends FrameLayout implements
playerControlView = new PlayerControlView(getContext());
}
if (fullScreenPlayerView == null) {
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
setFullscreen(false);
}
});
}
// Setting the player for the playerControlView
playerControlView.setPlayer(player);
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
@@ -368,7 +397,9 @@ class ReactExoplayerView extends FrameLayout implements
exoPlayerView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
togglePlayerControlVisibility();
if (!isPlayingAd()) {
togglePlayerControlVisibility();
}
}
});
@@ -394,8 +425,9 @@ class ReactExoplayerView extends FrameLayout implements
});
//Handling the fullScreenButton click event
ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen));
updateFullScreenButtonVisbility();
// Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player
eventListener = new Player.Listener() {
@@ -409,7 +441,6 @@ class ReactExoplayerView extends FrameLayout implements
if (pauseButton != null && pauseButton.getVisibility() == GONE) {
pauseButton.setVisibility(INVISIBLE);
}
reLayout(playPauseControlContainer);
//Remove this eventListener once its executed. since UI will work fine once after the reLayout is done
player.removeListener(eventListener);
@@ -429,7 +460,7 @@ class ReactExoplayerView extends FrameLayout implements
* Adding Player control to the frame layout
*/
private void addPlayerControl() {
if(player == null) return;
if(playerControlView == null) return;
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
@@ -534,6 +565,12 @@ class ReactExoplayerView extends FrameLayout implements
eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003");
return;
}
if (activity == null) {
Log.e("ExoPlayer Exception", "Failed to initialize Player!");
eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001");
return;
}
// Initialize handler to run on the main thread
activity.runOnUiThread(new Runnable() {
@@ -586,13 +623,22 @@ class ReactExoplayerView extends FrameLayout implements
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(getContext())
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
// Create an AdsLoader.
adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build();
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
player = new ExoPlayer.Builder(getContext(), renderersFactory)
.setTrackSelector(self.trackSelector)
.setBandwidthMeter(bandwidthMeter)
.setLoadControl(loadControl)
.setMediaSourceFactory(mediaSourceFactory)
.build();
player.addListener(self);
exoPlayerView.setPlayer(player);
adsLoader.setPlayer(player);
audioBecomingNoisyReceiver.setListener(self);
bandwidthMeter.addEventListener(new Handler(), self);
setPlayWhenReady(!isPaused);
@@ -622,11 +668,26 @@ class ReactExoplayerView extends FrameLayout implements
private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) {
ArrayList<MediaSource> mediaSourceList = buildTextSources();
MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager);
MediaSource mediaSourceWithAds = null;
if (adTagUrl != null) {
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
DataSpec adTagDataSpec = new DataSpec(adTagUrl);
mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView);
}
MediaSource mediaSource;
if (mediaSourceList.size() == 0) {
mediaSource = videoSource;
if (mediaSourceWithAds != null) {
mediaSource = mediaSourceWithAds;
} else {
mediaSource = videoSource;
}
} else {
mediaSourceList.add(0, videoSource);
if (mediaSourceWithAds != null) {
mediaSourceList.add(0, mediaSourceWithAds);
} else {
mediaSourceList.add(0, videoSource);
}
MediaSource[] textSourceArray = mediaSourceList.toArray(
new MediaSource[mediaSourceList.size()]
);
@@ -663,12 +724,6 @@ 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 {
@@ -714,7 +769,17 @@ class ReactExoplayerView extends FrameLayout implements
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
: uri.getLastPathSegment());
config.setDisableDisconnectError(this.disableDisconnectError);
MediaItem mediaItem = new MediaItem.Builder().setUri(uri).build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri);
if (adTagUrl != null) {
mediaItemBuilder.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(adTagUrl).build()
);
}
MediaItem mediaItem = mediaItemBuilder.build();
DrmSessionManagerProvider drmProvider = null;
if (drmSessionManager != null) {
drmProvider = new DrmSessionManagerProvider() {
@@ -727,7 +792,7 @@ class ReactExoplayerView extends FrameLayout implements
drmProvider = new DefaultDrmSessionManagerProvider();
}
switch (type) {
case C.TYPE_SS:
case CONTENT_TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false)
@@ -735,7 +800,7 @@ class ReactExoplayerView extends FrameLayout implements
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
case C.TYPE_DASH:
case CONTENT_TYPE_DASH:
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false)
@@ -743,14 +808,14 @@ class ReactExoplayerView extends FrameLayout implements
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
case C.TYPE_HLS:
case CONTENT_TYPE_HLS:
return new HlsMediaSource.Factory(
mediaDataSourceFactory
).setDrmSessionManagerProvider(drmProvider)
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
case C.TYPE_OTHER:
case CONTENT_TYPE_OTHER:
return new ProgressiveMediaSource.Factory(
mediaDataSourceFactory
).setDrmSessionManagerProvider(drmProvider)
@@ -798,12 +863,15 @@ class ReactExoplayerView extends FrameLayout implements
private void releasePlayer() {
if (player != null) {
adsLoader.setPlayer(null);
updateResumePosition();
player.release();
player.removeListener(this);
trackSelector = null;
player = null;
}
adsLoader.release();
adsLoader = null;
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener();
@@ -995,6 +1063,7 @@ class ReactExoplayerView extends FrameLayout implements
text += "ready";
eventEmitter.ready();
onBuffering(false);
clearProgressMessageHandler(); // ensure there is no other message
startProgressHandler();
videoLoaded();
if (selectTrackWhenReady && isUsingContentResolution) {
@@ -1034,7 +1103,7 @@ class ReactExoplayerView extends FrameLayout implements
}
private void videoLoaded() {
if (loadVideoStarted) {
if (!player.isPlayingAd() && loadVideoStarted) {
loadVideoStarted = false;
if (audioTrackType != null) {
setSelectedAudioTrack(audioTrackType, audioTrackValue);
@@ -1427,6 +1496,10 @@ class ReactExoplayerView extends FrameLayout implements
mReportBandwidth = reportBandwidth;
}
public void setAdTagUrl(final Uri uri) {
adTagUrl = uri;
}
public void setRawSrc(final Uri uri, final String extension) {
if (uri != null) {
boolean isSourceEqual = uri.equals(srcUri);
@@ -1778,6 +1851,22 @@ class ReactExoplayerView extends FrameLayout implements
this.disableBuffering = disableBuffering;
}
private void updateFullScreenButtonVisbility() {
if (playerControlView != null) {
final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
if (controls) {
//Handling the fullScreenButton click event
if (isFullscreen && fullScreenPlayerView != null && !fullScreenPlayerView.isShowing()) {
fullScreenButton.setVisibility(GONE);
} else {
fullScreenButton.setVisibility(VISIBLE);
}
} else {
fullScreenButton.setVisibility(GONE);
}
}
}
public void setDisableDisconnectError(boolean disableDisconnectError) {
this.disableDisconnectError = disableDisconnectError;
}
@@ -1793,15 +1882,6 @@ class ReactExoplayerView extends FrameLayout implements
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;
@@ -1815,7 +1895,7 @@ class ReactExoplayerView extends FrameLayout implements
| SYSTEM_UI_FLAG_FULLSCREEN;
}
eventEmitter.fullscreenWillPresent();
if (controls) {
if (controls && fullScreenPlayerView != null) {
fullScreenPlayerView.show();
}
post(() -> {
@@ -1825,7 +1905,7 @@ class ReactExoplayerView extends FrameLayout implements
} else {
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
eventEmitter.fullscreenWillDismiss();
if (controls) {
if (controls && fullScreenPlayerView != null) {
fullScreenPlayerView.dismiss();
reLayout(exoPlayerView);
}
@@ -1834,6 +1914,8 @@ class ReactExoplayerView extends FrameLayout implements
eventEmitter.fullscreenDidDismiss();
});
}
// need to be done at the end to avoid hiding fullscreen control button when fullScreenPlayerView is shown
updateFullScreenButtonVisbility();
}
public void setUseTextureView(boolean useTextureView) {
@@ -1902,9 +1984,9 @@ class ReactExoplayerView extends FrameLayout implements
*/
public void setControls(boolean controls) {
this.controls = controls;
if (player == null || exoPlayerView == null) return;
if (controls) {
addPlayerControl();
updateFullScreenButtonVisbility();
} else {
int indexOfPC = indexOfChild(playerControlView);
if (indexOfPC != -1) {
@@ -1916,4 +1998,9 @@ class ReactExoplayerView extends FrameLayout implements
public void setSubtitleStyle(SubtitleStyle style) {
exoPlayerView.setSubtitleStyle(style);
}
@Override
public void onAdEvent(AdEvent adEvent) {
eventEmitter.receiveAdEvent(adEvent.getType().name());
}
}

View File

@@ -31,6 +31,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_SRC = "src";
private static final String PROP_SRC_URI = "uri";
private static final String PROP_AD_TAG_URL = "adTagUrl";
private static final String PROP_SRC_TYPE = "type";
private static final String PROP_DRM = "drm";
private static final String PROP_DRM_TYPE = "type";
@@ -189,6 +190,18 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
}
}
@ReactProp(name = PROP_AD_TAG_URL)
public void setAdTagUrl(final ReactExoplayerView videoView, final String uriString) {
if (TextUtils.isEmpty(uriString)) {
return;
}
Uri adTagUrl = Uri.parse(uriString);
videoView.setAdTagUrl(adTagUrl);
}
@ReactProp(name = PROP_RESIZE_MODE)
public void setResizeMode(final ReactExoplayerView videoView, final String resizeModeOrdinalString) {
videoView.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString));

View File

@@ -56,6 +56,7 @@ class VideoEventEmitter {
private static final String EVENT_AUDIO_TRACKS = "onAudioTracks";
private static final String EVENT_TEXT_TRACKS = "onTextTracks";
private static final String EVENT_VIDEO_TRACKS = "onVideoTracks";
private static final String EVENT_ON_RECEIVE_AD_EVENT = "onReceiveAdEvent";
static final String[] Events = {
EVENT_LOAD_START,
@@ -82,6 +83,7 @@ class VideoEventEmitter {
EVENT_TEXT_TRACKS,
EVENT_VIDEO_TRACKS,
EVENT_BANDWIDTH,
EVENT_ON_RECEIVE_AD_EVENT
};
@Retention(RetentionPolicy.SOURCE)
@@ -110,6 +112,7 @@ class VideoEventEmitter {
EVENT_TEXT_TRACKS,
EVENT_VIDEO_TRACKS,
EVENT_BANDWIDTH,
EVENT_ON_RECEIVE_AD_EVENT
})
@interface VideoEvents {
}
@@ -432,6 +435,13 @@ class VideoEventEmitter {
receiveEvent(EVENT_AUDIO_BECOMING_NOISY, null);
}
void receiveAdEvent(String event) {
WritableMap map = Arguments.createMap();
map.putString("event", event);
receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map);
}
private void receiveEvent(@VideoEvents String type, WritableMap event) {
eventEmitter.receiveEvent(viewId, type, event);
}