Skip to content

Instantly share code, notes, and snippets.

@tobobo
Last active November 30, 2025 02:47
Show Gist options
  • Select an option

  • Save tobobo/c251ddefcba8b97ec8f44fca57fb281e to your computer and use it in GitHub Desktop.

Select an option

Save tobobo/c251ddefcba8b97ec8f44fca57fb281e to your computer and use it in GitHub Desktop.
Using ffmpeg-kit-react-native in a managed Expo project built by EAS

Using ffmpeg-kit-react-native in a managed Expo project built by EAS

This solution is based on @NooruddinLakhani's Medium post Resolved “FFmpegKit” Retirement Issue in React Native: A Complete Guide. I'm not very familiar with iOS and Android build processes but I was able to use LLM tools to implement it as an Expo plugin. Because of this I may not be very helpful in troubleshooting issues, but Claude 4 or Gemini Pro 2.5 may be able to help. Feedback welcome!

This has not been tested for local building—I only build my project on EAS, and this plugin has only been tested for use on EAS.

Prerequisites

  1. You are using Expo 53 (this has not been tested on any other versions)
  2. You are using a managed Expo project which you build with EAS (not locally)
  3. You have ffmpeg-kit-react-native 6.0.2 installed

Instructions

  1. Use patch-package or bun patch to change s.default_subspec = 'https' to `s.default_subspec = 'full-gpl' in node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
  2. Add ffmpeg-kit-plugin.js somewhere in your project (the example below has it in the root directory)
  3. Add this to your app.config file in the plugins list, taking in to account the location of the plugin in your repo
[
  './ffmpeg-kit-plugin.js',
  {
    iosUrl:
      'https://github.com/NooruddinLakhani/ffmpeg-kit-ios-full-gpl/archive/refs/tags/latest.zip',
    androidUrl:
      'https://github.com/NooruddinLakhani/ffmpeg-kit-full-gpl/releases/download/v1.0.0/ffmpeg-kit-full-gpl.aar',
  },
],

The current URLs point to files hosted by NooruddinLakhani, but you can host these files yourself at a different location if needed.

  1. Build your project with EAS

You should now be able to use your managed Expo app with ffmpeg-kit-react-native as before.

const fs = require('fs');
const path = require('path');
const {
withPlugins,
withDangerousMod,
withAppBuildGradle,
withProjectBuildGradle,
withPodfileProperties,
withCocoaPodsImport,
} = require('@expo/config-plugins');
const {
mergeContents,
} = require('@expo/config-plugins/build/utils/generateCode');
const withFfmpegKitIos = (config, { iosUrl }) => {
return withDangerousMod(config, [
'ios',
async (cfg) => {
const { platformProjectRoot } = cfg.modRequest;
const podspecPath = path.join(
platformProjectRoot,
'ffmpeg-kit-ios-full-gpl.podspec',
);
const podspec = `
Pod::Spec.new do |s|
s.name = 'ffmpeg-kit-ios-full-gpl'
s.version = '6.0' # Must match what ffmpeg-kit-react-native expects for this subspec
s.summary = 'Custom full-gpl FFmpegKit iOS frameworks from self-hosted source.'
s.homepage = 'https://github.com/arthenica/ffmpeg-kit' # Or your repo
s.license = { :type => 'LGPL' } # Or the correct license
s.author = { 'Your Name' => 'your.email@example.com' } # Update with your info
s.platform = :ios, '12.1'
s.static_framework = true
# Use the HTTP source to fetch the zipped package directly.
s.source = { :http => '${iosUrl}' }
# Adjust these paths if your zip structure is different.
# These paths are relative to the root of the extracted zip.
s.vendored_frameworks = [
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libswscale.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libswresample.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libavutil.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libavformat.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libavfilter.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libavdevice.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/libavcodec.xcframework',
'ffmpeg-kit-ios-full-gpl-latest/ffmpeg-kit-ios-full-gpl/6.0-80adc/ffmpegkit.xcframework'
]
end
`;
fs.writeFileSync(podspecPath, podspec);
const podfilePath = path.join(platformProjectRoot, 'Podfile');
let podfileContent = fs.readFileSync(podfilePath, 'utf-8');
const newPodEntry = `pod 'ffmpeg-kit-ios-full-gpl', :podspec => './ffmpeg-kit-ios-full-gpl.podspec'`;
if (!podfileContent.includes(newPodEntry)) {
const anchor = `use_expo_modules!`; // New, more reliable anchor
if (podfileContent.includes(anchor)) {
podfileContent = mergeContents({
tag: 'ffmpeg-kit-custom-pod',
src: podfileContent,
newSrc: newPodEntry,
anchor: new RegExp(
`^\\s*${anchor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
),
offset: 1, // Insert on the line *after* the anchor
comment: '#',
}).contents;
} else {
// Fallback if 'use_expo_modules!' is not found (less likely in modern Expo)
// Try to insert it after the main target declaration
const appName = config.name; // From app.json/app.config.js
const targetAnchor = `target '${appName}' do`;
if (appName && podfileContent.includes(targetAnchor)) {
podfileContent = mergeContents({
tag: 'ffmpeg-kit-custom-pod-fallback',
src: podfileContent,
newSrc: ` ${newPodEntry}`, // Add some indentation
anchor: new RegExp(
`^\\s*${targetAnchor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
),
offset: 1, // Insert after the target line
comment: '#',
}).contents;
console.log(
`[ffmpeg-kit-plugin] Used fallback anchor "target '${appName}' do" for Podfile modification.`,
);
} else {
console.warn(
`[ffmpeg-kit-plugin] Could not find "use_expo_modules!" or "target '${appName}' do" in Podfile. Custom pod for ffmpeg-kit may not be added correctly. Please check Podfile structure.`,
);
}
}
fs.writeFileSync(podfilePath, podfileContent);
}
return cfg;
},
]);
};
const withFfmpegKitAndroid = (config, { androidUrl }) => {
config = withAppBuildGradle(config, (cfg) => {
let buildGradle = cfg.modResults.contents;
const importUrl = 'import java.net.URL';
if (!buildGradle.includes(importUrl)) {
buildGradle = mergeContents({
tag: 'ffmpeg-kit-import-url',
src: buildGradle,
newSrc: importUrl,
anchor: /^/,
offset: 0,
comment: '//',
}).contents;
}
const appFlatDirLibsPath = '\\${projectDir}/../libs';
const appFlatDirRepo = `
repositories {
flatDir {
dirs "${appFlatDirLibsPath}"
}
}`;
if (
!buildGradle.match(
new RegExp(
`repositories\\s*\\{[\\s\\S]*?flatDir\\s*\\{[\\s\\S]*?dirs\\s*['"]${appFlatDirLibsPath.replace(
/[$.]/g,
'\\\\$&',
)}['"]`,
),
)
) {
buildGradle = mergeContents({
tag: 'ffmpeg-kit-app-flatdir-repo',
src: buildGradle,
newSrc: appFlatDirRepo,
anchor: /android\s*\{/,
offset: 1,
comment: '//',
}).contents;
}
const newDependencies = `
implementation(name: 'ffmpeg-kit-full-gpl', ext: 'aar')
implementation 'com.arthenica:smart-exception-java:0.2.1'`;
if (!buildGradle.includes("name: 'ffmpeg-kit-full-gpl', ext: 'aar'")) {
buildGradle = mergeContents({
tag: 'ffmpeg-kit-dependencies',
src: buildGradle,
newSrc: newDependencies,
anchor: /dependencies\s*\{/,
offset: 1,
comment: '//',
}).contents;
}
// Add configuration to exclude the problematic arthenica dependency from ffmpeg-kit-react-native
const excludeConfig = `
configurations.all {
exclude group: 'com.arthenica', module: 'ffmpeg-kit-https'
exclude group: 'com.arthenica', module: 'ffmpeg-kit-min'
exclude group: 'com.arthenica', module: 'ffmpeg-kit-audio'
exclude group: 'com.arthenica', module: 'ffmpeg-kit-video'
exclude group: 'com.arthenica', module: 'ffmpeg-kit-full'
exclude group: 'com.arthenica', module: 'ffmpeg-kit-full-gpl'
}`;
if (!buildGradle.includes('configurations.all')) {
buildGradle = mergeContents({
tag: 'ffmpeg-kit-exclude-config',
src: buildGradle,
newSrc: excludeConfig,
anchor: /android\s*\{/,
offset: -1,
comment: '//',
}).contents;
}
const downloadBlock = `
// Download AAR
def aarUrl = '${androidUrl}'
def aarFile = file("\${projectDir}/../libs/ffmpeg-kit-full-gpl.aar")
// Ensure directory exists
if (!aarFile.parentFile.exists()) {
aarFile.parentFile.mkdirs()
}
// Download during configuration if not present
if (!aarFile.exists()) {
println "[ffmpeg-kit] Downloading AAR from \$aarUrl..."
try {
new URL(aarUrl).withInputStream { i ->
aarFile.withOutputStream { it << i }
}
println "[ffmpeg-kit] AAR downloaded successfully"
} catch (Exception e) {
println "[ffmpeg-kit] Failed to download AAR during configuration: \${e.message}"
}
}
afterEvaluate {
tasks.register("downloadAar") {
description = "Downloads ffmpeg-kit AAR file"
group = "ffmpeg-kit"
outputs.file(aarFile)
doLast {
if (!aarFile.exists()) {
println "[ffmpeg-kit] Downloading AAR from \$aarUrl..."
new URL(aarUrl).withInputStream { i ->
aarFile.withOutputStream { it << i }
}
println "[ffmpeg-kit] AAR downloaded successfully"
}
}
}
preBuild.dependsOn("downloadAar")
}`;
if (!buildGradle.includes('def aarUrl =')) {
buildGradle = buildGradle + '\n' + downloadBlock;
}
cfg.modResults.contents = buildGradle;
return cfg;
});
config = withProjectBuildGradle(config, (cfg) => {
let buildGradle = cfg.modResults.contents;
buildGradle = buildGradle.replace(
/^\s*ffmpegKitPackage\s*=\s*"full-gpl"\s*(\r?\n)?/m,
'',
);
const projectFlatDirLibsPath = '$rootDir/libs';
const flatDirString = ` flatDir {\n dirs "${projectFlatDirLibsPath}"\n }`;
const allProjectsRepositoriesRegex =
/(allprojects\s*\{\s*repositories\s*\{)/;
const existingFlatDirRegex = new RegExp(
`allprojects\\s*\\{[\\s\\S]*?repositories\\s*\\{[\\s\\S]*?flatDir\\s*\\{[\\s\\S]*?dirs\\s*['"]${projectFlatDirLibsPath.replace(
/[$.]/g,
'\\$&',
)}['"]`,
);
if (!buildGradle.match(existingFlatDirRegex)) {
const match = buildGradle.match(allProjectsRepositoriesRegex);
if (match) {
const insertionPoint = match.index + match[0].length;
buildGradle =
buildGradle.substring(0, insertionPoint) +
'\n' +
flatDirString +
buildGradle.substring(insertionPoint);
}
}
cfg.modResults.contents = buildGradle;
return cfg;
});
return config;
};
module.exports = (config, options = {}) => {
const { iosUrl, androidUrl } = options;
if (!iosUrl) {
throw new Error(
'FFmpeg Kit plugin requires "iosUrl" option. Please provide the iOS download URL in your app.config.ts',
);
}
if (!androidUrl) {
throw new Error(
'FFmpeg Kit plugin requires "androidUrl" option. Please provide the Android AAR download URL in your app.config.ts',
);
}
return withPlugins(config, [
(config) => withFfmpegKitIos(config, { iosUrl }),
(config) => withFfmpegKitAndroid(config, { androidUrl }),
]);
};
@kkcoms
Copy link

kkcoms commented Nov 30, 2025

How did you guys manage to install ffmpeg-kit-react-native 6.0.2?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment