fix: ensure poster works as expected and add it to the sample (#3643)

* fix: ensure poster works as expected and add it to the sample
* chore: drop audioOnly property as not implemented on any platform
* fix(ios): do not save pause state before seeking
* fix(ts): onPlaybackRateChangeData was not correctly typed
This commit is contained in:
Olivier Bouillet 2024-04-05 10:35:57 +02:00 committed by GitHub
parent 051e884c8f
commit d6941392e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 50 additions and 27 deletions

View File

@ -7,6 +7,9 @@
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended" "plugin:@typescript-eslint/recommended"
], ],
"rules": {
"no-trailing-spaces": 1
},
"parserOptions": { "parserOptions": {
"requireConfigFile": false "requireConfigFile": false
} }

View File

@ -29,17 +29,6 @@ Indicates whether the player allows switching to external playback mode such as
- **true (default)** - allow switching to external playback mode - **true (default)** - allow switching to external playback mode
- **false** - Don't allow switching to external playback mode - **false** - Don't allow switching to external playback mode
### `audioOnly`
<PlatformsList types={['All']} />
Indicates whether the player should only play the audio track and instead of displaying the video track, show the poster instead.
- **false (default)** - Display the video as normal
- **true** - Show the poster and play the audio
For this to work, the poster prop must be set.
### `audioOutput` ### `audioOutput`
<PlatformsList types={['Android', 'iOS', 'visionOS']} /> <PlatformsList types={['Android', 'iOS', 'visionOS']} />

View File

@ -36,6 +36,7 @@ import Video, {
OnTextTrackDataChangedData, OnTextTrackDataChangedData,
TextTrackType, TextTrackType,
ISO639_1, ISO639_1,
OnSeekData,
OnPlaybackStateChangedData, OnPlaybackStateChangedData,
OnPlaybackRateChangeData, OnPlaybackRateChangeData,
} from 'react-native-video'; } from 'react-native-video';
@ -68,6 +69,7 @@ interface StateType {
srcListId: number; srcListId: number;
loop: boolean; loop: boolean;
showRNVControls: boolean; showRNVControls: boolean;
poster?: string;
} }
class VideoPlayer extends Component { class VideoPlayer extends Component {
@ -95,6 +97,7 @@ class VideoPlayer extends Component {
srcListId: 0, srcListId: 0,
loop: false, loop: false,
showRNVControls: false, showRNVControls: false,
poster: undefined,
}; };
seekerWidth = 0; seekerWidth = 0;
@ -140,7 +143,7 @@ class VideoPlayer extends Component {
type: TextTrackType.VTT, type: TextTrackType.VTT,
uri: 'https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt', uri: 'https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt',
}, },
] ],
}, },
]; ];
@ -190,6 +193,10 @@ class VideoPlayer extends Component {
}, },
]; ];
// poster which can be displayed
samplePoster =
'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png';
srcList = this.srcAllPlatformList.concat( srcList = this.srcAllPlatformList.concat(
Platform.OS === 'android' ? this.srcAndroidList : this.srcIosList, Platform.OS === 'android' ? this.srcAndroidList : this.srcIosList,
); );
@ -223,14 +230,26 @@ class VideoPlayer extends Component {
this.onTextTracks(data); this.onTextTracks(data);
}; };
onProgress = (data: OnProgressData) => { updateSeeker = () => {
if (!this.state.seeking) { // put this code in timeout as because it may be put just after a setState
setTimeout(()=> {
const position = this.calculateSeekerPosition(); const position = this.calculateSeekerPosition();
this.setSeekerPosition(position); this.setSeekerPosition(position);
} }, 1)
}
onProgress = (data: OnProgressData) => {
this.setState({currentTime: data.currentTime}); this.setState({currentTime: data.currentTime});
if (!this.state.seeking) {
this.updateSeeker()
}
}; };
onSeek = (data: OnSeekData) => {
this.setState({currentTime: data.currentTime});
this.updateSeeker()
}
onVideoLoadStart = () => { onVideoLoadStart = () => {
console.log('onVideoLoadStart'); console.log('onVideoLoadStart');
this.setState({isLoading: true}); this.setState({isLoading: true});
@ -662,6 +681,16 @@ class VideoPlayer extends Component {
}} }}
text="decoration" text="decoration"
/> />
<ToggleControl
isSelected={!!this.state.poster}
onPress={() => {
this.setState({
poster: this.state.poster ? undefined : this.samplePoster,
});
}}
selectedText="poster"
unselectedText="no poster"
/>
</View> </View>
<View style={styles.generalControls}> <View style={styles.generalControls}>
{/* shall be replaced by slider */} {/* shall be replaced by slider */}
@ -810,11 +839,13 @@ class VideoPlayer extends Component {
onAspectRatio={this.onAspectRatio} onAspectRatio={this.onAspectRatio}
onReadyForDisplay={this.onReadyForDisplay} onReadyForDisplay={this.onReadyForDisplay}
onBuffer={this.onVideoBuffer} onBuffer={this.onVideoBuffer}
onSeek={this.onSeek}
repeat={this.state.loop} repeat={this.state.loop}
selectedTextTrack={this.state.selectedTextTrack} selectedTextTrack={this.state.selectedTextTrack}
selectedAudioTrack={this.state.selectedAudioTrack} selectedAudioTrack={this.state.selectedAudioTrack}
playInBackground={false} playInBackground={false}
preventsDisplaySleepDuringVideoPlayback={true} preventsDisplaySleepDuringVideoPlayback={true}
poster={this.state.poster}
onPlaybackRateChange={this.onPlaybackRateChange} onPlaybackRateChange={this.onPlaybackRateChange}
onPlaybackStateChanged={this.onPlaybackStateChanged} onPlaybackStateChanged={this.onPlaybackStateChanged}
/> />

View File

@ -681,21 +681,18 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
_pendingSeekTime = seekTime.floatValue _pendingSeekTime = seekTime.floatValue
return return
} }
let wasPaused = _paused
RCTPlayerOperations.seek( RCTPlayerOperations.seek(
player: player, player: player,
playerItem: item, playerItem: item,
paused: wasPaused, paused: _paused,
seekTime: seekTime.floatValue, seekTime: seekTime.floatValue,
seekTolerance: seekTolerance.floatValue seekTolerance: seekTolerance.floatValue
) { [weak self] (_: Bool) in ) { [weak self] (_: Bool) in
guard let self else { return } guard let self else { return }
self._playerObserver.addTimeObserverIfNotSet() self._playerObserver.addTimeObserverIfNotSet()
if !wasPaused { self.setPaused(_paused)
self.setPaused(false)
}
self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))), self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))),
"seekTime": seekTime, "seekTime": seekTime,
"target": self.reactTag]) "target": self.reactTag])

