···11export type CompressedVideo = {
22 uri: string
33+ mimeType: string
34 size: number
45 // web only, can fall back to uri if missing
56 bytes?: ArrayBuffer
+13
src/state/queries/video/util.ts
···2424 })
2525 }, [])
2626}
2727+2828+export function mimeToExt(mimeType: string) {
2929+ switch (mimeType) {
3030+ case 'video/mp4':
3131+ return 'mp4'
3232+ case 'video/webm':
3333+ return 'webm'
3434+ case 'video/mpeg':
3535+ return 'mpeg'
3636+ default:
3737+ throw new Error(`Unsupported mime type: ${mimeType}`)
3838+ }
3939+}
+14-3
src/state/queries/video/video-upload.ts
···11import {createUploadTask, FileSystemUploadType} from 'expo-file-system'
22import {AppBskyVideoDefs} from '@atproto/api'
33+import {msg} from '@lingui/macro'
44+import {useLingui} from '@lingui/react'
35import {useMutation} from '@tanstack/react-query'
46import {nanoid} from 'nanoid/non-secure'
5768import {cancelable} from '#/lib/async/cancelable'
99+import {ServerError} from '#/lib/media/video/errors'
710import {CompressedVideo} from '#/lib/media/video/types'
88-import {createVideoEndpointUrl} from '#/state/queries/video/util'
1111+import {createVideoEndpointUrl, mimeToExt} from '#/state/queries/video/util'
912import {useAgent, useSession} from '#/state/session'
1013import {getServiceAuthAudFromUrl} from 'lib/strings/url-helpers'
1114···2225}) => {
2326 const {currentAccount} = useSession()
2427 const agent = useAgent()
2828+ const {_} = useLingui()
25292630 return useMutation({
2731 mutationKey: ['video', 'upload'],
2832 mutationFn: cancelable(async (video: CompressedVideo) => {
2933 const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', {
3034 did: currentAccount!.did,
3131- name: `${nanoid(12)}.mp4`,
3535+ name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`,
3236 })
33373438 const serviceAuthAud = getServiceAuthAudFromUrl(agent.dispatchUrl)
···5054 video.uri,
5155 {
5256 headers: {
5353- 'content-type': 'video/mp4',
5757+ 'content-type': video.mimeType,
5458 Authorization: `Bearer ${serviceAuth.token}`,
5559 },
5660 httpMethod: 'POST',
···6569 }
66706771 const responseBody = JSON.parse(res.body) as AppBskyVideoDefs.JobStatus
7272+7373+ if (!responseBody.jobId) {
7474+ throw new ServerError(
7575+ responseBody.error || _(msg`Failed to upload video`),
7676+ )
7777+ }
7878+6879 return responseBody
6980 }, signal),
7081 onError,
+13-8
src/state/queries/video/video-upload.web.ts
···11import {AppBskyVideoDefs} from '@atproto/api'
22+import {msg} from '@lingui/macro'
33+import {useLingui} from '@lingui/react'
24import {useMutation} from '@tanstack/react-query'
35import {nanoid} from 'nanoid/non-secure'
4657import {cancelable} from '#/lib/async/cancelable'
88+import {ServerError} from '#/lib/media/video/errors'
69import {CompressedVideo} from '#/lib/media/video/types'
77-import {createVideoEndpointUrl} from '#/state/queries/video/util'
1010+import {createVideoEndpointUrl, mimeToExt} from '#/state/queries/video/util'
811import {useAgent, useSession} from '#/state/session'
912import {getServiceAuthAudFromUrl} from 'lib/strings/url-helpers'
1013···2124}) => {
2225 const {currentAccount} = useSession()
2326 const agent = useAgent()
2727+ const {_} = useLingui()
24282529 return useMutation({
2630 mutationKey: ['video', 'upload'],
2731 mutationFn: cancelable(async (video: CompressedVideo) => {
2832 const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', {
2933 did: currentAccount!.did,
3030- name: `${nanoid(12)}.mp4`, // @TODO: make sure it's always mp4'
3434+ name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`,
3135 })
32363337 const serviceAuthAud = getServiceAuthAudFromUrl(agent.dispatchUrl)
···6367 xhr.responseText,
6468 ) as AppBskyVideoDefs.JobStatus
6569 resolve(uploadRes)
6666- onSuccess(uploadRes)
6770 } else {
6868- reject()
6969- onError(new Error('Failed to upload video'))
7171+ reject(new ServerError(_(msg`Failed to upload video`)))
7072 }
7173 }
7274 xhr.onerror = () => {
7373- reject()
7474- onError(new Error('Failed to upload video'))
7575+ reject(new ServerError(_(msg`Failed to upload video`)))
7576 }
7677 xhr.open('POST', uri)
7777- xhr.setRequestHeader('Content-Type', 'video/mp4')
7878+ xhr.setRequestHeader('Content-Type', video.mimeType)
7879 xhr.setRequestHeader('Authorization', `Bearer ${serviceAuth.token}`)
7980 xhr.send(bytes)
8081 },
8182 )
8383+8484+ if (!res.jobId) {
8585+ throw new ServerError(res.error || _(msg`Failed to upload video`))
8686+ }
82878388 return res
8489 }, signal),