Skip to content

[camera] Convert Windows to Pigeon #6925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 9, 2024
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
3 changes: 2 additions & 1 deletion packages/camera/camera_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 0.2.3

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

## 0.2.2
Expand Down
200 changes: 87 additions & 113 deletions packages/camera/camera_windows/lib/camera_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';

import 'src/messages.g.dart';

/// An implementation of [CameraPlatform] for Windows.
class CameraWindows extends CameraPlatform {
/// Creates a new Windows [CameraPlatform] implementation instance.
CameraWindows({@visibleForTesting CameraApi? api})
: _hostApi = api ?? CameraApi();

/// Registers the Windows implementation of CameraPlatform.
static void registerWith() {
CameraPlatform.instance = CameraWindows();
}

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

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

if (cameras == null) {
return <CameraDescription>[];
}

return cameras.map((Map<dynamic, dynamic> camera) {
return cameras.map((String? cameraName) {
return CameraDescription(
name: camera['name'] as String,
lensDirection:
parseCameraLensDirection(camera['lensFacing'] as String),
sensorOrientation: camera['sensorOrientation'] as int,
// This type is only nullable due to Pigeon limitations, see
// https://github.com/flutter/flutter/issues/97848. The native code
// will never return null.
name: cameraName!,
// TODO(stuartmorgan): Implement these; see
// https://github.com/flutter/flutter/issues/97540.
lensDirection: CameraLensDirection.front,
sensorOrientation: 0,
);
}).toList();
} on PlatformException catch (e) {
Expand Down Expand Up @@ -83,23 +86,8 @@ class CameraWindows extends CameraPlatform {
) async {
try {
// If resolutionPreset is not specified, plugin selects the highest resolution possible.
final Map<String, dynamic>? reply = await pluginChannel
.invokeMapMethod<String, dynamic>('create', <String, dynamic>{
'cameraName': cameraDescription.name,
'resolutionPreset': null != mediaSettings?.resolutionPreset
? _serializeResolutionPreset(mediaSettings!.resolutionPreset)
: null,
'fps': mediaSettings?.fps,
'videoBitrate': mediaSettings?.videoBitrate,
'audioBitrate': mediaSettings?.audioBitrate,
'enableAudio': mediaSettings?.enableAudio ?? true,
});

if (reply == null) {
throw CameraException('System', 'Cannot create camera');
}

return reply['cameraId']! as int;
return await _hostApi.create(
cameraDescription.name, _pigeonMediaSettings(mediaSettings));
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
Expand All @@ -110,35 +98,28 @@ class CameraWindows extends CameraPlatform {
int cameraId, {
ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown,
}) async {
final int requestedCameraId = cameraId;

/// Creates channel for camera events.
_cameraChannels.putIfAbsent(requestedCameraId, () {
final MethodChannel channel = MethodChannel(
'plugins.flutter.io/camera_windows/camera$requestedCameraId');
_cameraChannels.putIfAbsent(cameraId, () {
final MethodChannel channel =
MethodChannel('plugins.flutter.io/camera_windows/camera$cameraId');
channel.setMethodCallHandler(
(MethodCall call) => handleCameraMethodCall(call, requestedCameraId),
(MethodCall call) => handleCameraMethodCall(call, cameraId),
);
return channel;
});

final Map<String, double>? reply;
final PlatformSize reply;
try {
reply = await pluginChannel.invokeMapMethod<String, double>(
'initialize',
<String, dynamic>{
'cameraId': requestedCameraId,
},
);
reply = await _hostApi.initialize(cameraId);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}

cameraEventStreamController.add(
CameraInitializedEvent(
requestedCameraId,
reply!['previewWidth']!,
reply['previewHeight']!,
cameraId,
reply.width,
reply.height,
ExposureMode.auto,
false,
FocusMode.auto,
Expand All @@ -149,10 +130,7 @@ class CameraWindows extends CameraPlatform {

@override
Future<void> dispose(int cameraId) async {
await pluginChannel.invokeMethod<void>(
'dispose',
<String, dynamic>{'cameraId': cameraId},
);
await _hostApi.dispose(cameraId);

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

@override
Future<XFile> takePicture(int cameraId) async {
final String? path;
path = await pluginChannel.invokeMethod<String>(
'takePicture',
<String, dynamic>{'cameraId': cameraId},
);
final String path = await _hostApi.takePicture(cameraId);

return XFile(path!);
return XFile(path);
}

@override
Future<void> prepareForVideoRecording() =>
pluginChannel.invokeMethod<void>('prepareForVideoRecording');
Future<void> prepareForVideoRecording() async {
// No-op.
}

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

await pluginChannel.invokeMethod<void>(
'startVideoRecording',
<String, dynamic>{
'cameraId': options.cameraId,
'maxVideoDuration': options.maxDuration?.inMilliseconds,
},
);
await _hostApi.startVideoRecording(
options.cameraId, _pigeonVideoCaptureOptions(options));
}

@override
Future<XFile> stopVideoRecording(int cameraId) async {
final String? path;

path = await pluginChannel.invokeMethod<String>(
'stopVideoRecording',
<String, dynamic>{'cameraId': cameraId},
);
final String path = await _hostApi.stopVideoRecording(cameraId);

return XFile(path!);
return XFile(path);
}

@override
Expand Down Expand Up @@ -362,45 +327,19 @@ class CameraWindows extends CameraPlatform {

@override
Future<void> pausePreview(int cameraId) async {
await pluginChannel.invokeMethod<double>(
'pausePreview',
<String, dynamic>{'cameraId': cameraId},
);
await _hostApi.pausePreview(cameraId);
}

@override
Future<void> resumePreview(int cameraId) async {
await pluginChannel.invokeMethod<double>(
'resumePreview',
<String, dynamic>{'cameraId': cameraId},
);
await _hostApi.resumePreview(cameraId);
}

@override
Widget buildPreview(int cameraId) {
return Texture(textureId: cameraId);
}

/// Returns the resolution preset as a nullable String.
String? _serializeResolutionPreset(ResolutionPreset? resolutionPreset) {
switch (resolutionPreset) {
case null:
return null;
case ResolutionPreset.max:
return 'max';
case ResolutionPreset.ultraHigh:
return 'ultraHigh';
case ResolutionPreset.veryHigh:
return 'veryHigh';
case ResolutionPreset.high:
return 'high';
case ResolutionPreset.medium:
return 'medium';
case ResolutionPreset.low:
return 'low';
}
}

/// Converts messages received from the native platform into camera events.
///
/// This is only exposed for test purposes. It shouldn't be used by clients
Expand Down Expand Up @@ -440,17 +379,52 @@ class CameraWindows extends CameraPlatform {
}
}

/// Parses string presentation of the camera lens direction and returns enum value.
@visibleForTesting
CameraLensDirection parseCameraLensDirection(String string) {
switch (string) {
case 'front':
return CameraLensDirection.front;
case 'back':
return CameraLensDirection.back;
case 'external':
return CameraLensDirection.external;
/// Returns a [MediaSettings]'s Pigeon representation.
PlatformMediaSettings _pigeonMediaSettings(MediaSettings? settings) {
return PlatformMediaSettings(
resolutionPreset: _pigeonResolutionPreset(settings?.resolutionPreset),
enableAudio: settings?.enableAudio ?? true,
framesPerSecond: settings?.fps,
videoBitrate: settings?.videoBitrate,
audioBitrate: settings?.audioBitrate,
);
}

/// Returns a [ResolutionPreset]'s Pigeon representation.
PlatformResolutionPreset _pigeonResolutionPreset(
ResolutionPreset? resolutionPreset) {
if (resolutionPreset == null) {
// Provide a default if one isn't provided, since the native side needs
// to set something.
return PlatformResolutionPreset.max;
}
switch (resolutionPreset) {
case ResolutionPreset.max:
return PlatformResolutionPreset.max;
case ResolutionPreset.ultraHigh:
return PlatformResolutionPreset.ultraHigh;
case ResolutionPreset.veryHigh:
return PlatformResolutionPreset.veryHigh;
case ResolutionPreset.high:
return PlatformResolutionPreset.high;
case ResolutionPreset.medium:
return PlatformResolutionPreset.medium;
case ResolutionPreset.low:
return PlatformResolutionPreset.low;
}
throw ArgumentError('Unknown CameraLensDirection value');
// The enum comes from a different package, which could get a new value at
// any time, so provide a fallback that ensures this won't break when used
// with a version that contains new values. This is deliberately outside
// the switch rather than a `default` so that the linter will flag the
// switch as needing an update.
// ignore: dead_code
return PlatformResolutionPreset.max;
}

/// Returns a [VideoCamptureOptions]'s Pigeon representation.
PlatformVideoCaptureOptions _pigeonVideoCaptureOptions(
VideoCaptureOptions options) {
return PlatformVideoCaptureOptions(
maxDurationMilliseconds: options.maxDuration?.inMilliseconds);
}
}
Loading