@@ -5,6 +5,7 @@ import androidx.annotation.NonNull
5
5
import android.content.Intent
6
6
import android.net.Uri
7
7
import android.util.Log
8
+ import androidx.annotation.WorkerThread
8
9
9
10
import io.flutter.embedding.engine.plugins.FlutterPlugin
10
11
import io.flutter.plugin.common.MethodCall
@@ -15,13 +16,18 @@ import ly.img.android.AuthorizationException
15
16
import ly.img.android.IMGLY
16
17
import ly.img.android.VESDK
17
18
import ly.img.android.pesdk.VideoEditorSettingsList
19
+ import ly.img.android.pesdk.backend.decoder.VideoSource
18
20
import ly.img.android.pesdk.backend.model.state.LoadSettings
19
21
import ly.img.android.pesdk.kotlin_extension.continueWithExceptions
20
22
import ly.img.android.pesdk.utils.UriHelper
21
23
import ly.img.android.sdk.config.*
22
24
import ly.img.android.pesdk.backend.encoder.Encoder
23
25
import ly.img.android.pesdk.backend.model.EditorSDKResult
26
+ import ly.img.android.pesdk.backend.model.VideoPart
24
27
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
25
31
import ly.img.android.pesdk.utils.ThreadUtils
26
32
import ly.img.android.serializer._3.IMGLYFileWriter
27
33
@@ -37,8 +43,18 @@ class FlutterVESDK: FlutterIMGLY() {
37
43
companion object {
38
44
// This number must be unique. It is public to allow client code to change it if the same value is used elsewhere.
39
45
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
40
52
}
41
53
54
+ private var resolveManually: Boolean = false
55
+ private var currentEditorUID: String = UUID .randomUUID().toString()
56
+ private var settingsLists: MutableMap <String , SettingsList > = mutableMapOf ()
57
+
42
58
override fun onAttachedToEngine (@NonNull binding : FlutterPlugin .FlutterPluginBinding ) {
43
59
super .onAttachedToEngine(binding)
44
60
@@ -65,6 +81,7 @@ class FlutterVESDK: FlutterIMGLY() {
65
81
66
82
val video = call.argument<MutableMap <String , Any >>(" video" )
67
83
if (video != null ) {
84
+ val videoSegments = video[" segments" ] as ArrayList <* >?
68
85
val videosList = video[" videos" ] as ArrayList <String >?
69
86
val videoSource = video[" video" ] as String?
70
87
val size = video[" size" ] as ? MutableMap <String , Double >
@@ -73,9 +90,11 @@ class FlutterVESDK: FlutterIMGLY() {
73
90
74
91
if (videoSource != null ) {
75
92
this .present(videoSource.let { EmbeddedAsset (it).resolvedURI }, config, serialization)
76
- } else {
93
+ } else if (videosList != null ) {
77
94
val videos = videosList?.mapNotNull { EmbeddedAsset (it).resolvedURI }
78
95
this .present(videos, config, serialization, size)
96
+ } else {
97
+ this .present(videoSegments, config, serialization, size)
79
98
}
80
99
} else {
81
100
result.error(" VE.SDK" , " The video must not be null" , null )
@@ -84,6 +103,14 @@ class FlutterVESDK: FlutterIMGLY() {
84
103
val license = call.argument<String >(" license" )
85
104
this .result = result
86
105
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
+ }
87
114
} else {
88
115
result.notImplemented()
89
116
}
@@ -98,7 +125,12 @@ class FlutterVESDK: FlutterIMGLY() {
98
125
*/
99
126
override fun present (asset : String , config : HashMap <String , Any >? , serialization : String? ) {
100
127
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)
102
134
configuration.applyOn(settingsList)
103
135
currentConfig = configuration
104
136
@@ -108,41 +140,46 @@ class FlutterVESDK: FlutterIMGLY() {
108
140
}
109
141
}
110
142
111
- applyTheme(settingsList, configuration.theme)
112
-
143
+ editorWillOpenClosure?.invoke(settingsList)
113
144
readSerialisation(settingsList, serialization, false )
114
- startEditor(settingsList, EDITOR_RESULT_ID )
145
+ applyTheme(settingsList, configuration.theme)
146
+ startEditor(settingsList, EDITOR_RESULT_ID , FlutterVESDKActivity ::class .java)
115
147
}
116
148
117
149
/* *
118
150
* Configures and presents the editor.
119
151
*
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.
121
153
* @param config The *Configuration* to configure the editor with as if any.
122
154
* @param serialization The serialization to load into the editor if any.
123
155
*/
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
+
125
160
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)
127
167
configuration.applyOn(settingsList)
128
168
currentConfig = configuration
129
169
130
- var source = resolveSize(size)
131
- if (videos != null && videos.count() > 0 ) {
170
+ if (videoArray.isNotEmpty()) {
132
171
if (source == null ) {
133
172
if (size != null ) {
134
173
result?.error(" VE.SDK" , " Invalid video size: width and height must be greater than zero." , null )
135
- this .result = null
136
174
return
137
175
}
138
- val video = videos .first()
139
- source = retrieveURI( video)
176
+ val video = videoArray .first()
177
+ source = video.videoSource.getSourceAsUri( )
140
178
}
141
179
142
180
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)
146
183
}
147
184
}
148
185
} else {
@@ -157,10 +194,20 @@ class FlutterVESDK: FlutterIMGLY() {
157
194
it.source = source
158
195
}
159
196
197
+ editorWillOpenClosure?.invoke(settingsList)
198
+ readSerialisation(settingsList, serialization, false )
160
199
applyTheme(settingsList, configuration.theme)
200
+ startEditor(settingsList, EDITOR_RESULT_ID , FlutterVESDKActivity ::class .java)
201
+ }
161
202
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
164
211
}
165
212
166
213
private fun retrieveURI (source : String ) : Uri {
@@ -185,6 +232,50 @@ class FlutterVESDK: FlutterIMGLY() {
185
232
return LoadSettings .compositionSource(width.toInt(), height.toInt(), 60 )
186
233
}
187
234
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
+
188
279
/* *
189
280
* Unlocks the SDK with a stringified license.
190
281
*
@@ -222,8 +313,9 @@ class FlutterVESDK: FlutterIMGLY() {
222
313
val sourceUri = intentData.sourceUri
223
314
224
315
var serialization: Any? = null
316
+ val settingsList = intentData.settingsList
317
+
225
318
if (serializationConfig?.enabled == true ) {
226
- val settingsList = intentData.settingsList
227
319
skipIfNotExists {
228
320
settingsList.let { settingsList ->
229
321
serialization = when (serializationConfig.exportType) {
@@ -241,23 +333,53 @@ class FlutterVESDK: FlutterIMGLY() {
241
333
}
242
334
}
243
335
}
244
- settingsList.release()
245
336
} ? : run {
246
337
Log .e(" IMG.LY SDK" , " You need to include 'backend:serializer' Module, to use serialisation!" )
247
338
}
248
339
}
249
340
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
+
250
354
val map = mutableMapOf<String , Any ?>()
251
355
map[" video" ] = resultUri.toString()
252
356
map[" hasChanges" ] = (sourceUri?.path != resultUri?.path)
253
357
map[" serialization" ] = serialization
358
+ map[" segments" ] = segments
359
+ map[" identifier" ] = currentEditorUID
360
+ map[" videoSize" ] = sizeMap
361
+
254
362
currentActivity?.runOnUiThread {
255
363
this .result?.success(map)
256
364
this .result = null
257
365
}
366
+ if (! resolveManually) {
367
+ settingsList.release()
368
+ }
369
+ resolveManually = false
258
370
}
259
371
return true
260
372
}
261
373
return false
262
374
}
263
375
}
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
+ }
0 commit comments