From ab9fe32688331266c9406c1c14c0cd16830de8b1 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 7 Jul 2025 11:58:48 +0300 Subject: [PATCH] changes to handle staged videos --- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 81 ++++++++++++------- .../Post/Embed/VideoEmbed/index.tsx | 61 +++++++++----- src/view/com/composer/videos/VideoPreview.tsx | 4 +- 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx index 351e9f3056..fba3c1ac2b 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -13,7 +13,6 @@ import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause' import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' -import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' import {TimeIndicator} from './TimeIndicator' @@ -23,12 +22,14 @@ export const VideoEmbedInnerNative = React.forwardRef( embed, setStatus, setIsLoading, - setIsActive, + activeState, + setActiveState, }: { embed: AppBskyEmbedVideo.View setStatus: (status: 'playing' | 'paused') => void setIsLoading: (isLoading: boolean) => void - setIsActive: (isActive: boolean) => void + activeState: 'active' | 'inactive' | 'staged' + setActiveState: (activeState: 'active' | 'inactive' | 'staged') => void }, ref: React.Ref<{togglePlayback: () => void}>, ) { @@ -60,7 +61,13 @@ export const VideoEmbedInnerNative = React.forwardRef( beginMuted={autoplayDisabled ? false : muted} style={[a.rounded_sm]} onActiveChange={e => { - setIsActive(e.nativeEvent.isActive) + setActiveState( + ( + e.nativeEvent as unknown as { + state: 'active' | 'inactive' | 'staged' + } + ).state, + ) }} onLoadingChange={e => { setIsLoading(e.nativeEvent.isLoading) @@ -85,8 +92,13 @@ export const VideoEmbedInnerNative = React.forwardRef( accessibilityHint="" /> { - videoRef.current?.enterFullscreen(true) + activeState={activeState} + onPress={() => { + if (activeState === 'active') { + videoRef.current?.enterFullscreen(true) + } else { + videoRef.current?.togglePlayback() + } }} toggleMuted={() => { videoRef.current?.toggleMuted() @@ -97,20 +109,21 @@ export const VideoEmbedInnerNative = React.forwardRef( isPlaying={isPlaying} timeRemaining={timeRemaining} /> - ) }, ) function VideoControls({ - enterFullscreen, + activeState, + onPress, toggleMuted, togglePlayback, timeRemaining, isPlaying, }: { - enterFullscreen: () => void + activeState: 'active' | 'inactive' | 'staged' + onPress: () => void toggleMuted: () => void togglePlayback: () => void timeRemaining: number @@ -124,18 +137,25 @@ function VideoControls({ // 1. timeRemaining is a number - was seeing NaNs // 2. duration is greater than 0 - means metadata has loaded // 3. we're less than 5 second into the video - const showTime = !isNaN(timeRemaining) + const showTime = + !isNaN(timeRemaining) && + (activeState === 'active' ? true : timeRemaining > 0) return ( {showTime && } - - {muted ? ( - - ) : ( - - )} - + {activeState === 'active' && ( + + {muted ? ( + + ) : ( + + )} + + )} ) } @@ -173,12 +195,14 @@ function ControlButton({ label, accessibilityHint, style, + disabled, }: { onPress: () => void children: React.ReactNode label: string accessibilityHint: string style?: StyleProp + disabled?: boolean }) { return ( + hitSlop={HITSLOP_30} + disabled={disabled}> {children} diff --git a/src/components/Post/Embed/VideoEmbed/index.tsx b/src/components/Post/Embed/VideoEmbed/index.tsx index 8cb78ff70b..cce0e3a6f3 100644 --- a/src/components/Post/Embed/VideoEmbed/index.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.tsx @@ -1,15 +1,17 @@ -import React, {useCallback, useState} from 'react' +import {useCallback, useEffect, useRef, useState} from 'react' import {ActivityIndicator, View} from 'react-native' import {ImageBackground} from 'expo-image' import {type AppBskyEmbedVideo} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {isAndroid} from '#/platform/detection' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, platform, useTheme} from '#/alf' import {Button} from '#/components/Button' import {useThrottledValue} from '#/components/hooks/useThrottledValue' +import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {VideoEmbedInnerNative} from './VideoEmbedInner/VideoEmbedInnerNative' import * as VideoFallback from './VideoEmbedInner/VideoFallback' @@ -81,26 +83,31 @@ export function VideoEmbed({embed, crop}: Props) { function InnerWrapper({embed}: Props) { const {_} = useLingui() - const ref = React.useRef<{togglePlayback: () => void}>(null) + const ref = useRef<{togglePlayback: () => void}>(null) - const [status, setStatus] = React.useState<'playing' | 'paused' | 'pending'>( + const [status, setStatus] = useState<'playing' | 'paused' | 'pending'>( 'pending', ) - const [isLoading, setIsLoading] = React.useState(false) - const [isActive, setIsActive] = React.useState(false) - const showSpinner = useThrottledValue(isActive && isLoading, 100) + const [isLoading, setIsLoading] = useState(false) + const [activeState, setActiveState] = useState< + 'active' | 'inactive' | 'staged' + >('inactive') + const showSpinner = useThrottledValue( + activeState === 'active' && isLoading, + 100, + ) const showOverlay = - !isActive || - isLoading || - (status === 'paused' && !isActive) || - status === 'pending' + activeState === 'inactive' || + (activeState === 'active' && (isLoading || status === 'pending')) - React.useEffect(() => { - if (!isActive && status !== 'pending') { - setStatus('pending') + useEffect(() => { + if (isAndroid) { + if (activeState === 'inactive' && status !== 'pending') { + setStatus('pending') + } } - }, [isActive, status]) + }, [activeState, status]) return ( <> @@ -108,7 +115,8 @@ function InnerWrapper({embed}: Props) { embed={embed} setStatus={setStatus} setIsLoading={setIsLoading} - setIsActive={setIsActive} + activeState={activeState} + setActiveState={setActiveState} ref={ref} /> {showOverlay && ( )} + ) } diff --git a/src/view/com/composer/videos/VideoPreview.tsx b/src/view/com/composer/videos/VideoPreview.tsx index 255174beab..0cfa31237e 100644 --- a/src/view/com/composer/videos/VideoPreview.tsx +++ b/src/view/com/composer/videos/VideoPreview.tsx @@ -1,9 +1,9 @@ import React from 'react' import {View} from 'react-native' -import {ImagePickerAsset} from 'expo-image-picker' +import {type ImagePickerAsset} from 'expo-image-picker' import {BlueskyVideoView} from '@haileyok/bluesky-video' -import {CompressedVideo} from '#/lib/media/video/types' +import {type CompressedVideo} from '#/lib/media/video/types' import {clamp} from '#/lib/numbers' import {useAutoplayDisabled} from '#/state/preferences' import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'