diff options
author | Hailey <me@haileyok.com> | 2024-09-13 12:44:42 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-13 12:44:42 -0700 |
commit | 26508cfe6a89df4ae1ab1256753faa860597bbc8 (patch) | |
tree | 8f0bf4e8f65863ddbe8d1ede7df3fd342e6ab69b /src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx | |
parent | 78a531f5ffe9287b5384ec1649dfbc45435ced28 (diff) | |
download | voidsky-26508cfe6a89df4ae1ab1256753faa860597bbc8.tar.zst |
[Video] Remove `expo-video`, use `bluesky-video` (#5282)
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx')
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx | 305 |
1 files changed, 170 insertions, 135 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 8ed7658a6..39ed990ab 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -1,137 +1,136 @@ -import React, {useCallback, useRef} from 'react' -import {Pressable, View} from 'react-native' +import React, {useRef} from 'react' +import {Pressable, StyleProp, View, ViewStyle} from 'react-native' import Animated, {FadeInDown} from 'react-native-reanimated' -import {VideoPlayer, VideoView} from 'expo-video' import {AppBskyEmbedVideo} from '@atproto/api' +import {BlueskyVideoView} from '@haileyok/bluesky-video' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_30} from '#/lib/constants' import {clamp} from '#/lib/numbers' -import {isAndroid} from 'platform/detection' -import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' +import {useAutoplayDisabled} from '#/state/preferences' import {atoms as a, useTheme} from '#/alf' +import {useIsWithinMessage} from '#/components/dms/MessageContext' 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 { - AudioCategory, - PlatformInfo, -} from '../../../../../../modules/expo-bluesky-swiss-army' import {TimeIndicator} from './TimeIndicator' -export function VideoEmbedInnerNative({ - embed, - isFullscreen, - setIsFullscreen, - isMuted, - timeRemaining, -}: { - embed: AppBskyEmbedVideo.View - isFullscreen: boolean - setIsFullscreen: (isFullscreen: boolean) => void - timeRemaining: number - isMuted: boolean -}) { - const {_} = useLingui() - const {player} = useActiveVideoNative() - const ref = useRef<VideoView>(null) +export const VideoEmbedInnerNative = React.forwardRef( + function VideoEmbedInnerNative( + { + embed, + setStatus, + setIsLoading, + setIsActive, + }: { + embed: AppBskyEmbedVideo.View + setStatus: (status: 'playing' | 'paused') => void + setIsLoading: (isLoading: boolean) => void + setIsActive: (isActive: boolean) => void + }, + ref: React.Ref<{togglePlayback: () => void}>, + ) { + const {_} = useLingui() + const videoRef = useRef<BlueskyVideoView>(null) + const autoplayDisabled = useAutoplayDisabled() + const isWithinMessage = useIsWithinMessage() + + const [isMuted, setIsMuted] = React.useState(true) + const [isPlaying, setIsPlaying] = React.useState(false) + const [timeRemaining, setTimeRemaining] = React.useState(0) + const [error, setError] = React.useState<string>() - const enterFullscreen = useCallback(() => { - ref.current?.enterFullscreen() - }, []) + React.useImperativeHandle(ref, () => ({ + togglePlayback: () => { + videoRef.current?.togglePlayback() + }, + })) - let aspectRatio = 16 / 9 + if (error) { + throw new Error(error) + } - if (embed.aspectRatio) { - const {width, height} = embed.aspectRatio - aspectRatio = width / height - aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) - } + let aspectRatio = 16 / 9 - return ( - <View style={[a.flex_1, a.relative, {aspectRatio}]}> - <VideoView - ref={ref} - player={player} - style={[a.flex_1, a.rounded_sm]} - contentFit="cover" - nativeControls={isFullscreen} - accessibilityIgnoresInvertColors - onFullscreenEnter={() => { - PlatformInfo.setAudioCategory(AudioCategory.Playback) - PlatformInfo.setAudioActive(true) - player.muted = false - setIsFullscreen(true) - if (isAndroid) { - player.play() + if (embed.aspectRatio) { + const {width, height} = embed.aspectRatio + aspectRatio = width / height + aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) + } + + return ( + <View style={[a.flex_1, a.relative, {aspectRatio}]}> + <BlueskyVideoView + url={embed.playlist} + autoplay={!autoplayDisabled && !isWithinMessage} + beginMuted={true} + style={[a.rounded_sm]} + onActiveChange={e => { + setIsActive(e.nativeEvent.isActive) + }} + onLoadingChange={e => { + setIsLoading(e.nativeEvent.isLoading) + }} + onMutedChange={e => { + setIsMuted(e.nativeEvent.isMuted) + }} + onStatusChange={e => { + setStatus(e.nativeEvent.status) + setIsPlaying(e.nativeEvent.status === 'playing') + }} + onTimeRemainingChange={e => { + setTimeRemaining(e.nativeEvent.timeRemaining) + }} + onError={e => { + setError(e.nativeEvent.error) + }} + ref={videoRef} + accessibilityLabel={ + embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`) } - }} - onFullscreenExit={() => { - PlatformInfo.setAudioCategory(AudioCategory.Ambient) - PlatformInfo.setAudioActive(false) - player.muted = true - player.playbackRate = 1 - setIsFullscreen(false) - }} - accessibilityLabel={ - embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`) - } - accessibilityHint="" - /> - <VideoControls - player={player} - enterFullscreen={enterFullscreen} - isMuted={isMuted} - timeRemaining={timeRemaining} - /> - <MediaInsetBorder /> - </View> - ) -} + accessibilityHint="" + /> + <VideoControls + enterFullscreen={() => { + videoRef.current?.enterFullscreen() + }} + toggleMuted={() => { + videoRef.current?.toggleMuted() + }} + togglePlayback={() => { + videoRef.current?.togglePlayback() + }} + isMuted={isMuted} + isPlaying={isPlaying} + timeRemaining={timeRemaining} + /> + <MediaInsetBorder /> + </View> + ) + }, +) function VideoControls({ - player, enterFullscreen, + toggleMuted, + togglePlayback, timeRemaining, + isPlaying, isMuted, }: { - player: VideoPlayer enterFullscreen: () => void + toggleMuted: () => void + togglePlayback: () => void timeRemaining: number + isPlaying: boolean isMuted: boolean }) { const {_} = useLingui() const t = useTheme() - const onPressFullscreen = useCallback(() => { - switch (player.status) { - case 'idle': - case 'loading': - case 'readyToPlay': { - if (!player.playing) player.play() - enterFullscreen() - break - } - case 'error': { - player.replay() - break - } - } - }, [player, enterFullscreen]) - - const toggleMuted = useCallback(() => { - const muted = !player.muted - // We want to set this to the _inverse_ of the new value, because we actually want for the audio to be mixed when - // the video is muted, and vice versa. - const mix = !muted - const category = muted ? AudioCategory.Ambient : AudioCategory.Playback - - PlatformInfo.setAudioCategory(category) - PlatformInfo.setAudioActive(mix) - player.muted = muted - }, [player]) - // show countdown when: // 1. timeRemaining is a number - was seeing NaNs // 2. duration is greater than 0 - means metadata has loaded @@ -140,44 +139,80 @@ function VideoControls({ return ( <View style={[a.absolute, a.inset_0]}> - {showTime && <TimeIndicator time={timeRemaining} />} <Pressable - onPress={onPressFullscreen} + onPress={enterFullscreen} style={a.flex_1} accessibilityLabel={_(msg`Video`)} accessibilityHint={_(msg`Tap to enter full screen`)} accessibilityRole="button" /> - <Animated.View - entering={FadeInDown.duration(300)} - style={[ - a.absolute, - a.rounded_full, - a.justify_center, - { - backgroundColor: 'rgba(0, 0, 0, 0.5)', - paddingHorizontal: 4, - paddingVertical: 4, - bottom: 6, - right: 6, - minHeight: 21, - minWidth: 21, - }, - ]}> - <Pressable - onPress={toggleMuted} - style={a.flex_1} - accessibilityLabel={isMuted ? _(msg`Muted`) : _(msg`Unmuted`)} - accessibilityHint={_(msg`Tap to toggle sound`)} - accessibilityRole="button" - hitSlop={HITSLOP_30}> - {isMuted ? ( - <MuteIcon width={13} fill={t.palette.white} /> - ) : ( - <UnmuteIcon width={13} fill={t.palette.white} /> - )} - </Pressable> - </Animated.View> + <ControlButton + onPress={togglePlayback} + label={isPlaying ? _(msg`Pause`) : _(msg`Play`)} + accessibilityHint={_(msg`Tap to play or pause`)} + style={{left: 6}}> + {isPlaying ? ( + <PauseIcon width={13} fill={t.palette.white} /> + ) : ( + <PlayIcon width={13} fill={t.palette.white} /> + )} + </ControlButton> + {showTime && <TimeIndicator time={timeRemaining} style={{left: 33}} />} + + <ControlButton + onPress={toggleMuted} + label={isMuted ? _(msg`Unmute`) : _(msg`Mute`)} + accessibilityHint={_(msg`Tap to toggle sound`)} + style={{right: 6}}> + {isMuted ? ( + <MuteIcon width={13} fill={t.palette.white} /> + ) : ( + <UnmuteIcon width={13} fill={t.palette.white} /> + )} + </ControlButton> </View> ) } + +function ControlButton({ + onPress, + children, + label, + accessibilityHint, + style, +}: { + onPress: () => void + children: React.ReactNode + label: string + accessibilityHint: string + style?: StyleProp<ViewStyle> +}) { + return ( + <Animated.View + entering={FadeInDown.duration(300)} + style={[ + a.absolute, + a.rounded_full, + a.justify_center, + { + backgroundColor: 'rgba(0, 0, 0, 0.5)', + paddingHorizontal: 4, + paddingVertical: 4, + bottom: 6, + minHeight: 21, + minWidth: 21, + }, + style, + ]}> + <Pressable + onPress={onPress} + style={a.flex_1} + accessibilityLabel={label} + accessibilityHint={accessibilityHint} + accessibilityRole="button" + hitSlop={HITSLOP_30}> + {children} + </Pressable> + </Animated.View> + ) +} |