@technotoil/image-video-editor 0.1.0

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 (99) hide show
  1. package/ImageVideoEditor.podspec +21 -0
  2. package/README.md +136 -0
  3. package/android/build.gradle +76 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +13 -0
  6. package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +67 -0
  7. package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +548 -0
  8. package/android/src/main/java/com/technotoil/image_videoeditor/MediaFileUtils.kt +29 -0
  9. package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +305 -0
  10. package/android/src/main/java/com/technotoil/image_videoeditor/MediaPackage.kt +26 -0
  11. package/android/src/main/java/com/technotoil/image_videoeditor/MediaPickerModule.kt +111 -0
  12. package/android/src/main/java/com/technotoil/image_videoeditor/MediaPlayerModule.kt +34 -0
  13. package/android/src/main/java/com/technotoil/image_videoeditor/RNCameraViewManager.kt +761 -0
  14. package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +317 -0
  15. package/ios/PrivacyInfo.xcprivacy +38 -0
  16. package/ios/RNCameraViewManager.m +420 -0
  17. package/ios/RNFrameGrabber.m +61 -0
  18. package/ios/RNMediaEditor.m +905 -0
  19. package/ios/RNMediaLibrary.m +389 -0
  20. package/ios/RNMediaPicker.m +144 -0
  21. package/ios/RNMediaPlayer.m +73 -0
  22. package/ios/RNVideoPreviewManager.m +263 -0
  23. package/ios/frames/film_vintage.png +0 -0
  24. package/ios/frames/floral_gold.png +0 -0
  25. package/ios/frames/minimal_double.png +0 -0
  26. package/ios/frames/polaroid_white.png +0 -0
  27. package/ios/frames/watercolor_floral.png +0 -0
  28. package/lib/module/assets/frames/film_vintage.png +0 -0
  29. package/lib/module/assets/frames/floral_gold.png +0 -0
  30. package/lib/module/assets/frames/minimal_double.png +0 -0
  31. package/lib/module/assets/frames/polaroid_white.png +0 -0
  32. package/lib/module/assets/frames/watercolor_floral.png +0 -0
  33. package/lib/module/components/VideoEditor.js +156 -0
  34. package/lib/module/components/VideoEditor.js.map +1 -0
  35. package/lib/module/index.js +4 -0
  36. package/lib/module/index.js.map +1 -0
  37. package/lib/module/native/CameraView.js +104 -0
  38. package/lib/module/native/CameraView.js.map +1 -0
  39. package/lib/module/native/FrameGrabber.js +13 -0
  40. package/lib/module/native/FrameGrabber.js.map +1 -0
  41. package/lib/module/native/MediaEditor.js +19 -0
  42. package/lib/module/native/MediaEditor.js.map +1 -0
  43. package/lib/module/native/MediaLibrary.js +37 -0
  44. package/lib/module/native/MediaLibrary.js.map +1 -0
  45. package/lib/module/native/MediaPicker.js +13 -0
  46. package/lib/module/native/MediaPicker.js.map +1 -0
  47. package/lib/module/native/MediaPlayer.js +13 -0
  48. package/lib/module/native/MediaPlayer.js.map +1 -0
  49. package/lib/module/native/VideoPreview.js +12 -0
  50. package/lib/module/native/VideoPreview.js.map +1 -0
  51. package/lib/module/package.json +1 -0
  52. package/lib/module/screens/CropScreen.js +1211 -0
  53. package/lib/module/screens/CropScreen.js.map +1 -0
  54. package/lib/module/screens/EditorScreen.js +5752 -0
  55. package/lib/module/screens/EditorScreen.js.map +1 -0
  56. package/lib/module/screens/ExportScreen.js +289 -0
  57. package/lib/module/screens/ExportScreen.js.map +1 -0
  58. package/lib/module/screens/GalleryScreen.js +505 -0
  59. package/lib/module/screens/GalleryScreen.js.map +1 -0
  60. package/lib/module/screens/PickScreen.js +1195 -0
  61. package/lib/module/screens/PickScreen.js.map +1 -0
  62. package/lib/module/types.js +2 -0
  63. package/lib/module/types.js.map +1 -0
  64. package/lib/typescript/src/components/VideoEditor.d.ts +13 -0
  65. package/lib/typescript/src/index.d.ts +2 -0
  66. package/lib/typescript/src/native/CameraView.d.ts +23 -0
  67. package/lib/typescript/src/native/FrameGrabber.d.ts +2 -0
  68. package/lib/typescript/src/native/MediaEditor.d.ts +3 -0
  69. package/lib/typescript/src/native/MediaLibrary.d.ts +16 -0
  70. package/lib/typescript/src/native/MediaPicker.d.ts +2 -0
  71. package/lib/typescript/src/native/MediaPlayer.d.ts +1 -0
  72. package/lib/typescript/src/native/VideoPreview.d.ts +19 -0
  73. package/lib/typescript/src/screens/CropScreen.d.ts +9 -0
  74. package/lib/typescript/src/screens/EditorScreen.d.ts +10 -0
  75. package/lib/typescript/src/screens/ExportScreen.d.ts +9 -0
  76. package/lib/typescript/src/screens/GalleryScreen.d.ts +8 -0
  77. package/lib/typescript/src/screens/PickScreen.d.ts +13 -0
  78. package/lib/typescript/src/types.d.ts +58 -0
  79. package/package.json +101 -0
  80. package/src/assets/frames/film_vintage.png +0 -0
  81. package/src/assets/frames/floral_gold.png +0 -0
  82. package/src/assets/frames/minimal_double.png +0 -0
  83. package/src/assets/frames/polaroid_white.png +0 -0
  84. package/src/assets/frames/watercolor_floral.png +0 -0
  85. package/src/components/VideoEditor.tsx +182 -0
  86. package/src/index.tsx +2 -0
  87. package/src/native/CameraView.tsx +95 -0
  88. package/src/native/FrameGrabber.ts +21 -0
  89. package/src/native/MediaEditor.ts +33 -0
  90. package/src/native/MediaLibrary.ts +69 -0
  91. package/src/native/MediaPicker.ts +17 -0
  92. package/src/native/MediaPlayer.ts +16 -0
  93. package/src/native/VideoPreview.tsx +20 -0
  94. package/src/screens/CropScreen.tsx +968 -0
  95. package/src/screens/EditorScreen.tsx +4517 -0
  96. package/src/screens/ExportScreen.tsx +282 -0
  97. package/src/screens/GalleryScreen.tsx +412 -0
  98. package/src/screens/PickScreen.tsx +1094 -0
  99. package/src/types.ts +58 -0
