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() {
|
private void clearVideoView() {
|
||||||
if (surfaceView instanceof TextureView) {
|
if (surfaceView instanceof TextureView) {
|
||||||
player.clearVideoTextureView((TextureView) surfaceView);
|
player.clearVideoTextureView((TextureView) surfaceView);
|
||||||
@ -189,7 +196,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
|||||||
surfaceView.setAlpha(0);
|
surfaceView.setAlpha(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateShutterViewVisibility() {
|
public void updateShutterViewVisibility() {
|
||||||
if (this.hideShutterView) {
|
if (this.hideShutterView) {
|
||||||
hideShutterView();
|
hideShutterView();
|
||||||
} else {
|
} else {
|
||||||
|
@ -735,22 +735,28 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
ReactExoplayerView self = this;
|
ReactExoplayerView self = this;
|
||||||
Activity activity = themedReactContext.getCurrentActivity();
|
Activity activity = themedReactContext.getCurrentActivity();
|
||||||
// This ensures all props have been settled, to avoid async racing conditions.
|
// This ensures all props have been settled, to avoid async racing conditions.
|
||||||
|
Source runningSource = source;
|
||||||
mainRunnable = () -> {
|
mainRunnable = () -> {
|
||||||
if (viewHasDropped) {
|
if (viewHasDropped && runningSource == source) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (runningSource.getUri() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
// Initialize core configuration and listeners
|
// Initialize core configuration and listeners
|
||||||
initializePlayerCore(self);
|
initializePlayerCore(self);
|
||||||
}
|
}
|
||||||
if (playerNeedsSource && source.getUri() != null) {
|
if (playerNeedsSource) {
|
||||||
|
// Will force display of shutter view if needed
|
||||||
|
exoPlayerView.updateShutterViewVisibility();
|
||||||
exoPlayerView.invalidateAspectRatio();
|
exoPlayerView.invalidateAspectRatio();
|
||||||
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
|
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
|
||||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
es.execute(() -> {
|
es.execute(() -> {
|
||||||
// DRM initialization must run on a different thread
|
// DRM initialization must run on a different thread
|
||||||
if (viewHasDropped) {
|
if (viewHasDropped && runningSource == source) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
@ -761,12 +767,12 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
// Initialize handler to run on the main thread
|
// Initialize handler to run on the main thread
|
||||||
activity.runOnUiThread(() -> {
|
activity.runOnUiThread(() -> {
|
||||||
if (viewHasDropped) {
|
if (viewHasDropped && runningSource == source) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Source initialization must run on the main thread
|
// Source initialization must run on the main thread
|
||||||
initializePlayerSource();
|
initializePlayerSource(runningSource);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
self.playerNeedsSource = true;
|
self.playerNeedsSource = true;
|
||||||
DebugLog.e(TAG, "Failed to initialize Player! 1");
|
DebugLog.e(TAG, "Failed to initialize Player! 1");
|
||||||
@ -776,8 +782,8 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (source.getUri() != null) {
|
} else if (runningSource == source) {
|
||||||
initializePlayerSource();
|
initializePlayerSource(runningSource);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
self.playerNeedsSource = true;
|
self.playerNeedsSource = true;
|
||||||
@ -816,6 +822,11 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
.setEnableDecoderFallback(true)
|
.setEnableDecoderFallback(true)
|
||||||
.forceEnableMediaCodecAsynchronousQueueing();
|
.forceEnableMediaCodecAsynchronousQueueing();
|
||||||
|
|
||||||
|
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
|
||||||
|
if (useCache) {
|
||||||
|
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
|
||||||
|
}
|
||||||
|
|
||||||
ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings();
|
ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings();
|
||||||
imaSdkSettings.setLanguage(adLanguage);
|
imaSdkSettings.setLanguage(adLanguage);
|
||||||
|
|
||||||
@ -826,14 +837,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
.setAdEventListener(this)
|
.setAdEventListener(this)
|
||||||
.setAdErrorListener(this)
|
.setAdErrorListener(this)
|
||||||
.build();
|
.build();
|
||||||
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
|
mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
||||||
if (useCache) {
|
|
||||||
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adsLoader != null) {
|
|
||||||
mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
|
||||||
}
|
|
||||||
|
|
||||||
player = new ExoPlayer.Builder(getContext(), renderersFactory)
|
player = new ExoPlayer.Builder(getContext(), renderersFactory)
|
||||||
.setTrackSelector(self.trackSelector)
|
.setTrackSelector(self.trackSelector)
|
||||||
@ -846,6 +850,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
player.addListener(self);
|
player.addListener(self);
|
||||||
player.setVolume(muted ? 0.f : audioVolume * 1);
|
player.setVolume(muted ? 0.f : audioVolume * 1);
|
||||||
exoPlayerView.setPlayer(player);
|
exoPlayerView.setPlayer(player);
|
||||||
|
|
||||||
if (adsLoader != null) {
|
if (adsLoader != null) {
|
||||||
adsLoader.setPlayer(player);
|
adsLoader.setPlayer(player);
|
||||||
}
|
}
|
||||||
@ -884,31 +889,28 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
return drmSessionManager;
|
return drmSessionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializePlayerSource() {
|
private void initializePlayerSource(Source runningSource) {
|
||||||
if (source.getUri() == null) {
|
if (runningSource.getUri() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/// init DRM
|
/// init DRM
|
||||||
DrmSessionManager drmSessionManager = initializePlayerDrm();
|
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
|
// Failed to initialize DRM session manager - cannot continue
|
||||||
DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!");
|
DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// init source to manage ads and external text tracks
|
// init source to manage ads and external text tracks
|
||||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
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;
|
MediaSource mediaSourceWithAds = null;
|
||||||
if (adTagUrl != null && adsLoader != null) {
|
if (adTagUrl != null && BuildConfig.USE_EXOPLAYER_IMA) {
|
||||||
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
||||||
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
||||||
DataSpec adTagDataSpec = new DataSpec(adTagUrl);
|
DataSpec adTagDataSpec = new DataSpec(adTagUrl);
|
||||||
mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(source.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView);
|
DebugLog.w(TAG, "ads " + adTagUrl);
|
||||||
} else {
|
mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(runningSource.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView);
|
||||||
if (adTagUrl == null && adsLoader != null) {
|
exoPlayerView.showAds();
|
||||||
adsLoader.release();
|
|
||||||
adsLoader = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MediaSource mediaSource;
|
MediaSource mediaSource;
|
||||||
if (mediaSourceList.isEmpty()) {
|
if (mediaSourceList.isEmpty()) {
|
||||||
@ -943,8 +945,8 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (haveResumePosition) {
|
if (haveResumePosition) {
|
||||||
player.seekTo(resumeWindow, resumePosition);
|
player.seekTo(resumeWindow, resumePosition);
|
||||||
player.setMediaSource(mediaSource, false);
|
player.setMediaSource(mediaSource, false);
|
||||||
} else if (source.getStartPositionMs() > 0) {
|
} else if (runningSource.getStartPositionMs() > 0) {
|
||||||
player.setMediaSource(mediaSource, source.getStartPositionMs());
|
player.setMediaSource(mediaSource, runningSource.getStartPositionMs());
|
||||||
} else {
|
} else {
|
||||||
player.setMediaSource(mediaSource, true);
|
player.setMediaSource(mediaSource, true);
|
||||||
}
|
}
|
||||||
@ -1243,10 +1245,6 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
private void releasePlayer() {
|
private void releasePlayer() {
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
if (adsLoader != null) {
|
|
||||||
adsLoader.setPlayer(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(playbackServiceBinder != null) {
|
if(playbackServiceBinder != null) {
|
||||||
playbackServiceBinder.getService().unregisterPlayer(player);
|
playbackServiceBinder.getService().unregisterPlayer(player);
|
||||||
themedReactContext.unbindService(playbackServiceConnection);
|
themedReactContext.unbindService(playbackServiceConnection);
|
||||||
@ -1903,19 +1901,21 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (!isSourceEqual) {
|
if (!isSourceEqual) {
|
||||||
reloadSource();
|
reloadSource();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
clearSrc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearSrc() {
|
public void clearSrc() {
|
||||||
if (source.getUri() != null) {
|
if (source.getUri() != null) {
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.stop();
|
player.stop();
|
||||||
player.clearMediaItems();
|
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) {
|
public void setProgressUpdateInterval(final float progressUpdateInterval) {
|
||||||
@ -1927,6 +1927,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAdTagUrl(final Uri uri) {
|
public void setAdTagUrl(final Uri uri) {
|
||||||
|
DebugLog.w(TAG, "setAdTagUrl" + uri);
|
||||||
adTagUrl = uri;
|
adTagUrl = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,12 +89,7 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View
|
|||||||
@ReactProp(name = PROP_SRC)
|
@ReactProp(name = PROP_SRC)
|
||||||
fun setSrc(videoView: ReactExoplayerView, src: ReadableMap?) {
|
fun setSrc(videoView: ReactExoplayerView, src: ReadableMap?) {
|
||||||
val context = videoView.context.applicationContext
|
val context = videoView.context.applicationContext
|
||||||
val source = Source.parse(src, context)
|
videoView.setSrc(Source.parse(src, context))
|
||||||
if (source.uri == null) {
|
|
||||||
videoView.clearSrc()
|
|
||||||
} else {
|
|
||||||
videoView.setSrc(source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_AD_TAG_URL)
|
@ReactProp(name = PROP_AD_TAG_URL)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package com.brentvatne.react
|
package com.brentvatne.react
|
||||||
|
|
||||||
|
import com.brentvatne.common.api.Source
|
||||||
import com.brentvatne.exoplayer.ReactExoplayerView
|
import com.brentvatne.exoplayer.ReactExoplayerView
|
||||||
import com.facebook.react.bridge.Promise
|
import com.facebook.react.bridge.Promise
|
||||||
import com.facebook.react.bridge.ReactApplicationContext
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||||
import com.facebook.react.bridge.ReactMethod
|
import com.facebook.react.bridge.ReactMethod
|
||||||
|
import com.facebook.react.bridge.ReadableMap
|
||||||
import com.facebook.react.bridge.UiThreadUtil
|
import com.facebook.react.bridge.UiThreadUtil
|
||||||
import com.facebook.react.uimanager.UIManagerHelper
|
import com.facebook.react.uimanager.UIManagerHelper
|
||||||
import com.facebook.react.uimanager.common.UIManagerType
|
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
|
@ReactMethod
|
||||||
fun getCurrentPosition(reactTag: Int, promise: Promise) {
|
fun getCurrentPosition(reactTag: Int, promise: Promise) {
|
||||||
performOnPlayerView(reactTag) {
|
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 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.
|
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`
|
### `setFullScreen`
|
||||||
|
|
||||||
<PlatformsList types={['Android', 'iOS']} />
|
<PlatformsList types={['Android', 'iOS']} />
|
||||||
|
@ -252,6 +252,10 @@ const VideoPlayer: FC<Props> = ({}) => {
|
|||||||
cacheSizeMB: useCache ? 200 : 0,
|
cacheSizeMB: useCache ? 200 : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
videoRef.current?.setSource(currentSrc)
|
||||||
|
}, [currentSrc])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<StatusBar animated={true} backgroundColor="black" hidden={false} />
|
<StatusBar animated={true} backgroundColor="black" hidden={false} />
|
||||||
@ -261,7 +265,7 @@ const VideoPlayer: FC<Props> = ({}) => {
|
|||||||
<Video
|
<Video
|
||||||
showNotificationControls={showNotificationControls}
|
showNotificationControls={showNotificationControls}
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
source={currentSrc as ReactVideoSource}
|
// source={currentSrc as ReactVideoSource}
|
||||||
adTagUrl={additional?.adTagUrl}
|
adTagUrl={additional?.adTagUrl}
|
||||||
drm={additional?.drm}
|
drm={additional?.drm}
|
||||||
style={viewStyle}
|
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(setPlayerPauseStateCmd : (nonnull NSNumber*)reactTag paused : (nonnull BOOL)paused)
|
||||||
RCT_EXTERN_METHOD(setVolumeCmd : (nonnull NSNumber*)reactTag volume : (nonnull float*)volume)
|
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(setFullScreenCmd : (nonnull NSNumber*)reactTag fullscreen : (nonnull BOOL)fullScreen)
|
||||||
|
RCT_EXTERN_METHOD(setSourceCmd : (nonnull NSNumber*)reactTag source : (NSDictionary*)source)
|
||||||
|
|
||||||
RCT_EXTERN_METHOD(save
|
RCT_EXTERN_METHOD(save
|
||||||
: (nonnull NSNumber*)reactTag options
|
: (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:)
|
@objc(save:options:resolve:reject:)
|
||||||
func save(_ reactTag: NSNumber, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
func save(_ reactTag: NSNumber, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||||
performOnVideoView(withReactTag: reactTag, callback: { videoView in
|
performOnVideoView(withReactTag: reactTag, callback: { videoView in
|
||||||
|
199
src/Video.tsx
199
src/Video.tsx
@ -53,6 +53,7 @@ import type {
|
|||||||
OnReceiveAdEventData,
|
OnReceiveAdEventData,
|
||||||
ReactVideoProps,
|
ReactVideoProps,
|
||||||
CmcdData,
|
CmcdData,
|
||||||
|
ReactVideoSource,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export interface VideoRef {
|
export interface VideoRef {
|
||||||
@ -66,6 +67,7 @@ export interface VideoRef {
|
|||||||
) => void;
|
) => void;
|
||||||
setVolume: (volume: number) => void;
|
setVolume: (volume: number) => void;
|
||||||
setFullScreen: (fullScreen: boolean) => void;
|
setFullScreen: (fullScreen: boolean) => void;
|
||||||
|
setSource: (source?: ReactVideoSource) => void;
|
||||||
save: (options: object) => Promise<VideoSaveData> | void;
|
save: (options: object) => Promise<VideoSaveData> | void;
|
||||||
getCurrentPosition: () => Promise<number>;
|
getCurrentPosition: () => Promise<number>;
|
||||||
}
|
}
|
||||||
@ -157,98 +159,105 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
setRestoreUserInterfaceForPIPStopCompletionHandler,
|
setRestoreUserInterfaceForPIPStopCompletionHandler,
|
||||||
] = useState<boolean | undefined>();
|
] = useState<boolean | undefined>();
|
||||||
|
|
||||||
const src = useMemo<VideoSrc | undefined>(() => {
|
const sourceToUnternalSource = useCallback(
|
||||||
if (!source) {
|
(_source?: ReactVideoSource) => {
|
||||||
return undefined;
|
if (!_source) {
|
||||||
}
|
return undefined;
|
||||||
const resolvedSource = resolveAssetSourceForVideo(source);
|
|
||||||
let uri = resolvedSource.uri || '';
|
|
||||||
if (uri && uri.match(/^\//)) {
|
|
||||||
uri = `file://${uri}`;
|
|
||||||
}
|
|
||||||
if (!uri) {
|
|
||||||
console.log('Trying to load empty source');
|
|
||||||
}
|
|
||||||
const isNetwork = !!(uri && uri.match(/^(rtp|rtsp|http|https):/));
|
|
||||||
const isAsset = !!(
|
|
||||||
uri &&
|
|
||||||
uri.match(
|
|
||||||
/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedDrm = source.drm || drm;
|
|
||||||
const _textTracks = source.textTracks || textTracks;
|
|
||||||
const _drm = !selectedDrm
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
type: selectedDrm.type,
|
|
||||||
licenseServer: selectedDrm.licenseServer,
|
|
||||||
headers: generateHeaderForNative(selectedDrm.headers),
|
|
||||||
contentId: selectedDrm.contentId,
|
|
||||||
certificateUrl: selectedDrm.certificateUrl,
|
|
||||||
base64Certificate: selectedDrm.base64Certificate,
|
|
||||||
useExternalGetLicense: !!selectedDrm.getLicense,
|
|
||||||
multiDrm: selectedDrm.multiDrm,
|
|
||||||
localSourceEncryptionKeyScheme:
|
|
||||||
selectedDrm.localSourceEncryptionKeyScheme ||
|
|
||||||
localSourceEncryptionKeyScheme,
|
|
||||||
};
|
|
||||||
|
|
||||||
let _cmcd: NativeCmcdConfiguration | undefined;
|
|
||||||
if (Platform.OS === 'android' && source?.cmcd) {
|
|
||||||
const cmcd = source.cmcd;
|
|
||||||
|
|
||||||
if (typeof cmcd === 'boolean') {
|
|
||||||
_cmcd = cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined;
|
|
||||||
} else if (typeof cmcd === 'object' && !Array.isArray(cmcd)) {
|
|
||||||
const createCmcdHeader = (property?: CmcdData) =>
|
|
||||||
property ? generateHeaderForNative(property) : undefined;
|
|
||||||
|
|
||||||
_cmcd = {
|
|
||||||
mode: cmcd.mode ?? CmcdMode.MODE_QUERY_PARAMETER,
|
|
||||||
request: createCmcdHeader(cmcd.request),
|
|
||||||
session: createCmcdHeader(cmcd.session),
|
|
||||||
object: createCmcdHeader(cmcd.object),
|
|
||||||
status: createCmcdHeader(cmcd.status),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'Invalid CMCD configuration: Expected a boolean or an object.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
const resolvedSource = resolveAssetSourceForVideo(_source);
|
||||||
|
let uri = resolvedSource.uri || '';
|
||||||
|
if (uri && uri.match(/^\//)) {
|
||||||
|
uri = `file://${uri}`;
|
||||||
|
}
|
||||||
|
if (!uri) {
|
||||||
|
console.log('Trying to load empty source');
|
||||||
|
}
|
||||||
|
const isNetwork = !!(uri && uri.match(/^(rtp|rtsp|http|https):/));
|
||||||
|
const isAsset = !!(
|
||||||
|
uri &&
|
||||||
|
uri.match(
|
||||||
|
/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const selectedContentStartTime =
|
const selectedDrm = _source.drm || drm;
|
||||||
source.contentStartTime || contentStartTime;
|
const _textTracks = _source.textTracks || textTracks;
|
||||||
|
const _drm = !selectedDrm
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
type: selectedDrm.type,
|
||||||
|
licenseServer: selectedDrm.licenseServer,
|
||||||
|
headers: generateHeaderForNative(selectedDrm.headers),
|
||||||
|
contentId: selectedDrm.contentId,
|
||||||
|
certificateUrl: selectedDrm.certificateUrl,
|
||||||
|
base64Certificate: selectedDrm.base64Certificate,
|
||||||
|
useExternalGetLicense: !!selectedDrm.getLicense,
|
||||||
|
multiDrm: selectedDrm.multiDrm,
|
||||||
|
localSourceEncryptionKeyScheme:
|
||||||
|
selectedDrm.localSourceEncryptionKeyScheme ||
|
||||||
|
localSourceEncryptionKeyScheme,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
let _cmcd: NativeCmcdConfiguration | undefined;
|
||||||
uri,
|
if (Platform.OS === 'android' && source?.cmcd) {
|
||||||
isNetwork,
|
const cmcd = source.cmcd;
|
||||||
isAsset,
|
|
||||||
shouldCache: resolvedSource.shouldCache || false,
|
if (typeof cmcd === 'boolean') {
|
||||||
type: resolvedSource.type || '',
|
_cmcd = cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined;
|
||||||
mainVer: resolvedSource.mainVer || 0,
|
} else if (typeof cmcd === 'object' && !Array.isArray(cmcd)) {
|
||||||
patchVer: resolvedSource.patchVer || 0,
|
const createCmcdHeader = (property?: CmcdData) =>
|
||||||
requestHeaders: generateHeaderForNative(resolvedSource.headers),
|
property ? generateHeaderForNative(property) : undefined;
|
||||||
startPosition: resolvedSource.startPosition ?? -1,
|
|
||||||
cropStart: resolvedSource.cropStart || 0,
|
_cmcd = {
|
||||||
cropEnd: resolvedSource.cropEnd,
|
mode: cmcd.mode ?? CmcdMode.MODE_QUERY_PARAMETER,
|
||||||
contentStartTime: selectedContentStartTime,
|
request: createCmcdHeader(cmcd.request),
|
||||||
metadata: resolvedSource.metadata,
|
session: createCmcdHeader(cmcd.session),
|
||||||
drm: _drm,
|
object: createCmcdHeader(cmcd.object),
|
||||||
cmcd: _cmcd,
|
status: createCmcdHeader(cmcd.status),
|
||||||
textTracks: _textTracks,
|
};
|
||||||
textTracksAllowChunklessPreparation:
|
} else {
|
||||||
resolvedSource.textTracksAllowChunklessPreparation,
|
throw new Error(
|
||||||
};
|
'Invalid CMCD configuration: Expected a boolean or an object.',
|
||||||
}, [
|
);
|
||||||
drm,
|
}
|
||||||
source,
|
}
|
||||||
textTracks,
|
|
||||||
contentStartTime,
|
const selectedContentStartTime =
|
||||||
localSourceEncryptionKeyScheme,
|
_source.contentStartTime || contentStartTime;
|
||||||
]);
|
|
||||||
|
return {
|
||||||
|
uri,
|
||||||
|
isNetwork,
|
||||||
|
isAsset,
|
||||||
|
shouldCache: resolvedSource.shouldCache || false,
|
||||||
|
type: resolvedSource.type || '',
|
||||||
|
mainVer: resolvedSource.mainVer || 0,
|
||||||
|
patchVer: resolvedSource.patchVer || 0,
|
||||||
|
requestHeaders: generateHeaderForNative(resolvedSource.headers),
|
||||||
|
startPosition: resolvedSource.startPosition ?? -1,
|
||||||
|
cropStart: resolvedSource.cropStart || 0,
|
||||||
|
cropEnd: resolvedSource.cropEnd,
|
||||||
|
contentStartTime: selectedContentStartTime,
|
||||||
|
metadata: resolvedSource.metadata,
|
||||||
|
drm: _drm,
|
||||||
|
cmcd: _cmcd,
|
||||||
|
textTracks: _textTracks,
|
||||||
|
textTracksAllowChunklessPreparation:
|
||||||
|
resolvedSource.textTracksAllowChunklessPreparation,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[
|
||||||
|
contentStartTime,
|
||||||
|
drm,
|
||||||
|
localSourceEncryptionKeyScheme,
|
||||||
|
source,
|
||||||
|
textTracks,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const src = useMemo<VideoSrc | undefined>(() => {
|
||||||
|
return sourceToUnternalSource(source);
|
||||||
|
}, [sourceToUnternalSource, source]);
|
||||||
|
|
||||||
const _selectedTextTrack = useMemo(() => {
|
const _selectedTextTrack = useMemo(() => {
|
||||||
if (!selectedTextTrack) {
|
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(
|
const presentFullscreenPlayer = useCallback(
|
||||||
() => setFullScreen(true),
|
() => setFullScreen(true),
|
||||||
[setFullScreen],
|
[setFullScreen],
|
||||||
@ -628,6 +647,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
setVolume,
|
setVolume,
|
||||||
getCurrentPosition,
|
getCurrentPosition,
|
||||||
setFullScreen,
|
setFullScreen,
|
||||||
|
setSource,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
seek,
|
seek,
|
||||||
@ -640,6 +660,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
setVolume,
|
setVolume,
|
||||||
getCurrentPosition,
|
getCurrentPosition,
|
||||||
setFullScreen,
|
setFullScreen,
|
||||||
|
setSource,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ export interface VideoManagerType {
|
|||||||
licenseUrl: string,
|
licenseUrl: string,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
setFullScreenCmd: (reactTag: Int32, fullScreen: boolean) => Promise<void>;
|
setFullScreenCmd: (reactTag: Int32, fullScreen: boolean) => Promise<void>;
|
||||||
|
setSourceCmd: (reactTag: Int32, source?: UnsafeObject) => Promise<void>;
|
||||||
setVolumeCmd: (reactTag: Int32, volume: number) => Promise<void>;
|
setVolumeCmd: (reactTag: Int32, volume: number) => Promise<void>;
|
||||||
save: (reactTag: Int32, option: UnsafeObject) => Promise<VideoSaveData>;
|
save: (reactTag: Int32, option: UnsafeObject) => Promise<VideoSaveData>;
|
||||||
getCurrentPosition: (reactTag: Int32) => Promise<Int32>;
|
getCurrentPosition: (reactTag: Int32) => Promise<Int32>;
|
||||||
|
Loading…
Reference in New Issue
Block a user