attempt some sort of "usurping" system

This commit is contained in:
Samuel Newman
2024-07-03 17:25:11 +01:00
parent 8ddb28d3c5
commit 8282c434e0
4 changed files with 158 additions and 59 deletions
@@ -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<string | null>(null)
const [source, setSource] = useState<string | null>(null)
const [canBeUsurped, setCanBeUsurped] = useState(false)
const requestedViews = useRef<{[key: string]: {src: string; y: number}}>({})
const timeoutRef = useRef<ReturnType<typeof setTimeout>>()
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]),
}
}
+2 -2
View File
@@ -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 (
<View
@@ -0,0 +1,100 @@
import React, {useEffect, useRef, useState} from 'react'
import {View} from 'react-native'
import Hls from 'hls.js'
import {atoms as a, useTheme} from '#/alf'
import {useActiveVideoView} from './ActiveVideoContext'
export function VideoEmbed({source}: {source: string}) {
const [hls] = useState(() => new Hls())
const hasLoaded = useRef(false)
const {active, setActive, requestActive, allowUsurp} =
useActiveVideoView(source)
const ref = useRef<HTMLVideoElement>(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 (
<View
style={[
a.w_full,
a.rounded_sm,
{aspectRatio: 16 / 9},
a.overflow_hidden,
active ? t.atoms.bg_contrast_25 : t.atoms.bg_contrast_200,
a.my_xs,
]}>
<video
ref={ref}
style={a.flex_1}
playsInline
preload="none"
loop
muted
autoPlay={active}
onClick={() => {
if (!active) {
setActive()
}
}}
/>
</View>
)
}
@@ -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<HTMLVideoElement>(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 <video ref={ref} style={a.flex_1} controls playsInline autoPlay loop />
}