play/pause button when autoplay disabled

This commit is contained in:
Samuel Newman
2024-09-12 12:14:47 +01:00
parent b3065b2ba9
commit 5c7beb29b2
2 changed files with 110 additions and 34 deletions
@@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import {StyleProp, ViewStyle} from 'react-native'
import Animated, {FadeInDown, FadeOutDown} from 'react-native-reanimated' import Animated, {FadeInDown, FadeOutDown} from 'react-native-reanimated'
import {atoms as a, native, useTheme} from '#/alf' import {atoms as a, native, useTheme} from '#/alf'
@@ -8,7 +9,13 @@ import {Text} from '#/components/Typography'
* Absolutely positioned time indicator showing how many seconds are remaining * Absolutely positioned time indicator showing how many seconds are remaining
* Time is in seconds * Time is in seconds
*/ */
export function TimeIndicator({time}: {time: number}) { export function TimeIndicator({
time,
style,
}: {
time: number
style?: StyleProp<ViewStyle>
}) {
const t = useTheme() const t = useTheme()
if (isNaN(time)) { if (isNaN(time)) {
@@ -22,18 +29,20 @@ export function TimeIndicator({time}: {time: number}) {
<Animated.View <Animated.View
entering={native(FadeInDown.duration(300))} entering={native(FadeInDown.duration(300))}
exiting={native(FadeOutDown.duration(500))} exiting={native(FadeOutDown.duration(500))}
pointerEvents="none"
style={[ style={[
{ {
backgroundColor: 'rgba(0, 0, 0, 0.5)', backgroundColor: 'rgba(0, 0, 0, 0.5)',
borderRadius: 6, borderRadius: 6,
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 3, paddingVertical: 3,
position: 'absolute',
left: 6, left: 6,
bottom: 6, bottom: 6,
minHeight: 21, minHeight: 21,
justifyContent: 'center',
}, },
a.absolute,
a.justify_center,
style,
]}> ]}>
<Text <Text
style={[ style={[
@@ -1,5 +1,5 @@
import React, {useCallback, useRef} from 'react' import React, {useCallback, useEffect, useRef} from 'react'
import {Pressable, View} from 'react-native' import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
import Animated, {FadeInDown} from 'react-native-reanimated' import Animated, {FadeInDown} from 'react-native-reanimated'
import {VideoView} from 'expo-video' import {VideoView} from 'expo-video'
import {AppBskyEmbedVideo} from '@atproto/api' import {AppBskyEmbedVideo} from '@atproto/api'
@@ -8,10 +8,13 @@ import {useLingui} from '@lingui/react'
import {HITSLOP_30} from '#/lib/constants' import {HITSLOP_30} from '#/lib/constants'
import {clamp} from '#/lib/numbers' import {clamp} from '#/lib/numbers'
import {useAutoplayDisabled} from '#/state/preferences'
import {isAndroid} from 'platform/detection' import {isAndroid} from 'platform/detection'
import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 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 {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
import { import {
AudioCategory, AudioCategory,
@@ -99,6 +102,18 @@ function VideoControls({
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const {player, setMutedInFeed} = useActiveVideoNative() const {player, setMutedInFeed} = useActiveVideoNative()
const autoplayDisabled = useAutoplayDisabled()
const [isPlaying, setIsPlaying] = React.useState(player.playing)
useEffect(() => {
function listener(newIsPlaying: boolean) {
setIsPlaying(newIsPlaying)
}
player.addListener('playingChange', listener)
return () => {
player.removeListener('playingChange', listener)
}
}, [player])
const onPressFullscreen = useCallback(() => { const onPressFullscreen = useCallback(() => {
switch (player.status) { switch (player.status) {
@@ -116,6 +131,15 @@ function VideoControls({
} }
}, [player, enterFullscreen]) }, [player, enterFullscreen])
const togglePlayPause = useCallback(() => {
setIsPlaying(!player.playing)
if (player.playing) {
player.pause()
} else {
player.play()
}
}, [player])
const toggleMuted = useCallback(() => { const toggleMuted = useCallback(() => {
const muted = !player.muted 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 // We want to set this to the _inverse_ of the new value, because we actually want for the audio to be mixed when
@@ -137,7 +161,6 @@ function VideoControls({
return ( return (
<View style={[a.absolute, a.inset_0]}> <View style={[a.absolute, a.inset_0]}>
{showTime && <TimeIndicator time={timeRemaining} />}
<Pressable <Pressable
onPress={onPressFullscreen} onPress={onPressFullscreen}
style={a.flex_1} style={a.flex_1}
@@ -145,36 +168,80 @@ function VideoControls({
accessibilityHint={_(msg`Tap to enter full screen`)} accessibilityHint={_(msg`Tap to enter full screen`)}
accessibilityRole="button" accessibilityRole="button"
/> />
<Animated.View {autoplayDisabled && (
entering={FadeInDown.duration(300)} <ControlButton
style={[ onPress={togglePlayPause}
a.absolute, label={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
a.rounded_full, accessibilityHint={_(msg`Tap to play or pause`)}
a.justify_center, style={{left: 6}}>
{ {isPlaying ? (
backgroundColor: 'rgba(0, 0, 0, 0.5)', <PauseIcon width={13} fill={t.palette.white} />
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} /> <PlayIcon width={13} fill={t.palette.white} />
)} )}
</Pressable> </ControlButton>
</Animated.View> )}
{showTime && (
<TimeIndicator
time={timeRemaining}
style={autoplayDisabled ? {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> </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>
)
}