Polish video upload progress completion

This commit is contained in:
vineyardbovines
2026-08-11 15:01:35 -04:00
committed by Samuel Newman
parent 4cbd643964
commit d289c329e2
4 changed files with 51 additions and 13 deletions
+20 -11
View File
@@ -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 (
<ToolbarWrapper style={[a.flex_row, a.align_center, {paddingVertical: 5}]}>
<ProgressCircle
size={30}
borderWidth={1}
borderColor={t.atoms.border_contrast_low.borderColor}
color={
state.status === 'error'
? t.palette.negative_500
: t.palette.primary_500
}
progress={wheelProgress}
/>
{state.status === 'done' ? (
<Animated.View
entering={ZoomIn.duration(300)}
style={[a.align_center, a.justify_center, {height: 30, width: 30}]}>
<CircleCheckIcon size="lg" fill={t.palette.primary_500} />
</Animated.View>
) : (
<ProgressCircle
size={30}
borderWidth={1}
borderColor={t.atoms.border_contrast_low.borderColor}
color={
state.status === 'error'
? t.palette.negative_500
: t.palette.primary_500
}
progress={wheelProgress}
/>
)}
<Text style={[a.font_semi_bold, a.ml_sm]}>{text}</Text>
</ToolbarWrapper>
)
+6 -2
View File
@@ -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,
})
@@ -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)
@@ -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<VideoProgressPhase, [number, number]> = {
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,