maitrungduc1410 / react-native-video-trim

Video trimmer for React Native App
MIT License
60 stars 24 forks source link
react-native react-native-video react-native-video-editor react-native-video-processing react-native-video-trim

React Native Video Trim

Video trimmer for your React Native app

Features

Installation

npm install react-native-video-trim

# or with yarn

yarn add react-native-video-trim

For iOS (React Native CLI project)

Run the following command to setup for iOS:

npx pod-install ios

For Expo project

You need to run prebuild in order for native code takes effect:

npx expo prebuild

npx pod-install ios

Usage

[!IMPORTANT]
Note that for both Android and iOS you have to try on real device

[!IMPORTANT]
If you plan to trim remote file, you must install FFMPEG version from "https" onwards, "min" version won't work. See bottom to know how to install specific FFMPEG version

import { showEditor } from 'react-native-video-trim';

// ...

showEditor(videoUrl);

// or with output length limit

showEditor(videoUrl, {
  maxDuration: 20,
});

Usually this library will be used along with other library to select video file, Eg. react-native-image-picker. Below is real world example:

import * as React from 'react';

import {
  StyleSheet,
  View,
  Text,
  TouchableOpacity,
  NativeEventEmitter,
  NativeModules,
} from 'react-native';
import { isValidFile, showEditor } from 'react-native-video-trim';
import { launchImageLibrary } from 'react-native-image-picker';
import { useEffect } from 'react';

