import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { LayoutRectangle, StyleSheet, useWindowDimensions, View } from 'react-native'; import FullScreenChz from 'react-native-fullscreen-chz'; import { getPlayerId, RNPlayerView } from './Player'; import { OnLoadedEvent, OnPausedEvent, OnProgessEvent, ProgressInfo, VideoInfo } from './Player.types'; import { VideoViewProps } from './VideoView.types'; import { Controls, ControlsRef } from './internal/Controls'; export type VideoPlayerEventsObserver = { addEventListener: (listener: VideoPlayerListener) => void; removeEventListener: (listener: VideoPlayerListener) => void; }; export type VideoPlayerListener = { onLoading?: () => void; onLoaded?: (event: VideoInfo) => void; onPaused?: (event: boolean) => void; onProgress?: (event: ProgressInfo) => void; }; export type VideoViewRef = { showControlBar: (value: boolean) => void; isControlBarVisible: boolean; setFullscreen: (value: boolean) => void; lockOrientationLandscape: () => void; unlockOrientation: () => void; }; interface ElementDimensions { width: number; height: number; } export const VideoView = forwardRef( ( { style, onLoading, onLoaded, onPaused, onProgress, onFullscreen, player, onBack, onPrevious, onNext, forwardSeconds = 10, backwardSeconds = 10, alwaysFullscreen = false, leftButtons, rightButtons, ...rest }: VideoViewProps, ref ) => { const nativeRef = useRef(); const playerId = getPlayerId(player); const controlRef = useRef(); const [videoInfo, setVideoInfo] = useState(); const [progressInfo, setProgressInfo] = useState(); const [fullscreen, setFullscreen] = useState(alwaysFullscreen); const [viewLayout, setViewLayout] = useState(); const windowsDimensions = useWindowDimensions(); let parentDimensions = viewLayout as ElementDimensions; const videoSize = calculateVideoDimensions(parentDimensions, videoInfo?.videoSize); const [listeners] = useState([]); const playerObserver: VideoPlayerEventsObserver = { addEventListener: listener => { const isExist = listeners.includes(listener); if (!isExist) { listeners.push(listener); videoInfo && listeners.forEach(listener => listener.onLoaded?.(videoInfo)); progressInfo && listeners.forEach(listener => listener.onProgress?.(progressInfo)); } }, removeEventListener: listener => { const observerIndex = listeners.indexOf(listener); if (observerIndex !== -1) { listeners.splice(observerIndex, 1); } } }; useImperativeHandle(ref, () => ({ showControlBar: (value: boolean) => controlRef.current?.showControlBar(value), isControlBarVisible: controlRef.current?.isControlBarVisible || false, setFullscreen: (value: boolean) => setFullscreen(value), lockOrientationLandscape: () => nativeRef.current?.lockOrientationLandscape(), unlockOrientation: () => nativeRef.current?.unlockOrientation() })); useEffect(() => { if (fullscreen) { FullScreenChz.enable(); parentDimensions = windowsDimensions as ElementDimensions; if (alwaysFullscreen) { nativeRef.current?.lockOrientationLandscape(); } } else { FullScreenChz.disable(); parentDimensions = viewLayout as ElementDimensions; if (alwaysFullscreen) { nativeRef.current?.unlockOrientation(); } } onFullscreen?.(fullscreen); return () => { FullScreenChz.disable(); }; }, [fullscreen]); return ( setViewLayout(e.nativeEvent.layout)} > { listeners.forEach(listener => listener.onLoading?.()); onLoading?.(); }} onLoaded={(e: OnLoadedEvent) => { setVideoInfo(e.nativeEvent); listeners.forEach(listener => listener.onLoaded?.(e.nativeEvent)); onLoaded?.(e); }} onPaused={(e: OnPausedEvent) => { listeners.forEach(listener => listener.onPaused?.(e.nativeEvent.payload)); onPaused?.(e); }} onProgress={(e: OnProgessEvent) => { setProgressInfo(e.nativeEvent); listeners.forEach(listener => listener.onProgress?.(e.nativeEvent)); onProgress?.(e); }} {...rest} /> setFullscreen(!fullscreen)} leftButtons={leftButtons} rightButtons={rightButtons} /> ); } ); const styles = StyleSheet.create({ container: { flex: 1, width: '100%', height: '100%', backgroundColor: 'black', alignItems: 'center', justifyContent: 'center' }, fullscreen: { position: 'absolute', flex: 1, width: '100%', height: '100%', backgroundColor: 'black', padding: 0, margin: 0 } }); const calculateVideoDimensions = (parentLayout?: ElementDimensions, videoDimensions?: ElementDimensions | undefined): ElementDimensions => { 'worklet'; const aspectDimensions = videoDimensions?.height && videoDimensions?.width ? videoDimensions : { width: 16, height: 9 }; const parentDimensions = parentLayout || { width: 16, height: 9 }; const width = parentDimensions.height * (aspectDimensions.width / aspectDimensions.height); const height = parentDimensions.width * (aspectDimensions.height / aspectDimensions.width); const dimensions = height > parentDimensions.height ? { width, height: parentDimensions.height } : { width: parentDimensions.width, height }; return dimensions; };