tooot/src/components/Timeline/Shared/Attachment/Video.tsx

154 lines
4.6 KiB
TypeScript

import Button from '@components/Button'
import { StyleConstants } from '@utils/styles/constants'
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { AppState, AppStateStatus, Pressable, View } from 'react-native'
import { Blurhash } from 'react-native-blurhash'
import attachmentAspectRatio from './aspectRatio'
import AttachmentAltText from './AltText'
import { Platform } from 'expo-modules-core'
export interface Props {
total: number
index: number
sensitiveShown: boolean
video: Mastodon.AttachmentVideo | Mastodon.AttachmentGifv
gifv?: boolean
}
const AttachmentVideo: React.FC<Props> = ({
total,
index,
sensitiveShown,
video,
gifv = false
}) => {
const videoPlayer = useRef<Video>(null)
const [videoLoading, setVideoLoading] = useState(false)
const [videoLoaded, setVideoLoaded] = useState(false)
const [videoPosition, setVideoPosition] = useState<number>(0)
const [videoResizeMode, setVideoResizeMode] = useState<ResizeMode>(ResizeMode.COVER)
const playOnPress = useCallback(async () => {
setVideoLoading(true)
if (!videoLoaded) {
await videoPlayer.current?.loadAsync({ uri: video.url })
}
Platform.OS === 'android' && setVideoResizeMode(ResizeMode.CONTAIN)
await videoPlayer.current?.setPositionAsync(videoPosition)
await videoPlayer.current?.presentFullscreenPlayer()
videoPlayer.current?.playAsync()
setVideoLoading(false)
videoPlayer.current?.setOnPlaybackStatusUpdate(props => {
if (props.isLoaded) {
setVideoLoaded(true)
if (props.positionMillis) {
setVideoPosition(props.positionMillis)
}
}
})
}, [videoLoaded, videoPosition])
const appState = useRef(AppState.currentState)
useEffect(() => {
const appState = AppState.addEventListener('change', _handleAppStateChange)
return () => appState.remove()
}, [])
const _handleAppStateChange = async (nextAppState: AppStateStatus) => {
if (appState.current.match(/active/) && nextAppState.match(/inactive/)) {
// await videoPlayer.current?.stopAsync()
} else if (gifv && appState.current.match(/background/) && nextAppState.match(/active/)) {
await videoPlayer.current?.setIsMutedAsync(true)
await videoPlayer.current?.playAsync()
}
appState.current = nextAppState
}
const playerStatus = useRef<any>(null)
useEffect(() => {
videoPlayer.current?.setOnPlaybackStatusUpdate(playbackStatus => {
playerStatus.current = playbackStatus
})
}, [])
return (
<View
style={{
flex: 1,
flexBasis: '50%',
padding: StyleConstants.Spacing.XS / 2,
aspectRatio: attachmentAspectRatio({ total, index })
}}
>
<Video
accessibilityLabel={video.description}
ref={videoPlayer}
style={{
width: '100%',
height: '100%',
opacity: sensitiveShown ? 0 : 1
}}
usePoster
resizeMode={videoResizeMode}
{...(gifv
? {
shouldPlay: true,
isMuted: true,
isLooping: true,
source: { uri: video.url }
}
: {
posterSource: { uri: video.preview_url },
posterStyle: { resizeMode: ResizeMode.COVER }
})}
useNativeControls={false}
onFullscreenUpdate={async event => {
if (event.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) {
Platform.OS === 'android' && setVideoResizeMode(ResizeMode.COVER)
if (!gifv) {
await videoPlayer.current?.pauseAsync()
}
}
}}
/>
<Pressable
style={{
position: 'absolute',
width: '100%',
height: '100%',
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}}
onPress={gifv ? playOnPress : null}
>
{sensitiveShown ? (
video.blurhash ? (
<Blurhash
blurhash={video.blurhash}
style={{
width: '100%',
height: '100%'
}}
/>
) : null
) : !gifv || (gifv && playerStatus.current === false) ? (
<Button
round
overlay
size='L'
type='icon'
content='PlayCircle'
onPress={playOnPress}
loading={videoLoading}
/>
) : null}
<AttachmentAltText sensitiveShown={sensitiveShown} text={video.description} />
</Pressable>
</View>
)
}
export default AttachmentVideo