Align composer video preview with in-feed embed (#10447)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-05-08 18:37:07 +01:00
committed by GitHub
parent 92199774d5
commit 92fdb6630d
3 changed files with 150 additions and 126 deletions
+23 -18
View File
@@ -5,10 +5,10 @@ import {type ImagePickerAsset} from 'expo-image-picker'
import {BlueskyVideoView} from '@bsky.app/video' import {BlueskyVideoView} from '@bsky.app/video'
import {type CompressedVideo} from '#/lib/media/video/types' import {type CompressedVideo} from '#/lib/media/video/types'
import {clamp} from '#/lib/numbers'
import {useAutoplayDisabled} from '#/state/preferences' import {useAutoplayDisabled} from '#/state/preferences'
import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn' import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
import {atoms as a, useTheme} from '#/alf' import {atoms as a} from '#/alf'
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop' import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'
@@ -23,28 +23,29 @@ export function VideoPreview({
isActivePost: boolean isActivePost: boolean
clear: () => void clear: () => void
}) { }) {
const t = useTheme()
const playerRef = useRef<BlueskyVideoView>(null) const playerRef = useRef<BlueskyVideoView>(null)
const autoplayDisabled = useAutoplayDisabled() const autoplayDisabled = useAutoplayDisabled()
let aspectRatio = asset.width / asset.height
if (isNaN(aspectRatio)) { let aspectRatio: number | undefined
aspectRatio = 16 / 9 if (asset.width && asset.height) {
const raw = asset.width / asset.height
if (!Number.isNaN(raw)) {
aspectRatio = raw
}
} }
aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) let constrained: number | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
}
return ( return (
<View <View style={[a.pt_xs]}>
style={[ <ConstrainedImage
a.w_full, aspectRatio={constrained || 1}
a.rounded_sm, minMobileAspectRatio={14 / 9}>
{aspectRatio}, <View style={[a.flex_1, {backgroundColor: 'black'}]}>
a.overflow_hidden,
a.border,
t.atoms.border_contrast_low,
{backgroundColor: 'black'},
]}>
<View style={[a.absolute, a.inset_0]}> <View style={[a.absolute, a.inset_0]}>
<VideoTranscodeBackdrop uri={asset.uri} /> <VideoTranscodeBackdrop uri={asset.uri} />
</View> </View>
@@ -57,6 +58,7 @@ export function VideoPreview({
source={{uri: video.uri}} source={{uri: video.uri}}
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
cachePolicy="none" cachePolicy="none"
contentFit="contain"
/> />
) : ( ) : (
<BlueskyVideoView <BlueskyVideoView
@@ -71,10 +73,13 @@ export function VideoPreview({
)} )}
<ExternalEmbedRemoveBtn onRemove={clear} /> <ExternalEmbedRemoveBtn onRemove={clear} />
{autoplayDisabled && ( {autoplayDisabled && (
<View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> <View
style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
<PlayButtonIcon /> <PlayButtonIcon />
</View> </View>
)} )}
</View> </View>
</ConstrainedImage>
</View>
) )
} }
@@ -4,22 +4,21 @@ import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {type CompressedVideo} from '#/lib/media/video/types' import {type CompressedVideo} from '#/lib/media/video/types'
import {clamp} from '#/lib/numbers'
import {useAutoplayDisabled} from '#/state/preferences' import {useAutoplayDisabled} from '#/state/preferences'
import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn' import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
export function VideoPreview({ export function VideoPreview({
asset, asset,
video, video,
clear, clear,
}: { }: {
asset: ImagePickerAsset asset: ImagePickerAsset
video: CompressedVideo video: CompressedVideo
isActivePost: boolean
clear: () => void clear: () => void
}) { }) {
const {_} = useLingui() const {_} = useLingui()
@@ -27,36 +26,37 @@ export function VideoPreview({
// it's not possible using an img tag -sfn // it's not possible using an img tag -sfn
const autoplayDisabled = useAutoplayDisabled() const autoplayDisabled = useAutoplayDisabled()
let aspectRatio = asset.width / asset.height let aspectRatio: number | undefined
if (asset.width && asset.height) {
if (isNaN(aspectRatio)) { const raw = asset.width / asset.height
aspectRatio = 16 / 9 if (!Number.isNaN(raw)) {
aspectRatio = raw
}
} }
aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) let constrained: number | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
}
return ( return (
<View <View style={[a.pt_xs]}>
style={[ <ConstrainedImage
a.w_full, aspectRatio={constrained || 1}
a.rounded_sm, minMobileAspectRatio={14 / 9}>
{aspectRatio}, <View style={[a.flex_1, {backgroundColor: 'black'}]}>
a.overflow_hidden,
{backgroundColor: 'black'},
a.relative,
]}>
<ExternalEmbedRemoveBtn onRemove={clear} />
{video.mimeType === 'image/gif' ? ( {video.mimeType === 'image/gif' ? (
<img <img
src={video.uri} src={video.uri}
style={{width: '100%', height: '100%', objectFit: 'cover'}} style={{width: '100%', height: '100%', objectFit: 'contain'}}
alt="GIF" alt="GIF"
/> />
) : ( ) : (
<> <>
<video <video
src={video.uri} src={video.uri}
style={{width: '100%', height: '100%', objectFit: 'cover'}} style={{width: '100%', height: '100%', objectFit: 'contain'}}
autoPlay={!autoplayDisabled} autoPlay={!autoplayDisabled}
loop loop
muted muted
@@ -71,12 +71,20 @@ export function VideoPreview({
/> />
{autoplayDisabled && ( {autoplayDisabled && (
<View <View
style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> style={[
a.absolute,
a.inset_0,
a.justify_center,
a.align_center,
]}>
<PlayButtonIcon /> <PlayButtonIcon />
</View> </View>
)} )}
</> </>
)} )}
<ExternalEmbedRemoveBtn onRemove={clear} />
</View>
</ConstrainedImage>
</View> </View>
) )
} }
@@ -3,8 +3,8 @@ import {View} from 'react-native'
import ProgressPie from 'react-native-progress/Pie' import ProgressPie from 'react-native-progress/Pie'
import {type ImagePickerAsset} from 'expo-image-picker' import {type ImagePickerAsset} from 'expo-image-picker'
import {clamp} from '#/lib/numbers'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn' import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn'
import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop' import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'
@@ -22,22 +22,31 @@ export function VideoTranscodeProgress({
if (IS_WEB) return null if (IS_WEB) return null
let aspectRatio = asset.width / asset.height let aspectRatio: number | undefined
if (asset.width && asset.height) {
if (isNaN(aspectRatio)) { const raw = asset.width / asset.height
aspectRatio = 16 / 9 if (!Number.isNaN(raw)) {
aspectRatio = raw
}
} }
aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) let constrained: number | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
}
return ( return (
<View style={[a.pt_xs]}>
<ConstrainedImage
aspectRatio={constrained || 1}
minMobileAspectRatio={14 / 9}>
<View <View
style={[ style={[
a.w_full, a.flex_1,
t.atoms.bg_contrast_50, t.atoms.bg_contrast_50,
a.rounded_md, a.rounded_md,
a.overflow_hidden, a.overflow_hidden,
{aspectRatio},
]}> ]}>
<VideoTranscodeBackdrop uri={asset.uri} /> <VideoTranscodeBackdrop uri={asset.uri} />
<View <View
@@ -59,5 +68,7 @@ export function VideoTranscodeProgress({
</View> </View>
<ExternalEmbedRemoveBtn onRemove={clear} /> <ExternalEmbedRemoveBtn onRemove={clear} />
</View> </View>
</ConstrainedImage>
</View>
) )
} }