diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 3b2e8c5f..27b3f99d 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -70,7 +70,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager { - View view = manager.resolveView(reactTag); - - if (view instanceof ReactExoplayerView) { - ReactExoplayerView videoView = (ReactExoplayerView) view; - videoView.setPausedModifier(paused); - } - }); - } -} diff --git a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt new file mode 100644 index 00000000..2680756b --- /dev/null +++ b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt @@ -0,0 +1,59 @@ +package com.brentvatne.react + +import com.brentvatne.common.toolbox.ReactBridgeUtils +import com.brentvatne.exoplayer.ReactExoplayerView +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.UIManagerModule +import kotlin.math.roundToInt + +class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) { + override fun getName(): String { + return REACT_CLASS + } + + private fun performOnPlayerView(reactTag: Int, callback: (ReactExoplayerView?) -> Unit) { + UiThreadUtil.runOnUiThread { + val view = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + reactApplicationContext.fabricUIManager?.resolveView( + reactTag + ) + } else { + val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java) + uiManager?.resolveView(reactTag) + } + + if (view is ReactExoplayerView) { + callback(view) + } else { + callback(null) + } + } + } + + @ReactMethod + fun setPlayerPauseState(paused: Boolean?, reactTag: Int) { + performOnPlayerView(reactTag) { + it?.setPausedModifier(paused!!) + } + } + + @ReactMethod + fun seek(info: ReadableMap, reactTag: Int) { + if (!info.hasKey("time")) { + return + } + + val time = ReactBridgeUtils.safeGetInt(info, "time") + performOnPlayerView(reactTag) { + it?.seekTo((time * 1000f).roundToInt().toLong()) + } + } + + companion object { + private const val REACT_CLASS = "VideoManager" + } +} diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index de1140ef..b5856b22 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -28,7 +28,6 @@ RCT_EXPORT_VIEW_PROPERTY(pictureInPicture, BOOL); RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString); RCT_EXPORT_VIEW_PROPERTY(mixWithOthers, NSString); RCT_EXPORT_VIEW_PROPERTY(rate, float); -RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString); @@ -74,6 +73,8 @@ RCT_EXTERN_METHOD(save : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(seek : (NSDictionary*)info reactTag : (nonnull NSNumber*)reactTag) + RCT_EXTERN_METHOD(setLicenseResult : (NSString*)license licenseUrl : (NSString*)licenseUrl reactTag : (nonnull NSNumber*)reactTag) RCT_EXTERN_METHOD(setLicenseResultError : (NSString*)error licenseUrl : (NSString*)licenseUrl reactTag : (nonnull NSNumber*)reactTag) diff --git a/ios/Video/RCTVideoManager.swift b/ios/Video/RCTVideoManager.swift index e2055c02..94dfcc4c 100644 --- a/ios/Video/RCTVideoManager.swift +++ b/ios/Video/RCTVideoManager.swift @@ -11,77 +11,70 @@ class RCTVideoManager: RCTViewManager { return bridge.uiManager.methodQueue } + func performOnVideoView(withReactTag reactTag: NSNumber, callback: @escaping (RCTVideo?) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let self else { + callback(nil) + return + } + + guard let view = self.bridge.uiManager.view(forReactTag: reactTag) as? RCTVideo else { + RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: \(String(describing: view))") + callback(nil) + return + } + + callback(view) + } + } + @objc(save:reactTag:resolver:rejecter:) func save(options: NSDictionary, reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.prependUIBlock { _, viewRegistry in - let view = viewRegistry?[reactTag] - if !(view is RCTVideo) { - RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view)) - } else if let view = view as? RCTVideo { - view.save(options: options, resolve: resolve, reject: reject) - } - } + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.save(options: options, resolve: resolve, reject: reject) + }) + } + + @objc(seek:reactTag:) + func seek(info: NSDictionary, reactTag: NSNumber) { + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.setSeek(info) + }) } @objc(setLicenseResult:licenseUrl:reactTag:) func setLicenseResult(license: NSString, licenseUrl: NSString, reactTag: NSNumber) { - bridge.uiManager.prependUIBlock { _, viewRegistry in - let view = viewRegistry?[reactTag] - if !(view is RCTVideo) { - RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view)) - } else if let view = view as? RCTVideo { - view.setLicenseResult(license as String, licenseUrl as String) - } - } + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.setLicenseResult(license as String, licenseUrl as String) + }) } @objc(setLicenseResultError:licenseUrl:reactTag:) func setLicenseResultError(error: NSString, licenseUrl: NSString, reactTag: NSNumber) { - bridge.uiManager.prependUIBlock { _, viewRegistry in - let view = viewRegistry?[reactTag] - if !(view is RCTVideo) { - RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view)) - } else if let view = view as? RCTVideo { - view.setLicenseResultError(error as String, licenseUrl as String) - } - } + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.setLicenseResultError(error as String, licenseUrl as String) + }) } @objc(dismissFullscreenPlayer:) func dismissFullscreenPlayer(_ reactTag: NSNumber) { - bridge.uiManager.prependUIBlock { _, viewRegistry in - let view = viewRegistry?[reactTag] - if !(view is RCTVideo) { - RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view)) - } else if let view = view as? RCTVideo { - view.dismissFullscreenPlayer() - } - } + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.dismissFullscreenPlayer() + }) } @objc(presentFullscreenPlayer:) func presentFullscreenPlayer(_ reactTag: NSNumber) { - bridge.uiManager.prependUIBlock { _, viewRegistry in - let view = viewRegistry?[reactTag] - if !(view is RCTVideo) { - RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view)) - } else if let view = view as? RCTVideo { - view.presentFullscreenPlayer() - } - } + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.presentFullscreenPlayer() + }) } @objc(setPlayerPauseState:reactTag:) func setPlayerPauseState(paused: NSNumber, reactTag: NSNumber) { - bridge.uiManager.prependUIBlock { _, viewRegistry in - let view = viewRegistry?[reactTag] - if !(view is RCTVideo) { - RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view)) - } else if let view = view as? RCTVideo { - let paused = paused.boolValue - view.setPaused(paused) - } - } + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.setPaused(paused.boolValue) + }) } override class func requiresMainQueueSetup() -> Bool { diff --git a/src/Video.tsx b/src/Video.tsx index e496efc2..fac54d25 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -227,19 +227,22 @@ const Video = forwardRef( return; } + const callSeekFunction = () => { + VideoManager.seek( + { + time, + tolerance: tolerance || 0, + }, + getReactTag(nativeRef), + ); + }; + Platform.select({ - ios: () => { - nativeRef.current?.setNativeProps({ - seek: { - time, - tolerance: tolerance || 0, - }, - }); - }, + ios: callSeekFunction, + android: callSeekFunction, default: () => { - nativeRef.current?.setNativeProps({ - seek: time, - }); + // TODO: Implement VideoManager.seek for windows + nativeRef.current?.setNativeProps({seek: time}); }, })(); }, []); diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 6d718b8d..fd53d328 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -537,6 +537,7 @@ export type VideoSaveData = { export interface VideoManagerType { save: (option: object, reactTag: number) => Promise; + seek: (option: Seek, reactTag: number) => Promise; setPlayerPauseState: (paused: boolean, reactTag: number) => Promise; setLicenseResult: ( result: string,