diff options
Diffstat (limited to 'src/view/com/util/post-embeds')
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbed.tsx | 47 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbed.web.tsx | 8 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner.tsx | 143 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx | 96 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx | 3 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx (renamed from src/view/com/util/post-embeds/VideoEmbedInner.web.tsx) | 14 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx (renamed from src/view/com/util/post-embeds/VideoWebControls.tsx) | 0 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx (renamed from src/view/com/util/post-embeds/VideoWebControls.web.tsx) | 4 |
9 files changed, 143 insertions, 175 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index 429312d9e..887efac1a 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -1,21 +1,20 @@ -import React, {useCallback} from 'react' +import React from 'react' import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {VideoEmbedInnerNative} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' +import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army' import {useActiveVideoView} from './ActiveVideoContext' -import {VideoEmbedInner} from './VideoEmbedInner' export function VideoEmbed({source}: {source: string}) { const t = useTheme() const {active, setActive} = useActiveVideoView({source}) const {_} = useLingui() - const onPress = useCallback(() => setActive(), [setActive]) - return ( <View style={[ @@ -26,25 +25,27 @@ export function VideoEmbed({source}: {source: string}) { t.atoms.bg_contrast_25, a.my_xs, ]}> - {active ? ( - <VideoEmbedInner - source={source} - // web only - active={active} - setActive={setActive} - onScreen={true} - /> - ) : ( - <Button - style={[a.flex_1, t.atoms.bg_contrast_25]} - onPress={onPress} - label={_(msg`Play video`)} - variant="ghost" - color="secondary" - size="large"> - <ButtonIcon icon={PlayIcon} /> - </Button> - )} + <VisibilityView + enabled={true} + onChangeStatus={isActive => { + if (isActive) { + setActive() + } + }}> + {active ? ( + <VideoEmbedInnerNative /> + ) : ( + <Button + style={[a.flex_1, t.atoms.bg_contrast_25]} + onPress={setActive} + label={_(msg`Play video`)} + variant="ghost" + color="secondary" + size="large"> + <ButtonIcon icon={PlayIcon} /> + </Button> + )} + </VisibilityView> </View> ) } diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx index 08932f91f..70d887283 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx @@ -3,13 +3,15 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import { + HLSUnsupportedError, + VideoEmbedInnerWeb, +} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {Text} from '#/components/Typography' import {ErrorBoundary} from '../ErrorBoundary' import {useActiveVideoView} from './ActiveVideoContext' -import {VideoEmbedInner} from './VideoEmbedInner' -import {HLSUnsupportedError} from './VideoEmbedInner.web' export function VideoEmbed({source}: {source: string}) { const t = useTheme() @@ -60,7 +62,7 @@ export function VideoEmbed({source}: {source: string}) { <ViewportObserver sendPosition={sendPosition} isAnyViewActive={currentActiveView !== null}> - <VideoEmbedInner + <VideoEmbedInnerWeb source={source} active={active} setActive={setActive} diff --git a/src/view/com/util/post-embeds/VideoEmbedInner.tsx b/src/view/com/util/post-embeds/VideoEmbedInner.tsx deleted file mode 100644 index 9b1fd54fb..000000000 --- a/src/view/com/util/post-embeds/VideoEmbedInner.tsx +++ /dev/null @@ -1,143 +0,0 @@ -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 function VideoEmbedInner({}: { - source: string - active: boolean - setActive: () => void - onScreen: boolean -}) { - 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', - }, -}) diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx new file mode 100644 index 000000000..cc356fb06 --- /dev/null +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -0,0 +1,96 @@ +import React, {useEffect, useRef, useState} from 'react' +import {Pressable, View} from 'react-native' +import {VideoPlayer, VideoView} from 'expo-video' + +import {useVideoPlayer} from 'view/com/util/post-embeds/VideoPlayerContext' +import {android, atoms as a} from '#/alf' +import {Text} from '#/components/Typography' + +export function VideoEmbedInnerNative() { + const player = useVideoPlayer() + const ref = useRef<VideoView>(null) + + return ( + <View style={[a.flex_1, a.relative]} collapsable={false}> + <VideoView + ref={ref} + player={player} + style={a.flex_1} + nativeControls={true} + /> + <Controls + player={player} + enterFullscreen={() => ref.current?.enterFullscreen()} + /> + </View> + ) +} + +function Controls({ + player, + enterFullscreen, +}: { + player: VideoPlayer + enterFullscreen: () => void +}) { + const [duration, setDuration] = useState(() => Math.floor(player.duration)) + const [currentTime, setCurrentTime] = useState(() => + Math.floor(player.currentTime), + ) + + const timeRemaining = duration - currentTime + const minutes = Math.floor(timeRemaining / 60) + const seconds = String(timeRemaining % 60).padStart(2, '0') + + useEffect(() => { + const interval = setInterval(() => { + // duration gets reset to 0 on loop + if (player.duration) setDuration(Math.floor(player.duration)) + setCurrentTime(Math.floor(player.currentTime)) + // how often should we update the time? + // 1000 gets out of sync with the video time + }, 250) + + return () => { + clearInterval(interval) + } + }, [player]) + + if (isNaN(timeRemaining)) { + return null + } + + return ( + <View style={[a.absolute, a.inset_0]}> + <View + style={[ + { + backgroundColor: 'rgba(0, 0, 0, 0.75', + borderRadius: 6, + paddingHorizontal: 6, + paddingVertical: 3, + position: 'absolute', + left: 5, + bottom: 5, + }, + ]} + pointerEvents="none"> + <Text + style={[ + {color: 'white', fontSize: 12}, + a.font_bold, + android({lineHeight: 1.25}), + ]}> + {minutes}:{seconds} + </Text> + </View> + <Pressable + onPress={enterFullscreen} + style={a.flex_1} + accessibilityLabel="Video" + accessibilityHint="Tap to enter full screen" + accessibilityRole="button" + /> + </View> + ) +} diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx new file mode 100644 index 000000000..59da5be42 --- /dev/null +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.web.tsx @@ -0,0 +1,3 @@ +export function VideoEmbedInnerNative() { + throw new Error('VideoEmbedInnerNative may not be used on native.') +} diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx new file mode 100644 index 000000000..8664aae14 --- /dev/null +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.native.tsx @@ -0,0 +1,3 @@ +export function VideoEmbedInnerWeb() { + throw new Error('VideoEmbedInnerWeb may not be used on native.') +} diff --git a/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx index f5f47db50..c0021d9bb 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx @@ -5,17 +5,23 @@ import Hls from 'hls.js' import {atoms as a} from '#/alf' import {Controls} from './VideoWebControls' -export function VideoEmbedInner({ +export function VideoEmbedInnerWeb({ source, active, setActive, onScreen, }: { source: string - active: boolean - setActive: () => void - onScreen: boolean + active?: boolean + setActive?: () => void + onScreen?: boolean }) { + if (active == null || setActive == null || onScreen == null) { + throw new Error( + 'active, setActive, and onScreen are required VideoEmbedInner props on web.', + ) + } + const containerRef = useRef<HTMLDivElement>(null) const ref = useRef<HTMLVideoElement>(null) const [focused, setFocused] = useState(false) diff --git a/src/view/com/util/post-embeds/VideoWebControls.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx index 11e0867e4..11e0867e4 100644 --- a/src/view/com/util/post-embeds/VideoWebControls.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx diff --git a/src/view/com/util/post-embeds/VideoWebControls.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx index 2843664be..7caaf3abf 100644 --- a/src/view/com/util/post-embeds/VideoWebControls.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.web.tsx @@ -11,12 +11,12 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import type Hls from 'hls.js' -import {isIPhoneWeb} from '#/platform/detection' +import {isIPhoneWeb} from 'platform/detection' import { useAutoplayDisabled, useSetSubtitlesEnabled, useSubtitlesEnabled, -} from '#/state/preferences' +} from 'state/preferences' import {atoms as a, useTheme, web} from '#/alf' import {Button} from '#/components/Button' import {useInteractionState} from '#/components/hooks/useInteractionState' |