feat(drafts): skip re-compression when restoring pre-compressed videos

Update restoreVideo() to:
- Use restore_video_to_post action to avoid marking composer dirty
- Check compressed flag from stored video metadata
- If video is pre-compressed, skip compression and go straight to upload
- Pass localRefPath through to preserve video identity

This prevents unnecessary re-compression when restoring drafts with
videos that were already compressed before saving.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-01-30 20:49:00 +02:00
parent fb459ad61f
commit fd769e32d4
+173 -53
View File
@@ -69,6 +69,7 @@ import {
import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {type CompressedVideo} from '#/lib/media/video/types'
import {mimeToExt} from '#/lib/media/video/util'
import {useCallOnce} from '#/lib/once'
import {type NavigationProp} from '#/lib/routes/types'
@@ -164,6 +165,7 @@ import {
NO_VIDEO,
type NoVideoState,
processVideo,
uploadPrecompressedVideo,
type VideoState,
} from './state/video'
import {type TextInputRef} from './text-input/TextInput.types'
@@ -362,10 +364,14 @@ export const ComposePost = ({
postId,
videoUri: videoInfo.uri,
altText: videoInfo.altText,
compressed: videoInfo.compressed,
captionCount: videoInfo.captions.length,
})
let asset: ImagePickerAsset
let videoUri = videoInfo.uri
let videoSize = 0
let videoBytes: ArrayBuffer | undefined
if (IS_WEB) {
// Web: Convert blob URL to a File, then get video metadata (returns data URL)
@@ -375,8 +381,11 @@ export const ComposePost = ({
type: videoInfo.mimeType,
})
asset = await getVideoMetadata(file)
// For web, the data URL is used directly
videoUri = asset.uri
videoSize = blob.size
videoBytes = await blob.arrayBuffer()
} else {
let uri = videoInfo.uri
if (IS_ANDROID) {
// Android: expo-file-system double-encodes filenames with special chars.
// The file exists, but react-native-compressor's MediaMetadataRetriever
@@ -392,79 +401,190 @@ export const ComposePost = ({
source: videoInfo.uri,
temp: tempFile.uri,
})
uri = tempFile.uri
videoUri = tempFile.uri
videoSize = tempFile.size ?? 0
} else {
// iOS
const sourceFile = new FileSystem.File(videoInfo.uri)
videoSize = sourceFile.size ?? 0
}
asset = await getVideoMetadata(uri)
asset = await getVideoMetadata(videoUri)
}
// Start video processing using existing flow
const abortController = new AbortController()
composerDispatch({
type: 'update_post',
postId,
postAction: {
type: 'embed_add_video',
asset,
abortController,
},
})
// Restore alt text immediately
if (videoInfo.altText) {
if (videoInfo.compressed) {
// Video is already compressed - skip compression step
logger.debug('restoreVideo: using pre-compressed video', {
postId,
localRefPath: videoInfo.localRefPath,
})
// Create CompressedVideo object from stored data
const compressedVideo: CompressedVideo = {
uri: videoUri,
mimeType: videoInfo.mimeType,
size: videoSize,
bytes: videoBytes,
}
// Add video to post (at compressing status initially)
composerDispatch({
type: 'update_post',
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_add_video',
asset,
abortController,
localRefPath: videoInfo.localRefPath,
},
})
// Manually update to uploading state with the compressed video
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction: {
type: 'update_alt_text',
altText: videoInfo.altText,
type: 'compressing_to_uploading',
video: compressedVideo,
signal: abortController.signal,
},
},
})
}
// Restore captions (web only - captions use File objects)
if (IS_WEB && videoInfo.captions.length > 0) {
const captionTracks = videoInfo.captions.map(c => ({
lang: c.lang,
file: new File([c.content], `caption-${c.lang}.vtt`, {
type: 'text/vtt',
}),
}))
composerDispatch({
type: 'update_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction: {
type: 'update_captions',
updater: () => captionTracks,
signal: abortController.signal,
},
},
})
}
// Start video compression and upload
processVideo(
asset,
videoAction => {
// Restore alt text
if (videoInfo.altText) {
composerDispatch({
type: 'update_post',
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction,
videoAction: {
type: 'update_alt_text',
altText: videoInfo.altText,
signal: abortController.signal,
},
},
})
},
agent,
currentDid,
abortController.signal,
_,
)
}
// Restore captions (web only)
if (IS_WEB && videoInfo.captions.length > 0) {
const captionTracks = videoInfo.captions.map(c => ({
lang: c.lang,
file: new File([c.content], `caption-${c.lang}.vtt`, {
type: 'text/vtt',
}),
}))
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction: {
type: 'update_captions',
updater: () => captionTracks,
signal: abortController.signal,
},
},
})
}
// Start upload (skip compression)
uploadPrecompressedVideo(
compressedVideo,
videoAction => {
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction,
},
})
},
agent,
currentDid,
abortController.signal,
_,
)
} else {
// Video not compressed yet - start from beginning
logger.debug('restoreVideo: starting full compression', {
postId,
localRefPath: videoInfo.localRefPath,
})
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_add_video',
asset,
abortController,
localRefPath: videoInfo.localRefPath,
},
})
// Restore alt text immediately
if (videoInfo.altText) {
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction: {
type: 'update_alt_text',
altText: videoInfo.altText,
signal: abortController.signal,
},
},
})
}
// Restore captions (web only - captions use File objects)
if (IS_WEB && videoInfo.captions.length > 0) {
const captionTracks = videoInfo.captions.map(c => ({
lang: c.lang,
file: new File([c.content], `caption-${c.lang}.vtt`, {
type: 'text/vtt',
}),
}))
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction: {
type: 'update_captions',
updater: () => captionTracks,
signal: abortController.signal,
},
},
})
}
// Start video compression and upload
processVideo(
asset,
videoAction => {
composerDispatch({
type: 'restore_video_to_post',
postId,
postAction: {
type: 'embed_update_video',
videoAction,
},
})
},
agent,
currentDid,
abortController.signal,
_,
)
}
} catch (e) {
logger.error('Failed to restore video from draft', {
postId,