React Native FFmpeg

FFmpeg for React Native
1. Features
- Based on
MobileFFmpeg - Includes both
FFmpegandFFprobe Supports
- Both
AndroidandiOS - FFmpeg
v4.1,v4.2,v4.3andv4.4-devreleases arm-v7a,arm-v7a-neon,arm64-v8a,x86andx86_64architectures on AndroidAndroid API Level 16or laterarmv7,armv7s,arm64,arm64e,i386andx86_64architectures on iOSiOS SDK 9.3or later25 external libraries
fontconfig,freetype,fribidi,gmp,gnutls,kvazaar,lame,libaom,libass,libiconv,libilbc,libtheora,libvorbis,libvpx,libwebp,libxml2,opencore-amr,opus,shine,snappy,soxr,speex,twolame,vo-amrwbenc,wavpack4 external libraries with GPL license
vid.stab,x264,x265,xvidcoreConcurrent execution
zlibandMediaCodecAndroid system librariesbzip2,iconv,libuuid,zlibsystem libraries andAudioToolbox,VideoToolbox,AVFoundationsystem frameworks
- Both
Includes Typescript definitions
- Licensed under LGPL 3.0, can be customized to support GPL v3.0
2. Installation
$ yarn add react-native-ffmpeg
2.1 Android
On React Native < 0.60, manually link the module by running the following command.
$ react-native link react-native-ffmpeg2.2 iOS
2.2.1 Basic
On React Native >= 0.60,
- Add
use_native_modules!to yourPodfileand runpod install
On React Native < 0.60,
Add
react-native-ffmpegpod to yourPodfileand runpod installpod 'react-native-ffmpeg', :podspec => '../node_modules/react-native-ffmpeg/react-native-ffmpeg.podspec'DO NOT USE
react-native linkon iOS.react-native linkbreaks Cocoapods dependencies.
2.3 Packages
ffmpeg includes built-in encoders for some popular formats. However, there are certain external libraries that needs
to be enabled in order to encode specific formats/codecs. For example, to encode an mp3 file you need lame or
shine library enabled. You have to install a react-native-ffmpeg package that has at least one of them inside.
To encode an h264 video, you need to install a package with x264 inside. To encode vp8 or vp9 videos, you need
a react-native-ffmpeg package with libvpx inside.
react-native-ffmpeg provides eight packages that include different sets of external libraries. These packages are
named according to the external libraries included in them. Below you can see which libraries are enabled in each
package.
| min | min-gpl | https | https-gpl | audio | video | full | full-gpl | |
|---|---|---|---|---|---|---|---|---|
| external libraries | - | vid.stab x264 x265 xvidcore |
gmp gnutls |
gmp gnutls vid.stab x264 x265 xvidcore |
lame libilbc libvorbis opencore-amr opus shine soxr speex twolame vo-amrwbenc wavpack |
fontconfig freetype fribidi kvazaar libaom libass libiconv libtheora libvpx libwebp snappy |
fontconfig freetype fribidi gmp gnutls kvazaar lame libaom libass libiconv libilbc libtheora libvorbis libvpx libwebp libxml2 opencore-amr opus shine snappy soxr speex twolame vo-amrwbenc wavpack |
fontconfig freetype fribidi gmp gnutls kvazaar lame libaom libass libiconv libilbc libtheora libvorbis libvpx libwebp libxml2 opencore-amr opus shine snappy soxr speex twolame vid.stab vo-amrwbenc wavpack x264 x265 xvidcore |
| android system libraries | zlib MediaCodec |
|||||||
| ios system libraries | zlib AudioToolbox AVFoundation iconv VideoToolbox bzip2 |
|||||||
Installation of react-native-ffmpeg using instructions in 2.1 and 2.2 enables the default package, which is based on https package.
It is possible to enable other installed packages using the following steps.
2.3.1 Android
Edit
android/build.gradlefile and define package name inext.reactNativeFFmpegPackagevariable.ext { reactNativeFFmpegPackage = "<package name>" }
2.3.2 iOS
Edit
ios/Podfilefile and add package name assubspec. After that runpod installagain.pod 'react-native-ffmpeg/<package name>', :podspec => '../node_modules/react-native-ffmpeg/react-native-ffmpeg.podspec'Note that if you have
use_native_modules!in yourPodfile, specifying asubspecmay cause the following error. You can fix it by definingreact-native-ffmpegdependency beforeuse_native_modules!in yourPodfile.[!] There are multiple dependencies with different sources for `react-native-ffmpeg` in `Podfile`: - react-native-ffmpeg (from `../node_modules/react-native-ffmpeg`) - react-native-ffmpeg/<package name> (from `../node_modules/react-native-ffmpeg/react-native-ffmpeg.podspec`)
2.3.3 Package Names
The following table shows all package names defined for react-native-ffmpeg.
| Package | Main Release | LTS Release |
|---|---|---|
| min | min | min-lts |
| min-gpl | min-gpl | min-gpl-lts |
| https | https | https-lts |
| https-gpl | https-gpl | https-gpl-lts |
| audio | audio | audio-lts |
| video | video | video-lts |
| full | full | full-lts |
| full-gpl | full-gpl | full-gpl-lts |
2.4 LTS Release
react-native-ffmpeg is published in two different variants: Main Release and LTS Release. Both releases share the
same source code but is built with different settings. Below you can see the differences between the two.
In order to install the LTS variant, install the https-lts package using instructions in 2.3 or append -lts to
the package name you are using.
| | Main Release | LTS Release |
| :----: | :----: | :----: |
| Android API Level | 24 | 16 |
| Android Camera Access | Yes | - |
| Android Architectures | arm-v7a-neon
arm64-v8a
x86
x86-64 | arm-v7a
arm-v7a-neon
arm64-v8a
x86
x86-64 |
| Xcode Support | 10.1 | 7.3.1 |
| iOS SDK | 11.0 | 9.3 |
| iOS AVFoundation | Yes | - |
| iOS Architectures | arm64
x86-64
x86-64-mac-catalyst | armv7
arm64
i386
x86-64 |
3. Using
Execute synchronous FFmpeg commands.
Use execute() method with a single command
import { LogLevel, RNFFmpeg } from 'react-native-ffmpeg'; RNFFmpeg.execute('-i file1.mp4 -c:v mpeg4 file2.mp4').then(result => console.log(`FFmpeg process exited with rc=${result}.`));Use executeWithArguments() method with an array of arguments
import { LogLevel, RNFFmpeg } from 'react-native-ffmpeg'; RNFFmpeg.executeWithArguments(["-i", "file1.mp4", "-c:v", "mpeg4", "file2.mp4"]).then(result => console.log(`FFmpeg process exited with rc=${result}.`));
Execute asynchronous FFmpeg commands.
import { LogLevel, RNFFmpeg } from 'react-native-ffmpeg'; RNFFmpeg.executeAsync('-i file1.mp4 -c:v mpeg4 file2.mp4', completedExecution => { if (completedExecution.returnCode === 0) { console.log("FFmpeg process completed successfully"); } else { console.log(`FFmpeg process failed with rc=${completedExecution.returnCode}.`); } }).then(executionId => console.log(`Async FFmpeg process started with executionId ${executionId}.`));Execute FFprobe commands.
Use execute() method with a single command
import { LogLevel, RNFFprobe } from 'react-native-ffmpeg'; RNFFprobe.execute('-i file1.mp4').then(result => console.log(`FFprobe process exited with rc=${result}.`));Use executeWithArguments() method with an array of arguments
import { LogLevel, RNFFprobe } from 'react-native-ffmpeg'; RNFFprobe.executeWithArguments(["-i", "file1.mp4"]).then(result => console.log(`FFmpeg process exited with rc=${result}.`));
Check execution output. Zero represents successful execution, non-zero values represent failure.
RNFFmpegConfig.getLastReturnCode().then(returnCode => { console.log(`Last return code: ${returnCode}`); }); RNFFmpegConfig.getLastCommandOutput().then(output => { console.log(`Last command output: ${output}`); });Stop ongoing FFmpeg operations. Note that these two functions do not wait for termination to complete and return immediately.
- Stop all executions
RNFFmpeg.cancel(); - Stop a specific execution
RNFFmpeg.cancelExecution(executionId);
- Stop all executions
Get media information for a file.
- Print all fields
RNFFprobe.getMediaInformation('<file path or uri>').then(information => { console.log('Result: ' + JSON.stringify(information)); }); Print selected fields
RNFFprobe.getMediaInformation('<file path or uri>').then(information => { if (information.getMediaProperties() !== undefined) { if (information.getMediaProperties().filename !== undefined) { console.log(`Path: ${information.getMediaProperties().filename}`); } if (information.getMediaProperties().format_name !== undefined) { console.log(`Format: ${information.getMediaProperties().format_name}`); } if (information.getMediaProperties().bit_rate !== undefined) { console.log(`Bitrate: ${information.getMediaProperties().bit_rate}`); } if (information.getMediaProperties().duration !== undefined) { console.log(`Duration: ${information.getMediaProperties().duration}`); } if (information.getMediaProperties().start_time !== undefined) { console.log(`Start time: ${information.getMediaProperties().start_time}`); } if (information.getMediaProperties().nb_streams !== undefined) { console.log(`Number of streams: ${information.getMediaProperties().nb_streams.toString()}`); } let tags = information.getMediaProperties().tags; if (tags !== undefined) { Object.keys(tags).forEach((key) => { console.log(`Tag: ${key}:${tags[key]}`); }); } let streams = information.getStreams(); if (streams !== undefined) { for (let i = 0; i < streams.length; ++i) { let stream = streams[i]; console.log("---"); if (stream.getAllProperties().index !== undefined) { console.log(`Stream index: ${stream.getAllProperties().index.toString()}`); } if (stream.getAllProperties().codec_type !== undefined) { console.log(`Stream type: ${stream.getAllProperties().codec_type}`); } if (stream.getAllProperties().codec_name !== undefined) { console.log(`Stream codec: ${stream.getAllProperties().codec_name}`); } } } } });
- Print all fields
Enable log callback and redirect all
FFmpeg/FFprobelogs to a console/file/widget.logCallback = (log) => { this.appendLog(`${log.executionId}:${log.message}`); }; ... RNFFmpegConfig.enableLogCallback(this.logCallback);Enable statistics callback and follow the progress of an ongoing
FFmpegoperation.statisticsCallback = (statistics) => { console.log(`Statistics; executionId: ${statistics.executionId}, video frame number: ${statistics.videoFrameNumber}, video fps: ${statistics.videoFps}, video quality: ${statistics.videoQuality}, size: ${statistics.size}, time: ${statistics.time}, bitrate: ${statistics.bitrate}, speed: ${statistics.speed}`); }; ... RNFFmpegConfig.enableStatisticsCallback(this.statisticsCallback);Poll statistics without implementing statistics callback.
RNFFmpegConfig.getLastReceivedStatistics().then(statistics => console.log('Stats: ' + JSON.stringify(statistics)));List ongoing executions.
RNFFmpeg.listExecutions().then(executionList => { executionList.forEach(execution => { console.log(`Execution id is ${execution.executionId}`); console.log(`Execution start time is ` + new Date(execution.startTime)); console.log(`Execution command is ${execution.command}`); }); });Set log level.
RNFFmpegConfig.setLogLevel(LogLevel.AV_LOG_WARNING);Register your own fonts by specifying a custom fonts directory, so they are available to use in
FFmpegfilters. Please note that this function can not work on relative paths, you need to provide full file system path.- Without any font name mappings
RNFFmpegConfig.setFontDirectory('<folder with fonts>', null); - Apply custom font name mappings. This functionality is very useful if your font name includes ' ' (space) characters in it.
RNFFmpegConfig.setFontDirectory('<folder with fonts>', { my_easy_font_name: "my complex font name" });
- Without any font name mappings
Use your own
fontconfigconfiguration.RNFFmpegConfig.setFontconfigConfigurationPath('<fontconfig configuration directory>');Disable log functionality of the library. Logs will not be printed to console and log callback will be disabled.
RNFFmpegConfig.disableLogs();Disable statistics functionality of the library. Statistics callback will be disabled but the last received statistics data will be still available.
RNFFmpegConfig.disableStatistics();Create new
FFmpegpipe.RNFFmpegConfig.registerNewFFmpegPipe().then(pipe => { console.log("New ffmpeg pipe: " + pipe1); });4. Test Application
You can see how
react-native-ffmpegis used inside an application by running test application provided under the react-native-ffmpeg-test repository. It supports command execution, video encoding, accessing https, encoding audio, burning subtitles, video stabilisation, pipe operations and concurrent command execution.
5. Tips
Apply provided solutions if you encounter one of the following issues.
react-native-ffmpeguses file system paths, it does not know what anassetsfolder or aprojectfolder is. So you can't use resources on those folders directly, you need to provide full paths of those resources.
If
react-native-ffmpegrelease builds on Android fail with the following exception, make sure thatmavenCentral()is defined as a repository in yourbuild.gradleand it is listed beforejcenter().I/ReactNativeJS: Loading react-native-ffmpeg. I/mobile-ffmpeg: Loading mobile-ffmpeg. E/mobile-ffmpeg: OnLoad thread failed to GetStaticMethodID for log. java_vm_ext.cc:577] JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef called with pending exception java.lang.NoSuchMethodError: no static method "Lcom/arthenica/mobileffmpeg/Config;.log(JI[B)V" java_vm_ext.cc:577] at java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader, java.lang.Class) (Runtime.java:-2) java_vm_ext.cc:577] at java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader) (Runtime.java:1131) java_vm_ext.cc:577] at void java.lang.Runtime.loadLibrary0(java.lang.ClassLoader, java.lang.Class, java.lang.String) (Runtime.java:1085) java_vm_ext.cc:577] at void java.lang.Runtime.loadLibrary0(java.lang.Class, java.lang.String) (Runtime.java:1008) java_vm_ext.cc:577] at void java.lang.System.loadLibrary(java.lang.String) (System.java:1664) java_vm_ext.cc:577] at void com.arthenica.mobileffmpeg.Config.<clinit>() (:-1) java_vm_ext.cc:577] at void com.arthenica.mobileffmpeg.Config.c(com.arthenica.mobileffmpeg.h) (:-1) java_vm_ext.cc:577] at void com.arthenica.reactnative.RNFFmpegModule.enableLogEvents() (:-1) java_vm_ext.cc:577] at java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) (Method.java:-2) java_vm_ext.cc:577] at void com.facebook.react.bridge.JavaMethodWrapper.invoke(com.facebook.react.bridge.JSInstance, com.facebook.react.bridge.ReadableArray) (:-1) java_vm_ext.cc:577] at void com.facebook.react.bridge.JavaModuleWrapper.invoke(int, com.facebook.react.bridge.ReadableNativeArray) (:-1) java_vm_ext.cc:577] at void com.facebook.react.bridge.queue.NativeRunnable.run() (:-2) java_vm_ext.cc:577] at void android.os.Handler.handleCallback(android.os.Message) (Handler.java:938) java_vm_ext.cc:577] at void android.os.Handler.dispatchMessage(android.os.Message) (Handler.java:99) java_vm_ext.cc:577] at void com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(android.os.Message) (:-1) java_vm_ext.cc:577] at void android.os.Looper.loop() (Looper.java:223) java_vm_ext.cc:577] at void com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run() (:-1) java_vm_ext.cc:577] at void java.lang.Thread.run() (Thread.java:923) java_vm_ext.cc:577] java_vm_ext.cc:577] in call to NewGlobalRef java_vm_ext.cc:577] from java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader, java.lang.Class)Enabling
ProGuardon releases older thanv0.3.3causes linking errors. Please add the following rule inside yourproguard-rules.profile to preserve necessary method names and prevent linking errors.-keep class com.arthenica.mobileffmpeg.Config { native <methods>; void log(int, byte[]); void statistics(int, float, float, long , int, double, double); }ffmpegrequires a validfontconfigconfiguration to render subtitles. Unfortunately, Android does not include a defaultfontconfigconfiguration. So if you do not register a font or specify afontconfigconfiguration under Android, then the burning process will not produce any errors but subtitles won't be burned in your file. You can overcome this situation by registering a font usingRNFFmpeg.setFontDirectorymethod or specifying your ownfontconfigconfiguration usingRNFFmpeg.setFontconfigConfigurationPathmethod.By default, Xcode compresses
PNGfiles during packaging. If you use.pngfiles in your commands make sure you set the following two settings toNO. If one of them is set toYES, your operations may fail withError while decoding stream #0:0: Generic error in an external libraryerror.
Sometimes
react-native run-iosfails with weird build errors. Execute the following commands and try again.rm -rf ios/Pods ios/build ios/Podfile.lock cd ios pod installAdd
"postinstall": "sed -i '' 's\/#import <RCTAnimation\\/RCTValueAnimatedNode.h>\/#import \"RCTValueAnimatedNode.h\"\/' ./node_modules/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h"line to thescriptssection of yourpackage.jsonas recommended in react-native issue # 13198, if your build receives the following error for iOS.../node_modules/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h:10:9: fatal error: 'RCTAnimation/RCTValueAnimatedNode.h' file not found #import <RCTAnimation/RCTValueAnimatedNode.h> ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 error generated.When
pod installfails with the following message, delete thePodfile.lockfile and runpod installagain.[!] Unable to find a specification for 'react-native-ffmpeg'.If
react-native linkis used for iOS, build may fail with the error below. Runningpod installagain fixes this issue.../node_modules/react-native-ffmpeg/ios/Pods/Target Support Files/Pods-ReactNativeFFmpeg/Pods-ReactNativeFFmpeg.debug.xcconfig: unable to open file (in target "ReactNativeFFmpeg" in project "ReactNativeFFmpeg") (in target 'ReactNativeFFmpeg')On React Native < 0.60, using
cocoapodsfor iOS dependency management may produce duplicate symbols forlibReact.aandlibyoga.a. Add the following block to yourPodfileand runpod installagain.post_install do |installer| installer.pods_project.targets.each do |target| targets_to_ignore = %w(React yoga) if targets_to_ignore.include? target.name target.remove_from_project end end endSome
react-native-ffmpegpackages includelibc++_shared.sonative library. If a second library which also includeslibc++_shared.sois added as a dependency,gradlefails withMore than one file was found with OS independent path 'lib/x86/libc++_shared.so'error message.You can fix this error by adding the following block into your
build.gradle.android { packagingOptions { pickFirst 'lib/x86/libc++_shared.so' pickFirst 'lib/x86_64/libc++_shared.so' pickFirst 'lib/armeabi-v7a/libc++_shared.so' pickFirst 'lib/arm64-v8a/libc++_shared.so' } }
6. Updates
Refer to Changelog for updates.
7. License
This project is licensed under the LGPL v3.0. However, if installation is customized to use a package with -gpl
postfix (min-gpl, https-gpl, full-gpl) then ReactNativeFFmpeg is subject to the GPL v3.0 license.
In test application; embedded fonts are licensed under the SIL Open Font License, other digital assets are published in the public domain.
8. Patents
It is not clearly explained in their documentation, but it is believed that FFmpeg, kvazaar, x264 and x265
include algorithms which are subject to software patents. If you live in a country where software algorithms are
patentable then you'll probably need to pay royalty fees to patent holders. We are not lawyers though, so we recommend
that you seek legal advice first. See FFmpeg Patent Mini-FAQ.
9. Contributing
Feel free to submit issues or pull requests.
Please note that master branch includes only the latest released source code. Changes planned for the next release
are implemented under the development branch. Therefore, if you want to create a pull request, please open it against
the development.