Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 80 additions & 3 deletions App.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React, {Component} from 'react';

// Use to access video files https://github.com/react-native-image-picker/react-native-image-picker/tree/main
import {launchImageLibrary} from 'react-native-image-picker';

import {
StyleSheet,
Text,
Button,
View,
Platform,
NativeModules,
PermissionsAndroid,
} from 'react-native';
const {SdkEditorModule} = NativeModules;

Expand All @@ -23,12 +28,68 @@ async function openVideoEditor() {

async function openVideoEditorPIP() {
initSDK();
return await SdkEditorModule.openVideoEditorPIP();

// PLEASE GRANT ALL PERMISSIONS TO PROCEED
// The implementation below is for demonstration purposes to show how to use vide for PIP mode
await grantMediaPermissions()

const videoOptions: ImageLibraryOptions = {
mediaType: 'video',
videoQuality: 'high',
formatAsMp4: false,
quality: 1,
includeBase64: false,
selectionLimit: 1,
durationLimit: 0,
};

const result = await launchImageLibrary(videoOptions);

const videoPath = result.assets[0].originalPath
const videoUri = result.assets[0].uri
console.log('Open video editor in pip mode with video: path = ' + videoPath + ', uri = ' + videoUri);

if (Platform.OS === 'android') {
// IMPORTANT requirements
// There are 2 types of videoPath - external(/storage/emulated/0/Movies/sample.mp4) and internal (/data/data/$applicationId/...)
// Access to media is required for external - please grant all permissions.
return await SdkEditorModule.openVideoEditorPIP(videoPath);
} else {
return await SdkEditorModule.openVideoEditorPIP(videoUri);
}
}

async function openVideoEditorTrimmer() {
initSDK();
return await SdkEditorModule.openVideoEditorTrimmer();
initSDK();

// PLEASE GRANT ALL PERMISSIONS TO PROCEED
// The implementation below is for demonstration purposes to show how to use vide for PIP mode
await grantMediaPermissions()

const videoOptions: ImageLibraryOptions = {
mediaType: 'video',
videoQuality: 'high',
formatAsMp4: false,
quality: 1,
includeBase64: false,
selectionLimit: 1,
durationLimit: 0,
};

const result = await launchImageLibrary(videoOptions);

const videoPath = result.assets[0].originalPath
const videoUri = result.assets[0].uri
console.log('Open video editor in Trimmer mode with video: path = ' + videoPath + ', uri = ' + videoUri);

if (Platform.OS === 'android') {
// IMPORTANT requirements
// There are 2 types of videoPath - external(/storage/emulated/0/Movies/sample.mp4) and internal (/data/data/$applicationId/...)
// Access to media is required for external - please grant all permissions.
return await SdkEditorModule.openVideoEditorTrimmer(videoPath);
} else {
return await SdkEditorModule.openVideoEditorTrimmer(videoUri);
}
}

async function openIosPhotoEditor() {
Expand All @@ -41,6 +102,22 @@ async function openAndroidPhotoEditor() {
return await SdkEditorModule.openPhotoEditor();
}

// It is expected that the user grants all permissions.
// We do not check status here for simplicity
const grantMediaPermissions = async () => {
if (Platform.OS === 'ios') {
return
}

const status = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.ACCESS_MEDIA_LOCATION,
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.READ_MEDIA_VIDEO
]);

return status
};

