diff options
author | Eric Bailey <git@esb.lol> | 2025-01-17 20:09:44 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-18 02:09:44 +0000 |
commit | 3e0ac0a0668b5906d7b81dbc075cfd04ba89911c (patch) | |
tree | 5a113c11d8084ab0f9a1dd4edbae47b6e620c621 /src | |
parent | 6f3f11671d526c5d9a454c46f6daf305b588ef4c (diff) | |
download | voidsky-3e0ac0a0668b5906d7b81dbc075cfd04ba89911c.tar.zst |
Bring video cropping in line with images (#7462)
* Mimic image cropping for videos on web * Same on native
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbed.tsx | 67 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/VideoEmbed.web.tsx | 90 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 11 |
3 files changed, 113 insertions, 55 deletions
diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index f268bf8db..b45027089 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -5,9 +5,9 @@ import {AppBskyEmbedVideo} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {clamp} from '#/lib/numbers' +import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' import {VideoEmbedInnerNative} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' @@ -16,9 +16,11 @@ import * as VideoFallback from './VideoEmbedInner/VideoFallback' interface Props { embed: AppBskyEmbedVideo.View + crop?: 'none' | 'square' | 'constrained' } -export function VideoEmbed({embed}: Props) { +export function VideoEmbed({embed, crop}: Props) { + const t = useTheme() const [key, setKey] = useState(0) const renderError = useCallback( @@ -28,26 +30,51 @@ export function VideoEmbed({embed}: Props) { [key], ) - let aspectRatio = 16 / 9 - if (embed.aspectRatio) { - const {width, height} = embed.aspectRatio - aspectRatio = width / height - aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) + let aspectRatio: number | undefined + const dims = embed.aspectRatio + if (dims) { + aspectRatio = dims.width / dims.height + if (Number.isNaN(aspectRatio)) { + aspectRatio = undefined + } + } + + let constrained: number | undefined + let max: number | undefined + if (aspectRatio !== undefined) { + const ratio = 1 / 2 // max of 1:2 ratio in feeds + constrained = Math.max(aspectRatio, ratio) + max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread } + const cropDisabled = crop === 'none' + + const contents = ( + <ErrorBoundary renderError={renderError} key={key}> + <InnerWrapper embed={embed} /> + </ErrorBoundary> + ) return ( - <View - style={[ - a.w_full, - a.rounded_md, - a.overflow_hidden, - {aspectRatio}, - {backgroundColor: 'black'}, - a.mt_xs, - ]}> - <ErrorBoundary renderError={renderError} key={key}> - <InnerWrapper embed={embed} /> - </ErrorBoundary> + <View style={[a.pt_xs]}> + {cropDisabled ? ( + <View + style={[ + a.w_full, + a.overflow_hidden, + {aspectRatio: max ?? 1}, + a.rounded_md, + a.overflow_hidden, + t.atoms.bg_contrast_25, + ]}> + {contents} + </View> + ) : ( + <ConstrainedImage + fullBleed={crop === 'square'} + aspectRatio={constrained || 1}> + {contents} + </ConstrainedImage> + )} </View> ) } diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx index a1f4652ac..b0ded6754 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx @@ -5,7 +5,7 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {isFirefox} from '#/lib/browser' -import {clamp} from '#/lib/numbers' +import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' import { HLSUnsupportedError, VideoEmbedInnerWeb, @@ -18,7 +18,13 @@ import {ErrorBoundary} from '../ErrorBoundary' import {useActiveVideoWeb} from './ActiveVideoWebContext' import * as VideoFallback from './VideoEmbedInner/VideoFallback' -export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { +export function VideoEmbed({ + embed, + crop, +}: { + embed: AppBskyEmbedVideo.View + crop?: 'none' | 'square' | 'constrained' +}) { const ref = useRef<HTMLDivElement>(null) const {active, setActive, sendPosition, currentActiveView} = useActiveVideoWeb() @@ -52,42 +58,58 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { [key], ) - let aspectRatio = 16 / 9 + let aspectRatio: number | undefined + const dims = embed.aspectRatio + if (dims) { + aspectRatio = dims.width / dims.height + if (Number.isNaN(aspectRatio)) { + aspectRatio = undefined + } + } - if (embed.aspectRatio) { - const {width, height} = embed.aspectRatio - // min: 3/1, max: square - aspectRatio = clamp(width / height, 1 / 1, 3 / 1) + let constrained: number | undefined + let max: number | undefined + if (aspectRatio !== undefined) { + const ratio = 1 / 2 // max of 1:2 ratio in feeds + constrained = Math.max(aspectRatio, ratio) + max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread } + const cropDisabled = crop === 'none' + + const contents = ( + <div + ref={ref} + style={{display: 'flex', flex: 1, cursor: 'default'}} + onClick={evt => evt.stopPropagation()}> + <ErrorBoundary renderError={renderError} key={key}> + <ViewportObserver + sendPosition={sendPosition} + isAnyViewActive={currentActiveView !== null}> + <VideoEmbedInnerWeb + embed={embed} + active={active} + setActive={setActive} + onScreen={onScreen} + lastKnownTime={lastKnownTime} + /> + </ViewportObserver> + </ErrorBoundary> + </div> + ) return ( - <View - style={[ - a.w_full, - {aspectRatio}, - {backgroundColor: 'black'}, - a.relative, - a.rounded_md, - a.mt_xs, - ]}> - <div - ref={ref} - style={{display: 'flex', flex: 1, cursor: 'default'}} - onClick={evt => evt.stopPropagation()}> - <ErrorBoundary renderError={renderError} key={key}> - <ViewportObserver - sendPosition={sendPosition} - isAnyViewActive={currentActiveView !== null}> - <VideoEmbedInnerWeb - embed={embed} - active={active} - setActive={setActive} - onScreen={onScreen} - lastKnownTime={lastKnownTime} - /> - </ViewportObserver> - </ErrorBoundary> - </div> + <View style={[a.pt_xs]}> + {cropDisabled ? ( + <View style={[a.w_full, a.overflow_hidden, {aspectRatio: max ?? 1}]}> + {contents} + </View> + ) : ( + <ConstrainedImage + fullBleed={crop === 'square'} + aspectRatio={constrained || 1}> + {contents} + </ConstrainedImage> + )} </View> ) } diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 9dc43da8e..6f5f9d3ab 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -237,7 +237,16 @@ export function PostEmbeds({ if (AppBskyEmbedVideo.isView(embed)) { return ( <ContentHider modui={moderation?.ui('contentMedia')}> - <VideoEmbed embed={embed} /> + <VideoEmbed + embed={embed} + crop={ + viewContext === PostEmbedViewContext.ThreadHighlighted + ? 'none' + : viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia + ? 'square' + : 'constrained' + } + /> </ContentHider> ) } |