@@ -0,0 +1,317 @@
1
+ package com.technotoil.image_videoeditor
2
+
3
+ import android.net.Uri
4
+ import android.widget.FrameLayout
5
+ import android.widget.VideoView
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.uimanager.SimpleViewManager
8
+ import com.facebook.react.uimanager.ThemedReactContext
9
+ import com.facebook.react.uimanager.annotations.ReactProp
10
+ import com.facebook.react.uimanager.events.RCTEventEmitter
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
+ }
23
+
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)
32
+ var uri: String? = null
33
+ var isPaused: Boolean = false
34
+ var isMuted: Boolean = true
35
+ var mediaPlayer: android.media.MediaPlayer? = null
36
+ var trimStartMs: Int = 0
37
+ var trimEndMs: Int = 0
38
+ var lastEmittedTime: Int = -1
39
+ var isSeeking: Boolean = false
40
+
41
+ private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
42
+
43
+ private val checkProgressRunnable = object : Runnable {
44
+ override fun run() {
45
+ try {
46
+ android.util.Log.d("RNVideoPreview", "runnable tick: isPlaying = ${videoView.isPlaying}")
47
+ if (videoView.isPlaying) {
48
+ val current = videoView.currentPosition
49
+ if (trimEndMs > 0 && current >= trimEndMs) {
50
+ if (!isSeeking) {
51
+ isSeeking = true
52
+ videoView.seekTo(trimStartMs)
53
+ }
54
+ } else if (current < trimStartMs) {
55
+ if (!isSeeking) {
56
+ isSeeking = true
57
+ videoView.seekTo(trimStartMs)
58
+ }
59
+ } else {
60
+ isSeeking = false
61
+ }
62
+
63
+ if (id != android.view.View.NO_ID && current != lastEmittedTime) {
64
+ lastEmittedTime = current
65
+ android.util.Log.d("RNVideoPreview", "Emitting progress: $current, tag: $id")
66
+ val event = com.facebook.react.bridge.Arguments.createMap().apply {
67
+ putInt("currentTimeMs", current)
68
+ }
69
+ val reactContext = context as? com.facebook.react.bridge.ReactContext
70
+ 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
+ try {
82
+ reactContext.getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter::class.java)
83
+ ?.receiveEvent(id, "topChange", event)
84
+ } catch (e2: Exception) {
85
+ // ignore
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ } catch (e: Exception) {
92
+ // ignore
93
+ }
94
+ mainHandler.postDelayed(this, 100)
95
+ }
96
+ }
97
+
98
+ override fun onAttachedToWindow() {
99
+ super.onAttachedToWindow()
100
+ android.util.Log.d("RNVideoPreview", "onAttachedToWindow called! tag: $id")
101
+ mainHandler.post(checkProgressRunnable)
102
+ }
103
+
104
+ override fun onDetachedFromWindow() {
105
+ super.onDetachedFromWindow()
106
+ android.util.Log.d("RNVideoPreview", "onDetachedFromWindow called! tag: $id")
107
+ mainHandler.removeCallbacks(checkProgressRunnable)
108
+ }
109
+
110
+ private val mLayoutRunnable = Runnable {
111
+ measure(
112
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
113
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
114
+ )
115
+ layout(left, top, right, bottom)
116
+ }
117
+
118
+ override fun requestLayout() {
119
+ super.requestLayout()
120
+ post(mLayoutRunnable)
121
+ }
122
+
123
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
124
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
125
+
126
+ val parentWidth = measuredWidth
127
+ val parentHeight = measuredHeight
128
+
129
+ val videoW = videoView.videoW
130
+ val videoH = videoView.videoH
131
+
132
+ if (videoW > 0 && videoH > 0 && parentWidth > 0 && parentHeight > 0) {
133
+ val videoAspect = videoW.toFloat() / videoH.toFloat()
134
+ val parentAspect = parentWidth.toFloat() / parentHeight.toFloat()
135
+
136
+ var childWidth = parentWidth
137
+ var childHeight = parentHeight
138
+
139
+ if (videoView.resizeMode == "contain") {
140
+ if (videoAspect > parentAspect) {
141
+ childHeight = (parentWidth / videoAspect).toInt()
142
+ } else {
143
+ childWidth = (parentHeight * videoAspect).toInt()
144
+ }
145
+ } else {
146
+ if (videoAspect > parentAspect) {
147
+ childWidth = (parentHeight * videoAspect).toInt()
148
+ childHeight = parentHeight
149
+ } else {
150
+ childWidth = parentWidth
151
+ childHeight = (parentWidth / videoAspect).toInt()
152
+ }
153
+ }
154
+
155
+ videoView.measure(
156
+ MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
157
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
158
+ )
159
+ } else {
160
+ videoView.measure(widthMeasureSpec, heightMeasureSpec)
161
+ }
162
+ }
163
+
164
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
165
+ val parentWidth = right - left
166
+ val parentHeight = bottom - top
167
+
168
+ val childWidth = videoView.measuredWidth
169
+ val childHeight = videoView.measuredHeight
170
+
171
+ val childLeft = (parentWidth - childWidth) / 2
172
+ val childTop = (parentHeight - childHeight) / 2
173
+
174
+ videoView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight)
175
+ }
176
+
177
+ init {
178
+ android.util.Log.d("RNVideoPreview", "RNVideoView init called! tag: $id")
179
+ 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")
195
+ }
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
207
+ }
208
+
209
+ val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, android.view.Gravity.CENTER)
210
+ addView(videoView, lp)
211
+ }
212
+
213
+ fun updateVolume() {
214
+ try {
215
+ val volume = if (isMuted) 0f else 1f
216
+ mediaPlayer?.setVolume(volume, volume)
217
+ } catch (e: Exception) {
218
+ e.printStackTrace()
219
+ }
220
+ }
221
+ }
222
+
223
+ class RNVideoPreviewManager(private val reactContext: ReactApplicationContext) :
224
+ SimpleViewManager<RNVideoView>() {
225
+
226
+ override fun getName(): String = "RNVideoPreview"
227
+
228
+ override fun createViewInstance(reactContext: ThemedReactContext): RNVideoView {
229
+ return RNVideoView(reactContext)
230
+ }
231
+
232
+ override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any>? {
233
+ return com.facebook.react.common.MapBuilder.builder<String, Any>()
234
+ .put("topChange", com.facebook.react.common.MapBuilder.of(
235
+ "phasedRegistrationNames", com.facebook.react.common.MapBuilder.of("bubbled", "onChange")
236
+ ))
237
+ .build()
238
+ }
239
+
240
+ @ReactProp(name = "uri")
241
+ fun setUri(view: RNVideoView, uri: String?) {
242
+ if (uri == view.uri) return
243
+ view.uri = uri
244
+ if (uri.isNullOrEmpty()) {
245
+ view.mediaPlayer = null
246
+ view.videoView.stopPlayback()
247
+ return
248
+ }
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
+ }
261
+ }
262
+
263
+ @ReactProp(name = "paused")
264
+ fun setPaused(view: RNVideoView, paused: Boolean) {
265
+ android.util.Log.d("RNVideoPreview", "setPaused: paused=$paused, mediaPlayer=${view.mediaPlayer}")
266
+ view.isPaused = paused
267
+ if (paused) {
268
+ try { view.videoView.pause() } catch (e: Exception) { /* ignore if not yet prepared */ }
269
+ } 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
+ }
283
+ }
284
+ }
285
+
286
+ @ReactProp(name = "muted")
287
+ fun setMuted(view: RNVideoView, muted: Boolean) {
288
+ view.isMuted = muted
289
+ view.updateVolume()
290
+ }
291
+
292
+ @ReactProp(name = "resizeMode")
293
+ fun setResizeMode(view: RNVideoView, resizeMode: String?) {
294
+ view.videoView.resizeMode = resizeMode ?: "cover"
295
+ view.videoView.requestLayout()
296
+ }
297
+
298
+ @ReactProp(name = "trimStartMs")
299
+ fun setTrimStartMs(view: RNVideoView, trimStartMs: Int) {
300
+ view.trimStartMs = trimStartMs
301
+ if (view.videoView.currentPosition < trimStartMs) {
302
+ view.videoView.seekTo(trimStartMs)
303
+ }
304
+ }
305
+
306
+ @ReactProp(name = "trimEndMs")
307
+ fun setTrimEndMs(view: RNVideoView, trimEndMs: Int) {
308
+ view.trimEndMs = trimEndMs
309
+ }
310
+
311
+ @ReactProp(name = "seekToMs")
312
+ fun setSeekToMs(view: RNVideoView, seekToMs: Int) {
313
+ if (seekToMs >= 0) {
314
+ view.videoView.seekTo(seekToMs)
315
+ }
316
+ }
317
+ }
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>NSPrivacyAccessedAPITypes</key>
6
+ <array>
7
+ <dict>
8
+ <key>NSPrivacyAccessedAPIType</key>
9
+ <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
10
+ <key>NSPrivacyAccessedAPITypeReasons</key>
11
+ <array>
12
+ <string>3B52.1</string>
13
+ <string>C617.1</string>
14
+ </array>
15
+ </dict>
16
+ <dict>
17
+ <key>NSPrivacyAccessedAPIType</key>
18
+ <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
19
+ <key>NSPrivacyAccessedAPITypeReasons</key>
20
+ <array>
21
+ <string>CA92.1</string>
22
+ </array>
23
+ </dict>
24
+ <dict>
25
+ <key>NSPrivacyAccessedAPIType</key>
26
+ <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
27
+ <key>NSPrivacyAccessedAPITypeReasons</key>
28
+ <array>
29
+ <string>35F9.1</string>
30
+ </array>
31
+ </dict>
32
+ </array>
33
+ <key>NSPrivacyCollectedDataTypes</key>
34
+ <array/>
35
+ <key>NSPrivacyTracking</key>
36
+ <false/>
37
+ </dict>
38
+ </plist>