maitrungduc1410 / react-native-video-trim

Video trimmer for React Native App
MIT License
45 stars 21 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

FFMPEG Version

This library uses FFMPEG-Kit Android under the hood, by default FFMPEG-min is used, which gives smallest bundle size: https://github.com/arthenica/ffmpeg-kit#9-packages

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

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

Thanks