Rename some files and variables (#5587)
* Move composer reducers together * videoUploadState -> videoState * Inline videoDispatch
This commit is contained in:
@@ -82,13 +82,6 @@ import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {Gif} from '#/state/queries/tenor'
|
||||
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate'
|
||||
import {threadgateViewToAllowUISetting} from '#/state/queries/threadgate/util'
|
||||
import {NO_VIDEO, NoVideoState} from '#/state/queries/video/video'
|
||||
import {
|
||||
processVideo,
|
||||
VideoAction,
|
||||
VideoState,
|
||||
VideoState as VideoUploadState,
|
||||
} from '#/state/queries/video/video'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {ComposerOpts} from '#/state/shell/composer'
|
||||
@@ -123,7 +116,8 @@ import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {Text as NewText} from '#/components/Typography'
|
||||
import {composerReducer, createComposerState} from './state'
|
||||
import {composerReducer, createComposerState} from './state/composer'
|
||||
import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
|
||||
|
||||
const MAX_IMAGES = 4
|
||||
|
||||
@@ -200,16 +194,10 @@ export const ComposePost = ({
|
||||
createComposerState,
|
||||
)
|
||||
|
||||
let videoUploadState: VideoState | NoVideoState = NO_VIDEO
|
||||
let videoState: VideoState | NoVideoState = NO_VIDEO
|
||||
if (composerState.embed.media?.type === 'video') {
|
||||
videoUploadState = composerState.embed.media.video
|
||||
videoState = composerState.embed.media.video
|
||||
}
|
||||
const videoDispatch = useCallback(
|
||||
(videoAction: VideoAction) => {
|
||||
dispatch({type: 'embed_update_video', videoAction})
|
||||
},
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const selectVideo = React.useCallback(
|
||||
(asset: ImagePickerAsset) => {
|
||||
@@ -217,14 +205,14 @@ export const ComposePost = ({
|
||||
dispatch({type: 'embed_add_video', asset, abortController})
|
||||
processVideo(
|
||||
asset,
|
||||
videoDispatch,
|
||||
videoAction => dispatch({type: 'embed_update_video', videoAction}),
|
||||
agent,
|
||||
currentDid,
|
||||
abortController.signal,
|
||||
_,
|
||||
)
|
||||
},
|
||||
[_, videoDispatch, agent, currentDid],
|
||||
[_, agent, currentDid],
|
||||
)
|
||||
|
||||
// Whenever we receive an initial video uri, we should immediately run compression if necessary
|
||||
@@ -235,23 +223,26 @@ export const ComposePost = ({
|
||||
}, [initVideoUri, selectVideo])
|
||||
|
||||
const clearVideo = React.useCallback(() => {
|
||||
videoUploadState.abortController.abort()
|
||||
videoState.abortController.abort()
|
||||
dispatch({type: 'embed_remove_video'})
|
||||
}, [videoUploadState.abortController, dispatch])
|
||||
}, [videoState.abortController, dispatch])
|
||||
|
||||
const updateVideoDimensions = useCallback(
|
||||
(width: number, height: number) => {
|
||||
videoDispatch({
|
||||
type: 'update_dimensions',
|
||||
width,
|
||||
height,
|
||||
signal: videoUploadState.abortController.signal,
|
||||
dispatch({
|
||||
type: 'embed_update_video',
|
||||
videoAction: {
|
||||
type: 'update_dimensions',
|
||||
width,
|
||||
height,
|
||||
signal: videoState.abortController.signal,
|
||||
},
|
||||
})
|
||||
},
|
||||
[videoUploadState.abortController, videoDispatch],
|
||||
[videoState.abortController],
|
||||
)
|
||||
|
||||
const hasVideo = Boolean(videoUploadState.asset || videoUploadState.video)
|
||||
const hasVideo = Boolean(videoState.asset || videoState.video)
|
||||
|
||||
const [publishOnUpload, setPublishOnUpload] = useState(false)
|
||||
|
||||
@@ -288,7 +279,7 @@ export const ComposePost = ({
|
||||
graphemeLength > 0 ||
|
||||
images.length !== 0 ||
|
||||
extGif ||
|
||||
videoUploadState.status !== 'idle'
|
||||
videoState.status !== 'idle'
|
||||
) {
|
||||
closeAllDialogs()
|
||||
Keyboard.dismiss()
|
||||
@@ -303,7 +294,7 @@ export const ComposePost = ({
|
||||
closeAllDialogs,
|
||||
discardPromptControl,
|
||||
onClose,
|
||||
videoUploadState.status,
|
||||
videoState.status,
|
||||
])
|
||||
|
||||
useImperativeHandle(cancelRef, () => ({onPressCancel}))
|
||||
@@ -400,8 +391,8 @@ export const ComposePost = ({
|
||||
|
||||
if (
|
||||
!finishedUploading &&
|
||||
videoUploadState.asset &&
|
||||
videoUploadState.status !== 'done'
|
||||
videoState.asset &&
|
||||
videoState.status !== 'done'
|
||||
) {
|
||||
setPublishOnUpload(true)
|
||||
return
|
||||
@@ -414,7 +405,7 @@ export const ComposePost = ({
|
||||
images.length === 0 &&
|
||||
!extLink &&
|
||||
!quote &&
|
||||
videoUploadState.status === 'idle'
|
||||
videoState.status === 'idle'
|
||||
) {
|
||||
setError(_(msg`Did you want to say anything?`))
|
||||
return
|
||||
@@ -442,14 +433,14 @@ export const ComposePost = ({
|
||||
onStateChange: setProcessingState,
|
||||
langs: toPostLanguages(langPrefs.postLanguage),
|
||||
video:
|
||||
videoUploadState.status === 'done'
|
||||
videoState.status === 'done'
|
||||
? {
|
||||
blobRef: videoUploadState.pendingPublish.blobRef,
|
||||
blobRef: videoState.pendingPublish.blobRef,
|
||||
altText: videoAltText,
|
||||
captions: captions,
|
||||
aspectRatio: {
|
||||
width: videoUploadState.asset.width,
|
||||
height: videoUploadState.asset.height,
|
||||
width: videoState.asset.width,
|
||||
height: videoState.asset.height,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
@@ -550,20 +541,20 @@ export const ComposePost = ({
|
||||
setLangPrefs,
|
||||
threadgateAllowUISettings,
|
||||
videoAltText,
|
||||
videoUploadState.asset,
|
||||
videoUploadState.pendingPublish,
|
||||
videoUploadState.status,
|
||||
videoState.asset,
|
||||
videoState.pendingPublish,
|
||||
videoState.status,
|
||||
],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (videoUploadState.pendingPublish && publishOnUpload) {
|
||||
if (!videoUploadState.pendingPublish.mutableProcessed) {
|
||||
videoUploadState.pendingPublish.mutableProcessed = true
|
||||
if (videoState.pendingPublish && publishOnUpload) {
|
||||
if (!videoState.pendingPublish.mutableProcessed) {
|
||||
videoState.pendingPublish.mutableProcessed = true
|
||||
onPressPublish(true)
|
||||
}
|
||||
}
|
||||
}, [onPressPublish, publishOnUpload, videoUploadState.pendingPublish])
|
||||
}, [onPressPublish, publishOnUpload, videoState.pendingPublish])
|
||||
|
||||
const canPost = useMemo(
|
||||
() => graphemeLength <= MAX_GRAPHEME_LENGTH && !isAltTextRequiredAndMissing,
|
||||
@@ -576,10 +567,10 @@ export const ComposePost = ({
|
||||
const canSelectImages =
|
||||
images.length < MAX_IMAGES &&
|
||||
!extLink &&
|
||||
videoUploadState.status === 'idle' &&
|
||||
!videoUploadState.video
|
||||
videoState.status === 'idle' &&
|
||||
!videoState.video
|
||||
const hasMedia =
|
||||
images.length > 0 || Boolean(extLink) || Boolean(videoUploadState.video)
|
||||
images.length > 0 || Boolean(extLink) || Boolean(videoState.video)
|
||||
|
||||
const onEmojiButtonPress = useCallback(() => {
|
||||
openEmojiPicker?.(textInput.current?.getCursorPosition())
|
||||
@@ -694,9 +685,7 @@ export const ComposePost = ({
|
||||
size="small"
|
||||
style={[a.rounded_full, a.py_sm]}
|
||||
onPress={() => onPressPublish()}
|
||||
disabled={
|
||||
videoUploadState.status !== 'idle' && publishOnUpload
|
||||
}>
|
||||
disabled={videoState.status !== 'idle' && publishOnUpload}>
|
||||
<ButtonText style={[a.text_md]}>
|
||||
{replyTo ? (
|
||||
<Trans context="action">Reply</Trans>
|
||||
@@ -732,7 +721,7 @@ export const ComposePost = ({
|
||||
)}
|
||||
<ErrorBanner
|
||||
error={error}
|
||||
videoUploadState={videoUploadState}
|
||||
videoState={videoState}
|
||||
clearError={() => setError('')}
|
||||
clearVideo={clearVideo}
|
||||
/>
|
||||
@@ -798,17 +787,17 @@ export const ComposePost = ({
|
||||
style={[a.w_full, a.mt_lg]}
|
||||
entering={native(ZoomIn)}
|
||||
exiting={native(ZoomOut)}>
|
||||
{videoUploadState.asset &&
|
||||
(videoUploadState.status === 'compressing' ? (
|
||||
{videoState.asset &&
|
||||
(videoState.status === 'compressing' ? (
|
||||
<VideoTranscodeProgress
|
||||
asset={videoUploadState.asset}
|
||||
progress={videoUploadState.progress}
|
||||
asset={videoState.asset}
|
||||
progress={videoState.progress}
|
||||
clear={clearVideo}
|
||||
/>
|
||||
) : videoUploadState.video ? (
|
||||
) : videoState.video ? (
|
||||
<VideoPreview
|
||||
asset={videoUploadState.asset}
|
||||
video={videoUploadState.video}
|
||||
asset={videoState.asset}
|
||||
video={videoState.video}
|
||||
setDimensions={updateVideoDimensions}
|
||||
clear={clearVideo}
|
||||
/>
|
||||
@@ -854,9 +843,8 @@ export const ComposePost = ({
|
||||
t.atoms.border_contrast_medium,
|
||||
styles.bottomBar,
|
||||
]}>
|
||||
{videoUploadState.status !== 'idle' &&
|
||||
videoUploadState.status !== 'done' ? (
|
||||
<VideoUploadToolbar state={videoUploadState} />
|
||||
{videoState.status !== 'idle' && videoState.status !== 'done' ? (
|
||||
<VideoUploadToolbar state={videoState} />
|
||||
) : (
|
||||
<ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<SelectPhotoBtn
|
||||
@@ -1121,12 +1109,12 @@ const styles = StyleSheet.create({
|
||||
|
||||
function ErrorBanner({
|
||||
error: standardError,
|
||||
videoUploadState,
|
||||
videoState,
|
||||
clearError,
|
||||
clearVideo,
|
||||
}: {
|
||||
error: string
|
||||
videoUploadState: VideoUploadState | NoVideoState
|
||||
videoState: VideoState | NoVideoState
|
||||
clearError: () => void
|
||||
clearVideo: () => void
|
||||
}) {
|
||||
@@ -1134,7 +1122,7 @@ function ErrorBanner({
|
||||
const {_} = useLingui()
|
||||
|
||||
const videoError =
|
||||
videoUploadState.status === 'error' ? videoUploadState.error : undefined
|
||||
videoState.status === 'error' ? videoState.error : undefined
|
||||
const error = standardError || videoError
|
||||
|
||||
const onClearError = () => {
|
||||
@@ -1176,7 +1164,7 @@ function ErrorBanner({
|
||||
<ButtonIcon icon={X} />
|
||||
</Button>
|
||||
</View>
|
||||
{videoError && videoUploadState.jobId && (
|
||||
{videoError && videoState.jobId && (
|
||||
<NewText
|
||||
style={[
|
||||
{paddingLeft: 28},
|
||||
@@ -1185,7 +1173,7 @@ function ErrorBanner({
|
||||
a.leading_snug,
|
||||
t.atoms.text_contrast_low,
|
||||
]}>
|
||||
<Trans>Job ID: {videoUploadState.jobId}</Trans>
|
||||
<Trans>Job ID: {videoState.jobId}</Trans>
|
||||
</NewText>
|
||||
)}
|
||||
</View>
|
||||
@@ -1211,7 +1199,7 @@ function ToolbarWrapper({
|
||||
)
|
||||
}
|
||||
|
||||
function VideoUploadToolbar({state}: {state: VideoUploadState}) {
|
||||
function VideoUploadToolbar({state}: {state: VideoState}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const progress = state.progress
|
||||
|
||||
@@ -21,7 +21,7 @@ import {ComposerImage, cropImage} from '#/state/gallery'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {useTheme} from '#/alf'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {ComposerAction} from '../state'
|
||||
import {ComposerAction} from '../state/composer'
|
||||
import {EditImageDialog} from './EditImageDialog'
|
||||
import {ImageAltTextDialog} from './ImageAltTextDialog'
|
||||
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import {ImagePickerAsset} from 'expo-image-picker'
|
||||
|
||||
import {ComposerImage, createInitialImages} from '#/state/gallery'
|
||||
import {
|
||||
createVideoState,
|
||||
VideoAction,
|
||||
videoReducer,
|
||||
VideoState,
|
||||
} from '#/state/queries/video/video'
|
||||
import {ComposerOpts} from '#/state/shell/composer'
|
||||
import {createVideoState, VideoAction, videoReducer, VideoState} from './video'
|
||||
|
||||
type PostRecord = {
|
||||
uri: string
|
||||
@@ -0,0 +1,406 @@
|
||||
import {ImagePickerAsset} from 'expo-image-picker'
|
||||
import {AppBskyVideoDefs, BlobRef, BskyAgent} from '@atproto/api'
|
||||
import {JobStatus} from '@atproto/api/dist/client/types/app/bsky/video/defs'
|
||||
import {I18n} from '@lingui/core'
|
||||
import {msg} from '@lingui/macro'
|
||||
|
||||
import {createVideoAgent} from '#/lib/media/video/util'
|
||||
import {uploadVideo} from '#/lib/media/video/upload'
|
||||
import {AbortError} from '#/lib/async/cancelable'
|
||||
import {compressVideo} from '#/lib/media/video/compress'
|
||||
import {
|
||||
ServerError,
|
||||
UploadLimitError,
|
||||
VideoTooLargeError,
|
||||
} from '#/lib/media/video/errors'
|
||||
import {CompressedVideo} from '#/lib/media/video/types'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
export type VideoAction =
|
||||
| {
|
||||
type: 'compressing_to_uploading'
|
||||
video: CompressedVideo
|
||||
signal: AbortSignal
|
||||
}
|
||||
| {
|
||||
type: 'uploading_to_processing'
|
||||
jobId: string
|
||||
signal: AbortSignal
|
||||
}
|
||||
| {type: 'to_error'; error: string; signal: AbortSignal}
|
||||
| {
|
||||
type: 'to_done'
|
||||
blobRef: BlobRef
|
||||
signal: AbortSignal
|
||||
}
|
||||
| {type: 'update_progress'; progress: number; signal: AbortSignal}
|
||||
| {
|
||||
type: 'update_dimensions'
|
||||
width: number
|
||||
height: number
|
||||
signal: AbortSignal
|
||||
}
|
||||
| {
|
||||
type: 'update_job_status'
|
||||
jobStatus: AppBskyVideoDefs.JobStatus
|
||||
signal: AbortSignal
|
||||
}
|
||||
|
||||
const noopController = new AbortController()
|
||||
noopController.abort()
|
||||
|
||||
export const NO_VIDEO = Object.freeze({
|
||||
status: 'idle',
|
||||
progress: 0,
|
||||
abortController: noopController,
|
||||
asset: undefined,
|
||||
video: undefined,
|
||||
jobId: undefined,
|
||||
pendingPublish: undefined,
|
||||
})
|
||||
|
||||
export type NoVideoState = typeof NO_VIDEO
|
||||
|
||||
type ErrorState = {
|
||||
status: 'error'
|
||||
progress: 100
|
||||
abortController: AbortController
|
||||
asset: ImagePickerAsset | null
|
||||
video: CompressedVideo | null
|
||||
jobId: string | null
|
||||
error: string
|
||||
pendingPublish?: undefined
|
||||
}
|
||||
|
||||
type CompressingState = {
|
||||
status: 'compressing'
|
||||
progress: number
|
||||
abortController: AbortController
|
||||
asset: ImagePickerAsset
|
||||
video?: undefined
|
||||
jobId?: undefined
|
||||
pendingPublish?: undefined
|
||||
}
|
||||
|
||||
type UploadingState = {
|
||||
status: 'uploading'
|
||||
progress: number
|
||||
abortController: AbortController
|
||||
asset: ImagePickerAsset
|
||||
video: CompressedVideo
|
||||
jobId?: undefined
|
||||
pendingPublish?: undefined
|
||||
}
|
||||
|
||||
type ProcessingState = {
|
||||
status: 'processing'
|
||||
progress: number
|
||||
abortController: AbortController
|
||||
asset: ImagePickerAsset
|
||||
video: CompressedVideo
|
||||
jobId: string
|
||||
jobStatus: AppBskyVideoDefs.JobStatus | null
|
||||
pendingPublish?: undefined
|
||||
}
|
||||
|
||||
type DoneState = {
|
||||
status: 'done'
|
||||
progress: 100
|
||||
abortController: AbortController
|
||||
asset: ImagePickerAsset
|
||||
video: CompressedVideo
|
||||
jobId?: undefined
|
||||
pendingPublish: {blobRef: BlobRef; mutableProcessed: boolean}
|
||||
}
|
||||
|
||||
export type VideoState =
|
||||
| ErrorState
|
||||
| CompressingState
|
||||
| UploadingState
|
||||
| ProcessingState
|
||||
| DoneState
|
||||
|
||||
export function createVideoState(
|
||||
asset: ImagePickerAsset,
|
||||
abortController: AbortController,
|
||||
): CompressingState {
|
||||
return {
|
||||
status: 'compressing',
|
||||
progress: 0,
|
||||
abortController,
|
||||
asset,
|
||||
}
|
||||
}
|
||||
|
||||
export function videoReducer(
|
||||
state: VideoState,
|
||||
action: VideoAction,
|
||||
): VideoState {
|
||||
if (action.signal.aborted || action.signal !== state.abortController.signal) {
|
||||
// This action is stale and the process that spawned it is no longer relevant.
|
||||
return state
|
||||
}
|
||||
if (action.type === 'to_error') {
|
||||
return {
|
||||
status: 'error',
|
||||
progress: 100,
|
||||
abortController: state.abortController,
|
||||
error: action.error,
|
||||
asset: state.asset ?? null,
|
||||
video: state.video ?? null,
|
||||
jobId: state.jobId ?? null,
|
||||
}
|
||||
} else if (action.type === 'update_progress') {
|
||||
if (state.status === 'compressing' || state.status === 'uploading') {
|
||||
return {
|
||||
...state,
|
||||
progress: action.progress,
|
||||
}
|
||||
}
|
||||
} else if (action.type === 'update_dimensions') {
|
||||
if (state.asset) {
|
||||
return {
|
||||
...state,
|
||||
asset: {...state.asset, width: action.width, height: action.height},
|
||||
}
|
||||
}
|
||||
} else if (action.type === 'compressing_to_uploading') {
|
||||
if (state.status === 'compressing') {
|
||||
return {
|
||||
status: 'uploading',
|
||||
progress: 0,
|
||||
abortController: state.abortController,
|
||||
asset: state.asset,
|
||||
video: action.video,
|
||||
}
|
||||
}
|
||||
return state
|
||||
} else if (action.type === 'uploading_to_processing') {
|
||||
if (state.status === 'uploading') {
|
||||
return {
|
||||
status: 'processing',
|
||||
progress: 0,
|
||||
abortController: state.abortController,
|
||||
asset: state.asset,
|
||||
video: state.video,
|
||||
jobId: action.jobId,
|
||||
jobStatus: null,
|
||||
}
|
||||
}
|
||||
} else if (action.type === 'update_job_status') {
|
||||
if (state.status === 'processing') {
|
||||
return {
|
||||
...state,
|
||||
jobStatus: action.jobStatus,
|
||||
progress:
|
||||
action.jobStatus.progress !== undefined
|
||||
? action.jobStatus.progress / 100
|
||||
: state.progress,
|
||||
}
|
||||
}
|
||||
} else if (action.type === 'to_done') {
|
||||
if (state.status === 'processing') {
|
||||
return {
|
||||
status: 'done',
|
||||
progress: 100,
|
||||
abortController: state.abortController,
|
||||
asset: state.asset,
|
||||
video: state.video,
|
||||
pendingPublish: {
|
||||
blobRef: action.blobRef,
|
||||
mutableProcessed: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
console.error(
|
||||
'Unexpected video action (' +
|
||||
action.type +
|
||||
') while in ' +
|
||||
state.status +
|
||||
' state',
|
||||
)
|
||||
return state
|
||||
}
|
||||
|
||||
function trunc2dp(num: number) {
|
||||
return Math.trunc(num * 100) / 100
|
||||
}
|
||||
|
||||
export async function processVideo(
|
||||
asset: ImagePickerAsset,
|
||||
dispatch: (action: VideoAction) => void,
|
||||
agent: BskyAgent,
|
||||
did: string,
|
||||
signal: AbortSignal,
|
||||
_: I18n['_'],
|
||||
) {
|
||||
let video: CompressedVideo | undefined
|
||||
try {
|
||||
video = await compressVideo(asset, {
|
||||
onProgress: num => {
|
||||
dispatch({type: 'update_progress', progress: trunc2dp(num), signal})
|
||||
},
|
||||
signal,
|
||||
})
|
||||
} catch (e) {
|
||||
const message = getCompressErrorMessage(e, _)
|
||||
if (message !== null) {
|
||||
dispatch({
|
||||
type: 'to_error',
|
||||
error: message,
|
||||
signal,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
dispatch({
|
||||
type: 'compressing_to_uploading',
|
||||
video,
|
||||
signal,
|
||||
})
|
||||
|
||||
let uploadResponse: AppBskyVideoDefs.JobStatus | undefined
|
||||
try {
|
||||
uploadResponse = await uploadVideo({
|
||||
video,
|
||||
agent,
|
||||
did,
|
||||
signal,
|
||||
_,
|
||||
setProgress: p => {
|
||||
dispatch({type: 'update_progress', progress: p, signal})
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
const message = getUploadErrorMessage(e, _)
|
||||
if (message !== null) {
|
||||
dispatch({
|
||||
type: 'to_error',
|
||||
error: message,
|
||||
signal,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const jobId = uploadResponse.jobId
|
||||
dispatch({
|
||||
type: 'uploading_to_processing',
|
||||
jobId,
|
||||
signal,
|
||||
})
|
||||
|
||||
let pollFailures = 0
|
||||
while (true) {
|
||||
if (signal.aborted) {
|
||||
return // Exit async loop
|
||||
}
|
||||
|
||||
const videoAgent = createVideoAgent()
|
||||
let status: JobStatus | undefined
|
||||
let blob: BlobRef | undefined
|
||||
try {
|
||||
const response = await videoAgent.app.bsky.video.getJobStatus({jobId})
|
||||
status = response.data.jobStatus
|
||||
pollFailures = 0
|
||||
|
||||
if (status.state === 'JOB_STATE_COMPLETED') {
|
||||
blob = status.blob
|
||||
if (!blob) {
|
||||
throw new Error('Job completed, but did not return a blob')
|
||||
}
|
||||
} else if (status.state === 'JOB_STATE_FAILED') {
|
||||
throw new Error(status.error ?? 'Job failed to process')
|
||||
}
|
||||
} catch (e) {
|
||||
if (!status) {
|
||||
pollFailures++
|
||||
if (pollFailures < 50) {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
continue // Continue async loop
|
||||
}
|
||||
}
|
||||
|
||||
logger.error('Error processing video', {safeMessage: e})
|
||||
dispatch({
|
||||
type: 'to_error',
|
||||
error: _(msg`Video failed to process`),
|
||||
signal,
|
||||
})
|
||||
return // Exit async loop
|
||||
}
|
||||
|
||||
if (blob) {
|
||||
dispatch({
|
||||
type: 'to_done',
|
||||
blobRef: blob,
|
||||
signal,
|
||||
})
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'update_job_status',
|
||||
jobStatus: status,
|
||||
signal,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
status.state !== 'JOB_STATE_COMPLETED' &&
|
||||
status.state !== 'JOB_STATE_FAILED'
|
||||
) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
continue // Continue async loop
|
||||
}
|
||||
|
||||
return // Exit async loop
|
||||
}
|
||||
}
|
||||
|
||||
function getCompressErrorMessage(e: unknown, _: I18n['_']): string | null {
|
||||
if (e instanceof AbortError) {
|
||||
return null
|
||||
}
|
||||
if (e instanceof VideoTooLargeError) {
|
||||
return _(msg`The selected video is larger than 50MB.`)
|
||||
}
|
||||
logger.error('Error compressing video', {safeMessage: e})
|
||||
return _(msg`An error occurred while compressing the video.`)
|
||||
}
|
||||
|
||||
function getUploadErrorMessage(e: unknown, _: I18n['_']): string | null {
|
||||
if (e instanceof AbortError) {
|
||||
return null
|
||||
}
|
||||
logger.error('Error uploading video', {safeMessage: e})
|
||||
if (e instanceof ServerError || e instanceof UploadLimitError) {
|
||||
// https://github.com/bluesky-social/tango/blob/lumi/lumi/worker/permissions.go#L77
|
||||
switch (e.message) {
|
||||
case 'User is not allowed to upload videos':
|
||||
return _(msg`You are not allowed to upload videos.`)
|
||||
case 'Uploading is disabled at the moment':
|
||||
return _(
|
||||
msg`Hold up! We’re gradually giving access to video, and you’re still waiting in line. Check back soon!`,
|
||||
)
|
||||
case "Failed to get user's upload stats":
|
||||
return _(
|
||||
msg`We were unable to determine if you are allowed to upload videos. Please try again.`,
|
||||
)
|
||||
case 'User has exceeded daily upload bytes limit':
|
||||
return _(
|
||||
msg`You've reached your daily limit for video uploads (too many bytes)`,
|
||||
)
|
||||
case 'User has exceeded daily upload videos limit':
|
||||
return _(
|
||||
msg`You've reached your daily limit for video uploads (too many videos)`,
|
||||
)
|
||||
case 'Account is not old enough to upload videos':
|
||||
return _(
|
||||
msg`Your account is not yet old enough to upload videos. Please try again later.`,
|
||||
)
|
||||
default:
|
||||
return e.message
|
||||
}
|
||||
}
|
||||
return _(msg`An error occurred while uploading the video.`)
|
||||
}
|
||||
Reference in New Issue
Block a user