@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.
Files changed (35) hide show
  1. package/README.md +17 -3
  2. package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +2 -6
  3. package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +75 -35
  4. package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +51 -35
  5. package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +98 -117
  6. package/ios/RNMediaEditor.m +38 -7
  7. package/ios/RNMediaLibrary.m +19 -15
  8. package/ios/RNVideoPreviewManager.m +2 -0
  9. package/lib/commonjs/components/VideoEditor.js +100 -32
  10. package/lib/commonjs/components/VideoEditor.js.map +1 -1
  11. package/lib/commonjs/screens/CropScreen.js +5 -3
  12. package/lib/commonjs/screens/CropScreen.js.map +1 -1
  13. package/lib/commonjs/screens/EditorScreen.js +229 -44
  14. package/lib/commonjs/screens/EditorScreen.js.map +1 -1
  15. package/lib/commonjs/screens/PickScreen.js +214 -122
  16. package/lib/commonjs/screens/PickScreen.js.map +1 -1
  17. package/lib/module/components/VideoEditor.js +100 -33
  18. package/lib/module/components/VideoEditor.js.map +1 -1
  19. package/lib/module/screens/CropScreen.js +5 -3
  20. package/lib/module/screens/CropScreen.js.map +1 -1
  21. package/lib/module/screens/EditorScreen.js +229 -44
  22. package/lib/module/screens/EditorScreen.js.map +1 -1
  23. package/lib/module/screens/PickScreen.js +215 -123
  24. package/lib/module/screens/PickScreen.js.map +1 -1
  25. package/lib/typescript/src/components/VideoEditor.d.ts +10 -2
  26. package/lib/typescript/src/screens/CropScreen.d.ts +2 -1
  27. package/lib/typescript/src/screens/EditorScreen.d.ts +2 -1
  28. package/lib/typescript/src/screens/PickScreen.d.ts +4 -1
  29. package/lib/typescript/src/types.d.ts +1 -0
  30. package/package.json +4 -1
  31. package/src/components/VideoEditor.tsx +68 -11
  32. package/src/screens/CropScreen.tsx +8 -3
  33. package/src/screens/EditorScreen.tsx +227 -61
  34. package/src/screens/PickScreen.tsx +197 -119
  35. 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
- class RNVideoViewSubclass(context: android.content.Context) : VideoView(context) {
13
- var resizeMode: String = "cover"
14
- var videoW: Int = 0
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 VideoProgressEvent(surfaceId: Int, viewTag: Int, private val name: String, private val eventData: com.facebook.react.bridge.WritableMap) :
25
- com.facebook.react.uimanager.events.Event<VideoProgressEvent>(surfaceId, viewTag) {
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
- android.util.Log.d("RNVideoPreview", "runnable tick: isPlaying = ${videoView.isPlaying}")
47
- if (videoView.isPlaying) {
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
- videoView.seekTo(trimStartMs)
41
+ mediaPlayer!!.seekTo(trimStartMs)
53
42
  }
54
43
  } else if (current < trimStartMs) {
55
44
  if (!isSeeking) {
56
45
  isSeeking = true
57
- videoView.seekTo(trimStartMs)
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 (e2: Exception) {
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 (videoView.resizeMode == "contain") {
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
- videoView.measure(
124
+ textureView.measure(
156
125
  MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
157
126
  MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
158
127
  )
159
128
  } else {
160
- videoView.measure(widthMeasureSpec, heightMeasureSpec)
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 = videoView.measuredWidth
169
- val childHeight = videoView.measuredHeight
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
- videoView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight)
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
- videoView.setZOrderMediaOverlay(false)
181
- videoView.setOnPreparedListener { mp ->
182
- mediaPlayer = mp
183
- mp.isLooping = true
184
- val volume = if (isMuted) 0f else 1f
185
- mp.setVolume(volume, volume)
186
- // Seek to trim start before playing
187
- if (trimStartMs > 0) {
188
- mp.seekTo(trimStartMs)
189
- }
190
- if (!isPaused) {
191
- android.util.Log.d("RNVideoPreview", "onPrepared: starting playback, isPaused=$isPaused")
192
- videoView.start()
193
- } else {
194
- android.util.Log.d("RNVideoPreview", "onPrepared: isPaused=true, not starting")
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
- videoView.videoW = mp.videoWidth
197
- videoView.videoH = mp.videoHeight
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
- val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, android.view.Gravity.CENTER)
210
- addView(videoView, lp)
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.mediaPlayer = null
246
- view.videoView.stopPlayback()
249
+ view.releasePlayer()
247
250
  return
248
251
  }
249
- try {
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.videoView.pause() } catch (e: Exception) { /* ignore if not yet prepared */ }
259
+ try { view.mediaPlayer?.pause() } catch (e: Exception) {}
269
260
  } else {
270
- // Only call start() if mediaPlayer is prepared (not null)
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.videoView.resizeMode = resizeMode ?: "cover"
295
- view.videoView.requestLayout()
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
- if (view.videoView.currentPosition < trimStartMs) {
302
- view.videoView.seekTo(trimStartMs)
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.videoView.seekTo(seekToMs)
295
+ view.mediaPlayer?.seekTo(seekToMs)
315
296
  }
316
297
  }
317
298
  }
@@ -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
- // Bulletproof frame loading:
145
- // 1. Try Assets Catalog / Flat Bundle
146
- UIImage *uiFrame = [UIImage imageNamed:frameKey];
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
- // Bulletproof frame loading (same as editImage)
512
- UIImage *uiFrame = [UIImage imageNamed:frameKey];
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"],
@@ -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
- resolve(results);
237
- } @catch (NSException *exception) {
238
- reject(@"list_failed", exception.reason, nil);
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
- PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsWithLocalIdentifiers:@[localId] options:nil];
303
- PHAsset *asset = result.firstObject;
304
- if (!asset) {
305
- reject(@"not_found", @"Asset not found", nil);
306
- return;
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
- if (asset.mediaType == PHAssetMediaTypeImage) {
310
- PHImageRequestOptions *opts = [[PHImageRequestOptions alloc] init];
311
- opts.synchronous = YES;
312
- opts.networkAccessAllowed = YES;
312
+ if (asset.mediaType == PHAssetMediaTypeImage) {
313
+ PHImageRequestOptions *opts = [[PHImageRequestOptions alloc] init];
314
+ opts.synchronous = YES;
315
+ opts.networkAccessAllowed = YES;
313
316
 
314
- __block NSString *outUri = nil;
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,
@@ -31,6 +31,8 @@
31
31
  _seekToMs = -1;
32
32
  _isPlayerReady = NO;
33
33
 
34
+ self.clipsToBounds = YES;
35
+
34
36
  self.player = [[AVPlayer alloc] init];
35
37
  self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
36
38
  self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];