feat: add setSource API function fix ads playback (#4185)
* feat: add setSource API function fix ads playback
This commit is contained in:
parent
4c9db2845b
commit
9a3fcda3b8
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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<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()) {
|
||||
@ -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,20 +1901,22 @@ 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();
|
||||
}
|
||||
}
|
||||
exoPlayerView.hideAds();
|
||||
this.source = new Source();
|
||||
this.mediaDataSourceFactory = null;
|
||||
clearResumePosition();
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressUpdateInterval(final float progressUpdateInterval) {
|
||||
mProgressUpdateInterval = progressUpdateInterval;
|
||||
@ -1927,6 +1927,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
|
||||
public void setAdTagUrl(final Uri uri) {
|
||||
DebugLog.w(TAG, "setAdTagUrl" + uri);
|
||||
adTagUrl = uri;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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']} />
|
||||
|
@ -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} />
|
||||
@ -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}
|
||||
|
@ -75,6 +75,7 @@ RCT_EXTERN_METHOD(setLicenseResultErrorCmd : (nonnull NSNumber*)reactTag error :
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -53,6 +53,7 @@ import type {
|
||||
OnReceiveAdEventData,
|
||||
ReactVideoProps,
|
||||
CmcdData,
|
||||
ReactVideoSource,
|
||||
} from './types';
|
||||
|
||||
export interface VideoRef {
|
||||
@ -66,6 +67,7 @@ export interface VideoRef {
|
||||
) => void;
|
||||
setVolume: (volume: number) => void;
|
||||
setFullScreen: (fullScreen: boolean) => void;
|
||||
setSource: (source?: ReactVideoSource) => void;
|
||||
save: (options: object) => Promise<VideoSaveData> | void;
|
||||
getCurrentPosition: () => Promise<number>;
|
||||
}
|
||||
@ -157,11 +159,12 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
setRestoreUserInterfaceForPIPStopCompletionHandler,
|
||||
] = useState<boolean | undefined>();
|
||||
|
||||
const src = useMemo<VideoSrc | undefined>(() => {
|
||||
if (!source) {
|
||||
const sourceToUnternalSource = useCallback(
|
||||
(_source?: ReactVideoSource) => {
|
||||
if (!_source) {
|
||||
return undefined;
|
||||
}
|
||||
const resolvedSource = resolveAssetSourceForVideo(source);
|
||||
const resolvedSource = resolveAssetSourceForVideo(_source);
|
||||
let uri = resolvedSource.uri || '';
|
||||
if (uri && uri.match(/^\//)) {
|
||||
uri = `file://${uri}`;
|
||||
@ -177,8 +180,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
)
|
||||
);
|
||||
|
||||
const selectedDrm = source.drm || drm;
|
||||
const _textTracks = source.textTracks || textTracks;
|
||||
const selectedDrm = _source.drm || drm;
|
||||
const _textTracks = _source.textTracks || textTracks;
|
||||
const _drm = !selectedDrm
|
||||
? undefined
|
||||
: {
|
||||
@ -220,7 +223,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
}
|
||||
|
||||
const selectedContentStartTime =
|
||||
source.contentStartTime || contentStartTime;
|
||||
_source.contentStartTime || contentStartTime;
|
||||
|
||||
return {
|
||||
uri,
|
||||
@ -242,13 +245,19 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
textTracksAllowChunklessPreparation:
|
||||
resolvedSource.textTracksAllowChunklessPreparation,
|
||||
};
|
||||
}, [
|
||||
},
|
||||
[
|
||||
contentStartTime,
|
||||
drm,
|
||||
localSourceEncryptionKeyScheme,
|
||||
source,
|
||||
textTracks,
|
||||
contentStartTime,
|
||||
localSourceEncryptionKeyScheme,
|
||||
]);
|
||||
],
|
||||
);
|
||||
|
||||
const src = useMemo<VideoSrc | undefined>(() => {
|
||||
return sourceToUnternalSource(source);
|
||||
}, [sourceToUnternalSource, source]);
|
||||
|
||||
const _selectedTextTrack = useMemo(() => {
|
||||
if (!selectedTextTrack) {
|
||||
@ -370,6 +379,16 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setSource = useCallback(
|
||||
(_source?: ReactVideoSource) => {
|
||||
return NativeVideoManager.setSourceCmd(
|
||||
getReactTag(nativeRef),
|
||||
sourceToUnternalSource(_source),
|
||||
);
|
||||
},
|
||||
[sourceToUnternalSource],
|
||||
);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(
|
||||
() => setFullScreen(true),
|
||||
[setFullScreen],
|
||||
@ -628,6 +647,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
setVolume,
|
||||
getCurrentPosition,
|
||||
setFullScreen,
|
||||
setSource,
|
||||
}),
|
||||
[
|
||||
seek,
|
||||
@ -640,6 +660,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
setVolume,
|
||||
getCurrentPosition,
|
||||
setFullScreen,
|
||||
setSource,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -24,6 +24,7 @@ export interface VideoManagerType {
|
||||
licenseUrl: string,
|
||||
) => Promise<void>;
|
||||
setFullScreenCmd: (reactTag: Int32, fullScreen: boolean) => Promise<void>;
|
||||
setSourceCmd: (reactTag: Int32, source?: UnsafeObject) => Promise<void>;
|
||||
setVolumeCmd: (reactTag: Int32, volume: number) => Promise<void>;
|
||||
save: (reactTag: Int32, option: UnsafeObject) => Promise<VideoSaveData>;
|
||||
getCurrentPosition: (reactTag: Int32) => Promise<Int32>;
|
||||
|
Loading…
Reference in New Issue
Block a user