An ATproto social media client -- with an independent Appview.
at main 110 lines 3.3 kB view raw
1import {useCallback} from 'react' 2import {View} from 'react-native' 3import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {isSafari, isTouchDevice} from '#/lib/browser' 8import {atoms as a} from '#/alf' 9import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 10import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 11import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 12import {ControlButton} from './ControlButton' 13 14export function VolumeControl({ 15 muted, 16 changeMuted, 17 hovered, 18 onHover, 19 onEndHover, 20 drawFocus, 21}: { 22 muted: boolean 23 changeMuted: (muted: boolean | ((prev: boolean) => boolean)) => void 24 hovered: boolean 25 onHover: () => void 26 onEndHover: () => void 27 drawFocus: () => void 28}) { 29 const {_} = useLingui() 30 const [volume, setVolume] = useVideoVolumeState() 31 32 const onVolumeChange = useCallback( 33 (evt: React.ChangeEvent<HTMLInputElement>) => { 34 drawFocus() 35 const vol = sliderVolumeToVideoVolume(Number(evt.target.value)) 36 setVolume(vol) 37 changeMuted(vol === 0) 38 }, 39 [setVolume, drawFocus, changeMuted], 40 ) 41 42 const sliderVolume = muted ? 0 : videoVolumeToSliderVolume(volume) 43 44 const isZeroVolume = volume === 0 45 const onPressMute = useCallback(() => { 46 drawFocus() 47 if (isZeroVolume) { 48 setVolume(1) 49 changeMuted(false) 50 } else { 51 changeMuted(prevMuted => !prevMuted) 52 } 53 }, [drawFocus, setVolume, isZeroVolume, changeMuted]) 54 55 return ( 56 <View 57 onPointerEnter={onHover} 58 onPointerLeave={onEndHover} 59 style={[a.relative]}> 60 {hovered && !isTouchDevice && ( 61 <Animated.View 62 entering={FadeIn.duration(100)} 63 exiting={FadeOut.duration(100)} 64 style={[a.absolute, a.w_full, {height: 100, bottom: '100%'}]}> 65 <View 66 style={[ 67 a.flex_1, 68 a.mb_xs, 69 a.px_2xs, 70 a.py_xs, 71 {backgroundColor: 'rgba(0, 0, 0, 0.6)'}, 72 a.rounded_xs, 73 a.align_center, 74 ]}> 75 <input 76 type="range" 77 min={0} 78 max={100} 79 value={sliderVolume} 80 aria-label={_(msg`Volume`)} 81 style={ 82 // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h 83 isSafari ? {height: 92, minHeight: '100%'} : {height: '100%'} 84 } 85 onChange={onVolumeChange} 86 // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn 87 orient="vertical" 88 /> 89 </View> 90 </Animated.View> 91 )} 92 <ControlButton 93 active={muted || volume === 0} 94 activeLabel={_(msg({message: `Unmute`, context: 'video'}))} 95 inactiveLabel={_(msg({message: `Mute`, context: 'video'}))} 96 activeIcon={MuteIcon} 97 inactiveIcon={UnmuteIcon} 98 onPress={onPressMute} 99 /> 100 </View> 101 ) 102} 103 104function sliderVolumeToVideoVolume(value: number) { 105 return Math.pow(value / 100, 4) 106} 107 108function videoVolumeToSliderVolume(value: number) { 109 return Math.round(Math.pow(value, 1 / 4) * 100) 110}