@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.
- package/ImageVideoEditor.podspec +21 -0
- package/README.md +136 -0
- package/android/build.gradle +76 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +67 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +548 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaFileUtils.kt +29 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +305 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaPackage.kt +26 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaPickerModule.kt +111 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaPlayerModule.kt +34 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/RNCameraViewManager.kt +761 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +317 -0
- package/ios/PrivacyInfo.xcprivacy +38 -0
- package/ios/RNCameraViewManager.m +420 -0
- package/ios/RNFrameGrabber.m +61 -0
- package/ios/RNMediaEditor.m +905 -0
- package/ios/RNMediaLibrary.m +389 -0
- package/ios/RNMediaPicker.m +144 -0
- package/ios/RNMediaPlayer.m +73 -0
- package/ios/RNVideoPreviewManager.m +263 -0
- package/ios/frames/film_vintage.png +0 -0
- package/ios/frames/floral_gold.png +0 -0
- package/ios/frames/minimal_double.png +0 -0
- package/ios/frames/polaroid_white.png +0 -0
- package/ios/frames/watercolor_floral.png +0 -0
- package/lib/module/assets/frames/film_vintage.png +0 -0
- package/lib/module/assets/frames/floral_gold.png +0 -0
- package/lib/module/assets/frames/minimal_double.png +0 -0
- package/lib/module/assets/frames/polaroid_white.png +0 -0
- package/lib/module/assets/frames/watercolor_floral.png +0 -0
- package/lib/module/components/VideoEditor.js +156 -0
- package/lib/module/components/VideoEditor.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/CameraView.js +104 -0
- package/lib/module/native/CameraView.js.map +1 -0
- package/lib/module/native/FrameGrabber.js +13 -0
- package/lib/module/native/FrameGrabber.js.map +1 -0
- package/lib/module/native/MediaEditor.js +19 -0
- package/lib/module/native/MediaEditor.js.map +1 -0
- package/lib/module/native/MediaLibrary.js +37 -0
- package/lib/module/native/MediaLibrary.js.map +1 -0
- package/lib/module/native/MediaPicker.js +13 -0
- package/lib/module/native/MediaPicker.js.map +1 -0
- package/lib/module/native/MediaPlayer.js +13 -0
- package/lib/module/native/MediaPlayer.js.map +1 -0
- package/lib/module/native/VideoPreview.js +12 -0
- package/lib/module/native/VideoPreview.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/screens/CropScreen.js +1211 -0
- package/lib/module/screens/CropScreen.js.map +1 -0
- package/lib/module/screens/EditorScreen.js +5752 -0
- package/lib/module/screens/EditorScreen.js.map +1 -0
- package/lib/module/screens/ExportScreen.js +289 -0
- package/lib/module/screens/ExportScreen.js.map +1 -0
- package/lib/module/screens/GalleryScreen.js +505 -0
- package/lib/module/screens/GalleryScreen.js.map +1 -0
- package/lib/module/screens/PickScreen.js +1195 -0
- package/lib/module/screens/PickScreen.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/components/VideoEditor.d.ts +13 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/native/CameraView.d.ts +23 -0
- package/lib/typescript/src/native/FrameGrabber.d.ts +2 -0
- package/lib/typescript/src/native/MediaEditor.d.ts +3 -0
- package/lib/typescript/src/native/MediaLibrary.d.ts +16 -0
- package/lib/typescript/src/native/MediaPicker.d.ts +2 -0
- package/lib/typescript/src/native/MediaPlayer.d.ts +1 -0
- package/lib/typescript/src/native/VideoPreview.d.ts +19 -0
- package/lib/typescript/src/screens/CropScreen.d.ts +9 -0
- package/lib/typescript/src/screens/EditorScreen.d.ts +10 -0
- package/lib/typescript/src/screens/ExportScreen.d.ts +9 -0
- package/lib/typescript/src/screens/GalleryScreen.d.ts +8 -0
- package/lib/typescript/src/screens/PickScreen.d.ts +13 -0
- package/lib/typescript/src/types.d.ts +58 -0
- package/package.json +101 -0
- package/src/assets/frames/film_vintage.png +0 -0
- package/src/assets/frames/floral_gold.png +0 -0
- package/src/assets/frames/minimal_double.png +0 -0
- package/src/assets/frames/polaroid_white.png +0 -0
- package/src/assets/frames/watercolor_floral.png +0 -0
- package/src/components/VideoEditor.tsx +182 -0
- package/src/index.tsx +2 -0
- package/src/native/CameraView.tsx +95 -0
- package/src/native/FrameGrabber.ts +21 -0
- package/src/native/MediaEditor.ts +33 -0
- package/src/native/MediaLibrary.ts +69 -0
- package/src/native/MediaPicker.ts +17 -0
- package/src/native/MediaPlayer.ts +16 -0
- package/src/native/VideoPreview.tsx +20 -0
- package/src/screens/CropScreen.tsx +968 -0
- package/src/screens/EditorScreen.tsx +4517 -0
- package/src/screens/ExportScreen.tsx +282 -0
- package/src/screens/GalleryScreen.tsx +412 -0
- package/src/screens/PickScreen.tsx +1094 -0
- package/src/types.ts +58 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
package com.technotoil.image_videoeditor
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.ImageFormat
|
|
6
|
+
import android.graphics.SurfaceTexture
|
|
7
|
+
import android.hardware.camera2.*
|
|
8
|
+
import android.media.ImageReader
|
|
9
|
+
import android.media.MediaMetadataRetriever
|
|
10
|
+
import android.media.MediaRecorder
|
|
11
|
+
import android.net.Uri
|
|
12
|
+
import android.os.Handler
|
|
13
|
+
import android.os.HandlerThread
|
|
14
|
+
import android.util.Log
|
|
15
|
+
import android.view.Surface
|
|
16
|
+
import android.view.TextureView
|
|
17
|
+
import android.widget.FrameLayout
|
|
18
|
+
import com.facebook.react.bridge.*
|
|
19
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
20
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
21
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
22
|
+
import com.facebook.react.uimanager.UIManagerModule
|
|
23
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
24
|
+
import java.io.File
|
|
25
|
+
import java.io.FileOutputStream
|
|
26
|
+
|
|
27
|
+
@SuppressLint("MissingPermission")
|
|
28
|
+
class RNCameraView(context: Context) : FrameLayout(context) {
|
|
29
|
+
private val textureView = TextureView(context)
|
|
30
|
+
var facing: String = "front"
|
|
31
|
+
set(value) {
|
|
32
|
+
if (field != value) {
|
|
33
|
+
field = value
|
|
34
|
+
if (isCameraOpen) {
|
|
35
|
+
reopenCamera()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private var cameraDevice: CameraDevice? = null
|
|
41
|
+
private var captureSession: CameraCaptureSession? = null
|
|
42
|
+
private var imageReader: ImageReader? = null
|
|
43
|
+
private var mediaRecorder: MediaRecorder? = null
|
|
44
|
+
private var isCameraOpen = false
|
|
45
|
+
|
|
46
|
+
private var backgroundThread: HandlerThread? = null
|
|
47
|
+
private var backgroundHandler: Handler? = null
|
|
48
|
+
|
|
49
|
+
private var photoPromise: Promise? = null
|
|
50
|
+
private var videoRecordPromise: Promise? = null
|
|
51
|
+
private var videoStopPromise: Promise? = null
|
|
52
|
+
private var currentVideoFile: File? = null
|
|
53
|
+
private var isRecording = false
|
|
54
|
+
private var flashMode = "off"
|
|
55
|
+
private var previewBuilder: CaptureRequest.Builder? = null
|
|
56
|
+
|
|
57
|
+
private var currentPreviewSize: android.util.Size = android.util.Size(1920, 1080)
|
|
58
|
+
|
|
59
|
+
private val textureListener = object : TextureView.SurfaceTextureListener {
|
|
60
|
+
override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
|
|
61
|
+
openCamera()
|
|
62
|
+
}
|
|
63
|
+
override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {}
|
|
64
|
+
override fun onSurfaceTextureDestroyed(texture: SurfaceTexture): Boolean {
|
|
65
|
+
closeCamera()
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private val stateCallback = object : CameraDevice.StateCallback() {
|
|
72
|
+
override fun onOpened(camera: CameraDevice) {
|
|
73
|
+
cameraDevice = camera
|
|
74
|
+
isCameraOpen = true
|
|
75
|
+
createCameraPreview()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override fun onDisconnected(camera: CameraDevice) {
|
|
79
|
+
camera.close()
|
|
80
|
+
cameraDevice = null
|
|
81
|
+
isCameraOpen = false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override fun onError(camera: CameraDevice, error: Int) {
|
|
85
|
+
camera.close()
|
|
86
|
+
cameraDevice = null
|
|
87
|
+
isCameraOpen = false
|
|
88
|
+
Log.e("RNCameraView", "CameraDevice Error: $error")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private val imageAvailableListener = ImageReader.OnImageAvailableListener { reader ->
|
|
93
|
+
Log.w("RNCameraView", "imageAvailableListener triggered")
|
|
94
|
+
var image: android.media.Image? = null
|
|
95
|
+
try {
|
|
96
|
+
image = reader.acquireNextImage()
|
|
97
|
+
if (image == null) {
|
|
98
|
+
Log.e("RNCameraView", "No image acquired from reader")
|
|
99
|
+
return@OnImageAvailableListener
|
|
100
|
+
}
|
|
101
|
+
val buffer = image.planes[0].buffer
|
|
102
|
+
val bytes = ByteArray(buffer.remaining())
|
|
103
|
+
buffer.get(bytes)
|
|
104
|
+
|
|
105
|
+
// Save to cache dir
|
|
106
|
+
val file = File(context.cacheDir, "photo_${System.currentTimeMillis()}.jpg")
|
|
107
|
+
Log.w("RNCameraView", "Saving captured photo to path: ${file.absolutePath}")
|
|
108
|
+
FileOutputStream(file).use { it.write(bytes) }
|
|
109
|
+
|
|
110
|
+
val options = android.graphics.BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
111
|
+
android.graphics.BitmapFactory.decodeFile(file.absolutePath, options)
|
|
112
|
+
|
|
113
|
+
Log.w("RNCameraView", "Decoded bounds: width=${options.outWidth}, height=${options.outHeight}")
|
|
114
|
+
val map = Arguments.createMap().apply {
|
|
115
|
+
putString("uri", Uri.fromFile(file).toString())
|
|
116
|
+
putInt("width", options.outWidth)
|
|
117
|
+
putInt("height", options.outHeight)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Log.w("RNCameraView", "Resolving photoPromise with URI: ${Uri.fromFile(file)}")
|
|
121
|
+
photoPromise?.resolve(map)
|
|
122
|
+
photoPromise = null
|
|
123
|
+
} catch (e: Exception) {
|
|
124
|
+
Log.e("RNCameraView", "Error in imageAvailableListener: ${e.message}")
|
|
125
|
+
photoPromise?.reject("save_error", e.message)
|
|
126
|
+
photoPromise = null
|
|
127
|
+
} finally {
|
|
128
|
+
image?.close()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private fun getOptimalPreviewSize(sizes: Array<android.util.Size>?): android.util.Size {
|
|
133
|
+
if (sizes.isNullOrEmpty()) return android.util.Size(1920, 1080)
|
|
134
|
+
|
|
135
|
+
val targetRatio = 16.0 / 9.0
|
|
136
|
+
val tolerance = 0.1
|
|
137
|
+
|
|
138
|
+
val matches = sizes.filter {
|
|
139
|
+
val ratio = it.width.toFloat() / it.height.toFloat()
|
|
140
|
+
Math.abs(ratio - targetRatio) < tolerance || Math.abs((1.0 / ratio) - targetRatio) < tolerance
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (matches.isNotEmpty()) {
|
|
144
|
+
return matches.minByOrNull {
|
|
145
|
+
Math.abs(it.width - 1920) + Math.abs(it.height - 1080)
|
|
146
|
+
} ?: matches[0]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return sizes.minByOrNull {
|
|
150
|
+
Math.abs(it.width - 1920) + Math.abs(it.height - 1080)
|
|
151
|
+
} ?: sizes[0]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun adjustAspectRatio(viewWidth: Int, viewHeight: Int) {
|
|
155
|
+
if (viewWidth == 0 || viewHeight == 0) return
|
|
156
|
+
val previewSize = currentPreviewSize
|
|
157
|
+
|
|
158
|
+
val previewAspect = previewSize.height.toFloat() / previewSize.width.toFloat()
|
|
159
|
+
val viewAspect = viewWidth.toFloat() / viewHeight.toFloat()
|
|
160
|
+
|
|
161
|
+
val matrix = android.graphics.Matrix()
|
|
162
|
+
|
|
163
|
+
var scaleX = 1f
|
|
164
|
+
var scaleY = 1f
|
|
165
|
+
|
|
166
|
+
if (viewAspect > previewAspect) {
|
|
167
|
+
scaleY = viewAspect / previewAspect
|
|
168
|
+
} else {
|
|
169
|
+
scaleX = previewAspect / viewAspect
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
matrix.setScale(scaleX, scaleY, viewWidth / 2f, viewHeight / 2f)
|
|
173
|
+
|
|
174
|
+
val reactContext = context as? com.facebook.react.bridge.ReactContext
|
|
175
|
+
if (reactContext != null) {
|
|
176
|
+
reactContext.runOnUiQueueThread {
|
|
177
|
+
textureView.setTransform(matrix)
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
textureView.setTransform(matrix)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
init {
|
|
185
|
+
addView(textureView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
186
|
+
textureView.surfaceTextureListener = textureListener
|
|
187
|
+
textureView.addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ ->
|
|
188
|
+
adjustAspectRatio(right - left, bottom - top)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
override fun onAttachedToWindow() {
|
|
193
|
+
super.onAttachedToWindow()
|
|
194
|
+
startBackgroundThread()
|
|
195
|
+
if (textureView.isAvailable) {
|
|
196
|
+
openCamera()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
override fun onDetachedFromWindow() {
|
|
201
|
+
closeCamera()
|
|
202
|
+
stopBackgroundThread()
|
|
203
|
+
super.onDetachedFromWindow()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private fun startBackgroundThread() {
|
|
207
|
+
backgroundThread = HandlerThread("CameraBackground").also { it.start() }
|
|
208
|
+
backgroundHandler = Handler(backgroundThread!!.looper)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private fun stopBackgroundThread() {
|
|
212
|
+
backgroundThread?.quitSafely()
|
|
213
|
+
try {
|
|
214
|
+
backgroundThread?.join()
|
|
215
|
+
backgroundThread = null
|
|
216
|
+
backgroundHandler = null
|
|
217
|
+
} catch (e: InterruptedException) {
|
|
218
|
+
e.printStackTrace()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fun openCamera() {
|
|
223
|
+
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
224
|
+
try {
|
|
225
|
+
val cameraId = manager.cameraIdList.firstOrNull { id ->
|
|
226
|
+
val chars = manager.getCameraCharacteristics(id)
|
|
227
|
+
val facingChar = chars.get(CameraCharacteristics.LENS_FACING)
|
|
228
|
+
if (facing == "back") facingChar == CameraMetadata.LENS_FACING_BACK
|
|
229
|
+
else facingChar == CameraMetadata.LENS_FACING_FRONT
|
|
230
|
+
} ?: manager.cameraIdList.firstOrNull() ?: return
|
|
231
|
+
|
|
232
|
+
manager.openCamera(cameraId, stateCallback, backgroundHandler)
|
|
233
|
+
} catch (e: Exception) {
|
|
234
|
+
Log.e("RNCameraView", "openCamera failed: ${e.message}")
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private fun closeCamera() {
|
|
239
|
+
try {
|
|
240
|
+
captureSession?.close()
|
|
241
|
+
captureSession = null
|
|
242
|
+
cameraDevice?.close()
|
|
243
|
+
cameraDevice = null
|
|
244
|
+
isCameraOpen = false
|
|
245
|
+
imageReader?.close()
|
|
246
|
+
imageReader = null
|
|
247
|
+
|
|
248
|
+
mediaRecorder?.let {
|
|
249
|
+
try {
|
|
250
|
+
it.reset()
|
|
251
|
+
} catch (e: Exception) {}
|
|
252
|
+
try {
|
|
253
|
+
it.release()
|
|
254
|
+
} catch (e: Exception) {}
|
|
255
|
+
}
|
|
256
|
+
mediaRecorder = null
|
|
257
|
+
isRecording = false
|
|
258
|
+
} catch (e: Exception) {
|
|
259
|
+
e.printStackTrace()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private fun reopenCamera() {
|
|
264
|
+
closeCamera()
|
|
265
|
+
openCamera()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private fun createCameraPreview() {
|
|
269
|
+
val device = cameraDevice ?: return
|
|
270
|
+
val texture = textureView.surfaceTexture ?: return
|
|
271
|
+
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
272
|
+
try {
|
|
273
|
+
val chars = manager.getCameraCharacteristics(device.id)
|
|
274
|
+
val map = chars.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
|
|
275
|
+
val previewSize = getOptimalPreviewSize(map?.getOutputSizes(SurfaceTexture::class.java))
|
|
276
|
+
currentPreviewSize = previewSize
|
|
277
|
+
texture.setDefaultBufferSize(previewSize.width, previewSize.height)
|
|
278
|
+
val reactContext = context as? com.facebook.react.bridge.ReactContext
|
|
279
|
+
reactContext?.runOnUiQueueThread {
|
|
280
|
+
adjustAspectRatio(textureView.width, textureView.height)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
val surface = Surface(texture)
|
|
284
|
+
|
|
285
|
+
// Image reader setup
|
|
286
|
+
val readerSizes = map?.getOutputSizes(ImageFormat.JPEG) ?: emptyArray()
|
|
287
|
+
val photoSize = readerSizes.firstOrNull() ?: android.util.Size(1920, 1080)
|
|
288
|
+
imageReader = ImageReader.newInstance(photoSize.width, photoSize.height, ImageFormat.JPEG, 2)
|
|
289
|
+
imageReader?.setOnImageAvailableListener(imageAvailableListener, backgroundHandler)
|
|
290
|
+
|
|
291
|
+
val builder = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
|
|
292
|
+
builder.addTarget(surface)
|
|
293
|
+
val hasFlash = chars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
|
|
294
|
+
if (hasFlash) {
|
|
295
|
+
if (flashMode == "on") {
|
|
296
|
+
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH)
|
|
297
|
+
} else {
|
|
298
|
+
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
previewBuilder = builder
|
|
302
|
+
|
|
303
|
+
device.createCaptureSession(listOf(surface, imageReader!!.surface), object : CameraCaptureSession.StateCallback() {
|
|
304
|
+
override fun onConfigured(session: CameraCaptureSession) {
|
|
305
|
+
if (cameraDevice == null) return
|
|
306
|
+
captureSession = session
|
|
307
|
+
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
|
|
308
|
+
try {
|
|
309
|
+
session.setRepeatingRequest(builder.build(), null, backgroundHandler)
|
|
310
|
+
} catch (e: Exception) {
|
|
311
|
+
e.printStackTrace()
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
override fun onConfigureFailed(session: CameraCaptureSession) {
|
|
316
|
+
Log.e("RNCameraView", "Preview session configuration failed")
|
|
317
|
+
}
|
|
318
|
+
}, backgroundHandler)
|
|
319
|
+
} catch (e: Exception) {
|
|
320
|
+
e.printStackTrace()
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fun capturePhoto(promise: Promise) {
|
|
325
|
+
Log.w("RNCameraView", "capturePhoto called")
|
|
326
|
+
val device = cameraDevice ?: run {
|
|
327
|
+
Log.e("RNCameraView", "capturePhoto error: Camera not ready")
|
|
328
|
+
return promise.reject("camera_error", "Camera not ready")
|
|
329
|
+
}
|
|
330
|
+
val reader = imageReader ?: run {
|
|
331
|
+
Log.e("RNCameraView", "capturePhoto error: ImageReader not ready")
|
|
332
|
+
return promise.reject("camera_error", "ImageReader not ready")
|
|
333
|
+
}
|
|
334
|
+
val session = captureSession ?: run {
|
|
335
|
+
Log.e("RNCameraView", "capturePhoto error: Session not ready")
|
|
336
|
+
return promise.reject("camera_error", "Session not ready")
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
photoPromise = promise
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
val captureBuilder = device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
|
|
343
|
+
captureBuilder.addTarget(reader.surface)
|
|
344
|
+
captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
|
|
345
|
+
|
|
346
|
+
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
347
|
+
val chars = manager.getCameraCharacteristics(device.id)
|
|
348
|
+
val sensorOrientation = chars.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 90
|
|
349
|
+
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, sensorOrientation)
|
|
350
|
+
|
|
351
|
+
val hasFlash = chars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
|
|
352
|
+
if (hasFlash) {
|
|
353
|
+
if (flashMode == "on") {
|
|
354
|
+
captureBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH)
|
|
355
|
+
} else {
|
|
356
|
+
captureBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// session.stopRepeating()
|
|
361
|
+
Log.w("RNCameraView", "Calling session.capture")
|
|
362
|
+
session.capture(captureBuilder.build(), object : CameraCaptureSession.CaptureCallback() {
|
|
363
|
+
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
|
|
364
|
+
Log.w("RNCameraView", "onCaptureStarted: timestamp=$timestamp, frameNumber=$frameNumber")
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
override fun onCaptureFailed(session: CameraCaptureSession, request: CaptureRequest, failure: CaptureFailure) {
|
|
368
|
+
Log.e("RNCameraView", "onCaptureFailed: reason=${failure.reason}, wasImageCaptured=${failure.wasImageCaptured()}")
|
|
369
|
+
photoPromise?.reject("capture_failed", "Capture failed: reason=${failure.reason}")
|
|
370
|
+
photoPromise = null
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
|
|
374
|
+
Log.w("RNCameraView", "onCaptureCompleted triggered")
|
|
375
|
+
}
|
|
376
|
+
}, backgroundHandler)
|
|
377
|
+
} catch (e: Exception) {
|
|
378
|
+
Log.e("RNCameraView", "capturePhoto exception: ${e.message}")
|
|
379
|
+
promise.reject("capture_error", e.message)
|
|
380
|
+
photoPromise = null
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
fun startRecording(promise: Promise) {
|
|
385
|
+
if (isRecording) {
|
|
386
|
+
return promise.reject("already_recording", "Camera is already recording video.")
|
|
387
|
+
}
|
|
388
|
+
val recordAudioPermission = androidx.core.content.ContextCompat.checkSelfPermission(
|
|
389
|
+
context,
|
|
390
|
+
android.Manifest.permission.RECORD_AUDIO
|
|
391
|
+
)
|
|
392
|
+
if (recordAudioPermission != android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
|
393
|
+
return promise.reject("permission_denied", "Microphone permission is not granted.")
|
|
394
|
+
}
|
|
395
|
+
val cameraPermission = androidx.core.content.ContextCompat.checkSelfPermission(
|
|
396
|
+
context,
|
|
397
|
+
android.Manifest.permission.CAMERA
|
|
398
|
+
)
|
|
399
|
+
if (cameraPermission != android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
|
400
|
+
return promise.reject("permission_denied", "Camera permission is not granted.")
|
|
401
|
+
}
|
|
402
|
+
val device = cameraDevice ?: return promise.reject("camera_error", "Camera not ready")
|
|
403
|
+
val texture = textureView.surfaceTexture ?: return promise.reject("camera_error", "Texture not ready")
|
|
404
|
+
|
|
405
|
+
videoRecordPromise = promise
|
|
406
|
+
currentVideoFile = File(context.cacheDir, "video_${System.currentTimeMillis()}.mp4")
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
closeCamera() // Close standard preview first
|
|
410
|
+
openCamera() // Wait for camera to open and prepare recorder
|
|
411
|
+
|
|
412
|
+
// Wait for cameraDevice is ready to record
|
|
413
|
+
backgroundHandler?.post {
|
|
414
|
+
while (cameraDevice == null) {
|
|
415
|
+
Thread.sleep(50)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
val recordDevice = cameraDevice!!
|
|
419
|
+
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
420
|
+
val chars = manager.getCameraCharacteristics(recordDevice.id)
|
|
421
|
+
val sensorOrientation = chars.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 90
|
|
422
|
+
|
|
423
|
+
mediaRecorder = MediaRecorder(context).apply {
|
|
424
|
+
setAudioSource(MediaRecorder.AudioSource.MIC)
|
|
425
|
+
setVideoSource(MediaRecorder.VideoSource.SURFACE)
|
|
426
|
+
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
|
427
|
+
setOutputFile(currentVideoFile!!.absolutePath)
|
|
428
|
+
setVideoEncodingBitRate(10000000)
|
|
429
|
+
setVideoFrameRate(30)
|
|
430
|
+
setVideoSize(1280, 720)
|
|
431
|
+
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
|
|
432
|
+
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
|
433
|
+
|
|
434
|
+
// Front camera video needs correct rotation
|
|
435
|
+
setOrientationHint(sensorOrientation)
|
|
436
|
+
prepare()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
val map = chars.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
|
|
440
|
+
val previewSize = getOptimalPreviewSize(map?.getOutputSizes(SurfaceTexture::class.java))
|
|
441
|
+
currentPreviewSize = previewSize
|
|
442
|
+
texture.setDefaultBufferSize(previewSize.width, previewSize.height)
|
|
443
|
+
val reactContext = context as? com.facebook.react.bridge.ReactContext
|
|
444
|
+
reactContext?.runOnUiQueueThread {
|
|
445
|
+
adjustAspectRatio(textureView.width, textureView.height)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
val previewSurface = Surface(texture)
|
|
449
|
+
val recorderSurface = mediaRecorder!!.surface
|
|
450
|
+
|
|
451
|
+
val recordBuilder = recordDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
|
|
452
|
+
recordBuilder.addTarget(previewSurface)
|
|
453
|
+
recordBuilder.addTarget(recorderSurface)
|
|
454
|
+
|
|
455
|
+
val hasFlash = chars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
|
|
456
|
+
if (hasFlash) {
|
|
457
|
+
if (flashMode == "on") {
|
|
458
|
+
recordBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH)
|
|
459
|
+
} else {
|
|
460
|
+
recordBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
recordDevice.createCaptureSession(listOf(previewSurface, recorderSurface), object : CameraCaptureSession.StateCallback() {
|
|
465
|
+
override fun onConfigured(session: CameraCaptureSession) {
|
|
466
|
+
captureSession = session
|
|
467
|
+
recordBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
|
|
468
|
+
try {
|
|
469
|
+
session.setRepeatingRequest(recordBuilder.build(), null, backgroundHandler)
|
|
470
|
+
mediaRecorder!!.start()
|
|
471
|
+
isRecording = true
|
|
472
|
+
videoRecordPromise?.resolve(null)
|
|
473
|
+
videoRecordPromise = null
|
|
474
|
+
} catch (e: Exception) {
|
|
475
|
+
videoRecordPromise?.reject("record_error", e.message)
|
|
476
|
+
videoRecordPromise = null
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
override fun onConfigureFailed(session: CameraCaptureSession) {
|
|
481
|
+
videoRecordPromise?.reject("record_error", "Video Session configure failed")
|
|
482
|
+
videoRecordPromise = null
|
|
483
|
+
}
|
|
484
|
+
}, backgroundHandler)
|
|
485
|
+
}
|
|
486
|
+
} catch (e: Exception) {
|
|
487
|
+
promise.reject("record_error", e.message)
|
|
488
|
+
videoRecordPromise = null
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
fun stopRecording(promise: Promise) {
|
|
493
|
+
if (!isRecording || mediaRecorder == null) {
|
|
494
|
+
return promise.reject("not_recording", "Camera is not recording.")
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
videoStopPromise = promise
|
|
498
|
+
|
|
499
|
+
backgroundHandler?.post {
|
|
500
|
+
try {
|
|
501
|
+
mediaRecorder!!.stop()
|
|
502
|
+
mediaRecorder!!.reset()
|
|
503
|
+
mediaRecorder = null
|
|
504
|
+
isRecording = false
|
|
505
|
+
|
|
506
|
+
val file = currentVideoFile ?: return@post promise.reject("error", "Recorded video file is missing")
|
|
507
|
+
|
|
508
|
+
val retriever = MediaMetadataRetriever()
|
|
509
|
+
retriever.setDataSource(file.absolutePath)
|
|
510
|
+
val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: 0L
|
|
511
|
+
val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 1280
|
|
512
|
+
val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 720
|
|
513
|
+
retriever.release()
|
|
514
|
+
|
|
515
|
+
val map = Arguments.createMap().apply {
|
|
516
|
+
putString("uri", Uri.fromFile(file).toString())
|
|
517
|
+
putDouble("durationMs", duration.toDouble())
|
|
518
|
+
putInt("width", width)
|
|
519
|
+
putInt("height", height)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Reopen normal camera preview
|
|
523
|
+
reopenCamera()
|
|
524
|
+
|
|
525
|
+
videoStopPromise?.resolve(map)
|
|
526
|
+
videoStopPromise = null
|
|
527
|
+
} catch (e: Exception) {
|
|
528
|
+
videoStopPromise?.reject("stop_error", e.message)
|
|
529
|
+
videoStopPromise = null
|
|
530
|
+
reopenCamera()
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
class CameraEvent(surfaceId: Int, viewId: Int, private val name: String, private val data: WritableMap?) :
|
|
536
|
+
com.facebook.react.uimanager.events.Event<CameraEvent>(surfaceId, viewId) {
|
|
537
|
+
|
|
538
|
+
override fun getEventName(): String = name
|
|
539
|
+
override fun getEventData(): WritableMap? = data
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private fun emitEvent(eventName: String, eventData: WritableMap?) {
|
|
543
|
+
Log.w("RNCameraView", "emitEvent called: eventName=$eventName, eventData=$eventData")
|
|
544
|
+
val reactContext = context as? ReactContext ?: return
|
|
545
|
+
try {
|
|
546
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
547
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
|
|
548
|
+
if (dispatcher != null) {
|
|
549
|
+
Log.w("RNCameraView", "emitEvent: using dispatcher")
|
|
550
|
+
dispatcher.dispatchEvent(CameraEvent(surfaceId, id, eventName, eventData))
|
|
551
|
+
} else {
|
|
552
|
+
Log.w("RNCameraView", "emitEvent: dispatcher is null, using RCTEventEmitter")
|
|
553
|
+
reactContext.getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter::class.java)
|
|
554
|
+
?.receiveEvent(id, eventName, eventData)
|
|
555
|
+
}
|
|
556
|
+
} catch (e: Exception) {
|
|
557
|
+
Log.w("RNCameraView", "emitEvent: first attempt failed, trying fallback: ${e.message}")
|
|
558
|
+
try {
|
|
559
|
+
reactContext.getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter::class.java)
|
|
560
|
+
?.receiveEvent(id, eventName, eventData)
|
|
561
|
+
} catch (e2: Exception) {
|
|
562
|
+
Log.e("RNCameraView", "emitEvent error: ${e2.message}")
|
|
563
|
+
e2.printStackTrace()
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
class EventPromise(
|
|
569
|
+
private val eventName: String,
|
|
570
|
+
private val emit: (String, WritableMap?) -> Unit
|
|
571
|
+
) : Promise {
|
|
572
|
+
override fun resolve(value: Any?) {
|
|
573
|
+
val map = value as? WritableMap
|
|
574
|
+
emit(eventName, map)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
override fun reject(code: String?, message: String?) {
|
|
578
|
+
val map = Arguments.createMap().apply {
|
|
579
|
+
putString("error", message ?: "Unknown error")
|
|
580
|
+
}
|
|
581
|
+
emit(eventName, map)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
override fun reject(code: String?, throwable: Throwable?) {
|
|
585
|
+
reject(code, throwable?.message)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
override fun reject(code: String?, message: String?, throwable: Throwable?) {
|
|
589
|
+
reject(code, message)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
override fun reject(throwable: Throwable) {
|
|
593
|
+
reject("error", throwable.message)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
override fun reject(throwable: Throwable, userInfo: WritableMap) {
|
|
597
|
+
reject("error", throwable.message)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
override fun reject(code: String?, userInfo: WritableMap) {
|
|
601
|
+
reject(code, "error")
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
override fun reject(code: String?, throwable: Throwable?, userInfo: WritableMap) {
|
|
605
|
+
reject(code, throwable?.message)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
override fun reject(code: String?, message: String?, userInfo: WritableMap) {
|
|
609
|
+
reject(code, message)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
override fun reject(code: String?, message: String?, throwable: Throwable?, userInfo: WritableMap?) {
|
|
613
|
+
reject(code, message)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
override fun reject(message: String) {
|
|
617
|
+
reject("error", message)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
fun setPhotoTrigger(trigger: String?) {
|
|
622
|
+
Log.w("RNCameraView", "setPhotoTrigger called with: $trigger")
|
|
623
|
+
if (trigger.isNullOrEmpty()) return
|
|
624
|
+
val promise = EventPromise("topPhotoCaptured", ::emitEvent)
|
|
625
|
+
capturePhoto(promise)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
fun setRecordTrigger(trigger: String?) {
|
|
629
|
+
if (trigger == "start") {
|
|
630
|
+
val startPromise = EventPromise("topRecordStarted", ::emitEvent)
|
|
631
|
+
startRecording(startPromise)
|
|
632
|
+
} else if (trigger == "stop") {
|
|
633
|
+
val stopPromise = EventPromise("topRecordStopped", ::emitEvent)
|
|
634
|
+
stopRecording(stopPromise)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
fun setFlashMode(flash: String?) {
|
|
639
|
+
val newFlash = flash ?: "off"
|
|
640
|
+
if (flashMode != newFlash) {
|
|
641
|
+
flashMode = newFlash
|
|
642
|
+
applyFlashToPreview()
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private fun applyFlashToPreview() {
|
|
647
|
+
val session = captureSession ?: return
|
|
648
|
+
val builder = previewBuilder ?: return
|
|
649
|
+
val device = cameraDevice ?: return
|
|
650
|
+
try {
|
|
651
|
+
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
652
|
+
val chars = manager.getCameraCharacteristics(device.id)
|
|
653
|
+
val hasFlash = chars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
|
|
654
|
+
if (hasFlash) {
|
|
655
|
+
if (flashMode == "on") {
|
|
656
|
+
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH)
|
|
657
|
+
} else {
|
|
658
|
+
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF)
|
|
659
|
+
}
|
|
660
|
+
session.setRepeatingRequest(builder.build(), null, backgroundHandler)
|
|
661
|
+
}
|
|
662
|
+
} catch (e: Exception) {
|
|
663
|
+
e.printStackTrace()
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
class RNCameraViewManager(private val reactContext: ReactApplicationContext) :
|
|
669
|
+
SimpleViewManager<RNCameraView>() {
|
|
670
|
+
|
|
671
|
+
override fun getName(): String = "RNCameraView"
|
|
672
|
+
|
|
673
|
+
override fun createViewInstance(reactContext: ThemedReactContext): RNCameraView {
|
|
674
|
+
return RNCameraView(reactContext)
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
@ReactProp(name = "facing")
|
|
678
|
+
fun setFacing(view: RNCameraView, facing: String?) {
|
|
679
|
+
view.facing = facing ?: "front"
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@ReactProp(name = "flashMode")
|
|
683
|
+
fun setFlashMode(view: RNCameraView, flashMode: String?) {
|
|
684
|
+
view.setFlashMode(flashMode)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
@ReactProp(name = "photoTrigger")
|
|
688
|
+
fun setPhotoTrigger(view: RNCameraView, photoTrigger: String?) {
|
|
689
|
+
view.setPhotoTrigger(photoTrigger)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
@ReactProp(name = "recordTrigger")
|
|
693
|
+
fun setRecordTrigger(view: RNCameraView, recordTrigger: String?) {
|
|
694
|
+
view.setRecordTrigger(recordTrigger)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
698
|
+
return com.facebook.react.common.MapBuilder.builder<String, Any>()
|
|
699
|
+
.put("topPhotoCaptured", com.facebook.react.common.MapBuilder.of("registrationName", "onPhotoCaptured"))
|
|
700
|
+
.put("topRecordStarted", com.facebook.react.common.MapBuilder.of("registrationName", "onRecordStarted"))
|
|
701
|
+
.put("topRecordStopped", com.facebook.react.common.MapBuilder.of("registrationName", "onRecordStopped"))
|
|
702
|
+
.build()
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
class RNCameraModule(private val reactContext: ReactApplicationContext) :
|
|
707
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
708
|
+
|
|
709
|
+
override fun getName(): String = "RNCameraModule"
|
|
710
|
+
|
|
711
|
+
@ReactMethod
|
|
712
|
+
fun capturePhoto(reactTag: Int, promise: Promise) {
|
|
713
|
+
val uiManager = reactContext.getNativeModule(UIManagerModule::class.java)
|
|
714
|
+
reactContext.runOnUiQueueThread {
|
|
715
|
+
try {
|
|
716
|
+
val view = uiManager?.resolveView(reactTag) as? RNCameraView
|
|
717
|
+
if (view != null) {
|
|
718
|
+
view.capturePhoto(promise)
|
|
719
|
+
} else {
|
|
720
|
+
promise.reject("error", "Camera view not found")
|
|
721
|
+
}
|
|
722
|
+
} catch (e: Exception) {
|
|
723
|
+
promise.reject("error", e.message)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
@ReactMethod
|
|
729
|
+
fun startRecording(reactTag: Int, promise: Promise) {
|
|
730
|
+
val uiManager = reactContext.getNativeModule(UIManagerModule::class.java)
|
|
731
|
+
reactContext.runOnUiQueueThread {
|
|
732
|
+
try {
|
|
733
|
+
val view = uiManager?.resolveView(reactTag) as? RNCameraView
|
|
734
|
+
if (view != null) {
|
|
735
|
+
view.startRecording(promise)
|
|
736
|
+
} else {
|
|
737
|
+
promise.reject("error", "Camera view not found")
|
|
738
|
+
}
|
|
739
|
+
} catch (e: Exception) {
|
|
740
|
+
promise.reject("error", e.message)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
@ReactMethod
|
|
746
|
+
fun stopRecording(reactTag: Int, promise: Promise) {
|
|
747
|
+
val uiManager = reactContext.getNativeModule(UIManagerModule::class.java)
|
|
748
|
+
reactContext.runOnUiQueueThread {
|
|
749
|
+
try {
|
|
750
|
+
val view = uiManager?.resolveView(reactTag) as? RNCameraView
|
|
751
|
+
if (view != null) {
|
|
752
|
+
view.stopRecording(promise)
|
|
753
|
+
} else {
|
|
754
|
+
promise.reject("error", "Camera view not found")
|
|
755
|
+
}
|
|
756
|
+
} catch (e: Exception) {
|
|
757
|
+
promise.reject("error", e.message)
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|