diff options
Diffstat (limited to 'src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx')
-rw-r--r-- | src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx | 148 |
1 files changed, 94 insertions, 54 deletions
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx index 580cf363a..8b0858b69 100644 --- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx @@ -1,22 +1,32 @@ import React from 'react' import { ActivityIndicator, - Dimensions, GestureResponderEvent, Pressable, StyleSheet, + useWindowDimensions, View, } from 'react-native' +import Animated, { + measure, + runOnJS, + useAnimatedRef, + useFrameCallback, +} from 'react-native-reanimated' import {Image} from 'expo-image' import {WebView} from 'react-native-webview' -import YoutubePlayer from 'react-native-youtube-iframe' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' +import {AppBskyEmbedExternal} from '@atproto/api' import {EmbedPlayerParams, getPlayerHeight} from 'lib/strings/embed-player' import {EventStopper} from '../EventStopper' -import {AppBskyEmbedExternal} from '@atproto/api' import {isNative} from 'platform/detection' -import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' +import {useExternalEmbedsPrefs} from 'state/preferences' +import {useModalControls} from 'state/modals' interface ShouldStartLoadRequest { url: string @@ -32,6 +42,8 @@ function PlaceholderOverlay({ isPlayerActive: boolean onPress: (event: GestureResponderEvent) => void }) { + const {_} = useLingui() + // If the player is active and not loading, we don't want to show the overlay. if (isPlayerActive && !isLoading) return null @@ -39,8 +51,8 @@ function PlaceholderOverlay({ <View style={[styles.layer, styles.overlayLayer]}> <Pressable accessibilityRole="button" - accessibilityLabel="Play Video" - accessibilityHint="" + accessibilityLabel={_(msg`Play Video`)} + accessibilityHint={_(msg`Play Video`)} onPress={onPress} style={[styles.overlayContainer, styles.topRadius]}> {!isPlayerActive ? ( @@ -77,31 +89,21 @@ function Player({ return ( <View style={[styles.layer, styles.playerLayer]}> <EventStopper> - {isNative && params.type === 'youtube_video' ? ( - <YoutubePlayer - videoId={params.videoId} - play - height={height} - onReady={onLoad} - webViewStyle={[styles.webview, styles.topRadius]} + <View style={{height, width: '100%'}}> + <WebView + javaScriptEnabled={true} + onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} + mediaPlaybackRequiresUserAction={false} + allowsInlineMediaPlayback + bounces={false} + allowsFullscreenVideo + nestedScrollEnabled + source={{uri: params.playerUri}} + onLoad={onLoad} + setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads) + style={[styles.webview, styles.topRadius]} /> - ) : ( - <View style={{height, width: '100%'}}> - <WebView - javaScriptEnabled={true} - onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} - mediaPlaybackRequiresUserAction={false} - allowsInlineMediaPlayback - bounces={false} - allowsFullscreenVideo - nestedScrollEnabled - source={{uri: params.playerUri}} - onLoad={onLoad} - setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads) - style={[styles.webview, styles.topRadius]} - /> - </View> - )} + </View> </EventStopper> </View> ) @@ -116,6 +118,10 @@ export function ExternalPlayer({ params: EmbedPlayerParams }) { const navigation = useNavigation<NavigationProp>() + const insets = useSafeAreaInsets() + const windowDims = useWindowDimensions() + const externalEmbedsPrefs = useExternalEmbedsPrefs() + const {openModal} = useModalControls() const [isPlayerActive, setPlayerActive] = React.useState(false) const [isLoading, setIsLoading] = React.useState(true) @@ -124,34 +130,51 @@ export function ExternalPlayer({ height: 0, }) - const viewRef = React.useRef<View>(null) + const viewRef = useAnimatedRef() + + const frameCallback = useFrameCallback(() => { + const measurement = measure(viewRef) + if (!measurement) return + + const {height: winHeight, width: winWidth} = windowDims + + // Get the proper screen height depending on what is going on + const realWinHeight = isNative // If it is native, we always want the larger number + ? winHeight > winWidth + ? winHeight + : winWidth + : winHeight // On web, we always want the actual screen height + + const top = measurement.pageY + const bot = measurement.pageY + measurement.height + + // We can use the same logic on all platforms against the screenHeight that we get above + const isVisible = top <= realWinHeight - insets.bottom && bot >= insets.top + + if (!isVisible) { + runOnJS(setPlayerActive)(false) + } + }, false) // False here disables autostarting the callback // watch for leaving the viewport due to scrolling React.useEffect(() => { + // We don't want to do anything if the player isn't active + if (!isPlayerActive) return + // Interval for scrolling works in most cases, However, for twitch embeds, if we navigate away from the screen the webview will // continue playing. We need to watch for the blur event const unsubscribe = navigation.addListener('blur', () => { setPlayerActive(false) }) - const interval = setInterval(() => { - viewRef.current?.measure((x, y, w, h, pageX, pageY) => { - const window = Dimensions.get('window') - const top = pageY - const bot = pageY + h - const isVisible = isNative - ? top >= 0 && bot <= window.height - : !(top >= window.height || bot <= 0) - if (!isVisible) { - setPlayerActive(false) - } - }) - }, 1e3) + // Start watching for changes + frameCallback.setActive(true) + return () => { unsubscribe() - clearInterval(interval) + frameCallback.setActive(false) } - }, [viewRef, navigation]) + }, [navigation, isPlayerActive, frameCallback]) // calculate height for the player and the screen size const height = React.useMemo( @@ -168,12 +191,26 @@ export function ExternalPlayer({ setIsLoading(false) }, []) - const onPlayPress = React.useCallback((event: GestureResponderEvent) => { - // Prevent this from propagating upward on web - event.preventDefault() + const onPlayPress = React.useCallback( + (event: GestureResponderEvent) => { + // Prevent this from propagating upward on web + event.preventDefault() - setPlayerActive(true) - }, []) + if (externalEmbedsPrefs?.[params.source] === undefined) { + openModal({ + name: 'embed-consent', + source: params.source, + onAccept: () => { + setPlayerActive(true) + }, + }) + return + } + + setPlayerActive(true) + }, + [externalEmbedsPrefs, openModal, params.source], + ) // measure the layout to set sizing const onLayout = React.useCallback( @@ -187,7 +224,7 @@ export function ExternalPlayer({ ) return ( - <View + <Animated.View ref={viewRef} style={{height}} collapsable={false} @@ -205,7 +242,6 @@ export function ExternalPlayer({ accessibilityIgnoresInvertColors /> )} - <PlaceholderOverlay isLoading={isLoading} isPlayerActive={isPlayerActive} @@ -217,7 +253,7 @@ export function ExternalPlayer({ height={height} onLoad={onLoad} /> - </View> + </Animated.View> ) } @@ -248,4 +284,8 @@ const styles = StyleSheet.create({ webview: { backgroundColor: 'transparent', }, + gifContainer: { + width: '100%', + overflow: 'hidden', + }, }) |