Skip to content

Commit bf15ec0

Browse files
committed
Add version 2.8.0
1 parent 7db0bf6 commit bf15ec0

File tree

13 files changed

+396
-88
lines changed

13 files changed

+396
-88
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## [2.8.0]
2+
3+
### Added
4+
5+
* [video_editor_sdk] Added `VideoEditorResult.segments`, `VideoEditorResult.videoSize`, and `VideoEditorResult.release()` which enable serialization of the individual video composition components if `VideoOptions.segments` is enabled.
6+
* [video_editor_sdk] Added `FlutterVESDK.editorWillOpenClosure` and `FlutterVESDK.editorWillExportClosure` which allow further native configuration on Android.
7+
* [photo_editor_sdk] Added `FlutterPESDK.editorWillOpenClosure` and `FlutterPESDK.editorWillExportClosure` which allow further native configuration on Android.
8+
9+
### Fixed
10+
11+
* [imgly_sdk] Fixed `TextOptions.canvasActions` would use `StickerCanvasAction` instead of `TextCanvasAction`.
12+
113
## [2.7.1]
214

315
### Fixed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ In order to run any samples or use any wrapper without a watermark,
44
you'll have to purchase a commercial PhotoEditor SDK or VideoEditor SDK
55
license. Visit https://img.ly for more details.
66

7-
Copyright (c) 2014-2022, img.ly GmbH
7+
Copyright (c) 2014-2023, img.ly GmbH
88
All rights reserved.
99

1010
Redistribution and use in source and binary forms, with or without

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Add the plugin package to the `pubspec.yaml` file in your project:
3030

