From d289c329e2fd714a4b07aa21e98dd7ded0040e94 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Tue, 11 Aug 2026 15:01:35 -0400 Subject: [PATCH] Polish video upload progress completion --- src/view/com/composer/Composer.tsx | 31 ++++++++++++------- src/view/com/composer/state/video.ts | 8 +++-- .../com/composer/state/videoProgress.test.ts | 11 +++++++ src/view/com/composer/state/videoProgress.ts | 14 +++++++++ 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 5cb96bceb5..f9129c56e9 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -125,6 +125,7 @@ import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as EmojiPicker from '#/components/EmojiPicker' +import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' @@ -2717,17 +2718,25 @@ function VideoUploadToolbar({state}: {state: VideoState}) { return ( - + {state.status === 'done' ? ( + + + + ) : ( + + )} {text} ) diff --git a/src/view/com/composer/state/video.ts b/src/view/com/composer/state/video.ts index 5becb1568d..eeca7dca47 100644 --- a/src/view/com/composer/state/video.ts +++ b/src/view/com/composer/state/video.ts @@ -17,7 +17,11 @@ import {uploadVideo} from '#/lib/media/video/upload' import {createVideoAgent} from '#/lib/media/video/util' import {isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' -import {advanceVideoProgress, videoProgressForPhase} from './videoProgress' +import { + advanceVideoProgress, + didSkipVideoCompression, + videoProgressForPhase, +} from './videoProgress' type CaptionsTrack = {lang: string; file: File} @@ -331,7 +335,7 @@ export async function processVideo( dispatch({ type: 'compressing_to_uploading', video, - compressionSkipped: video.passthroughReason !== undefined, + compressionSkipped: didSkipVideoCompression(video.passthroughReason), signal, }) diff --git a/src/view/com/composer/state/videoProgress.test.ts b/src/view/com/composer/state/videoProgress.test.ts index 53e662c3fe..92d8fc0835 100644 --- a/src/view/com/composer/state/videoProgress.test.ts +++ b/src/view/com/composer/state/videoProgress.test.ts @@ -1,9 +1,20 @@ import { advanceVideoProgress, + didSkipVideoCompression, videoProgressForPhase, videoProgressWithinPhase, } from './videoProgress' +describe('didSkipVideoCompression', () => { + it('distinguishes a real skip from a failed compression attempt', () => { + expect(didSkipVideoCompression(undefined)).toBe(false) + expect(didSkipVideoCompression('below-byte-threshold')).toBe(true) + expect(didSkipVideoCompression('no-webcodecs')).toBe(true) + expect(didSkipVideoCompression('gif')).toBe(true) + expect(didSkipVideoCompression('compress-error-fallback')).toBe(false) + }) +}) + describe('videoProgressForPhase', () => { it('maps each phase onto one continuous timeline', () => { expect(videoProgressForPhase('compressing', 0)).toBe(0) diff --git a/src/view/com/composer/state/videoProgress.ts b/src/view/com/composer/state/videoProgress.ts index 8d43064c2f..bee9be68fa 100644 --- a/src/view/com/composer/state/videoProgress.ts +++ b/src/view/com/composer/state/videoProgress.ts @@ -1,3 +1,5 @@ +import {type VideoCompressSkipReason} from '#/lib/media/video/types' + export type VideoProgressPhase = | 'compressing' | 'uploading' @@ -14,6 +16,18 @@ const PHASE_RANGES: Record = { processing: [0.5, 1], } +export function didSkipVideoCompression( + passthroughReason: VideoCompressSkipReason | undefined, +): boolean { + // The web compressor can return the original file after reporting partial + // compression progress. Keep that case on the post-compression upload band + // so the global indicator cannot jump backwards. + return ( + passthroughReason !== undefined && + passthroughReason !== 'compress-error-fallback' + ) +} + export function videoProgressForPhase( phase: VideoProgressPhase, phaseProgress: number,