Skip to content

Commit

Permalink
feat: add setSource API function fix ads playback (TheWidlarzGroup#4185)
Browse files Browse the repository at this point in the history
* feat: add setSource API function fix ads playback
  • Loading branch information
freeboub authored Oct 10, 2024
1 parent 4c9db28 commit 9a3fcda
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ public ExoPlayerView(Context context) {
}
}

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);
Expand Down Expand Up @@ -189,7 +196,7 @@ private void showShutterView() {
surfaceView.setAlpha(0);
}

private void updateShutterViewVisibility() {
public void updateShutterViewVisibility() {
if (this.hideShutterView) {
hideShutterView();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,22 +735,28 @@ private void initializePlayer() {
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) {
Expand All @@ -761,12 +767,12 @@ private void initializePlayer() {

// 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");
Expand All @@ -776,8 +782,8 @@ private void initializePlayer() {
}
});
});
} else if (source.getUri() != null) {
initializePlayerSource();
} else if (runningSource == source) {
initializePlayerSource(runningSource);
}
} catch (Exception ex) {
self.playerNeedsSource = true;
Expand Down Expand Up @@ -816,6 +822,11 @@ private void initializePlayerCore(ReactExoplayerView self) {
.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);

Expand All @@ -826,14 +837,7 @@ private void initializePlayerCore(ReactExoplayerView self) {
.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)
Expand All @@ -846,6 +850,7 @@ private void initializePlayerCore(ReactExoplayerView self) {
player.addListener(self);
player.setVolume(muted ? 0.f : audioVolume * 1);
exoPlayerView.setPlayer(player);

if (adsLoader != null) {
adsLoader.setPlayer(player);
}
Expand Down Expand Up @@ -884,31 +889,28 @@ private DrmSessionManager initializePlayerDrm() {
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<MediaSource> 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()) {
Expand Down Expand Up @@ -943,8 +945,8 @@ private void initializePlayerSource() {
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);
}
Expand Down Expand Up @@ -1243,10 +1245,6 @@ private MediaSource buildTextSource(String title, Uri uri, String mimeType, Stri

private void releasePlayer() {
if (player != null) {
if (adsLoader != null) {
adsLoader.setPlayer(null);
}

if(playbackServiceBinder != null) {
playbackServiceBinder.getService().unregisterPlayer(player);
themedReactContext.unbindService(playbackServiceConnection);
Expand Down Expand Up @@ -1903,19 +1901,21 @@ public void setSrc(Source source) {
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) {
Expand All @@ -1927,6 +1927,7 @@ public void setReportBandwidth(boolean reportBandwidth) {
}

public void setAdTagUrl(final Uri uri) {
DebugLog.w(TAG, "setAdTagUrl" + uri);
adTagUrl = uri;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 10 additions & 0 deletions docs/pages/component/methods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`

<PlatformsList types={['Android', 'iOS']} />

`setSource(source: ReactVideoSource): Promise<void>`

This function will change the source exactly like [source](./props#source) property.
Changing source with this function will overide source provided as props.

### `setFullScreen`

<PlatformsList types={['Android', 'iOS']} />
Expand Down
6 changes: 5 additions & 1 deletion examples/basic/src/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ const VideoPlayer: FC<Props> = ({}) => {
cacheSizeMB: useCache ? 200 : 0,
};

useEffect(() => {
videoRef.current?.setSource(currentSrc)
}, [currentSrc])

return (
<View style={styles.container}>
<StatusBar animated={true} backgroundColor="black" hidden={false} />
Expand All @@ -261,7 +265,7 @@ const VideoPlayer: FC<Props> = ({}) => {
<Video
showNotificationControls={showNotificationControls}
ref={videoRef}
source={currentSrc as ReactVideoSource}
// source={currentSrc as ReactVideoSource}
adTagUrl={additional?.adTagUrl}
drm={additional?.drm}
style={viewStyle}
Expand Down
1 change: 1 addition & 0 deletions ios/Video/RCTVideoManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager)
RCT_EXTERN_METHOD(setPlayerPauseStateCmd : (nonnull NSNumber*)reactTag paused : (nonnull BOOL)paused)
RCT_EXTERN_METHOD(setVolumeCmd : (nonnull NSNumber*)reactTag volume : (nonnull float*)volume)
RCT_EXTERN_METHOD(setFullScreenCmd : (nonnull NSNumber*)reactTag fullscreen : (nonnull BOOL)fullScreen)
RCT_EXTERN_METHOD(setSourceCmd : (nonnull NSNumber*)reactTag source : (NSDictionary*)source)

RCT_EXTERN_METHOD(save
: (nonnull NSNumber*)reactTag options
Expand Down
7 changes: 7 additions & 0 deletions ios/Video/RCTVideoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ class RCTVideoManager: RCTViewManager {
})
}

@objc(setSourceCmd:source:)
func setSourceCmd(_ reactTag: NSNumber, source: NSDictionary) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setSrc(source)
})
}

@objc(save:options:resolve:reject:)
func save(_ reactTag: NSNumber, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
Expand Down
Loading

0 comments on commit 9a3fcda

Please sign in to comment.