Enable 300mb video uploads (#10497)

This commit is contained in:
Samuel Newman
2026-05-26 17:23:43 +03:00
committed by GitHub
parent 4eb692cf87
commit 1790fddc78
7 changed files with 49 additions and 17 deletions
+1
View File
@@ -16,6 +16,7 @@ export enum Features {
DmsNewMessageComposerEnable = 'dms:new_message_composer:enable', DmsNewMessageComposerEnable = 'dms:new_message_composer:enable',
ComposerLanguageDetectionEnable = 'composer:language_detection:enable', ComposerLanguageDetectionEnable = 'composer:language_detection:enable',
PostGalleryEmbedEnable = 'post_gallery_embed:enable', PostGalleryEmbedEnable = 'post_gallery_embed:enable',
LargeVideoUploads = 'large_video_uploads:enable',
AATest = 'aa-test', AATest = 'aa-test',
} }
+2 -1
View File
@@ -188,7 +188,8 @@ export const VIDEO_MAX_DURATION_MS = 3 * 60 * 1000 // 3 minutes in milliseconds
* Maximum size of a video in megabytes, _not_ mebibytes. Backend uses * Maximum size of a video in megabytes, _not_ mebibytes. Backend uses
* ISO megabytes. * ISO megabytes.
*/ */
export const VIDEO_MAX_SIZE = 1000 * 1000 * 100 // 100mb export const VIDEO_MAX_SIZE_REDUCED = 1000 * 1000 * 100 // 100mb
export const VIDEO_MAX_SIZE = 3000 * 1000 * 100 // 300mb
export const SUPPORTED_MIME_TYPES = [ export const SUPPORTED_MIME_TYPES = [
'video/mp4', 'video/mp4',
+1
View File
@@ -12,6 +12,7 @@ export async function compressVideo(
opts?: { opts?: {
signal?: AbortSignal signal?: AbortSignal
onProgress?: (progress: number) => void onProgress?: (progress: number) => void
TEMP_enableLargeVideoUploads?: boolean
}, },
): Promise<CompressedVideo> { ): Promise<CompressedVideo> {
const {onProgress, signal} = opts || {} const {onProgress, signal} = opts || {}
+9 -3
View File
@@ -1,22 +1,28 @@
import {type ImagePickerAsset} from 'expo-image-picker' import {type ImagePickerAsset} from 'expo-image-picker'
import {VIDEO_MAX_SIZE} from '#/lib/constants' import {VIDEO_MAX_SIZE, VIDEO_MAX_SIZE_REDUCED} from '#/lib/constants'
import {VideoTooLargeError} from '#/lib/media/video/errors' import {VideoTooLargeError} from '#/lib/media/video/errors'
import {type CompressedVideo} from './types' import {type CompressedVideo} from './types'
// doesn't actually compress, converts to ArrayBuffer // doesn't actually compress, converts to ArrayBuffer
export async function compressVideo( export async function compressVideo(
asset: ImagePickerAsset, asset: ImagePickerAsset,
_opts?: { opts?: {
signal?: AbortSignal signal?: AbortSignal
onProgress?: (progress: number) => void onProgress?: (progress: number) => void
TEMP_enableLargeVideoUploads?: number
}, },
): Promise<CompressedVideo> { ): Promise<CompressedVideo> {
const {mimeType, base64} = parseDataUrl(asset.uri) const {mimeType, base64} = parseDataUrl(asset.uri)
const blob = base64ToBlob(base64, mimeType) const blob = base64ToBlob(base64, mimeType)
const uri = URL.createObjectURL(blob) const uri = URL.createObjectURL(blob)
if (blob.size > VIDEO_MAX_SIZE) { if (
blob.size >
(opts?.TEMP_enableLargeVideoUploads
? VIDEO_MAX_SIZE
: VIDEO_MAX_SIZE_REDUCED)
) {
throw new VideoTooLargeError() throw new VideoTooLargeError()
} }
+1 -1
View File
@@ -1,6 +1,6 @@
export class VideoTooLargeError extends Error { export class VideoTooLargeError extends Error {
constructor() { constructor() {
super('Videos cannot be larger than 100 MB') super('Videos cannot be larger than 300 MB')
this.name = 'VideoTooLargeError' this.name = 'VideoTooLargeError'
} }
} }
+12 -6
View File
@@ -222,6 +222,10 @@ export const ComposePost = ({
const [publishingStage, setPublishingStage] = useState('') const [publishingStage, setPublishingStage] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const enableLargeVideoUploads = ax.features.enabled(
ax.features.LargeVideoUploads,
)
/** /**
* Track when a draft was created so we can measure draft age in metrics. * Track when a draft was created so we can measure draft age in metrics.
* Set when a draft is loaded via handleSelectDraft. * Set when a draft is loaded via handleSelectDraft.
@@ -344,9 +348,10 @@ export const ComposePost = ({
currentDid, currentDid,
abortController.signal, abortController.signal,
i18n, i18n,
enableLargeVideoUploads,
) )
}, },
[i18n, agent, currentDid, composerDispatch], [i18n, agent, currentDid, composerDispatch, enableLargeVideoUploads],
) )
const onInitVideo = useNonReactiveCallback(() => { const onInitVideo = useNonReactiveCallback(() => {
@@ -491,6 +496,7 @@ export const ComposePost = ({
currentDid, currentDid,
abortController.signal, abortController.signal,
i18n, i18n,
enableLargeVideoUploads,
) )
} catch (e) { } catch (e) {
logger.error('Failed to restore video from draft', { logger.error('Failed to restore video from draft', {
@@ -499,7 +505,7 @@ export const ComposePost = ({
}) })
} }
}, },
[i18n, agent, currentDid, composerDispatch], [i18n, agent, currentDid, composerDispatch, enableLargeVideoUploads],
) )
const handleSelectDraft = useCallback( const handleSelectDraft = useCallback(
@@ -810,7 +816,7 @@ export const ComposePost = ({
)), )),
) )
const getFilteredThread = (): { const getFilteredThread = useCallback((): {
type: 'none' | 'trailing-only' | 'non-trailing' type: 'none' | 'trailing-only' | 'non-trailing'
filteredThread: ThreadDraft filteredThread: ThreadDraft
} => { } => {
@@ -838,7 +844,7 @@ export const ComposePost = ({
type: hasNonTrailingEmpty ? 'non-trailing' : 'trailing-only', type: hasNonTrailingEmpty ? 'non-trailing' : 'trailing-only',
filteredThread, filteredThread,
} }
} }, [thread])
const onPressPublish = useCallback(async () => { const onPressPublish = useCallback(async () => {
if (isPublishing) { if (isPublishing) {
@@ -1011,7 +1017,7 @@ export const ComposePost = ({
setLangPrefs.savePostLanguageToHistory() setLangPrefs.savePostLanguageToHistory()
if (initQuote) { if (initQuote) {
// We want to wait for the quote count to update before we call `onPost`, which will refetch data // We want to wait for the quote count to update before we call `onPost`, which will refetch data
whenAppViewReady(agent, initQuote.uri, res => { void whenAppViewReady(agent, initQuote.uri, res => {
const anchor = res.data.thread.at(0) const anchor = res.data.thread.at(0)
if ( if (
AppBskyUnspeccedDefs.isThreadItemPost(anchor?.value) && AppBskyUnspeccedDefs.isThreadItemPost(anchor?.value) &&
@@ -1059,7 +1065,6 @@ export const ComposePost = ({
l, l,
ax, ax,
agent, agent,
thread,
canPost, canPost,
isPublishing, isPublishing,
currentLanguages, currentLanguages,
@@ -1077,6 +1082,7 @@ export const ComposePost = ({
cleanupPublishedDraft, cleanupPublishedDraft,
loadedDraftCreatedAt, loadedDraftCreatedAt,
emptyPostsPromptControl, emptyPostsPromptControl,
getFilteredThread,
]) ])
const handleConfirmSkipEmpty = () => { const handleConfirmSkipEmpty = () => {
+23 -6
View File
@@ -264,6 +264,7 @@ export async function processVideo(
did: string, did: string,
signal: AbortSignal, signal: AbortSignal,
i18n: I18n, i18n: I18n,
TEMP_enableLargeVideoUploads: boolean,
) { ) {
let video: CompressedVideo | undefined let video: CompressedVideo | undefined
try { try {
@@ -272,9 +273,14 @@ export async function processVideo(
dispatch({type: 'update_progress', progress: trunc2dp(num), signal}) dispatch({type: 'update_progress', progress: trunc2dp(num), signal})
}, },
signal, signal,
TEMP_enableLargeVideoUploads,
}) })
} catch (e) { } catch (e) {
const message = getCompressErrorMessage(e, i18n) const message = getCompressErrorMessage(
e,
i18n,
TEMP_enableLargeVideoUploads,
)
if (message !== null) { if (message !== null) {
dispatch({ dispatch({
type: 'to_error', type: 'to_error',
@@ -303,7 +309,7 @@ export async function processVideo(
}, },
}) })
} catch (e) { } catch (e) {
const message = getUploadErrorMessage(e, i18n) const message = getUploadErrorMessage(e, i18n, TEMP_enableLargeVideoUploads)
if (message !== null) { if (message !== null) {
dispatch({ dispatch({
type: 'to_error', type: 'to_error',
@@ -387,20 +393,30 @@ export async function processVideo(
} }
} }
function getCompressErrorMessage(e: unknown, i18n: I18n): string | null { function getCompressErrorMessage(
e: unknown,
i18n: I18n,
TEMP_enableLargeVideoUploads: boolean,
): string | null {
const videoSize = TEMP_enableLargeVideoUploads ? 300 : 100
if (e instanceof AbortError) { if (e instanceof AbortError) {
return null return null
} }
if (e instanceof VideoTooLargeError) { if (e instanceof VideoTooLargeError) {
return i18n._( return i18n._(
msg`The selected video is larger than 100 MB. Please try again with a smaller file.`, msg`The selected video is larger than ${videoSize} MB. Please try again with a smaller file.`,
) )
} }
logger.error('Error compressing video', {safeMessage: e}) logger.error('Error compressing video', {safeMessage: e})
return i18n._(msg`An error occurred while compressing the video.`) return i18n._(msg`An error occurred while compressing the video.`)
} }
function getUploadErrorMessage(e: unknown, i18n: I18n): string | null { function getUploadErrorMessage(
e: unknown,
i18n: I18n,
TEMP_enableLargeVideoUploads: boolean,
): string | null {
const videoSize = TEMP_enableLargeVideoUploads ? 300 : 100
if (e instanceof AbortError) { if (e instanceof AbortError) {
return null return null
} }
@@ -430,8 +446,9 @@ function getUploadErrorMessage(e: unknown, i18n: I18n): string | null {
msg`Your account is not yet old enough to upload videos. Please try again later.`, msg`Your account is not yet old enough to upload videos. Please try again later.`,
) )
case 'file size (100000001 bytes) is larger than the maximum allowed size (100000000 bytes)': case 'file size (100000001 bytes) is larger than the maximum allowed size (100000000 bytes)':
case 'file size (300000001 bytes) is larger than the maximum allowed size (300000000 bytes)':
return i18n._( return i18n._(
msg`The selected video is larger than 100 MB. Please try again with a smaller file.`, msg`The selected video is larger than ${videoSize} MB. Please try again with a smaller file.`,
) )
case 'Confirm your email address to upload videos': case 'Confirm your email address to upload videos':
return i18n._(msg`Please confirm your email address to upload videos.`) return i18n._(msg`Please confirm your email address to upload videos.`)