diff options
author | dan <dan.abramov@gmail.com> | 2024-10-03 11:41:23 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-03 11:41:23 +0900 |
commit | d2392d2d64c46d0fd0b6d97b3b4715b5b8c825d3 (patch) | |
tree | 6c5aaa7e42f6543112140464d084db14b4495987 /src/state/queries/video/video-upload.ts | |
parent | c2dac855cc61e05d21b7dbb81f1fef26f1a9e1e7 (diff) | |
download | voidsky-d2392d2d64c46d0fd0b6d97b3b4715b5b8c825d3.tar.zst |
Refactor video uploads (#5570)
* Remove unused video field * Stop exposing video dispatch * Move cancellation out of the reducer * Make useUploadStatusQuery controlled by jobId * Rename SetStatus to SetProcessing This action only has one callsite and it's always passing "processing". * Move jobId into video reducer state * Make cancellation scoped * Inline useCompressVideoMutation * Move processVideo down the file * Extract getErrorMessage * useServiceAuthToken -> getServiceAuthToken * useVideoAgent -> createVideoAgent * useVideoUploadLimits -> getVideoUploadLimits * useUploadVideoMutation -> uploadVideo * Use async/await in processVideo * Inline onVideoCompressed into processVideo * Use async/await for uploadVideo * Factor out error messages * Guard dispatch with signal This lets us remove the scattered signal checks around dispatch. * Move job polling out of RQ * Handle poll failures * Remove unnecessary guards * Slightly more accurate condition * Move initVideoUri handling out of the hook * Remove dead argument It wasn't being used before either. * Remove unused detailed status This isn't being used because we're only respecting that state variable when isProcessing=true, but isProcessing is always false during video upload. If we want to re-add this later, it should really just be derived from the reducer state. * Harden the video reducer * Tie all spawned work to a signal * Preserve asset/media for nicer error state * Rename actions to match states * Inline useUploadVideo This abstraction is getting in the way of some future work. * Move MIME check to the only place that handles it
Diffstat (limited to 'src/state/queries/video/video-upload.ts')
-rw-r--r-- | src/state/queries/video/video-upload.ts | 111 |
1 files changed, 57 insertions, 54 deletions
diff --git a/src/state/queries/video/video-upload.ts b/src/state/queries/video/video-upload.ts index 170b53890..46f24a58b 100644 --- a/src/state/queries/video/video-upload.ts +++ b/src/state/queries/video/video-upload.ts @@ -1,76 +1,79 @@ import {createUploadTask, FileSystemUploadType} from 'expo-file-system' -import {AppBskyVideoDefs} from '@atproto/api' +import {AppBskyVideoDefs, BskyAgent} from '@atproto/api' +import {I18n} from '@lingui/core' import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useMutation} from '@tanstack/react-query' import {nanoid} from 'nanoid/non-secure' -import {cancelable} from '#/lib/async/cancelable' +import {AbortError} from '#/lib/async/cancelable' import {ServerError} from '#/lib/media/video/errors' import {CompressedVideo} from '#/lib/media/video/types' import {createVideoEndpointUrl, mimeToExt} from '#/state/queries/video/util' -import {useSession} from '#/state/session' -import {useServiceAuthToken, useVideoUploadLimits} from './video-upload.shared' +import {getServiceAuthToken, getVideoUploadLimits} from './video-upload.shared' -export const useUploadVideoMutation = ({ - onSuccess, - onError, +export async function uploadVideo({ + video, + agent, + did, setProgress, signal, + _, }: { - onSuccess: (response: AppBskyVideoDefs.JobStatus) => void - onError: (e: any) => void + video: CompressedVideo + agent: BskyAgent + did: string setProgress: (progress: number) => void signal: AbortSignal -}) => { - const {currentAccount} = useSession() - const getToken = useServiceAuthToken({ + _: I18n['_'] +}) { + if (signal.aborted) { + throw new AbortError() + } + await getVideoUploadLimits(agent, _) + + const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', { + did, + name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`, + }) + + if (signal.aborted) { + throw new AbortError() + } + const token = await getServiceAuthToken({ + agent, lxm: 'com.atproto.repo.uploadBlob', exp: Date.now() / 1000 + 60 * 30, // 30 minutes }) - const checkLimits = useVideoUploadLimits() - const {_} = useLingui() + const uploadTask = createUploadTask( + uri, + video.uri, + { + headers: { + 'content-type': video.mimeType, + Authorization: `Bearer ${token}`, + }, + httpMethod: 'POST', + uploadType: FileSystemUploadType.BINARY_CONTENT, + }, + p => setProgress(p.totalBytesSent / p.totalBytesExpectedToSend), + ) - return useMutation({ - mutationKey: ['video', 'upload'], - mutationFn: cancelable(async (video: CompressedVideo) => { - await checkLimits() + if (signal.aborted) { + throw new AbortError() + } + const res = await uploadTask.uploadAsync() - const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', { - did: currentAccount!.did, - name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`, - }) + if (!res?.body) { + throw new Error('No response') + } - const uploadTask = createUploadTask( - uri, - video.uri, - { - headers: { - 'content-type': video.mimeType, - Authorization: `Bearer ${await getToken()}`, - }, - httpMethod: 'POST', - uploadType: FileSystemUploadType.BINARY_CONTENT, - }, - p => setProgress(p.totalBytesSent / p.totalBytesExpectedToSend), - ) - const res = await uploadTask.uploadAsync() + const responseBody = JSON.parse(res.body) as AppBskyVideoDefs.JobStatus - if (!res?.body) { - throw new Error('No response') - } + if (!responseBody.jobId) { + throw new ServerError(responseBody.error || _(msg`Failed to upload video`)) + } - const responseBody = JSON.parse(res.body) as AppBskyVideoDefs.JobStatus - - if (!responseBody.jobId) { - throw new ServerError( - responseBody.error || _(msg`Failed to upload video`), - ) - } - - return responseBody - }, signal), - onError, - onSuccess, - }) + if (signal.aborted) { + throw new AbortError() + } + return responseBody } |