about summary refs log tree commit diff
path: root/src/view/com/composer/videos/SelectVideoBtn.tsx
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-10-03 11:41:23 +0900
committerGitHub <noreply@github.com>2024-10-03 11:41:23 +0900
commitd2392d2d64c46d0fd0b6d97b3b4715b5b8c825d3 (patch)
tree6c5aaa7e42f6543112140464d084db14b4495987 /src/view/com/composer/videos/SelectVideoBtn.tsx
parentc2dac855cc61e05d21b7dbb81f1fef26f1a9e1e7 (diff)
downloadvoidsky-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/view/com/composer/videos/SelectVideoBtn.tsx')
-rw-r--r--src/view/com/composer/videos/SelectVideoBtn.tsx47
1 files changed, 36 insertions, 11 deletions
diff --git a/src/view/com/composer/videos/SelectVideoBtn.tsx b/src/view/com/composer/videos/SelectVideoBtn.tsx
index 2f2b4c3e7..bbb3d95f2 100644
--- a/src/view/com/composer/videos/SelectVideoBtn.tsx
+++ b/src/view/com/composer/videos/SelectVideoBtn.tsx
@@ -9,12 +9,14 @@ import {
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {SUPPORTED_MIME_TYPES, SupportedMimeTypes} from '#/lib/constants'
+import {BSKY_SERVICE} from '#/lib/constants'
 import {useVideoLibraryPermission} from '#/lib/hooks/usePermissions'
+import {getHostnameFromUrl} from '#/lib/strings/url-helpers'
+import {isWeb} from '#/platform/detection'
 import {isNative} from '#/platform/detection'
 import {useModalControls} from '#/state/modals'
 import {useSession} from '#/state/session'
-import {BSKY_SERVICE} from 'lib/constants'
-import {getHostnameFromUrl} from 'lib/strings/url-helpers'
 import {atoms as a, useTheme} from '#/alf'
 import {Button} from '#/components/Button'
 import {VideoClip_Stroke2_Corner0_Rounded as VideoClipIcon} from '#/components/icons/VideoClip'
@@ -58,16 +60,25 @@ export function SelectVideoBtn({onSelectVideo, disabled, setError}: Props) {
           UIImagePickerPreferredAssetRepresentationMode.Current,
       })
       if (response.assets && response.assets.length > 0) {
-        if (isNative) {
-          if (typeof response.assets[0].duration !== 'number')
-            throw Error('Asset is not a video')
-          if (response.assets[0].duration > VIDEO_MAX_DURATION) {
-            setError(_(msg`Videos must be less than 60 seconds long`))
-            return
-          }
-        }
+        const asset = response.assets[0]
         try {
-          onSelectVideo(response.assets[0])
+          if (isWeb) {
+            // compression step on native converts to mp4, so no need to check there
+            const mimeType = getMimeType(asset)
+            if (
+              !SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)
+            ) {
+              throw Error(_(msg`Unsupported video type: ${mimeType}`))
+            }
+          } else {
+            if (typeof asset.duration !== 'number') {
+              throw Error('Asset is not a video')
+            }
+            if (asset.duration > VIDEO_MAX_DURATION) {
+              throw Error(_(msg`Videos must be less than 60 seconds long`))
+            }
+          }
+          onSelectVideo(asset)
         } catch (err) {
           if (err instanceof Error) {
             setError(err.message)
@@ -132,3 +143,17 @@ function VerifyEmailPrompt({control}: {control: Prompt.PromptControlProps}) {
     />
   )
 }
+
+function getMimeType(asset: ImagePickerAsset) {
+  if (isWeb) {
+    const [mimeType] = asset.uri.slice('data:'.length).split(';base64,')
+    if (!mimeType) {
+      throw new Error('Could not determine mime type')
+    }
+    return mimeType
+  }
+  if (!asset.mimeType) {
+    throw new Error('Could not determine mime type')
+  }
+  return asset.mimeType
+}