refactor: internal refactor for prepare new arch (#3980)

* chore(js): fix typo

* refactor(js): refactor type code for codegen

* refactor(js): refactor Video component

- parse shutterColor value within JS
- remove internal fullscreen state

* chore(js): add deprecation warning comment

* fix(js): fix return type

* fix(js): fix import path

* refactor(android): apply changed API for new arch

* refactor(ios): apply changed API for new arch

* fix(ios): fix wrong name

* refactor: refactor VideoDecoderProperties

- rename and add wrapper

* refactor(android): Code fixes for backward compatibility with Kotlin
This commit is contained in:
YangJH 2024-07-12 17:27:42 +09:00 committed by GitHub
parent de8ade0620
commit c2084c2ace
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 284 additions and 299 deletions

View File

@ -47,7 +47,7 @@ enum class EventTypes(val eventName: String) {
companion object {
fun toMap() =
mutableMapOf<String, Any>().apply {
EventTypes.entries.forEach { eventType ->
EventTypes.values().toList().forEach { eventType ->
put("top${eventType.eventName.removePrefix("on")}", mapOf("registrationName" to eventType.eventName))
}
}

View File

@ -7,7 +7,6 @@ import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.media3.common.util.Util;
import com.brentvatne.common.api.BufferConfig;
import com.brentvatne.common.api.BufferingStrategy;
@ -203,7 +202,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
}
@ReactProp(name = PROP_TEXT_TRACKS)
public void setPropTextTracks(final ReactExoplayerView videoView,
public void setTextTracks(final ReactExoplayerView videoView,
@Nullable ReadableArray textTracks) {
SideLoadedTextTrackList sideLoadedTextTracks = SideLoadedTextTrackList.Companion.parse(textTracks);
videoView.setTextTracks(sideLoadedTextTracks);
@ -245,12 +244,12 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
}
@ReactProp(name = PROP_MAXIMUM_BIT_RATE)
public void setMaxBitRate(final ReactExoplayerView videoView, final int maxBitRate) {
videoView.setMaxBitRateModifier(maxBitRate);
public void setMaxBitRate(final ReactExoplayerView videoView, final float maxBitRate) {
videoView.setMaxBitRateModifier((int)maxBitRate);
}
@ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT)
public void minLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) {
public void setMinLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) {
videoView.setMinLoadRetryCountModifier(minLoadRetryCount);
}
@ -310,12 +309,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setSubtitleStyle(SubtitleStyle.parse(src));
}
@ReactProp(name = PROP_SHUTTER_COLOR, customType = "Color")
public void setShutterColor(final ReactExoplayerView videoView, final Integer color) {
videoView.setShutterColor(color == null ? Color.BLACK : color);
@ReactProp(name = PROP_SHUTTER_COLOR, defaultInt = 0)
public void setShutterColor(final ReactExoplayerView videoView, final int color) {
videoView.setShutterColor(color == 0 ? Color.BLACK : color);
}
@ReactProp(name = PROP_BUFFER_CONFIG)
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
BufferConfig config = BufferConfig.parse(bufferConfig);

View File

@ -13,7 +13,7 @@ class ReactVideoPackage(private val config: ReactExoplayerConfig? = null) : Reac
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
listOf(
VideoDecoderPropertiesModule(reactContext),
VideoDecoderInfoModule(reactContext),
VideoManagerModule(reactContext)
)

View File

@ -11,7 +11,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import java.util.UUID
class VideoDecoderPropertiesModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) {
class VideoDecoderInfoModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = REACT_CLASS
@ReactMethod
@ -37,36 +37,36 @@ class VideoDecoderPropertiesModule(reactContext: ReactApplicationContext?) : Rea
}
@ReactMethod
fun isCodecSupported(mimeType: String?, width: Int, height: Int, p: Promise) {
fun isCodecSupported(mimeType: String?, width: Double, height: Double, p: Promise?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
p.resolve("unsupported")
p?.resolve("unsupported")
return
}
val mRegularCodecs = MediaCodecList(MediaCodecList.REGULAR_CODECS)
val format = MediaFormat.createVideoFormat(mimeType!!, width, height)
val format = MediaFormat.createVideoFormat(mimeType!!, width.toInt(), height.toInt())
val codecName = mRegularCodecs.findDecoderForFormat(format)
if (codecName == null) {
p.resolve("unsupported")
p?.resolve("unsupported")
return
}
// Fallback for android < 10
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
p.resolve("software")
p?.resolve("software")
return
}
val isHardwareAccelerated = mRegularCodecs.codecInfos.any {
it.name.equals(codecName, ignoreCase = true) && it.isHardwareAccelerated
}
p.resolve(if (isHardwareAccelerated) "software" else "hardware")
p?.resolve(if (isHardwareAccelerated) "software" else "hardware")
}
@ReactMethod
fun isHEVCSupported(p: Promise) = isCodecSupported("video/hevc", 1920, 1080, p)
fun isHEVCSupported(p: Promise) = isCodecSupported("video/hevc", 1920.0, 1080.0, p)
companion object {
private val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
private const val SECURITY_LEVEL_PROPERTY = "securityLevel"
private const val REACT_CLASS = "VideoDecoderProperties"
private const val REACT_CLASS = "VideoDecoderInfoModule"
}
}

