From e23e02b359263021f4db92057bc956ff280fcdf4 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Tue, 28 May 2024 09:33:21 +0200 Subject: [PATCH] chore(sample): review seeker in sample (#3787) --- examples/basic/src/VideoPlayer.tsx | 202 ++--------------------- examples/basic/src/components/Seeker.tsx | 155 +++++++++++++++++ 2 files changed, 169 insertions(+), 188 deletions(-) create mode 100644 examples/basic/src/components/Seeker.tsx diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index b5fae5fb..1054aa16 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -7,10 +7,8 @@ import { TouchableOpacity, View, ActivityIndicator, - PanResponder, ToastAndroid, Platform, - PanResponderInstance, Alert, } from 'react-native'; @@ -53,6 +51,7 @@ import styles from './styles'; import AudioTrackSelector from './components/AudioTracksSelector'; import TextTrackSelector from './components/TextTracksSelector'; import VideoTrackSelector from './components/VideoTracksSelector'; +import Seeker from './components/Seeker'; type AdditionnalSourceInfo = { textTracks: TextTracks; @@ -77,10 +76,6 @@ interface StateType { fullscreen: true; decoration: true; isLoading: boolean; - seekerFillWidth: number; - seekerPosition: number; - seekerOffset: number; - seeking: boolean; audioTracks: Array; textTracks: Array; videoTracks: Array; @@ -93,6 +88,7 @@ interface StateType { useCache: boolean; poster?: string; showNotificationControls: boolean; + isSeeking: boolean; } class VideoPlayer extends Component { @@ -109,10 +105,6 @@ class VideoPlayer extends Component { fullscreen: true, decoration: true, isLoading: false, - seekerFillWidth: 0, - seekerPosition: 0, - seekerOffset: 0, - seeking: false, audioTracks: [], textTracks: [], videoTracks: [], @@ -127,10 +119,9 @@ class VideoPlayer extends Component { useCache: false, poster: undefined, showNotificationControls: false, + isSeeking: false, }; - seekerWidth = 0; - // internal usage change to index if you want to select tracks by index instead of lang textTracksSelectionBy = 'index'; @@ -271,7 +262,6 @@ class VideoPlayer extends Component { ); video?: VideoRef; - seekPanResponder?: PanResponderInstance; popupInfo = () => { VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => { @@ -300,24 +290,13 @@ class VideoPlayer extends Component { 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) => { this.setState({currentTime: data.currentTime}); - if (!this.state.seeking) { - this.updateSeeker(); - } }; onSeek = (data: OnSeekData) => { + this.setState({isSeeking: false}); this.setState({currentTime: data.currentTime}); - this.updateSeeker(); }; onVideoLoadStart = () => { @@ -407,13 +386,6 @@ class VideoPlayer extends Component { 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) => { if (visible) { if (Platform.OS === 'android') { @@ -501,166 +473,20 @@ class VideoPlayer extends Component { ); } - componentDidMount() { - this.initSeekPanResponder(); - } - - /** - * 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); - }, - }); + videoSeek(position: number) { + this.setState({isSeeking: true}); + this.video?.seek(position); } 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 ( - - - (this.seekerWidth = event.nativeEvent.layout.width) - } - pointerEvents={'none'}> - - - - - - + this.videoSeek(prop)} + isUISeeking={this.state.isSeeking} + /> ); } diff --git a/examples/basic/src/components/Seeker.tsx b/examples/basic/src/components/Seeker.tsx new file mode 100644 index 00000000..2ca7a280 --- /dev/null +++ b/examples/basic/src/components/Seeker.tsx @@ -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 ( + + setSeekerWidth(event.nativeEvent.layout.width)} + pointerEvents={'none'}> + + + + + + + ); +}; + +export default Seeker;