Skip to content

Commit 8bf294f

Browse files
committed
Add version 2.16.0
1 parent c6ecc28 commit 8bf294f

12 files changed

+393
-109
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [2.16.0]
2+
3+
### Added
4+
5+
* [react-native-videoeditorsdk] Added duration action for text and stickers.
6+
* [react-native-videoeditorsdk] Added `VideoEditorResult.segments`, `VideoEditorResult.videoSize`, and `VideoEditorResult.release()` which enable serialization of the individual video composition components if `configuration.export.video.segments` is enabled.
7+
18
## [2.15.0]
29

310
### Changed

README.md

+22-12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Check out our [video tutorial](https://img.ly/blog/a-photo-and-video-editor-for-
3232
With version `2.13.0`, we recommend using `compileSdkVersion` not lower than `31` for Android. However, this might interfere with your application's Android Gradle Plugin version if this is set to `4.x`.
3333

3434
If you don't use a newer Android Gradle Plugin version, e.g., by updating at least to RN 0.68.0, you'll most likely encounter a build error similar to:
35+
3536
```
3637
FAILURE: Build failed with an exception.
3738
@@ -44,9 +45,11 @@ Run with --stacktrace option to get the stack trace. Run with --info or --debug
4445
4546
* Get more help at https://help.gradle.org
4647
```
48+
4749
As a workaround you can create the following symlinks:
48-
1. Inside `/Users/YOUR-USERNAME/Library/Android/sdk/build-tools/31.0.0/`: Create a `dx` symlink for the `d8` file with `ln -s d8 dx`.
49-
2. From there, go to `./lib/` and create a `dx.jar` symlink for the `d8.jar` file with `ln -s d8.jar dx.jar`.
50+
51+
1. Inside `/Users/YOUR-USERNAME/Library/Android/sdk/build-tools/31.0.0/`: Create a `dx` symlink for the `d8` file with `ln -s d8 dx`.
52+
2. From there, go to `./lib/` and create a `dx.jar` symlink for the `d8.jar` file with `ln -s d8.jar dx.jar`.
5053

5154
### Expo CLI
5255

@@ -63,7 +66,7 @@ In order to use this module with the Expo CLI you can make use of our integrated
6366
```sh
6467
expo install react-native-videoeditorsdk
6568
```
66-
69+
6770
This will automatically install [`react-native-imglysdk`](https://npmjs.org/package/react-native-imglysdk) which you can use to configure your application with our Expo config plugin.
6871

6972
2. Inside your app's `app.json` or `app.config.js` add our config plugin:
@@ -83,7 +86,7 @@ In order to use this module with the Expo CLI you can make use of our integrated
8386
"react-native-imglysdk",
8487
{
8588
"android": {
86-
"version": "10.1.1",
89+
"version": "10.4.0",
8790
"modules": [
8891
"ui:core",
8992
"ui:transform",
@@ -103,7 +106,7 @@ In order to use this module with the Expo CLI you can make use of our integrated
103106
```
104107

105108
For further information on the available modules, please refer to step 4 of the React Native CLI [Android](#android) guide below.
106-
109+
107110
**Please note that the `react-native-imglysdk` module manages both VideoEditor SDK as well as PhotoEditor SDK so you only need to add the Expo config plugin once even when using both SDKs.**
108111

109112
3. From version `2.15.0` the iOS deployment target needs to be set to at least iOS 13. You can use the `expo-build-properties` config plugin for this. Please refer to the [official Expo docs](https://docs.expo.dev/versions/v45.0.0/sdk/build-properties/).
@@ -149,6 +152,7 @@ For older React Native versions autolinking is not available and VideoEditor SDK
149152
#### Android
150153

151154
1. Add the img.ly repository and plugin by opening the `android/build.gradle` file (**not** `android/app/build.gradle`) and adding these lines at the top:
155+
152156
```groovy
153157
buildscript {
154158
repositories {
@@ -157,11 +161,12 @@ For older React Native versions autolinking is not available and VideoEditor SDK
157161
}
158162
dependencies {
159163
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.32"
160-
classpath 'ly.img.android.sdk:plugin:10.1.1'
164+
classpath 'ly.img.android.sdk:plugin:10.4.0'
161165
}
162166
}
163167
```
164-
In order to update VideoEditor SDK for Android replace the version string `10.1.1` with a [newer release](https://github.com/imgly/vesdk-android-demo/releases).
168+
169+
In order to update VideoEditor SDK for Android replace the version string `10.4.0` with a [newer release](https://github.com/imgly/vesdk-android-demo/releases).
165170

166171
2. Still in the `android/build.gradle` file (**not** `android/app/build.gradle`), add these lines at the bottom:
167172

@@ -189,7 +194,8 @@ For older React Native versions autolinking is not available and VideoEditor SDK
189194
}
190195
```
191196

192-
4. Configure VideoEditor SDK for Android by opening the `android/app/build.gradle` file (**not** `android/build.gradle`) and adding the following lines under `apply plugin: "com.android.application"`:
197+
4. Configure VideoEditor SDK for Android by opening the `android/app/build.gradle` file (**not** `android/build.gradle`) and adding the following lines under `apply plugin: "com.android.application"`:
198+
193199
```groovy
194200
apply plugin: 'ly.img.android.sdk'
195201
apply plugin: 'kotlin-android'
@@ -237,25 +243,29 @@ For older React Native versions autolinking is not available and VideoEditor SDK
237243
Import the module in your `App.js`:
238244

239245
```js
240-
import {VESDK, VideoEditorModal, Configuration} from 'react-native-videoeditorsdk';
246+
import {
247+
VESDK,
248+
VideoEditorModal,
249+
Configuration,
250+
} from "react-native-videoeditorsdk";
241251
```
242252

243253
Each platform requires a separate license file. [Unlock VideoEditor SDK](./index.d.ts#L41-L53) automatically for both platforms with a single line of code via [platform-specific file extensions](https://reactnative.dev/docs/platform-specific-code#platform-specific-extensions):
244254

245255
```js
246-
VESDK.unlockWithLicense(require('./vesdk_license'));
256+
VESDK.unlockWithLicense(require("./vesdk_license"));
247257
```
248258

249259
Open the editor with a video:
250260

251261
```js
252-
VESDK.openEditor(require('./video.mp4'));
262+
VESDK.openEditor(require("./video.mp4"));
253263
```
254264

255265
Or use the component to open the editor:
256266

257267
```jsx
258-
<VideoEditorModal visible={true} video={require('./video.mp4')}/>
268+
<VideoEditorModal visible={true} video={require("./video.mp4")} />
259269
```
260270

261271
Please see the [code documentation](./index.d.ts) for more details and additional [customization and configuration options](./configuration.ts).

RNVideoEditorSDK.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ Pod::Spec.new do |s|
1818

1919
s.dependency 'React'
2020
s.dependency 'React-RCTImage'
21-
s.dependency 'VideoEditorSDK', '~> 11.1'
21+
s.dependency 'VideoEditorSDK', '~> 11.3'
2222
end

android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ imglyConfig {
2020
}
2121
}
2222

23-
def MIN_LY_IMG_ANDROID_SDK_PLUGIN_VERSION = "10.1.1"
23+
def MIN_LY_IMG_ANDROID_SDK_PLUGIN_VERSION = "10.4.0"
2424

2525
task checkVersion {
2626
if (imglyConfig.convertToVersionNumber(imglyConfig.getVersion()) < imglyConfig.convertToVersionNumber(MIN_LY_IMG_ANDROID_SDK_PLUGIN_VERSION)) {

android/src/main/java/ly/img/react_native/vesdk/RNVideoEditorSDKModule.kt

+123-25
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import org.json.JSONObject
2222
import java.io.File
2323
import ly.img.android.pesdk.backend.encoder.Encoder
2424
import ly.img.android.pesdk.backend.model.EditorSDKResult
25+
import ly.img.android.pesdk.backend.model.VideoPart
26+
import ly.img.android.pesdk.backend.model.state.LoadState
2527
import ly.img.android.pesdk.backend.model.state.VideoCompositionSettings
2628
import ly.img.android.serializer._3.IMGLYFileReader
2729
import ly.img.android.serializer._3.IMGLYFileWriter
@@ -39,13 +41,25 @@ class RNVideoEditorSDKModule(reactContext: ReactApplicationContext) : ReactConte
3941

4042
private var currentPromise: Promise? = null
4143
private var currentConfig: Configuration? = null
44+
private var resolveManually: Boolean = false
45+
private var currentEditorUID: String = UUID.randomUUID().toString()
46+
private var settingsLists: MutableMap<String, SettingsList> = mutableMapOf()
4247

4348
@ReactMethod
4449
fun unlockWithLicense(license: String) {
4550
VESDK.initSDKWithLicenseData(license)
4651
IMGLY.authorize()
4752
}
4853

54+
@ReactMethod
55+
fun releaseTemporaryData(identifier: String) {
56+
val settingsList = settingsLists[identifier]
57+
if (settingsList != null) {
58+
settingsList.release()
59+
settingsLists.remove(identifier)
60+
}
61+
}
62+
4963
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, intent: Intent?) {
5064
val data = try {
5165
intent?.let { EditorSDKResult(it) }
@@ -67,60 +81,96 @@ class RNVideoEditorSDKModule(reactContext: ReactApplicationContext) : ReactConte
6781
val serializationConfig = currentConfig?.export?.serialization
6882

6983
var serialization: Any? = null
84+
val settingsList = data.settingsList
85+
7086
if (serializationConfig?.enabled == true) {
71-
val settingsList = data.settingsList
7287
skipIfNotExists {
7388
settingsList.let { settingsList ->
7489
if (serializationConfig.embedSourceImage == true) {
75-
Log.i("ImgLySdk", "EmbedSourceImage is currently not supported by the Android SDK")
90+
Log.i(
91+
"ImgLySdk",
92+
"EmbedSourceImage is currently not supported by the Android SDK"
93+
)
7694
}
7795
serialization = when (serializationConfig.exportType) {
7896
SerializationExportType.FILE_URL -> {
7997
val uri = serializationConfig.filename?.let {
8098
Uri.parse("$it.json")
81-
} ?: Uri.fromFile(File.createTempFile("serialization-" + UUID.randomUUID().toString(), ".json"))
82-
Encoder.createOutputStream(uri).use { outputStream ->
83-
IMGLYFileWriter(settingsList).writeJson(outputStream)
84-
}
99+
} ?: Uri.fromFile(
100+
File.createTempFile(
101+
"serialization-" + UUID.randomUUID()
102+
.toString(), ".json"
103+
)
104+
)
105+
Encoder.createOutputStream(uri)
106+
.use { outputStream ->
107+
IMGLYFileWriter(settingsList).writeJson(
108+
outputStream
109+
)
110+
}
85111
uri.toString()
86112
}
87113
SerializationExportType.OBJECT -> {
88114
ReactJSON.convertJsonToMap(
89-
JSONObject(
90-
IMGLYFileWriter(settingsList).writeJsonAsString()
91-
)
115+
JSONObject(
116+
IMGLYFileWriter(settingsList).writeJsonAsString()
117+
)
92118
)
93119
}
94120
}
95121
}
96-
settingsList.release()
97122
} ?: run {
98-
Log.i("ImgLySdk", "You need to include 'backend:serializer' Module, to use serialisation!")
123+
Log.i(
124+
"ImgLySdk",
125+
"You need to include 'backend:serializer' Module, to use serialisation!"
126+
)
99127
}
100128
}
101129

130+
var segments: ReadableArray? = null
131+
val canvasSize = settingsList[LoadState::class].sourceSize
132+
val serializedSize = reactMap(
133+
"height" to canvasSize.height,
134+
"width" to canvasSize.width
135+
)
136+
137+
if (resolveManually) {
138+
settingsLists[currentEditorUID] = settingsList
139+
segments = serializeVideoSegments(settingsList)
140+
}
141+
102142
currentPromise?.resolve(
103-
reactMap(
104-
"video" to resultPath?.toString(),
105-
"hasChanges" to (sourcePath?.path != resultPath?.path),
106-
"serialization" to serialization
107-
)
143+
reactMap(
144+
"video" to resultPath?.toString(),
145+
"hasChanges" to (sourcePath?.path != resultPath?.path),
146+
"serialization" to serialization,
147+
"segments" to segments,
148+
"identifier" to currentEditorUID,
149+
"videoSize" to serializedSize
150+
)
108151
)
152+
if (!resolveManually) {
153+
settingsList.release()
154+
}
155+
resolveManually = false
109156
}()
110157
}
111158
}
112159
}
113160
}
114161
}
115162

116-
117163
override fun onNewIntent(intent: Intent?) {
118164
}
119165

120166
@ReactMethod
121167
fun present(video: String, config: ReadableMap?, serialization: String?, promise: Promise) {
122168
val configuration = ConfigLoader.readFrom(config?.toHashMap() ?: mapOf())
123-
val settingsList = VideoEditorSettingsList(configuration.export?.serialization?.enabled == true)
169+
val exportVideoSegments = config?.getMap("export")?.getMap("video")?.getBoolean("segments") == true
170+
val createTemporaryFiles = configuration.export?.serialization?.enabled == true || exportVideoSegments
171+
resolveManually = exportVideoSegments
172+
173+
val settingsList = VideoEditorSettingsList(createTemporaryFiles)
124174
configuration.applyOn(settingsList)
125175
currentConfig = configuration
126176
currentPromise = promise
@@ -135,30 +185,33 @@ class RNVideoEditorSDKModule(reactContext: ReactApplicationContext) : ReactConte
135185

136186
@ReactMethod
137187
fun presentComposition(videos: ReadableArray, config: ReadableMap?, serialization: String?, size: ReadableMap?, promise: Promise) {
138-
val array = videos.toArrayList()
139-
val videoArray = array.filterIsInstance<String>().takeIf { it.size == array.size } ?: arrayListOf()
188+
val videoArray = deserializeVideoParts(videos)
140189
var source = resolveSize(size)
141190

142191
val configuration = ConfigLoader.readFrom(config?.toHashMap() ?: mapOf())
143-
val settingsList = VideoEditorSettingsList(configuration.export?.serialization?.enabled == true)
192+
193+
val exportVideoSegments = config?.getMap("export")?.getMap("video")?.getBoolean("segments") == true
194+
val createTemporaryFiles = configuration.export?.serialization?.enabled == true || exportVideoSegments
195+
resolveManually = exportVideoSegments
196+
197+
val settingsList = VideoEditorSettingsList(createTemporaryFiles)
144198
configuration.applyOn(settingsList)
145199
currentConfig = configuration
146200
currentPromise = promise
147201

148-
if (videoArray.count() > 0) {
202+
if (videoArray.isNotEmpty()) {
149203
if (source == null) {
150204
if (size != null) {
151205
promise.reject("VESDK", "Invalid video size: width and height must be greater than zero.")
152206
return
153207
}
154208
val video = videoArray.first()
155-
source = retrieveURI(video)
209+
source = video.videoSource.getSourceAsUri()
156210
}
157211

158212
settingsList.configure<VideoCompositionSettings> { loadSettings ->
159213
videoArray.forEach {
160-
val resolvedSource = retrieveURI(it)
161-
loadSettings.addCompositionPart(VideoCompositionSettings.VideoPart(resolvedSource))
214+
loadSettings.addCompositionPart(it)
162215
}
163216
}
164217
} else {
@@ -176,6 +229,50 @@ class RNVideoEditorSDKModule(reactContext: ReactApplicationContext) : ReactConte
176229
startEditor(settingsList)
177230
}
178231

232+
private fun serializeVideoSegments(settingsList: SettingsList): ReadableArray {
233+
val compositionParts = WritableNativeArray()
234+
settingsList[VideoCompositionSettings::class].videos.forEach {
235+
val source = it.videoSource.getSourceAsUri().toString()
236+
val trimStart = it.trimStartInNano / 1000000000.0f
237+
val trimEnd = it.trimEndInNano / 1000000000.0f
238+
239+
val videoPart = reactMap(
240+
"videoURI" to source,
241+
"startTime" to trimStart.toDouble(),
242+
"endTime" to trimEnd.toDouble()
243+
)
244+
compositionParts.pushMap(videoPart)
245+
}
246+
return compositionParts
247+
}
248+
249+
private fun deserializeVideoParts(videos: ReadableArray) : List<VideoPart> {
250+
val parts = emptyList<VideoPart>().toMutableList()
251+
252+
videos.toArrayList().forEach {
253+
if (it is String) {
254+
val videoPart = VideoPart(retrieveURI(it))
255+
parts.add(videoPart)
256+
} else if (it is Map<*, *>) {
257+
val uri = it["videoURI"] as String?
258+
val trimStart = it["startTime"] as Double?
259+
val trimEnd = it["endTime"] as Double?
260+
261+
if (uri != null) {
262+
val videoPart = VideoPart(retrieveURI(uri))
263+
if (trimStart != null) {
264+
videoPart.trimStartInNanoseconds = (trimStart * 1000000000.0f).toLong()
265+
}
266+
if (trimEnd != null) {
267+
videoPart.trimEndInNanoseconds = (trimEnd * 1000000000.0f).toLong()
268+
}
269+
parts.add(videoPart)
270+
}
271+
}
272+
}
273+
return parts
274+
}
275+
179276
private fun retrieveURI(source: String) : Uri {
180277
return if (source.startsWith("data:")) {
181278
UriHelper.createFromBase64String(source.substringAfter("base64,"))
@@ -211,6 +308,7 @@ class RNVideoEditorSDKModule(reactContext: ReactApplicationContext) : ReactConte
211308

212309
private fun startEditor(settingsList: VideoEditorSettingsList?) {
213310
val currentActivity = this.currentActivity ?: throw RuntimeException("Can't start the Editor because there is no current activity")
311+
currentEditorUID = UUID.randomUUID().toString()
214312
if (settingsList != null) {
215313
MainThreadRunnable {
216314
VideoEditorBuilder(currentActivity)

0 commit comments

Comments
 (0)