Bring video cropping in line with images (#7462)
* Mimic image cropping for videos on web * Same on native
This commit is contained in:
@@ -5,9 +5,9 @@ import {AppBskyEmbedVideo} from '@atproto/api'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
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 {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 {Button} from '#/components/Button'
|
||||||
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
|
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
|
||||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||||
@@ -16,9 +16,11 @@ import * as VideoFallback from './VideoEmbedInner/VideoFallback'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
embed: AppBskyEmbedVideo.View
|
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 [key, setKey] = useState(0)
|
||||||
|
|
||||||
const renderError = useCallback(
|
const renderError = useCallback(
|
||||||
@@ -28,26 +30,51 @@ export function VideoEmbed({embed}: Props) {
|
|||||||
[key],
|
[key],
|
||||||
)
|
)
|
||||||
|
|
||||||
let aspectRatio = 16 / 9
|
let aspectRatio: number | undefined
|
||||||
if (embed.aspectRatio) {
|
const dims = embed.aspectRatio
|
||||||
const {width, height} = embed.aspectRatio
|
if (dims) {
|
||||||
aspectRatio = width / height
|
aspectRatio = dims.width / dims.height
|
||||||
aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1)
|
if (Number.isNaN(aspectRatio)) {
|
||||||
|
aspectRatio = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
let constrained: number | undefined
|
||||||
<View
|
let max: number | undefined
|
||||||
style={[
|
if (aspectRatio !== undefined) {
|
||||||
a.w_full,
|
const ratio = 1 / 2 // max of 1:2 ratio in feeds
|
||||||
a.rounded_md,
|
constrained = Math.max(aspectRatio, ratio)
|
||||||
a.overflow_hidden,
|
max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
|
||||||
{aspectRatio},
|
}
|
||||||
{backgroundColor: 'black'},
|
const cropDisabled = crop === 'none'
|
||||||
a.mt_xs,
|
|
||||||
]}>
|
const contents = (
|
||||||
<ErrorBoundary renderError={renderError} key={key}>
|
<ErrorBoundary renderError={renderError} key={key}>
|
||||||
<InnerWrapper embed={embed} />
|
<InnerWrapper embed={embed} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {msg} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {isFirefox} from '#/lib/browser'
|
import {isFirefox} from '#/lib/browser'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage'
|
||||||
import {
|
import {
|
||||||
HLSUnsupportedError,
|
HLSUnsupportedError,
|
||||||
VideoEmbedInnerWeb,
|
VideoEmbedInnerWeb,
|
||||||
@@ -18,7 +18,13 @@ import {ErrorBoundary} from '../ErrorBoundary'
|
|||||||
import {useActiveVideoWeb} from './ActiveVideoWebContext'
|
import {useActiveVideoWeb} from './ActiveVideoWebContext'
|
||||||
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
|
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 ref = useRef<HTMLDivElement>(null)
|
||||||
const {active, setActive, sendPosition, currentActiveView} =
|
const {active, setActive, sendPosition, currentActiveView} =
|
||||||
useActiveVideoWeb()
|
useActiveVideoWeb()
|
||||||
@@ -52,24 +58,25 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
[key],
|
[key],
|
||||||
)
|
)
|
||||||
|
|
||||||
let aspectRatio = 16 / 9
|
let aspectRatio: number | undefined
|
||||||
|
const dims = embed.aspectRatio
|
||||||
if (embed.aspectRatio) {
|
if (dims) {
|
||||||
const {width, height} = embed.aspectRatio
|
aspectRatio = dims.width / dims.height
|
||||||
// min: 3/1, max: square
|
if (Number.isNaN(aspectRatio)) {
|
||||||
aspectRatio = clamp(width / height, 1 / 1, 3 / 1)
|
aspectRatio = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
let constrained: number | undefined
|
||||||
<View
|
let max: number | undefined
|
||||||
style={[
|
if (aspectRatio !== undefined) {
|
||||||
a.w_full,
|
const ratio = 1 / 2 // max of 1:2 ratio in feeds
|
||||||
{aspectRatio},
|
constrained = Math.max(aspectRatio, ratio)
|
||||||
{backgroundColor: 'black'},
|
max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
|
||||||
a.relative,
|
}
|
||||||
a.rounded_md,
|
const cropDisabled = crop === 'none'
|
||||||
a.mt_xs,
|
|
||||||
]}>
|
const contents = (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{display: 'flex', flex: 1, cursor: 'default'}}
|
style={{display: 'flex', flex: 1, cursor: 'default'}}
|
||||||
@@ -88,6 +95,21 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
</ViewportObserver>
|
</ViewportObserver>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,16 @@ export function PostEmbeds({
|
|||||||
if (AppBskyEmbedVideo.isView(embed)) {
|
if (AppBskyEmbedVideo.isView(embed)) {
|
||||||
return (
|
return (
|
||||||
<ContentHider modui={moderation?.ui('contentMedia')}>
|
<ContentHider modui={moderation?.ui('contentMedia')}>
|
||||||
<VideoEmbed embed={embed} />
|
<VideoEmbed
|
||||||
|
embed={embed}
|
||||||
|
crop={
|
||||||
|
viewContext === PostEmbedViewContext.ThreadHighlighted
|
||||||
|
? 'none'
|
||||||
|
: viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
|
||||||
|
? 'square'
|
||||||
|
: 'constrained'
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ContentHider>
|
</ContentHider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user