Skip to content

Commit 5cc6418

Browse files
[camera] Convert Windows to Pigeon (flutter#6925)
Replaces all of the manual method channel code in `camera_windows` with Pigeon. I attempted to change the structure as little as possible, since this was already a large change. I don't particularly like the way the native result callback objects are managed (passed to `Camera` and tracked in a map), but I decided that redesigning that would be out of scope and introduced a `std::variant` to allow minimal changes to that structure. That does slightly undermine the type safety of the callbacks, but it's still strictly enforced at the level of the helpers that interact with the map. Fixes flutter#117905
1 parent f34184e commit 5cc6418

24 files changed

+2788
-1464
lines changed

packages/camera/camera_windows/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 0.2.3
22

3+
* Converts native platform calls to Pigeon.
34
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
45

56
## 0.2.2

packages/camera/camera_windows/lib/camera_windows.dart

Lines changed: 87 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,21 @@ import 'package:flutter/services.dart';
1010
import 'package:flutter/widgets.dart';
1111
import 'package:stream_transform/stream_transform.dart';
1212

13+
import 'src/messages.g.dart';
14+
1315
/// An implementation of [CameraPlatform] for Windows.
1416
class CameraWindows extends CameraPlatform {
17+
/// Creates a new Windows [CameraPlatform] implementation instance.
18+
CameraWindows({@visibleForTesting CameraApi? api})
19+
: _hostApi = api ?? CameraApi();
20+
1521
/// Registers the Windows implementation of CameraPlatform.
1622
static void registerWith() {
1723
CameraPlatform.instance = CameraWindows();
1824
}
1925

20-
/// The method channel used to interact with the native platform.
21-
@visibleForTesting
22-
final MethodChannel pluginChannel =
23-
const MethodChannel('plugins.flutter.io/camera_windows');
26+
/// Interface for calling host-side code.
27+
final CameraApi _hostApi;
2428

2529
/// Camera specific method channels to allow communicating with specific cameras.
2630
final Map<int, MethodChannel> _cameraChannels = <int, MethodChannel>{};
@@ -43,19 +47,18 @@ class CameraWindows extends CameraPlatform {
4347
@override
4448
Future<List<CameraDescription>> availableCameras() async {
4549
try {
46-
final List<Map<dynamic, dynamic>>? cameras = await pluginChannel
47-
.invokeListMethod<Map<dynamic, dynamic>>('availableCameras');
50+
final List<String?> cameras = await _hostApi.getAvailableCameras();
4851

49-
if (cameras == null) {
50-
return <CameraDescription>[];
51-
}
52-
53-
return cameras.map((Map<dynamic, dynamic> camera) {
52+
return cameras.map((String? cameraName) {
5453
return CameraDescription(
55-
name: camera['name'] as String,
56-
lensDirection:
57-
parseCameraLensDirection(camera['lensFacing'] as String),
58-
sensorOrientation: camera['sensorOrientation'] as int,
54+
// This type is only nullable due to Pigeon limitations, see
55+
// https://github.com/flutter/flutter/issues/97848. The native code
56+
// will never return null.
57+
name: cameraName!,
58+
// TODO(stuartmorgan): Implement these; see
59+
// https://github.com/flutter/flutter/issues/97540.
60+
lensDirection: CameraLensDirection.front,
61+
sensorOrientation: 0,
5962
);
6063
}).toList();
6164
} on PlatformException catch (e) {
@@ -83,23 +86,8 @@ class CameraWindows extends CameraPlatform {
8386
) async {
8487
try {
8588
// If resolutionPreset is not specified, plugin selects the highest resolution possible.
86-
final Map<String, dynamic>? reply = await pluginChannel
87-
.invokeMapMethod<String, dynamic>('create', <String, dynamic>{
88-
'cameraName': cameraDescription.name,
89-
'resolutionPreset': null != mediaSettings?.resolutionPreset
90-
? _serializeResolutionPreset(mediaSettings!.resolutionPreset)
91-
: null,
92-
'fps': mediaSettings?.fps,
93-
'videoBitrate': mediaSettings?.videoBitrate,
94-
'audioBitrate': mediaSettings?.audioBitrate,
95-
'enableAudio': mediaSettings?.enableAudio ?? true,
96-
});
97-
98-
if (reply == null) {
99-
throw CameraException('System', 'Cannot create camera');
100-
}
101-
102-
return reply['cameraId']! as int;
89+
return await _hostApi.create(
90+
cameraDescription.name, _pigeonMediaSettings(mediaSettings));
10391
} on PlatformException catch (e) {
10492
throw CameraException(e.code, e.message);
10593
}
@@ -110,35 +98,28 @@ class CameraWindows extends CameraPlatform {
11098
int cameraId, {
11199
ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown,
112100
}) async {
113-
final int requestedCameraId = cameraId;
114-
115101
/// Creates channel for camera events.
116-
_cameraChannels.putIfAbsent(requestedCameraId, () {
117-
final MethodChannel channel = MethodChannel(
118-
'plugins.flutter.io/camera_windows/camera$requestedCameraId');
102+
_cameraChannels.putIfAbsent(cameraId, () {
103+
final MethodChannel channel =
104+
MethodChannel('plugins.flutter.io/camera_windows/camera$cameraId');
119105
channel.setMethodCallHandler(
120-
(MethodCall call) => handleCameraMethodCall(call, requestedCameraId),
106+
(MethodCall call) => handleCameraMethodCall(call, cameraId),
121107
);
122108
return channel;
123109
});
124110

125-
final Map<String, double>? reply;
111+
final PlatformSize reply;
126112
try {
127-
reply = await pluginChannel.invokeMapMethod<String, double>(
128-
'initialize',
129-
<String, dynamic>{
130-
'cameraId': requestedCameraId,
131-
},
132-
);
113+
reply = await _hostApi.initialize(cameraId);
133114
} on PlatformException catch (e) {
134115
throw CameraException(e.code, e.message);
135116
}
136117

137118
cameraEventStreamController.add(
138119
CameraInitializedEvent(
139-
requestedCameraId,
140-
reply!['previewWidth']!,
141-
reply['previewHeight']!,
120+
cameraId,
121+
reply.width,
122+
reply.height,
142123
ExposureMode.auto,
143124
false,
144125
FocusMode.auto,
@@ -149,10 +130,7 @@ class CameraWindows extends CameraPlatform {
149130

150131
@override
151132
Future<void> dispose(int cameraId) async {
152-
await pluginChannel.invokeMethod<void>(
153-
'dispose',
154-
<String, dynamic>{'cameraId': cameraId},
155-
);
133+
await _hostApi.dispose(cameraId);
156134

157135
// Destroy method channel after camera is disposed to be able to handle last messages.
158136
if (_cameraChannels.containsKey(cameraId)) {
@@ -217,18 +195,15 @@ class CameraWindows extends CameraPlatform {
217195

218196
@override
219197
Future<XFile> takePicture(int cameraId) async {
220-
final String? path;
221-
path = await pluginChannel.invokeMethod<String>(
222-
'takePicture',
223-
<String, dynamic>{'cameraId': cameraId},
224-
);
198+
final String path = await _hostApi.takePicture(cameraId);
225199

226-
return XFile(path!);
200+
return XFile(path);
227201
}
228202

229203
@override
230-
Future<void> prepareForVideoRecording() =>
231-
pluginChannel.invokeMethod<void>('prepareForVideoRecording');
204+
Future<void> prepareForVideoRecording() async {
205+
// No-op.
206+
}
232207

233208
@override
234209
Future<void> startVideoRecording(int cameraId,
@@ -244,25 +219,15 @@ class CameraWindows extends CameraPlatform {
244219
'Streaming is not currently supported on Windows');
245220
}
246221

247-
await pluginChannel.invokeMethod<void>(
248-
'startVideoRecording',
249-
<String, dynamic>{
250-
'cameraId': options.cameraId,
251-
'maxVideoDuration': options.maxDuration?.inMilliseconds,
252-
},
253-
);
222+
await _hostApi.startVideoRecording(
223+
options.cameraId, _pigeonVideoCaptureOptions(options));
254224
}
255225

256226
@override
257227
Future<XFile> stopVideoRecording(int cameraId) async {
258-
final String? path;
259-
260-
path = await pluginChannel.invokeMethod<String>(
261-
'stopVideoRecording',
262-
<String, dynamic>{'cameraId': cameraId},
263-
);
228+
final String path = await _hostApi.stopVideoRecording(cameraId);
264229

265-
return XFile(path!);
230+
return XFile(path);
266231
}
267232

268233
@override
@@ -362,45 +327,19 @@ class CameraWindows extends CameraPlatform {
362327

363328
@override
364329
Future<void> pausePreview(int cameraId) async {
365-
await pluginChannel.invokeMethod<double>(
366-
'pausePreview',
367-
<String, dynamic>{'cameraId': cameraId},
368-
);
330+
await _hostApi.pausePreview(cameraId);
369331
}
370332

371333
@override
372334
Future<void> resumePreview(int cameraId) async {
373-
await pluginChannel.invokeMethod<double>(
374-
'resumePreview',
375-
<String, dynamic>{'cameraId': cameraId},
376-
);
335+
await _hostApi.resumePreview(cameraId);
377336
}
378337

379338
@override
380339
Widget buildPreview(int cameraId) {
381340
return Texture(textureId: cameraId);
382341
}
383342

384-
/// Returns the resolution preset as a nullable String.
385-
String? _serializeResolutionPreset(ResolutionPreset? resolutionPreset) {
386-
switch (resolutionPreset) {
387-
case null:
388-
return null;
389-
case ResolutionPreset.max:
390-
return 'max';
391-
case ResolutionPreset.ultraHigh:
392-
return 'ultraHigh';
393-
case ResolutionPreset.veryHigh:
394-
return 'veryHigh';
395-
case ResolutionPreset.high:
396-
return 'high';
397-
case ResolutionPreset.medium:
398-
return 'medium';
399-
case ResolutionPreset.low:
400-
return 'low';
401-
}
402-
}
403-
404343
/// Converts messages received from the native platform into camera events.
405344
///
406345
/// This is only exposed for test purposes. It shouldn't be used by clients
@@ -440,17 +379,52 @@ class CameraWindows extends CameraPlatform {
440379
}
441380
}
442381

443-
/// Parses string presentation of the camera lens direction and returns enum value.
444-
@visibleForTesting
445-
CameraLensDirection parseCameraLensDirection(String string) {
446-
switch (string) {
447-
case 'front':
448-
return CameraLensDirection.front;
449-
case 'back':
450-
return CameraLensDirection.back;
451-
case 'external':
452-
return CameraLensDirection.external;
382+
/// Returns a [MediaSettings]'s Pigeon representation.
383+
PlatformMediaSettings _pigeonMediaSettings(MediaSettings? settings) {
384+
return PlatformMediaSettings(
385+
resolutionPreset: _pigeonResolutionPreset(settings?.resolutionPreset),
386+
enableAudio: settings?.enableAudio ?? true,
387+
framesPerSecond: settings?.fps,
388+
videoBitrate: settings?.videoBitrate,
389+
audioBitrate: settings?.audioBitrate,
390+
);
391+
}
392+
393+
/// Returns a [ResolutionPreset]'s Pigeon representation.
394+
PlatformResolutionPreset _pigeonResolutionPreset(
395+
ResolutionPreset? resolutionPreset) {
396+
if (resolutionPreset == null) {
397+
// Provide a default if one isn't provided, since the native side needs
398+
// to set something.
399+
return PlatformResolutionPreset.max;
400+
}
401+
switch (resolutionPreset) {
402+
case ResolutionPreset.max:
403+
return PlatformResolutionPreset.max;
404+
case ResolutionPreset.ultraHigh:
405+
return PlatformResolutionPreset.ultraHigh;
406+
case ResolutionPreset.veryHigh:
407+
return PlatformResolutionPreset.veryHigh;
408+
case ResolutionPreset.high:
409+
return PlatformResolutionPreset.high;
410+
case ResolutionPreset.medium:
411+
return PlatformResolutionPreset.medium;
412+
case ResolutionPreset.low:
413+
return PlatformResolutionPreset.low;
453414
}
454-
throw ArgumentError('Unknown CameraLensDirection value');
415+
// The enum comes from a different package, which could get a new value at
416+
// any time, so provide a fallback that ensures this won't break when used
417+
// with a version that contains new values. This is deliberately outside
418+
// the switch rather than a `default` so that the linter will flag the
419+
// switch as needing an update.
420+
// ignore: dead_code
421+
return PlatformResolutionPreset.max;
422+
}
423+
424+
/// Returns a [VideoCamptureOptions]'s Pigeon representation.
425+
PlatformVideoCaptureOptions _pigeonVideoCaptureOptions(
426+
VideoCaptureOptions options) {
427+
return PlatformVideoCaptureOptions(
428+
maxDurationMilliseconds: options.maxDuration?.inMilliseconds);
455429
}
456430
}

0 commit comments

Comments
 (0)