about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-01-17 20:09:44 -0600
committerGitHub <noreply@github.com>2025-01-18 02:09:44 +0000
commit3e0ac0a0668b5906d7b81dbc075cfd04ba89911c (patch)
tree5a113c11d8084ab0f9a1dd4edbae47b6e620c621 /src
parent6f3f11671d526c5d9a454c46f6daf305b588ef4c (diff)
downloadvoidsky-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.tsx67
-rw-r--r--src/view/com/util/post-embeds/VideoEmbed.web.tsx90
-rw-r--r--src/view/com/util/post-embeds/index.tsx11
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>
     )
   }