Skip to content

Using FFmpegKitNext on React Native

Taner Sener edited this page Jul 15, 2026 · 3 revisions

This page shows how to use FFmpegKitNext in a React Native application after the ffmpeg-kit-next-react-native package and its native binaries have already been added to the app.

The React Native package supports Android, iOS and iPadOS. Android-only APIs are marked below.

1. Import the API

Import only the classes your app uses.

import {
  FFmpegKit,
  FFmpegKitConfig,
  FFprobeKit
} from 'ffmpeg-kit-next-react-native';

Most API calls initialize the native module automatically. If you want to suppress the native load confirmation, initialize before the first FFmpegKit call.

await FFmpegKitConfig.init(false);

Calling FFmpegKitConfig.uninit() before application termination is optional.

2. Execute FFmpeg

execute returns a promise that resolves with an FFmpegSession after the command finishes.

const session = await FFmpegKit.execute('-i file1.mp4 -c:v mpeg4 file2.mp4');
const returnCode = await session.getReturnCode();

if (ReturnCode.isSuccess(returnCode)) {
  // SUCCESS
} else if (ReturnCode.isCancel(returnCode)) {
  // CANCEL
} else {
  const state = FFmpegKitConfig.sessionStateToString(await session.getState());
  const output = await session.getAllLogsAsString();
  const failStackTrace = await session.getFailStackTrace();

  console.log(`FFmpeg failed with state ${state} and rc ${returnCode}.`);
  console.log(output);
  console.log(failStackTrace);
}

For paths, filtergraphs, SAF URLs or user-provided values, prefer argument arrays. They avoid quoting and escaping problems caused by command-string parsing.

const session = await FFmpegKit.executeWithArguments([
  '-i', inputPath,
  '-vf', 'scale=1280:-2',
  '-c:v', 'mpeg4',
  outputPath,
]);

Use executeAsync or executeWithArgumentsAsync for normal UI flows. The method returns after the native session is scheduled; the complete callback receives the final session.

const session = await FFmpegKit.executeWithArgumentsAsync([
  '-i', inputPath,
  '-vf', 'scale=1280:-2',
  '-c:v', 'mpeg4',
  outputPath,
], async completedSession => {
  const rc = await completedSession.getReturnCode();

  if (ReturnCode.isSuccess(rc)) {
    // SUCCESS
  } else {
    console.log(await completedSession.getAllLogsAsString());
  }
}, log => {
  console.log(log.getMessage());
}, statistics => {
  console.log(`frame=${statistics.getVideoFrameNumber()} time=${statistics.getTime()} speed=${statistics.getSpeed()}`);
});

console.log(`Started FFmpeg session ${session.getSessionId()}.`);

3. Inspect Sessions

Every FFmpeg, FFprobe and media-information execution creates a session.

const session = await FFmpegKit.executeWithArguments(arguments);

const sessionId = session.getSessionId();
const command = session.getCommand();
const commandArguments = session.getArguments();
const state = await session.getState();
const returnCode = await session.getReturnCode();

const createTime = session.getCreateTime();
const startTime = session.getStartTime();
const endTime = await session.getEndTime();
const duration = await session.getDuration();

const output = await session.getOutput();
const failStackTrace = await session.getFailStackTrace();

const logs = await session.getAllLogs();
const statistics = await session.getAllStatistics();
const lastStatistics = await session.getLastReceivedStatistics();

getLogs() and getLogsAsString() return the log entries already delivered to the session. getAllLogs(), getAllLogsAsString() and getAllStatistics() wait briefly for asynchronous native messages that may still be in transit.

Session history keeps recent sessions. Set the history size before starting heavy batches if you need more or fewer entries.

await FFmpegKitConfig.setSessionHistorySize(100);

const allSessions = await FFmpegKitConfig.getSessions();
const ffmpegSessions = await FFmpegKitConfig.getFFmpegSessions();
const ffprobeSessions = await FFmpegKitConfig.getFFprobeSessions();
const mediaInformationSessions = await FFmpegKitConfig.getMediaInformationSessions();
const runningSessions = await FFmpegKitConfig.getSessionsByState(SessionState.RUNNING);

