chore(sample): review seeker in sample (#3787)
This commit is contained in:
parent
5059e7a7f1
commit
e23e02b359
@ -7,10 +7,8 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
PanResponder,
|
|
||||||
ToastAndroid,
|
ToastAndroid,
|
||||||
Platform,
|
Platform,
|
||||||
PanResponderInstance,
|
|
||||||
Alert,
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
@ -53,6 +51,7 @@ import styles from './styles';
|
|||||||
import AudioTrackSelector from './components/AudioTracksSelector';
|
import AudioTrackSelector from './components/AudioTracksSelector';
|
||||||
import TextTrackSelector from './components/TextTracksSelector';
|
import TextTrackSelector from './components/TextTracksSelector';
|
||||||
import VideoTrackSelector from './components/VideoTracksSelector';
|
import VideoTrackSelector from './components/VideoTracksSelector';
|
||||||
|
import Seeker from './components/Seeker';
|
||||||
|
|
||||||
type AdditionnalSourceInfo = {
|
type AdditionnalSourceInfo = {
|
||||||
textTracks: TextTracks;
|
textTracks: TextTracks;
|
||||||
@ -77,10 +76,6 @@ interface StateType {
|
|||||||
fullscreen: true;
|
fullscreen: true;
|
||||||
decoration: true;
|
decoration: true;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
seekerFillWidth: number;
|
|
||||||
seekerPosition: number;
|
|
||||||
seekerOffset: number;
|
|
||||||
seeking: boolean;
|
|
||||||
audioTracks: Array<AudioTrack>;
|
audioTracks: Array<AudioTrack>;
|
||||||
textTracks: Array<TextTrack>;
|
textTracks: Array<TextTrack>;
|
||||||
videoTracks: Array<VideoTrack>;
|
videoTracks: Array<VideoTrack>;
|
||||||
@ -93,6 +88,7 @@ interface StateType {
|
|||||||
useCache: boolean;
|
useCache: boolean;
|
||||||
poster?: string;
|
poster?: string;
|
||||||
showNotificationControls: boolean;
|
showNotificationControls: boolean;
|
||||||
|
isSeeking: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoPlayer extends Component {
|
class VideoPlayer extends Component {
|
||||||
@ -109,10 +105,6 @@ class VideoPlayer extends Component {
|
|||||||
fullscreen: true,
|
fullscreen: true,
|
||||||
decoration: true,
|
decoration: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
seekerFillWidth: 0,
|
|
||||||
seekerPosition: 0,
|
|
||||||
seekerOffset: 0,
|
|
||||||
seeking: false,
|
|
||||||
audioTracks: [],
|
audioTracks: [],
|
||||||
textTracks: [],
|
textTracks: [],
|
||||||
videoTracks: [],
|
videoTracks: [],
|
||||||
@ -127,10 +119,9 @@ class VideoPlayer extends Component {
|
|||||||
useCache: false,
|
useCache: false,
|
||||||
poster: undefined,
|
poster: undefined,
|
||||||
showNotificationControls: false,
|
showNotificationControls: false,
|
||||||
|
isSeeking: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
seekerWidth = 0;
|
|
||||||
|
|
||||||
// internal usage change to index if you want to select tracks by index instead of lang
|
// internal usage change to index if you want to select tracks by index instead of lang
|
||||||
textTracksSelectionBy = 'index';
|
textTracksSelectionBy = 'index';
|
||||||
|
|
||||||
@ -271,7 +262,6 @@ class VideoPlayer extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
video?: VideoRef;
|
video?: VideoRef;
|
||||||
seekPanResponder?: PanResponderInstance;
|
|
||||||
|
|
||||||
popupInfo = () => {
|
popupInfo = () => {
|
||||||
VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => {
|
VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => {
|
||||||
@ -300,24 +290,13 @@ class VideoPlayer extends Component {
|
|||||||
this.onVideoTracks(data);
|
this.onVideoTracks(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateSeeker = () => {
|
|
||||||
// put this code in timeout as because it may be put just after a setState
|
|
||||||
setTimeout(() => {
|
|
||||||
const position = this.calculateSeekerPosition();
|
|
||||||
this.setSeekerPosition(position);
|
|
||||||
}, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
onProgress = (data: OnProgressData) => {
|
onProgress = (data: OnProgressData) => {
|
||||||
this.setState({currentTime: data.currentTime});
|
this.setState({currentTime: data.currentTime});
|
||||||
if (!this.state.seeking) {
|
|
||||||
this.updateSeeker();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onSeek = (data: OnSeekData) => {
|
onSeek = (data: OnSeekData) => {
|
||||||
|
this.setState({isSeeking: false});
|
||||||
this.setState({currentTime: data.currentTime});
|
this.setState({currentTime: data.currentTime});
|
||||||
this.updateSeeker();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onVideoLoadStart = () => {
|
onVideoLoadStart = () => {
|
||||||
@ -407,13 +386,6 @@ class VideoPlayer extends Component {
|
|||||||
this.setState({paused: !event.hasAudioFocus});
|
this.setState({paused: !event.hasAudioFocus});
|
||||||
};
|
};
|
||||||
|
|
||||||
getCurrentTimePercentage = () => {
|
|
||||||
if (this.state.currentTime > 0 && this.state.duration !== 0) {
|
|
||||||
return this.state.currentTime / this.state.duration;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
toast = (visible: boolean, message: string) => {
|
toast = (visible: boolean, message: string) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
@ -501,166 +473,20 @@ class VideoPlayer extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
videoSeek(position: number) {
|
||||||
this.initSeekPanResponder();
|
this.setState({isSeeking: true});
|
||||||
}
|
this.video?.seek(position);
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the seekbar and attach its handlers
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constrain the location of the seeker to the
|
|
||||||
* min/max value based on how big the
|
|
||||||
* seeker is.
|
|
||||||
*
|
|
||||||
* @param {float} val position of seeker handle in px
|
|
||||||
* @return {float} constrained position of seeker handle in px
|
|
||||||
*/
|
|
||||||
constrainToSeekerMinMax(val = 0) {
|
|
||||||
if (val <= 0) {
|
|
||||||
return 0;
|
|
||||||
} else if (val >= this.seekerWidth) {
|
|
||||||
return this.seekerWidth;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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}
|
|
||||||
*/
|
|
||||||
setSeekerPosition(position = 0) {
|
|
||||||
const state = this.state;
|
|
||||||
position = this.constrainToSeekerMinMax(position);
|
|
||||||
|
|
||||||
state.seekerFillWidth = position;
|
|
||||||
state.seekerPosition = position;
|
|
||||||
|
|
||||||
if (!state.seeking) {
|
|
||||||
state.seekerOffset = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the position that the seeker should be
|
|
||||||
* at along its track.
|
|
||||||
*
|
|
||||||
* @return {float} position of seeker handle in px based on currentTime
|
|
||||||
*/
|
|
||||||
calculateSeekerPosition() {
|
|
||||||
const percent = this.state.currentTime / this.state.duration;
|
|
||||||
return this.seekerWidth * percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the time that the video should be at
|
|
||||||
* based on where the seeker handle is.
|
|
||||||
*
|
|
||||||
* @return {float} time in ms based on seekerPosition.
|
|
||||||
*/
|
|
||||||
calculateTimeFromSeekerPosition() {
|
|
||||||
const percent = this.state.seekerPosition / this.seekerWidth;
|
|
||||||
return this.state.duration * percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get our seekbar responder going
|
|
||||||
*/
|
|
||||||
initSeekPanResponder() {
|
|
||||||
this.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 state = this.state;
|
|
||||||
// this.clearControlTimeout()
|
|
||||||
const position = evt.nativeEvent.locationX;
|
|
||||||
this.setSeekerPosition(position);
|
|
||||||
state.seeking = true;
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When panning, update the seekbar position, duh.
|
|
||||||
*/
|
|
||||||
onPanResponderMove: (evt, gestureState) => {
|
|
||||||
const position = this.state.seekerOffset + gestureState.dx;
|
|
||||||
this.setSeekerPosition(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 = this.calculateTimeFromSeekerPosition();
|
|
||||||
const state = this.state;
|
|
||||||
if (time >= state.duration && !state.isLoading) {
|
|
||||||
state.paused = true;
|
|
||||||
this.onEnd();
|
|
||||||
} else {
|
|
||||||
this.video?.seek(time);
|
|
||||||
state.seeking = false;
|
|
||||||
}
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSeekBar() {
|
renderSeekBar() {
|
||||||
if (!this.seekPanResponder) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const seekerStyle = [
|
|
||||||
styles.seekbarFill,
|
|
||||||
{
|
|
||||||
width: this.state.seekerFillWidth > 0 ? this.state.seekerFillWidth : 0,
|
|
||||||
backgroundColor: '#FFF',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const seekerPositionStyle = [
|
|
||||||
styles.seekbarHandle,
|
|
||||||
{
|
|
||||||
left: this.state.seekerPosition > 0 ? this.state.seekerPosition : 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const seekerPointerStyle = [
|
|
||||||
styles.seekbarCircle,
|
|
||||||
{backgroundColor: '#FFF'},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<Seeker
|
||||||
style={styles.seekbarContainer}
|
currentTime={this.state.currentTime}
|
||||||
{...this.seekPanResponder.panHandlers}
|
duration={this.state.duration}
|
||||||
{...styles.generalControls}>
|
isLoading={this.state.isLoading}
|
||||||
<View
|
videoSeek={prop => this.videoSeek(prop)}
|
||||||
style={styles.seekbarTrack}
|
isUISeeking={this.state.isSeeking}
|
||||||
onLayout={event =>
|
/>
|
||||||
(this.seekerWidth = event.nativeEvent.layout.width)
|
|
||||||
}
|
|
||||||
pointerEvents={'none'}>
|
|
||||||
<View style={seekerStyle} pointerEvents={'none'} />
|
|
||||||
</View>
|
|
||||||
<View style={seekerPositionStyle} pointerEvents={'none'}>
|
|
||||||
<View style={seekerPointerStyle} pointerEvents={'none'} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
155
examples/basic/src/components/Seeker.tsx
Normal file
155
examples/basic/src/components/Seeker.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
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) {
|
||||||
|
console.log('update position from currentTime');
|
||||||
|
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;
|
Loading…
Reference in New Issue
Block a user