diff --git a/android/build.gradle b/android/build.gradle index 1dfe61d..724ac50 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ group 'com.u0x48lab.body_detection' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.20' repositories { google() mavenCentral() diff --git a/android/src/main/kotlin/com/u0x48lab/body_detection/BodyDetectionPlugin.kt b/android/src/main/kotlin/com/u0x48lab/body_detection/BodyDetectionPlugin.kt index 68231f0..d27e6da 100644 --- a/android/src/main/kotlin/com/u0x48lab/body_detection/BodyDetectionPlugin.kt +++ b/android/src/main/kotlin/com/u0x48lab/body_detection/BodyDetectionPlugin.kt @@ -5,6 +5,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import androidx.annotation.NonNull +import androidx.camera.core.CameraSelector import androidx.camera.core.ImageProxy import com.google.android.gms.tasks.OnFailureListener import com.google.android.gms.tasks.OnSuccessListener @@ -25,6 +26,7 @@ class BodyDetectionPlugin: FlutterPlugin, MethodChannel.MethodCallHandler, Event private var poseDetectionEnabled = false private var bodyMaskDetectionEnabled = false private val poseDetector = MLKitPoseDetector(true) + private var lensFacing = CameraSelector.LENS_FACING_FRONT private val selfieSegmenter = MLKitSelfieSegmenter() override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { @@ -78,7 +80,7 @@ class BodyDetectionPlugin: FlutterPlugin, MethodChannel.MethodCallHandler, Event result.success(null) } "startCameraStream" -> { - val session = CameraSession(context) + val session = CameraSession(context, lensFacing) session.start { imageProxy, rotationDegrees -> handleCameraFrame(imageProxy, rotationDegrees) } @@ -90,13 +92,19 @@ class BodyDetectionPlugin: FlutterPlugin, MethodChannel.MethodCallHandler, Event cameraSession = null result.success(true) } + "switchCamera" -> { + val isFront = call.argument("lensFacing") as String? + lensFacing = + if (isFront?.equals("FRONT") == true) CameraSelector.LENS_FACING_BACK else CameraSelector.LENS_FACING_BACK + result.success(true) + } else -> { result.notImplemented() } } } - @SuppressLint("UnsafeExperimentalUsageError") + @SuppressLint("UnsafeExperimentalUsageError", "UnsafeOptInUsageError") private fun handleCameraFrame(imageProxy: ImageProxy, rotationDegrees: Int) { val bitmap = BitmapUtils.getBitmap(imageProxy, true) val width = bitmap?.width ?: 0 diff --git a/android/src/main/kotlin/com/u0x48lab/body_detection/CameraSession.kt b/android/src/main/kotlin/com/u0x48lab/body_detection/CameraSession.kt index 659531c..08bad54 100644 --- a/android/src/main/kotlin/com/u0x48lab/body_detection/CameraSession.kt +++ b/android/src/main/kotlin/com/u0x48lab/body_detection/CameraSession.kt @@ -13,18 +13,17 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import java.util.concurrent.ExecutionException -class CameraSession(private var context: Context) { +class CameraSession(private var context: Context, private var lensFacing: Int) { private var processOutput: ((ImageProxy, Int) -> Unit)? = null private var cameraProvider: ProcessCameraProvider? = null private var analysisUseCase: ImageAnalysis? = null - private var lensFacing = CameraSelector.LENS_FACING_FRONT - private var cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA + private var cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build() private val lifecycle = CustomLifecycle() init { val cameraProviderFuture = ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener( - Runnable { + { try { cameraProvider = cameraProviderFuture.get() @@ -94,6 +93,7 @@ class CameraSession(private var context: Context) { if (analysisUseCase != null) { cameraProvider!!.unbind(analysisUseCase) + analysisUseCase = null } } @@ -108,13 +108,12 @@ class CameraSession(private var context: Context) { val useCase = builder.build() useCase.setAnalyzer( - ContextCompat.getMainExecutor(context), - ImageAnalysis.Analyzer { imageProxy: ImageProxy -> - val isImageFlipped = lensFacing == CameraSelector.LENS_FACING_FRONT - val rotationDegrees = imageProxy.imageInfo.rotationDegrees - processOutput?.let { it(imageProxy, rotationDegrees) } - } - ) + ContextCompat.getMainExecutor(context) + ) { imageProxy: ImageProxy -> + val isImageFlipped = lensFacing == CameraSelector.LENS_FACING_FRONT + val rotationDegrees = imageProxy.imageInfo.rotationDegrees + processOutput?.let { it(imageProxy, rotationDegrees) } + } cameraProvider!!.bindToLifecycle(lifecycle, cameraSelector, useCase) diff --git a/example/android/build.gradle b/example/android/build.gradle index ed45c65..97e6f5e 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.20' repositories { google() mavenCentral() diff --git a/example/lib/main.dart b/example/lib/main.dart index 7b92cb4..0dd924c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,6 +30,7 @@ class _MyAppState extends State { bool _isDetectingPose = false; bool _isDetectingBodyMask = false; + LensFacing _lens = LensFacing.back; Image? _selectedImage; @@ -164,6 +165,18 @@ class _MyAppState extends State { }); } + Future _toggleLens() async { + await _stopCameraStream(); + if (_lens == LensFacing.front) { + await BodyDetection.switchCamera(LensFacing.back); + _lens = LensFacing.back; + } else { + await BodyDetection.switchCamera(LensFacing.front); + _lens = LensFacing.front; + } + await _startCameraStream(); + } + Future _toggleDetectBodyMask() async { if (_isDetectingBodyMask) { await BodyDetection.disableBodyMaskDetection(); @@ -271,6 +284,12 @@ class _MyAppState extends State { ? const Text('Turn off pose detection') : const Text('Turn on pose detection'), ), + OutlinedButton( + onPressed: _toggleLens, + child: _lens == LensFacing.front + ? const Text('Switch to back camera') + : const Text('Switch to front camera'), + ), OutlinedButton( onPressed: _toggleDetectBodyMask, child: _isDetectingBodyMask diff --git a/ios/Classes/CameraSession.swift b/ios/Classes/CameraSession.swift index 8c6f696..5d13bff 100644 --- a/ios/Classes/CameraSession.swift +++ b/ios/Classes/CameraSession.swift @@ -28,9 +28,10 @@ public class CameraSession: NSObject { private var isUsingFrontCamera = true private var processOutput: ((CMSampleBuffer, UIImage.Orientation) -> Void)? - public override init() { + public init(isUsingFrontCamera: Bool) { super.init() - + + self.isUsingFrontCamera = isUsingFrontCamera self.setUpCaptureSessionOutput() self.setUpCaptureSessionInput() } diff --git a/ios/Classes/SwiftBodyDetectionPlugin.swift b/ios/Classes/SwiftBodyDetectionPlugin.swift index 25fd33b..eaaf3aa 100644 --- a/ios/Classes/SwiftBodyDetectionPlugin.swift +++ b/ios/Classes/SwiftBodyDetectionPlugin.swift @@ -7,6 +7,7 @@ public class SwiftBodyDetectionPlugin: NSObject, FlutterPlugin { private var cameraSession: CameraSession? private var poseDetectionEnabled = false private var bodyMaskDetectionEnabled = false + private var isUsingFrontCamera = true private let poseDetector = MLKitPoseDetector(stream: true) private let selfieSegmenter = MLKitSelfieSegmenter() @@ -126,7 +127,7 @@ public class SwiftBodyDetectionPlugin: NSObject, FlutterPlugin { print("Camera session already active! Call stopCameraStream first and try again.") return } - let session = CameraSession() + let session = CameraSession(isUsingFrontCamera: isUsingFrontCamera) session.start(closure: self.handleCameraFrame) self.cameraSession = session result(true) @@ -142,6 +143,20 @@ public class SwiftBodyDetectionPlugin: NSObject, FlutterPlugin { self.cameraSession = nil result(true) return + case "switchCamera": + do { + guard let arguments = call.arguments as? [String : Any] else { + throw BodyDetectionPluginError.badArgument("Expected dictionary type.") + } + guard let lensFacing = arguments["lensFacing"] as? NSString else { + throw BodyDetectionPluginError.badArgument("lensFacing") + } + isUsingFrontCamera = lensFacing == "FRONT" + result(true) + } catch { + result(error.toFlutterError()); + } + return // Method not implemented. default: diff --git a/lib/body_detection.dart b/lib/body_detection.dart index d666d0d..cc68f4b 100644 --- a/lib/body_detection.dart +++ b/lib/body_detection.dart @@ -10,6 +10,11 @@ import 'models/body_mask.dart'; import 'png_image.dart'; import 'types.dart'; +enum LensFacing { + front, + back, +} + class BodyDetection { static const MethodChannel _channel = MethodChannel('com.0x48lab/body_detection'); @@ -99,6 +104,16 @@ class BodyDetection { } } + static Future switchCamera(LensFacing facing) async { + try { + await _channel.invokeMethod('switchCamera', { + 'lensFacing': facing == LensFacing.front ? "FRONT" : "BACK", + }); + } on PlatformException catch (e) { + throw BodyDetectionException(e.code, e.message); + } + } + static Future enablePoseDetection() async { try { await _channel.invokeMethod('enablePoseDetection');