View File

@ -48,6 +48,7 @@
"test": "echo no test available", "test": "echo no test available",
"check-ios": "scripts/swift-format.sh && scripts/swift-lint.sh && scripts/clang-format.sh", "check-ios": "scripts/swift-format.sh && scripts/swift-lint.sh && scripts/clang-format.sh",
"check-android": "scripts/kotlin-lint.sh", "check-android": "scripts/kotlin-lint.sh",
"check-all": "yarn check-android; yarn check-ios; yarn lint",
"codegen": "node ./node_modules/react-native/scripts/generate-codegen-artifacts.js --path ./ ./output" "codegen": "node ./node_modules/react-native/scripts/generate-codegen-artifacts.js --path ./ ./output"
}, },
"files": [ "files": [

View File

@ -121,6 +121,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
setRestoreUserInterfaceForPIPStopCompletionHandler, setRestoreUserInterfaceForPIPStopCompletionHandler,
] = useState<boolean | undefined>(); ] = useState<boolean | undefined>();
const hasPoster = !!poster;
const posterStyle = useMemo<StyleProp<ImageStyle>>( const posterStyle = useMemo<StyleProp<ImageStyle>>(
() => ({ () => ({
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
@ -286,19 +288,20 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
const onVideoLoadStart = useCallback( const onVideoLoadStart = useCallback(
(e: NativeSyntheticEvent<OnLoadStartData>) => { (e: NativeSyntheticEvent<OnLoadStartData>) => {
hasPoster && setShowPoster(true);
onLoadStart?.(e.nativeEvent); onLoadStart?.(e.nativeEvent);
}, },
[onLoadStart], [hasPoster, onLoadStart],
); );
const onVideoLoad = useCallback( const onVideoLoad = useCallback(
(e: NativeSyntheticEvent<OnLoadData>) => { (e: NativeSyntheticEvent<OnLoadData>) => {
if (Platform.OS === 'windows') { if (Platform.OS === 'windows') {
setShowPoster(false); hasPoster && setShowPoster(false);
} }
onLoad?.(e.nativeEvent); onLoad?.(e.nativeEvent);
}, },
[onLoad, setShowPoster], [onLoad, hasPoster, setShowPoster],
); );
const onVideoError = useCallback( const onVideoError = useCallback(
@ -388,9 +391,9 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
); );
const _onReadyForDisplay = useCallback(() => { const _onReadyForDisplay = useCallback(() => {
setShowPoster(false); hasPoster && setShowPoster(false);
onReadyForDisplay?.(); onReadyForDisplay?.();
}, [setShowPoster, onReadyForDisplay]); }, [setShowPoster, hasPoster, onReadyForDisplay]);
const _onPictureInPictureStatusChanged = useCallback( const _onPictureInPictureStatusChanged = useCallback(
(e: NativeSyntheticEvent<OnPictureInPictureStatusChangedData>) => { (e: NativeSyntheticEvent<OnPictureInPictureStatusChangedData>) => {
@ -567,7 +570,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
_onReceiveAdEvent as (e: NativeSyntheticEvent<object>) => void _onReceiveAdEvent as (e: NativeSyntheticEvent<object>) => void
} }
/> />
{showPoster ? ( {hasPoster && showPoster ? (
<Image style={posterStyle} source={{uri: poster}} /> <Image style={posterStyle} source={{uri: poster}} />
) : null} ) : null}
</View> </View>

View File

@ -186,7 +186,6 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
drm?: Drm; drm?: Drm;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
adTagUrl?: string; adTagUrl?: string;
audioOnly?: boolean;
audioOutput?: AudioOutput; // Mobile audioOutput?: AudioOutput; // Mobile
automaticallyWaitsToMinimizeStalling?: boolean; // iOS automaticallyWaitsToMinimizeStalling?: boolean; // iOS
bufferConfig?: BufferConfig; // Android bufferConfig?: BufferConfig; // Android