Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {useCallback} from 'react'
2import {View} from 'react-native'
3import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
4import {msg} from '@lingui/core/macro'
5import {useLingui} from '@lingui/react'
6
7import {atoms as a} from '#/alf'
8import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
9import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
10import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
11import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
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 && !IS_WEB_TOUCH_DEVICE && (
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 IS_WEB_SAFARI
84 ? {height: 92, minHeight: '100%'}
85 : {height: '100%'}
86 }
87 onChange={onVolumeChange}
88 // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
89 orient="vertical"
90 />
91 </View>
92 </Animated.View>
93 )}
94 <ControlButton
95 active={muted || volume === 0}
96 activeLabel={_(msg({message: `Unmute`, context: 'video'}))}
97 inactiveLabel={_(msg({message: `Mute`, context: 'video'}))}
98 activeIcon={MuteIcon}
99 inactiveIcon={UnmuteIcon}
100 onPress={onPressMute}
101 />
102 </View>
103 )
104}
105
106function sliderVolumeToVideoVolume(value: number) {
107 return Math.pow(value / 100, 4)
108}
109
110function videoVolumeToSliderVolume(value: number) {
111 return Math.round(Math.pow(value, 1 / 4) * 100)
112}