View File

@ -1,12 +1,10 @@
package com.brentvatne.react
import com.brentvatne.common.toolbox.ReactBridgeUtils
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
@ -37,31 +35,33 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB
}
@ReactMethod
fun setPlayerPauseState(paused: Boolean?, reactTag: Int) {
fun setPlayerPauseStateCmd(reactTag: Int, paused: Boolean?) {
performOnPlayerView(reactTag) {
it?.setPausedModifier(paused!!)
}
}
@ReactMethod
fun seek(info: ReadableMap, reactTag: Int) {
if (!info.hasKey("time")) {
return
}
val time = ReactBridgeUtils.safeGetInt(info, "time")
fun seekCmd(reactTag: Int, time: Float, tolerance: Float) {
performOnPlayerView(reactTag) {
it?.seekTo((time * 1000f).roundToInt().toLong())
}
}
@ReactMethod
fun setVolume(volume: Float, reactTag: Int) {
fun setVolumeCmd(reactTag: Int, volume: Float) {
performOnPlayerView(reactTag) {
it?.setVolumeModifier(volume)
}
}
@ReactMethod
fun setFullScreenCmd(reactTag: Int, fullScreen: Boolean) {
performOnPlayerView(reactTag) {
it?.setFullscreen(fullScreen)
}
}
@ReactMethod
fun getCurrentPosition(reactTag: Int, promise: Promise) {
performOnPlayerView(reactTag) {
@ -69,13 +69,6 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB
}
}
@ReactMethod
fun setFullScreen(fullScreen: Boolean, reactTag: Int) {
performOnPlayerView(reactTag) {
it?.setFullscreen(fullScreen)
}
}
companion object {
private const val REACT_CLASS = "VideoManager"
}

View File

@ -762,15 +762,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
}
@objc
func setSeek(_ info: NSDictionary!) {
let seekTime: NSNumber! = info["time"] as! NSNumber
let seekTolerance: NSNumber! = info["tolerance"] as! NSNumber
func setSeek(_ time: NSNumber, _ tolerance: NSNumber) {
let item: AVPlayerItem? = _player?.currentItem
_pendingSeek = true
guard item != nil, let player = _player, let item, item.status == AVPlayerItem.Status.readyToPlay else {
_pendingSeekTime = seekTime.floatValue
_pendingSeekTime = time.floatValue
return
}
@ -778,15 +776,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
player: player,
playerItem: item,
paused: _paused,
seekTime: seekTime.floatValue,
seekTolerance: seekTolerance.floatValue
seekTime: time.floatValue,
seekTolerance: tolerance.floatValue
) { [weak self] (_: Bool) in
guard let self else { return }
self._playerObserver.addTimeObserverIfNotSet()
self.setPaused(self._paused)
self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))),
"seekTime": seekTime,
"seekTime": time,
"target": self.reactTag])
}
@ -1303,7 +1301,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
// MARK: - Export
@objc
func save(options: NSDictionary!, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
func save(_ options: NSDictionary!, _ resolve: @escaping RCTPromiseResolveBlock, _ reject: @escaping RCTPromiseRejectBlock) {
RCTVideoSave.save(
options: options,
resolve: resolve,
@ -1320,14 +1318,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
_resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl)
}
func dismissFullscreenPlayer() {
setFullscreen(false)
}
func presentFullscreenPlayer() {
setFullscreen(true)
}
// MARK: - RCTPlayerObserverHandler
func handleTimeUpdate(time _: CMTime) {
@ -1381,18 +1371,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
Task {
if self._pendingSeek {
self.setSeek([
"time": NSNumber(value: self._pendingSeekTime),
"tolerance": NSNumber(value: 100),
])
self.setSeek(NSNumber(value: self._pendingSeekTime), NSNumber(value: 100))
self._pendingSeek = false
}
if self._startPosition >= 0 {
self.setSeek([
"time": NSNumber(value: self._startPosition),
"tolerance": NSNumber(value: 100),
])
self.setSeek(NSNumber(value: self._startPosition), NSNumber(value: 100))
self._startPosition = -1
}

View File

@ -1,5 +1,5 @@
#import "React/RCTViewManager.h"
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager)
@ -37,7 +37,7 @@ RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
RCT_EXPORT_VIEW_PROPERTY(localSourceEncryptionKeyScheme, NSString);
RCT_EXPORT_VIEW_PROPERTY(subtitleStyle, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(showNotificationControls, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showNotificationControls, BOOL);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
@ -68,31 +68,21 @@ RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onTextTrackDataChanged, RCTDirectEventBlock);
RCT_EXTERN_METHOD(seekCmd : (nonnull NSNumber*)reactTag time : (nonnull NSNumber*)time tolerance : (nonnull NSNumber*)tolerance)
RCT_EXTERN_METHOD(setLicenseResultCmd : (nonnull NSNumber*)reactTag lisence : (NSString*)license licenseUrl : (NSString*)licenseUrl)
RCT_EXTERN_METHOD(setLicenseResultErrorCmd : (nonnull NSNumber*)reactTag error : (NSString*)error licenseUrl : (NSString*)licenseUrl)
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(save
: (NSDictionary*)options reactTag
: (nonnull NSNumber*)reactTag resolver
: (RCTPromiseResolveBlock)resolve rejecter
: (nonnull NSNumber*)reactTag options
: (NSDictionary*)options resolve
: (RCTPromiseResolveBlock)resolve reject
: (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)
RCT_EXTERN_METHOD(setPlayerPauseState : (nonnull NSNumber*)paused reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(presentFullscreenPlayer : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(dismissFullscreenPlayer : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(setVolume : (nonnull float*)volume reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(getCurrentPosition
: (nonnull NSNumber*)reactTag resolver
: (RCTPromiseResolveBlock)resolve rejecter
: (nonnull NSNumber*)reactTag resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(setFullScreen : (BOOL)fullScreen reactTag : (nonnull NSNumber*)reactTag)
@end

View File

@ -30,76 +30,62 @@ class RCTVideoManager: RCTViewManager {
}
}
@objc(save:reactTag:resolver:rejecter:)
func save(options: NSDictionary, reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
@objc(seekCmd:time:tolerance:)
func seekCmd(_ reactTag: NSNumber, time: NSNumber, tolerance: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.save(options: options, resolve: resolve, reject: reject)
videoView?.setSeek(time, tolerance)
})
}
@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) {
@objc(setLicenseResultCmd:license:licenseUrl:)
func setLicenseResultCmd(_ reactTag: NSNumber, license: NSString, licenseUrl: NSString) {
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) {
@objc(setLicenseResultErrorCmd:error:licenseUrl:)
func setLicenseResultErrorCmd(_ reactTag: NSNumber, error: NSString, licenseUrl: NSString) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setLicenseResultError(error as String, licenseUrl as String)
})
}
@objc(dismissFullscreenPlayer:)
func dismissFullscreenPlayer(_ reactTag: NSNumber) {
@objc(setPlayerPauseStateCmd:paused:)
func setPlayerPauseStateCmd(_ reactTag: NSNumber, paused: Bool) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.dismissFullscreenPlayer()
videoView?.setPaused(paused)
})
}
@objc(presentFullscreenPlayer:)
func presentFullscreenPlayer(_ reactTag: NSNumber) {
@objc(setVolumeCmd:volume:)
func setVolumeCmd(_ reactTag: NSNumber, volume: Float) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.presentFullscreenPlayer()
videoView?.setVolume(volume)
})
}
@objc(setPlayerPauseState:reactTag:)
func setPlayerPauseState(paused: NSNumber, reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setPaused(paused.boolValue)
})
}
@objc(setVolume:reactTag:)
func setVolume(value: Float, reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setVolume(value)
})
}
@objc(getCurrentPosition:resolver:rejecter:)
func getCurrentPosition(reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.getCurrentPlaybackTime(resolve, reject)
})
}
@objc(setFullScreen:reactTag:)
func setFullScreen(fullScreen: Bool, reactTag: NSNumber) {
@objc(setFullScreenCmd:fullscreen:)
func setFullScreenCmd(_ reactTag: NSNumber, fullScreen: Bool) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setFullscreen(fullScreen)
})
}
@objc(save:options:resolve:reject:)
func save(_ reactTag: NSNumber, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.save(options, resolve, reject)
})
}
@objc(getCurrentPosition:resolve:reject:)
func getCurrentPosition(_ reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.getCurrentPlaybackTime(resolve, reject)
})
}
override class func requiresMainQueueSetup() -> Bool {
return true
}