export default function App() {
  useEffect(() => {
    const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
    const subscription = eventEmitter.addListener('VideoTrim', (event) => {
      switch (event.name) {
        case 'onLoad': {
          // on media loaded successfully
          console.log('onLoadListener', event);
          break;
        }
        case 'onShow': {
          console.log('onShowListener', event);
          break;
        }
        case 'onHide': {
          console.log('onHide', event);
          break;
        }
        case 'onStartTrimming': {
          console.log('onStartTrimming', event);
          break;
        }
        case 'onFinishTrimming': {
          console.log('onFinishTrimming', event);
          break;
        }
        case 'onCancelTrimming': {
          console.log('onCancelTrimming', event);
          break;
        }
        case 'onCancel': {
          console.log('onCancel', event);
          break;
        }
        case 'onError': {
          console.log('onError', event);
          break;
        }
        case 'onLog': {
          console.log('onLog', event);
          break;
        }
        case 'onStatistics': {
          console.log('onStatistics', event);
          break;
        }
      }
    });

    return () => {
      subscription.remove();
    };
  }, []);

  return (
    <View style={styles.container}>
      <TouchableOpacity
        onPress={async () => {
          const result = await launchImageLibrary({
            mediaType: 'video',
            assetRepresentationMode: 'current',
          });

          isValidFile(result.assets![0]?.uri || '').then((res) =>
            console.log(res)
          );

          showEditor(result.assets![0]?.uri || '', {
            maxDuration: 20,
          });
        }}
        style={{ padding: 10, backgroundColor: 'red' }}
      >
        <Text>Launch Library</Text>
      </TouchableOpacity>
      <TouchableOpacity
        onPress={() => {
          isValidFile('invalid file path').then((res) => console.log(res));
        }}
        style={{
          padding: 10,
          backgroundColor: 'blue',
          marginTop: 20,
        }}
      >
        <Text>Check Video Valid</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Methods

showEditor(videoPath: string, config?: EditorConfig)

Main method to show Video Editor UI.

Params:

If saveToPhoto = true, you must ensure that you have request permission to write to photo/gallery

If openShareSheetOnFinish=true, on Android you'll need to update AndroidManifest.xml like below:

</application>
  ...
  <provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/file_paths" />
  </provider>
</application>

If you face issue when building Android app related to file_paths, then you may need to create res/xml/file_paths.xml: with the following content:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <files-path name="internal_files" path="." />
  <external-path name="external_files" path="." />
</paths>

isValidFile(videoPath: string)

This method is to check if a path is a valid video/audio

closeEditor()

Close Editor

listFiles()

Return array of generated output files in app storage. (Promise<string[]>)

cleanFiles()

Clean all generated output files in app storage. Return number of successfully deleted files (Promise<number>)

deleteFile()

Delete a file in app storage. Return true if success

Events

To listen for events you interest, do the following:

useEffect(() => {
  const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
  const subscription = eventEmitter.addListener('VideoTrim', (event) => {
    switch (event.name) {
      case 'onLoad': {
        console.log('onLoadListener', event);
        break;
      }
      case 'onShow': {
        console.log('onShowListener', event);
        break;
      }
      case 'onHide': {
        console.log('onHide', event);
        break;
      }
      case 'onStartTrimming': {
        console.log('onStartTrimming', event);
        break;
      }
      case 'onFinishTrimming': {
        console.log('onFinishTrimming', event);
        break;
      }
      case 'onCancelTrimming': {
        console.log('onCancelTrimming', event);
        break;
      }
      case 'onCancel': {
        console.log('onCancel', event);
        break;
      }
      case 'onError': {
        console.log('onError', event);
        break;
      }
      case 'onLog': {
        console.log('onLog', event);
        break;
      }
      case 'onStatistics': {
        console.log('onStatistics', event);
        break;
      }
    }
  });

  return () => {
    subscription.remove();
  };
}, []);

Audio support

For audio only you have to pass type=audio and outputExt:

showEditor(url, {
  type: 'audio', // important
  outputExt: 'wav', // important: any audio type for output file extension
})

You must install FFMPEG version from "https" onwards, "min" version won't work. Eg.

// Android: android/build.gradle > buildscript > ext

buildscript {
    ext {
        ffmpegKitPackage = "full"
    }

---
// iOS:

FFMPEGKIT_PACKAGE=https npx pod-install ios

// or

FFMPEGKIT_PACKAGE=https pod install

Cancel trimming

While trimming, you can press Cancel to terminate the process.

Related props: enableCancelTrimming, cancelTrimmingButtonText, enableCancelTrimmingDialog, cancelTrimmingDialogTitle, cancelTrimmingDialogMessage, cancelTrimmingDialogCancelText, cancelTrimmingDialogConfirmText

Fail to load media

If there's error while loading media, there'll be a prompt

Related props: alertOnFailToLoad, alertOnFailTitle, alertOnFailMessage, alertOnFailCloseText

Customize FFMPEG version

This library uses FFMPEG-Kit Android under the hood, by default FFMPEG min is used, which gives smallest bundle size. To customize the package version please see below

Android

If you ever need to use other version of FFMPEG-Kit for Android, you can do the following, in your android/build.gradle > buildscript > ext:

buildscript {
    ext {
        ffmpegKitPackage = "full" // default "min"

        ffmpegKitPackageVersion = "5.1.LTS" // default 6.0-2
    }

iOS

Same as Android, there're 2 environment variables respectively you can use to specify FFMPEG Kit version you want to use: FFMPEG_KIT_PACKAGE and FFMPEG_KIT_PACKAGE_VERSION.

You need to pass the variables when running pod install. Eg:

# override package name, default: min
FFMPEGKIT_PACKAGE=full npx pod-install ios

# override package version, default: '~> 6.0
FFMPEGKIT_PACKAGE_VERSION=5.1 npx pod-install ios

# or both
FFMPEGKIT_PACKAGE=full FFMPEGKIT_PACKAGE_VERSION=5.1 npx pod-install ios

Packages

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
dav1d
fontconfig
freetype
fribidi
kvazaar
libass
libiconv
libtheora
libvpx
libwebp
snappy
zimg
dav1d
fontconfig
freetype
fribidi
gmp
gnutls
kvazaar
lame
libass
libiconv
libilbc
libtheora
libvorbis
libvpx
libwebp
libxml2
opencore-amr
opus
shine
snappy
soxr
speex
twolame
vo-amrwbenc
zimg
dav1d
fontconfig
freetype
fribidi
gmp
gnutls
kvazaar
lame
libass
libiconv
libilbc
libtheora
libvorbis
libvpx
libwebp
libxml2
opencore-amr
opus
shine
snappy
soxr
speex
twolame
vid.stab
vo-amrwbenc
x264
x265
xvidcore
zimg
android system libraries zlib
MediaCodec
ios system libraries bzip2
AudioToolbox
AVFoundation
iconv
VideoToolbox
zlib
macos system libraries bzip2
AudioToolbox
AVFoundation
Core Image
iconv
OpenCL
OpenGL
VideoToolbox
zlib
tvos system libraries bzip2
AudioToolbox
iconv
VideoToolbox
zlib

Android: update SDK version

You can override sdk version to use any version in your android/build.gradle > buildscript > ext

buildscript {
    ext {
        VideoTrim_compileSdkVersion = 34
        VideoTrim_minSdkVersion = 26
        VideoTrim_targetSdkVersion = 34
    }
}

Naming conflict with ffmpeg-kit-react-native

This issue is due to this package and ffmpeg-kit-react-native have same ffmpegkit class name under the hood:

To fix it we need to synchronize the FFMPEG Kit version of the 2 packages. For example: as shown in the image above, ffmpeg-kit-react-native uses https, version 6.0, hence we need to run this:

FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install

Another way to find out exactly FFMPEG Kit version that ffmpeg-kit-react-native using is to (check here for package name) and package version is version in package.json

When you know the package name + version, simply run pod install with FFMPEGKIT_PACKAGE and FFMPEG_KIT_PACKAGE_VERSION like above

Thanks