this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at e28f6d2f370b4e882ed6f23d08ca0f8d94dbac5f 223 lines 6.4 kB view raw
1import {useImperativeHandle, useRef, useState} from 'react' 2import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' 3import {type AppBskyEmbedVideo} from '@atproto/api' 4import {BlueskyVideoView} from '@haileyok/bluesky-video' 5import {msg} from '@lingui/core/macro' 6import {useLingui} from '@lingui/react' 7 8import {HITSLOP_30} from '#/lib/constants' 9import {useAutoplayDisabled} from '#/state/preferences' 10import {atoms as a, useTheme} from '#/alf' 11import {useIsWithinMessage} from '#/components/dms/MessageContext' 12import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 13import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause' 14import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' 15import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 16import {MediaInsetBorder} from '#/components/MediaInsetBorder' 17import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 18import {GifPresentationControls} from '../GifPresentationControls' 19import {TimeIndicator} from './TimeIndicator' 20 21export function VideoEmbedInnerNative({ 22 ref, 23 embed, 24 setStatus, 25 setIsLoading, 26 setIsActive, 27}: { 28 ref: React.Ref<{togglePlayback: () => void}> 29 embed: AppBskyEmbedVideo.View 30 setStatus: (status: 'playing' | 'paused') => void 31 setIsLoading: (isLoading: boolean) => void 32 setIsActive: (isActive: boolean) => void 33}) { 34 const {_} = useLingui() 35 const videoRef = useRef<BlueskyVideoView>(null) 36 const autoplayDisabled = useAutoplayDisabled() 37 const isWithinMessage = useIsWithinMessage() 38 const [muted, setMuted] = useVideoMuteState() 39 40 const [isPlaying, setIsPlaying] = useState(false) 41 const [timeRemaining, setTimeRemaining] = useState(0) 42 const [error, setError] = useState<string>() 43 44 useImperativeHandle(ref, () => ({ 45 togglePlayback: () => { 46 videoRef.current?.togglePlayback() 47 }, 48 })) 49 50 if (error) { 51 throw new Error(error) 52 } 53 54 const isGif = embed.presentation === 'gif' 55 56 return ( 57 <View style={[a.flex_1, a.relative]}> 58 <BlueskyVideoView 59 url={embed.playlist} 60 autoplay={!autoplayDisabled && !isWithinMessage} 61 beginMuted={isGif || (autoplayDisabled ? false : muted)} 62 style={[a.rounded_sm]} 63 onActiveChange={e => { 64 setIsActive(e.nativeEvent.isActive) 65 }} 66 onLoadingChange={e => { 67 setIsLoading(e.nativeEvent.isLoading) 68 }} 69 onMutedChange={e => { 70 if (!isGif) { 71 setMuted(e.nativeEvent.isMuted) 72 } 73 }} 74 onStatusChange={e => { 75 setStatus(e.nativeEvent.status) 76 setIsPlaying(e.nativeEvent.status === 'playing') 77 }} 78 onTimeRemainingChange={e => { 79 setTimeRemaining(e.nativeEvent.timeRemaining) 80 }} 81 onError={e => { 82 setError(e.nativeEvent.error) 83 }} 84 ref={videoRef} 85 accessibilityLabel={ 86 embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`) 87 } 88 accessibilityHint="" 89 /> 90 {isGif ? ( 91 <GifPresentationControls 92 onPress={() => { 93 videoRef.current?.togglePlayback() 94 }} 95 isPlaying={isPlaying} 96 isLoading={false} 97 altText={embed.alt} 98 /> 99 ) : ( 100 <VideoPresentationControls 101 enterFullscreen={() => { 102 videoRef.current?.enterFullscreen(true) 103 }} 104 toggleMuted={() => { 105 videoRef.current?.toggleMuted() 106 }} 107 togglePlayback={() => { 108 videoRef.current?.togglePlayback() 109 }} 110 isPlaying={isPlaying} 111 timeRemaining={timeRemaining} 112 /> 113 )} 114 <MediaInsetBorder /> 115 </View> 116 ) 117} 118 119function VideoPresentationControls({ 120 enterFullscreen, 121 toggleMuted, 122 togglePlayback, 123 timeRemaining, 124 isPlaying, 125}: { 126 enterFullscreen: () => void 127 toggleMuted: () => void 128 togglePlayback: () => void 129 timeRemaining: number 130 isPlaying: boolean 131}) { 132 const {_} = useLingui() 133 const t = useTheme() 134 const [muted] = useVideoMuteState() 135 136 // show countdown when: 137 // 1. timeRemaining is a number - was seeing NaNs 138 // 2. duration is greater than 0 - means metadata has loaded 139 // 3. we're less than 5 second into the video 140 const showTime = !isNaN(timeRemaining) 141 142 return ( 143 <View style={[a.absolute, a.inset_0]}> 144 <Pressable 145 onPress={enterFullscreen} 146 style={a.flex_1} 147 accessibilityLabel={_(msg`Video`)} 148 accessibilityHint={_(msg`Enters full screen`)} 149 accessibilityRole="button" 150 /> 151 <ControlButton 152 onPress={togglePlayback} 153 label={isPlaying ? _(msg`Pause`) : _(msg`Play`)} 154 accessibilityHint={_(msg`Plays or pauses the video`)} 155 style={{left: 6}}> 156 {isPlaying ? ( 157 <PauseIcon width={13} fill={t.palette.white} /> 158 ) : ( 159 <PlayIcon width={13} fill={t.palette.white} /> 160 )} 161 </ControlButton> 162 {showTime && <TimeIndicator time={timeRemaining} style={{left: 33}} />} 163 164 <ControlButton 165 onPress={toggleMuted} 166 label={ 167 muted 168 ? _(msg({message: `Unmute`, context: 'video'})) 169 : _(msg({message: `Mute`, context: 'video'})) 170 } 171 accessibilityHint={_(msg`Toggles the sound`)} 172 style={{right: 6}}> 173 {muted ? ( 174 <MuteIcon width={13} fill={t.palette.white} /> 175 ) : ( 176 <UnmuteIcon width={13} fill={t.palette.white} /> 177 )} 178 </ControlButton> 179 </View> 180 ) 181} 182 183function ControlButton({ 184 onPress, 185 children, 186 label, 187 accessibilityHint, 188 style, 189}: { 190 onPress: () => void 191 children: React.ReactNode 192 label: string 193 accessibilityHint: string 194 style?: StyleProp<ViewStyle> 195}) { 196 return ( 197 <View 198 style={[ 199 a.absolute, 200 a.rounded_full, 201 a.justify_center, 202 { 203 backgroundColor: 'rgba(0, 0, 0, 0.5)', 204 paddingHorizontal: 4, 205 paddingVertical: 4, 206 bottom: 6, 207 minHeight: 21, 208 minWidth: 21, 209 }, 210 style, 211 ]}> 212 <Pressable 213 onPress={onPress} 214 style={a.flex_1} 215 accessibilityLabel={label} 216 accessibilityHint={accessibilityHint} 217 accessibilityRole="button" 218 hitSlop={HITSLOP_30}> 219 {children} 220 </Pressable> 221 </View> 222 ) 223}