click to steal active video
This commit is contained in:
@@ -22,6 +22,7 @@ const ActiveVideoContext = React.createContext<{
|
|||||||
export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
|
export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
|
||||||
const [activeViewId, setActiveViewId] = useState<string | null>(null)
|
const [activeViewId, setActiveViewId] = useState<string | null>(null)
|
||||||
const [source, setSource] = useState<string | null>(null)
|
const [source, setSource] = useState<string | null>(null)
|
||||||
|
const [manuallySet, setManuallySet] = useState(false)
|
||||||
const {height: windowHeight} = useWindowDimensions()
|
const {height: windowHeight} = useWindowDimensions()
|
||||||
|
|
||||||
const measurementCallbacks = useRef<Record<string, () => DOMRect | void>>({})
|
const measurementCallbacks = useRef<Record<string, () => DOMRect | void>>({})
|
||||||
@@ -46,7 +47,37 @@ export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
|
|||||||
const active = closestToMiddle(windowHeight, videosInView)
|
const active = closestToMiddle(windowHeight, videosInView)
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
setActiveViewId(active.id)
|
// change the active view if it's not already active
|
||||||
|
// if the user has manually set the active view, don't change it
|
||||||
|
// i.e. if the user clicks on a video at the bottom of the screen
|
||||||
|
// it takes precidence over the video that is closest to the middle
|
||||||
|
setActiveViewId(activeView => {
|
||||||
|
if (activeView !== active.id) {
|
||||||
|
if (manuallySet && videosInView.find(({id}) => id === activeView)) {
|
||||||
|
return activeView
|
||||||
|
}
|
||||||
|
setManuallySet(false)
|
||||||
|
return active.id
|
||||||
|
}
|
||||||
|
return activeView
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// if no videos are in view, unset the active view
|
||||||
|
// if the active view is partially in view, keep it active
|
||||||
|
setActiveViewId(activeView => {
|
||||||
|
const activeRect = locations.find(({id}) => id === activeView)?.rect
|
||||||
|
|
||||||
|
if (activeRect) {
|
||||||
|
const topCond = activeRect.top + activeRect.height / 2 >= 0
|
||||||
|
const bottomCond =
|
||||||
|
activeRect.bottom - activeRect.height / 2 <= windowHeight
|
||||||
|
if (topCond && bottomCond) {
|
||||||
|
return activeView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
findAndActivateVideo()
|
findAndActivateVideo()
|
||||||
@@ -55,7 +86,7 @@ export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
|
|||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
}
|
}
|
||||||
}, [activeViewId, windowHeight])
|
}, [manuallySet, windowHeight])
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -63,6 +94,7 @@ export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
|
|||||||
setActiveView: (viewId: string, src: string) => {
|
setActiveView: (viewId: string, src: string) => {
|
||||||
setActiveViewId(viewId)
|
setActiveViewId(viewId)
|
||||||
setSource(src)
|
setSource(src)
|
||||||
|
setManuallySet(true)
|
||||||
},
|
},
|
||||||
registerMeasurementCallback: (viewId: string, callback: () => void) => {
|
registerMeasurementCallback: (viewId: string, callback: () => void) => {
|
||||||
measurementCallbacks.current[viewId] = callback
|
measurementCallbacks.current[viewId] = callback
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ export function VideoEmbed({source}: {source: string}) {
|
|||||||
a.my_xs,
|
a.my_xs,
|
||||||
]}>
|
]}>
|
||||||
{active ? (
|
{active ? (
|
||||||
<VideoEmbedInner source={source} active={active} />
|
<VideoEmbedInner
|
||||||
|
source={source}
|
||||||
|
active={active}
|
||||||
|
setActive={setActive}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
style={[a.flex_1, t.atoms.bg_contrast_25]}
|
style={[a.flex_1, t.atoms.bg_contrast_25]}
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ export function VideoEmbed({source}: {source: string}) {
|
|||||||
]}>
|
]}>
|
||||||
<div ref={ref} style={{display: 'flex', flex: 1}}>
|
<div ref={ref} style={{display: 'flex', flex: 1}}>
|
||||||
{hasBeenActive ? (
|
{hasBeenActive ? (
|
||||||
<VideoEmbedInner source={source} active={active} />
|
<VideoEmbedInner
|
||||||
|
source={source}
|
||||||
|
active={active}
|
||||||
|
setActive={setActive}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
style={[a.flex_1, t.atoms.bg_contrast_25]}
|
style={[a.flex_1, t.atoms.bg_contrast_25]}
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import {atoms as a} from '#/alf'
|
|||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useVideoPlayer} from './VideoPlayerContext'
|
import {useVideoPlayer} from './VideoPlayerContext'
|
||||||
|
|
||||||
export function VideoEmbedInner({}: {source: string; active: boolean}) {
|
export function VideoEmbedInner({}: {
|
||||||
|
source: string
|
||||||
|
active: boolean
|
||||||
|
setActive: () => void
|
||||||
|
}) {
|
||||||
const player = useVideoPlayer()
|
const player = useVideoPlayer()
|
||||||
const aref = useAnimatedRef<Animated.View>()
|
const aref = useAnimatedRef<Animated.View>()
|
||||||
const {height: windowHeight} = useWindowDimensions()
|
const {height: windowHeight} = useWindowDimensions()
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import {atoms as a, useTheme} from '#/alf'
|
|||||||
export function VideoEmbedInner({
|
export function VideoEmbedInner({
|
||||||
source,
|
source,
|
||||||
active,
|
active,
|
||||||
|
setActive,
|
||||||
}: {
|
}: {
|
||||||
source: string
|
source: string
|
||||||
active: boolean
|
active: boolean
|
||||||
|
setActive: () => void
|
||||||
}) {
|
}) {
|
||||||
const [hls] = useState(() => new Hls())
|
const [hls] = useState(() => new Hls())
|
||||||
const ref = useRef<HTMLVideoElement>(null)
|
const ref = useRef<HTMLVideoElement>(null)
|
||||||
|
const [focused, setFocused] = useState(false)
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,6 +41,15 @@ export function VideoEmbedInner({
|
|||||||
}
|
}
|
||||||
}, [source, hls])
|
}, [source, hls])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (active) {
|
||||||
|
ref.current?.play()
|
||||||
|
} else {
|
||||||
|
ref.current?.pause()
|
||||||
|
setFocused(false)
|
||||||
|
}
|
||||||
|
}, [active])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -55,8 +67,22 @@ export function VideoEmbedInner({
|
|||||||
playsInline
|
playsInline
|
||||||
preload="none"
|
preload="none"
|
||||||
loop
|
loop
|
||||||
muted
|
muted={!focused}
|
||||||
autoPlay={active}
|
onClick={evt => {
|
||||||
|
evt.stopPropagation()
|
||||||
|
if (focused) {
|
||||||
|
if (ref.current?.paused) {
|
||||||
|
ref.current.play()
|
||||||
|
} else {
|
||||||
|
ref.current?.pause()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!active) {
|
||||||
|
setActive()
|
||||||
|
}
|
||||||
|
setFocused(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user