about summary refs log tree commit diff
path: root/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
blob: 4bb66e17a99e5d67cebe0227de11d5e6ff23d6b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {useCallback} from 'react'
import {View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {isSafari, isTouchDevice} from '#/lib/browser'
import {atoms as a} from '#/alf'
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {ControlButton} from './ControlButton'

export function VolumeControl({
  muted,
  changeMuted,
  hovered,
  onHover,
  onEndHover,
  drawFocus,
}: {
  muted: boolean
  changeMuted: (muted: boolean | ((prev: boolean) => boolean)) => void
  hovered: boolean
  onHover: () => void
  onEndHover: () => void
  drawFocus: () => void
}) {
  const {_} = useLingui()
  const [volume, setVolume] = useVideoVolumeState()

  const onVolumeChange = useCallback(
    (evt: React.ChangeEvent<HTMLInputElement>) => {
      drawFocus()
      const vol = sliderVolumeToVideoVolume(Number(evt.target.value))
      setVolume(vol)
      changeMuted(vol === 0)
    },
    [setVolume, drawFocus, changeMuted],
  )

  const sliderVolume = muted ? 0 : videoVolumeToSliderVolume(volume)

  const isZeroVolume = volume === 0
  const onPressMute = useCallback(() => {
    drawFocus()
    if (isZeroVolume) {
      setVolume(1)
      changeMuted(false)
    } else {
      changeMuted(prevMuted => !prevMuted)
    }
  }, [drawFocus, setVolume, isZeroVolume, changeMuted])

  return (
    <View
      onPointerEnter={onHover}
      onPointerLeave={onEndHover}
      style={[a.relative]}>
      {hovered && !isTouchDevice && (
        <Animated.View
          entering={FadeIn.duration(100)}
          exiting={FadeOut.duration(100)}
          style={[a.absolute, a.w_full, {height: 100, bottom: '100%'}]}>
          <View
            style={[
              a.flex_1,
              a.mb_xs,
              a.px_2xs,
              a.py_xs,
              {backgroundColor: 'rgba(0, 0, 0, 0.6)'},
              a.rounded_xs,
              a.align_center,
            ]}>
            <input
              type="range"
              min={0}
              max={100}
              value={sliderVolume}
              aria-label={_(msg`Volume`)}
              style={
                // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h
                isSafari ? {height: 92, minHeight: '100%'} : {height: '100%'}
              }
              onChange={onVolumeChange}
              // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
              orient="vertical"
            />
          </View>
        </Animated.View>
      )}
      <ControlButton
        active={muted || volume === 0}
        activeLabel={_(msg({message: `Unmute`, context: 'video'}))}
        inactiveLabel={_(msg({message: `Mute`, context: 'video'}))}
        activeIcon={MuteIcon}
        inactiveIcon={UnmuteIcon}
        onPress={onPressMute}
      />
    </View>
  )
}

function sliderVolumeToVideoVolume(value: number) {
  return Math.pow(value / 100, 4)
}

function videoVolumeToSliderVolume(value: number) {
  return Math.round(Math.pow(value, 1 / 4) * 100)
}