From fff2c079c2554861764974aaeeb56f79a25ba82a Mon Sep 17 00:00:00 2001 From: Samuel Newman 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 --- .../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(null) + const activeViewLocationRef = useRef(Infinity) const [source, setSource] = useState(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 ( - + {children} ) } -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