Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

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