const lastSession = await FFmpegKitConfig.getLastSession();
const lastCompletedSession = await FFmpegKitConfig.getLastCompletedSession();

await FFmpegKitConfig.deleteSession(sessionId);
await FFmpegKitConfig.clearSessions();

Use FFmpegKit.listSessions() when you only need FFmpeg sessions and FFprobeKit.listFFprobeSessions() / FFprobeKit.listMediaInformationSessions() for FFprobe-related history.

4. Execute FFprobe and Read Media Information

Use FFprobeKit for FFprobe commands.

const session = await FFprobeKit.execute(
  '-hide_banner -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 file1.mp4',
);

if (!ReturnCode.isSuccess(await session.getReturnCode())) {
  console.log(await session.getOutput());
}

Argument-array execution is available for FFprobe as well.

await FFprobeKit.executeWithArgumentsAsync([
  '-hide_banner',
  '-print_format', 'json',
  '-show_format',
  '-show_streams',
  inputPath,
], async completedSession => {
  console.log(await completedSession.getOutput());
}, log => {
  console.log(log.getMessage());
});

Use getMediaInformation when you want parsed format, stream and chapter metadata.

const mediaSession = await FFprobeKit.getMediaInformation(inputPath);
const information = mediaSession.getMediaInformation();

if (information !== undefined) {
  const duration = information.getDuration();
  const format = information.getFormat();
  const bitrate = information.getBitrate();
  const streamCount = information.getStreams().length;
}

Custom media information commands must print FFprobe JSON output.

const customSession = await FFprobeKit.getMediaInformationFromCommandArguments([
  '-v', 'error',
  '-hide_banner',
  '-print_format', 'json',
  '-show_format',
  '-show_streams',
  '-i', inputPath,
]);

Prefer getMediaInformation(path) or getMediaInformationFromCommandArguments(...) for paths and URLs. If you use getMediaInformationFromCommand(...), quote or escape paths yourself.

5. Cancel Running Work

Cancel all running FFmpeg sessions:

await FFmpegKit.cancel();

Cancel one running FFmpeg session:

await FFmpegKit.cancel(sessionId);

You can also cancel through a session object:

await session.cancel();

Cancellation is asynchronous. The session callback is still the right place to observe the final state and return code.

6. Use Storage Access Framework URIs on Android

Android content:// URIs from the system picker cannot be passed directly to FFmpeg. Convert them to ffkitsaf: URLs first.

const uri = await FFmpegKitConfig.selectDocumentForRead('*/*', [
  'image/*',
  'video/*',
  'audio/*',
]);

if (uri != null) {
  const inputUrl = await FFmpegKitConfig.getSafParameterForRead(uri);

  if (inputUrl != null) {
    await FFmpegKit.executeWithArgumentsAsync([
      '-i', inputUrl,
      '-c:v', 'mpeg4',
      outputPath,
    ], async session => {
      console.log(await session.getReturnCode());
    });
  }
}

Create a write URL with the Android document picker:

const uri = await FFmpegKitConfig.selectDocumentForWrite('video.mp4', 'video/*');

if (uri != null) {
  const outputUrl = await FFmpegKitConfig.getSafParameterForWrite(uri);

  if (outputUrl != null) {
    await FFmpegKit.executeWithArguments([
      '-i', inputPath,
      '-c:v', 'mpeg4',
      outputUrl,
    ]);
  }
}

Use a custom open mode when Android's ContentResolver.openFileDescriptor needs one.

const readWriteUrl = await FFmpegKitConfig.getSafParameter(uri, 'rw');

By default, a generated SAF URL is not reusable and is released automatically after the native file descriptor is closed. If you need to use the same URL in more than one command, create it with the reusable flag set to true and unregister it yourself after the last command.

const reusableInputUrl = await FFmpegKitConfig.getSafParameterForRead(uri, true);

