Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -1066,8 +1073,6 @@ void stopRecording(Integer id, String albumName, Runnable onFinished) {
}
}



public void reStartCamera(IsCameraEnabled getCameraId) {
for (Map.Entry<String, VideoCapturerInfoEx> item : mVideoCapturers.entrySet()) {
if (!item.getValue().isScreenCapture && getCameraId.isEnabled(item.getKey())) {
Expand All @@ -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) {
Expand Down
69 changes: 51 additions & 18 deletions common/darwin/Classes/CameraUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
}];
}


Expand Down
25 changes: 20 additions & 5 deletions common/darwin/Classes/FlutterRTCMediaStream.m
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ - (void)getUserVideo:(NSDictionary*)constraints
AVCaptureDevice* videoDevice;
NSString* videoDeviceId = nil;
NSString* facingMode = nil;
AVCaptureDevicePosition position = AVCaptureDevicePositionUnspecified;
NSArray<AVCaptureDevice*>* captureDevices = [self captureDevices];

if ([videoConstraints isKindOfClass:[NSDictionary class]]) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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];
Expand Down
2 changes: 2 additions & 0 deletions common/darwin/Classes/FlutterWebRTCPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ typedef void (^CapturerStopHandler)(CompletionHandler _Nonnull handler);
NSMutableDictionary<NSNumber*, FlutterRTCMediaRecorder*>* _Nonnull recorders;
@property(nonatomic, strong)
NSMutableDictionary<NSString*, CapturerStopHandler>* _Nullable videoCapturerStopHandlers;
@property(nonatomic, strong)
NSMutableDictionary<NSString*, NSMutableDictionary*>* _Nullable videoCaptureState;

@property(nonatomic, strong)
NSMutableDictionary<NSString*, RTCFrameCryptor*>* _Nullable frameCryptors;
Expand Down
3 changes: 3 additions & 0 deletions common/darwin/Classes/FlutterWebRTCPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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];
}
}
}
Expand Down