react: ^17.0.1 => 17.0.2
react-dom: ^17.0.1 => 17.0.2
react-native: https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz => 0.63.2
react-native-web: ~0.15.0 => 0.15.7
Expo Workflow: managed
eas-cli/0.22.0 linux-x64 node-v14.17.3
I’m trying to add
react-native-ffmpeg
to my project with video-lts packages (LTS use minSdkVersion 16).
How to edit build.gradle to define package name in ext?
Or how to increase minSdkVersion to required level?
import { ExpoConfig, ConfigContext } from '@expo/config';
import { ConfigPlugin, withAppBuildGradle } from '@expo/config-plugins';
const withFfmpegMod: ConfigPlugin = (config) => {
return withAppBuildGradle(config, config => {
config.mods = {
...config.mods,
android: {
appBuildGradle: (config) => {
config.modResults.contents = `ext {
reactNativeFFmpegPackage = "video-lts"
return config;
return config;
export default ({ config }: ConfigContext): ExpoConfig => withFfmpegMod(config as ExpoConfig);
"Run gradlew" phase.
See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.
[stderr] /build/workingdir/build/android/app/src/debug/AndroidManifest.xml Error:
[stderr] uses-sdk:minSdkVersion 21 cannot be smaller than version 24 declared in library [:react-native-ffmpeg] /build/workingdir/build/node_modules/react-native-ffmpeg/android/build/intermediates/library_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 21
[stderr] Suggestion: use a compatible library with a minSdk of at most 21,
[stderr] or increase this project's minSdk version to at least 24,
[stderr] or use tools:overrideLibrary="com.arthenica.reactnative" to force usage (may lead to runtime failures)
> Task :unimodules-core:compileDebugKotlin
[stderr] FAILURE: Build failed with an exception.
[stderr] * What went wrong:
[stderr] Execution failed for task ':app:processDebugMainManifest'.
[stderr] > Manifest merger failed : uses-sdk:minSdkVersion 21 cannot be smaller than version 24 declared in library [:react-native-ffmpeg] /build/workingdir/build/node_modules/react-native-ffmpeg/android/build/intermediates/library_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 21
[stderr] Suggestion: use a compatible library with a minSdk of at most 21,
[stderr] or increase this project's minSdk version to at least 24,
[stderr] or use tools:overrideLibrary="com.arthenica.reactnative" to force usage (may lead to runtime failures)
[stderr] * Try:
[stderr] Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
[stderr] * Get more help at https://help.gradle.org
[stderr] BUILD FAILED in 2m 1s
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.8/userguide/command_line_interface.html#sec:command_line_warnings
429 actionable tasks: 429 executed
Error: Gradle build failed with unknown error. Please see logs for the "Run gradlew" phase.
EDIT: I’ve found an easier way to set *SdkVersion without writing your own plugin
Hey @expo_karaushu
I just managed to get this to work! (Android only for now).
This is my first time trying to write my own config plugins, so I’m sure some things could be done better. Also, I don’t like all the manual config.replace(/regex/, string)
and the brute force way I’m replacing the packagingOptions
, but at least it seems to work!
I only tried building for Android so far and have not actually tested the results yet.
I created the following files:
plugins/withMinAndroidSdkVersion.js
const {
withProjectBuildGradle,
withPlugins,
} = require('@expo/config-plugins');
function setMinSdkVersion(buildGradle, minVersion) {
const regexpMinSdkVersion = /\bminSdkVersion\s*=\s*(\d+)/;
const match = buildGradle.match(regexpMinSdkVersion);
if (match) {
const version = parseInt(match[1], 10);
if (version < minVersion) {
buildGradle = buildGradle.replace(
/\bminSdkVersion\s*=\s*\d+/,
`minSdkVersion = ${minVersion}`
} else {
console.warn(`WARN: minSdkVersion is already >= ${version}`);
return buildGradle;
const withMinSdkVersion = (config, { minSdkVersion } = {}) => {
return withProjectBuildGradle(config, (config) => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = setMinSdkVersion(
config.modResults.contents,
minSdkVersion
} else {
throw new Error(
"Can't set minSdkVersion in the project build.gradle, because it's not groovy"
return config;
module.exports = (config, props) =>
withPlugins(config, [
[withMinSdkVersion, props],
plugins/withFfmpegPackage.js
const {
withAppBuildGradle,
withProjectBuildGradle,
withPlugins,
} = require('@expo/config-plugins');
function setFfmpegPackage(buildGradle, packageName) {
const regexpReactNativeFfmpegPackage =
/\breactNativeFFmpegPackage\s*=\s*"([^"]*)"/;
const match = buildGradle.match(regexpReactNativeFfmpegPackage);
if (match) {
return buildGradle.replace(
regexpReactNativeFfmpegPackage,
`reactNativeFFmpegPackage = "${packageName}"`
// Set the ffmpeg native package
return buildGradle.replace(
/\bext\s?{/,
`ext {
reactNativeFFmpegPackage = "${packageName}"`
function addPickFirst(buildGradle, paths) {
const regexpPackagingOptions = /\bpackagingOptions\s*{[^}]*}/;
const packagingOptionsMatch = buildGradle.match(
regexpPackagingOptions
let bodyLines = [];
paths.forEach((path) => {
bodyLines.push(` pickFirst '${path}'`);
let body = bodyLines.join('\n');
if (packagingOptionsMatch) {
console.warn(
'WARN: Replacing packagingOptions in app build.gradle'
return buildGradle.replace(
regexpPackagingOptions,
`packagingOptions {
${body}
const regexpAndroid = /\nandroid\s*{/;
const androidMatch = buildGradle.match(regexpAndroid);
if (androidMatch) {
return buildGradle.replace(
regexpAndroid,
android {
packagingOptions {
${body}
throw new Error('Could not find where to add packagingOptions');
const withFfmpegPackage = (
config,
{ ffmpegPackage: packageName = 'min' } = {}
) => {
return withProjectBuildGradle(config, (config) => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = setFfmpegPackage(
config.modResults.contents,
packageName
} else {
throw new Error(
"Can't set ffmpeg package name in the project build.gradle, because it's not groovy"
return config;
const withPickFirstFbjni = (config, props = {}) => {
return withAppBuildGradle(config, (config) => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = addPickFirst(
config.modResults.contents,
['lib/**/libfbjni.so', 'lib/**/libc++_shared.so']
} else {
throw new Error(
"Can't add pickFirst '**/libfbjni.so' because app build.grandle is not groovy"
return config;
module.exports = (config, props) =>
withPlugins(config, [
[withFfmpegPackage, props],
[withPickFirstFbjni, props],
Then I added the plugins to app.json
like this:
"plugins": [
"./plugins/withMinAndroidSdkVersion.js",
"minSdkVersion": 24
"./plugins/withFfmpegPackage.js",
"ffmpegPackage": "full"
This took a lot of trial and error to get it to build. I had first got the ffmpeg package name working, but then got the same error about the minSdkVersion
as you got. I wrote another plugin to fix that and then I got an error about duplicate libfbjni.so
files:
[stderr] FAILURE: Build failed with an exception.
[stderr] * What went wrong:
[stderr] Execution failed for task ':app:mergeDebugNativeLibs'.
[stderr] > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
[stderr] > More than one file was found with OS independent path 'lib/x86/libfbjni.so'. If you are using jniLibs and CMake IMPORTED targets, see https://developer.android.com/studio/preview/features#automatic_packaging_of_prebuilt_dependencies_used_by_cmake
I managed to fix that, after which I got the same error, but this time about libc++_shared.so
.
When I first started this I got complaints about not being able to use import
and export
. How are you doing it that you did not get those errors? Or is that just a side effect of using TypeScript?
@charliecruzan, do you have any suggestions on how to improve my config plugins?
@wodin The package you guys used is deprecated and superseded by FFmpegKit now. GitHub - tanersener/react-native-ffmpeg: FFmpeg for react-native. Not maintained anymore. Superseded by FFmpegKit.
And here is the new Package living.
I have not looked at this again since September, but the installation instructions look very promising. As far as I can see, for Android, all you need to do is add the following to build.gradle
:
ext {
ffmpegKitPackage = "<package name>"
It’s not clear to me whether you’d need to specify the minSdkVersion
or make it pick the first libfbjni.so
or libc++_shared.so
, but I suspect you might.
Anyway it seems like you should be able to make some minor modifications to my config plugin to get it to work with ffmpeg-kit
.
I did not bother with making an npm out of the plugins. I just created a “plugin” subdirectory in the app, saved the plugins there and then referenced them using “./plugin/xxx.js”.
Then I tested by running expo prebuild
.
I’ve been working on integrating ffmpeg-kit-react-native for a bit now. Specifically on android I’m running into the same issue @wodin mentioned with duplicate libc++_shared.so
. I found that this is a common issue with the library which has a known fix. However I’m not 100% how to implement this change with a config plugin because I don’t know really know the difference between withAppBuildGradle and withProjectBuildGradle mods from expo/config-plugins. Currently I’m working based off of @bacon’s plugin on the @config-plugins/ffmpeg-kit-react-native
repo. I’ll let you know if I get it working with a patch-package.
export const withAndroidPackagingOptions: ConfigPlugin = (config) => {
return withAppBuildGradle(config, (config) => {
if (config.modResults.language === "groovy") {
config.modResults.contents = addAndroidPackagingOptions(
config.modResults.contents
).contents;
} else {
WarningAggregator.addWarningAndroid(
"@config-plugins/react-native-pdf",
`Cannot automatically configure app build.gradle if it's not groovy`
main/packages/ffmpeg-kit-react-native
Out-of-tree Expo config plugins for packages that haven't adopted the config plugin system yet.