import React, {useCallback, useEffect, useId, useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' import {VideoPlayerStatus} from 'expo-video' import {AppBskyEmbedVideo} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {clamp} from '#/lib/numbers' import {useGate} from '#/lib/statsig/statsig' import {VideoEmbedInnerNative} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative' import {atoms as a} from '#/alf' import {Button} from '#/components/Button' import {Loader} from '#/components/Loader' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army' import {ErrorBoundary} from '../ErrorBoundary' import {useActiveVideoNative} from './ActiveVideoNativeContext' import * as VideoFallback from './VideoEmbedInner/VideoFallback' interface Props { embed: AppBskyEmbedVideo.View } export function VideoEmbed({embed}: Props) { const gate = useGate() const [key, setKey] = useState(0) const renderError = useCallback( (error: unknown) => ( setKey(key + 1)} /> ), [key], ) let aspectRatio = 16 / 9 if (embed.aspectRatio) { const {width, height} = embed.aspectRatio aspectRatio = width / height aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) } if (!gate('video_view_on_posts')) { return null } return ( ) } function InnerWrapper({embed}: Props) { const {_} = useLingui() const {activeSource, activeViewId, setActiveSource, player} = useActiveVideoNative() const viewId = useId() const [playerStatus, setPlayerStatus] = useState('loading') const [isMuted, setIsMuted] = useState(player.muted) const [isFullscreen, setIsFullscreen] = React.useState(false) const [timeRemaining, setTimeRemaining] = React.useState(0) const isActive = embed.playlist === activeSource && activeViewId === viewId const isLoading = isActive && (playerStatus === 'waitingToPlayAtSpecifiedRate' || playerStatus === 'loading') useEffect(() => { if (isActive) { // eslint-disable-next-line @typescript-eslint/no-shadow const volumeSub = player.addListener('volumeChange', ({isMuted}) => { setIsMuted(isMuted) }) const timeSub = player.addListener( 'timeRemainingChange', secondsRemaining => { setTimeRemaining(secondsRemaining) }, ) const statusSub = player.addListener( 'statusChange', (status, _oldStatus, error) => { setPlayerStatus(status) if (status === 'error') { throw error } }, ) return () => { volumeSub.remove() timeSub.remove() statusSub.remove() } } }, [player, isActive]) useEffect(() => { if (!isActive && playerStatus !== 'loading') { setPlayerStatus('loading') } }, [isActive, playerStatus]) const onChangeStatus = (isVisible: boolean) => { if (isFullscreen) { return } if (isVisible) { setActiveSource(embed.playlist, viewId) if (!player.playing) { player.play() } } else { player.muted = true if (player.playing) { player.pause() } } } return ( {isActive ? ( ) : null} {!isActive || isLoading ? ( {embed.alt} ) : null} ) } function VideoError({retry}: {error: unknown; retry: () => void}) { return ( An error occurred while loading the video. Please try again later. ) }