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 }),
]);
};
@thacio
Copy link

thacio commented Jun 2, 2025

You are the GOAT! Great job on everything and thanks for the help.
Managed to get it working, fed everything to opus (logs, expo prebuild gradle, the gradle from the plugin, ffmpeg-kit-plugin.js.

Got this patch and modification.
I did not check what changed though. But I can confirm it's working for expo sdk 52 on ios and android. (compiled and tested in app)

diff --git a/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec b/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
index 889d3e8..a265d7a 100644
--- a/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
+++ b/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
 
   s.source       = { :git => "https://github.com/arthenica/ffmpeg-kit.git", :tag => "react.native.v#{s.version}" }
 
-  s.default_subspec   = 'https'
+  s.default_subspec   = 'full-gpl'
 
   s.dependency "React-Core"
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 }) => {
 // First, patch the ffmpeg-kit-react-native build.gradle
 config = withDangerousMod(config, [
   'android',
   async (cfg) => {
     const { platformProjectRoot } = cfg.modRequest;
     const ffmpegKitBuildGradlePath = path.join(
       platformProjectRoot,
       '..',
       'node_modules',
       'ffmpeg-kit-react-native',
       'android',
       'build.gradle'
     );

     if (fs.existsSync(ffmpegKitBuildGradlePath)) {
       let buildGradle = fs.readFileSync(ffmpegKitBuildGradlePath, 'utf-8');
       
       // Replace the Maven dependency with local AAR
       const originalDependency = /implementation 'com\.arthenica:ffmpeg-kit-'.*/;
       const replacement = `implementation(name: 'ffmpeg-kit-full-gpl', ext: 'aar')`;
       
       if (buildGradle.match(originalDependency)) {
         buildGradle = buildGradle.replace(originalDependency, replacement);
         
         // Also add the smart-exception dependency if not present
         if (!buildGradle.includes('smart-exception-java')) {
           buildGradle = buildGradle.replace(
             replacement,
             `${replacement}\n  implementation 'com.arthenica:smart-exception-java:0.2.1'`
           );
         }
         
         fs.writeFileSync(ffmpegKitBuildGradlePath, buildGradle);
         console.log('[ffmpeg-kit-plugin] Patched ffmpeg-kit-react-native build.gradle');
       }
     }
     
     return cfg;
   },
 ]);

 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;
   }

   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 }),
 ]);
};

@tobobo
Copy link
Author

tobobo commented Jun 2, 2025

@thacio awesome, glad you got it working! The main difference I see is that it's going in and modifying the ffmpeg-kit-react-native module. I tried to avoid this because of supposed "best practices" and I figured that I should just be able to modify the output gradle file, but since the library isn't going to be updated again I don't think this is any riskier than patching the package or using this custom plugin in general. Thanks for sharing your working solution for Expo 52!

@RuslanAktaev
Copy link

RuslanAktaev commented Jun 26, 2025

@tobobo thank you!!!

I had same issue with

[RUN_GRADLEW] > Could not find com.arthenica:ffmpeg-kit-https:6.0-2.

Fixed by updating the patch:

