import React, { useRef, useState, useEffect } from 'react'; import { Animated, TouchableWithoutFeedback, View, Platform, ActivityIndicator, type ViewStyle, } from 'react-native'; import IVSPlayer, { type IVSPlayerRef, LogLevel, type IVSPlayerProps } from 'amazon-ivs-react-native-player'; import styled from 'styled-components/native'; import { Dropdown } from 'react-native-element-dropdown'; import { enterFullScreen, exitFullScreen, px } from '../utiles'; import colors from '../colors'; import Slider from '@react-native-assets/slider'; import Volume from '../Components/Volume'; import PlayPauseButton from '../Components/PlayPauseButton'; import FullScreenButton from '../Components/FullScreenButton'; interface Quality { bitrate: number; codecs: string; framerate: number; height: number; name: string; width: number; } interface DataResponse { qualities: Quality[]; sessionId: string; version: string; } export interface IVSPlayerComponentProps extends IVSPlayerProps { isFullScreen?: boolean; hideSeekBar?: boolean; autoHideControls?: boolean; hidePlayButton?: boolean; LeftCustomComponent?: React.ComponentType; RightCustomComponent?: React.ComponentType; Header?: React.ComponentType; leftCustomComponentContainerStyle?: ViewStyle; rightCustomComponentContainerStyle?: ViewStyle; } const formatTime = (seconds: number) => { if (!isFinite(seconds)) return `00:00`; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins < 10 ? '0' : ''}${mins}:${secs < 10 ? '0' : ''}${secs}`; }; const isIOS = Platform.OS === 'ios'; const IOSPlayer: React.FC = ({ onQualityChange, streamUrl, autoplay = true, loop = true, hideSeekBar = false, autoHideControls = true, logLevel = LogLevel.IVSLogLevelError, muted = false, paused: initialPaused = false, hidePlayButton = false, playbackRate = 1.0, volume: defaultVolume = 1.0, quality: initialQuality, autoMaxQuality, autoQualityMode = true, onVideoStatistics, maxBitrate, liveLowLatency, rebufferToLive = false, style, onPipChange, onTimePoint, resizeMode, Header, pipEnabled, onRebuffering, onLiveLatencyChange, onError, onLoadStart, onTextMetadataCue, onSeek, initialBufferDuration, isFullScreen: isInitFullScreen = false, leftCustomComponentContainerStyle, rightCustomComponentContainerStyle, LeftCustomComponent, RightCustomComponent, }) => { const mediaPlayerRef = useRef(null); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [paused, setPaused] = useState(initialPaused); const [qualities, setQualities] = useState([]); const [selectedQuality, setSelectedQuality] = useState(initialQuality || null); const controlsOpacity = useRef(new Animated.Value(1)).current; const controlsTimeout = useRef(null); const [volume, setVolume] = useState(defaultVolume); const [isFullScreen, setIsFullScreen] = useState(isInitFullScreen); const [isBuffering, setIsBuffering] = useState(false); const handleUserInteraction = () => { showControls(); }; const handleSliderValueChange = (value: number) => { handleUserInteraction(); mediaPlayerRef.current?.seekTo(value); }; const togglePlayPause = () => { handleUserInteraction(); setPaused(!paused); }; const showControls = () => { Animated.timing(controlsOpacity, { toValue: 1, duration: 300, useNativeDriver: true, }).start(); // Only reset controls timeout if autoHideControls is true if (autoHideControls) { resetControlsTimeout(); } }; const hideControls = () => { if (!autoHideControls) return; // Prevent hiding if autoHideControls is false Animated.timing(controlsOpacity, { toValue: 0, duration: 300, useNativeDriver: true, }).start(); }; const resetControlsTimeout = () => { if (controlsTimeout.current) { clearTimeout(controlsTimeout.current); } controlsTimeout.current = setTimeout(() => { hideControls(); }, 3000); }; useEffect(() => { if (!autoHideControls) { showControls(); // Keep controls visible if autoHideControls is false if (controlsTimeout.current) { clearTimeout(controlsTimeout.current); } } }, [autoHideControls]); useEffect(() => { setPaused(initialPaused); }, [initialPaused]); const handleFullScreen = () => { if (isFullScreen) { exitFullScreen(); } else { enterFullScreen(); } setIsFullScreen(!isFullScreen); }; useEffect(() => { if (!isInitFullScreen) { exitFullScreen(); } else { enterFullScreen(); } setIsFullScreen(isInitFullScreen); }, [isInitFullScreen]); const handleQualityChange = (item: Quality) => { setSelectedQuality(item); }; const handleData = (data: DataResponse) => { if (data && data.qualities) { setQualities(data.qualities); const defaultQuality = data?.qualities[0]; setSelectedQuality(defaultQuality); } }; useEffect(() => { if (autoHideControls) { resetControlsTimeout(); } return () => { if (controlsTimeout.current) { clearTimeout(controlsTimeout.current); } }; }, [autoHideControls]); const handleVolumeValueChange = (value: number) => { handleUserInteraction(); setVolume(value); }; const handleBuffering = (buffering: boolean) => { if (onRebuffering) { onRebuffering(); } setIsBuffering(buffering); }; const handleError = (error: string) => { if (onError) { onError(error); } }; return ( handleBuffering(true)} onError={handleError} onLiveLatencyChange={onLiveLatencyChange} onData={(data: DataResponse) => handleData(data)} onVideoStatistics={onVideoStatistics} onPlayerStateChange={(state) => handleBuffering(state === 'Buffering')} onLoad={(loadedDuration) => setDuration(loadedDuration || 0)} onLoadStart={onLoadStart} onProgress={(position) => setCurrentTime(position)} onTimePoint={onTimePoint} resizeMode={resizeMode} pipEnabled={pipEnabled} onTextMetadataCue={onTextMetadataCue} onDurationChange={(newDuration) => isFinite(newDuration || 0) && setDuration(newDuration || 0)} onSeek={onSeek} onQualityChange={onQualityChange} onPipChange={onPipChange} initialBufferDuration={initialBufferDuration} style={{ backgroundColor: '#000', ...style }} /> {isBuffering && isIOS && ( Loading... )} {!isFullScreen && } {Header &&
} {LeftCustomComponent && } {RightCustomComponent && } {!hideSeekBar && ( {formatTime(currentTime)} {formatTime(duration)} )} {!hidePlayButton && } handleQualityChange(item)} placeholder="Quality" placeholderStyle={{ color: 'white', fontSize: 10 }} selectedTextStyle={{ color: 'white', fontSize: 13 }} containerStyle={{ backgroundColor: '#fff' }} style={{ backgroundColor: 'rgba(124,122,122,0.5)', padding: px(3), borderRadius: 5, width: px(30), }} itemTextStyle={{ color: 'black' }} /> ); }; const Container = styled.View` flex: 1; width: 100%; `; const PlayTime = styled.Text` color: ${colors.white}; `; const DropDownContainer = styled.View``; const PlayButtonContainer = styled.View` flex: 1; align-self: center; left: 20%; `; const FullScreenContainer = styled.View` flex: 1; `; const IVSPlayerWrapper = styled.View` flex: 1; position: relative; `; const Overlay = styled.View` position: absolute; top: 0; left: 0; right: 0; bottom: 0; justify-content: space-between; padding-left: ${px(4)}px; padding-right: ${px(3)}px; z-index: 1; `; const Top = styled.View``; const TopInner = styled.View` margin-top: ${px(8)}px; flex-direction: row; align-items: center; justify-content: space-between; `; const Bottom = styled.View` justify-content: flex-end; `; const AnimatedControls = styled(Animated.View)` position: absolute; top: 0; left: 0; right: 0; bottom: 0; `; const BottomSectionOne = styled.View` flex-direction: row; align-items: center; z-index: 99999; `; const BottomSectionTwo = styled.View` flex-direction: row; width: 100%; justify-content: space-between; align-items: center; margin-bottom: ${px(isIOS ? 7 : 3)}px; `; const BufferingOverlay = styled.View` position: absolute; top: 0; left: 0; right: 0; bottom: 0; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.5); z-index: 2; `; const BufferingText = styled.Text` color: ${colors.white}; margin-top: 10px; `; const CustomComponentContainer = styled.View` flex-direction: row; justify-content: space-between; `; const ContentOne = styled.View` height: 80%; min-width: ${px(20)}px; margin-bottom: ${px(10)}px; `; export default IOSPlayer;