[Video] 🫧 Move logic around by platform (#5003)

zio/stable
Hailey 2024-08-28 08:46:47 -07:00 committed by GitHub
parent 9aa2b2d14e
commit 5ae0d40a14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 77 additions and 95 deletions

View File

@ -52,7 +52,7 @@ import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {TestCtrls} from '#/view/com/testing/TestCtrls'
import {ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoContext'
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoNativeContext'
import * as Toast from '#/view/com/util/Toast'
import {Shell} from '#/view/shell'
import {ThemeProvider as Alf} from '#/alf'

View File

@ -40,7 +40,7 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoContext'
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext'
import * as Toast from '#/view/com/util/Toast'
import {ToastContainer} from '#/view/com/util/Toast.web'
import {Shell} from '#/view/shell/index'

View File

@ -0,0 +1,40 @@
import React from 'react'
import {useVideoPlayer, VideoPlayer} from 'expo-video'
import {isNative} from '#/platform/detection'
const Context = React.createContext<{
activeSource: string | null
setActiveSource: (src: string) => void
player: VideoPlayer
} | null>(null)
export function Provider({children}: {children: React.ReactNode}) {
if (!isNative) {
throw new Error('ActiveVideoProvider may only be used on native.')
}
const [activeSource, setActiveSource] = React.useState('')
const player = useVideoPlayer(activeSource, p => {
p.muted = true
p.loop = true
p.play()
})
return (
<Context.Provider value={{activeSource, setActiveSource, player}}>
{children}
</Context.Provider>
)
}
export function useActiveVideoNative() {
const context = React.useContext(Context)
if (!context) {
throw new Error(
'useActiveVideoNative must be used within a ActiveVideoNativeProvider',
)
}
return context
}

View File

@ -8,19 +8,21 @@ import React, {
} from 'react'
import {useWindowDimensions} from 'react-native'
import {isNative} from '#/platform/detection'
import {VideoPlayerProvider} from './VideoPlayerContext'
import {isNative, isWeb} from '#/platform/detection'
const ActiveVideoContext = React.createContext<{
const Context = React.createContext<{
activeViewId: string | null
setActiveView: (viewId: string, src: string) => void
setActiveView: (viewId: string) => void
sendViewPosition: (viewId: string, y: number) => void
} | null>(null)
export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
export function Provider({children}: {children: React.ReactNode}) {
if (!isWeb) {
throw new Error('ActiveVideoWebContext may onl be used on web.')
}
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
@ -31,9 +33,8 @@ export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
}, [activeViewId])
const setActiveView = useCallback(
(viewId: string, src: string) => {
(viewId: 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
@ -88,32 +89,26 @@ export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
[activeViewId, setActiveView, sendViewPosition],
)
return (
<ActiveVideoContext.Provider value={value}>
<VideoPlayerProvider source={source ?? ''}>
{children}
</VideoPlayerProvider>
</ActiveVideoContext.Provider>
return <Context.Provider value={value}>{children}</Context.Provider>
}
export function useActiveVideoWeb() {
const context = React.useContext(Context)
if (!context) {
throw new Error(
'useActiveVideoWeb must be used within a ActiveVideoWebProvider',
)
}
export function useActiveVideoView({source}: {source: string}) {
const context = React.useContext(ActiveVideoContext)
if (!context) {
throw new Error('useActiveVideo must be used within a ActiveVideoProvider')
}
const {activeViewId, setActiveView, sendViewPosition} = context
const id = useId()
return {
active: context.activeViewId === id,
setActive: useCallback(
() => context.setActiveView(id, source),
[context, id, source],
),
currentActiveView: context.activeViewId,
sendPosition: useCallback(
(y: number) => context.sendViewPosition(id, y),
[context, id],
),
active: activeViewId === id,
setActive: () => {
setActiveView(id)
},
currentActiveView: activeViewId,
sendPosition: (y: number) => sendViewPosition(id, y),
}
}

View File

@ -9,12 +9,13 @@ import {Button, ButtonIcon} from '#/components/Button'
import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play'
import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army'
import {ErrorBoundary} from '../ErrorBoundary'
import {useActiveVideoView} from './ActiveVideoContext'
import {useActiveVideoNative} from './ActiveVideoNativeContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
export function VideoEmbed({source}: {source: string}) {
const t = useTheme()
const {active, setActive} = useActiveVideoView({source})
const {activeSource, setActiveSource} = useActiveVideoNative()
const isActive = source === activeSource
const {_} = useLingui()
const [key, setKey] = useState(0)
@ -40,15 +41,17 @@ export function VideoEmbed({source}: {source: string}) {
enabled={true}
onChangeStatus={isActive => {
if (isActive) {
setActive()
setActiveSource(source)
}
}}>
{active ? (
{isActive ? (
<VideoEmbedInnerNative />
) : (
<Button
style={[a.flex_1, t.atoms.bg_contrast_25]}
onPress={setActive}
onPress={() => {
setActiveSource(source)
}}
label={_(msg`Play video`)}
variant="ghost"
color="secondary"

View File

@ -8,14 +8,14 @@ import {
} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
import {atoms as a, useTheme} from '#/alf'
import {ErrorBoundary} from '../ErrorBoundary'
import {useActiveVideoView} from './ActiveVideoContext'
import {useActiveVideoWeb} from './ActiveVideoWebContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
export function VideoEmbed({source}: {source: string}) {
const t = useTheme()
const ref = useRef<HTMLDivElement>(null)
const {active, setActive, sendPosition, currentActiveView} =
useActiveVideoView({source})
useActiveVideoWeb()
const [onScreen, setOnScreen] = useState(false)
useEffect(() => {

View File

@ -9,7 +9,7 @@ import {useIsFocused} from '@react-navigation/native'
import {HITSLOP_30} from '#/lib/constants'
import {useAppState} from '#/lib/hooks/useAppState'
import {logger} from '#/logger'
import {useVideoPlayer} from '#/view/com/util/post-embeds/VideoPlayerContext'
import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext'
import {atoms as a, useTheme} from '#/alf'
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
@ -20,7 +20,7 @@ import {
import {TimeIndicator} from './TimeIndicator'
export function VideoEmbedInnerNative() {
const player = useVideoPlayer()
const {player} = useActiveVideoNative()
const ref = useRef<VideoView>(null)
const isScreenFocused = useIsFocused()
const isAppFocused = useAppState()

View File

@ -1,47 +0,0 @@
import React, {useContext} from 'react'
import type {VideoPlayer} from 'expo-video'
import {useVideoPlayer as useExpoVideoPlayer} from 'expo-video'
import {logger} from '#/logger'
import {
AudioCategory,
PlatformInfo,
} from '../../../../../modules/expo-bluesky-swiss-army'
const VideoPlayerContext = React.createContext<VideoPlayer | null>(null)
export function VideoPlayerProvider({
source,
children,
}: {
source: string
children: React.ReactNode
}) {
// eslint-disable-next-line @typescript-eslint/no-shadow
const player = useExpoVideoPlayer(source, player => {
try {
PlatformInfo.setAudioCategory(AudioCategory.Ambient)
PlatformInfo.setAudioActive(false)
player.loop = true
player.muted = true
player.play()
} catch (err) {
logger.error('Failed to init video player', {safeMessage: err})
}
})
return (
<VideoPlayerContext.Provider value={player}>
{children}
</VideoPlayerContext.Provider>
)
}
export function useVideoPlayer() {
const context = useContext(VideoPlayerContext)
if (!context) {
throw new Error('useVideoPlayer must be used within a VideoPlayerProvider')
}
return context
}

View File

@ -1,9 +0,0 @@
import React from 'react'
export function VideoPlayerProvider({children}: {children: React.ReactNode}) {
return children
}
export function useVideoPlayer() {
throw new Error('useVideoPlayer must not be used on web')
}