lazy-load mediabunny on web
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,8 @@
|
|||||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||||
import {
|
import {
|
||||||
ALL_FORMATS,
|
|
||||||
type AudioCodec,
|
type AudioCodec,
|
||||||
BlobSource,
|
type Input as MediaBunnyInput,
|
||||||
BufferTarget,
|
|
||||||
canEncodeAudio,
|
|
||||||
canEncodeVideo,
|
|
||||||
Conversion,
|
|
||||||
Input,
|
|
||||||
Mp4OutputFormat,
|
|
||||||
Output,
|
|
||||||
type VideoCodec,
|
type VideoCodec,
|
||||||
WebMOutputFormat,
|
|
||||||
} from 'mediabunny'
|
} from 'mediabunny'
|
||||||
|
|
||||||
import {VIDEO_MAX_SIZE} from '#/lib/constants'
|
import {VIDEO_MAX_SIZE} from '#/lib/constants'
|
||||||
@@ -23,6 +14,7 @@ import {
|
|||||||
COMPRESSION_MIN_SIZE_BYTES,
|
COMPRESSION_MIN_SIZE_BYTES,
|
||||||
COMPRESSION_TARGET_BITRATE,
|
COMPRESSION_TARGET_BITRATE,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
import {loadMediaBunny} from './mediabunny'
|
||||||
import {type CompressedVideo, type ProbedMetadata} from './types'
|
import {type CompressedVideo, type ProbedMetadata} from './types'
|
||||||
|
|
||||||
// Codecs to try in order of preference
|
// Codecs to try in order of preference
|
||||||
@@ -112,6 +104,7 @@ export async function compressVideo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function probeWithMediaBunny(blob: Blob): Promise<ProbedMetadata> {
|
async function probeWithMediaBunny(blob: Blob): Promise<ProbedMetadata> {
|
||||||
|
const {ALL_FORMATS, BlobSource, Input} = await loadMediaBunny()
|
||||||
const input = new Input({source: new BlobSource(blob), formats: ALL_FORMATS})
|
const input = new Input({source: new BlobSource(blob), formats: ALL_FORMATS})
|
||||||
try {
|
try {
|
||||||
const videoTrack = await input.getPrimaryVideoTrack()
|
const videoTrack = await input.getPrimaryVideoTrack()
|
||||||
@@ -153,6 +146,7 @@ async function findEncodableVideoCodec(
|
|||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
): Promise<{codec: VideoCodec; useWebM: boolean} | null> {
|
): Promise<{codec: VideoCodec; useWebM: boolean} | null> {
|
||||||
|
const {canEncodeVideo} = await loadMediaBunny()
|
||||||
for (const codec of VIDEO_CODECS) {
|
for (const codec of VIDEO_CODECS) {
|
||||||
const canEncode = await canEncodeVideo(codec, {
|
const canEncode = await canEncodeVideo(codec, {
|
||||||
width,
|
width,
|
||||||
@@ -179,9 +173,10 @@ const AUDIO_CODECS_MP4: AudioCodec[] = ['aac']
|
|||||||
const AUDIO_CODECS_WEBM: AudioCodec[] = ['opus', 'vorbis']
|
const AUDIO_CODECS_WEBM: AudioCodec[] = ['opus', 'vorbis']
|
||||||
|
|
||||||
async function findEncodableAudioCodec(
|
async function findEncodableAudioCodec(
|
||||||
audioTrack: Awaited<ReturnType<Input['getPrimaryAudioTrack']>>,
|
audioTrack: Awaited<ReturnType<MediaBunnyInput['getPrimaryAudioTrack']>>,
|
||||||
useWebM: boolean,
|
useWebM: boolean,
|
||||||
): Promise<{codec: AudioCodec} | null> {
|
): Promise<{codec: AudioCodec} | null> {
|
||||||
|
const {canEncodeAudio} = await loadMediaBunny()
|
||||||
if (!audioTrack) {
|
if (!audioTrack) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -228,6 +223,17 @@ async function doCompression(
|
|||||||
): Promise<CompressedVideo> {
|
): Promise<CompressedVideo> {
|
||||||
const {onProgress, signal} = opts
|
const {onProgress, signal} = opts
|
||||||
|
|
||||||
|
const {
|
||||||
|
ALL_FORMATS,
|
||||||
|
BlobSource,
|
||||||
|
BufferTarget,
|
||||||
|
Conversion,
|
||||||
|
Input,
|
||||||
|
Mp4OutputFormat,
|
||||||
|
Output,
|
||||||
|
WebMOutputFormat,
|
||||||
|
} = await loadMediaBunny()
|
||||||
|
|
||||||
const input = new Input({
|
const input = new Input({
|
||||||
source: new BlobSource(blob),
|
source: new BlobSource(blob),
|
||||||
formats: ALL_FORMATS,
|
formats: ALL_FORMATS,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
type MediaBunnyModule = typeof import('mediabunny')
|
||||||
|
|
||||||
|
let promise: Promise<MediaBunnyModule> | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily loads mediabunny into its own webpack chunk and caches the resulting
|
||||||
|
* promise so the module is fetched at most once. If the import fails (e.g. a
|
||||||
|
* transient chunk-load error), the cached promise is reset so a later call can
|
||||||
|
* retry the load.
|
||||||
|
*/
|
||||||
|
export function loadMediaBunny(): Promise<MediaBunnyModule> {
|
||||||
|
promise ??= import(/* webpackChunkName: "mediabunny" */ 'mediabunny').catch(
|
||||||
|
e => {
|
||||||
|
// reset so a later attempt can retry after a transient chunk-load failure
|
||||||
|
promise = undefined
|
||||||
|
throw e
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return promise
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||||
import {ALL_FORMATS, BlobSource, Input} from 'mediabunny'
|
|
||||||
|
|
||||||
|
import {loadMediaBunny} from '#/lib/media/video/mediabunny'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
|
||||||
export function hasWebCodecs(): boolean {
|
export function hasWebCodecs(): boolean {
|
||||||
@@ -55,6 +55,7 @@ async function getMetadataWithWebCodecs(
|
|||||||
file: File,
|
file: File,
|
||||||
blobUrl: string,
|
blobUrl: string,
|
||||||
): Promise<ImagePickerAsset> {
|
): Promise<ImagePickerAsset> {
|
||||||
|
const {ALL_FORMATS, BlobSource, Input} = await loadMediaBunny()
|
||||||
const input = new Input({
|
const input = new Input({
|
||||||
source: new BlobSource(file),
|
source: new BlobSource(file),
|
||||||
formats: ALL_FORMATS,
|
formats: ALL_FORMATS,
|
||||||
|
|||||||
Reference in New Issue
Block a user