Enable 300mb video uploads (#10497)
This commit is contained in:
@@ -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',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 || {}
|
||||||
|
|||||||
@@ -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,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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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.`)
|
||||||
|
|||||||
Reference in New Issue
Block a user