about summary refs log tree commit diff
path: root/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx')
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx587
1 files changed, 0 insertions, 587 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx
deleted file mode 100644
index 7caaf3abf..000000000
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx
+++ /dev/null
@@ -1,587 +0,0 @@
-import React, {
-  useCallback,
-  useEffect,
-  useRef,
-  useState,
-  useSyncExternalStore,
-} from 'react'
-import {Pressable, View} from 'react-native'
-import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import type Hls from 'hls.js'
-
-import {isIPhoneWeb} from 'platform/detection'
-import {
-  useAutoplayDisabled,
-  useSetSubtitlesEnabled,
-  useSubtitlesEnabled,
-} from 'state/preferences'
-import {atoms as a, useTheme, web} from '#/alf'
-import {Button} from '#/components/Button'
-import {useInteractionState} from '#/components/hooks/useInteractionState'
-import {
-  ArrowsDiagonalIn_Stroke2_Corner0_Rounded as ArrowsInIcon,
-  ArrowsDiagonalOut_Stroke2_Corner0_Rounded as ArrowsOutIcon,
-} from '#/components/icons/ArrowsDiagonal'
-import {
-  CC_Filled_Corner0_Rounded as CCActiveIcon,
-  CC_Stroke2_Corner0_Rounded as CCInactiveIcon,
-} from '#/components/icons/CC'
-import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
-import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause'
-import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
-import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
-import {Loader} from '#/components/Loader'
-import {Text} from '#/components/Typography'
-
-export function Controls({
-  videoRef,
-  hlsRef,
-  active,
-  setActive,
-  focused,
-  setFocused,
-  onScreen,
-  fullscreenRef,
-  hasSubtitleTrack,
-}: {
-  videoRef: React.RefObject<HTMLVideoElement>
-  hlsRef: React.RefObject<Hls | undefined>
-  active: boolean
-  setActive: () => void
-  focused: boolean
-  setFocused: (focused: boolean) => void
-  onScreen: boolean
-  fullscreenRef: React.RefObject<HTMLDivElement>
-  hasSubtitleTrack: boolean
-}) {
-  const {
-    play,
-    pause,
-    playing,
-    muted,
-    toggleMute,
-    togglePlayPause,
-    currentTime,
-    duration,
-    buffering,
-    error,
-    canPlay,
-  } = useVideoUtils(videoRef)
-  const t = useTheme()
-  const {_} = useLingui()
-  const subtitlesEnabled = useSubtitlesEnabled()
-  const setSubtitlesEnabled = useSetSubtitlesEnabled()
-  const {
-    state: hovered,
-    onIn: onMouseEnter,
-    onOut: onMouseLeave,
-  } = useInteractionState()
-  const [isFullscreen, toggleFullscreen] = useFullscreen(fullscreenRef)
-  const {state: hasFocus, onIn: onFocus, onOut: onBlur} = useInteractionState()
-  const [interactingViaKeypress, setInteractingViaKeypress] = useState(false)
-
-  const onKeyDown = useCallback(() => {
-    setInteractingViaKeypress(true)
-  }, [])
-
-  useEffect(() => {
-    if (interactingViaKeypress) {
-      document.addEventListener('click', () => setInteractingViaKeypress(false))
-      return () => {
-        document.removeEventListener('click', () =>
-          setInteractingViaKeypress(false),
-        )
-      }
-    }
-  }, [interactingViaKeypress])
-
-  // pause + unfocus when another video is active
-  useEffect(() => {
-    if (!active) {
-      pause()
-      setFocused(false)
-    }
-  }, [active, pause, setFocused])
-
-  // autoplay/pause based on visibility
-  const autoplayDisabled = useAutoplayDisabled()
-  useEffect(() => {
-    if (active && !autoplayDisabled) {
-      if (onScreen) {
-        play()
-      } else {
-        pause()
-      }
-    }
-  }, [onScreen, pause, active, play, autoplayDisabled])
-
-  // use minimal quality when not focused
-  useEffect(() => {
-    if (!hlsRef.current) return
-    if (focused) {
-      // auto decide quality based on network conditions
-      hlsRef.current.autoLevelCapping = -1
-    } else {
-      hlsRef.current.autoLevelCapping = 0
-    }
-  }, [hlsRef, focused])
-
-  useEffect(() => {
-    if (!hlsRef.current) return
-    if (hasSubtitleTrack && subtitlesEnabled && canPlay) {
-      hlsRef.current.subtitleTrack = 0
-    } else {
-      hlsRef.current.subtitleTrack = -1
-    }
-  }, [hasSubtitleTrack, subtitlesEnabled, hlsRef, canPlay])
-
-  // clicking on any button should focus the player, if it's not already focused
-  const drawFocus = useCallback(() => {
-    if (!active) {
-      setActive()
-    }
-    setFocused(true)
-  }, [active, setActive, setFocused])
-
-  const onPressEmptySpace = useCallback(() => {
-    if (!focused) {
-      drawFocus()
-    } else {
-      togglePlayPause()
-    }
-  }, [togglePlayPause, drawFocus, focused])
-
-  const onPressPlayPause = useCallback(() => {
-    drawFocus()
-    togglePlayPause()
-  }, [drawFocus, togglePlayPause])
-
-  const onPressSubtitles = useCallback(() => {
-    drawFocus()
-    setSubtitlesEnabled(!subtitlesEnabled)
-  }, [drawFocus, setSubtitlesEnabled, subtitlesEnabled])
-
-  const onPressMute = useCallback(() => {
-    drawFocus()
-    toggleMute()
-  }, [drawFocus, toggleMute])
-
-  const onPressFullscreen = useCallback(() => {
-    drawFocus()
-    toggleFullscreen()
-  }, [drawFocus, toggleFullscreen])
-
-  const showControls =
-    (focused && !playing) || (interactingViaKeypress ? hasFocus : hovered)
-
-  return (
-    <div
-      style={{
-        position: 'absolute',
-        inset: 0,
-        overflow: 'hidden',
-        display: 'flex',
-        flexDirection: 'column',
-      }}
-      onClick={evt => {
-        evt.stopPropagation()
-        setInteractingViaKeypress(false)
-      }}
-      onMouseEnter={onMouseEnter}
-      onMouseLeave={onMouseLeave}
-      onFocus={onFocus}
-      onBlur={onBlur}
-      onKeyDown={onKeyDown}>
-      <Pressable
-        accessibilityRole="button"
-        accessibilityHint={_(
-          focused
-            ? msg`Unmute video`
-            : playing
-            ? msg`Pause video`
-            : msg`Play video`,
-        )}
-        style={a.flex_1}
-        onPress={onPressEmptySpace}
-      />
-      <View
-        style={[
-          a.flex_shrink_0,
-          a.w_full,
-          a.px_sm,
-          a.pt_sm,
-          a.pb_md,
-          a.gap_md,
-          a.flex_row,
-          a.align_center,
-          web({
-            background:
-              'linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.7))',
-          }),
-          showControls ? {opacity: 1} : {opacity: 0},
-        ]}>
-        <Button
-          label={_(playing ? msg`Pause` : msg`Play`)}
-          onPress={onPressPlayPause}
-          {...btnProps}>
-          {playing ? (
-            <PauseIcon fill={t.palette.white} width={20} />
-          ) : (
-            <PlayIcon fill={t.palette.white} width={20} />
-          )}
-        </Button>
-        <View style={a.flex_1} />
-        <Text style={{color: t.palette.white}}>
-          {formatTime(currentTime)} / {formatTime(duration)}
-        </Text>
-        {hasSubtitleTrack && (
-          <Button
-            label={_(
-              subtitlesEnabled ? msg`Disable subtitles` : msg`Enable subtitles`,
-            )}
-            onPress={onPressSubtitles}
-            {...btnProps}>
-            {subtitlesEnabled ? (
-              <CCActiveIcon fill={t.palette.white} width={20} />
-            ) : (
-              <CCInactiveIcon fill={t.palette.white} width={20} />
-            )}
-          </Button>
-        )}
-        <Button
-          label={_(muted ? msg`Unmute` : msg`Mute`)}
-          onPress={onPressMute}
-          {...btnProps}>
-          {muted ? (
-            <MuteIcon fill={t.palette.white} width={20} />
-          ) : (
-            <UnmuteIcon fill={t.palette.white} width={20} />
-          )}
-        </Button>
-        {!isIPhoneWeb && (
-          <Button
-            label={_(muted ? msg`Unmute` : msg`Mute`)}
-            onPress={onPressFullscreen}
-            {...btnProps}>
-            {isFullscreen ? (
-              <ArrowsInIcon fill={t.palette.white} width={20} />
-            ) : (
-              <ArrowsOutIcon fill={t.palette.white} width={20} />
-            )}
-          </Button>
-        )}
-      </View>
-      {(showControls || !focused) && (
-        <Animated.View
-          entering={FadeIn.duration(200)}
-          exiting={FadeOut.duration(200)}
-          style={[
-            a.absolute,
-            {
-              height: 5,
-              bottom: 0,
-              left: 0,
-              right: 0,
-              backgroundColor: 'rgba(255,255,255,0.4)',
-            },
-          ]}>
-          {duration > 0 && (
-            <View
-              style={[
-                a.h_full,
-                a.mr_auto,
-                {
-                  backgroundColor: t.palette.white,
-                  width: `${(currentTime / duration) * 100}%`,
-                  opacity: 0.8,
-                },
-              ]}
-            />
-          )}
-        </Animated.View>
-      )}
-      {(buffering || error) && (
-        <Animated.View
-          pointerEvents="none"
-          entering={FadeIn.delay(1000).duration(200)}
-          exiting={FadeOut.duration(200)}
-          style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
-          {buffering && <Loader fill={t.palette.white} size="lg" />}
-          {error && (
-            <Text style={{color: t.palette.white}}>
-              <Trans>An error occurred</Trans>
-            </Text>
-          )}
-        </Animated.View>
-      )}
-    </div>
-  )
-}
-
-const btnProps = {
-  variant: 'ghost',
-  shape: 'round',
-  size: 'medium',
-  style: a.p_2xs,
-  hoverStyle: {backgroundColor: 'rgba(255, 255, 255, 0.1)'},
-} as const
-
-function formatTime(time: number) {
-  if (isNaN(time)) {
-    return '--'
-  }
-
-  time = Math.round(time)
-
-  const minutes = Math.floor(time / 60)
-  const seconds = String(time % 60).padStart(2, '0')
-
-  return `${minutes}:${seconds}`
-}
-
-function useVideoUtils(ref: React.RefObject<HTMLVideoElement>) {
-  const [playing, setPlaying] = useState(false)
-  const [muted, setMuted] = useState(true)
-  const [currentTime, setCurrentTime] = useState(0)
-  const [duration, setDuration] = useState(0)
-  const [buffering, setBuffering] = useState(false)
-  const [error, setError] = useState(false)
-  const [canPlay, setCanPlay] = useState(false)
-  const playWhenReadyRef = useRef(false)
-
-  useEffect(() => {
-    if (!ref.current) return
-
-    let bufferingTimeout: ReturnType<typeof setTimeout> | undefined
-
-    function round(num: number) {
-      return Math.round(num * 100) / 100
-    }
-
-    // Initial values
-    setCurrentTime(round(ref.current.currentTime) || 0)
-    setDuration(round(ref.current.duration) || 0)
-    setMuted(ref.current.muted)
-    setPlaying(!ref.current.paused)
-
-    const handleTimeUpdate = () => {
-      if (!ref.current) return
-      setCurrentTime(round(ref.current.currentTime) || 0)
-    }
-
-    const handleDurationChange = () => {
-      if (!ref.current) return
-      setDuration(round(ref.current.duration) || 0)
-    }
-
-    const handlePlay = () => {
-      setPlaying(true)
-    }
-
-    const handlePause = () => {
-      setPlaying(false)
-    }
-
-    const handleVolumeChange = () => {
-      if (!ref.current) return
-      setMuted(ref.current.muted)
-    }
-
-    const handleError = () => {
-      setError(true)
-    }
-
-    const handleCanPlay = () => {
-      setBuffering(false)
-      setCanPlay(true)
-
-      if (!ref.current) return
-      if (playWhenReadyRef.current) {
-        ref.current.play()
-        playWhenReadyRef.current = false
-      }
-    }
-
-    const handleCanPlayThrough = () => {
-      setBuffering(false)
-    }
-
-    const handleWaiting = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      bufferingTimeout = setTimeout(() => {
-        setBuffering(true)
-      }, 200) // Delay to avoid frequent buffering state changes
-    }
-
-    const handlePlaying = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      setBuffering(false)
-      setError(false)
-    }
-
-    const handleSeeking = () => {
-      setBuffering(true)
-    }
-
-    const handleSeeked = () => {
-      setBuffering(false)
-    }
-
-    const handleStalled = () => {
-      if (bufferingTimeout) clearTimeout(bufferingTimeout)
-      bufferingTimeout = setTimeout(() => {
-        setBuffering(true)
-      }, 200) // Delay to avoid frequent buffering state changes
-    }
-
-    const handleEnded = () => {
-      setPlaying(false)
-      setBuffering(false)
-      setError(false)
-    }
-
-    const abortController = new AbortController()
-
-    ref.current.addEventListener('timeupdate', handleTimeUpdate, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('durationchange', handleDurationChange, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('play', handlePlay, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('pause', handlePause, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('volumechange', handleVolumeChange, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('error', handleError, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('canplay', handleCanPlay, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('canplaythrough', handleCanPlayThrough, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('waiting', handleWaiting, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('playing', handlePlaying, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('seeking', handleSeeking, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('seeked', handleSeeked, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('stalled', handleStalled, {
-      signal: abortController.signal,
-    })
-    ref.current.addEventListener('ended', handleEnded, {
-      signal: abortController.signal,
-    })
-
-    return () => {
-      abortController.abort()
-      clearTimeout(bufferingTimeout)
-    }
-  }, [ref])
-
-  const play = useCallback(() => {
-    if (!ref.current) return
-
-    if (ref.current.ended) {
-      ref.current.currentTime = 0
-    }
-
-    if (ref.current.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
-      playWhenReadyRef.current = true
-    } else {
-      const promise = ref.current.play()
-      if (promise !== undefined) {
-        promise.catch(err => {
-          console.error('Error playing video:', err)
-        })
-      }
-    }
-  }, [ref])
-
-  const pause = useCallback(() => {
-    if (!ref.current) return
-
-    ref.current.pause()
-    playWhenReadyRef.current = false
-  }, [ref])
-
-  const togglePlayPause = useCallback(() => {
-    if (!ref.current) return
-
-    if (ref.current.paused) {
-      play()
-    } else {
-      pause()
-    }
-  }, [ref, play, pause])
-
-  const mute = useCallback(() => {
-    if (!ref.current) return
-
-    ref.current.muted = true
-  }, [ref])
-
-  const unmute = useCallback(() => {
-    if (!ref.current) return
-
-    ref.current.muted = false
-  }, [ref])
-
-  const toggleMute = useCallback(() => {
-    if (!ref.current) return
-
-    ref.current.muted = !ref.current.muted
-  }, [ref])
-
-  return {
-    play,
-    pause,
-    togglePlayPause,
-    duration,
-    currentTime,
-    playing,
-    muted,
-    mute,
-    unmute,
-    toggleMute,
-    buffering,
-    error,
-    canPlay,
-  }
-}
-
-function fullscreenSubscribe(onChange: () => void) {
-  document.addEventListener('fullscreenchange', onChange)
-  return () => document.removeEventListener('fullscreenchange', onChange)
-}
-
-function useFullscreen(ref: React.RefObject<HTMLElement>) {
-  const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
-    Boolean(document.fullscreenElement),
-  )
-
-  const toggleFullscreen = useCallback(() => {
-    if (isFullscreen) {
-      document.exitFullscreen()
-    } else {
-      if (!ref.current) return
-      ref.current.requestFullscreen()
-    }
-  }, [isFullscreen, ref])
-
-  return [isFullscreen, toggleFullscreen] as const
-}