video player gif presentation style

This commit is contained in:
Samuel Newman
2026-01-30 17:28:42 +02:00
parent 812ff4d154
commit cd5cd02535
5 changed files with 127 additions and 14 deletions
@@ -0,0 +1,85 @@
import {Pressable, StyleSheet, View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {atoms as a, useTheme} from '#/alf'
import {Fill} from '#/components/Fill'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
export function GifPresentationControls({
onPress,
isPlaying,
isLoading,
}: {
onPress: () => void
isPlaying: boolean
isLoading?: boolean
}) {
const {_} = useLingui()
const t = useTheme()
return (
<>
<Pressable
accessibilityRole="button"
accessibilityHint={_(msg`Plays or pauses the GIF`)}
accessibilityLabel={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
style={[
a.absolute,
a.align_center,
a.justify_center,
a.inset_0,
a.w_full,
a.h_full,
{
zIndex: 2,
},
]}
onPress={onPress}>
{isLoading ? (
<View style={[a.align_center, a.justify_center]}>
<Loader size="xl" />
</View>
) : !isPlaying ? (
<PlayButtonIcon />
) : undefined}
</Pressable>
{!isPlaying && (
<Fill
style={[
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
{
opacity: 0.3,
zIndex: 1,
},
]}
/>
)}
<View style={styles.gifBadgeContainer}>
<Text style={styles.gifBadge}>
<Trans>GIF</Trans>
</Text>
</View>
</>
)
}
const styles = StyleSheet.create({
gifBadgeContainer: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
position: 'absolute',
right: 5,
bottom: 5,
zIndex: 2,
},
gifBadge: {
color: 'white',
fontSize: 7,
fontWeight: '600',
},
})
@@ -15,6 +15,7 @@ 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 {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {GifPresentationControls} from '../GifPresentationControls'
import {TimeIndicator} from './TimeIndicator'
export function VideoEmbedInnerNative({
@@ -50,6 +51,8 @@ export function VideoEmbedInnerNative({
throw new Error(error)
}
const isGif = embed.presentation === 'gif'
return (
<View style={[a.flex_1, a.relative]}>
<BlueskyVideoView
@@ -82,25 +85,35 @@ export function VideoEmbedInnerNative({
}
accessibilityHint=""
/>
<VideoControls
enterFullscreen={() => {
videoRef.current?.enterFullscreen(true)
}}
toggleMuted={() => {
videoRef.current?.toggleMuted()
}}
togglePlayback={() => {
videoRef.current?.togglePlayback()
}}
isPlaying={isPlaying}
timeRemaining={timeRemaining}
/>
{isGif ? (
<GifPresentationControls
onPress={() => {
videoRef.current?.togglePlayback()
}}
isPlaying={isPlaying}
isLoading={false}
/>
) : (
<VideoPresentationControls
enterFullscreen={() => {
videoRef.current?.enterFullscreen(true)
}}
toggleMuted={() => {
videoRef.current?.toggleMuted()
}}
togglePlayback={() => {
videoRef.current?.togglePlayback()
}}
isPlaying={isPlaying}
timeRemaining={timeRemaining}
/>
)}
<MediaInsetBorder />
</View>
)
}
function VideoControls({
function VideoPresentationControls({
enterFullscreen,
toggleMuted,
togglePlayback,
@@ -99,6 +99,7 @@ export function VideoEmbedInnerWeb({
onScreen={onScreen}
fullscreenRef={containerRef}
hasSubtitleTrack={hasSubtitleTrack}
isGif={embed.presentation === 'gif'}
/>
</div>
</View>
@@ -27,6 +27,7 @@ import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {IS_WEB_MOBILE_IOS, IS_WEB_TOUCH_DEVICE} from '#/env'
import {GifPresentationControls} from '../../GifPresentationControls'
import {TimeIndicator} from '../TimeIndicator'
import {ControlButton} from './ControlButton'
import {Scrubber} from './Scrubber'
@@ -44,6 +45,7 @@ export function Controls({
fullscreenRef,
hlsLoading,
hasSubtitleTrack,
isGif,
}: {
videoRef: React.RefObject<HTMLVideoElement | null>
hlsRef: React.RefObject<Hls | undefined | null>
@@ -55,6 +57,7 @@ export function Controls({
fullscreenRef: React.RefObject<HTMLDivElement | null>
hlsLoading: boolean
hasSubtitleTrack: boolean
isGif: boolean
}) {
const {
play,
@@ -287,6 +290,16 @@ export function Controls({
((focused || autoplayDisabled) && !playing) ||
(interactingViaKeypress ? hasFocus : hovered)
if (isGif) {
return (
<GifPresentationControls
isPlaying={playing}
isLoading={showSpinner}
onPress={onPressPlayPause}
/>
)
}
return (
<div
style={{
@@ -98,6 +98,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
setActive={setActive}
onScreen={onScreen}
lastKnownTime={lastKnownTime}
isGif={embed.presentation === 'gif'}
/>
</OnlyNearScreen>
</ErrorBoundary>