Something went wrong. Try again.
Bluesky app fork with some witchin' additions 💫 forked from jollywhoppers.com/witchsky.app
Something went wrong. Try again.
6.7 kB · 227 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228import {useImperativeHandle, useRef, useState} from 'react'import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'import {type AppBskyEmbedVideo} from '@atproto/api'import {BlueskyVideoView} from '@bsky.app/video'import {msg} from '@lingui/core/macro'import {useLingui} from '@lingui/react'
import {HITSLOP_30} from '#/lib/constants'import {useAutoplayDisabled} from '#/state/preferences'import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'import {atoms as a, useTheme} from '#/alf'import {useIsWithinMessage} from '#/components/dms/MessageContext'import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause'import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'import {KeepAwake} from '#/components/KeepAwake'import {MediaInsetBorder} from '#/components/MediaInsetBorder'import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'import {GifPresentationControls} from '../GifPresentationControls'import {TimeIndicator} from './TimeIndicator'
export function VideoEmbedInnerNative({ ref, embed, setStatus, setIsLoading, setIsActive,}: { ref: React.Ref<{togglePlayback: () => void}> embed: AppBskyEmbedVideo.View setStatus: (status: 'playing' | 'paused') => void setIsLoading: (isLoading: boolean) => void setIsActive: (isActive: boolean) => void}) { const {_} = useLingui() const videoRef = useRef<BlueskyVideoView>(null) const autoplayDisabled = useAutoplayDisabled() const isWithinMessage = useIsWithinMessage() const [muted, setMuted] = useVideoMuteState()
const [isPlaying, setIsPlaying] = useState(false) const [timeRemaining, setTimeRemaining] = useState(0) const [error, setError] = useState<string>()
useImperativeHandle(ref, () => ({ togglePlayback: () => { videoRef.current?.togglePlayback() }, }))
if (error) { throw new Error(error) }
const isGif = embed.presentation === 'gif'
return ( <View style={[a.flex_1, a.relative]}> <BlueskyVideoView url={embed.playlist} autoplay={!autoplayDisabled && !isWithinMessage} beginMuted={isGif || (autoplayDisabled ? false : muted)} style={[a.rounded_sm]} onActiveChange={e => { setIsActive(e.nativeEvent.isActive) }} onLoadingChange={e => { setIsLoading(e.nativeEvent.isLoading) }} onMutedChange={e => { if (!isGif) { setMuted(e.nativeEvent.isMuted) } }} onStatusChange={e => { setStatus(e.nativeEvent.status) setIsPlaying(e.nativeEvent.status === 'playing') }} onTimeRemainingChange={e => { setTimeRemaining(e.nativeEvent.timeRemaining) }} onError={e => { setError(e.nativeEvent.error) }} ref={videoRef} accessibilityLabel={ embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`) } accessibilityHint="" /> {isGif ? ( <GifPresentationControls onPress={() => { videoRef.current?.togglePlayback() }} isPlaying={isPlaying} isLoading={false} altText={embed.alt} /> ) : ( <VideoPresentationControls enterFullscreen={() => { videoRef.current?.enterFullscreen(true) }} toggleMuted={() => { videoRef.current?.toggleMuted() }} togglePlayback={() => { videoRef.current?.togglePlayback() }} isPlaying={isPlaying} timeRemaining={timeRemaining} /> )} <MediaInsetBorder /> <KeepAwake enabled={isPlaying} /> </View> )}
function VideoPresentationControls({ enterFullscreen, toggleMuted, togglePlayback, timeRemaining, isPlaying,}: { enterFullscreen: () => void toggleMuted: () => void togglePlayback: () => void timeRemaining: number isPlaying: boolean}) { const {_} = useLingui() const t = useTheme() const [muted] = useVideoMuteState()
// show countdown when: // 1. timeRemaining is a number - was seeing NaNs // 2. duration is greater than 0 - means metadata has loaded // 3. we're less than 5 second into the video const showTime = !isNaN(timeRemaining)
return ( <View style={[a.absolute, a.inset_0]}> <Pressable onPress={enterFullscreen} style={a.flex_1} accessibilityLabel={_(msg`Video`)} accessibilityHint={_(msg`Enters full screen`)} accessibilityRole="button" /> <ControlButton onPress={togglePlayback} label={isPlaying ? _(msg`Pause`) : _(msg`Play`)} accessibilityHint={_(msg`Plays or pauses the video`)} style={{left: 6}}> {isPlaying ? ( <PauseIcon width={13} fill={t.palette.white} /> ) : ( <PlayIcon width={13} fill={t.palette.white} /> )} </ControlButton> {showTime && <TimeIndicator time={timeRemaining} style={{left: 33}} />}
<ControlButton onPress={toggleMuted} label={ muted ? _(msg({message: `Unmute`, context: 'video'})) : _(msg({message: `Mute`, context: 'video'})) } accessibilityHint={_(msg`Toggles the sound`)} style={{right: 6}}> {muted ? ( <MuteIcon width={13} fill={t.palette.white} /> ) : ( <UnmuteIcon width={13} fill={t.palette.white} /> )} </ControlButton> </View> )}
function ControlButton({ onPress, children, label, accessibilityHint, style,}: { onPress: () => void children: React.ReactNode label: string accessibilityHint: string style?: StyleProp<ViewStyle>}) { const enableSquareButtons = useEnableSquareButtons() return ( <View style={[ a.absolute, enableSquareButtons ? a.rounded_sm : a.rounded_full, a.justify_center, { backgroundColor: 'rgba(0, 0, 0, 0.5)', paddingHorizontal: 4, paddingVertical: 4, bottom: 6, minHeight: 21, minWidth: 21, }, style, ]}> <Pressable onPress={onPress} style={a.flex_1} accessibilityLabel={label} accessibilityHint={accessibilityHint} accessibilityRole="button" hitSlop={HITSLOP_30}> {children} </Pressable> </View> )}