Skip to content

Commit 32a2d60

Browse files
martingeorgiumauricioluz
authored andcommitted
[Camera] Return all possible cameras on iOS (flutter#5636)
1 parent 3094ad5 commit 32a2d60

File tree

5 files changed

+137
-2
lines changed

5 files changed

+137
-2
lines changed

packages/camera/camera/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.7
2+
3+
* Returns all the available cameras on iOS.
4+
15
## 0.9.6
26

37
* Adds audio access permission handling logic on iOS to fix an issue with `prepareForVideoRecording` not awaiting for the audio permission request result.

packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
25C3919135C3D981E6F800D0 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */; };
1616
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */; };
1717
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
18+
43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */; };
1819
788A065A27B0E02900533D74 /* StreamingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 788A065927B0E02900533D74 /* StreamingTest.m */; };
1920
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
2021
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
@@ -71,6 +72,7 @@
7172
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>"; };
7273
1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7374
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
75+
43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AvailableCamerasTest.m; sourceTree = "<group>"; };
7476
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>"; };
7577
788A065927B0E02900533D74 /* StreamingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamingTest.m; sourceTree = "<group>"; };
7678
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -148,6 +150,7 @@
148150
E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */,
149151
E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */,
150152
788A065927B0E02900533D74 /* StreamingTest.m */,
153+
43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */,
151154
);
152155
path = RunnerTests;
153156
sourceTree = "<group>";
@@ -418,6 +421,7 @@
418421
E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */,
419422
E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */,
420423
E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */,
424+
43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */,
421425
F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */,
422426
E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */,
423427
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@import camera;
6+
@import camera.Test;
7+
@import XCTest;
8+
@import AVFoundation;
9+
#import <OCMock/OCMock.h>
10+
#import "MockFLTThreadSafeFlutterResult.h"
11+
12+
@interface AvailableCamerasTest : XCTestCase
13+
@end
14+
15+
@implementation AvailableCamerasTest
16+
17+
- (void)testAvailableCamerasShouldReturnAllCamerasOnMultiCameraIPhone {
18+
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
19+
XCTestExpectation *expectation =
20+
[[XCTestExpectation alloc] initWithDescription:@"Result finished"];
21+
22+
// iPhone 13 Cameras:
23+
AVCaptureDevice *wideAngleCamera = OCMClassMock([AVCaptureDevice class]);
24+
OCMStub([wideAngleCamera uniqueID]).andReturn(@"0");
25+
OCMStub([wideAngleCamera position]).andReturn(AVCaptureDevicePositionBack);
26+
27+
AVCaptureDevice *frontFacingCamera = OCMClassMock([AVCaptureDevice class]);
28+
OCMStub([frontFacingCamera uniqueID]).andReturn(@"1");
29+
OCMStub([frontFacingCamera position]).andReturn(AVCaptureDevicePositionFront);
30+
31+
AVCaptureDevice *ultraWideCamera = OCMClassMock([AVCaptureDevice class]);
32+
OCMStub([ultraWideCamera uniqueID]).andReturn(@"2");
33+
OCMStub([ultraWideCamera position]).andReturn(AVCaptureDevicePositionBack);
34+
35+
AVCaptureDevice *telephotoCamera = OCMClassMock([AVCaptureDevice class]);
36+
OCMStub([telephotoCamera uniqueID]).andReturn(@"3");
37+
OCMStub([telephotoCamera position]).andReturn(AVCaptureDevicePositionBack);
38+
39+
NSMutableArray *requiredTypes =
40+
[@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ]
41+
mutableCopy];
42+
if (@available(iOS 13.0, *)) {
43+
[requiredTypes addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera];
44+
}
45+
46+
id discoverySessionMock = OCMClassMock([AVCaptureDeviceDiscoverySession class]);
47+
OCMStub([discoverySessionMock discoverySessionWithDeviceTypes:requiredTypes
48+
mediaType:AVMediaTypeVideo
49+
position:AVCaptureDevicePositionUnspecified])
50+
.andReturn(discoverySessionMock);
51+
52+
NSMutableArray *cameras = [NSMutableArray array];
53+
[cameras addObjectsFromArray:@[ wideAngleCamera, frontFacingCamera, telephotoCamera ]];
54+
if (@available(iOS 13.0, *)) {
55+
[cameras addObject:ultraWideCamera];
56+
}
57+
OCMStub([discoverySessionMock devices]).andReturn([NSArray arrayWithArray:cameras]);
58+
59+
MockFLTThreadSafeFlutterResult *resultObject =
60+
[[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation];
61+
62+
// Set up method call
63+
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"availableCameras"
64+
arguments:nil];
65+
66+
[camera handleMethodCallAsync:call result:resultObject];
67+
68+
// Verify the result
69+
NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult;
70+
if (@available(iOS 13.0, *)) {
71+
XCTAssertTrue([dictionaryResult count] == 4);
72+
} else {
73+
XCTAssertTrue([dictionaryResult count] == 3);
74+
}
75+
}
76+
- (void)testAvailableCamerasShouldReturnOneCameraOnSingleCameraIPhone {
77+
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
78+
XCTestExpectation *expectation =
79+
[[XCTestExpectation alloc] initWithDescription:@"Result finished"];
80+
81+
// iPhone 8 Cameras:
82+
AVCaptureDevice *wideAngleCamera = OCMClassMock([AVCaptureDevice class]);
83+
OCMStub([wideAngleCamera uniqueID]).andReturn(@"0");
84+
OCMStub([wideAngleCamera position]).andReturn(AVCaptureDevicePositionBack);
85+
86+
AVCaptureDevice *frontFacingCamera = OCMClassMock([AVCaptureDevice class]);
87+
OCMStub([frontFacingCamera uniqueID]).andReturn(@"1");
88+
OCMStub([frontFacingCamera position]).andReturn(AVCaptureDevicePositionFront);
89+
90+
NSMutableArray *requiredTypes =
91+
[@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ]
92+
mutableCopy];
93+
if (@available(iOS 13.0, *)) {
94+
[requiredTypes addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera];
95+
}
96+
97+
id discoverySessionMock = OCMClassMock([AVCaptureDeviceDiscoverySession class]);
98+
OCMStub([discoverySessionMock discoverySessionWithDeviceTypes:requiredTypes
99+
mediaType:AVMediaTypeVideo
100+
position:AVCaptureDevicePositionUnspecified])
101+
.andReturn(discoverySessionMock);
102+
103+
NSMutableArray *cameras = [NSMutableArray array];
104+
[cameras addObjectsFromArray:@[ wideAngleCamera, frontFacingCamera ]];
105+
OCMStub([discoverySessionMock devices]).andReturn([NSArray arrayWithArray:cameras]);
106+
107+
MockFLTThreadSafeFlutterResult *resultObject =
108+
[[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation];
109+
110+
// Set up method call
111+
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"availableCameras"
112+
arguments:nil];
113+
114+
[camera handleMethodCallAsync:call result:resultObject];
115+
116+
// Verify the result
117+
NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult;
118+
XCTAssertTrue([dictionaryResult count] == 2);
119+
}
120+
121+
@end

packages/camera/camera/ios/Classes/CameraPlugin.m

+7-1
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,14 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call
101101
result:(FLTThreadSafeFlutterResult *)result {
102102
if ([@"availableCameras" isEqualToString:call.method]) {
103103
if (@available(iOS 10.0, *)) {
104+
NSMutableArray *discoveryDevices =
105+
[@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ]
106+
mutableCopy];
107+
if (@available(iOS 13.0, *)) {
108+
[discoveryDevices addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera];
109+
}
104110
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
105-
discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ]
111+
discoverySessionWithDeviceTypes:discoveryDevices
106112
mediaType:AVMediaTypeVideo
107113
position:AVCaptureDevicePositionUnspecified];
108114
NSArray<AVCaptureDevice *> *devices = discoverySession.devices;

packages/camera/camera/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
44
Dart.
55
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.9.6
7+
version: 0.9.7
88

99
environment:
1010
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)