export default class App extends Component {

constructor() {
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

<!-- END OPTIONAL PERMISSIONS -->
<application
Expand Down
Binary file removed android/app/src/main/assets/sample_video.mp4
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@ package com.vesdkreactnativecliintegrationsample
import android.app.Activity
import android.content.Intent
import android.content.res.AssetManager
import android.media.MediaScannerConnection
import android.net.Uri
import android.util.Log
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import com.banuba.sdk.cameraui.data.PipConfig
import com.banuba.sdk.core.data.TrackData
import com.banuba.sdk.export.data.ExportResult
import com.banuba.sdk.export.utils.EXTRA_EXPORTED_SUCCESS
import com.banuba.sdk.core.license.BanubaVideoEditor
import com.banuba.sdk.core.license.LicenseStateCallback
import com.banuba.sdk.export.data.ExportResult
import com.banuba.sdk.export.utils.EXTRA_EXPORTED_SUCCESS
import com.banuba.sdk.pe.PhotoCreationActivity
import com.banuba.sdk.ve.flow.VideoCreationActivity
import com.facebook.react.bridge.*
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink
import java.io.*
import java.util.*
import com.banuba.sdk.pe.PhotoCreationActivity
import com.banuba.sdk.pe.PhotoExportResultContract

class SdkEditorModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
class SdkEditorModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {

companion object {
const val TAG = "SdkEditorModule"
Expand Down Expand Up @@ -184,29 +183,32 @@ class SdkEditorModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
}

@ReactMethod
fun openVideoEditorPIP(promise: Promise) {
fun openVideoEditorPIP(pipVideoPath: String, promise: Promise) {
Log.d(TAG, "openVideoEditorPIP = $pipVideoPath")
checkLicense(callback = { isValid ->
if (isValid) {
// ✅ The license is active
val hostActivity = currentActivity
if (hostActivity == null) {
promise.reject(ERR_CODE_NO_HOST_CONTROLLER, "")
} else {
// sample_video.mp4 file is hardcoded for demonstrating how to open video editor sdk in the simplest case.
// Please provide valid video URL to open Video Editor in PIP.
val sampleVideoFileName = "sample_video.mp4"
val filesStorage: File = hostActivity.applicationContext.filesDir
val assets: AssetManager = hostActivity.applicationContext.assets
val sampleVideoFile = prepareMediaFile(assets, filesStorage, sampleVideoFileName)

this.resultPromise = promise
val intent = VideoCreationActivity.startFromCamera(

MediaScannerConnection.scanFile(
hostActivity,
PipConfig(video = sampleVideoFile.toUri(), openPipSettings = false),
null,
null
)
hostActivity.startActivityForResult(intent, OPEN_VIDEO_EDITOR_REQUEST_CODE)
arrayOf(File(pipVideoPath).absolutePath),
arrayOf()
) { path, uri ->
Log.d(TAG, "Found path = $path, uri = $uri")

val intent = VideoCreationActivity.startFromCamera(
hostActivity,
PipConfig(video = uri, openPipSettings = false),
null,
null
)
hostActivity.startActivityForResult(intent, OPEN_VIDEO_EDITOR_REQUEST_CODE)
}
}
} else {
// ❌ Use of SDK is restricted: the license is revoked or expired
Expand All @@ -216,29 +218,31 @@ class SdkEditorModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
}

@ReactMethod
fun openVideoEditorTrimmer(promise: Promise) {
fun openVideoEditorTrimmer(videoPath: String, promise: Promise) {
checkLicense(callback = { isValid ->
if (isValid) {
// ✅ The license is active
val hostActivity = currentActivity
if (hostActivity == null) {
promise.reject(ERR_CODE_NO_HOST_CONTROLLER, "")
} else {
// sample_video.mp4 file is hardcoded for demonstrating how to open video editor sdk in the simplest case.
// Please provide valid video URL to open Video Editor in trimmer.
val sampleVideoFileName = "sample_video.mp4"
val filesStorage: File = hostActivity.applicationContext.filesDir
val assets: AssetManager = hostActivity.applicationContext.assets
val sampleVideoFile = prepareMediaFile(assets, filesStorage, sampleVideoFileName)

this.resultPromise = promise
val intent = VideoCreationActivity.startFromTrimmer(

MediaScannerConnection.scanFile(
hostActivity,
arrayOf(sampleVideoFile.toUri()),
null,
null
)
hostActivity.startActivityForResult(intent, OPEN_VIDEO_EDITOR_REQUEST_CODE)
arrayOf(File(videoPath).absolutePath),
arrayOf()
) { path, uri ->
Log.d(TAG, "Found path = $path, uri = $uri")

val intent = VideoCreationActivity.startFromTrimmer(
hostActivity,
arrayOf(uri),
null,
null
)
hostActivity.startActivityForResult(intent, OPEN_VIDEO_EDITOR_REQUEST_CODE)
}
}
} else {
// ❌ Use of SDK is restricted: the license is revoked or expired
Expand Down
8 changes: 8 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,10 @@ PODS:
- React-Mapbuffer (0.73.0):
- glog
- React-debug
- react-native-image-picker (7.1.1):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
- React-nativeconfig (0.73.0)
- React-NativeModulesApple (0.73.0):
- glog
Expand Down Expand Up @@ -1174,6 +1178,7 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
Expand Down Expand Up @@ -1291,6 +1296,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/logger"
React-Mapbuffer:
:path: "../node_modules/react-native/ReactCommon"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
React-nativeconfig:
:path: "../node_modules/react-native/ReactCommon"
React-NativeModulesApple:
Expand Down Expand Up @@ -1387,6 +1394,7 @@ SPEC CHECKSUMS:
React-jsinspector: 9f6fb9ed9f03a0fb961ab8dc2e0e0ee0dc729e77
React-logger: 008caec0d6a587abc1e71be21bfac5ba1662fe6a
React-Mapbuffer: 58fe558faf52ecde6705376700f848d0293d1cef
react-native-image-picker: 1a7cd3224036e080fe46bcb955f2eb42fcbf7acc
React-nativeconfig: a063483672b8add47a4875b0281e202908ff6747
React-NativeModulesApple: 169506a5fd708ab22811f76ee06a976595c367a1
React-perflogger: b61e5db8e5167f5e70366e820766c492847c082e
Expand Down
26 changes: 13 additions & 13 deletions ios/SdkEditorModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,21 @@ class SdkEditorModule: NSObject, RCTBridgeModule {
}
}

@objc func openVideoEditorPIP(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
@objc func openVideoEditorPIP(_ pipVideoPath: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
self.currentResolve = resolve
self.currentReject = reject

prepareAudioBrowser()
DispatchQueue.main.async {
guard let presentedVC = RCTPresentedViewController() else {
reject("RCTPresentedViewController returned nil", nil, nil)
return
}

// sample_pip_video.mp4 file is hardcoded for demonstrating how to open video editor sdk in the simplest case.
// Please provide valid video URL to open Video Editor in PIP.
let pipVideoURL = Bundle.main.url(forResource: "sample_video", withExtension: "mp4")
guard let pipVideoURL = URL(string: pipVideoPath) else {
reject("Failed to instantiate URL from String", nil, nil)
return
}

let pipLaunchConfig = VideoEditorLaunchConfig(
entryPoint: .pip,
Expand All @@ -106,29 +108,27 @@ class SdkEditorModule: NSObject, RCTBridgeModule {
}
}

@objc func openVideoEditorTrimmer(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
@objc func openVideoEditorTrimmer(_ trimmerVideoPath: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
self.currentResolve = resolve
self.currentReject = reject

prepareAudioBrowser()

DispatchQueue.main.async {
guard let presentedVC = RCTPresentedViewController() else {
reject("RCTPresentedViewController returned nil", nil, nil)
return
}

// sample_video.mp4 file is hardcoded for demonstrating how to open video editor sdk in the simplest case.
// Please provide valid video URL to open Video Editor in Trimmer.
let trimmerVideoURL = Bundle.main.url(forResource: "sample_video", withExtension: "mp4")!
let fileManager = FileManager.default
let tmpURL = fileManager.temporaryDirectory.appendingPathComponent("sample_video.mp4")
try? fileManager.removeItem(at: tmpURL)
try? fileManager.copyItem(at: trimmerVideoURL, to: tmpURL)
guard let trimmerVideoURL = URL(string: trimmerVideoPath) else {
reject("Failed to instantiate URL from String", nil, nil)
return
}

let trimmerLaunchConfig = VideoEditorLaunchConfig(
entryPoint: .trimmer,
hostController: presentedVC,
videoItems: [tmpURL],
videoItems: [trimmerVideoURL],
musicTrack: nil,
animated: true
)
Expand Down
4 changes: 2 additions & 2 deletions ios/SdkEditorModuleBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ @interface RCT_EXTERN_MODULE(SdkEditorModule, NSObject)

RCT_EXTERN_METHOD(closeAudioBrowser: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(openVideoEditorPIP: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openVideoEditorPIP:(NSString *)pipVideoPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(openVideoEditorTrimmer: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openVideoEditorTrimmer:(NSString *)trimmerVideoPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(initPhotoEditor:(NSString *) token resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
C2A6917D294C9CE6005DFA75 /* AudioBrowserModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A6917C294C9CE6005DFA75 /* AudioBrowserModule.swift */; };
C2A69180294C9D1C005DFA75 /* CustomViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A6917F294C9D1C005DFA75 /* CustomViewControllerFactory.swift */; };
C2A69183294CA0AB005DFA75 /* sample_audio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C2A69182294CA0AB005DFA75 /* sample_audio.mp3 */; };
C2B80BDF294DFBFA006D11FA /* sample_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = C2B80BDE294DFBFA006D11FA /* sample_video.mp4 */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -46,7 +45,6 @@
C2A6917C294C9CE6005DFA75 /* AudioBrowserModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioBrowserModule.swift; sourceTree = "<group>"; };
C2A6917F294C9D1C005DFA75 /* CustomViewControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomViewControllerFactory.swift; sourceTree = "<group>"; };
C2A69182294CA0AB005DFA75 /* sample_audio.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = sample_audio.mp3; path = ../android/app/src/main/assets/sample_audio.mp3; sourceTree = "<group>"; };
C2B80BDE294DFBFA006D11FA /* sample_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; name = sample_video.mp4; path = ../android/app/src/main/assets/sample_video.mp4; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F3D506A8DB72071BDDDE58E8 /* libPods-vesdkreactnativecliintegrationsample-vesdkreactnativecliintegrationsampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-vesdkreactnativecliintegrationsample-vesdkreactnativecliintegrationsampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -104,7 +102,6 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
C2B80BDE294DFBFA006D11FA /* sample_video.mp4 */,
7837EBDA28AF8E3C00174DDB /* Localizable.strings */,
13B07FAE1A68108700A75B9A /* vesdkreactnativecliintegrationsample */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
Expand Down Expand Up @@ -199,7 +196,6 @@
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
C2B80BDF294DFBFA006D11FA /* sample_video.mp4 in Resources */,
C2A69183294CA0AB005DFA75 /* sample_audio.mp3 in Resources */,
7837EBDB28AF8E3C00174DDB /* Localizable.strings in Resources */,
7837EBDD28AF91FD00174DDB /* Music in Resources */,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"eslint": "^7.32.0",
"jest": "^29.6.3",
"metro-react-native-babel-preset": "^0.76.9",
"react-native-image-picker": "7.1.1",
"react-test-renderer": "18.0.0",
"typescript": "5.0.4"
},
Expand Down
Loading