play/pause button when autoplay disabled
This commit is contained in:
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user