@technotoil/image-video-editor 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +2 -6
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +75 -35
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +51 -35
- package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +98 -117
- package/ios/RNMediaEditor.m +38 -7
- package/ios/RNMediaLibrary.m +19 -15
- package/ios/RNVideoPreviewManager.m +2 -0
- package/lib/commonjs/components/VideoEditor.js +100 -32
- package/lib/commonjs/components/VideoEditor.js.map +1 -1
- package/lib/commonjs/screens/CropScreen.js +5 -3
- package/lib/commonjs/screens/CropScreen.js.map +1 -1
- package/lib/commonjs/screens/EditorScreen.js +229 -44
- package/lib/commonjs/screens/EditorScreen.js.map +1 -1
- package/lib/commonjs/screens/PickScreen.js +214 -122
- package/lib/commonjs/screens/PickScreen.js.map +1 -1
- package/lib/module/components/VideoEditor.js +100 -33
- package/lib/module/components/VideoEditor.js.map +1 -1
- package/lib/module/screens/CropScreen.js +5 -3
- package/lib/module/screens/CropScreen.js.map +1 -1
- package/lib/module/screens/EditorScreen.js +229 -44
- package/lib/module/screens/EditorScreen.js.map +1 -1
- package/lib/module/screens/PickScreen.js +215 -123
- package/lib/module/screens/PickScreen.js.map +1 -1
- package/lib/typescript/src/components/VideoEditor.d.ts +10 -2
- package/lib/typescript/src/screens/CropScreen.d.ts +2 -1
- package/lib/typescript/src/screens/EditorScreen.d.ts +2 -1
- package/lib/typescript/src/screens/PickScreen.d.ts +4 -1
- package/lib/typescript/src/types.d.ts +1 -0
- package/package.json +4 -1
- package/src/components/VideoEditor.tsx +68 -11
- package/src/screens/CropScreen.tsx +8 -3
- package/src/screens/EditorScreen.tsx +227 -61
- package/src/screens/PickScreen.tsx +197 -119
- package/src/types.ts +1 -0
|
@@ -9,26 +9,12 @@ import com.facebook.react.uimanager.ThemedReactContext
|
|
|
9
9
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
10
10
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var videoH: Int = 0
|
|
16
|
-
|
|
17
|
-
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
18
|
-
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
19
|
-
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
20
|
-
setMeasuredDimension(width, height)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
12
|
+
import android.view.TextureView
|
|
13
|
+
import android.graphics.SurfaceTexture
|
|
14
|
+
import android.view.Surface
|
|
23
15
|
|
|
24
|
-
class
|
|
25
|
-
|
|
26
|
-
override fun getEventName(): String = name
|
|
27
|
-
override fun getEventData(): com.facebook.react.bridge.WritableMap? = eventData
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
31
|
-
val videoView = RNVideoViewSubclass(context)
|
|
16
|
+
class RNVideoView(context: android.content.Context) : FrameLayout(context), TextureView.SurfaceTextureListener {
|
|
17
|
+
val textureView = TextureView(context)
|
|
32
18
|
var uri: String? = null
|
|
33
19
|
var isPaused: Boolean = false
|
|
34
20
|
var isMuted: Boolean = true
|
|
@@ -37,24 +23,27 @@ class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
|
37
23
|
var trimEndMs: Int = 0
|
|
38
24
|
var lastEmittedTime: Int = -1
|
|
39
25
|
var isSeeking: Boolean = false
|
|
26
|
+
var resizeMode: String = "cover"
|
|
27
|
+
var videoW: Int = 0
|
|
28
|
+
var videoH: Int = 0
|
|
29
|
+
var mSurface: Surface? = null
|
|
40
30
|
|
|
41
31
|
private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
42
32
|
|
|
43
33
|
private val checkProgressRunnable = object : Runnable {
|
|
44
34
|
override fun run() {
|
|
45
35
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
val current = videoView.currentPosition
|
|
36
|
+
if (mediaPlayer?.isPlaying == true) {
|
|
37
|
+
val current = mediaPlayer!!.currentPosition
|
|
49
38
|
if (trimEndMs > 0 && current >= trimEndMs) {
|
|
50
39
|
if (!isSeeking) {
|
|
51
40
|
isSeeking = true
|
|
52
|
-
|
|
41
|
+
mediaPlayer!!.seekTo(trimStartMs)
|
|
53
42
|
}
|
|
54
43
|
} else if (current < trimStartMs) {
|
|
55
44
|
if (!isSeeking) {
|
|
56
45
|
isSeeking = true
|
|
57
|
-
|
|
46
|
+
mediaPlayer!!.seekTo(trimStartMs)
|
|
58
47
|
}
|
|
59
48
|
} else {
|
|
60
49
|
isSeeking = false
|
|
@@ -62,49 +51,32 @@ class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
|
62
51
|
|
|
63
52
|
if (id != android.view.View.NO_ID && current != lastEmittedTime) {
|
|
64
53
|
lastEmittedTime = current
|
|
65
|
-
android.util.Log.d("RNVideoPreview", "Emitting progress: $current, tag: $id")
|
|
66
54
|
val event = com.facebook.react.bridge.Arguments.createMap().apply {
|
|
67
55
|
putInt("currentTimeMs", current)
|
|
68
56
|
}
|
|
69
57
|
val reactContext = context as? com.facebook.react.bridge.ReactContext
|
|
70
58
|
if (reactContext != null) {
|
|
71
|
-
try {
|
|
72
|
-
val surfaceId = com.facebook.react.uimanager.UIManagerHelper.getSurfaceId(reactContext)
|
|
73
|
-
val eventDispatcher = com.facebook.react.uimanager.UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
|
|
74
|
-
if (eventDispatcher != null) {
|
|
75
|
-
eventDispatcher.dispatchEvent(VideoProgressEvent(surfaceId, id, "topChange", event))
|
|
76
|
-
} else {
|
|
77
|
-
reactContext.getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter::class.java)
|
|
78
|
-
?.receiveEvent(id, "topChange", event)
|
|
79
|
-
}
|
|
80
|
-
} catch (e: Exception) {
|
|
81
59
|
try {
|
|
82
60
|
reactContext.getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter::class.java)
|
|
83
61
|
?.receiveEvent(id, "topChange", event)
|
|
84
|
-
} catch (
|
|
85
|
-
// ignore
|
|
86
|
-
}
|
|
87
|
-
}
|
|
62
|
+
} catch (e: Exception) {}
|
|
88
63
|
}
|
|
89
64
|
}
|
|
90
65
|
}
|
|
91
|
-
} catch (e: Exception) {
|
|
92
|
-
// ignore
|
|
93
|
-
}
|
|
66
|
+
} catch (e: Exception) {}
|
|
94
67
|
mainHandler.postDelayed(this, 100)
|
|
95
68
|
}
|
|
96
69
|
}
|
|
97
70
|
|
|
98
71
|
override fun onAttachedToWindow() {
|
|
99
72
|
super.onAttachedToWindow()
|
|
100
|
-
android.util.Log.d("RNVideoPreview", "onAttachedToWindow called! tag: $id")
|
|
101
73
|
mainHandler.post(checkProgressRunnable)
|
|
102
74
|
}
|
|
103
75
|
|
|
104
76
|
override fun onDetachedFromWindow() {
|
|
105
77
|
super.onDetachedFromWindow()
|
|
106
|
-
android.util.Log.d("RNVideoPreview", "onDetachedFromWindow called! tag: $id")
|
|
107
78
|
mainHandler.removeCallbacks(checkProgressRunnable)
|
|
79
|
+
releasePlayer()
|
|
108
80
|
}
|
|
109
81
|
|
|
110
82
|
private val mLayoutRunnable = Runnable {
|
|
@@ -126,9 +98,6 @@ class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
|
126
98
|
val parentWidth = measuredWidth
|
|
127
99
|
val parentHeight = measuredHeight
|
|
128
100
|
|
|
129
|
-
val videoW = videoView.videoW
|
|
130
|
-
val videoH = videoView.videoH
|
|
131
|
-
|
|
132
101
|
if (videoW > 0 && videoH > 0 && parentWidth > 0 && parentHeight > 0) {
|
|
133
102
|
val videoAspect = videoW.toFloat() / videoH.toFloat()
|
|
134
103
|
val parentAspect = parentWidth.toFloat() / parentHeight.toFloat()
|
|
@@ -136,7 +105,7 @@ class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
|
136
105
|
var childWidth = parentWidth
|
|
137
106
|
var childHeight = parentHeight
|
|
138
107
|
|
|
139
|
-
if (
|
|
108
|
+
if (resizeMode == "contain") {
|
|
140
109
|
if (videoAspect > parentAspect) {
|
|
141
110
|
childHeight = (parentWidth / videoAspect).toInt()
|
|
142
111
|
} else {
|
|
@@ -152,12 +121,12 @@ class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
|
152
121
|
}
|
|
153
122
|
}
|
|
154
123
|
|
|
155
|
-
|
|
124
|
+
textureView.measure(
|
|
156
125
|
MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
|
|
157
126
|
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
|
|
158
127
|
)
|
|
159
128
|
} else {
|
|
160
|
-
|
|
129
|
+
textureView.measure(widthMeasureSpec, heightMeasureSpec)
|
|
161
130
|
}
|
|
162
131
|
}
|
|
163
132
|
|
|
@@ -165,49 +134,84 @@ class RNVideoView(context: android.content.Context) : FrameLayout(context) {
|
|
|
165
134
|
val parentWidth = right - left
|
|
166
135
|
val parentHeight = bottom - top
|
|
167
136
|
|
|
168
|
-
val childWidth =
|
|
169
|
-
val childHeight =
|
|
137
|
+
val childWidth = textureView.measuredWidth
|
|
138
|
+
val childHeight = textureView.measuredHeight
|
|
170
139
|
|
|
171
140
|
val childLeft = (parentWidth - childWidth) / 2
|
|
172
141
|
val childTop = (parentHeight - childHeight) / 2
|
|
173
142
|
|
|
174
|
-
|
|
143
|
+
textureView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight)
|
|
175
144
|
}
|
|
176
145
|
|
|
177
146
|
init {
|
|
178
|
-
android.util.Log.d("RNVideoPreview", "RNVideoView init called! tag: $id")
|
|
179
147
|
clipChildren = true
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
148
|
+
textureView.surfaceTextureListener = this
|
|
149
|
+
val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, android.view.Gravity.CENTER)
|
|
150
|
+
addView(textureView, lp)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
|
154
|
+
mSurface = Surface(surface)
|
|
155
|
+
preparePlayer()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
|
|
159
|
+
|
|
160
|
+
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
|
161
|
+
mSurface?.release()
|
|
162
|
+
mSurface = null
|
|
163
|
+
releasePlayer()
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
|
|
168
|
+
|
|
169
|
+
fun preparePlayer() {
|
|
170
|
+
if (uri.isNullOrEmpty() || mSurface == null) return
|
|
171
|
+
releasePlayer()
|
|
172
|
+
try {
|
|
173
|
+
mediaPlayer = android.media.MediaPlayer().apply {
|
|
174
|
+
setSurface(mSurface)
|
|
175
|
+
val parsedUri = Uri.parse(uri)
|
|
176
|
+
if (parsedUri.scheme == "file" || uri!!.startsWith("/")) {
|
|
177
|
+
val path = parsedUri.path ?: if (uri!!.startsWith("file://")) uri!!.substring(7) else uri!!
|
|
178
|
+
setDataSource(path)
|
|
179
|
+
} else {
|
|
180
|
+
setDataSource(context, parsedUri)
|
|
181
|
+
}
|
|
182
|
+
isLooping = true
|
|
183
|
+
val volume = if (isMuted) 0f else 1f
|
|
184
|
+
setVolume(volume, volume)
|
|
185
|
+
|
|
186
|
+
setOnPreparedListener { mp ->
|
|
187
|
+
videoW = mp.videoWidth
|
|
188
|
+
videoH = mp.videoHeight
|
|
189
|
+
requestLayout()
|
|
190
|
+
if (trimStartMs > 0) {
|
|
191
|
+
mp.seekTo(trimStartMs)
|
|
192
|
+
}
|
|
193
|
+
if (!isPaused) {
|
|
194
|
+
mp.start()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
setOnCompletionListener { mp ->
|
|
198
|
+
mp.seekTo(trimStartMs)
|
|
199
|
+
mp.start()
|
|
200
|
+
}
|
|
201
|
+
setOnErrorListener { _, _, _ -> true }
|
|
202
|
+
prepareAsync()
|
|
195
203
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
requestLayout()
|
|
199
|
-
}
|
|
200
|
-
videoView.setOnCompletionListener { mp ->
|
|
201
|
-
mp.seekTo(trimStartMs)
|
|
202
|
-
mp.start()
|
|
203
|
-
}
|
|
204
|
-
videoView.setOnErrorListener { mp, what, extra ->
|
|
205
|
-
android.util.Log.e("RNVideoPreview", "VideoView error: what=$what, extra=$extra")
|
|
206
|
-
true // Return true to prevent default "Can't play this video" dialog
|
|
204
|
+
} catch (e: Exception) {
|
|
205
|
+
e.printStackTrace()
|
|
207
206
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun releasePlayer() {
|
|
210
|
+
try {
|
|
211
|
+
mediaPlayer?.stop()
|
|
212
|
+
mediaPlayer?.release()
|
|
213
|
+
} catch (e: Exception) {}
|
|
214
|
+
mediaPlayer = null
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
fun updateVolume() {
|
|
@@ -242,44 +246,19 @@ class RNVideoPreviewManager(private val reactContext: ReactApplicationContext) :
|
|
|
242
246
|
if (uri == view.uri) return
|
|
243
247
|
view.uri = uri
|
|
244
248
|
if (uri.isNullOrEmpty()) {
|
|
245
|
-
view.
|
|
246
|
-
view.videoView.stopPlayback()
|
|
249
|
+
view.releasePlayer()
|
|
247
250
|
return
|
|
248
251
|
}
|
|
249
|
-
|
|
250
|
-
view.mediaPlayer = null
|
|
251
|
-
val parsedUri = Uri.parse(uri)
|
|
252
|
-
if (parsedUri.scheme == "file" || uri.startsWith("/")) {
|
|
253
|
-
val path = parsedUri.path ?: if (uri.startsWith("file://")) uri.substring(7) else uri
|
|
254
|
-
view.videoView.setVideoPath(path)
|
|
255
|
-
} else {
|
|
256
|
-
view.videoView.setVideoURI(parsedUri)
|
|
257
|
-
}
|
|
258
|
-
} catch (e: Exception) {
|
|
259
|
-
e.printStackTrace()
|
|
260
|
-
}
|
|
252
|
+
view.preparePlayer()
|
|
261
253
|
}
|
|
262
254
|
|
|
263
255
|
@ReactProp(name = "paused")
|
|
264
256
|
fun setPaused(view: RNVideoView, paused: Boolean) {
|
|
265
|
-
android.util.Log.d("RNVideoPreview", "setPaused: paused=$paused, mediaPlayer=${view.mediaPlayer}")
|
|
266
257
|
view.isPaused = paused
|
|
267
258
|
if (paused) {
|
|
268
|
-
try { view.
|
|
259
|
+
try { view.mediaPlayer?.pause() } catch (e: Exception) {}
|
|
269
260
|
} else {
|
|
270
|
-
|
|
271
|
-
if (view.mediaPlayer != null) {
|
|
272
|
-
try {
|
|
273
|
-
if (!view.videoView.isPlaying) {
|
|
274
|
-
view.videoView.start()
|
|
275
|
-
}
|
|
276
|
-
} catch (e: Exception) {
|
|
277
|
-
android.util.Log.w("RNVideoPreview", "setPaused: start() failed: ${e.message}")
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
android.util.Log.d("RNVideoPreview", "setPaused: mediaPlayer null, will auto-start when prepared")
|
|
281
|
-
// isPaused is already false so onPrepared will call start()
|
|
282
|
-
}
|
|
261
|
+
try { view.mediaPlayer?.start() } catch (e: Exception) {}
|
|
283
262
|
}
|
|
284
263
|
}
|
|
285
264
|
|
|
@@ -291,15 +270,17 @@ class RNVideoPreviewManager(private val reactContext: ReactApplicationContext) :
|
|
|
291
270
|
|
|
292
271
|
@ReactProp(name = "resizeMode")
|
|
293
272
|
fun setResizeMode(view: RNVideoView, resizeMode: String?) {
|
|
294
|
-
view.
|
|
295
|
-
view.
|
|
273
|
+
view.resizeMode = resizeMode ?: "cover"
|
|
274
|
+
view.requestLayout()
|
|
296
275
|
}
|
|
297
276
|
|
|
298
277
|
@ReactProp(name = "trimStartMs")
|
|
299
278
|
fun setTrimStartMs(view: RNVideoView, trimStartMs: Int) {
|
|
300
279
|
view.trimStartMs = trimStartMs
|
|
301
|
-
|
|
302
|
-
|
|
280
|
+
view.mediaPlayer?.let {
|
|
281
|
+
if (it.currentPosition < trimStartMs) {
|
|
282
|
+
it.seekTo(trimStartMs)
|
|
283
|
+
}
|
|
303
284
|
}
|
|
304
285
|
}
|
|
305
286
|
|
|
@@ -311,7 +292,7 @@ class RNVideoPreviewManager(private val reactContext: ReactApplicationContext) :
|
|
|
311
292
|
@ReactProp(name = "seekToMs")
|
|
312
293
|
fun setSeekToMs(view: RNVideoView, seekToMs: Int) {
|
|
313
294
|
if (seekToMs >= 0) {
|
|
314
|
-
view.
|
|
295
|
+
view.mediaPlayer?.seekTo(seekToMs)
|
|
315
296
|
}
|
|
316
297
|
}
|
|
317
298
|
}
|
package/ios/RNMediaEditor.m
CHANGED
|
@@ -139,11 +139,26 @@ RCT_REMAP_METHOD(editImage,
|
|
|
139
139
|
|
|
140
140
|
// 3. Apply Frame after crop
|
|
141
141
|
NSString *frameKey = options[@"frame"];
|
|
142
|
+
NSString *frameUriString = options[@"frameUri"];
|
|
142
143
|
BOOL frameApplied = NO;
|
|
143
|
-
if ([frameKey isKindOfClass:NSString.class] && frameKey.length > 0) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
if (([frameKey isKindOfClass:NSString.class] && frameKey.length > 0) || ([frameUriString isKindOfClass:NSString.class] && frameUriString.length > 0)) {
|
|
145
|
+
UIImage *uiFrame = nil;
|
|
146
|
+
|
|
147
|
+
if ([frameUriString isKindOfClass:NSString.class] && frameUriString.length > 0) {
|
|
148
|
+
NSURL *furl = [NSURL URLWithString:frameUriString];
|
|
149
|
+
if ([furl.scheme isEqualToString:@"http"] || [furl.scheme isEqualToString:@"https"]) {
|
|
150
|
+
furl = [self downloadToCache:furl];
|
|
151
|
+
} else if ([furl.scheme isEqualToString:@"file"]) {
|
|
152
|
+
furl = [self cleanURL:frameUriString];
|
|
153
|
+
}
|
|
154
|
+
if (furl) {
|
|
155
|
+
uiFrame = [UIImage imageWithData:[NSData dataWithContentsOfURL:furl]];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!uiFrame && frameKey && frameKey.length > 0) {
|
|
160
|
+
uiFrame = [UIImage imageNamed:frameKey];
|
|
161
|
+
}
|
|
147
162
|
|
|
148
163
|
// 2. Try various bundle paths
|
|
149
164
|
if (!uiFrame) {
|
|
@@ -500,6 +515,7 @@ RCT_REMAP_METHOD(trimVideo,
|
|
|
500
515
|
NSString *tintHex = options[@"tintColor"];
|
|
501
516
|
NSNumber *tintOpacity = options[@"tintOpacity"];
|
|
502
517
|
NSString *frameKey = options[@"frame"];
|
|
518
|
+
NSString *frameUriString = options[@"frameUri"];
|
|
503
519
|
|
|
504
520
|
NSNumber *rotateDegrees = options[@"rotateDegrees"] ?: @0;
|
|
505
521
|
BOOL flipX = [options[@"flipX"] boolValue];
|
|
@@ -507,9 +523,24 @@ RCT_REMAP_METHOD(trimVideo,
|
|
|
507
523
|
|
|
508
524
|
// Prepare Frame before block to avoid reloading it 30-60 times a second
|
|
509
525
|
CIImage *capturedFrameImg = nil;
|
|
510
|
-
if (frameKey && frameKey.length > 0) {
|
|
511
|
-
|
|
512
|
-
|
|
526
|
+
if (([frameKey isKindOfClass:NSString.class] && frameKey.length > 0) || ([frameUriString isKindOfClass:NSString.class] && frameUriString.length > 0)) {
|
|
527
|
+
UIImage *uiFrame = nil;
|
|
528
|
+
|
|
529
|
+
if ([frameUriString isKindOfClass:NSString.class] && frameUriString.length > 0) {
|
|
530
|
+
NSURL *furl = [NSURL URLWithString:frameUriString];
|
|
531
|
+
if ([furl.scheme isEqualToString:@"http"] || [furl.scheme isEqualToString:@"https"]) {
|
|
532
|
+
furl = [self downloadToCache:furl];
|
|
533
|
+
} else if ([furl.scheme isEqualToString:@"file"]) {
|
|
534
|
+
furl = [self cleanURL:frameUriString];
|
|
535
|
+
}
|
|
536
|
+
if (furl) {
|
|
537
|
+
uiFrame = [UIImage imageWithData:[NSData dataWithContentsOfURL:furl]];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!uiFrame && frameKey && frameKey.length > 0) {
|
|
542
|
+
uiFrame = [UIImage imageNamed:frameKey];
|
|
543
|
+
}
|
|
513
544
|
if (!uiFrame) {
|
|
514
545
|
NSArray *searchPaths = @[
|
|
515
546
|
[[NSBundle mainBundle] pathForResource:frameKey ofType:@"png" inDirectory:@"frames"],
|
package/ios/RNMediaLibrary.m
CHANGED
|
@@ -119,6 +119,7 @@ RCT_REMAP_METHOD(listAlbums,
|
|
|
119
119
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
120
120
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
121
121
|
{
|
|
122
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
122
123
|
@try {
|
|
123
124
|
NSNumber *limit = options[@"limit"] ?: @200;
|
|
124
125
|
NSNumber *offset = options[@"offset"] ?: @0;
|
|
@@ -233,11 +234,12 @@ RCT_REMAP_METHOD(listAlbums,
|
|
|
233
234
|
[results addObject:item];
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
resolve(results);
|
|
238
|
+
} @catch (NSException *exception) {
|
|
239
|
+
reject(@"list_failed", exception.reason, nil);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
239
242
|
}
|
|
240
|
-
}
|
|
241
243
|
|
|
242
244
|
- (void)exportVideoFallback:(PHAsset *)asset
|
|
243
245
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
@@ -299,19 +301,20 @@ RCT_REMAP_METHOD(exportAsset,
|
|
|
299
301
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
300
302
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
301
303
|
{
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
304
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
305
|
+
PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsWithLocalIdentifiers:@[localId] options:nil];
|
|
306
|
+
PHAsset *asset = result.firstObject;
|
|
307
|
+
if (!asset) {
|
|
308
|
+
reject(@"not_found", @"Asset not found", nil);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
308
311
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
if (asset.mediaType == PHAssetMediaTypeImage) {
|
|
313
|
+
PHImageRequestOptions *opts = [[PHImageRequestOptions alloc] init];
|
|
314
|
+
opts.synchronous = YES;
|
|
315
|
+
opts.networkAccessAllowed = YES;
|
|
313
316
|
|
|
314
|
-
|
|
317
|
+
__block NSString *outUri = nil;
|
|
315
318
|
[[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset options:opts resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
|
|
316
319
|
if (imageData) {
|
|
317
320
|
NSString *fileName = [NSString stringWithFormat:@"export_%@.jpg", [[NSUUID UUID] UUIDString]];
|
|
@@ -357,6 +360,7 @@ RCT_REMAP_METHOD(exportAsset,
|
|
|
357
360
|
} else {
|
|
358
361
|
reject(@"unsupported", @"Unsupported asset type", nil);
|
|
359
362
|
}
|
|
363
|
+
});
|
|
360
364
|
}
|
|
361
365
|
|
|
362
366
|
RCT_REMAP_METHOD(saveToGallery,
|