about summary refs log tree commit diff
path: root/src/view/com/util/post-embeds/ActiveVideoContext.tsx
blob: d18dfc090816291e0f4d260b092807ccef746ee0 (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
111
112
113
114
115
116
117
118
119
import React, {
  useCallback,
  useEffect,
  useId,
  useMemo,
  useRef,
  useState,
} from 'react'
import {useWindowDimensions} from 'react-native'

import {isNative} from '#/platform/detection'
import {VideoPlayerProvider} from './VideoPlayerContext'

const ActiveVideoContext = React.createContext<{
  activeViewId: string | null
  setActiveView: (viewId: string, src: string) => void
  sendViewPosition: (viewId: string, y: number) => void
} | null>(null)

export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
  const [activeViewId, setActiveViewId] = useState<string | null>(null)
  const activeViewLocationRef = useRef(Infinity)
  const [source, setSource] = useState<string | null>(null)
  const {height: windowHeight} = useWindowDimensions()

  // minimising re-renders by using refs
  const manuallySetRef = useRef(false)
  const activeViewIdRef = useRef(activeViewId)
  useEffect(() => {
    activeViewIdRef.current = activeViewId
  }, [activeViewId])

  const setActiveView = useCallback(
    (viewId: string, src: string) => {
      setActiveViewId(viewId)
      setSource(src)
      manuallySetRef.current = true
      // we don't know the exact position, but it's definitely on screen
      // so just guess that it's in the middle. Any value is fine
      // so long as it's not offscreen
      activeViewLocationRef.current = windowHeight / 2
    },
    [windowHeight],
  )

  const sendViewPosition = useCallback(
    (viewId: string, y: number) => {
      if (isNative) return

      if (viewId === activeViewIdRef.current) {
        activeViewLocationRef.current = y
      } else {
        if (
          distanceToIdealPosition(y) <
          distanceToIdealPosition(activeViewLocationRef.current)
        ) {
          // if the old view was manually set, only usurp if the old view is offscreen
          if (
            manuallySetRef.current &&
            withinViewport(activeViewLocationRef.current)
          ) {
            return
          }

          setActiveViewId(viewId)
          activeViewLocationRef.current = y
          manuallySetRef.current = false
        }
      }

      function distanceToIdealPosition(yPos: number) {
        return Math.abs(yPos - windowHeight / 2.5)
      }

      function withinViewport(yPos: number) {
        return yPos > 0 && yPos < windowHeight
      }
    },
    [windowHeight],
  )

  const value = useMemo(
    () => ({
      activeViewId,
      setActiveView,
      sendViewPosition,
    }),
    [activeViewId, setActiveView, sendViewPosition],
  )

  return (
    <ActiveVideoContext.Provider value={value}>
      <VideoPlayerProvider source={source ?? ''}>
        {children}
      </VideoPlayerProvider>
    </ActiveVideoContext.Provider>
  )
}

export function useActiveVideoView({source}: {source: string}) {
  const context = React.useContext(ActiveVideoContext)
  if (!context) {
    throw new Error('useActiveVideo must be used within a ActiveVideoProvider')
  }
  const id = useId()

  return {
    active: context.activeViewId === id,
    setActive: useCallback(
      () => context.setActiveView(id, source),
      [context, id, source],
    ),
    currentActiveView: context.activeViewId,
    sendPosition: useCallback(
      (y: number) => context.sendViewPosition(id, y),
      [context, id],
    ),
  }
}