Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[Camera] Return all possible cameras on iOS #5636

Merged
Merged
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.5

* Return all the available cameras on iOS.
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍


## 0.9.4+24

* Fixes preview orientation when pausing preview with locked orientation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 46;
objects = {

/* Begin PBXBuildFile section */
Expand All @@ -15,6 +15,7 @@
25C3919135C3D981E6F800D0 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */; };
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */; };
788A065A27B0E02900533D74 /* StreamingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 788A065927B0E02900533D74 /* StreamingTest.m */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
Expand Down Expand Up @@ -70,6 +71,7 @@
14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AvailableCamerasTest.m; sourceTree = "<group>"; };
59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
788A065927B0E02900533D74 /* StreamingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamingTest.m; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
Expand Down Expand Up @@ -145,6 +147,7 @@
E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */,
E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */,
788A065927B0E02900533D74 /* StreamingTest.m */,
43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */,
);
path = RunnerTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -415,6 +418,7 @@
E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */,
E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */,
E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */,
43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */,
F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */,
E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */,
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@import camera;
@import camera.Test;
@import XCTest;
@import AVFoundation;
#import <OCMock/OCMock.h>
#import "MockFLTThreadSafeFlutterResult.h"

@interface AvailableCamerasTest : XCTestCase
@end

@implementation AvailableCamerasTest

- (void)testAvailableCamerasShouldReturnAllCamerasOnMultiCameraIPhone {
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Result finished"];

// iPhone 13 Cameras:
AVCaptureDevice *wideAngleCamera = OCMClassMock([AVCaptureDevice class]);
OCMStub([wideAngleCamera uniqueID]).andReturn(@"0");
OCMStub([wideAngleCamera position]).andReturn(AVCaptureDevicePositionBack);

AVCaptureDevice *frontFacingCamera = OCMClassMock([AVCaptureDevice class]);
OCMStub([frontFacingCamera uniqueID]).andReturn(@"1");
OCMStub([frontFacingCamera position]).andReturn(AVCaptureDevicePositionFront);

AVCaptureDevice *ultraWideCamera = OCMClassMock([AVCaptureDevice class]);
OCMStub([ultraWideCamera uniqueID]).andReturn(@"2");
OCMStub([ultraWideCamera position]).andReturn(AVCaptureDevicePositionBack);

AVCaptureDevice *telephotoCamera = OCMClassMock([AVCaptureDevice class]);
OCMStub([telephotoCamera uniqueID]).andReturn(@"3");
OCMStub([telephotoCamera position]).andReturn(AVCaptureDevicePositionBack);

NSMutableArray *requiredTypes = [NSMutableArray array];
[requiredTypes addObjectsFromArray:@[
AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera
]];
Copy link
Contributor

Choose a reason for hiding this comment

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

This is both more readable and more performant (the latter doesn't matter in a test of course, but it's a better practice in general) as:

NSMutableArray *requiredTypes = [@[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera] mutableCopy];

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure 👍

if (@available(iOS 13.0, *)) {
[requiredTypes addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera];
}

id discoverySessionMock = OCMClassMock([AVCaptureDeviceDiscoverySession class]);
OCMStub([discoverySessionMock discoverySessionWithDeviceTypes:requiredTypes
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified])
.andReturn(discoverySessionMock);

NSMutableArray *cameras = [NSMutableArray array];
[cameras addObjectsFromArray:@[ wideAngleCamera, frontFacingCamera, telephotoCamera ]];
if (@available(iOS 13.0, *)) {
[cameras addObject:ultraWideCamera];
}
OCMStub([discoverySessionMock devices]).andReturn([NSArray arrayWithArray:cameras]);

MockFLTThreadSafeFlutterResult *resultObject =
[[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation];

// Set up method call
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"availableCameras"
arguments:nil];

[camera handleMethodCallAsync:call result:resultObject];

// Verify the result
NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult;
if (@available(iOS 13.0, *)) {
XCTAssertTrue([dictionaryResult count] == 4);
} else {
XCTAssertTrue([dictionaryResult count] == 3);
}
}
- (void)testAvailableCamerasShouldReturnOneCameraOnSingleCameraIPhone {
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Result finished"];

// iPhone 8 Cameras:
AVCaptureDevice *wideAngleCamera = OCMClassMock([AVCaptureDevice class]);
OCMStub([wideAngleCamera uniqueID]).andReturn(@"0");
OCMStub([wideAngleCamera position]).andReturn(AVCaptureDevicePositionBack);

AVCaptureDevice *frontFacingCamera = OCMClassMock([AVCaptureDevice class]);
OCMStub([frontFacingCamera uniqueID]).andReturn(@"1");
OCMStub([frontFacingCamera position]).andReturn(AVCaptureDevicePositionFront);

NSMutableArray *requiredTypes = [NSMutableArray array];
[requiredTypes addObjectsFromArray:@[
Copy link
Contributor

Choose a reason for hiding this comment

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

Same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera
]];
if (@available(iOS 13.0, *)) {
[requiredTypes addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera];
}

id discoverySessionMock = OCMClassMock([AVCaptureDeviceDiscoverySession class]);
OCMStub([discoverySessionMock discoverySessionWithDeviceTypes:requiredTypes
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified])
.andReturn(discoverySessionMock);

NSMutableArray *cameras = [NSMutableArray array];
[cameras addObjectsFromArray:@[ wideAngleCamera, frontFacingCamera ]];
OCMStub([discoverySessionMock devices]).andReturn([NSArray arrayWithArray:cameras]);

MockFLTThreadSafeFlutterResult *resultObject =
[[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation];

// Set up method call
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"availableCameras"
arguments:nil];

[camera handleMethodCallAsync:call result:resultObject];

// Verify the result
NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult;
XCTAssertTrue([dictionaryResult count] == 2);
}

@end
8 changes: 7 additions & 1 deletion packages/camera/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,14 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call
result:(FLTThreadSafeFlutterResult *)result {
if ([@"availableCameras" isEqualToString:call.method]) {
if (@available(iOS 10.0, *)) {
NSMutableArray *discoveryDevices = [NSMutableArray array];
[discoveryDevices addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
[discoveryDevices addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera];
Copy link
Contributor

Choose a reason for hiding this comment

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

NSMutableArray *discoveryDevices = [@[ addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera, addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera ] mutableCopy];

(run through clang-format, of course.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

if (@available(iOS 13.0, *)) {
[discoveryDevices addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera];
}
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ]
discoverySessionWithDeviceTypes:discoveryDevices
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
NSArray<AVCaptureDevice *> *devices = discoverySession.devices;
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.9.4+24
version: 0.9.5

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down