[Video] 🫧 Move logic around by platform (#5003)
parent
9aa2b2d14e
commit
5ae0d40a14
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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')
|
||||
}
|
Loading…
Reference in New Issue