react-native-custom-video-player
Version:
This is Custom Video Player
702 lines (670 loc) • 21.9 kB
JavaScript
import React, { useState, useRef, useEffect } from "react";
import {
View,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
AppState,
Dimensions,
StatusBar,
BackHandler,
Image,
FlatList,
} from "react-native";
import Video from "react-native-video";
import Slider from "react-native-sliders";
import Orientation from "react-native-orientation-locker";
const CustomVideoPlayer = (props) => {
const {
style = {},
source,
poster,
title = "",
autoPlay = false,
playInBackground = false,
showSeeking10SecondsButton = true,
showHeader = true,
showFullScreenButton = true,
showSettingButton = true,
showMuteButton = true,
} = props;
const videoRef = useRef(null);
const [isPaused, setIsPaused] = useState(!autoPlay);
const [isMuted, setIsMuted] = useState(false);
const [isVideoFocused, setIsVideoFocused] = useState(true);
const [isShowSettingsBottomSheet, setIsShowSettingsBottomSheet] =
useState(false);
const [isShowVideoQualitiesSettings, setIsShowVideoQualitiesSettings] =
useState(false);
const [isShowVideoSpeedSettings, setIsShowVideoSpeedSettings] =
useState(false);
const [isShowVideoSoundSettings, setIsShowVideoSoundSettings] =
useState(false);
const [isVideoFullScreen, setIsVideoFullScreen] = useState(false);
const [isErrorInLoadVideo, setIsVErrorInLoadVideo] = useState(false);
const [isVideoEnd, setIsVideoEnd] = useState(false);
const [videoDuration, setVideoDuration] = useState(0);
const [videoQuality, setVideoQuality] = useState(0);
const [videoSound, setVideoSound] = useState(1.0);
const [currentVideoDuration, setCurrentVideoDuration] = useState(0);
const [videoRate, setVideoRate] = useState(1);
const [dimension, setDimension] = useState(Dimensions.get("window"));
const [QUALITYDATA, setQUALITYDATA] = useState([]);
const portraitStyle = {
alignSelf: "center",
height: 200,
width: 330,
...style,
};
const landScapeStyle = {
alignSelf: "center",
height: dimension.height,
width: dimension.width,
};
const videoStyle = isVideoFullScreen ? landScapeStyle : portraitStyle;
useEffect(() => {
const dimensionSubscriber = Dimensions.addEventListener(
"change",
({ window, screen }) => {
setDimension(window);
setIsVideoFullScreen(window.width > window.height ? true : false);
}
);
const backHandlerSubscriber = BackHandler.addEventListener(
"hardwareBackPress",
() => {
if (isVideoFullScreen) {
Orientation.lockToPortrait();
StatusBar.setHidden(false);
return true;
} else {
return false;
}
}
);
return () => {
dimensionSubscriber.remove();
backHandlerSubscriber.remove();
};
}, [isVideoFullScreen]);
useEffect(() => {
const appStateSubscriber = AppState.addEventListener("change", (state) => {
if (playInBackground && isPaused == false) {
setIsPaused(false);
} else {
setIsPaused(true);
}
});
return () => {
appStateSubscriber.remove();
};
}, [isPaused]);
const videoHeaders = () => (
<View
style={{
paddingHorizontal: 10,
width: videoStyle.width,
height: 35,
position: "absolute",
top: 0,
left: 0,
backgroundColor: "rgba(0 ,0, 0,0.5)",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
zIndex: 100000,
}}
>
<Text
numberOfLines={1}
style={{ color: "white", fontSize: 12, width: videoStyle.width - 170 }}
>
{title}
</Text>
<View
style={{
flexDirection: "row-reverse",
alignItems: "center",
justifyContent: "space-between",
}}
>
{showSettingButton && (
<TouchableOpacity
onPress={() => {
setIsShowSettingsBottomSheet(true);
setIsVideoFocused(false);
}}
>
<Image
source={require("./images/settings.png")}
style={{ width: 18, height: 18 }}
/>
</TouchableOpacity>
)}
{showMuteButton && (
<TouchableOpacity
onPress={() => {
setIsMuted(!isMuted);
if (isMuted && videoSound == 0) {
setVideoSound(1.0);
}
setTimeout(() => {
setIsVideoFocused(false);
}, 800);
}}
style={{ marginRight: 10 }}
>
<Image
source={
isMuted
? require("./images/muteSound.png")
: require("./images/fullSound.png")
}
style={{ width: 20, height: 20 }}
/>
</TouchableOpacity>
)}
</View>
</View>
);
const videoCenter = () => (
<View
style={{
width: videoStyle.width,
height: 35,
position: "absolute",
top: videoStyle.height / 2 - 20,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: videoStyle.width / 5,
zIndex: 100000,
}}
>
<TouchableOpacity
onPress={() => videoRef.current.seek(currentVideoDuration - 10)}
>
<Image
source={require("./images/decrease10Seconds.png")}
style={{ width: 25, height: 25 }}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
if (isVideoEnd) {
videoRef.current.seek(0);
setIsVideoEnd(false);
setCurrentVideoDuration(0);
setIsPaused(false);
setTimeout(() => {
setIsVideoFocused(false);
}, 800);
} else {
setIsPaused(!isPaused);
setTimeout(() => {
setIsVideoFocused(false);
}, 800);
}
}}
>
<Image
source={
isPaused
? require("./images/play.png")
: require("./images/pause.png")
}
style={{ width: 17, height: 17 }}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => videoRef.current.seek(currentVideoDuration + 10)}
>
<Image
source={require("./images/increase10Seconds.png")}
style={{ width: 25, height: 25 }}
/>
</TouchableOpacity>
</View>
);
const videoFooter = () => (
<View
style={{
paddingHorizontal: 10,
width: videoStyle.width,
height: 35,
position: "absolute",
bottom: 0,
left: 0,
backgroundColor: "rgba(0 ,0, 0,0.5)",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
zIndex: 100000,
}}
>
<Text style={{ color: "white", fontSize: 12 }}>
{new Date(currentVideoDuration * 1000).toISOString().substr(14, 5)}
</Text>
<Slider
//disabled={isRecordBeforePlay}
maximumValue={videoDuration}
minimumValue={0}
minimumTrackTintColor="white"
maximumTrackTintColor="gray"
thumbTintColor="white"
thumbStyle={{ height: 12, width: 12 }}
trackStyle={{ height: 1.8, width: videoStyle.width - 140 }}
useNativeDriver
value={currentVideoDuration}
onSlidingComplete={(sliderData) => {
setCurrentVideoDuration(sliderData[0]);
videoRef.current.seek(sliderData[0]);
}}
/>
<Text style={{ color: "white", fontSize: 12 }}>
{new Date(videoDuration * 1000).toISOString().substr(14, 5)}
</Text>
{showFullScreenButton && (
<TouchableOpacity
onPress={() => {
if (isVideoFullScreen) {
StatusBar.setHidden(false);
Orientation.lockToPortrait();
} else {
StatusBar.setHidden(true);
Orientation.lockToLandscapeLeft();
}
setIsVideoFocused(false);
}}
>
<Image
source={
isVideoFullScreen
? require("./images/exitFullScreen.png")
: require("./images/fullScreen.png")
}
style={{ width: 18, height: 18 }}
/>
</TouchableOpacity>
)}
</View>
);
const videoSettingsView = () => (
<TouchableWithoutFeedback>
<View
style={{
position: "absolute",
bottom: 0,
left: 0,
backgroundColor: "rgba(0 ,0, 0,0.9)",
alignItems: "center",
justifyContent: "center",
zIndex: 100000,
...videoStyle,
}}
>
<TouchableOpacity
style={{
justifyContent: "center",
alignItems: "center",
position: "absolute",
right: 10,
top: 10,
}}
onPress={() => {
setIsShowSettingsBottomSheet(false);
}}
>
<Image
source={require("./images/close.png")}
style={{ width: 20, height: 22 }}
/>
</TouchableOpacity>
<View
style={{
width: 170,
flexDirection: "row",
justifyContent: "space-between",
position: "absolute",
top: videoStyle.height / 6,
}}
>
<TouchableOpacity
style={{ justifyContent: "center", alignItems: "center" }}
onPress={() => {
setIsShowVideoQualitiesSettings(true);
setIsShowVideoSpeedSettings(false);
setIsShowVideoSoundSettings(false);
}}
>
<Image
source={require("./images/quality.png")}
style={{ width: 26, height: 27 }}
/>
<Text style={{ color: "white", fontSize: 13 }}>Quality</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ justifyContent: "center", alignItems: "center" }}
onPress={() => {
setIsShowVideoQualitiesSettings(false);
setIsShowVideoSpeedSettings(true);
setIsShowVideoSoundSettings(false);
}}
>
<Image
source={require("./images/speed.png")}
style={{ width: 20, height: 25 }}
/>
<Text style={{ color: "white", fontSize: 13 }}>Speed</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ justifyContent: "center", alignItems: "center" }}
onPress={() => {
setIsShowVideoQualitiesSettings(false);
setIsShowVideoSpeedSettings(false);
setIsShowVideoSoundSettings(true);
}}
>
<Image
source={require("./images/soundMixer.png")}
style={{ width: 20, height: 22 }}
/>
<Text style={{ color: "white", fontSize: 13 }}>Sound</Text>
</TouchableOpacity>
</View>
<View
style={{
justifyContent: "center",
position: "absolute",
top: videoStyle.height / 2.5,
}}
>
{isShowVideoQualitiesSettings ? (
<TouchableWithoutFeedback>
<View
style={{
width: 260,
borderWidth: 0,
borderColor: "red",
alignItems: "center",
justifyContent: "space-between",
}}
>
{QUALITYDATA.length != 0 && (
<FlatList
data={QUALITYDATA}
renderItem={({ item }) => {
return (
<TouchableOpacity
style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
width: 60,
height: 25,
borderWidth: 1,
borderColor: "white",
borderRadius: 4,
marginHorizontal: 5,
marginVertical: 4,
}}
onPress={() => {
setVideoQuality(item.height);
setIsShowSettingsBottomSheet(false);
}}
>
{videoQuality == item.height && (
<Image
source={require("./images/dot.png")}
style={{ width: 10, height: 10 }}
/>
)}
<Text
style={{
color: "white",
fontSize: 13,
marginLeft: videoQuality == item.height ? 3 : 0,
}}
>
{item.height}
</Text>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => item.trackId.toString()}
numColumns={3}
/>
)}
</View>
</TouchableWithoutFeedback>
) : isShowVideoSpeedSettings ? (
<TouchableWithoutFeedback>
<View
style={{
width: 260,
borderWidth: 0,
borderColor: "red",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 10,
}}
>
<TouchableOpacity
style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
width: 70,
height: 25,
borderWidth: 1,
borderColor: "white",
borderRadius: 4,
}}
onPress={() => {
setVideoRate(0.5);
setIsShowSettingsBottomSheet(false);
}}
>
{videoRate == 0.5 && (
<Image
source={require("./images/dot.png")}
style={{ width: 10, height: 10 }}
/>
)}
<Text
style={{
color: "white",
fontSize: 13,
marginLeft: videoRate == 0.5 ? 3 : 0,
}}
>
Slow
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
width: 70,
height: 25,
borderWidth: 1,
borderColor: "white",
borderRadius: 4,
}}
onPress={() => {
setVideoRate(1);
setIsShowSettingsBottomSheet(false);
}}
>
{videoRate == 1 && (
<Image
source={require("./images/dot.png")}
style={{ width: 10, height: 10 }}
/>
)}
<Text
style={{
color: "white",
fontSize: 13,
marginLeft: videoRate == 1 ? 3 : 0,
}}
>
Normal
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
width: 70,
height: 25,
borderWidth: 1,
borderColor: "white",
borderRadius: 4,
}}
onPress={() => {
setVideoRate(4);
setIsShowSettingsBottomSheet(false);
}}
>
{videoRate == 4 && (
<Image
source={require("./images/dot.png")}
style={{ width: 10, height: 10 }}
/>
)}
<Text
style={{
color: "white",
fontSize: 13,
marginLeft: videoRate == 4 ? 3 : 0,
}}
>
Fast
</Text>
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
) : isShowVideoSoundSettings ? (
<View style={{ justifyContent: "center", alignItems: "center" }}>
<Slider
//disabled={isRecordBeforePlay}
maximumValue={1}
minimumValue={0}
step={0.1}
minimumTrackTintColor="white"
maximumTrackTintColor="gray"
thumbTintColor="white"
thumbStyle={{ height: 12, width: 12 }}
trackStyle={{ height: 1.8, width: videoStyle.width - 130 }}
useNativeDriver
value={videoSound}
onSlidingComplete={(sliderData) => {
setVideoSound(Number(sliderData[0].toFixed(1)));
if (sliderData[0] == 0) {
setIsMuted(true);
} else {
setIsMuted(false);
}
}}
/>
<Text style={{ color: "white" }}>{videoSound}</Text>
</View>
) : null}
</View>
</View>
</TouchableWithoutFeedback>
);
const videoPosterView = () => (
<Image
source={{
uri: poster,
}}
style={{
height: videoStyle.height,
width: videoStyle.width,
position: "absolute",
top: 0,
left: 0,
backgroundColor: "rgba(0 ,0, 0,0.5)",
}}
/>
);
const videoErrorView = () => (
<View
style={{
height: videoStyle.height,
width: videoStyle.width,
position: "absolute",
top: 0,
left: 0,
backgroundColor: "rgba(0 ,0, 0,0.5)",
justifyContent: "center",
alignItems: "center",
}}
>
<Image
source={require("./images/error.png")}
style={{ width: 30, height: 30 }}
/>
<Text style={{ color: "white", fontSize: 12, marginTop: 0 }}>
Error when load video
</Text>
</View>
);
return (
<TouchableWithoutFeedback
onPress={() => {
setIsVideoFocused(!isVideoFocused);
}}
>
<View style={videoStyle}>
<Video
style={{ flex: 1 }}
posterResizeMode="cover"
resizeMode="cover"
ref={videoRef}
source={source}
paused={isPaused}
muted={isMuted}
rate={videoRate}
selectedVideoTrack={{
type: "resolution",
value: videoQuality,
}}
volume={videoSound}
playInBackground={playInBackground}
onLoad={(videoData) => {
setQUALITYDATA(videoData.videoTracks);
setVideoQuality(videoData.videoTracks[0].height);
setVideoDuration(videoData.duration);
setIsVErrorInLoadVideo(false);
}}
onProgress={(videoData) =>
setCurrentVideoDuration(videoData.currentTime)
}
onError={(videoData) => setIsVErrorInLoadVideo(true)}
onEnd={() => {
setIsVideoEnd(true);
setIsPaused(true);
setIsVideoFocused(true);
setCurrentVideoDuration(0);
}}
/>
{isVideoFocused && showHeader && videoHeaders()}
{isVideoFocused &&
showSeeking10SecondsButton &&
!isErrorInLoadVideo &&
videoCenter()}
{isVideoFocused && videoFooter()}
{isShowSettingsBottomSheet && videoSettingsView()}
{currentVideoDuration == 0 && poster && videoPosterView()}
{isErrorInLoadVideo && videoErrorView()}
</View>
</TouchableWithoutFeedback>
);
};
export default CustomVideoPlayer;