diff --git a/src/state/queries/video/video.ts b/src/state/queries/video/video.ts index dbbb6c2026..fabee6ad1a 100644 --- a/src/state/queries/video/video.ts +++ b/src/state/queries/video/video.ts @@ -16,7 +16,13 @@ import {logger} from '#/logger' import {createVideoAgent} from '#/state/queries/video/util' import {uploadVideo} from '#/state/queries/video/video-upload' -export type VideoAction = +type Action = + | {type: 'to_idle'; nextController: AbortController} + | { + type: 'idle_to_compressing' + asset: ImagePickerAsset + signal: AbortSignal + } | { type: 'compressing_to_uploading' video: CompressedVideo @@ -46,20 +52,15 @@ export type VideoAction = 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 IdleState = { + status: 'idle' + progress: 0 + abortController: AbortController + asset?: undefined + video?: undefined + jobId?: undefined + pendingPublish?: undefined +} type ErrorState = { status: 'error' @@ -113,7 +114,8 @@ type DoneState = { pendingPublish: {blobRef: BlobRef; mutableProcessed: boolean} } -export type VideoState = +export type State = + | IdleState | ErrorState | CompressingState | UploadingState @@ -121,21 +123,19 @@ export type VideoState = | DoneState export function createVideoState( - asset: ImagePickerAsset, - abortController: AbortController, -): CompressingState { + abortController: AbortController = new AbortController(), +): IdleState { return { - status: 'compressing', + status: 'idle', progress: 0, abortController, - asset, } } -export function videoReducer( - state: VideoState, - action: VideoAction, -): VideoState { +export function videoReducer(state: State, action: Action): State { + if (action.type === 'to_idle') { + return createVideoState(action.nextController) + } 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 @@ -157,6 +157,15 @@ export function videoReducer( progress: action.progress, } } + } else if (action.type === 'idle_to_compressing') { + if (state.status === 'idle') { + return { + status: 'compressing', + progress: 0, + abortController: state.abortController, + asset: action.asset, + } + } } else if (action.type === 'update_dimensions') { if (state.asset) { return { @@ -229,12 +238,18 @@ function trunc2dp(num: number) { export async function processVideo( asset: ImagePickerAsset, - dispatch: (action: VideoAction) => void, + dispatch: (action: Action) => void, agent: BskyAgent, did: string, signal: AbortSignal, _: I18n['_'], ) { + dispatch({ + type: 'idle_to_compressing', + asset, + signal, + }) + let video: CompressedVideo | undefined try { video = await compressVideo(asset, { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 59aae29516..185a57fc35 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -82,12 +82,11 @@ 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 { + createVideoState, processVideo, - VideoAction, - VideoState, - VideoState as VideoUploadState, + State as VideoUploadState, + videoReducer, } from '#/state/queries/video/video' import {useAgent, useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' @@ -193,38 +192,24 @@ export const ComposePost = ({ const [videoAltText, setVideoAltText] = useState('') const [captions, setCaptions] = useState<{lang: string; file: File}[]>([]) - // TODO: Move more state here. - const [composerState, dispatch] = useReducer( - composerReducer, - {initImageUris}, - createComposerState, - ) - - let videoUploadState: VideoState | NoVideoState = NO_VIDEO - if (composerState.embed.media?.type === 'video') { - videoUploadState = composerState.embed.media.video - } - const videoDispatch = useCallback( - (videoAction: VideoAction) => { - dispatch({type: 'embed_update_video', videoAction}) - }, - [dispatch], + const [videoUploadState, videoDispatch] = useReducer( + videoReducer, + undefined, + createVideoState, ) const selectVideo = React.useCallback( (asset: ImagePickerAsset) => { - const abortController = new AbortController() - dispatch({type: 'embed_add_video', asset, abortController}) processVideo( asset, videoDispatch, agent, currentDid, - abortController.signal, + videoUploadState.abortController.signal, _, ) }, - [_, videoDispatch, agent, currentDid], + [_, videoUploadState.abortController, videoDispatch, agent, currentDid], ) // Whenever we receive an initial video uri, we should immediately run compression if necessary @@ -236,8 +221,8 @@ export const ComposePost = ({ const clearVideo = React.useCallback(() => { videoUploadState.abortController.abort() - dispatch({type: 'embed_remove_video'}) - }, [videoUploadState.abortController, dispatch]) + videoDispatch({type: 'to_idle', nextController: new AbortController()}) + }, [videoUploadState.abortController, videoDispatch]) const updateVideoDimensions = useCallback( (width: number, height: number) => { @@ -248,7 +233,7 @@ export const ComposePost = ({ signal: videoUploadState.abortController.signal, }) }, - [videoUploadState.abortController, videoDispatch], + [videoUploadState.abortController], ) const hasVideo = Boolean(videoUploadState.asset || videoUploadState.video) @@ -264,6 +249,12 @@ export const ComposePost = ({ ) const [postgate, setPostgate] = useState(createPostgateRecord({post: ''})) + // TODO: Move more state here. + const [composerState, dispatch] = useReducer( + composerReducer, + {initImageUris}, + createComposerState, + ) let images = NO_IMAGES if (composerState.embed.media?.type === 'images') { images = composerState.embed.media.images @@ -866,7 +857,7 @@ export const ComposePost = ({ /> 0} + disabled={!canSelectImages} setError={setError} /> @@ -1126,7 +1117,7 @@ function ErrorBanner({ clearVideo, }: { error: string - videoUploadState: VideoUploadState | NoVideoState + videoUploadState: VideoUploadState clearError: () => void clearVideo: () => void }) { diff --git a/src/view/com/composer/state.ts b/src/view/com/composer/state.ts index 8e974ad7a5..5588de1aa3 100644 --- a/src/view/com/composer/state.ts +++ b/src/view/com/composer/state.ts @@ -1,12 +1,4 @@ -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' type PostRecord = { @@ -19,16 +11,11 @@ type ImagesMedia = { labels: string[] } -type VideoMedia = { - type: 'video' - video: VideoState -} - type ComposerEmbed = { // TODO: Other record types. record: PostRecord | undefined // TODO: Other media types. - media: ImagesMedia | VideoMedia | undefined + media: ImagesMedia | undefined } export type ComposerState = { @@ -40,13 +27,6 @@ export type ComposerAction = | {type: 'embed_add_images'; images: ComposerImage[]} | {type: 'embed_update_image'; image: ComposerImage} | {type: 'embed_remove_image'; image: ComposerImage} - | { - type: 'embed_add_video' - asset: ImagePickerAsset - abortController: AbortController - } - | {type: 'embed_remove_video'} - | {type: 'embed_update_video'; videoAction: VideoAction} const MAX_IMAGES = 4 @@ -56,9 +36,6 @@ export function composerReducer( ): ComposerState { switch (action.type) { case 'embed_add_images': { - if (action.images.length === 0) { - return state - } const prevMedia = state.embed.media let nextMedia = prevMedia if (!prevMedia) { @@ -127,55 +104,6 @@ export function composerReducer( } return state } - case 'embed_add_video': { - const prevMedia = state.embed.media - let nextMedia = prevMedia - if (!prevMedia) { - nextMedia = { - type: 'video', - video: createVideoState(action.asset, action.abortController), - } - } - return { - ...state, - embed: { - ...state.embed, - media: nextMedia, - }, - } - } - case 'embed_update_video': { - const videoAction = action.videoAction - const prevMedia = state.embed.media - let nextMedia = prevMedia - if (prevMedia?.type === 'video') { - nextMedia = { - ...prevMedia, - video: videoReducer(prevMedia.video, videoAction), - } - } - return { - ...state, - embed: { - ...state.embed, - media: nextMedia, - }, - } - } - case 'embed_remove_video': { - const prevMedia = state.embed.media - let nextMedia = prevMedia - if (prevMedia?.type === 'video') { - nextMedia = undefined - } - return { - ...state, - embed: { - ...state.embed, - media: nextMedia, - }, - } - } default: return state } @@ -194,7 +122,6 @@ export function createComposerState({ labels: [], } } - // TODO: initial video. return { embed: { record: undefined,