diff --git a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.js b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.js index 1c1d042d3893..45f47eb87c36 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.js +++ b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {memo, useState} from 'react'; +import React, {memo, useCallback, useState} from 'react'; import {View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import Animated, {runOnJS, useAnimatedStyle, useDerivedValue} from 'react-native-reanimated'; @@ -9,6 +9,7 @@ import IconButton from '@components/VideoPlayer/IconButton'; import {useVolumeContext} from '@components/VideoPlayerContexts/VolumeContext'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as NumberUtils from '@libs/NumberUtils'; import stylePropTypes from '@styles/stylePropTypes'; const propTypes = { @@ -38,27 +39,35 @@ function VolumeButton({style, small}) { const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.value)}); const [isSliderBeingUsed, setIsSliderBeingUsed] = useState(false); - const onSliderLayout = (e) => { + const onSliderLayout = useCallback((e) => { setSliderHeight(e.nativeEvent.layout.height); - }; + }, []); + + const changeVolumeOnPan = useCallback( + (event) => { + const val = NumberUtils.roundToTwoDecimalPlaces(1 - event.y / sliderHeight); + volume.value = NumberUtils.clamp(val, 0, 1); + }, + [sliderHeight, volume], + ); const pan = Gesture.Pan() - .onBegin(() => { + .onBegin((event) => { runOnJS(setIsSliderBeingUsed)(true); + changeVolumeOnPan(event); }) .onChange((event) => { - const val = Math.floor((1 - event.y / sliderHeight) * 100) / 100; - volume.value = Math.min(Math.max(val, 0), 1); + changeVolumeOnPan(event); }) - .onEnd(() => { + .onFinalize(() => { runOnJS(setIsSliderBeingUsed)(false); }); const progressBarStyle = useAnimatedStyle(() => ({height: `${volume.value * 100}%`})); - const updateIcon = (vol) => { + const updateIcon = useCallback((vol) => { setVolumeIcon({icon: getVolumeIcon(vol)}); - }; + }, []); useDerivedValue(() => { runOnJS(updateVolume)(volume.value); diff --git a/src/components/VideoPlayerContexts/VolumeContext.js b/src/components/VideoPlayerContexts/VolumeContext.js index 2df463654075..a0b972d37a0d 100644 --- a/src/components/VideoPlayerContexts/VolumeContext.js +++ b/src/components/VideoPlayerContexts/VolumeContext.js @@ -14,7 +14,7 @@ function VolumeContextProvider({children}) { if (!currentVideoPlayerRef.current) { return; } - currentVideoPlayerRef.current.setStatusAsync({volume: newVolume}); + currentVideoPlayerRef.current.setStatusAsync({volume: newVolume, isMuted: newVolume === 0}); volume.value = newVolume; }, [currentVideoPlayerRef, volume], diff --git a/src/libs/NumberUtils.ts b/src/libs/NumberUtils.ts index 60e5246f5ed2..62d6fa00906a 100644 --- a/src/libs/NumberUtils.ts +++ b/src/libs/NumberUtils.ts @@ -76,4 +76,20 @@ function roundDownToLargestMultiple(p: number, q: number) { return Math.floor(p / q) * q; } -export {rand64, generateHexadecimalValue, generateRandomInt, parseFloatAnyLocale, roundDownToLargestMultiple}; +/** + * Rounds a number to two decimal places. + * @returns the rounded value + */ +function roundToTwoDecimalPlaces(value: number): number { + return Math.round(value * 100) / 100; +} + +/** + * Clamps a value between a minimum and maximum value. + * @returns the clamped value + */ +function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} + +export {rand64, generateHexadecimalValue, generateRandomInt, parseFloatAnyLocale, roundDownToLargestMultiple, roundToTwoDecimalPlaces, clamp};