From 8282c434e076400ccc8d3d246b7270616e09355c Mon Sep 17 00:00:00 2001 From: Samuel Newman <10959775+mozzius@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:25:11 +0100 Subject: [PATCH] attempt some sort of "usurping" system --- .../util/post-embeds/ActiveVideoContext.tsx | 61 ++++++++++- src/view/com/util/post-embeds/VideoEmbed.tsx | 4 +- .../com/util/post-embeds/VideoEmbed.web.tsx | 100 ++++++++++++++++++ .../util/post-embeds/VideoEmbedInner.web.tsx | 52 --------- 4 files changed, 158 insertions(+), 59 deletions(-) create mode 100644 src/view/com/util/post-embeds/VideoEmbed.web.tsx delete mode 100644 src/view/com/util/post-embeds/VideoEmbedInner.web.tsx diff --git a/src/view/com/util/post-embeds/ActiveVideoContext.tsx b/src/view/com/util/post-embeds/ActiveVideoContext.tsx index 6804436a7e..8e5f094ecb 100644 --- a/src/view/com/util/post-embeds/ActiveVideoContext.tsx +++ b/src/view/com/util/post-embeds/ActiveVideoContext.tsx @@ -1,25 +1,71 @@ -import React, {useCallback, useId, useMemo, useState} from 'react' +import React, { + useCallback, + useEffect, + useId, + useMemo, + useRef, + useState, +} from 'react' import {VideoPlayerProvider} from './VideoPlayerContext' const ActiveVideoContext = React.createContext<{ activeViewId: string | null setActiveView: (viewId: string, src: string) => void + requestActive: (viewId: string, src: string, y: number) => void + allowUsurp: (viewId: string) => void } | null>(null) export function ActiveVideoProvider({children}: {children: React.ReactNode}) { const [activeViewId, setActiveViewId] = useState(null) const [source, setSource] = useState(null) + const [canBeUsurped, setCanBeUsurped] = useState(false) + const requestedViews = useRef<{[key: string]: {src: string; y: number}}>({}) + const timeoutRef = useRef>() + + useEffect(() => { + return () => { + clearTimeout(timeoutRef.current) + } + }, []) + const value = useMemo( () => ({ activeViewId, setActiveView: (viewId: string, src: string) => { setActiveViewId(viewId) setSource(src) + setCanBeUsurped(false) + }, + requestActive: (viewId: string, src: string, y: number) => { + if (activeViewId && !canBeUsurped) { + return + } + if (requestedViews.current[viewId]) { + requestedViews.current[viewId].y = y + return + } + clearTimeout(timeoutRef.current) + requestedViews.current[viewId] = {src, y} + timeoutRef.current = setTimeout(() => { + const sortedViews = Object.entries(requestedViews.current).sort( + ([, {y: y1}], [, {y: y2}]) => y1 - y2, + ) + const [topViewId, {src: topSrc}] = sortedViews[0] + setActiveViewId(topViewId) + setSource(topSrc) + setCanBeUsurped(false) + requestedViews.current = {} + }, 100) + }, + allowUsurp: (viewId: string) => { + if (activeViewId === viewId) { + setCanBeUsurped(true) + } }, }), - [activeViewId], + [activeViewId, canBeUsurped], ) return ( @@ -31,7 +77,7 @@ export function ActiveVideoProvider({children}: {children: React.ReactNode}) { ) } -export function useActiveVideoView() { +export function useActiveVideoView(source: string) { const context = React.useContext(ActiveVideoContext) if (!context) { throw new Error('useActiveVideo must be used within a ActiveVideoProvider') @@ -41,8 +87,13 @@ export function useActiveVideoView() { return { active: context.activeViewId === id, setActive: useCallback( - (source: string) => context.setActiveView(id, source), - [context, id], + () => context.setActiveView(id, source), + [context, id, source], ), + requestActive: useCallback( + (y: number) => context.requestActive(id, source, y), + [context, id, source], + ), + allowUsurp: useCallback(() => context.allowUsurp(id), [context, id]), } } diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index 5e5293a553..f0d8f477c5 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -11,10 +11,10 @@ import {VideoEmbedInner} from './VideoEmbedInner' export function VideoEmbed({source}: {source: string}) { const t = useTheme() - const {active, setActive} = useActiveVideoView() + const {active, setActive} = useActiveVideoView(source) const {_} = useLingui() - const onPress = useCallback(() => setActive(source), [setActive, source]) + const onPress = useCallback(() => setActive(), [setActive]) return ( new Hls()) + const hasLoaded = useRef(false) + const {active, setActive, requestActive, allowUsurp} = + useActiveVideoView(source) + const ref = useRef(null) + const t = useTheme() + + // Use HLS.js to play HLS video + useEffect(() => { + if (ref.current && active && !hasLoaded.current) { + hasLoaded.current = true + if (ref.current.canPlayType('application/vnd.apple.mpegurl')) { + ref.current.src = source + } else if (Hls.isSupported()) { + hls.loadSource(source) + hls.attachMedia(ref.current) + } else { + // TODO: fallback + } + } + }, [source, active, hls]) + + useEffect(() => { + return () => { + hls.destroy() + } + }, [hls]) + + useEffect(() => { + if (ref.current) { + const observer = new IntersectionObserver( + ([entry]) => { + if (ref.current) { + if (entry.intersectionRatio > 0.25) { + if (active) { + if (ref.current.paused) { + ref.current.play() + } + } else { + requestActive(entry.boundingClientRect.y) + } + } else { + if (active) { + if (!ref.current.paused) { + ref.current.pause() + } + } + } + + if (entry.intersectionRatio < 0.75) { + allowUsurp() + } + } + }, + {threshold: [0, 0.25, 1]}, + ) + + observer.observe(ref.current) + + return () => { + observer.disconnect() + } + } + }, [active, requestActive, allowUsurp]) + + return ( + + + ) +} diff --git a/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx deleted file mode 100644 index cb02743c6f..0000000000 --- a/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, {useEffect, useRef} from 'react' -import Hls from 'hls.js' - -import {atoms as a} from '#/alf' - -export const VideoEmbedInner = ({source}: {source: string}) => { - const ref = useRef(null) - - // Use HLS.js to play HLS video - useEffect(() => { - if (ref.current) { - if (ref.current.canPlayType('application/vnd.apple.mpegurl')) { - ref.current.src = source - } else if (Hls.isSupported()) { - var hls = new Hls() - hls.loadSource(source) - hls.attachMedia(ref.current) - } else { - // TODO: fallback - } - } - }, [source]) - - useEffect(() => { - if (ref.current) { - const observer = new IntersectionObserver( - ([entry]) => { - if (ref.current) { - if (entry.isIntersecting) { - if (ref.current.paused) { - ref.current.play() - } - } else { - if (!ref.current.paused) { - ref.current.pause() - } - } - } - }, - {threshold: 0}, - ) - - observer.observe(ref.current) - - return () => { - observer.disconnect() - } - } - }, []) - - return