diff --git a/android/src/main/java/com/oney/WebRTCModule/EffectsSDKCameraCapturer.kt b/android/src/main/java/com/oney/WebRTCModule/EffectsSDKCameraCapturer.kt new file mode 100644 index 000000000..a39cddb5d --- /dev/null +++ b/android/src/main/java/com/oney/WebRTCModule/EffectsSDKCameraCapturer.kt @@ -0,0 +1,401 @@ +package com.oney.WebRTCModule + +import android.content.Context +import android.graphics.Bitmap +import android.util.Log +import android.util.Size +import com.effectssdk.tsvb.Camera +import com.effectssdk.tsvb.EffectsSDK +import com.effectssdk.tsvb.EffectsSDKStatus +import com.effectssdk.tsvb.pipeline.CameraPipeline +import com.effectssdk.tsvb.pipeline.ColorCorrectionMode +import com.effectssdk.tsvb.pipeline.OnFrameAvailableListener +import com.effectssdk.tsvb.pipeline.PipelineMode +import kotlinx.coroutines.runBlocking +import org.webrtc.CameraEnumerator +import org.webrtc.CameraVideoCapturer +import org.webrtc.CameraVideoCapturer.CameraEventsHandler +import org.webrtc.CapturerObserver +import org.webrtc.NV21Buffer +import org.webrtc.SurfaceTextureHelper +import org.webrtc.VideoFrame +import java.net.URL +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + + +/** + * Custom video capturer for Effects SDK + */ +class EffectsSDKCameraCapturer( + private val device: String, + private val eventsHandler: CameraEventsHandler, + enumerator: CameraEnumerator +) : CameraVideoCapturer { + + private var isPipelineCameraUsed: Boolean = false + + private var context: Context? = null + private var capturerObserver: CapturerObserver? = null + //Default WebRTC capturer. Used until EffectsSDK not ready to provide frames + //You can remove this if you don't need non-processed frames + private var webRtcCameraCapturer: CameraVideoCapturer? = null + + private var cameraPipeline: CameraPipeline? = null + private var defaultPipelineOptions = EffectsSdkOptionsCache() + private var currentPipelineOptions = EffectsSdkOptionsCache() + + init { + //Custom event handler. Used until EffectsSDK not ready to provide frames + val cameraEventHandler = object : CameraEventsHandler { + override fun onCameraError(p0: String?) { + if (!isPipelineCameraUsed) eventsHandler.onCameraError(p0) + } + + override fun onCameraDisconnected() { + if (!isPipelineCameraUsed) eventsHandler.onCameraDisconnected() + } + + override fun onCameraFreezed(p0: String?) { + if (!isPipelineCameraUsed) eventsHandler.onCameraFreezed(p0) + } + + override fun onCameraOpening(p0: String?) { + if (!isPipelineCameraUsed) eventsHandler.onCameraOpening(p0) + } + + override fun onFirstFrameAvailable() { + if (!isPipelineCameraUsed) eventsHandler.onFirstFrameAvailable() + } + + override fun onCameraClosed() { + if (!isPipelineCameraUsed) eventsHandler.onCameraClosed() + } + + } + webRtcCameraCapturer = enumerator.createCapturer(device, cameraEventHandler) + } + + override fun initialize( + surfaceTextureHelper: SurfaceTextureHelper?, + context: Context?, + observer: CapturerObserver? + ) { + if (!isPipelineCameraUsed) { + //Custom Capturer observer. Used until EffectsSDK not ready to provide frames + val nativeCapturerObserver = object : CapturerObserver { + override fun onCapturerStarted(p0: Boolean) { + if (!isPipelineCameraUsed) capturerObserver?.onCapturerStarted(p0) + } + + override fun onCapturerStopped() { + if (!isPipelineCameraUsed) capturerObserver?.onCapturerStopped() + + } + + override fun onFrameCaptured(p0: VideoFrame?) { + if (!isPipelineCameraUsed) capturerObserver?.onFrameCaptured(p0) + } + } + webRtcCameraCapturer?.initialize(surfaceTextureHelper, context, nativeCapturerObserver) + } + this.context = context + capturerObserver = observer + } + + + override fun startCapture(width: Int, height: Int, framerate: Int) { + if (!isPipelineCameraUsed) { + webRtcCameraCapturer?.startCapture(width, height, framerate) + } else { + createPipeline(height, width) + cameraPipeline?.startPipeline() + cameraPipeline?.setOnFrameAvailableListener(onFrameAvailableListener) + } + } + + override fun stopCapture() { + if (!isPipelineCameraUsed) { + webRtcCameraCapturer?.stopCapture() + } else { + cameraPipeline?.setOnFrameAvailableListener(null) + cameraPipeline?.release() + cameraPipeline = null + } + eventsHandler.onCameraClosed() + } + + private fun createPipeline(width: Int = 1280, height: Int = 720) { + val factory = EffectsSDK.createSDKFactory() + cameraPipeline = factory.createCameraPipeline( + context!!, + camera = if (device == "1") Camera.FRONT else Camera.BACK, + resolution = Size(width, height) + ) + setPipelineOptionsFromCache(currentPipelineOptions) + } + + /* + * If you don't need frames, you should set "empty" options to avoid background segmentation + */ + fun enableVideo(enabled: Boolean) { + if (enabled) { + setPipelineOptionsFromCache(currentPipelineOptions) + cameraPipeline?.setOnFrameAvailableListener(onFrameAvailableListener) + } else { + setPipelineOptionsFromCache(defaultPipelineOptions) + cameraPipeline?.setOnFrameAvailableListener(null) + } + } + + override fun changeCaptureFormat(width: Int, height: Int, framerate: Int) { + if (!isPipelineCameraUsed) { + webRtcCameraCapturer?.changeCaptureFormat(width, height, framerate) + } + } + + override fun dispose() { + if (!isPipelineCameraUsed) { + webRtcCameraCapturer?.dispose() + } + } + + override fun isScreencast(): Boolean { + return false + } + + override fun switchCamera(switchEventsHandler: CameraVideoCapturer.CameraSwitchHandler?) { + if (!isPipelineCameraUsed) { + webRtcCameraCapturer?.switchCamera(switchEventsHandler) + } + switchEventsHandler?.onCameraSwitchDone(true) + } + + override fun switchCamera( + switchEventsHandler: CameraVideoCapturer.CameraSwitchHandler?, + p1: String? + ) { + if (!isPipelineCameraUsed) { + webRtcCameraCapturer?.switchCamera(switchEventsHandler, p1) + } + switchEventsHandler?.onCameraSwitchDone(true) + } + + private fun getNV21(scaled: Bitmap): ByteArray { + val argb = IntArray(scaled.width * scaled.height) + scaled.getPixels(argb, 0, scaled.width, 0, 0, scaled.width, scaled.height) + val yuv = ByteArray(scaled.width * scaled.height * 3 / 2) + encodeYUV420SP(yuv, argb, scaled.width, scaled.height) + return yuv + } + + private fun encodeYUV420SP(yuv420sp: ByteArray, argb: IntArray, width: Int, height: Int) { + val frameSize = width * height + + var yIndex = 0 + var uvIndex = frameSize + + var R: Int + var G: Int + var B: Int + var Y: Int + var U: Int + var V: Int + var index = 0 + for (j in 0 until height) { + for (i in 0 until width) { + R = (argb[index] and 0xff0000) shr 16 + G = (argb[index] and 0xff00) shr 8 + B = (argb[index] and 0xff) shr 0 + + Y = ((66 * R + 129 * G + 25 * B + 128) shr 8) + 16 + U = ((-38 * R - 74 * G + 112 * B + 128) shr 8) + 128 + V = ((112 * R - 94 * G - 18 * B + 128) shr 8) + 128 + + yuv420sp[yIndex++] = Y.toByte() + if (j % 2 == 0 && index % 2 == 0) { + yuv420sp[uvIndex++] = V.toByte() + yuv420sp[uvIndex++] = U.toByte() + } + + index++ + } + } + } + + private val onFrameAvailableListener = OnFrameAvailableListener { bitmap, timestamp -> + if (!isPipelineCameraUsed) { + isPipelineCameraUsed = true + webRtcCameraCapturer?.stopCapture() + webRtcCameraCapturer?.dispose() + webRtcCameraCapturer = null + } + val videoFrame = VideoFrame( + NV21Buffer( + getNV21(bitmap), + bitmap.width, + bitmap.height, + { bitmap.recycle() } + ), + 0, + timestamp * 1_000_000 //millisectonds to nanoseconds + ) + capturerObserver?.onFrameCaptured(videoFrame) + } + + fun initializeEffectsSdk(customerId: String, url: String?): EffectsSDKStatus { + var result: EffectsSDKStatus + runBlocking { + result = if (url == null) { + initializeCallback(context!!, customerId) + } else { + initializeCallback(context!!, customerId, URL(url)) + } + } + if (result == EffectsSDKStatus.ACTIVE) { + createPipeline() + cameraPipeline?.startPipeline() + cameraPipeline?.setFlipX(false) + cameraPipeline?.setOnFrameAvailableListener(onFrameAvailableListener) + } + return result + } + + private suspend fun initializeCallback( + context: Context, + customerId: String, + url: URL? = null + ): EffectsSDKStatus { + return suspendCoroutine { continuation -> + EffectsSDK.initialize(context, customerId, url) { sdkStatus -> + continuation.resume(sdkStatus) + } + } + } + + fun initializeEffectsSdkLocal(localKey: String): EffectsSDKStatus { + return EffectsSDK.initialize(context!!, localKey) + } + + fun getCurrentDevice(): String { + return device + } + + fun setPipelineMode(pipelineMode: String) { + var value: String = pipelineMode.split('.')[1] + if (value == "noEffects") value = "no_effect" + val mode: PipelineMode = PipelineMode.valueOf(value.uppercase()) + currentPipelineOptions.pipelineMode = mode + cameraPipeline?.setMode(mode) + } + + fun setBlurPower(blurPower: Float) { + currentPipelineOptions.blurPower = blurPower + cameraPipeline?.setBlurPower(blurPower) + } + + fun enableBeautification(enableBeautification: Boolean) { + currentPipelineOptions.isBeautificationEnabled = enableBeautification + cameraPipeline?.enableBeautification(enableBeautification) + } + + fun isBeautificationEnabled(): Boolean { + return cameraPipeline?.isBeautificationEnabled()!! + } + + fun setBeautificationPower(power: Double) { + val intValue = (power * 100).toInt() + currentPipelineOptions.beautificationPower = intValue + cameraPipeline?.setBeautificationPower(intValue) + } + + fun getZoomLevel(): Double { + return (cameraPipeline?.getZoomLevel()!! / 100).toDouble() + } + + fun setZoomLevel(zoomLevel: Double) { + val intValue = (zoomLevel * 100).toInt() + currentPipelineOptions.zoomLevel = intValue + cameraPipeline?.setZoomLevel(intValue) + } + + fun enableSharpening(enableSharpening: Boolean) { + currentPipelineOptions.isSharpeningEnabled = enableSharpening + cameraPipeline?.enableSharpening(enableSharpening) + } + + fun getSharpeningStrength(): Double { + return cameraPipeline?.getSharpeningStrength()!!.toDouble() + } + + fun setSharpeningStrength(strength: Double) { + currentPipelineOptions.sharpeningStrength = strength.toFloat() + cameraPipeline?.setSharpeningStrength(strength.toFloat()) + } + + fun setColorCorrectionMode(mode: String) { + val value: String = mode.split('.')[1] + val colorCorrectionMode = when (value) { + "noFilterMode" -> ColorCorrectionMode.NO_FILTER_MODE + "colorCorrectionMode" -> ColorCorrectionMode.COLOR_CORRECTION_MODE + "colorGradingMode" -> ColorCorrectionMode.COLOR_GRADING_MODE + "presetMode" -> ColorCorrectionMode.PRESET_MODE + "lowLightMode" -> ColorCorrectionMode.LOW_LIGHT_MODE + else -> { + Log.w( + this.javaClass.simpleName, + "Incorrect color correction constant value. NO_FILTER_MODE set." + ) + ColorCorrectionMode.NO_FILTER_MODE + } + } + currentPipelineOptions.colorCorrectionMode = colorCorrectionMode + cameraPipeline?.setColorCorrectionMode(colorCorrectionMode) + } + + fun setColorFilterStrength(strength: Double) { + currentPipelineOptions.colorFilterStrength = strength.toFloat() + cameraPipeline?.setColorFilterStrength(strength.toFloat()) + } + + fun setColorGradingReference(bitmap: Bitmap) { + currentPipelineOptions.colorGradingReference = bitmap + cameraPipeline?.setColorGradingReferenceImage(bitmap) + } + + fun setBackgroundBitmap(bitmap: Bitmap) { + currentPipelineOptions.backgroundBitmap = bitmap + cameraPipeline?.setBackground(bitmap) + } + + private fun setPipelineOptionsFromCache(cache: EffectsSdkOptionsCache) { + cameraPipeline?.let { pipeline -> + pipeline.setMode(cache.pipelineMode) + pipeline.setBlurPower(cache.blurPower) + pipeline.setColorCorrectionMode(cache.colorCorrectionMode) + pipeline.enableSharpening(cache.isSharpeningEnabled) + pipeline.enableBeautification(cache.isBeautificationEnabled) + pipeline.setBeautificationPower(cache.beautificationPower) + pipeline.setColorFilterStrength(cache.colorFilterStrength) + pipeline.setSharpeningStrength(cache.sharpeningStrength) + pipeline.setZoomLevel(cache.zoomLevel) + cache.backgroundBitmap?.let { img -> pipeline.setBackground(img) } + cache.colorGradingReference?.let { img -> pipeline.setColorGradingReferenceImage(img) } + } + } + + private data class EffectsSdkOptionsCache( + var pipelineMode: PipelineMode = PipelineMode.NO_EFFECT, + var blurPower: Float = 0f, + var colorCorrectionMode: ColorCorrectionMode = ColorCorrectionMode.NO_FILTER_MODE, + var isSharpeningEnabled: Boolean = false, + var isBeautificationEnabled: Boolean = false, + var beautificationPower: Int = 0, + var colorFilterStrength: Float = 0f, + var sharpeningStrength: Float = 0f, + var zoomLevel: Int = 0, + var backgroundBitmap: Bitmap? = null, + var colorGradingReference: Bitmap? = null + ) + +} \ No newline at end of file diff --git a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java index 7fd9f1b65..de5f664a3 100644 --- a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java +++ b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java @@ -22,6 +22,7 @@ import com.oney.WebRTCModule.videoEffects.ProcessorProvider; import com.oney.WebRTCModule.videoEffects.VideoEffectProcessor; import com.oney.WebRTCModule.videoEffects.VideoFrameProcessor; +import com.effectssdk.tsvb.EffectsSDKStatus; import org.webrtc.*; @@ -31,6 +32,7 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** @@ -44,6 +46,8 @@ class GetUserMediaImpl { private static final String TAG = WebRTCModule.TAG; private static final int PERMISSION_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE); + + private static final Map effectsSDKCapturerMap = new ConcurrentHashMap<>(); private CameraEnumerator cameraEnumerator; private final ReactApplicationContext reactContext; @@ -141,6 +145,74 @@ private void checkMandatoryConstraints(MediaConstraints peerConstraints) { peerConstraints.mandatory.addAll(valid); } + private boolean getEffectsSDKConstraint(ReadableMap videoConstraints) { + try { + if (videoConstraints != null && videoConstraints.hasKey("effectsSdkRequired")) { + return videoConstraints.getBoolean("effectsSdkRequired"); + } + } catch (Exception e) { + Log.w(TAG, "Error checking EffectsSDK constraint", e); + } + return false; + } + + private VideoCapturer createEffectsSDKVideoCapturer(String cameraName, CameraVideoCapturer.CameraEventsHandler eventsHandler) { + try { + return new EffectsSDKCameraCapturer(cameraName, eventsHandler, getCameraEnumerator()); + } catch (Exception e) { + Log.e(TAG, "Failed to create EffectsSDKVideoCapturer", e); + return null; + } + } + + public EffectsSDKStatus initializeEffectsSdk(String trackId, String customerId, String url) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + return capturer.initializeEffectsSdk(customerId, url); + } + return EffectsSDKStatus.INACTIVE; + } + + private EffectsSDKCameraCapturer getEffectsSdkVideoCapturer(String trackId) { + return effectsSDKCapturerMap.get(trackId); + } + + private String getCameraNameFromConstraints(ReadableMap videoConstraints) { + String[] deviceNames = getCameraEnumerator().getDeviceNames(); + + // Try deviceId first + if (videoConstraints.hasKey("deviceId")) { + String requestedDeviceId = videoConstraints.getString("deviceId"); + for (String name : deviceNames) { + if (name.equals(requestedDeviceId)) { + return name; + } + } + } + + // Try facingMode + if (videoConstraints.hasKey("facingMode")) { + String requestedFacingMode = videoConstraints.getString("facingMode"); + boolean wantFrontCamera = "user".equals(requestedFacingMode); + for (String name : deviceNames) { + if (getCameraEnumerator().isFrontFacing(name) == wantFrontCamera) { + return name; + } + } + } + + // Fallback to front camera + for (String name : deviceNames) { + if (getCameraEnumerator().isFrontFacing(name)) { + return name; + } + } + + // Final fallback to first available camera + return deviceNames.length > 0 ? deviceNames[0] : null; + } + + private CameraEnumerator getCameraEnumerator() { if (cameraEnumerator == null) { if (Camera2Enumerator.isSupported(reactContext)) { @@ -214,10 +286,37 @@ void getUserMedia(final ReadableMap constraints, final Callback successCallback, Log.d(TAG, "getUserMedia(video): " + videoConstraintsMap); - CameraCaptureController cameraCaptureController = new CameraCaptureController( + boolean effectsSdkRequired = getEffectsSDKConstraint(videoConstraintsMap); + + CameraCaptureController videoCaptureController = new CameraCaptureController( reactContext.getCurrentActivity(), getCameraEnumerator(), videoConstraintsMap); - - videoTrack = createVideoTrack(cameraCaptureController); + + if (effectsSdkRequired) { + String cameraName = getCameraNameFromConstraints(videoConstraintsMap); + if (cameraName != null) { + CameraVideoCapturer.CameraEventsHandler cameraEventsHandler = new CameraVideoCapturer.CameraEventsHandler() { + @Override + public void onCameraError(String errorDescription) {} + @Override + public void onCameraDisconnected() {} + @Override + public void onCameraFreezed(String errorDescription) {} + @Override + public void onCameraOpening(String cameraName) {} + @Override + public void onFirstFrameAvailable() {} + @Override + public void onCameraClosed() {} + }; + + VideoCapturer effectsSDKCapturer = createEffectsSDKVideoCapturer(cameraName, cameraEventsHandler); + if (effectsSDKCapturer != null) { + videoCaptureController.videoCapturer = effectsSDKCapturer; + } + } + } + + videoTrack = createVideoTrack(videoCaptureController); } if (audioTrack == null && videoTrack == null) { @@ -252,6 +351,8 @@ void mediaStreamTrackSetEnabled(String trackId, final boolean enabled) { void disposeTrack(String id) { TrackPrivate track = tracks.remove(id); if (track != null) { + effectsSDKCapturerMap.remove(id); + track.dispose(); } } @@ -397,6 +498,7 @@ VideoTrack createVideoTrack(AbstractVideoCaptureController videoCaptureControlle if (videoCapturer == null) { return null; } + PeerConnectionFactory pcFactory = webRTCModule.mFactory; EglBase.Context eglContext = EglUtils.getRootEglBaseContext(); @@ -408,6 +510,10 @@ VideoTrack createVideoTrack(AbstractVideoCaptureController videoCaptureControlle } String id = UUID.randomUUID().toString(); + + if (videoCapturer instanceof EffectsSDKCameraCapturer) { + effectsSDKCapturerMap.put(id, (EffectsSDKCameraCapturer) videoCapturer); + } TrackCapturerEventsEmitter eventsEmitter = new TrackCapturerEventsEmitter(webRTCModule, id); videoCaptureController.setCapturerEventsListener(eventsEmitter); @@ -463,6 +569,89 @@ void setVideoEffects(String trackId, ReadableArray names) { } } + void switchCamera(String trackId) { + EffectsSDKCameraCapturer effectsSDKCapturer = effectsSDKCapturerMap.get(trackId); + if (effectsSDKCapturer != null) { + switchEffectsSDKCamera(effectsSDKCapturer); + return; + } + + TrackPrivate trackPrivate = tracks.get(trackId); + if (trackPrivate == null || !(trackPrivate.videoCaptureController instanceof CameraCaptureController)) { + return; + } + + CameraCaptureController controller = (CameraCaptureController) trackPrivate.videoCaptureController; + VideoCapturer videoCapturer = controller.videoCapturer; + + if (!(videoCapturer instanceof CameraVideoCapturer)) { + return; + } + + switchStandardCamera((CameraVideoCapturer) videoCapturer, controller.getDeviceId()); + } + + + private void switchEffectsSDKCamera(EffectsSDKCameraCapturer capturer) { + String[] deviceNames = getCameraEnumerator().getDeviceNames(); + String currentDevice = capturer.getCurrentDevice(); + Log.d(TAG, "Current EffectsSDK camera device: " + currentDevice); + + // Find opposite camera + for (String deviceName : deviceNames) { + if (!deviceName.equals(currentDevice)) { + try { + Log.d(TAG, "Switching EffectsSDK camera from " + currentDevice + " to " + deviceName); + capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { + @Override + public void onCameraSwitchDone(boolean isFrontCamera) { + Log.d(TAG, "EffectsSDK camera switch successful, now using: " + (isFrontCamera ? "front" : "back")); + } + + @Override + public void onCameraSwitchError(String error) { + Log.e(TAG, "EffectsSDK camera switch failed: " + error); + } + }, deviceName); + return; + } catch (Exception e) { + Log.w(TAG, "Failed to switch to device " + deviceName + ": " + e.getMessage()); + } + } + } + Log.w(TAG, "No opposite camera found for current device: " + currentDevice); + } + + + private void switchStandardCamera(CameraVideoCapturer capturer, String currentDeviceId) { + String[] deviceNames = getCameraEnumerator().getDeviceNames(); + boolean currentIsFrontFacing = false; + + try { + currentIsFrontFacing = getCameraEnumerator().isFrontFacing(currentDeviceId); + } catch (Exception e) { + // Use default value + } + + for (String deviceName : deviceNames) { + try { + boolean isFrontFacing = getCameraEnumerator().isFrontFacing(deviceName); + if (isFrontFacing != currentIsFrontFacing) { + capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { + @Override + public void onCameraSwitchDone(boolean isFrontCamera) {} + + @Override + public void onCameraSwitchError(String error) {} + }, deviceName); + return; + } + } catch (Exception e) { + // Continue to next camera + } + } + } + /** * Application/library-specific private members of local * {@code MediaStreamTrack}s created by {@code GetUserMediaImpl}. @@ -533,6 +722,101 @@ public void dispose() { } } + // EffectsSDK methods + public void setEffectsSdkPipelineMode(String trackId, String pipelineMode) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setPipelineMode(pipelineMode); + } + } + + public void setEffectsSdkBlurPower(String trackId, double blurPower) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setBlurPower((float) blurPower); + } + } + + public void enableEffectsSdkVideoStream(String trackId, boolean enabled) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.enableVideo(enabled); + } + } + + public void enableEffectsSdkBeautification(String trackId, boolean enableBeautification) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.enableBeautification(enableBeautification); + } + } + + public boolean isEffectsSdkBeautificationEnabled(String trackId) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + return capturer.isBeautificationEnabled(); + } + return false; + } + + public void setEffectsSdkBeautificationPower(String trackId, double beautificationPower) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setBeautificationPower(beautificationPower); + } + } + + public void setEffectsSdkZoomLevel(String trackId, double zoomLevel) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setZoomLevel(zoomLevel); + } + } + + public double getEffectsSdkZoomLevel(String trackId) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + return capturer.getZoomLevel(); + } + return 0.0; + } + + public void enableEffectsSdkSharpening(String trackId, boolean enableSharpening) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.enableSharpening(enableSharpening); + } + } + + public void setEffectsSdkSharpeningStrength(String trackId, double strength) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setSharpeningStrength(strength); + } + } + + public double getEffectsSdkSharpeningStrength(String trackId) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + return capturer.getSharpeningStrength(); + } + return 0.0; + } + + public void setEffectsSdkColorFilterStrength(String trackId, double strength) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setColorFilterStrength(strength); + } + } + + public void setEffectsSdkColorCorrectionMode(String trackId, String colorCorrectionMode) { + EffectsSDKCameraCapturer capturer = getEffectsSdkVideoCapturer(trackId); + if (capturer != null) { + capturer.setColorCorrectionMode(colorCorrectionMode); + } + } + public interface BiConsumer { void accept(T t, U u); } diff --git a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java index e9337be1c..dbdef9a43 100644 --- a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java +++ b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java @@ -19,10 +19,13 @@ import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoDecoderFactory; import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory; +import com.effectssdk.tsvb.EffectsSDKStatus; import org.webrtc.AddIceObserver; import org.webrtc.AudioProcessingFactory; @@ -953,6 +956,136 @@ public void mediaStreamTrackSetVolume(int pcId, String id, double volume) { }); } + @ReactMethod + public void switchCamera(String trackId, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + try { + getUserMediaImpl.switchCamera(trackId); + promise.resolve(true); + } catch (Exception e) { + promise.reject("CAMERA_SWITCH_ERROR", e.getMessage(), e); + } + }); + } + + @ReactMethod + public void initializeEffectsSdk(String trackId, String customerId, String url, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + try { + EffectsSDKStatus status = getUserMediaImpl.initializeEffectsSdk(trackId, customerId, url); + promise.resolve(status.toString()); + } catch (Exception e) { + promise.reject("EFFECTS_SDK_INIT_ERROR", e.getMessage(), e); + } + }); + } + + @ReactMethod + public void setEffectsSdkPipelineMode(String trackId, String pipelineMode) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkPipelineMode(trackId, pipelineMode); + }); + } + + @ReactMethod + public void setEffectsSdkBlurPower(String trackId, double blurPower) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkBlurPower(trackId, blurPower); + }); + } + + @ReactMethod + public void enableEffectsSdkVideoStream(String trackId, boolean enabled) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.enableEffectsSdkVideoStream(trackId, enabled); + }); + } + + @ReactMethod + public void enableEffectsSdkBeautification(String trackId, boolean enableBeautification) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.enableEffectsSdkBeautification(trackId, enableBeautification); + }); + } + + @ReactMethod + public void isEffectsSdkBeautificationEnabled(String trackId, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + try { + boolean enabled = getUserMediaImpl.isEffectsSdkBeautificationEnabled(trackId); + promise.resolve(enabled); + } catch (Exception e) { + promise.reject("EFFECTS_SDK_ERROR", e.getMessage(), e); + } + }); + } + + @ReactMethod + public void setEffectsSdkBeautificationPower(String trackId, double beautificationPower) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkBeautificationPower(trackId, beautificationPower); + }); + } + + @ReactMethod + public void setEffectsSdkZoomLevel(String trackId, double zoomLevel) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkZoomLevel(trackId, zoomLevel); + }); + } + + @ReactMethod + public void getEffectsSdkZoomLevel(String trackId, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + try { + double zoomLevel = getUserMediaImpl.getEffectsSdkZoomLevel(trackId); + promise.resolve(zoomLevel); + } catch (Exception e) { + promise.reject("EFFECTS_SDK_ERROR", e.getMessage(), e); + } + }); + } + + @ReactMethod + public void enableEffectsSdkSharpening(String trackId, boolean enableSharpening) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.enableEffectsSdkSharpening(trackId, enableSharpening); + }); + } + + @ReactMethod + public void setEffectsSdkSharpeningStrength(String trackId, double strength) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkSharpeningStrength(trackId, strength); + }); + } + + @ReactMethod + public void getEffectsSdkSharpeningStrength(String trackId, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + try { + double strength = getUserMediaImpl.getEffectsSdkSharpeningStrength(trackId); + promise.resolve(strength); + } catch (Exception e) { + promise.reject("EFFECTS_SDK_ERROR", e.getMessage(), e); + } + }); + } + + @ReactMethod + public void setEffectsSdkColorFilterStrength(String trackId, double strength) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkColorFilterStrength(trackId, strength); + }); + } + + @ReactMethod + public void setEffectsSdkColorCorrectionMode(String trackId, String colorCorrectionMode) { + ThreadUtils.runOnExecutor(() -> { + getUserMediaImpl.setEffectsSdkColorCorrectionMode(trackId, colorCorrectionMode); + }); + } + /** * This serializes the transceivers current direction and mid and returns them * for update when an sdp negotiation/renegotiation happens