3131
```yaml
3232
dependencies:
33-
video_editor_sdk: ^2.7.1
33+
video_editor_sdk: ^2.8.0
3434
```
3535
3636
Install the new dependency:
@@ -107,13 +107,13 @@ Run with --stacktrace option to get the stack trace. Run with --info or --debug
107107
}
108108
dependencies {
109109
...
110-
+ classpath 'ly.img.android.sdk:plugin:10.4.0'
110+
+ classpath 'ly.img.android.sdk:plugin:10.4.1'
111111
...
112112
}
113113
}
114114
```
115115

116-
In order to update VideoEditor SDK for Android replace the version string `10.4.0` with a [newer release](https://github.com/imgly/pesdk-android-demo/releases).
116+
In order to update VideoEditor SDK for Android replace the version string `10.4.1` with a [newer release](https://github.com/imgly/pesdk-android-demo/releases).
117117

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

android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ imglyConfig {
3939
}
4040
}
4141

42-
def MIN_LY_IMG_ANDROID_SDK_PLUGIN_VERSION = "10.4.0"
42+
def MIN_LY_IMG_ANDROID_SDK_PLUGIN_VERSION = "10.4.1"
4343

4444
task checkVersion {
4545
if (imglyConfig.convertToVersionNumber(imglyConfig.getVersion()) < imglyConfig.convertToVersionNumber(MIN_LY_IMG_ANDROID_SDK_PLUGIN_VERSION)) {

android/src/main/AndroidManifest.xml

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
android:exported="false"
99
android:enabled="false"
1010
android:name="ly.img.android.IMGLYAutoInit" />
11+
<activity android:name="FlutterVESDKActivity" />
1112
</application>
1213
</manifest>

android/src/main/kotlin/ly/img/flutter/video_editor_sdk/FlutterVESDK.kt

+142-20
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull
55
import android.content.Intent
66
import android.net.Uri
77
import android.util.Log
8+
import androidx.annotation.WorkerThread
89

910
import io.flutter.embedding.engine.plugins.FlutterPlugin
1011
import io.flutter.plugin.common.MethodCall
@@ -15,13 +16,18 @@ import ly.img.android.AuthorizationException
1516
import ly.img.android.IMGLY
1617
import ly.img.android.VESDK
1718
import ly.img.android.pesdk.VideoEditorSettingsList
19+
import ly.img.android.pesdk.backend.decoder.VideoSource
1820
import ly.img.android.pesdk.backend.model.state.LoadSettings
1921
import ly.img.android.pesdk.kotlin_extension.continueWithExceptions
2022
import ly.img.android.pesdk.utils.UriHelper
2123
import ly.img.android.sdk.config.*
2224
import ly.img.android.pesdk.backend.encoder.Encoder
2325
import ly.img.android.pesdk.backend.model.EditorSDKResult
26+
import ly.img.android.pesdk.backend.model.VideoPart
2427
import ly.img.android.pesdk.backend.model.state.VideoCompositionSettings
28+
import ly.img.android.pesdk.backend.model.state.manager.SettingsList
29+
import ly.img.android.pesdk.backend.model.state.manager.StateHandler
30+
import ly.img.android.pesdk.ui.activity.VideoEditorActivity
2531
import ly.img.android.pesdk.utils.ThreadUtils
2632
import ly.img.android.serializer._3.IMGLYFileWriter
2733

@@ -37,8 +43,18 @@ class FlutterVESDK: FlutterIMGLY() {
3743
companion object {
3844
// This number must be unique. It is public to allow client code to change it if the same value is used elsewhere.
3945
var EDITOR_RESULT_ID = 29064
46+
47+
/** A closure to modify a *VideoEditorSettingsList* before the editor is opened. */
48+
var editorWillOpenClosure: ((settingsList: VideoEditorSettingsList) -> Unit)? = null
49+
50+
/** A closure allowing access to the *StateHandler* before the editor is exporting. */
51+
var editorWillExportClosure: ((stateHandler: StateHandler) -> Unit)? = null
4052
}
4153

54+
private var resolveManually: Boolean = false
55+
private var currentEditorUID: String = UUID.randomUUID().toString()
56+
private var settingsLists: MutableMap<String, SettingsList> = mutableMapOf()
57+
4258
override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
4359
super.onAttachedToEngine(binding)
4460

@@ -65,6 +81,7 @@ class FlutterVESDK: FlutterIMGLY() {
6581

6682
val video = call.argument<MutableMap<String, Any>>("video")
6783
if (video != null) {
84+
val videoSegments = video["segments"] as ArrayList<*>?
6885
val videosList = video["videos"] as ArrayList<String>?
6986
val videoSource = video["video"] as String?
7087
val size = video["size"] as? MutableMap<String, Double>
@@ -73,9 +90,11 @@ class FlutterVESDK: FlutterIMGLY() {
7390

7491
if (videoSource != null) {
7592
this.present(videoSource.let { EmbeddedAsset(it).resolvedURI }, config, serialization)
76-
} else {
93+
} else if (videosList != null) {
7794
val videos = videosList?.mapNotNull { EmbeddedAsset(it).resolvedURI }
7895
this.present(videos, config, serialization, size)
96+
} else {
97+
this.present(videoSegments, config, serialization, size)
7998
}
8099
} else {
81100
result.error("VE.SDK", "The video must not be null", null)
@@ -84,6 +103,14 @@ class FlutterVESDK: FlutterIMGLY() {
84103
val license = call.argument<String>("license")
85104
this.result = result
86105
this.resolveLicense(license)
106+
} else if (call.method == "release") {
107+
val identifier = call.argument<String>("identifier")
108+
if (identifier == null) {
109+
result.error("VE.SDK", "The identifier must not be null", null)
110+
} else {
111+
this.result = result
112+
this.releaseTemporaryData(identifier)
113+
}
87114
} else {
88115
result.notImplemented()
89116
}
@@ -98,7 +125,12 @@ class FlutterVESDK: FlutterIMGLY() {
98125
*/
99126
override fun present(asset: String, config: HashMap<String, Any>?, serialization: String?) {
100127
val configuration = ConfigLoader.readFrom(config ?: mapOf())
101-
val settingsList = VideoEditorSettingsList(configuration.export?.serialization?.enabled == true)
128+
val serializationEnabled = configuration.export?.serialization?.enabled == true
129+
val exportVideoSegments = configuration.export?.video?.segments == true
130+
val createTemporaryFiles = serializationEnabled || exportVideoSegments
131+
resolveManually = exportVideoSegments
132+
133+
val settingsList = VideoEditorSettingsList(createTemporaryFiles)
102134
configuration.applyOn(settingsList)
103135
currentConfig = configuration
104136

@@ -108,41 +140,46 @@ class FlutterVESDK: FlutterIMGLY() {
108140
}
109141
}
110142

111-
applyTheme(settingsList, configuration.theme)
112-
143+
editorWillOpenClosure?.invoke(settingsList)
113144
readSerialisation(settingsList, serialization, false)
114-
startEditor(settingsList, EDITOR_RESULT_ID)
145+
applyTheme(settingsList, configuration.theme)
146+
startEditor(settingsList, EDITOR_RESULT_ID, FlutterVESDKActivity::class.java)
115147
}
116148

117149
/**
118150
* Configures and presents the editor.
119151
*
120-
* @param videos The video sources as *List<String>* which should be loaded into the editor.
152+
* @param videos The video sources as *List<*>* which should be loaded into the editor.
121153
* @param config The *Configuration* to configure the editor with as if any.
122154
* @param serialization The serialization to load into the editor if any.
123155
*/
124-
private fun present(videos: List<String>?, config: HashMap<String, Any>?, serialization: String?, size: Map<String, Any>?) {
156+
private fun present(videos: List<*>?, config: HashMap<String, Any>?, serialization: String?, size: Map<String, Any>?) {
157+
val videoArray = deserializeVideoParts(videos)
158+
var source = resolveSize(size)
159+
125160
val configuration = ConfigLoader.readFrom(config ?: mapOf())
126-
val settingsList = VideoEditorSettingsList(configuration.export?.serialization?.enabled == true)
161+
val serializationEnabled = configuration.export?.serialization?.enabled == true
162+
val exportVideoSegments = configuration.export?.video?.segments == true
163+
val createTemporaryFiles = serializationEnabled || exportVideoSegments
164+
resolveManually = exportVideoSegments
165+
166+
val settingsList = VideoEditorSettingsList(createTemporaryFiles)
127167
configuration.applyOn(settingsList)
128168
currentConfig = configuration
129169

130-
var source = resolveSize(size)
131-
if (videos != null && videos.count() > 0) {
170+
if (videoArray.isNotEmpty()) {
132171
if (source == null) {
133172
if (size != null) {
134173
result?.error("VE.SDK", "Invalid video size: width and height must be greater than zero.", null)
135-
this.result = null
136174
return
137175
}
138-
val video = videos.first()
139-
source = retrieveURI(video)
176+
val video = videoArray.first()
177+
source = video.videoSource.getSourceAsUri()
140178
}
141179

142180
settingsList.configure<VideoCompositionSettings> { loadSettings ->
143-
videos.forEach {
144-
val resolvedSource = retrieveURI(it)
145-
loadSettings.addCompositionPart(VideoCompositionSettings.VideoPart(resolvedSource))
181+
videoArray.forEach {
182+
loadSettings.addCompositionPart(it)
146183
}
147184
}
148185
} else {
@@ -157,10 +194,20 @@ class FlutterVESDK: FlutterIMGLY() {
157194
it.source = source
158195
}
159196

197+
editorWillOpenClosure?.invoke(settingsList)
198+
readSerialisation(settingsList, serialization, false)
160199
applyTheme(settingsList, configuration.theme)
200+
startEditor(settingsList, EDITOR_RESULT_ID, FlutterVESDKActivity::class.java)
201+
}
161202

162-
readSerialisation(settingsList, serialization, false)
163-
startEditor(settingsList, EDITOR_RESULT_ID)
203+
private fun releaseTemporaryData(identifier: String) {
204+
val settingsList = settingsLists[identifier]
205+
if (settingsList != null) {
206+
settingsList.release()
207+
settingsLists.remove(identifier)
208+
}
209+
this.result?.success(null)
210+
this.result = null
164211
}
165212

166213
private fun retrieveURI(source: String) : Uri {
@@ -185,6 +232,50 @@ class FlutterVESDK: FlutterIMGLY() {
185232
return LoadSettings.compositionSource(width.toInt(), height.toInt(), 60)
186233
}
187234

235+
private fun serializeVideoSegments(settingsList: SettingsList): List<*> {
236+
val compositionParts = mutableListOf<MutableMap<String, Any?>>()
237+
settingsList[VideoCompositionSettings::class].videos.forEach {
238+
val source = it.videoSource.getSourceAsUri().toString()
239+
val trimStart = it.trimStartInNano / 1000000000.0f
240+
val trimEnd = it.trimEndInNano / 1000000000.0f
241+
242+
val videoPart = mutableMapOf<String, Any?>(
243+
"videoUri" to source,
244+
"startTime" to trimStart.toDouble(),
245+
"endTime" to trimEnd.toDouble()
246+
)
247+
compositionParts.add(videoPart)
248+
}
249+
return compositionParts
250+
}
251+
252+
private fun deserializeVideoParts(videos: List<*>?) : List<VideoPart> {
253+
val parts = emptyList<VideoPart>().toMutableList()
254+
255+
videos?.forEach {
256+
if (it is String) {
257+
val videoPart = VideoPart(retrieveURI(EmbeddedAsset(it).resolvedURI))
258+
parts.add(videoPart)
259+
} else if (it is Map<*, *>) {
260+
val uri = it["videoUri"] as String?
261+
val trimStart = it["startTime"] as Double?
262+
val trimEnd = it["endTime"] as Double?
263+
264+
if (uri != null) {
265+
val videoPart = VideoPart(retrieveURI(EmbeddedAsset(uri).resolvedURI))
266+
if (trimStart != null) {
267+
videoPart.trimStartInNanoseconds = (trimStart * 1000000000.0f).toLong()
268+
}
269+
if (trimEnd != null) {
270+
videoPart.trimEndInNanoseconds = (trimEnd * 1000000000.0f).toLong()
271+
}
272+
parts.add(videoPart)
273+
}
274+
}
275+
}
276+
return parts
277+
}
278+
188279
/**
189280
* Unlocks the SDK with a stringified license.
190281
*
@@ -222,8 +313,9 @@ class FlutterVESDK: FlutterIMGLY() {
222313
val sourceUri = intentData.sourceUri
223314

224315
var serialization: Any? = null
316+
val settingsList = intentData.settingsList
317+
225318
if (serializationConfig?.enabled == true) {
226-
val settingsList = intentData.settingsList
227319
skipIfNotExists {
228320
settingsList.let { settingsList ->
229321
serialization = when (serializationConfig.exportType) {
@@ -241,23 +333,53 @@ class FlutterVESDK: FlutterIMGLY() {
241333
}
242334
}
243335
}
244-
settingsList.release()
245336
} ?: run {
246337
Log.e("IMG.LY SDK", "You need to include 'backend:serializer' Module, to use serialisation!")
247338
}
248339
}
249340

341+
var segments: List<*>? = null
342+
val canvasSize = sourceUri?.let { VideoSource.create(it).fetchFormatInfo()?.size }
343+
val sizeMap = mutableMapOf<String, Any>()
344+
if (canvasSize != null && canvasSize.height >= 0 && canvasSize.width >= 0) {
345+
sizeMap["height"] = canvasSize.height.toDouble()
346+
sizeMap["width"] = canvasSize.width.toDouble()
347+
}
348+
349+
if (resolveManually) {
350+
settingsLists[currentEditorUID] = settingsList
351+
segments = serializeVideoSegments(settingsList)
352+
}
353+
250354
val map = mutableMapOf<String, Any?>()
251355
map["video"] = resultUri.toString()
252356
map["hasChanges"] = (sourceUri?.path != resultUri?.path)
253357
map["serialization"] = serialization
358+
map["segments"] = segments
359+
map["identifier"] = currentEditorUID
360+
map["videoSize"] = sizeMap
361+
254362
currentActivity?.runOnUiThread {
255363
this.result?.success(map)
256364
this.result = null
257365
}
366+
if (!resolveManually) {
367+
settingsList.release()
368+
}
369+
resolveManually = false
258370
}
259371
return true
260372
}
261373
return false
262374
}
263375
}
376+
377+
/** A *VideoEditorActivity* used for the native interfaces. */
378+
class FlutterVESDKActivity: VideoEditorActivity() {
379+
@WorkerThread
380+
override fun onExportStart(stateHandler: StateHandler) {
381+
FlutterVESDK.editorWillExportClosure?.invoke(stateHandler)
382+
383+
super.onExportStart(stateHandler)
384+
}
385+
}

example/android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
buildscript {
22
ext.kotlin_version = '1.5.32'
3-
ext.vesdk_version = '10.4.0'
3+
ext.vesdk_version = '10.4.1'
44

55
repositories {
66
google()

0 commit comments

Comments
 (0)