feat: add ability to define poster props as Image type and render poster as custom component (#3972)

This commit is contained in:
Kamil Moskała
2024-07-22 22:38:35 +02:00
committed by GitHub
parent 1ee5811c8e
commit adbd06e2df
10 changed files with 184 additions and 72 deletions

View File

@@ -8,7 +8,13 @@ import React, {
} from 'react';
import type {ElementRef} from 'react';
import {View, StyleSheet, Image, Platform, processColor} from 'react-native';
import type {StyleProp, ImageStyle, NativeSyntheticEvent} from 'react-native';
import type {
StyleProp,
ImageStyle,
NativeSyntheticEvent,
ViewStyle,
ImageResizeMode,
} from 'react-native';
import NativeVideoComponent from './specs/VideoNativeComponent';
import type {
@@ -67,8 +73,9 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
source,
style,
resizeMode,
posterResizeMode,
poster,
posterResizeMode,
renderLoader,
drm,
textTracks,
selectedVideoTrack,
@@ -113,25 +120,28 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
ref,
) => {
const nativeRef = useRef<ElementRef<typeof NativeVideoComponent>>(null);
const [showPoster, setShowPoster] = useState(!!poster);
const isPosterDeprecated = typeof poster === 'string';
const hasPoster = useMemo(() => {
if (renderLoader) {
return true;
}
if (isPosterDeprecated) {
return !!poster;
}
return !!poster?.source;
}, [isPosterDeprecated, poster, renderLoader]);
const [showPoster, setShowPoster] = useState(hasPoster);
const [
_restoreUserInterfaceForPIPStopCompletionHandler,
setRestoreUserInterfaceForPIPStopCompletionHandler,
] = useState<boolean | undefined>();
const hasPoster = !!poster;
const posterStyle = useMemo<StyleProp<ImageStyle>>(
() => ({
...StyleSheet.absoluteFillObject,
resizeMode:
posterResizeMode && posterResizeMode !== 'none'
? posterResizeMode
: 'contain',
}),
[posterResizeMode],
);
const src = useMemo<VideoSrc | undefined>(() => {
if (!source) {
return undefined;
@@ -598,13 +608,78 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
: ViewType.SURFACE;
}, [drm, useSecureView, useTextureView, viewType]);
const _renderPoster = useCallback(() => {
if (!hasPoster || !showPoster) {
return null;
}
// poster resize mode
let _posterResizeMode: ImageResizeMode = 'contain';
if (!isPosterDeprecated && poster?.resizeMode) {
_posterResizeMode = poster.resizeMode;
} else if (posterResizeMode && posterResizeMode !== 'none') {
_posterResizeMode = posterResizeMode;
}
// poster style
const baseStyle: StyleProp<ImageStyle> = {
...StyleSheet.absoluteFillObject,
resizeMode: _posterResizeMode,
};
let posterStyle: StyleProp<ImageStyle> = baseStyle;
if (!isPosterDeprecated && poster?.style) {
const styles = Array.isArray(poster.style)
? poster.style
: [poster.style];
posterStyle = [baseStyle, ...styles];
}
// render poster
if (renderLoader && (poster || posterResizeMode)) {
console.warn(
'You provided both `renderLoader` and `poster` or `posterResizeMode` props. `renderLoader` will be used.',
);
}
// render loader
if (renderLoader) {
return <View style={StyleSheet.absoluteFill}>{renderLoader}</View>;
}
return (
<Image
{...(isPosterDeprecated ? {} : poster)}
source={isPosterDeprecated ? {uri: poster} : poster?.source}
style={posterStyle}
/>
);
}, [
hasPoster,
isPosterDeprecated,
poster,
posterResizeMode,
renderLoader,
showPoster,
]);
const _style: StyleProp<ViewStyle> = useMemo(
() => ({
...StyleSheet.absoluteFillObject,
...(showPoster ? {display: 'none'} : {}),
}),
[showPoster],
);
return (
<View style={style}>
<NativeVideoComponent
ref={nativeRef}
{...rest}
src={src}
style={StyleSheet.absoluteFill}
style={_style}
resizeMode={resizeMode}
restoreUserInterfaceForPIPStopCompletionHandler={
_restoreUserInterfaceForPIPStopCompletionHandler
@@ -679,9 +754,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
}
viewType={_viewType}
/>
{hasPoster && showPoster ? (
<Image style={posterStyle} source={{uri: poster}} />
) : null}
{_renderPoster()}
</View>
);
},

View File

@@ -1,6 +1,14 @@
import type {ISO639_1} from './language';
import type {ReactVideoEvents} from './events';
import type {StyleProp, ViewProps, ViewStyle} from 'react-native';
import type {
ImageProps,
StyleProp,
ViewProps,
ViewStyle,
ImageRequireSource,
ImageURISource,
} from 'react-native';
import type {ReactNode} from 'react';
import type VideoResizeMode from './ResizeMode';
import type FilterType from './FilterType';
import type ViewType from './ViewType';
@@ -34,6 +42,13 @@ export type ReactVideoSource = Readonly<
}
>;
export type ReactVideoPosterSource = ImageURISource | ImageRequireSource;
export type ReactVideoPoster = Omit<ImageProps, 'source'> & {
// prevents giving source in the array
source?: ReactVideoPosterSource;
};
export type VideoMetadata = Readonly<{
title?: string;
subtitle?: string;
@@ -243,12 +258,14 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
pictureInPicture?: boolean; // iOS
playInBackground?: boolean;
playWhenInactive?: boolean; // iOS
poster?: string;
poster?: string | ReactVideoPoster; // string is deprecated
/** @deprecated use **resizeMode** key in **poster** props instead */
posterResizeMode?: EnumValues<PosterResizeModeType>;
preferredForwardBufferDuration?: number; // iOS
preventsDisplaySleepDuringVideoPlayback?: boolean;
progressUpdateInterval?: number;
rate?: number;
renderLoader?: ReactNode;
repeat?: boolean;
reportBandwidth?: boolean; //Android
resizeMode?: EnumValues<VideoResizeMode>;