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
17 changed files with 284 additions and 299 deletions

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>