Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Bring video cropping in line with images (#7462)

* Mimic image cropping for videos on web

* Same on native

authored by

Eric Bailey and committed by
GitHub
3e0ac0a0 6f3f1167

+113 -55
+47 -20
src/view/com/util/post-embeds/VideoEmbed.tsx
··· 5 5 import {msg, Trans} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 8 - import {clamp} from '#/lib/numbers' 8 + import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' 9 9 import {VideoEmbedInnerNative} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative' 10 - import {atoms as a} from '#/alf' 10 + import {atoms as a, useTheme} from '#/alf' 11 11 import {Button} from '#/components/Button' 12 12 import {useThrottledValue} from '#/components/hooks/useThrottledValue' 13 13 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' ··· 16 16 17 17 interface Props { 18 18 embed: AppBskyEmbedVideo.View 19 + crop?: 'none' | 'square' | 'constrained' 19 20 } 20 21 21 - export function VideoEmbed({embed}: Props) { 22 + export function VideoEmbed({embed, crop}: Props) { 23 + const t = useTheme() 22 24 const [key, setKey] = useState(0) 23 25 24 26 const renderError = useCallback( ··· 28 30 [key], 29 31 ) 30 32 31 - let aspectRatio = 16 / 9 32 - if (embed.aspectRatio) { 33 - const {width, height} = embed.aspectRatio 34 - aspectRatio = width / height 35 - aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) 33 + let aspectRatio: number | undefined 34 + const dims = embed.aspectRatio 35 + if (dims) { 36 + aspectRatio = dims.width / dims.height 37 + if (Number.isNaN(aspectRatio)) { 38 + aspectRatio = undefined 39 + } 36 40 } 37 41 42 + let constrained: number | undefined 43 + let max: number | undefined 44 + if (aspectRatio !== undefined) { 45 + const ratio = 1 / 2 // max of 1:2 ratio in feeds 46 + constrained = Math.max(aspectRatio, ratio) 47 + max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread 48 + } 49 + const cropDisabled = crop === 'none' 50 + 51 + const contents = ( 52 + <ErrorBoundary renderError={renderError} key={key}> 53 + <InnerWrapper embed={embed} /> 54 + </ErrorBoundary> 55 + ) 56 + 38 57 return ( 39 - <View 40 - style={[ 41 - a.w_full, 42 - a.rounded_md, 43 - a.overflow_hidden, 44 - {aspectRatio}, 45 - {backgroundColor: 'black'}, 46 - a.mt_xs, 47 - ]}> 48 - <ErrorBoundary renderError={renderError} key={key}> 49 - <InnerWrapper embed={embed} /> 50 - </ErrorBoundary> 58 + <View style={[a.pt_xs]}> 59 + {cropDisabled ? ( 60 + <View 61 + style={[ 62 + a.w_full, 63 + a.overflow_hidden, 64 + {aspectRatio: max ?? 1}, 65 + a.rounded_md, 66 + a.overflow_hidden, 67 + t.atoms.bg_contrast_25, 68 + ]}> 69 + {contents} 70 + </View> 71 + ) : ( 72 + <ConstrainedImage 73 + fullBleed={crop === 'square'} 74 + aspectRatio={constrained || 1}> 75 + {contents} 76 + </ConstrainedImage> 77 + )} 51 78 </View> 52 79 ) 53 80 }
+56 -34
src/view/com/util/post-embeds/VideoEmbed.web.tsx
··· 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {isFirefox} from '#/lib/browser' 8 - import {clamp} from '#/lib/numbers' 8 + import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' 9 9 import { 10 10 HLSUnsupportedError, 11 11 VideoEmbedInnerWeb, ··· 18 18 import {useActiveVideoWeb} from './ActiveVideoWebContext' 19 19 import * as VideoFallback from './VideoEmbedInner/VideoFallback' 20 20 21 - export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { 21 + export function VideoEmbed({ 22 + embed, 23 + crop, 24 + }: { 25 + embed: AppBskyEmbedVideo.View 26 + crop?: 'none' | 'square' | 'constrained' 27 + }) { 22 28 const ref = useRef<HTMLDivElement>(null) 23 29 const {active, setActive, sendPosition, currentActiveView} = 24 30 useActiveVideoWeb() ··· 52 58 [key], 53 59 ) 54 60 55 - let aspectRatio = 16 / 9 61 + let aspectRatio: number | undefined 62 + const dims = embed.aspectRatio 63 + if (dims) { 64 + aspectRatio = dims.width / dims.height 65 + if (Number.isNaN(aspectRatio)) { 66 + aspectRatio = undefined 67 + } 68 + } 56 69 57 - if (embed.aspectRatio) { 58 - const {width, height} = embed.aspectRatio 59 - // min: 3/1, max: square 60 - aspectRatio = clamp(width / height, 1 / 1, 3 / 1) 70 + let constrained: number | undefined 71 + let max: number | undefined 72 + if (aspectRatio !== undefined) { 73 + const ratio = 1 / 2 // max of 1:2 ratio in feeds 74 + constrained = Math.max(aspectRatio, ratio) 75 + max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread 61 76 } 77 + const cropDisabled = crop === 'none' 78 + 79 + const contents = ( 80 + <div 81 + ref={ref} 82 + style={{display: 'flex', flex: 1, cursor: 'default'}} 83 + onClick={evt => evt.stopPropagation()}> 84 + <ErrorBoundary renderError={renderError} key={key}> 85 + <ViewportObserver 86 + sendPosition={sendPosition} 87 + isAnyViewActive={currentActiveView !== null}> 88 + <VideoEmbedInnerWeb 89 + embed={embed} 90 + active={active} 91 + setActive={setActive} 92 + onScreen={onScreen} 93 + lastKnownTime={lastKnownTime} 94 + /> 95 + </ViewportObserver> 96 + </ErrorBoundary> 97 + </div> 98 + ) 62 99 63 100 return ( 64 - <View 65 - style={[ 66 - a.w_full, 67 - {aspectRatio}, 68 - {backgroundColor: 'black'}, 69 - a.relative, 70 - a.rounded_md, 71 - a.mt_xs, 72 - ]}> 73 - <div 74 - ref={ref} 75 - style={{display: 'flex', flex: 1, cursor: 'default'}} 76 - onClick={evt => evt.stopPropagation()}> 77 - <ErrorBoundary renderError={renderError} key={key}> 78 - <ViewportObserver 79 - sendPosition={sendPosition} 80 - isAnyViewActive={currentActiveView !== null}> 81 - <VideoEmbedInnerWeb 82 - embed={embed} 83 - active={active} 84 - setActive={setActive} 85 - onScreen={onScreen} 86 - lastKnownTime={lastKnownTime} 87 - /> 88 - </ViewportObserver> 89 - </ErrorBoundary> 90 - </div> 101 + <View style={[a.pt_xs]}> 102 + {cropDisabled ? ( 103 + <View style={[a.w_full, a.overflow_hidden, {aspectRatio: max ?? 1}]}> 104 + {contents} 105 + </View> 106 + ) : ( 107 + <ConstrainedImage 108 + fullBleed={crop === 'square'} 109 + aspectRatio={constrained || 1}> 110 + {contents} 111 + </ConstrainedImage> 112 + )} 91 113 </View> 92 114 ) 93 115 }
+10 -1
src/view/com/util/post-embeds/index.tsx
··· 237 237 if (AppBskyEmbedVideo.isView(embed)) { 238 238 return ( 239 239 <ContentHider modui={moderation?.ui('contentMedia')}> 240 - <VideoEmbed embed={embed} /> 240 + <VideoEmbed 241 + embed={embed} 242 + crop={ 243 + viewContext === PostEmbedViewContext.ThreadHighlighted 244 + ? 'none' 245 + : viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia 246 + ? 'square' 247 + : 'constrained' 248 + } 249 + /> 241 250 </ContentHider> 242 251 ) 243 252 }