Skip to content

Allow switching between back and front camera #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For if, there should be CameraSelector.LENS_FACING_FRONT. Since both condition have same statement you cannot toggle back to front camera after switching it to the back camera for the first time. Please update this in your commit. I found your commit very helpful for my project. This was the only issue I found. ThankYou:) Happy Coding

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -94,6 +93,7 @@ class CameraSession(private var context: Context) {

if (analysisUseCase != null) {
cameraProvider!!.unbind(analysisUseCase)
analysisUseCase = null
}
}

Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.20'
repositories {
google()
mavenCentral()
Expand Down
19 changes: 19 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class _MyAppState extends State<MyApp> {

bool _isDetectingPose = false;
bool _isDetectingBodyMask = false;
LensFacing _lens = LensFacing.back;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LensFacing _lens = LensFacing.front;
Updated


Image? _selectedImage;

Expand Down Expand Up @@ -164,6 +165,18 @@ class _MyAppState extends State<MyApp> {
});
}

Future<void> _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<void> _toggleDetectBodyMask() async {
if (_isDetectingBodyMask) {
await BodyDetection.disableBodyMaskDetection();
Expand Down Expand Up @@ -271,6 +284,12 @@ class _MyAppState extends State<MyApp> {
? 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
Expand Down
5 changes: 3 additions & 2 deletions ios/Classes/CameraSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
17 changes: 16 additions & 1 deletion ios/Classes/SwiftBodyDetectionPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
15 changes: 15 additions & 0 deletions lib/body_detection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -99,6 +104,16 @@ class BodyDetection {
}
}

static Future<void> switchCamera(LensFacing facing) async {
try {
await _channel.invokeMethod<void>('switchCamera', <String, dynamic>{
'lensFacing': facing == LensFacing.front ? "FRONT" : "BACK",
});
} on PlatformException catch (e) {
throw BodyDetectionException(e.code, e.message);
}
}

static Future<void> enablePoseDetection() async {
try {
await _channel.invokeMethod<void>('enablePoseDetection');
Expand Down