diff options
Diffstat (limited to 'src/view/com/util/post-embeds/VideoEmbedInner.tsx')
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner.tsx | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner.tsx b/src/view/com/util/post-embeds/VideoEmbedInner.tsx new file mode 100644 index 000000000..ef0678709 --- /dev/null +++ b/src/view/com/util/post-embeds/VideoEmbedInner.tsx @@ -0,0 +1,138 @@ +import React, {useCallback, useEffect, useRef, useState} from 'react' +import {Pressable, StyleSheet, useWindowDimensions, View} from 'react-native' +import Animated, { + measure, + runOnJS, + useAnimatedRef, + useFrameCallback, + useSharedValue, +} from 'react-native-reanimated' +import {VideoPlayer, VideoView} from 'expo-video' + +import {atoms as a} from '#/alf' +import {Text} from '#/components/Typography' +import {useVideoPlayer} from './VideoPlayerContext' + +export const VideoEmbedInner = ({}: {source: string}) => { + const player = useVideoPlayer() + const aref = useAnimatedRef<Animated.View>() + const {height: windowHeight} = useWindowDimensions() + const hasLeftView = useSharedValue(false) + const ref = useRef<VideoView>(null) + + const onEnterView = useCallback(() => { + if (player.status === 'readyToPlay') { + player.play() + } + }, [player]) + + const onLeaveView = useCallback(() => { + player.pause() + }, [player]) + + const enterFullscreen = useCallback(() => { + if (ref.current) { + ref.current.enterFullscreen() + } + }, []) + + useFrameCallback(() => { + const measurement = measure(aref) + + if (measurement) { + if (hasLeftView.value) { + // Check if the video is in view + if ( + measurement.pageY >= 0 && + measurement.pageY + measurement.height <= windowHeight + ) { + runOnJS(onEnterView)() + hasLeftView.value = false + } + } else { + // Check if the video is out of view + if ( + measurement.pageY + measurement.height < 0 || + measurement.pageY > windowHeight + ) { + runOnJS(onLeaveView)() + hasLeftView.value = true + } + } + } + }) + + return ( + <Animated.View + style={[a.flex_1, a.relative]} + ref={aref} + collapsable={false}> + <VideoView + ref={ref} + player={player} + style={a.flex_1} + nativeControls={true} + /> + <VideoControls player={player} enterFullscreen={enterFullscreen} /> + </Animated.View> + ) +} + +function VideoControls({ + player, + enterFullscreen, +}: { + player: VideoPlayer + enterFullscreen: () => void +}) { + const [currentTime, setCurrentTime] = useState(Math.floor(player.currentTime)) + + useEffect(() => { + const interval = setInterval(() => { + setCurrentTime(Math.floor(player.duration - player.currentTime)) + // how often should we update the time? + // 1000 gets out of sync with the video time + }, 250) + + return () => { + clearInterval(interval) + } + }, [player]) + + const minutes = Math.floor(currentTime / 60) + const seconds = String(currentTime % 60).padStart(2, '0') + + return ( + <View style={[a.absolute, a.inset_0]}> + <View style={styles.timeContainer} pointerEvents="none"> + <Text style={styles.timeElapsed}> + {minutes}:{seconds} + </Text> + </View> + <Pressable + onPress={enterFullscreen} + style={a.flex_1} + accessibilityLabel="Video" + accessibilityHint="Tap to enter full screen" + accessibilityRole="button" + /> + </View> + ) +} + +const styles = StyleSheet.create({ + timeContainer: { + backgroundColor: 'rgba(0, 0, 0, 0.75)', + borderRadius: 6, + paddingHorizontal: 6, + paddingVertical: 3, + position: 'absolute', + left: 5, + bottom: 5, + }, + timeElapsed: { + color: 'white', + fontSize: 12, + fontWeight: 'bold', + }, +}) |