From b36a2e33d301396c3910aa4f44d77ca265844228 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 28 Jan 2026 17:50:09 +0200 Subject: [PATCH] get videos working on native --- package.json | 1 + src/components/MediaPreview.tsx | 1 + src/view/com/composer/Composer.tsx | 9 ++++---- src/view/com/composer/drafts/DraftItem.tsx | 21 +++++++++++++++---- src/view/com/composer/drafts/state/api.ts | 4 +++- src/view/com/composer/drafts/state/storage.ts | 1 - src/view/com/util/fab/FABInner.tsx | 4 +--- yarn.lock | 5 +++++ 8 files changed, 33 insertions(+), 13 deletions(-) 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"