From 9a3fcda3b8ca4689c9131a12a8375fc43d442f80 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:59:41 +0200 Subject: [PATCH] feat: add setSource API function fix ads playback (#4185) * feat: add setSource API function fix ads playback --- .../brentvatne/exoplayer/ExoPlayerView.java | 9 +- .../exoplayer/ReactExoplayerView.java | 73 +++---- .../exoplayer/ReactExoplayerViewManager.kt | 7 +- .../brentvatne/react/VideoManagerModule.kt | 9 + docs/pages/component/methods.mdx | 10 + examples/basic/src/VideoPlayer.tsx | 6 +- ios/Video/RCTVideoManager.m | 1 + ios/Video/RCTVideoManager.swift | 7 + src/Video.tsx | 199 ++++++++++-------- src/specs/NativeVideoManager.ts | 1 + 10 files changed, 189 insertions(+), 133 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index e8dc92c9..78d15763 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -93,6 +93,13 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { } } + public void showAds() { + adOverlayFrameLayout.setVisibility(View.GONE); + } + public void hideAds() { + adOverlayFrameLayout.setVisibility(View.VISIBLE); + } + private void clearVideoView() { if (surfaceView instanceof TextureView) { player.clearVideoTextureView((TextureView) surfaceView); @@ -189,7 +196,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { surfaceView.setAlpha(0); } - private void updateShutterViewVisibility() { + public void updateShutterViewVisibility() { if (this.hideShutterView) { hideShutterView(); } else { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index aec2d692..c340e149 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -735,22 +735,28 @@ public class ReactExoplayerView extends FrameLayout implements ReactExoplayerView self = this; Activity activity = themedReactContext.getCurrentActivity(); // This ensures all props have been settled, to avoid async racing conditions. + Source runningSource = source; mainRunnable = () -> { - if (viewHasDropped) { + if (viewHasDropped && runningSource == source) { return; } try { + if (runningSource.getUri() == null) { + return; + } if (player == null) { // Initialize core configuration and listeners initializePlayerCore(self); } - if (playerNeedsSource && source.getUri() != null) { + if (playerNeedsSource) { + // Will force display of shutter view if needed + exoPlayerView.updateShutterViewVisibility(); exoPlayerView.invalidateAspectRatio(); // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(() -> { // DRM initialization must run on a different thread - if (viewHasDropped) { + if (viewHasDropped && runningSource == source) { return; } if (activity == null) { @@ -761,12 +767,12 @@ public class ReactExoplayerView extends FrameLayout implements // Initialize handler to run on the main thread activity.runOnUiThread(() -> { - if (viewHasDropped) { + if (viewHasDropped && runningSource == source) { return; } try { // Source initialization must run on the main thread - initializePlayerSource(); + initializePlayerSource(runningSource); } catch (Exception ex) { self.playerNeedsSource = true; DebugLog.e(TAG, "Failed to initialize Player! 1"); @@ -776,8 +782,8 @@ public class ReactExoplayerView extends FrameLayout implements } }); }); - } else if (source.getUri() != null) { - initializePlayerSource(); + } else if (runningSource == source) { + initializePlayerSource(runningSource); } } catch (Exception ex) { self.playerNeedsSource = true; @@ -816,6 +822,11 @@ public class ReactExoplayerView extends FrameLayout implements .setEnableDecoderFallback(true) .forceEnableMediaCodecAsynchronousQueueing(); + DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); + if (useCache) { + mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); + } + ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); imaSdkSettings.setLanguage(adLanguage); @@ -826,14 +837,7 @@ public class ReactExoplayerView extends FrameLayout implements .setAdEventListener(this) .setAdErrorListener(this) .build(); - DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); - if (useCache) { - mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); - } - - if (adsLoader != null) { - mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); - } + mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); player = new ExoPlayer.Builder(getContext(), renderersFactory) .setTrackSelector(self.trackSelector) @@ -846,6 +850,7 @@ public class ReactExoplayerView extends FrameLayout implements player.addListener(self); player.setVolume(muted ? 0.f : audioVolume * 1); exoPlayerView.setPlayer(player); + if (adsLoader != null) { adsLoader.setPlayer(player); } @@ -884,31 +889,28 @@ public class ReactExoplayerView extends FrameLayout implements return drmSessionManager; } - private void initializePlayerSource() { - if (source.getUri() == null) { + private void initializePlayerSource(Source runningSource) { + if (runningSource.getUri() == null) { return; } /// init DRM DrmSessionManager drmSessionManager = initializePlayerDrm(); - if (drmSessionManager == null && source.getDrmProps() != null && source.getDrmProps().getDrmType() != null) { + if (drmSessionManager == null && runningSource.getDrmProps() != null && runningSource.getDrmProps().getDrmType() != null) { // Failed to initialize DRM session manager - cannot continue DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); return; } // init source to manage ads and external text tracks ArrayList mediaSourceList = buildTextSources(); - MediaSource videoSource = buildMediaSource(source.getUri(), source.getExtension(), drmSessionManager, source.getCropStartMs(), source.getCropEndMs()); + MediaSource videoSource = buildMediaSource(runningSource.getUri(), runningSource.getExtension(), drmSessionManager, runningSource.getCropStartMs(), runningSource.getCropEndMs()); MediaSource mediaSourceWithAds = null; - if (adTagUrl != null && adsLoader != null) { + if (adTagUrl != null && BuildConfig.USE_EXOPLAYER_IMA) { DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); DataSpec adTagDataSpec = new DataSpec(adTagUrl); - mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(source.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); - } else { - if (adTagUrl == null && adsLoader != null) { - adsLoader.release(); - adsLoader = null; - } + DebugLog.w(TAG, "ads " + adTagUrl); + mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(runningSource.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); + exoPlayerView.showAds(); } MediaSource mediaSource; if (mediaSourceList.isEmpty()) { @@ -943,8 +945,8 @@ public class ReactExoplayerView extends FrameLayout implements if (haveResumePosition) { player.seekTo(resumeWindow, resumePosition); player.setMediaSource(mediaSource, false); - } else if (source.getStartPositionMs() > 0) { - player.setMediaSource(mediaSource, source.getStartPositionMs()); + } else if (runningSource.getStartPositionMs() > 0) { + player.setMediaSource(mediaSource, runningSource.getStartPositionMs()); } else { player.setMediaSource(mediaSource, true); } @@ -1243,10 +1245,6 @@ public class ReactExoplayerView extends FrameLayout implements private void releasePlayer() { if (player != null) { - if (adsLoader != null) { - adsLoader.setPlayer(null); - } - if(playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); themedReactContext.unbindService(playbackServiceConnection); @@ -1903,19 +1901,21 @@ public class ReactExoplayerView extends FrameLayout implements if (!isSourceEqual) { reloadSource(); } + } else { + clearSrc(); } } - public void clearSrc() { if (source.getUri() != null) { if (player != null) { player.stop(); player.clearMediaItems(); } - this.source = new Source(); - this.mediaDataSourceFactory = null; - clearResumePosition(); } + exoPlayerView.hideAds(); + this.source = new Source(); + this.mediaDataSourceFactory = null; + clearResumePosition(); } public void setProgressUpdateInterval(final float progressUpdateInterval) { @@ -1927,6 +1927,7 @@ public class ReactExoplayerView extends FrameLayout implements } public void setAdTagUrl(final Uri uri) { + DebugLog.w(TAG, "setAdTagUrl" + uri); adTagUrl = uri; } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt index a21b29a3..9921ca03 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -89,12 +89,7 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View @ReactProp(name = PROP_SRC) fun setSrc(videoView: ReactExoplayerView, src: ReadableMap?) { val context = videoView.context.applicationContext - val source = Source.parse(src, context) - if (source.uri == null) { - videoView.clearSrc() - } else { - videoView.setSrc(source) - } + videoView.setSrc(Source.parse(src, context)) } @ReactProp(name = PROP_AD_TAG_URL) diff --git a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt index f6c0affe..d3968276 100644 --- a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt +++ b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt @@ -1,10 +1,12 @@ package com.brentvatne.react +import com.brentvatne.common.api.Source import com.brentvatne.exoplayer.ReactExoplayerView 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 com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.common.UIManagerType @@ -63,6 +65,13 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB } } + @ReactMethod + fun setSourceCmd(reactTag: Int, source: ReadableMap?) { + performOnPlayerView(reactTag) { + it?.setSrc(Source.parse(source, reactApplicationContext)) + } + } + @ReactMethod fun getCurrentPosition(reactTag: Int, promise: Promise) { performOnPlayerView(reactTag) { diff --git a/docs/pages/component/methods.mdx b/docs/pages/component/methods.mdx index 7e66040b..b3b0ebc0 100644 --- a/docs/pages/component/methods.mdx +++ b/docs/pages/component/methods.mdx @@ -115,6 +115,16 @@ This function will change the volume exactly like [volume](./props#volume) prope This function retrieves and returns the precise current position of the video playback, measured in seconds. This function will throw an error if player is not initialized. + +### `setSource` + + + +`setSource(source: ReactVideoSource): Promise` + +This function will change the source exactly like [source](./props#source) property. +Changing source with this function will overide source provided as props. + ### `setFullScreen` diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index 5956124d..feb4db90 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -252,6 +252,10 @@ const VideoPlayer: FC = ({}) => { cacheSizeMB: useCache ? 200 : 0, }; + useEffect(() => { + videoRef.current?.setSource(currentSrc) + }, [currentSrc]) + return (