diff --git a/src/analytics/features/index.ts b/src/analytics/features/index.ts index 7b1d9cbbaf..6cbafeaa22 100644 --- a/src/analytics/features/index.ts +++ b/src/analytics/features/index.ts @@ -77,22 +77,6 @@ export function getFeatures() { export function getFeatureDescription(feature: Features, i18n: I18n) { switch (feature) { - case Features.VideoAllow10MinuteEnable: - return { - key: feature, - name: i18n._( - msg({ - message: 'Longer videos', - comment: 'Name for a feature flag (longer videos)', - }), - ), - description: i18n._( - msg({ - message: 'Enable 10-minute video uploads.', - comment: 'Description of a feature flag (10-minute video uploads)', - }), - ), - } case Features.CanonicalPostNumberingEnable: return { key: feature, diff --git a/src/analytics/features/types.ts b/src/analytics/features/types.ts index c817bc5bf2..4d3cf5e197 100644 --- a/src/analytics/features/types.ts +++ b/src/analytics/features/types.ts @@ -18,8 +18,6 @@ export enum Features { PostGalleryEmbedEnable = 'post_gallery_embed:enable', PostThreadKnownLikersEnable = 'post_thread:known_likers:enable', CustomLogoJapanEnable = 'custom_logo:japan:enable', - VideoAllow10MinuteEnable = 'video:allow-10-minute:enable', - VideoMultipartUploadEnable = 'video:multipart_upload:enable', SearchStarterPacksV2Enable = 'search_starter_packs_v2:enable', FollowSortEnable = 'follow_sort:enable', OnboardingInterestsRequiredEnable = 'onboarding:interests:required:enable', diff --git a/src/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index 8e43589281..78a4574f0f 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -5,10 +5,7 @@ import {type Platform} from 'react-native' import {type NotificationReason} from '#/lib/hooks/useNotificationHandler' -import { - type VideoCompressSkipReason, - type VideoUploadTransport, -} from '#/lib/media/video/types' +import {type VideoCompressSkipReason} from '#/lib/media/video/types' import {type NotificationType} from '#/state/queries/notifications/types' import {type FeedDescriptor} from '#/state/queries/post-feed' import {type LiveEventFeedMetricContext} from '#/features/liveEvents/types' @@ -1473,7 +1470,6 @@ export type Events = { bytes: number elapsedMs: number throughputBytesPerSec: number - transport: VideoUploadTransport } 'video:upload:uploadFailed': { uploadId: string @@ -1481,7 +1477,6 @@ export type Events = { bytes: number errorClass: string elapsedMs: number - transport: VideoUploadTransport } 'video:upload:processingStarted': { uploadId: string diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 514312aeb9..d12c5a2aed 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -194,8 +194,7 @@ export const MAX_LABELERS = 20 export const VIDEO_SERVICE = 'https://video.bsky.app' export const VIDEO_SERVICE_DID = 'did:web:video.bsky.app' -export const VIDEO_MAX_DURATION_MS = 3 * 60 * 1000 // 3 minutes in milliseconds -export const VIDEO_10_MINUTE_MAX_DURATION_MS = 10 * 60 * 1000 +export const VIDEO_MAX_DURATION_MS = 10 * 60 * 1000 // 10 minutes in milliseconds /** * Maximum size of a video in megabytes, _not_ mebibytes. Backend uses * ISO megabytes. diff --git a/src/lib/media/video/errors.ts b/src/lib/media/video/errors.ts index cbf3083d1f..cdeeb4095e 100644 --- a/src/lib/media/video/errors.ts +++ b/src/lib/media/video/errors.ts @@ -5,13 +5,6 @@ export class VideoTooLargeError extends Error { } } -export class ServerError extends Error { - constructor(message: string) { - super(message) - this.name = 'ServerError' - } -} - export class UploadLimitError extends Error { constructor(message: string) { super(message) diff --git a/src/lib/media/video/multipart/upload.ts b/src/lib/media/video/multipart/upload.ts index 3b0048ecd2..db9cc50218 100644 --- a/src/lib/media/video/multipart/upload.ts +++ b/src/lib/media/video/multipart/upload.ts @@ -26,15 +26,12 @@ import {createUploadPart} from './uploadPart' import {uploadParts} from './uploadParts' import {delay, isRetryableMultipartError, retryDelayMs} from './utils' -export class MultipartFallbackError extends Error {} - export async function uploadVideoMultipart({ video, client, dispatchUrl, setProgress, signal, - onStarted, }: { video: CompressedVideo client: Client @@ -42,25 +39,12 @@ export async function uploadVideoMultipart({ dispatchUrl: string | URL setProgress: (progress: number) => void signal: AbortSignal - onStarted?: () => void }): Promise { throwIfAborted(signal) const tokenProvider = createTokenProvider(client, dispatchUrl, signal) const token = await tokenProvider.get() const name = `${nanoid(12)}.${mimeToExt(video.mimeType)}` - let session - try { - session = await startUpload({token, video, name, signal}) - } catch (err) { - if (signal.aborted) throw new AbortError() - // A server without multipart support, or one with the kill switch active, - // leaves no reservation behind. The legacy path remains authoritative. - throw new MultipartFallbackError( - err instanceof Error ? err.message : 'Multipart upload unavailable', - ) - } - onStarted?.() - + const session = await startUpload({token, video, name, signal}) const {jobId} = session const abortOnCancel = () => { void tokenProvider @@ -89,7 +73,7 @@ export async function uploadVideoMultipart({ }) } catch (err) { if (signal.aborted) throw new AbortError() - return await abortThenFallbackOrResolve( + return await abortThenRethrowOrResolve( jobId, await tokenProvider.get(), err, @@ -166,14 +150,14 @@ async function finishAndRecover({ } } catch (err) { throwIfAborted(signal) - return await abortThenFallbackOrResolve(jobId, token, err) + return await abortThenRethrowOrResolve(jobId, token, err) } createdFailures++ if (createdFailures < MULTIPART_FINISH_ATTEMPTS) { await delay(500 * 2 ** (createdFailures - 1), signal) continue } - return await abortThenFallbackOrResolve(jobId, token, finishError) + return await abortThenRethrowOrResolve(jobId, token, finishError) case 'finishing': // The service may have assembled the upload even though the finish // request failed. Poll and retry instead of starting a second upload. @@ -224,16 +208,21 @@ async function getUploadStatusWithRetry( throw lastError } -async function abortThenFallbackOrResolve( +/** + * Releases the reservation for an upload we can no longer finish, then surfaces + * the failure that got us here. The abort can race a service-side completion, + * so a `completed` result is resolved as a success instead. + */ +async function abortThenRethrowOrResolve( jobId: string, token: string, cause: unknown, ): Promise { const result = await abortUploadWithRetry(jobId, token) if (result.state === 'aborted') { - throw new MultipartFallbackError( - cause instanceof Error ? cause.message : 'Multipart upload failed', - ) + throw cause instanceof Error + ? cause + : new MultipartUploadError('Multipart upload failed') } if (result.state === 'completed' && result.completedJobId) { const status = await getUploadStatus(jobId, token) diff --git a/src/lib/media/video/telemetry.ts b/src/lib/media/video/telemetry.ts index 188abb6d80..6b17ffa571 100644 --- a/src/lib/media/video/telemetry.ts +++ b/src/lib/media/video/telemetry.ts @@ -5,7 +5,6 @@ import {nanoid} from 'nanoid/non-secure' import { type ProbedMetadata, type VideoCompressSkipReason, - type VideoUploadTransport, } from '#/lib/media/video/types' import {Sentry} from '#/logger/sentry/lib' import {type Metrics} from '#/analytics/metrics' @@ -46,7 +45,6 @@ export type VideoTelemetry = { compressCompleted: (video: {size: number; mimeType: string}) => void compressFailed: (e: unknown) => void uploadStarted: (bytes: number) => void - uploadTransport: (transport: VideoUploadTransport) => void uploadCompleted: (jobId: string) => void uploadFailed: (e: unknown) => void processingStarted: (jobId: string) => void @@ -72,7 +70,6 @@ export function createVideoTelemetry({ let phaseStartedAt = startedAt let jobId: string | undefined let uploadBytes: number | undefined - let uploadTransport: VideoUploadTransport = 'legacy' let txnEnded = false let abortBound = true @@ -229,11 +226,6 @@ export function createVideoTelemetry({ metric('video:upload:uploadStarted', {uploadId, engine, bytes}) }, - uploadTransport(transport) { - uploadTransport = transport - phaseSpan?.setAttribute('video.upload.transport', transport) - }, - uploadCompleted(id) { jobId = id const elapsedMs = Date.now() - phaseStartedAt @@ -246,7 +238,6 @@ export function createVideoTelemetry({ elapsedMs, throughputBytesPerSec: elapsedMs > 0 ? Math.round((bytes * 1000) / elapsedMs) : 0, - transport: uploadTransport, }) endPhaseSpan() phase = undefined @@ -259,7 +250,6 @@ export function createVideoTelemetry({ bytes: uploadBytes ?? 0, errorClass: errorClass(e), elapsedMs: Date.now() - phaseStartedAt, - transport: uploadTransport, }) endTxn('error') detachAbort() diff --git a/src/lib/media/video/types.ts b/src/lib/media/video/types.ts index f929c06664..64984d4388 100644 --- a/src/lib/media/video/types.ts +++ b/src/lib/media/video/types.ts @@ -5,8 +5,6 @@ export type VideoCompressSkipReason = 'gif' | 'below-byte-threshold' | 'no-webcodecs' | 'compress-error-fallback' -export type VideoUploadTransport = 'multipart' | 'legacy' | 'legacy-fallback' - export type CompressedVideo = { uri: string mimeType: string diff --git a/src/lib/media/video/upload.ts b/src/lib/media/video/upload.ts index d5a2af5146..1e390372b5 100644 --- a/src/lib/media/video/upload.ts +++ b/src/lib/media/video/upload.ts @@ -1,116 +1,37 @@ -import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy' import {type Client} from '@atproto/lex' import {type I18n} from '@lingui/core' -import {msg} from '@lingui/core/macro' -import {nanoid} from 'nanoid/non-secure' import {AbortError} from '#/lib/async/cancelable' -import {ServerError} from '#/lib/media/video/errors' -import { - type CompressedVideo, - type VideoUploadTransport, -} from '#/lib/media/video/types' -import {Features, features} from '#/analytics/features' -import {type app} from '#/lexicons' -import {MultipartFallbackError, uploadVideoMultipart} from './multipart/upload' -import { - getServiceAuthToken, - getVideoUploadLimits, - serviceAuthExp, -} from './upload.shared' -import {createVideoEndpointUrl, mimeToExt} from './util' +import {type CompressedVideo} from '#/lib/media/video/types' +import {uploadVideoMultipart} from './multipart/upload' +import {getVideoUploadLimits} from './upload.shared' export async function uploadVideo({ video, client, dispatchUrl, - did, setProgress, signal, i18n, - onTransport, }: { video: CompressedVideo client: Client /** The account's PDS/dispatch URL, for the uploadBlob service-auth token. */ dispatchUrl: string | URL - did: string setProgress: (progress: number) => void signal: AbortSignal i18n: I18n - onTransport?: (transport: VideoUploadTransport) => void }) { if (signal.aborted) { throw new AbortError() } await getVideoUploadLimits(client, i18n) - if (features.isOn(Features.VideoMultipartUploadEnable)) { - try { - return await uploadVideoMultipart({ - video, - client, - dispatchUrl, - setProgress, - signal, - onStarted: () => onTransport?.('multipart'), - }) - } catch (err) { - if (!(err instanceof MultipartFallbackError)) throw err - onTransport?.('legacy-fallback') - setProgress(0) - } - } else { - onTransport?.('legacy') - } - - 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({ + return await uploadVideoMultipart({ + video, client, dispatchUrl, - lxm: 'com.atproto.repo.uploadBlob', - exp: serviceAuthExp(), + setProgress, + signal, }) - 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), - ) - - if (signal.aborted) { - throw new AbortError() - } - const res = await uploadTask.uploadAsync() - - if (!res?.body) { - throw new Error('No response') - } - - const responseBody = JSON.parse(res.body) as app.bsky.video.defs.JobStatus - - if (!responseBody.jobId) { - throw new ServerError( - responseBody.error || i18n._(msg`Failed to upload video`), - ) - } - - if (signal.aborted) { - throw new AbortError() - } - return responseBody } diff --git a/src/lib/media/video/upload.web.ts b/src/lib/media/video/upload.web.ts deleted file mode 100644 index b0fcbaaf09..0000000000 --- a/src/lib/media/video/upload.web.ts +++ /dev/null @@ -1,129 +0,0 @@ -import {type Client} from '@atproto/lex' -import {type I18n} from '@lingui/core' -import {msg} from '@lingui/core/macro' -import {nanoid} from 'nanoid/non-secure' - -import {AbortError} from '#/lib/async/cancelable' -import {ServerError} from '#/lib/media/video/errors' -import { - type CompressedVideo, - type VideoUploadTransport, -} from '#/lib/media/video/types' -import {Features, features} from '#/analytics/features' -import {type app} from '#/lexicons' -import {MultipartFallbackError, uploadVideoMultipart} from './multipart/upload' -import { - getServiceAuthToken, - getVideoUploadLimits, - serviceAuthExp, -} from './upload.shared' -import {createVideoEndpointUrl, mimeToExt} from './util' - -export async function uploadVideo({ - video, - client, - dispatchUrl, - did, - setProgress, - signal, - i18n, - onTransport, -}: { - video: CompressedVideo - client: Client - /** The account's PDS/dispatch URL, for the uploadBlob service-auth token. */ - dispatchUrl: string | URL - did: string - setProgress: (progress: number) => void - signal: AbortSignal - i18n: I18n - onTransport?: (transport: VideoUploadTransport) => void -}) { - if (signal.aborted) { - throw new AbortError() - } - await getVideoUploadLimits(client, i18n) - - if (features.isOn(Features.VideoMultipartUploadEnable)) { - try { - return await uploadVideoMultipart({ - video, - client, - dispatchUrl, - setProgress, - signal, - onStarted: () => onTransport?.('multipart'), - }) - } catch (err) { - if (!(err instanceof MultipartFallbackError)) throw err - onTransport?.('legacy-fallback') - setProgress(0) - } - } else { - onTransport?.('legacy') - } - - const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', { - did, - name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`, - }) - - let bytes = video.bytes - if (!bytes) { - if (signal.aborted) { - throw new AbortError() - } - bytes = await fetch(video.uri).then(res => res.arrayBuffer()) - } - - if (signal.aborted) { - throw new AbortError() - } - const token = await getServiceAuthToken({ - client, - dispatchUrl, - lxm: 'com.atproto.repo.uploadBlob', - exp: serviceAuthExp(), - }) - - if (signal.aborted) { - throw new AbortError() - } - const xhr = new XMLHttpRequest() - const res = await new Promise( - (resolve, reject) => { - xhr.upload.addEventListener('progress', e => { - const progress = e.loaded / e.total - setProgress(progress) - }) - xhr.onloadend = () => { - if (signal.aborted) { - reject(new AbortError()) - } else if (xhr.readyState === 4) { - const uploadRes = JSON.parse( - xhr.responseText, - ) as app.bsky.video.defs.JobStatus - resolve(uploadRes) - } else { - reject(new ServerError(i18n._(msg`Failed to upload video`))) - } - } - xhr.onerror = () => { - reject(new ServerError(i18n._(msg`Failed to upload video`))) - } - xhr.open('POST', uri) - xhr.setRequestHeader('Content-Type', video.mimeType) - xhr.setRequestHeader('Authorization', `Bearer ${token}`) - xhr.send(bytes) - }, - ) - - if (!res.jobId) { - throw new ServerError(res.error || i18n._(msg`Failed to upload video`)) - } - - if (signal.aborted) { - throw new AbortError() - } - return res -} diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 6b4bf9a63f..8738b1e1ed 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -62,7 +62,6 @@ import { MAX_GRAPHEME_LENGTH, SUPPORTED_MIME_TYPES, type SupportedMimeTypes, - VIDEO_10_MINUTE_MAX_DURATION_MS, VIDEO_MAX_DURATION_MS, } from '#/lib/constants' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -270,17 +269,10 @@ export const ComposePost = ({ const {currentAccount} = useSession() const t = useTheme() const ax = useAnalytics() - const allow10MinuteVideos = ax.features.enabled( - ax.features.VideoAllow10MinuteEnable, - ) - const videoMaxDurationMs = allow10MinuteVideos - ? VIDEO_10_MINUTE_MAX_DURATION_MS - : VIDEO_MAX_DURATION_MS const client = useAppviewClient() const chatClient = useChatClient() const pdsClient = usePdsClient() const queryClient = useQueryClient() - const currentDid = currentAccount!.did /* * The host the video service-auth token is minted for. This is the same value * that seeds the session's PDS routing, so the audience always matches the host @@ -451,7 +443,7 @@ export const ComposePost = ({ * Fail early on duration so we don't spend time compressing a video the * server would reject anyway. */ - if (asset.duration != null && asset.duration > videoMaxDurationMs) { + if (asset.duration != null && asset.duration > VIDEO_MAX_DURATION_MS) { composerDispatch({ type: 'update_post', postId: postId, @@ -459,9 +451,7 @@ export const ComposePost = ({ type: 'embed_update_video', videoAction: { type: 'to_error', - error: allow10MinuteVideos - ? l`Videos must be 10 minutes or less.` - : l`Videos must be less than 3 minutes long.`, + error: l`Videos must be 10 minutes or less.`, signal: abortController.signal, }, }, @@ -483,23 +473,12 @@ export const ComposePost = ({ }, pdsClient, currentDispatchUrl, - currentDid, abortController.signal, i18n, telemetry, ) }, - [ - l, - i18n, - pdsClient, - currentDispatchUrl, - currentDid, - composerDispatch, - ax.metric, - videoMaxDurationMs, - allow10MinuteVideos, - ], + [l, i18n, pdsClient, currentDispatchUrl, composerDispatch, ax.metric], ) const onInitVideo = useNonReactiveCallback(() => { @@ -596,7 +575,7 @@ export const ComposePost = ({ }, }) - if (asset.duration != null && asset.duration > videoMaxDurationMs) { + if (asset.duration != null && asset.duration > VIDEO_MAX_DURATION_MS) { composerDispatch({ type: 'update_post', postId, @@ -604,9 +583,7 @@ export const ComposePost = ({ type: 'embed_update_video', videoAction: { type: 'to_error', - error: allow10MinuteVideos - ? l`Videos must be 10 minutes or less.` - : l`Videos must be less than 3 minutes long.`, + error: l`Videos must be 10 minutes or less.`, signal: abortController.signal, }, }, @@ -667,7 +644,6 @@ export const ComposePost = ({ }, pdsClient, currentDispatchUrl, - currentDid, abortController.signal, i18n, telemetry, @@ -679,17 +655,7 @@ export const ComposePost = ({ }) } }, - [ - l, - i18n, - pdsClient, - currentDispatchUrl, - currentDid, - composerDispatch, - ax.metric, - videoMaxDurationMs, - allow10MinuteVideos, - ], + [l, i18n, pdsClient, currentDispatchUrl, composerDispatch, ax.metric], ) const handleSelectDraft = useCallback( diff --git a/src/view/com/composer/SelectMediaButton.tsx b/src/view/com/composer/SelectMediaButton.tsx index 15872a4733..83c371d336 100644 --- a/src/view/com/composer/SelectMediaButton.tsx +++ b/src/view/com/composer/SelectMediaButton.tsx @@ -6,7 +6,6 @@ import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import { - VIDEO_10_MINUTE_MAX_DURATION_MS, VIDEO_MAX_DURATION_MS, VIDEO_MAX_SIZE, VIDEO_MAX_SIZE_MB, @@ -23,7 +22,6 @@ import {Button} from '#/components/Button' import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' import {Image_Stroke2_Corner2_Rounded as ImageIcon} from '#/components/icons/Image' import * as toast from '#/components/Toast' -import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' import {isAnimatedGif} from './videos/isAnimatedGif' import {hasWebCodecs} from './videos/metadata' @@ -400,13 +398,6 @@ export function SelectMediaButton({ autoOpen, }: SelectMediaButtonProps) { const {_} = useLingui() - const ax = useAnalytics() - const allow10MinuteVideos = ax.features.enabled( - ax.features.VideoAllow10MinuteEnable, - ) - const videoMaxDurationMs = allow10MinuteVideos - ? VIDEO_10_MINUTE_MAX_DURATION_MS - : VIDEO_MAX_DURATION_MS const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() const {requestVideoAccessIfNeeded} = useVideoLibraryPermission() const sheetWrapper = useSheetWrapper() @@ -426,7 +417,7 @@ export function SelectMediaButton({ } = await processImagePickerAssets(rawAssets, { selectionCountRemaining, allowedAssetTypes, - videoMaxDurationMs, + videoMaxDurationMs: VIDEO_MAX_DURATION_MS, }) /* @@ -451,9 +442,9 @@ export function SelectMediaButton({ [SelectedAssetError.MaxVideos]: _( msg`You can only select one video at a time.`, ), - [SelectedAssetError.VideoTooLong]: allow10MinuteVideos - ? _(msg`Videos must be 10 minutes or less.`) - : _(msg`Videos must be less than 3 minutes long.`), + [SelectedAssetError.VideoTooLong]: _( + msg`Videos must be 10 minutes or less.`, + ), [SelectedAssetError.MaxGIFs]: _( msg`You can only select one GIF at a time.`, ), @@ -473,14 +464,7 @@ export function SelectMediaButton({ errors, }) }, - [ - _, - onSelectAssets, - selectionCountRemaining, - allowedAssetTypes, - videoMaxDurationMs, - allow10MinuteVideos, - ], + [_, onSelectAssets, selectionCountRemaining, allowedAssetTypes], ) const onPressSelectMedia = useCallback(async () => { @@ -503,7 +487,10 @@ export function SelectMediaButton({ } const {assets, canceled} = await sheetWrapper( - openUnifiedPicker({selectionCountRemaining, videoMaxDurationMs}), + openUnifiedPicker({ + selectionCountRemaining, + videoMaxDurationMs: VIDEO_MAX_DURATION_MS, + }), ) if (canceled) return @@ -516,7 +503,6 @@ export function SelectMediaButton({ sheetWrapper, processSelectedAssets, selectionCountRemaining, - videoMaxDurationMs, ]) useEffect(() => { diff --git a/src/view/com/composer/state/video.ts b/src/view/com/composer/state/video.ts index 2f7a1ead62..c0764d343e 100644 --- a/src/view/com/composer/state/video.ts +++ b/src/view/com/composer/state/video.ts @@ -6,11 +6,8 @@ import {msg} from '@lingui/core/macro' import {AbortError} from '#/lib/async/cancelable' import {VIDEO_MAX_SIZE_MB} from '#/lib/constants' import {compressVideo} from '#/lib/media/video/compress' -import { - ServerError, - UploadLimitError, - VideoTooLargeError, -} from '#/lib/media/video/errors' +import {UploadLimitError, VideoTooLargeError} from '#/lib/media/video/errors' +import {MultipartUploadError} from '#/lib/media/video/multipart/api' import {type VideoTelemetry} from '#/lib/media/video/telemetry' import {type CompressedVideo} from '#/lib/media/video/types' import {uploadVideo} from '#/lib/media/video/upload' @@ -294,7 +291,6 @@ export async function processVideo( dispatch: (action: VideoAction) => void, client: Client, dispatchUrl: string | URL, - did: string, signal: AbortSignal, i18n: I18n, telemetry: VideoTelemetry, @@ -344,10 +340,8 @@ export async function processVideo( video, client, dispatchUrl, - did, signal, i18n, - onTransport: telemetry.uploadTransport, setProgress: p => { dispatch({type: 'update_progress', progress: p, signal}) }, @@ -513,7 +507,7 @@ function getUploadErrorMessage(e: unknown, i18n: I18n): string | null { if (e instanceof AbortError) { return null } - if (e instanceof ServerError || e instanceof UploadLimitError) { + if (e instanceof MultipartUploadError || 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':