155 lines
3.8 KiB
TypeScript
155 lines
3.8 KiB
TypeScript
|
import React, {useCallback, useEffect, useState} from 'react';
|
||
|
import {PanResponder, View} from 'react-native';
|
||
|
import styles from '../styles';
|
||
|
|
||
|
interface SeekerProps {
|
||
|
currentTime: number;
|
||
|
duration: number;
|
||
|
isLoading: boolean;
|
||
|
isUISeeking: boolean;
|
||
|
videoSeek: (arg0: number) => void;
|
||
|
}
|
||
|
|
||
|
const Seeker = ({
|
||
|
currentTime,
|
||
|
duration,
|
||
|
isLoading,
|
||
|
isUISeeking,
|
||
|
videoSeek,
|
||
|
}: SeekerProps) => {
|
||
|
const [seeking, setSeeking] = useState(false);
|
||
|
const [seekerPosition, setSeekerPosition] = useState(0);
|
||
|
const [seekerWidth, setSeekerWidth] = useState(0);
|
||
|
|
||
|
/**
|
||
|
* Set the position of the seekbar's components
|
||
|
* (both fill and handle) according to the
|
||
|
* position supplied.
|
||
|
*
|
||
|
* @param {float} position position in px of seeker handle}
|
||
|
*/
|
||
|
const updateSeekerPosition = useCallback(
|
||
|
(position = 0) => {
|
||
|
if (position <= 0) {
|
||
|
position = 0;
|
||
|
} else if (position >= seekerWidth) {
|
||
|
position = seekerWidth;
|
||
|
}
|
||
|
setSeekerPosition(position);
|
||
|
},
|
||
|
[seekerWidth],
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Return the time that the video should be at
|
||
|
* based on where the seeker handle is.
|
||
|
*
|
||
|
* @return {float} time in ms based on seekerPosition.
|
||
|
*/
|
||
|
const calculateTimeFromSeekerPosition = () => {
|
||
|
const percent = seekerPosition / seekerWidth;
|
||
|
return duration * percent;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get our seekbar responder going
|
||
|
*/
|
||
|
|
||
|
const seekPanResponder = PanResponder.create({
|
||
|
// Ask to be the responder.
|
||
|
onStartShouldSetPanResponder: (_evt, _gestureState) => true,
|
||
|
onMoveShouldSetPanResponder: (_evt, _gestureState) => true,
|
||
|
|
||
|
/**
|
||
|
* When we start the pan tell the machine that we're
|
||
|
* seeking. This stops it from updating the seekbar
|
||
|
* position in the onProgress listener.
|
||
|
*/
|
||
|
onPanResponderGrant: (evt, _gestureState) => {
|
||
|
const position = evt.nativeEvent.locationX;
|
||
|
updateSeekerPosition(position);
|
||
|
setSeeking(true);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* When panning, update the seekbar position, duh.
|
||
|
*/
|
||
|
onPanResponderMove: (evt, _gestureState) => {
|
||
|
const position = evt.nativeEvent.locationX;
|
||
|
updateSeekerPosition(position);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* On release we update the time and seek to it in the video.
|
||
|
* If you seek to the end of the video we fire the
|
||
|
* onEnd callback
|
||
|
*/
|
||
|
onPanResponderRelease: (_evt, _gestureState) => {
|
||
|
const time = calculateTimeFromSeekerPosition();
|
||
|
if (time >= duration && !isLoading) {
|
||
|
// FIXME ...
|
||
|
// state.paused = true;
|
||
|
// this.onEnd();
|
||
|
} else {
|
||
|
videoSeek(time);
|
||
|
setSeeking(false);
|
||
|
}
|
||
|
},
|
||
|
});
|
||
|
|
||
|
useEffect(() => {
|
||
|
if (!isLoading && !seeking && !isUISeeking) {
|
||
|
const percent = currentTime / duration;
|
||
|
const position = seekerWidth * percent;
|
||
|
updateSeekerPosition(position);
|
||
|
}
|
||
|
}, [
|
||
|
currentTime,
|
||
|
duration,
|
||
|
isLoading,
|
||
|
seekerWidth,
|
||
|
seeking,
|
||
|
isUISeeking,
|
||
|
updateSeekerPosition,
|
||
|
]);
|
||
|
|
||
|
if (!seekPanResponder) {
|
||
|
return null;
|
||
|
}
|
||
|
const seekerStyle = [
|
||
|
styles.seekbarFill,
|
||
|
{
|
||
|
width: seekerPosition > 0 ? seekerPosition : 0,
|
||
|
backgroundColor: '#FFF',
|
||
|
},
|
||
|
];
|
||
|
|
||
|
const seekerPositionStyle = [
|
||
|
styles.seekbarHandle,
|
||
|
{
|
||
|
left: seekerPosition > 0 ? seekerPosition : 0,
|
||
|
},
|
||
|
];
|
||
|
|
||
|
const seekerPointerStyle = [styles.seekbarCircle, {backgroundColor: '#FFF'}];
|
||
|
|
||
|
return (
|
||
|
<View
|
||
|
style={styles.seekbarContainer}
|
||
|
{...seekPanResponder.panHandlers}
|
||
|
{...styles.generalControls}>
|
||
|
<View
|
||
|
style={styles.seekbarTrack}
|
||
|
onLayout={event => setSeekerWidth(event.nativeEvent.layout.width)}
|
||
|
pointerEvents={'none'}>
|
||
|
<View style={seekerStyle} pointerEvents={'none'} />
|
||
|
</View>
|
||
|
<View style={seekerPositionStyle} pointerEvents={'none'}>
|
||
|
<View style={seekerPointerStyle} pointerEvents={'none'} />
|
||
|
</View>
|
||
|
</View>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export default Seeker;
|