Hide video pillarboxing on web when the card fits (#11118)
This commit is contained in:
@@ -30,6 +30,13 @@ import * as VideoFallback from './VideoEmbedInner/VideoFallback'
|
|||||||
|
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum card width for the overlay controls (play, time, CC, volume,
|
||||||
|
* fullscreen) to fit without crowding. Narrower cards fall back to the
|
||||||
|
* full-width pillarbox.
|
||||||
|
*/
|
||||||
|
const MIN_CARD_WIDTH = 280
|
||||||
|
|
||||||
export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
@@ -91,6 +98,21 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
constrained = Math.max(aspectRatio, ratio)
|
constrained = Math.max(aspectRatio, ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [containerWidth, setContainerWidth] = useState(0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Portrait videos render at their own ratio instead of pillarboxed, but
|
||||||
|
* only when the resulting card fits the overlay controls. Videos taller
|
||||||
|
* than 1:2 would still show bars inside a ratio-fit card, and an unknown
|
||||||
|
* ratio can't be fit, so both keep the full-width pillarbox - a narrow
|
||||||
|
* card with black slices down the sides looks broken (see #9371).
|
||||||
|
*/
|
||||||
|
const cardWidth = containerWidth * Math.min(aspectRatio ?? 1, 1)
|
||||||
|
const fullBleed =
|
||||||
|
aspectRatio === undefined ||
|
||||||
|
aspectRatio < 1 / 2 ||
|
||||||
|
(containerWidth > 0 && cardWidth < MIN_CARD_WIDTH)
|
||||||
|
|
||||||
const contents = (
|
const contents = (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -98,6 +120,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
|
position: 'relative',
|
||||||
backgroundColor: t.palette.black,
|
backgroundColor: t.palette.black,
|
||||||
backgroundImage: `url(${embed.thumbnail})`,
|
backgroundImage: `url(${embed.thumbnail})`,
|
||||||
backgroundSize: 'contain',
|
backgroundSize: 'contain',
|
||||||
@@ -105,6 +128,36 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
}}
|
}}
|
||||||
onClick={evt => evt.stopPropagation()}>
|
onClick={evt => evt.stopPropagation()}>
|
||||||
|
{fullBleed && embed.thumbnail && (
|
||||||
|
<>
|
||||||
|
{/* blurred backdrop fills the bars when the video is boxed */}
|
||||||
|
<div
|
||||||
|
aria-hidden
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
backgroundImage: `url(${embed.thumbnail})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
filter: 'blur(32px)',
|
||||||
|
// hide the transparent fade the blur creates at the edges
|
||||||
|
transform: 'scale(1.2)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* redraw the sharp thumbnail above the blur */}
|
||||||
|
<div
|
||||||
|
aria-hidden
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
backgroundImage: `url(${embed.thumbnail})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<ErrorBoundary renderError={renderError} key={key}>
|
<ErrorBoundary renderError={renderError} key={key}>
|
||||||
<OnlyNearScreen>
|
<OnlyNearScreen>
|
||||||
<VideoEmbedInnerWeb
|
<VideoEmbedInnerWeb
|
||||||
@@ -120,12 +173,14 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.pt_xs]}>
|
<View
|
||||||
|
style={[a.pt_xs]}
|
||||||
|
onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}>
|
||||||
<ViewportObserver
|
<ViewportObserver
|
||||||
sendPosition={isGif ? noop : sendPosition}
|
sendPosition={isGif ? noop : sendPosition}
|
||||||
isAnyViewActive={currentActiveView !== null}>
|
isAnyViewActive={currentActiveView !== null}>
|
||||||
<ConstrainedImage
|
<ConstrainedImage
|
||||||
fullBleed
|
fullBleed={fullBleed}
|
||||||
aspectRatio={constrained || 1}
|
aspectRatio={constrained || 1}
|
||||||
// slightly smaller max height than images
|
// slightly smaller max height than images
|
||||||
// images use 16 / 9, for reference
|
// images use 16 / 9, for reference
|
||||||
|
|||||||
Reference in New Issue
Block a user