View File

@ -5,57 +5,47 @@ import React, {
useRef,
forwardRef,
useImperativeHandle,
type ComponentRef,
} from 'react';
import {
View,
StyleSheet,
Image,
Platform,
type StyleProp,
type ImageStyle,
type NativeSyntheticEvent,
} from 'react-native';
import type {ElementRef} from 'react';
import {View, StyleSheet, Image, Platform, processColor} from 'react-native';
import type {StyleProp, ImageStyle, NativeSyntheticEvent} from 'react-native';
import NativeVideoComponent, {
type OnAudioFocusChangedData,
type OnAudioTracksData,
type OnBandwidthUpdateData,
type OnBufferData,
type OnControlsVisibilityChange,
type OnExternalPlaybackChangeData,
type OnGetLicenseData,
type OnLoadStartData,
type OnPictureInPictureStatusChangedData,
type OnPlaybackStateChangedData,
type OnProgressData,
type OnSeekData,
type OnTextTrackDataChangedData,
type OnTimedMetadataData,
type OnVideoAspectRatioData,
type OnVideoErrorData,
type OnVideoTracksData,
type VideoComponentType,
type VideoSrc,
import NativeVideoComponent from './specs/VideoNativeComponent';
import type {
OnAudioFocusChangedData,
OnAudioTracksData,
OnBandwidthUpdateData,
OnBufferData,
OnControlsVisibilityChange,
OnExternalPlaybackChangeData,
OnGetLicenseData,
OnLoadStartData,
OnPictureInPictureStatusChangedData,
OnPlaybackStateChangedData,
OnProgressData,
OnSeekData,
OnTextTrackDataChangedData,
OnTimedMetadataData,
OnVideoAspectRatioData,
OnVideoErrorData,
OnVideoTracksData,
VideoSrc,
} from './specs/VideoNativeComponent';
import {
generateHeaderForNative,
getReactTag,
resolveAssetSourceForVideo,
} from './utils';
import {VideoManager} from './specs/VideoNativeComponent';
import {
type OnLoadData,
type OnTextTracksData,
type OnReceiveAdEventData,
type ReactVideoProps,
ViewType,
import NativeVideoManager from './specs/NativeVideoManager';
import type {VideoSaveData} from './specs/NativeVideoManager';
import {ViewType} from './types';
import type {
OnLoadData,
OnTextTracksData,
OnReceiveAdEventData,
ReactVideoProps,
} from './types';
export type VideoSaveData = {
uri: string;
};
export interface VideoRef {
seek: (time: number, tolerance?: number) => void;
resume: () => void;
@ -65,10 +55,10 @@ export interface VideoRef {
restoreUserInterfaceForPictureInPictureStopCompleted: (
restore: boolean,
) => void;
save: (options: object) => Promise<VideoSaveData>;
setVolume: (volume: number) => void;
getCurrentPosition: () => Promise<number>;
setFullScreen: (fullScreen: boolean) => void;
save: (options: object) => Promise<VideoSaveData> | void;
getCurrentPosition: () => Promise<number>;
}
const Video = forwardRef<VideoRef, ReactVideoProps>(
@ -79,7 +69,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
resizeMode,
posterResizeMode,
poster,
fullscreen,
drm,
textTracks,
selectedVideoTrack,
@ -88,6 +77,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
useTextureView,
useSecureView,
viewType,
shutterColor,
onLoadStart,
onLoad,
onError,
@ -122,9 +112,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
},
ref,
) => {
const nativeRef = useRef<ComponentRef<VideoComponentType>>(null);
const nativeRef = useRef<ElementRef<typeof NativeVideoComponent>>(null);
const [showPoster, setShowPoster] = useState(!!poster);
const [isFullscreen, setIsFullscreen] = useState(fullscreen);
const [
_restoreUserInterfaceForPIPStopCompletionHandler,
setRestoreUserInterfaceForPIPStopCompletionHandler,
@ -274,12 +263,10 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
}
const callSeekFunction = () => {
VideoManager.seek(
{
time,
tolerance: tolerance || 0,
},
NativeVideoManager.seekCmd(
getReactTag(nativeRef),
time,
tolerance || 0,
);
};
@ -287,31 +274,59 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
ios: callSeekFunction,
android: callSeekFunction,
default: () => {
// TODO: Implement VideoManager.seek for windows
// TODO: Implement VideoManager.seekCmd for windows
nativeRef.current?.setNativeProps({seek: time});
},
})();
}, []);
const presentFullscreenPlayer = useCallback(() => {
setIsFullscreen(true);
}, [setIsFullscreen]);
const dismissFullscreenPlayer = useCallback(() => {
setIsFullscreen(false);
}, [setIsFullscreen]);
const save = useCallback((options: object) => {
// VideoManager.save can be null on android & windows
return VideoManager.save?.(options, getReactTag(nativeRef));
}, []);
const pause = useCallback(() => {
return VideoManager.setPlayerPauseState(true, getReactTag(nativeRef));
return NativeVideoManager.setPlayerPauseStateCmd(
getReactTag(nativeRef),
true,
);
}, []);
const resume = useCallback(() => {
return VideoManager.setPlayerPauseState(false, getReactTag(nativeRef));
return NativeVideoManager.setPlayerPauseStateCmd(
getReactTag(nativeRef),
false,
);
}, []);
const setVolume = useCallback((volume: number) => {
return NativeVideoManager.setVolumeCmd(getReactTag(nativeRef), volume);
}, []);
const setFullScreen = useCallback((fullScreen: boolean) => {
return NativeVideoManager.setFullScreenCmd(
getReactTag(nativeRef),
fullScreen,
);
}, []);
const presentFullscreenPlayer = useCallback(
() => setFullScreen(true),
[setFullScreen],
);
const dismissFullscreenPlayer = useCallback(
() => setFullScreen(false),
[setFullScreen],
);
const save = useCallback((options: object) => {
// VideoManager.save can be null on android & windows
if (Platform.OS !== 'ios') {
return;
}
// @todo Must implement it in a different way.
return NativeVideoManager.save?.(getReactTag(nativeRef), options);
}, []);
const getCurrentPosition = useCallback(() => {
// @todo Must implement it in a different way.
return NativeVideoManager.getCurrentPosition(getReactTag(nativeRef));
}, []);
const restoreUserInterfaceForPictureInPictureStopCompleted = useCallback(
@ -321,18 +336,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[setRestoreUserInterfaceForPIPStopCompletionHandler],
);
const setVolume = useCallback((volume: number) => {
return VideoManager.setVolume(volume, getReactTag(nativeRef));
}, []);
const getCurrentPosition = useCallback(() => {
return VideoManager.getCurrentPosition(getReactTag(nativeRef));
}, []);
const setFullScreen = useCallback((fullScreen: boolean) => {
return VideoManager.setFullScreen(fullScreen, getReactTag(nativeRef));
}, []);
const onVideoLoadStart = useCallback(
(e: NativeSyntheticEvent<OnLoadStartData>) => {
hasPoster && setShowPoster(true);
@ -379,6 +382,11 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[onPlaybackStateChanged],
);
const _shutterColor = useMemo(() => {
const color = processColor(shutterColor);
return typeof color === 'number' ? color : undefined;
}, [shutterColor]);
// android only
const _onTimedMetadata = useCallback(
(e: NativeSyntheticEvent<OnTimedMetadataData>) => {
@ -494,56 +502,41 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[onControlsVisibilityChange],
);
const useExternalGetLicense = drm?.getLicense instanceof Function;
const usingExternalGetLicense = drm?.getLicense instanceof Function;
const onGetLicense = useCallback(
(event: NativeSyntheticEvent<OnGetLicenseData>) => {
if (useExternalGetLicense) {
const data = event.nativeEvent;
if (data && data.spcBase64) {
const getLicenseOverride = drm.getLicense(
async (event: NativeSyntheticEvent<OnGetLicenseData>) => {
if (!usingExternalGetLicense) {
return;
}
const data = event.nativeEvent;
let result;
if (data?.spcBase64) {
try {
// Handles both scenarios, getLicenseOverride being a promise and not.
const license = await drm.getLicense(
data.spcBase64,
data.contentId,
data.licenseUrl,
data.loadedLicenseUrl,
);
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
getLicensePromise
.then((result) => {
if (result !== undefined) {
nativeRef.current &&
VideoManager.setLicenseResult(
result,
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
} else {
nativeRef.current &&
VideoManager.setLicenseResultError(
'Empty license result',
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
}
})
.catch(() => {
nativeRef.current &&
VideoManager.setLicenseResultError(
'fetch error',
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
});
} else {
VideoManager.setLicenseResultError(
'No spc received',
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
result =
typeof license === 'string' ? license : 'Empty license result';
} catch {
result = 'fetch error';
}
} else {
result = 'No spc received';
}
if (nativeRef.current) {
NativeVideoManager.setLicenseResultErrorCmd(
getReactTag(nativeRef),
result,
data.loadedLicenseUrl,
);
}
},
[drm, useExternalGetLicense],
[drm, usingExternalGetLicense],
);
useImperativeHandle(
@ -613,7 +606,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
src={src}
style={StyleSheet.absoluteFill}
resizeMode={resizeMode}
fullscreen={isFullscreen}
restoreUserInterfaceForPIPStopCompletionHandler={
_restoreUserInterfaceForPIPStopCompletionHandler
}
@ -621,7 +613,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
selectedTextTrack={_selectedTextTrack}
selectedAudioTrack={_selectedAudioTrack}
selectedVideoTrack={_selectedVideoTrack}
onGetLicense={useExternalGetLicense ? onGetLicense : undefined}
shutterColor={_shutterColor}
onGetLicense={usingExternalGetLicense ? onGetLicense : undefined}
onVideoLoad={
onLoad || hasPoster
? (onVideoLoad as (e: NativeSyntheticEvent<object>) => void)

View File

@ -0,0 +1,29 @@
import {Platform} from 'react-native';
import NativeVideoDecoderInfoModule from './specs/NativeVideoDecoderInfoModule';
const errMsgGen = (moduleName: string, propertyName: string) =>
`The method or property ${moduleName}.${propertyName} is not available on ${Platform.OS}.`;
export const VideoDecoderProperties = {
async getWidevineLevel() {
if (Platform.OS !== 'android') {
throw new Error(errMsgGen('VideoDecoderProperties', 'getWidevineLevel'));
}
return NativeVideoDecoderInfoModule.getWidevineLevel();
},
async isCodecSupported(
...args: Parameters<typeof NativeVideoDecoderInfoModule.isCodecSupported>
) {
if (Platform.OS !== 'android') {
throw new Error(errMsgGen('VideoDecoderProperties', 'isCodecSupported'));
}
return NativeVideoDecoderInfoModule.isCodecSupported(...args);
},
async isHEVCSupported() {
if (Platform.OS !== 'android') {
throw new Error(errMsgGen('VideoDecoderProperties', 'isHEVCSupported'));
}
return NativeVideoDecoderInfoModule.isHEVCSupported();
},
};

View File

@ -1,5 +1,5 @@
import Video from './Video';
export {VideoDecoderProperties} from './specs/VideoNativeComponent';
export {VideoDecoderProperties} from './VideoDecoderProperties';
export * from './types';
export type {VideoRef} from './Video';
export default Video;

View File

@ -0,0 +1,15 @@
import {NativeModules} from 'react-native';
import type {Int32} from 'react-native/Libraries/Types/CodegenTypes';
// @TODO rename to "Spec" when applying new arch
interface VideoDecoderInfoModuleType {
getWidevineLevel: () => Promise<Int32>;
isCodecSupported: (
mimeType: string,
width: Int32,
height: Int32,
) => Promise<'unsupported' | 'hardware' | 'software'>;
isHEVCSupported: () => Promise<'unsupported' | 'hardware' | 'software'>;
}
export default NativeModules.VideoDecoderInfoModule as VideoDecoderInfoModuleType;

View File

@ -0,0 +1,32 @@
import {NativeModules} from 'react-native';
import type {
Int32,
Float,
UnsafeObject,
} from 'react-native/Libraries/Types/CodegenTypes';
export type VideoSaveData = {
uri: string;
};
// @TODO rename to "Spec" when applying new arch
export interface VideoManagerType {
seekCmd: (reactTag: Int32, time: Float, tolerance?: Float) => Promise<void>;
setPlayerPauseStateCmd: (reactTag: Int32, paused: boolean) => Promise<void>;
setLicenseResultCmd: (
reactTag: Int32,
result: string,
licenseUrl: string,
) => Promise<void>;
setLicenseResultErrorCmd: (
reactTag: Int32,
error: string,
licenseUrl: string,
) => Promise<void>;
setFullScreenCmd: (reactTag: Int32, fullScreen: boolean) => Promise<void>;
setVolumeCmd: (reactTag: Int32, volume: number) => Promise<void>;
save: (reactTag: Int32, option: UnsafeObject) => Promise<VideoSaveData>;
getCurrentPosition: (reactTag: Int32) => Promise<Int32>;
}
export default NativeModules.VideoManager as VideoManagerType;

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/ban-types */
import type {HostComponent, ViewProps} from 'react-native';
import {NativeModules, requireNativeComponent} from 'react-native';
import {requireNativeComponent} from 'react-native';
import type {
DirectEventHandler,
Double,
@ -91,11 +91,6 @@ type SelectedVideoTrack = Readonly<{
value?: string;
}>;
export type Seek = Readonly<{
time: Float;
tolerance?: Float;
}>;
type BufferConfigLive = Readonly<{
maxPlaybackSpeed?: Float;
minPlaybackSpeed?: Float;
@ -289,7 +284,7 @@ export type OnAudioFocusChangedData = Readonly<{
type ControlsStyles = Readonly<{
hideSeekBar?: boolean;
seekIncrementMS?: number;
seekIncrementMS?: Int32;
}>;
export type OnControlsVisibilityChange = Readonly<{
@ -300,10 +295,13 @@ export interface VideoNativeProps extends ViewProps {
src?: VideoSrc;
adTagUrl?: string;
allowsExternalPlayback?: boolean; // ios, true
disableFocus?: boolean; // android
maxBitRate?: Float;
resizeMode?: WithDefault<string, 'none'>;
repeat?: boolean;
automaticallyWaitsToMinimizeStalling?: boolean;
shutterColor?: Int32;
audioOutput?: WithDefault<string, 'speaker'>;
textTracks?: TextTracks;
selectedTextTrack?: SelectedTextTrack;
selectedAudioTrack?: SelectedAudioTrack;
@ -375,45 +373,8 @@ export interface VideoNativeProps extends ViewProps {
onVideoTracks?: DirectEventHandler<OnVideoTracksData>; // android
}
export type VideoComponentType = HostComponent<VideoNativeProps>;
export type VideoSaveData = {
uri: string;
};
export interface VideoManagerType {
save: (option: object, reactTag: number) => Promise<VideoSaveData>;
seek: (option: Seek, reactTag: number) => Promise<void>;
setPlayerPauseState: (paused: boolean, reactTag: number) => Promise<void>;
setLicenseResult: (
result: string,
licenseUrl: string,
reactTag: number,
) => Promise<void>;
setLicenseResultError: (
error: string,
licenseUrl: string,
reactTag: number,
) => Promise<void>;
setVolume: (volume: number, reactTag: number) => Promise<void>;
getCurrentPosition: (reactTag: number) => Promise<number>;
setFullScreen: (fullScreen: boolean, reactTag: number) => Promise<void>;
}
export interface VideoDecoderPropertiesType {
getWidevineLevel: () => Promise<number>;
isCodecSupported: (
mimeType: string,
width: number,
height: number,
) => Promise<'unsupported' | 'hardware' | 'software'>;
isHEVCSupported: () => Promise<'unsupported' | 'hardware' | 'software'>;
}
export const VideoManager = NativeModules.VideoManager as VideoManagerType;
export const VideoDecoderProperties =
NativeModules.VideoDecoderProperties as VideoDecoderPropertiesType;
type NativeVideoComponentType = HostComponent<VideoNativeProps>;
export default requireNativeComponent<VideoNativeProps>(
'RCTVideo',
) as VideoComponentType;
) as NativeVideoComponentType;

View File

@ -2,10 +2,10 @@
* Define Available view type for android
* these values shall match android spec, see ViewType.kt
*/
enum ResizeMode {
enum ViewType {
TEXTURE = 0,
SURFACE = 1,
SURFACE_SECURE = 2,
}
export default ResizeMode;
export default ViewType;

View File

@ -68,7 +68,7 @@ export type Drm = Readonly<{
contentId: string,
licenseUrl: string,
loadedLicenseUrl: string,
) => void; // ios
) => string | Promise<string>; // ios
/* eslint-enable @typescript-eslint/no-unused-vars */
}>;

View File

@ -43,6 +43,10 @@ export function resolveAssetSourceForVideo(
return source as ReactVideoSourceProperties;
}
/**
* @deprecated
* Do not use this fn anymore. "findNodeHandle" will be deprecated.
* */
export function getReactTag(
ref: RefObject<
| Component<unknown, unknown, unknown>