diff --git a/package.json b/package.json
index ce8ba8430f..e8baad5c50 100644
--- a/package.json
+++ b/package.json
@@ -166,6 +166,7 @@
"expo-task-manager": "~14.0.9",
"expo-updates": "~29.0.14",
"expo-video": "~3.0.15",
+ "expo-video-thumbnails": "^10.0.8",
"expo-web-browser": "~15.0.10",
"fast-deep-equal": "^3.1.3",
"fast-text-encoding": "^1.0.6",
diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx
index d8d2e430f2..acd4b313fb 100644
--- a/src/components/MediaPreview.tsx
+++ b/src/components/MediaPreview.tsx
@@ -135,6 +135,7 @@ export function VideoItem({
{maxWidth: 100},
a.justify_center,
a.align_center,
+ a.rounded_xs,
]}>
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index f1827201a1..e077d21af7 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -354,13 +354,14 @@ export const ComposePost = ({
})
asset = await getVideoMetadata(file)
} else {
- // Native: Use file URI directly with minimal asset properties
- // The native compressVideo only needs uri and mimeType
+ // Native: Get video metadata using react-native-compressor
+ const {getVideoMetaData} = require('react-native-compressor')
+ const metadata = await getVideoMetaData(videoInfo.uri)
asset = {
uri: videoInfo.uri,
mimeType: videoInfo.mimeType,
- width: 0,
- height: 0,
+ width: metadata.width,
+ height: metadata.height,
}
}
diff --git a/src/view/com/composer/drafts/DraftItem.tsx b/src/view/com/composer/drafts/DraftItem.tsx
index 3c0813733c..b82c508f55 100644
--- a/src/view/com/composer/drafts/DraftItem.tsx
+++ b/src/view/com/composer/drafts/DraftItem.tsx
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from 'react'
import {Pressable, View} from 'react-native'
+import * as VideoThumbnails from 'expo-video-thumbnails'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -13,6 +14,7 @@ import {DotGrid_Stroke2_Corner0_Rounded as DotsIcon} from '#/components/icons/Do
import * as MediaPreview from '#/components/MediaPreview'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
+import {IS_WEB} from '#/env'
import {type DraftPostDisplay, type DraftSummary} from './state/schema'
import * as storage from './state/storage'
@@ -226,14 +228,25 @@ function DraftMediaPreview({post}: {post: DraftPostDisplay}) {
if (post.video?.exists && post.video.localPath) {
try {
const url = await storage.loadMediaFromLocal(post.video.localPath)
- setVideoThumbnail(url)
+ if (IS_WEB) {
+ // can't generate thumbnails on web
+ setVideoThumbnail("yep, there's a video")
+ } else {
+ console.log('generating thumbnail of ', url)
+ const thumbnail = await VideoThumbnails.getThumbnailAsync(url, {
+ time: 0,
+ quality: 0.2,
+ })
+ console.log(thumbnail)
+ setVideoThumbnail(thumbnail.uri)
+ }
} catch (e) {
// Video doesn't exist locally
}
}
}
- loadMedia()
+ void loadMedia()
}, [post.images, post.video])
// Nothing to show
@@ -249,9 +262,9 @@ function DraftMediaPreview({post}: {post: DraftPostDisplay}) {
{post.gif && (
)}
- {post.video && (
+ {post.video && videoThumbnail && (
)}
diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts
index e329e4abba..7b4e686eb7 100644
--- a/src/view/com/composer/drafts/state/api.ts
+++ b/src/view/com/composer/drafts/state/api.ts
@@ -5,6 +5,7 @@ import {type AppBskyDraftDefs, RichText} from '@atproto/api'
import {nanoid} from 'nanoid/non-secure'
import {getImageDim} from '#/lib/media/manip'
+import {mimeToExt} from '#/lib/media/video/util'
import {type ComposerImage} from '#/state/gallery'
import {type Gif} from '#/state/queries/tenor'
import {
@@ -211,7 +212,8 @@ async function serializeVideo(
// Encode mime type in the path for restoration
const mimeType = videoState.video.mimeType || 'video/mp4'
- const localRefPath = `video:${mimeType}:${nanoid()}`
+ const ext = mimeToExt(mimeType)
+ const localRefPath = `video:${mimeType}:${nanoid()}.${ext}`
localRefPaths.set(localRefPath, videoState.video.uri)
// Read caption file contents as text
diff --git a/src/view/com/composer/drafts/state/storage.ts b/src/view/com/composer/drafts/state/storage.ts
index 557e51fa6c..6aa40b44c2 100644
--- a/src/view/com/composer/drafts/state/storage.ts
+++ b/src/view/com/composer/drafts/state/storage.ts
@@ -13,7 +13,6 @@ function getMediaDirectory(): Directory {
}
function getMediaFile(localRefPath: string): File {
- // Use localRefPath as filename (URL-encoded for filesystem safety)
const safeFilename = encodeURIComponent(localRefPath)
return new File(getMediaDirectory(), safeFilename)
}
diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx
index 5610f6b258..67853f68a6 100644
--- a/src/view/com/util/fab/FABInner.tsx
+++ b/src/view/com/util/fab/FABInner.tsx
@@ -12,8 +12,7 @@ import {PressableScale} from '#/lib/custom-animations/PressableScale'
import {useHaptics} from '#/lib/haptics'
import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
import {clamp} from '#/lib/numbers'
-import {ios, useBreakpoints, useTheme} from '#/alf'
-import {atoms as a} from '#/alf'
+import {atoms as a, ios, useBreakpoints, useTheme} from '#/alf'
import {IS_WEB} from '#/env'
export interface FABProps extends ComponentProps {
@@ -61,7 +60,6 @@ export function FABInner({testID, icon, onPress, style, ...props}: FABProps) {
{backgroundColor: t.palette.primary_500},
a.align_center,
a.justify_center,
- a.shadow_sm,
style,
]}
{...props}>
diff --git a/yarn.lock b/yarn.lock
index e5265e5672..64df080005 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9495,6 +9495,11 @@ expo-updates@~29.0.14:
ignore "^5.3.1"
resolve-from "^5.0.0"
+expo-video-thumbnails@^10.0.8:
+ version "10.0.8"
+ resolved "https://registry.yarnpkg.com/expo-video-thumbnails/-/expo-video-thumbnails-10.0.8.tgz#a6313cea8e58dd0d5041d389a4fe4fa182eab176"
+ integrity sha512-nPUtP7ERLf5DY5V2A6gquRP5rP3Uvq6+FVkDwG9R3KKhFeTYkWZ5Ce1iQ7Yt5qDNQqcUcgEqmRpGCbJmn9ckKA==
+
expo-video@~3.0.15:
version "3.0.15"
resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-3.0.15.tgz#38921dab5bc877572b64728acb58097716239aa7"