diff --git a/iOS/GBViewController.m b/iOS/GBViewController.m index df4a0a1..8030ae9 100644 --- a/iOS/GBViewController.m +++ b/iOS/GBViewController.m @@ -52,6 +52,10 @@ NSTimer *_disableCameraTimer; AVCaptureDevicePosition _cameraPosition; UIButton *_cameraPositionButton; + NSArray *_allCaptureDevices; + NSArray *_backCaptureDevices; + AVCaptureDevice *_selectedCaptureDevice; + UIButton *_changeCameraButton; __weak GCController *_lastController; @@ -241,30 +245,82 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) _motionManager = [[CMMotionManager alloc] init]; _cameraPosition = AVCaptureDevicePositionBack; + _selectedCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; + + // Back camera setup + NSArray *deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, + AVCaptureDeviceTypeBuiltInTelephotoCamera]; + if (@available(iOS 13.0, *)) { + // AVCaptureDeviceTypeBuiltInUltraWideCamera is only available in iOS 13+ + deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, + AVCaptureDeviceTypeBuiltInUltraWideCamera, + AVCaptureDeviceTypeBuiltInTelephotoCamera]; + } + + // Use a discovery session to gather the capture devices (all back cameras as well as the front camera) + AVCaptureDeviceDiscoverySession *cameraDiscoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:deviceTypes + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + _allCaptureDevices = cameraDiscoverySession.devices; + + // Filter only the back cameras into a list used for switching between them + NSMutableArray *filteredBackCameras = [NSMutableArray array]; + for (AVCaptureDevice *device in _allCaptureDevices) { + if ([device position] == AVCaptureDevicePositionBack) { + [filteredBackCameras addObject:device]; + } + } + _backCaptureDevices = filteredBackCameras; + _cameraPositionButton = [[UIButton alloc] initWithFrame:CGRectMake(8, 0, 32, 32)]; + _changeCameraButton = [[UIButton alloc] initWithFrame:CGRectMake(8, + 0, + 32, + 32)]; [self didRotateFromInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation]; if (@available(iOS 13.0, *)) { [_cameraPositionButton setImage:[UIImage systemImageNamed:@"camera.rotate" withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleLarge]] forState:UIControlStateNormal]; _cameraPositionButton.backgroundColor = [UIColor systemBackgroundColor]; + + [_changeCameraButton setImage:[UIImage systemImageNamed:@"camera" + withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleLarge]] + forState:UIControlStateNormal]; + _changeCameraButton.backgroundColor = [UIColor systemBackgroundColor]; } else { - UIImage *image = [[UIImage imageNamed:@"CameraRotateTemplate"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - [_cameraPositionButton setImage:image + UIImage *rotateImage = [[UIImage imageNamed:@"CameraRotateTemplate"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [_cameraPositionButton setImage:rotateImage forState:UIControlStateNormal]; _cameraPositionButton.backgroundColor = [UIColor whiteColor]; + + UIImage *selectCameraImage = [[UIImage imageNamed:@"CameraTemplate"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [_changeCameraButton setImage:selectCameraImage + forState:UIControlStateNormal]; + _changeCameraButton.backgroundColor = [UIColor whiteColor]; } _cameraPositionButton.layer.cornerRadius = 6; _cameraPositionButton.alpha = 0; [_cameraPositionButton addTarget:self action:@selector(rotateCamera) forControlEvents:UIControlEventTouchUpInside]; + _changeCameraButton.layer.cornerRadius = 6; + _changeCameraButton.alpha = 0; + [_changeCameraButton addTarget:self + action:@selector(changeCamera) + forControlEvents:UIControlEventTouchUpInside]; + [_backgroundView addSubview:_cameraPositionButton]; - + // Only show the select camera button if we have more than one back camera to swap between. + if ([_backCaptureDevices count] > 1) { + [_backgroundView addSubview:_changeCameraButton]; + } + _cameraQueue = dispatch_queue_create("SameBoy Camera Queue", NULL); [UNUserNotificationCenter currentNotificationCenter].delegate = self; @@ -686,6 +742,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { UIEdgeInsets insets = self.window.safeAreaInsets; _cameraPositionButton.frame = CGRectMake(insets.left + 8, _backgroundView.bounds.size.height - 8 - insets.bottom - 32, 32, 32); + _changeCameraButton.frame = CGRectMake(insets.right - 8, _backgroundView.bounds.size.height - 8 - insets.bottom - 32, 32, 32); } - (UIInterfaceOrientationMask)supportedInterfaceOrientations @@ -1123,12 +1180,20 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response - (AVCaptureDevice *)captureDevice { - NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; - for (AVCaptureDevice *device in devices) { + for (AVCaptureDevice *device in _allCaptureDevices) { if ([device position] == _cameraPosition) { - return device; + // There is only one front camera, return it + if (_cameraPosition == AVCaptureDevicePositionFront) { + return device; + } + + // There may be several back cameras, return the one with the matching type + if ([device deviceType] == [_selectedCaptureDevice deviceType]) { + return device; + } } } + // Return the default camera return [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; } @@ -1292,4 +1357,23 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response }); } +- (void)changeCamera +{ + dispatch_async(_cameraQueue, ^{ + // Get index of selected camera and select the next one, wrapping to the beginning + NSUInteger i = [_backCaptureDevices indexOfObject:_selectedCaptureDevice]; + int nextIndex = (i + 1) % _backCaptureDevices.count; + _selectedCaptureDevice = _backCaptureDevices[nextIndex]; + + [_cameraSession stopRunning]; + _cameraSession = nil; + _cameraConnection = nil; + _cameraOutput = nil; + if (_cameraNeedsUpdate) { + _cameraNeedsUpdate = false; + GB_camera_updated(&_gb); + } + }); +} + @end