if (reusableInputUrl != null) {
  try {
    await FFprobeKit.executeWithArguments([
      '-hide_banner',
      '-show_streams',
      reusableInputUrl,
    ]);

    await FFprobeKit.executeWithArguments([
      '-hide_banner',
      '-show_format',
      reusableInputUrl,
    ]);
  } finally {
    await FFmpegKitConfig.unregisterSafProtocolUrl(reusableInputUrl);
  }
}

The SAF picker and SAF URL conversion methods are Android-only.

7. Configure Callbacks, Logs and Redirection

Global callbacks apply to all matching sessions. Pass undefined to disable a callback.

FFmpegKitConfig.enableFFmpegSessionCompleteCallback(async session => {
  console.log(`FFmpeg session completed: ${session.getSessionId()}`);
});

FFmpegKitConfig.enableFFprobeSessionCompleteCallback(async session => {
  console.log(`FFprobe session completed: ${session.getSessionId()}`);
});

FFmpegKitConfig.enableMediaInformationSessionCompleteCallback(async session => {
  console.log(`Media information session completed: ${session.getSessionId()}`);
});

FFmpegKitConfig.enableLogCallback(log => {
  console.log(log.getMessage());
});

FFmpegKitConfig.enableStatisticsCallback(statistics => {
  console.log(`time=${statistics.getTime()} size=${statistics.getSize()}`);
});

Set the active FFmpeg log level when you need more or less native output.

await FFmpegKitConfig.setLogLevel(Level.AV_LOG_INFO);

Log and statistics redirection is enabled by default. If you call FFmpegKitConfig.disableRedirection(), log callbacks, statistics callbacks and FFprobeKit.getMediaInformation methods stop working. Use enableRedirection() to turn it back on.

await FFmpegKitConfig.disableRedirection();
await FFmpegKitConfig.enableRedirection();

React Native also exposes independent switches for log and statistics callback delivery.

await FFmpegKitConfig.disableLogs();
await FFmpegKitConfig.enableLogs();

await FFmpegKitConfig.disableStatistics();
await FFmpegKitConfig.enableStatistics();

Use log redirection strategies to control native console printing when callbacks are or are not registered.

FFmpegKitConfig.setLogRedirectionStrategy(
  LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED,
);

For one quiet session, create the session explicitly and execute it through FFmpegKitConfig.

const quietSession = await FFprobeSession.create(
  FFmpegKitConfig.parseArguments(ffprobeCommand),
  async session => {
    console.log(await session.getReturnCode());
  },
  undefined,
  LogRedirectionStrategy.NEVER_PRINT_LOGS,
);

await FFmpegKitConfig.asyncFFprobeExecute(quietSession);

8. Use Fonts and drawtext

The FFmpeg drawtext filter requires native binaries that include freetype and libharfbuzz. If you want to use font family names through fontconfig, the native binaries must also include fontconfig. Without fontconfig, use an explicit fontfile path.

await FFmpegKit.executeWithArguments([
  '-i', inputPath,
  '-vf', `drawtext=fontfile=${fontFilePath}:text='Hello':x=20:y=20`,
  outputPath,
]);

Register platform font directories and custom font-name mappings when fontconfig is available.

await FFmpegKitConfig.setFontDirectoryList([
  customFontDirectory,
  '/system/fonts',
  '/System/Library/Fonts',
], {
  MyFontName: 'Doppio One',
});

await FFmpegKit.executeWithArguments([
  '-i', inputPath,
  '-vf', "drawtext=font='MyFontName':text='Hello':x=20:y=20",
  outputPath,
]);

Use setFontDirectory(path) for a single directory.

9. Use FFmpegKit Protocols

FFmpegKitNext provides custom protocol helpers for data that is not represented by a normal filesystem path.

  • ffkitsaf: is used by the Android SAF helpers in section 6.
  • ffkitmem: is a seekable finite in-memory input/output protocol.
  • ffkitstream: is a non-seekable memory-backed streaming input/output protocol.

React Native exchanges protocol buffer data with the native layer as base64 strings.