diff --git a/node_modules/ffmpeg-kit-react-native/android/build.gradle b/node_modules/ffmpeg-kit-react-native/android/build.gradle
index 2909280..0ec1512 100644
--- a/node_modules/ffmpeg-kit-react-native/android/build.gradle
+++ b/node_modules/ffmpeg-kit-react-native/android/build.gradle
@@ -125,5 +125,6 @@ repositories {
 
 dependencies {
   api 'com.facebook.react:react-native:+'
-  implementation 'com.arthenica:ffmpeg-kit-' + safePackageName(safeExtGet('ffmpegKitPackage', 'https')) + ':' + safePackageVersion(safeExtGet('ffmpegKitPackage', 'https'))
+  implementation(name: 'ffmpeg-kit-full-gpl', ext: 'aar')
+  implementation 'com.arthenica:smart-exception-java:0.2.1'
 }
diff --git a/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec b/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
index 889d3e8..a265d7a 100644
--- a/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
+++ b/node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
 
   s.source       = { :git => "https://github.com/arthenica/ffmpeg-kit.git", :tag => "react.native.v#{s.version}" }
 
-  s.default_subspec   = 'https'
+  s.default_subspec   = 'full-gpl'
 
   s.dependency "React-Core"

@manssorr
Copy link

When building a new android preview build on EAS I face this issue (it was working prefect in dev):

and I fix it using this patch (Sonnet helped me):

[RUN_GRADLEW] > Task :expo-modules-core:compileReleaseKotlin
[RUN_GRADLEW] FAILURE: Build failed with an exception.
[RUN_GRADLEW] * What went wrong:
[RUN_GRADLEW] A problem was found with the configuration of task ':ffmpeg-kit-react-native:generateReleaseLintModel' (type 'LintModelWriterTask').
[RUN_GRADLEW]   - Gradle detected a problem with the following location: '/private/var/folders/_s/wkm0rzkj06s6mm3pw29jjb3c0000gn/T/eas-build-local-nodejs/79f61b9e-6be6-4055-b821-be552d55a2d5/build/android/libs/ffmpeg-kit-full-gpl.aar'.
[RUN_GRADLEW] Reason: Task ':ffmpeg-kit-react-native:generateReleaseLintModel' uses this output of task ':app:downloadAar' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
[RUN_GRADLEW]     
[RUN_GRADLEW]     Possible solutions:
[RUN_GRADLEW]       1. Declare task ':app:downloadAar' as an input of ':ffmpeg-kit-react-native:generateReleaseLintModel'.
[RUN_GRADLEW]       2. Declare an explicit dependency on ':app:downloadAar' from ':ffmpeg-kit-react-native:generateReleaseLintModel' using Task#dependsOn.
[RUN_GRADLEW]       3. Declare an explicit dependency on ':app:downloadAar' from ':ffmpeg-kit-react-native:generateReleaseLintModel' using Task#mustRunAfter.
[RUN_GRADLEW]     
[RUN_GRADLEW]     For more information, please refer to https://docs.gradle.org/8.10.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
[RUN_GRADLEW] * Try:
[RUN_GRADLEW] > Run with --stacktrace option to get the stack trace.
[RUN_GRADLEW] > Run with --info or --debug option to get more log output.
[RUN_GRADLEW] > Run with --scan to get full insights.
[RUN_GRADLEW] > Get more help at https://help.gradle.org.
[RUN_GRADLEW] BUILD FAILED in 2m 17s
[RUN_GRADLEW] Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
[RUN_GRADLEW] You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
[RUN_GRADLEW] For more on this, please refer to https://docs.gradle.org/8.10.2/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
[RUN_GRADLEW] 1089 actionable tasks: 1089 executed
[RUN_GRADLEW] Error: Gradle build failed with unknown error. See logs for the "Run gradlew" phase for more information.

diff --git a/android/build.gradle b/android/build.gradle
index 2909280ab1fa30840204409dc12d6405722aa714..6ea79621abc8ccee458bb3edb3930c206c4885fd 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -125,5 +125,32 @@ repositories {
 
 dependencies {
   api 'com.facebook.react:react-native:+'
-  implementation 'com.arthenica:ffmpeg-kit-' + safePackageName(safeExtGet('ffmpegKitPackage', 'https')) + ':' + safePackageVersion(safeExtGet('ffmpegKitPackage', 'https'))
+  implementation(name: 'ffmpeg-kit-full-gpl', ext: 'aar')
+  implementation 'com.arthenica:smart-exception-java:0.2.1'
+}
+
+// Ensure proper task dependencies for AAR file
+afterEvaluate {
+  tasks.matching { task ->
+    task.name.contains('lint') || task.name.contains('Lint')
+  }.configureEach { task ->
+    if (project.findProject(':app')) {
+      def downloadAarTask = project.findProject(':app').tasks.findByName('downloadAar')
+      if (downloadAarTask) {
+        task.dependsOn(downloadAarTask)
+      }
+    }
+  }
+  
+  // Also ensure preBuild and compile tasks depend on downloadAar
+  tasks.matching { task ->
+    task.name.startsWith('compile') || task.name.contains('preBuild')
+  }.configureEach { task ->
+    if (project.findProject(':app')) {
+      def downloadAarTask = project.findProject(':app').tasks.findByName('downloadAar')
+      if (downloadAarTask) {
+        task.dependsOn(downloadAarTask)
+      }
+    }
+  }
 }
diff --git a/ffmpeg-kit-react-native.podspec b/ffmpeg-kit-react-native.podspec
index 889d3e8f308f94a338869afedc8c25d8b1ebb53a..a265d7a074054a1b3f66f43117f8db561f05a589 100644
--- a/ffmpeg-kit-react-native.podspec
+++ b/ffmpeg-kit-react-native.podspec
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
 
   s.source       = { :git => "https://github.com/arthenica/ffmpeg-kit.git", :tag => "react.native.v#{s.version}" }
 
-  s.default_subspec   = 'https'
+  s.default_subspec   = 'full-gpl'
 
   s.dependency "React-Core"
 

@daniedu
Copy link

daniedu commented Aug 9, 2025

This thread is a life saver. It finally works on an Expo app in a monorepo using @manssorr's patch. The app is on Expo v50.0.0. and it build the apk no issues in android, havent tried on IOS, Thanks for the help in this thread, didnt need to use a LLM much, the plugin and the patch worked.

@dominiceden
Copy link

dominiceden commented Aug 10, 2025

Thank you so much for this, absolute lifesaver! Worked on my Expo 53 app building with EAS Build.

@DryreL
Copy link

DryreL commented Nov 25, 2025

Awesome thread!

@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