Bluesky app fork with some witchin' additions 💫

Fix video letterboxing issue (#9339)

* give video a black background on web

* Video crop on web tweaks (#9371)

* Remove video embed crop option to reduce confusion

* Improve default thumb and border on web

---------

Co-authored-by: Eric Bailey <git@esb.lol>

authored by samuel.fm

Eric Bailey and committed by
GitHub
1caac024 24c96ac8

+30 -72
-2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
··· 7 7 8 8 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 9 9 import {atoms as a} from '#/alf' 10 - import {MediaInsetBorder} from '#/components/MediaInsetBorder' 11 10 import * as BandwidthEstimate from './bandwidth-estimate' 12 11 import {Controls} from './web-controls/VideoControls' 13 12 ··· 102 101 hasSubtitleTrack={hasSubtitleTrack} 103 102 /> 104 103 </div> 105 - <MediaInsetBorder /> 106 104 </View> 107 105 ) 108 106 }
+9 -29
src/components/Post/Embed/VideoEmbed/index.tsx
··· 7 7 8 8 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 9 9 import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' 10 - import {atoms as a, useTheme} from '#/alf' 10 + import {atoms as a} 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' 20 19 } 21 20 22 - export function VideoEmbed({embed, crop}: Props) { 23 - const t = useTheme() 21 + export function VideoEmbed({embed}: Props) { 24 22 const [key, setKey] = useState(0) 25 23 26 24 const renderError = useCallback( ··· 40 38 } 41 39 42 40 let constrained: number | undefined 43 - let max: number | undefined 44 41 if (aspectRatio !== undefined) { 45 42 const ratio = 1 / 2 // max of 1:2 ratio in feeds 46 43 constrained = Math.max(aspectRatio, ratio) 47 - max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread 48 44 } 49 - const cropDisabled = crop === 'none' 50 45 51 46 const contents = ( 52 47 <ErrorBoundary renderError={renderError} key={key}> ··· 56 51 57 52 return ( 58 53 <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 - // slightly smaller max height than images 76 - // images use 16 / 9, for reference 77 - minMobileAspectRatio={14 / 9}> 78 - {contents} 79 - </ConstrainedImage> 80 - )} 54 + <ConstrainedImage 55 + aspectRatio={constrained || 1} 56 + // slightly smaller max height than images 57 + // images use 16 / 9, for reference 58 + minMobileAspectRatio={14 / 9}> 59 + {contents} 60 + </ConstrainedImage> 81 61 </View> 82 62 ) 83 63 }
+15 -33
src/components/Post/Embed/VideoEmbed/index.web.tsx
··· 17 17 import {atoms as a, useTheme} from '#/alf' 18 18 import {useIsWithinMessage} from '#/components/dms/MessageContext' 19 19 import {useFullscreen} from '#/components/hooks/useFullscreen' 20 + import {MediaInsetBorder} from '#/components/MediaInsetBorder' 20 21 import { 21 22 HLSUnsupportedError, 22 23 VideoEmbedInnerWeb, ··· 25 26 import {useActiveVideoWeb} from './ActiveVideoWebContext' 26 27 import * as VideoFallback from './VideoEmbedInner/VideoFallback' 27 28 28 - export function VideoEmbed({ 29 - embed, 30 - crop, 31 - }: { 32 - embed: AppBskyEmbedVideo.View 33 - crop?: 'none' | 'square' | 'constrained' 34 - }) { 29 + export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { 35 30 const t = useTheme() 36 31 const ref = useRef<HTMLDivElement>(null) 37 32 const {active, setActive, sendPosition, currentActiveView} = ··· 76 71 } 77 72 78 73 let constrained: number | undefined 79 - let max: number | undefined 80 74 if (aspectRatio !== undefined) { 81 75 const ratio = 1 / 2 // max of 1:2 ratio in feeds 82 76 constrained = Math.max(aspectRatio, ratio) 83 - max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread 84 77 } 85 - const cropDisabled = crop === 'none' 86 78 87 79 const contents = ( 88 80 <div ··· 91 83 display: 'flex', 92 84 flex: 1, 93 85 cursor: 'default', 86 + backgroundColor: t.palette.black, 94 87 backgroundImage: `url(${embed.thumbnail})`, 95 - backgroundSize: 'cover', 88 + backgroundSize: 'contain', 89 + backgroundPosition: 'center', 90 + backgroundRepeat: 'no-repeat', 96 91 }} 97 92 onClick={evt => evt.stopPropagation()}> 98 93 <ErrorBoundary renderError={renderError} key={key}> ··· 114 109 <ViewportObserver 115 110 sendPosition={sendPosition} 116 111 isAnyViewActive={currentActiveView !== null}> 117 - {cropDisabled ? ( 118 - <View 119 - style={[ 120 - a.w_full, 121 - a.overflow_hidden, 122 - {aspectRatio: max ?? 1}, 123 - a.rounded_md, 124 - a.overflow_hidden, 125 - t.atoms.bg_contrast_25, 126 - ]}> 127 - {contents} 128 - </View> 129 - ) : ( 130 - <ConstrainedImage 131 - fullBleed={crop === 'square'} 132 - aspectRatio={constrained || 1} 133 - // slightly smaller max height than images 134 - // images use 16 / 9, for reference 135 - minMobileAspectRatio={14 / 9}> 136 - {contents} 137 - </ConstrainedImage> 138 - )} 112 + <ConstrainedImage 113 + fullBleed 114 + aspectRatio={constrained || 1} 115 + // slightly smaller max height than images 116 + // images use 16 / 9, for reference 117 + minMobileAspectRatio={14 / 9}> 118 + {contents} 119 + <MediaInsetBorder /> 120 + </ConstrainedImage> 139 121 </ViewportObserver> 140 122 </View> 141 123 )
+1 -1
src/components/Post/Embed/index.tsx
··· 112 112 <ContentHider 113 113 modui={rest.moderation?.ui('contentMedia')} 114 114 activeStyle={[a.mt_sm]}> 115 - <VideoEmbed embed={embed.view} crop="constrained" /> 115 + <VideoEmbed embed={embed.view} /> 116 116 </ContentHider> 117 117 ) 118 118 }
+5 -7
src/view/com/util/images/AutoSizedImage.tsx
··· 13 13 import {type Dimensions} from '#/lib/media/types' 14 14 import {isNative} from '#/platform/detection' 15 15 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' 16 - import {atoms as a, useBreakpoints, useTheme} from '#/alf' 16 + import {atoms as a, useTheme} from '#/alf' 17 17 import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal' 18 18 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 19 19 import {Text} from '#/components/Typography' ··· 30 30 children: React.ReactNode 31 31 }) { 32 32 const t = useTheme() 33 - const {gtMobile} = useBreakpoints() 34 33 /** 35 34 * Computed as a % value to apply as `paddingTop`, this basically controls 36 35 * the height of the image. 37 36 */ 38 37 const outerAspectRatio = React.useMemo<DimensionValue>(() => { 39 - const ratio = 40 - isNative || !gtMobile 41 - ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box 42 - : Math.min(1 / aspectRatio, 1) // 1:1 bounding box 38 + const ratio = isNative 39 + ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box 40 + : Math.min(1 / aspectRatio, 1) // 1:1 bounding box 43 41 return `${ratio * 100}%` 44 - }, [aspectRatio, gtMobile, minMobileAspectRatio]) 42 + }, [aspectRatio, minMobileAspectRatio]) 45 43 46 44 return ( 47 45 <View style={[a.w_full]}>