Use ffkitmem: for finite base64 inputs and outputs.

const input = await FFmpegKitInputBuffer.fromBase64(inputBase64, 'jpg');
const output = await FFmpegKitOutputBuffer.create('jpg');

await FFmpegKit.executeWithArgumentsAsync([
  '-i', input.getUrl(),
  '-vf', 'scale=640:-2',
  output.getUrl(),
], async session => {
  try {
    if (ReturnCode.isSuccess(await session.getReturnCode())) {
      const convertedBase64 = await output.toBase64();
      const outputSize = await output.getSize();
      console.log(`Converted base64 length ${convertedBase64.length}, stored ${outputSize} bytes.`);
    } else {
      console.log(await session.getAllLogsAsString());
    }
  } finally {
    await input.close();
    await output.close();
  }
});

Use ffkitstream: when data is produced or consumed incrementally. Stream reads and writes can wait for native buffers, so do not run heavy stream loops on a performance-sensitive UI path.

const input = await FFmpegKitStreamInput.create('raw');

await FFmpegKit.executeWithArgumentsAsync([
  '-f', 's16le',
  '-ar', '48000',
  '-ac', '2',
  '-i', input.getUrl(),
  outputPath,
], async session => {
  await input.close();
});

const written = await input.write(pcmBase64Chunk, 5000);
if (written === 0) {
  // Retry or handle backpressure before closing the input.
}
await input.closeInput();

For streaming output, read until an empty string signals end of stream.

const output = await FFmpegKitStreamOutput.create('raw');

await FFmpegKit.executeWithArgumentsAsync([
  '-i', inputPath,
  '-f', 's16le',
  '-acodec', 'pcm_s16le',
  output.getUrl(),
], async session => {
  console.log(`Stream output command completed: ${await session.getReturnCode()}`);
});

try {
  while (true) {
    const base64Chunk = await output.read(4096);
    if (base64Chunk.length === 0) {
      break;
    }

    consume(base64Chunk);
  }
} finally {
  await output.close();
}

Close every FFmpegKitInputBuffer, FFmpegKitOutputBuffer, FFmpegKitStreamInput and FFmpegKitStreamOutput you create.

10. Use Named Pipes

Named pipes are useful when FFmpeg should read from or write to a path while another part of the app supplies the bytes.

const pipe = await FFmpegKitConfig.registerNewFFmpegPipe();
if (pipe == null) {
  throw new Error('Failed to create FFmpeg pipe.');
}

await FFmpegKit.executeWithArgumentsAsync([
  '-i', pipe,
  '-c:v', 'mpeg4',
  outputPath,
], async session => {
  await FFmpegKitConfig.closeFFmpegPipe(pipe);
});

await FFmpegKitConfig.writeToPipe(inputPath, pipe);

Always close pipes with closeFFmpegPipe when the command is done.

11. Platform and Diagnostic Helpers

Read version and package information when reporting diagnostics.

const platform = await FFmpegKitConfig.getPlatform();
const arch = await ArchDetect.getArch();
const ffmpegVersion = await FFmpegKitConfig.getFFmpegVersion();
const ffmpegKitVersion = await FFmpegKitConfig.getVersion();
const buildDate = await FFmpegKitConfig.getBuildDate();
const externalLibraries = await Packages.getExternalLibraries();

Set native environment variables before running the command that needs them.

await FFmpegKitConfig.setEnvironmentVariable('FFREPORT', `file=${reportPath}`);

List Android camera IDs supported by FFmpegKit on the current Android device. This method is Android-only.

const cameraIds = await FFmpegKitConfig.getSupportedCameraIds();

Ignore a signal when embedding FFmpegKit in frameworks that also manage signals, such as Unity, Xamarin or other Mono-based runtimes.

await FFmpegKitConfig.ignoreSignal(Signal.SIGXCPU);

12. Test Applications

The React Native test application in the ffmpeg-kit-next-test project shows complete UI examples for commands, FFprobe, SAF, ffkitmem, pipes, fonts, concurrent execution and statistics.

Clone this wiki locally