How to Use FFmpeg with React Native After FFmpegKit's Retirement

Your build worked last quarter and now pod install dies fetching a framework that returns 404. The package still resolves from npm, the import still type-checks, and nothing in the error message says the project was shut down on purpose. It was.
Quick answer: ffmpeg-kit-react-native, the library most teams used to run FFmpeg React Native side, was retired and its prebuilt binaries were pulled from their hosts, so an npm install that resolves will still fail at CocoaPods or Gradle with a 404. The on-device replacements each cover a slice: FFmpegKitNext ships source only, react-native-video-processing wraps native trim, react-native-compressor does hardware compression with no filter graph, and Expo managed workflows can't load a custom native module at all. The one path that still spans iOS and Android is to stop encoding on the phone: upload the source to storage, POST a single job to a video API like FFmpeg Micro, and take a webhook when the output is ready.ffmpeg-kit-react-native didn't break. It was withdrawn.
A dead dependency usually means an unmaintained repo, and the standard fix is to fork it and carry the maintenance yourself. FFmpegKit wasn't abandoned, though. Taner Sener announced the retirement in "Saying Goodbye to FFmpegKit," and the arthenica/ffmpeg-kit repo carries issue #1099, "Thank You and React Native Migration Guide," as the closing note to the React Native community. The source is still readable. What disappeared was the binary hosting, and that is the part nobody wants to inherit.
The reason matters for how you plan. FFmpegKit's real product was cross-compiled FFmpeg for iOS, Android, arm64, x86_64, simulators, and roughly eight package variants (min, min-gpl, https, https-gpl, audio, video, full, full-gpl). Every FFmpeg release meant rebuilding that matrix and paying to serve it. A fork gets you the build scripts, not the CI budget or the bandwidth bill.
FFmpegKitNext, started in July 2026, is the community continuation, and it is source-only. That is a real project, but it means somebody on your team owns an FFmpeg toolchain, a signing story, and a release cadence. If that person doesn't exist, this is not a dependency swap.
What each ffmpeg-kit-react-native alternative actually does
None of the remaining libraries is a drop-in, and the gap is always the same one: FFmpeg's filter graph. Trimming, compressing, and format conversion have native equivalents on both platforms. Drawing text, overlaying a watermark, concatenating clips with a transition, or ducking a music bed under a voiceover do not.
| Option | What it covers | Platforms | Filter graph | Works in Expo managed |
|---|---|---|---|---|
| FFmpegKitNext | Full FFmpeg, if you build it | iOS, Android | Yes | No |
| react-native-video-processing | Native trim, basic compression presets | iOS, Android | No | No |
| react-native-compressor | Hardware-accelerated compression | iOS, Android | No | No |
| expo-image-and-video-compressor | Hardware-accelerated compression | iOS, Android | No | With a dev client |
| expo-video-encoder | JPEG sequence to H.264 | iOS only | No | With a dev client |
Read that table as a menu of narrow jobs, not a replacement set. React native video compression is genuinely solved by react-native-compressor, which calls AVAssetExportSession on iOS and MediaCodec on Android and gets hardware silicon doing the work. Anything shaped like a real FFmpeg command still has no on-device home.
Expo managed apps were never going to run FFmpeg
An Expo managed project runs inside Expo Go, which ships a fixed set of native modules. FFmpeg is not one of them and cannot be added at runtime, so the answer for a managed app is not "find the right package." You either run expo prebuild and adopt a development build with a config plugin, at which point you own native directories and a custom binary, or you keep the managed workflow and move the encode off the device.
The second option is why so many Expo teams land on a cloud API: it's the only one that leaves the workflow intact.
The offload path: upload, one job, webhook back
The encode your app needs for a phone capture is usually this, and it's worth seeing the command before seeing the API call:
ffmpeg -i input.mov \
-vf "scale='min(1080,iw)':-2" \
-c:v libx264 -preset veryfast -crf 26 \
-c:a aac -b:a 128k \
-movflags +faststart output.mp4
That's roughly 30 seconds of work on a laptop and several minutes of sustained CPU on a mid-range Android, with thermal throttling and battery drain the user feels. Running it server side turns the app's job into three network calls.
Never put a video API key in the bundle. A React Native JavaScript bundle is extractable from any installed APK or IPA in about a minute, so the app talks to your backend and your backend holds the credential.
import * as ImagePicker from 'expo-image-picker';
const API = 'https://api.yourapp.com';
export async function encodeFromPhone() {
const picked = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Videos,
});
if (picked.canceled) return;
// 1. Your backend mints a presigned PUT and holds the API key.
const { uploadUrl, sourceUrl } = await fetch(`${API}/uploads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contentType: 'video/quicktime' }),
}).then((r) => r.json());
// 2. Send the bytes straight from disk. Do not base64 the file.
const file = await fetch(picked.assets[0].uri).then((r) => r.blob());
await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': 'video/quicktime' },
body: file,
});
// 3. One job submit. Your backend forwards it and returns a job id.
return fetch(`${API}/encode`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: sourceUrl, preset: 'social-1080p' }),
}).then((r) => r.json());
}
Your backend then forwards that source URL to FFmpeg Micro as a single job with a callback URL, and the exact job fields are in the docs. Because the job takes a URL, the file never round-trips through your server either. The same presigned-URL trick that keeps bytes out of your app also keeps them out of your API layer, which is worth reading about in give FFmpeg a presigned URL.
For the webhook side, treat the callback as at-least-once and key it on your own job id. The retry, ordering, and idempotency patterns are covered in webhooks for long-running video jobs.
Pitfalls that cost people a release cycle
The failure modes here cluster in two places: package resolution that lies, and file URIs that aren't files.
- A stale
yarn.lockor a warm CI cache will resolve ffmpeg-kit-react-native fine and fail only at the native fetch step. Grep your lockfile, not justpackage.json, and check transitive dependencies that vendored it. - Forks that re-host the old binaries on someone's personal CDN or a Drive link are unsigned artifacts of unknown provenance going into a shipped app. Build from FFmpegKitNext source or don't ship FFmpeg at all.
base64encoding a video to send it throughfetchinflates the payload by about 33% and puts the whole file on the JS heap. Use the blob form above for small clips andFileSystem.uploadAsyncwithBINARY_CONTENT, or react-native-blob-util, for anything over a few hundred megabytes.- Android 10 and later hand you
content://URIs under scoped storage, and iOS photo picks can come back asph://asset references. Neither is a path an encoder can open, so copy to the app's cache directory first. - Compressing on device before uploading "to save bandwidth" pays the battery and heat cost and then hands the server a degraded source. Upload the original on Wi-Fi and let the encode happen once, at full quality.
When to keep the encode on the phone
Offloading is wrong for three real cases. An offline-first app has no network to offload to. A sub-second trim before playback feels worse with a round trip than without one. And content that legally can't leave the device, like medical or legal capture, has to be processed locally no matter the cost.
In all three, reach for a hardware-accelerated compression library or call AVFoundation and MediaCodec directly rather than shipping a general-purpose FFmpeg build. The narrow native path is smaller, faster, and doesn't put tens of megabytes of encoder into an app download that Apple will prompt users about over cellular past 200 MB. Server-side FFmpeg is for the filter work: captions, overlays, concatenation, audio mixing. Those are also the jobs the app has the least business doing while the user watches a spinner. The same reasoning applies to browser-side capture, which we covered in converting MediaRecorder WebM server side.
FAQ
Is ffmpeg-kit-react-native still usable in 2026?
ffmpeg-kit-react-native is not usable as an installed dependency, because the prebuilt frameworks and AARs it downloads during pod install and Gradle sync return 404. Existing apps that already shipped with the binaries embedded keep working, but no new build can fetch them.
What replaced ffmpeg-kit-react-native?
Nothing replaced ffmpeg-kit-react-native as a single package covering both platforms. FFmpegKitNext continues the project as source you compile yourself, react-native-video-processing and react-native-compressor each cover one native operation, and teams that need FFmpeg's filter graph generally move the encode to a server-side API.
Can I use FFmpeg in an Expo managed app?
An Expo managed app cannot use FFmpeg, since Expo Go only loads the native modules it was built with and FFmpeg is not among them. Running expo prebuild and switching to a development build makes a custom native module possible, and offloading the encode to an HTTP API keeps the managed workflow intact.
How do I compress video in React Native without FFmpeg?
React native video compression without FFmpeg is handled by react-native-compressor, which uses AVAssetExportSession on iOS and MediaCodec on Android for hardware-accelerated output. It exposes quality presets and target bitrates, but it can't run scale filters, overlays, or text, so anything beyond a straight re-encode needs a server.
Does offloading the encode make the app feel slower?
Offloading usually feels faster to the user, because the upload runs in the background while they keep using the app and a webhook fires when the output is ready. On-device encoding blocks behind a progress bar, heats the phone, and gets throttled partway through on longer files.
If your React Native app needs captions burned in, a watermark stamped, clips concatenated, or a 4K capture squeezed down for upload, that work fits in one API call with no binaries in your bundle and no servers to run. Sign up free and point a test job at a file you already have.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Add Video Processing to Your Lovable App
Lovable video processing fails because Supabase Edge Functions run Deno and can't run FFmpeg. The fix: Supabase Storage, a signed URL, and one video API call.

Expo Video Compression Isn't a Camera Setting. Do It on Upload.
expo-camera records ~95 MB per minute at 720p with no bitrate or fps control. Expo video compression options compared, plus the pipeline that fixes it.

Convert MP3 to MP4 with FFmpeg: Cover Image, YouTube-Ready
Convert mp3 to mp4 with FFmpeg without the hang, the divisible-by-2 error, or a still image encoded at 30 fps. Full command, YouTube specs, batch loop.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free