Address review: extract shared compression constants, log empty output

- Move TARGET_BITRATE, MAX_DIMENSION, and the 25 MB skip threshold to a
  new src/lib/media/video/constants.ts so both compress.web.ts and the
  upcoming native engine use the same values. Codec lists stay local to
  compress.web.ts since they are mediabunny-specific.
- When mediabunny's BufferTarget returns a null buffer after a clean
  execute(), log via logger.error so Sentry captures the unexpected
  state. The outer catch still demotes the throw to a warn and falls
  back to the original blob, so user-visible behavior is unchanged.
This commit is contained in:
vineyardbovines
2026-06-23 11:09:10 -04:00
parent 36785ffce0
commit a9179fa296
2 changed files with 26 additions and 11 deletions
+18 -11
View File
@@ -18,12 +18,13 @@ import {VIDEO_MAX_SIZE} from '#/lib/constants'
import {VideoTooLargeError} from '#/lib/media/video/errors'
import {logger} from '#/logger'
import {hasWebCodecs} from '#/view/com/composer/videos/metadata'
import {
COMPRESSION_MAX_DIMENSION,
COMPRESSION_MIN_SIZE_BYTES,
COMPRESSION_TARGET_BITRATE,
} from './constants'
import {type CompressedVideo} from './types'
const TARGET_BITRATE = 3_000_000 // 3mbps, matches native
const MAX_DIMENSION = 1920 // matches native
const MIN_SIZE_FOR_COMPRESSION = 25 * 1000 * 1000 // 25mb, matches native
// Codecs to try in order of preference
// avc (H.264) is most compatible, vp9/vp8 are fallbacks for WebM
const VIDEO_CODECS: VideoCodec[] = ['avc', 'hevc', 'vp9', 'vp8']
@@ -51,12 +52,12 @@ export async function compressVideo(
size: blob.size,
mimeType: blob.type,
isGif,
minSizeForCompression: MIN_SIZE_FOR_COMPRESSION,
minSizeForCompression: COMPRESSION_MIN_SIZE_BYTES,
})
// Try MediaBunny compression if WebCodecs is available and file is large enough
// Skip GIFs - MediaBunny doesn't support them
if (hasWebCodecs() && blob.size >= MIN_SIZE_FOR_COMPRESSION && !isGif) {
if (hasWebCodecs() && blob.size >= COMPRESSION_MIN_SIZE_BYTES && !isGif) {
try {
return await doCompression(blob, asset.uri, {onProgress, signal})
} catch (e) {
@@ -68,7 +69,7 @@ export async function compressVideo(
logger.debug('compress: skipping compression', {
hasWebCodecs: hasWebCodecs(),
blobSize: blob.size,
minSize: MIN_SIZE_FOR_COMPRESSION,
minSize: COMPRESSION_MIN_SIZE_BYTES,
})
}
@@ -93,7 +94,7 @@ async function findEncodableVideoCodec(
const canEncode = await canEncodeVideo(codec, {
width,
height,
bitrate: TARGET_BITRATE,
bitrate: COMPRESSION_TARGET_BITRATE,
})
logger.debug('compress: checking video codec', {
codec,
@@ -182,7 +183,7 @@ async function doCompression(
const {width, height} = calculateDimensions(
videoTrack.displayWidth,
videoTrack.displayHeight,
MAX_DIMENSION,
COMPRESSION_MAX_DIMENSION,
)
logger.debug('compress: video dimensions', {
@@ -232,7 +233,7 @@ async function doCompression(
output,
video: {
codec: codecInfo.codec,
bitrate: TARGET_BITRATE,
bitrate: COMPRESSION_TARGET_BITRATE,
width,
height,
fit: 'contain',
@@ -268,7 +269,13 @@ async function doCompression(
const bytes = target.buffer
if (!bytes) {
throw new Error('MediaBunny compression produced no output')
// mediabunny's BufferTarget reports a null buffer after a successful
// execute(). Should not happen in normal use; recoverable here because
// the outer compressVideo() catches and falls back to the original blob,
// but worth flagging in Sentry so we can chase the root cause.
const err = new Error('Compression produced empty output')
logger.error(err, {})
throw err
}
const mimeType = codecInfo.useWebM ? 'video/webm' : 'video/mp4'
+8
View File
@@ -0,0 +1,8 @@
// Shared compression knobs. Mirrored between native (compress.ts) and web
// (compress.web.ts) so both platforms produce videos with the same target.
export const COMPRESSION_TARGET_BITRATE = 3_000_000 // 3 Mbps
export const COMPRESSION_MAX_DIMENSION = 1920
// Web skips compression entirely for files under this size; native applies its
// own threshold logic inside expo-bluesky-video-compress's probe step.
export const COMPRESSION_MIN_SIZE_BYTES = 25_000_000