From fff2c079c2554861764974aaeeb56f79a25ba82a Mon Sep 17 00:00:00 2001
From: Samuel Newman <mozzius@protonmail.com>
Date: Wed, 7 Aug 2024 18:47:51 +0100
Subject: [Videos] Video player - PR #2 - better web support (#4732)

* attempt some sort of "usurping" system

* polling-based active video approach

* split into inner component again

* click to steal active video

* disable findAndActivateVideo on native

* new intersectionobserver approach - wip

* fix types

* disable perf optimisation to allow overflow

* make active player indicator subtler, clean up video utils

* partially fix double-playing

* start working on controls

* fullscreen API

* get buttons working somewhat

* rm source from where it shouldn't be

* use video elem as source of truth

* fix keyboard nav + mute state

* new icons, add fullscreen + time + fix play

* unmount when far offscreen + round 2dp

* listen globally to clicks rather than blur event

* move controls to new file

* reduce quality when not active

* add hover state to buttons

* stop propagation of videoplayer click

* move around autoplay effects

* increase background contrast

* add subtitles button

* add stopPropagation to root of video player

* clean up VideoWebControls

* fix chrome

* change quality based on focused state

* use autoLevelCapping instead of nextLevel

* get subtitle track from stream

* always use hlsjs

* rework hls into a ref

* render player earlier, allowing preload

* add error boundary

* clean up component structure and organisation

* rework fullscreen API

* disable fullscreen on iPhone

* don't play when ready on pause

* debounce buffering

* simplify giant list of event listeners

* update pref

* reduce prop drilling

* minimise rerenders in `ActiveViewContext`

* restore prop drilling

---------

Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com>
Co-authored-by: Hailey <me@haileyok.com>
---
 .../com/util/post-embeds/ActiveVideoContext.tsx    | 89 +++++++++++++++++++---
 1 file changed, 80 insertions(+), 9 deletions(-)

(limited to 'src/view/com/util/post-embeds/ActiveVideoContext.tsx')

diff --git a/src/view/com/util/post-embeds/ActiveVideoContext.tsx b/src/view/com/util/post-embeds/ActiveVideoContext.tsx
index 6804436a7..d18dfc090 100644
--- a/src/view/com/util/post-embeds/ActiveVideoContext.tsx
+++ b/src/view/com/util/post-embeds/ActiveVideoContext.tsx
@@ -1,37 +1,103 @@
-import React, {useCallback, useId, useMemo, useState} from 'react'
+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: (viewId: string, src: string) => {
-        setActiveViewId(viewId)
-        setSource(src)
-      },
+      setActiveView,
+      sendViewPosition,
     }),
-    [activeViewId],
+    [activeViewId, setActiveView, sendViewPosition],
   )
 
   return (
     <ActiveVideoContext.Provider value={value}>
-      <VideoPlayerProvider source={source ?? ''} viewId={activeViewId}>
+      <VideoPlayerProvider source={source ?? ''}>
         {children}
       </VideoPlayerProvider>
     </ActiveVideoContext.Provider>
   )
 }
 
-export function useActiveVideoView() {
+export function useActiveVideoView({source}: {source: string}) {
   const context = React.useContext(ActiveVideoContext)
   if (!context) {
     throw new Error('useActiveVideo must be used within a ActiveVideoProvider')
@@ -41,7 +107,12 @@ export function useActiveVideoView() {
   return {
     active: context.activeViewId === id,
     setActive: useCallback(
-      (source: string) => context.setActiveView(id, source),
+      () => context.setActiveView(id, source),
+      [context, id, source],
+    ),
+    currentActiveView: context.activeViewId,
+    sendPosition: useCallback(
+      (y: number) => context.sendViewPosition(id, y),
       [context, id],
     ),
   }
-- 
cgit 1.4.1

