diff --git a/android/src/main/java/io/getstream/webrtc/flutter/GetUserMediaImpl.java b/android/src/main/java/io/getstream/webrtc/flutter/GetUserMediaImpl.java index ed6cb0d697..9d7167f786 100755 --- a/android/src/main/java/io/getstream/webrtc/flutter/GetUserMediaImpl.java +++ b/android/src/main/java/io/getstream/webrtc/flutter/GetUserMediaImpl.java @@ -792,12 +792,12 @@ private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream medi deviceId = result.first; VideoCapturer videoCapturer = result.second; - if (facingMode == null && cameraEnumerator.isFrontFacing(deviceId)) { + if (cameraEnumerator.isFrontFacing(deviceId)) { facingMode = "user"; - } else if (facingMode == null && cameraEnumerator.isBackFacing(deviceId)) { + } else if (cameraEnumerator.isBackFacing(deviceId)) { facingMode = "environment"; } - // else, leave facingMode as it was + // else, leave facingMode as it was (for non-standard cameras) PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory(); VideoSource videoSource = pcFactory.createVideoSource(false); @@ -841,6 +841,7 @@ private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream medi info.fps = targetFps; info.capturer = videoCapturer; info.cameraName = deviceId; + info.isFrontFacing = cameraEnumerator.isFrontFacing(deviceId); // Find actual capture format. Size actualSize = null; @@ -992,12 +993,15 @@ private void requestPermissions( } void switchCamera(String id, Result result) { - VideoCapturer videoCapturer = mVideoCapturers.get(id).capturer; - if (videoCapturer == null) { + VideoCapturerInfoEx info = mVideoCapturers.get(id); + if (info == null || info.capturer == null) { resultError("switchCamera", "Video capturer not found for id: " + id, result); return; } + VideoCapturer videoCapturer = info.capturer; + boolean currentIsFrontFacing = info.isFrontFacing; + CameraEnumerator cameraEnumerator; if (Camera2Enumerator.isSupported(applicationContext)) { @@ -1010,21 +1014,24 @@ void switchCamera(String id, Result result) { // if sourceId given, use specified sourceId first final String[] deviceNames = cameraEnumerator.getDeviceNames(); for (String name : deviceNames) { - if (cameraEnumerator.isFrontFacing(name) == !isFacing) { + if (cameraEnumerator.isFrontFacing(name) == !currentIsFrontFacing) { + final String targetCameraName = name; + final boolean newIsFrontFacing = !currentIsFrontFacing; CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) videoCapturer; cameraVideoCapturer.switchCamera( new CameraVideoCapturer.CameraSwitchHandler() { @Override public void onCameraSwitchDone(boolean b) { - isFacing = !isFacing; - result.success(b); + info.isFrontFacing = newIsFrontFacing; + info.cameraName = targetCameraName; + result.success(info.isFrontFacing); } @Override public void onCameraSwitchError(String s) { resultError("switchCamera", "Switching camera failed: " + id, result); } - }, name); + }, targetCameraName); return; } } @@ -1066,8 +1073,6 @@ void stopRecording(Integer id, String albumName, Runnable onFinished) { } } - - public void reStartCamera(IsCameraEnabled getCameraId) { for (Map.Entry item : mVideoCapturers.entrySet()) { if (!item.getValue().isScreenCapture && getCameraId.isEnabled(item.getKey())) { @@ -1084,8 +1089,9 @@ public interface IsCameraEnabled { boolean isEnabled(String id); } - public static class VideoCapturerInfoEx extends VideoCapturerInfo { + public static class VideoCapturerInfoEx extends VideoCapturerInfo { public CameraEventsHandler cameraEventsHandler; + public boolean isFrontFacing; } public VideoCapturerInfoEx getCapturerInfo(String trackId) { diff --git a/common/darwin/Classes/CameraUtils.m b/common/darwin/Classes/CameraUtils.m index e05d32e055..5248d8aa82 100644 --- a/common/darwin/Classes/CameraUtils.m +++ b/common/darwin/Classes/CameraUtils.m @@ -272,33 +272,66 @@ - (void)mediaStreamTrackSetExposurePoint:(nonnull RTCMediaStreamTrack*)track } - (void)mediaStreamTrackSwitchCamera:(RTCMediaStreamTrack*)track result:(FlutterResult)result { + NSString* trackId = track.trackId; + NSMutableDictionary* captureState = self.videoCaptureState[trackId]; + if (!self.videoCapturer) { - NSLog(@"Video capturer is null. Can't switch camera"); - return; + NSLog(@"Video capturer is null. Can't switch camera"); + result([FlutterError errorWithCode:@"Error while switching camera" + message:@"Video capturer not found" + details:nil]); + return; } + #if TARGET_OS_IPHONE [self.videoCapturer stopCapture]; #endif - self._usingFrontCamera = !self._usingFrontCamera; + + BOOL usingFrontCamera; + NSInteger targetWidth; + NSInteger targetHeight; + NSInteger targetFps; + + if (captureState) { + // Use per-track state + usingFrontCamera = [captureState[@"usingFrontCamera"] boolValue]; + targetWidth = [captureState[@"targetWidth"] integerValue]; + targetHeight = [captureState[@"targetHeight"] integerValue]; + targetFps = [captureState[@"targetFps"] integerValue]; + } else { + // Use global state for backward compatibility + usingFrontCamera = self._usingFrontCamera; + targetWidth = self._lastTargetWidth; + targetHeight = self._lastTargetHeight; + targetFps = self._lastTargetFps; + } + + usingFrontCamera = !usingFrontCamera; AVCaptureDevicePosition position = - self._usingFrontCamera ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack; + usingFrontCamera ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack; AVCaptureDevice* videoDevice = [self findDeviceForPosition:position]; AVCaptureDeviceFormat* selectedFormat = [self selectFormatForDevice:videoDevice - targetWidth:self._lastTargetWidth - targetHeight:self._lastTargetHeight]; + targetWidth:targetWidth + targetHeight:targetHeight]; [self.videoCapturer startCaptureWithDevice:videoDevice - format:selectedFormat - fps:[self selectFpsForFormat:selectedFormat - targetFps:self._lastTargetFps] - completionHandler:^(NSError* error) { - if (error != nil) { - result([FlutterError errorWithCode:@"Error while switching camera" - message:@"Error while switching camera" - details:error]); - } else { - result([NSNumber numberWithBool:self._usingFrontCamera]); - } - }]; + format:selectedFormat + fps:[self selectFpsForFormat:selectedFormat + targetFps:targetFps] + completionHandler:^(NSError* error) { + if (error != nil) { + result([FlutterError errorWithCode:@"Error while switching camera" + message:@"Error while switching camera" + details:error]); + } else { + // Update per-track state + if (captureState) { + captureState[@"usingFrontCamera"] = @(usingFrontCamera); + } + // Update global state for backward compatibility + self._usingFrontCamera = usingFrontCamera; + result([NSNumber numberWithBool:usingFrontCamera]); + } + }]; } diff --git a/common/darwin/Classes/FlutterRTCMediaStream.m b/common/darwin/Classes/FlutterRTCMediaStream.m index 868cbb0e9a..6d28db627e 100644 --- a/common/darwin/Classes/FlutterRTCMediaStream.m +++ b/common/darwin/Classes/FlutterRTCMediaStream.m @@ -413,6 +413,7 @@ - (void)getUserVideo:(NSDictionary*)constraints AVCaptureDevice* videoDevice; NSString* videoDeviceId = nil; NSString* facingMode = nil; + AVCaptureDevicePosition position = AVCaptureDevicePositionUnspecified; NSArray* captureDevices = [self captureDevices]; if ([videoConstraints isKindOfClass:[NSDictionary class]]) { @@ -456,7 +457,6 @@ - (void)getUserVideo:(NSDictionary*)constraints // https://www.w3.org/TR/mediacapture-streams/#def-constraint-facingMode facingMode = videoConstraints[@"facingMode"]; if (facingMode && [facingMode isKindOfClass:[NSString class]]) { - AVCaptureDevicePosition position; if ([facingMode isEqualToString:@"environment"]) { self._usingFrontCamera = NO; position = AVCaptureDevicePositionBack; @@ -486,6 +486,10 @@ - (void)getUserVideo:(NSDictionary*)constraints videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; } + if (videoDevice && position == AVCaptureDevicePositionUnspecified) { + position = videoDevice.position; + } + int possibleWidth = [self getConstrainInt:videoConstraints forKey:@"width"]; if (possibleWidth != 0) { targetWidth = possibleWidth; @@ -551,10 +555,6 @@ - (void)getUserVideo:(NSDictionary*)constraints NSInteger selectedWidth = (NSInteger) selectedDimension.width; NSInteger selectedHeight = (NSInteger) selectedDimension.height; NSInteger selectedFps = [self selectFpsForFormat:selectedFormat targetFps:targetFps]; - - self._lastTargetFps = selectedFps; - self._lastTargetWidth = targetWidth; - self._lastTargetHeight = targetHeight; NSLog(@"target format %ldx%ld, targetFps: %ld, selected format: %ldx%ld, selected fps %ld", targetWidth, targetHeight, targetFps, selectedWidth, selectedHeight, selectedFps); @@ -583,6 +583,21 @@ - (void)getUserVideo:(NSDictionary*)constraints LocalVideoTrack *localVideoTrack = [[LocalVideoTrack alloc] initWithTrack:videoTrack videoProcessing:videoProcessingAdapter]; __weak RTCCameraVideoCapturer* capturer = self.videoCapturer; + + // Store camera state per track + NSMutableDictionary* captureState = [NSMutableDictionary new]; + captureState[@"usingFrontCamera"] = @(position == AVCaptureDevicePositionFront); + captureState[@"targetWidth"] = @(targetWidth); + captureState[@"targetHeight"] = @(targetHeight); + captureState[@"targetFps"] = @(selectedFps); + self.videoCaptureState[videoTrack.trackId] = captureState; + + // Update global state for backward compatibility + self._usingFrontCamera = (position == AVCaptureDevicePositionFront); + self._lastTargetFps = selectedFps; + self._lastTargetWidth = targetWidth; + self._lastTargetHeight = targetHeight; + self.videoCapturerStopHandlers[videoTrack.trackId] = ^(CompletionHandler handler) { NSLog(@"Stop video capturer, trackID %@", videoTrack.trackId); [capturer stopCaptureWithCompletionHandler:handler]; diff --git a/common/darwin/Classes/FlutterWebRTCPlugin.h b/common/darwin/Classes/FlutterWebRTCPlugin.h index aaae74f117..c1fc19457b 100644 --- a/common/darwin/Classes/FlutterWebRTCPlugin.h +++ b/common/darwin/Classes/FlutterWebRTCPlugin.h @@ -47,6 +47,8 @@ typedef void (^CapturerStopHandler)(CompletionHandler _Nonnull handler); NSMutableDictionary* _Nonnull recorders; @property(nonatomic, strong) NSMutableDictionary* _Nullable videoCapturerStopHandlers; +@property(nonatomic, strong) + NSMutableDictionary* _Nullable videoCaptureState; @property(nonatomic, strong) NSMutableDictionary* _Nullable frameCryptors; diff --git a/common/darwin/Classes/FlutterWebRTCPlugin.m b/common/darwin/Classes/FlutterWebRTCPlugin.m index f40c9b01c0..eca2f10a03 100644 --- a/common/darwin/Classes/FlutterWebRTCPlugin.m +++ b/common/darwin/Classes/FlutterWebRTCPlugin.m @@ -211,6 +211,7 @@ - (instancetype)initWithChannel:(FlutterMethodChannel*)channel self.dataCryptors = [NSMutableDictionary new]; self.keyProviders = [NSMutableDictionary new]; self.videoCapturerStopHandlers = [NSMutableDictionary new]; + self.videoCaptureState = [NSMutableDictionary new]; self.recorders = [NSMutableDictionary new]; #if TARGET_OS_IPHONE self.focusMode = @"locked"; @@ -696,6 +697,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { result(nil); }); [self.videoCapturerStopHandlers removeObjectForKey:videoTrack.trackId]; + [self.videoCaptureState removeObjectForKey:videoTrack.trackId]; } } for (RTCAudioTrack* track in stream.audioTracks) { @@ -793,6 +795,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSLog(@"video capturer stopped, trackID = %@", track.trackId); }); [self.videoCapturerStopHandlers removeObjectForKey:track.trackId]; + [self.videoCaptureState removeObjectForKey:track.trackId]; } } }