From 57f3aa88bfd4207bf6e483d16e945b687a8f41bf Mon Sep 17 00:00:00 2001 From: Hidenori Matsubayashi Date: Mon, 28 Aug 2023 12:37:20 +0000 Subject: [PATCH] camera: add ELinuxCamera dartPluginClass (#81) Fixed https://github.com/sony/flutter-elinux/issues/211 Signed-off-by: Hidenori Matsubayashi --- packages/camera/CHANGELOG.md | 3 + .../camera/elinux/camera_elinux_plugin.cc | 38 +- .../elinux/channels/method_channel_camera.cc | 2 +- .../elinux/channels/method_channel_device.cc | 2 +- packages/camera/lib/camera_elinux.dart | 6 + packages/camera/lib/src/elinux_camera.dart | 645 ++++++++++++++++++ packages/camera/lib/src/type_conversion.dart | 49 ++ packages/camera/lib/src/utils.dart | 56 ++ packages/camera/pubspec.yaml | 4 +- 9 files changed, 800 insertions(+), 5 deletions(-) create mode 100644 packages/camera/lib/camera_elinux.dart create mode 100644 packages/camera/lib/src/elinux_camera.dart create mode 100644 packages/camera/lib/src/type_conversion.dart create mode 100644 packages/camera/lib/src/utils.dart diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 620bdb4..c9baa6b 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.3.2 +* Add ELinuxCamera (camera dartPluginClass for eLinux) for flutter 3.13. + ## 0.3.1 * Update for flutter official camera plugin v0.10.5+3 diff --git a/packages/camera/elinux/camera_elinux_plugin.cc b/packages/camera/elinux/camera_elinux_plugin.cc index 4f841e0..c9c1bff 100644 --- a/packages/camera/elinux/camera_elinux_plugin.cc +++ b/packages/camera/elinux/camera_elinux_plugin.cc @@ -86,6 +86,12 @@ class CameraPlugin : public flutter::Plugin { void HandleTakePictureCall( const flutter::EncodableValue* message, std::unique_ptr> result); + void HandleGetMinExposureOffsetCall( + const flutter::EncodableValue* message, + std::unique_ptr> result); + void HandleGetMaxExposureOffsetCall( + const flutter::EncodableValue* message, + std::unique_ptr> result); void HandleStartImageStreamCall( const flutter::EncodableValue* message, std::unique_ptr> result); @@ -169,9 +175,9 @@ void CameraPlugin::HandleMethodCall( } else if (!method_name.compare(kCameraChannelApiSetExposurePoint)) { result->NotImplemented(); } else if (!method_name.compare(kCameraChannelApiGetMinExposureOffset)) { - result->NotImplemented(); + HandleGetMinExposureOffsetCall(method_call.arguments(), std::move(result)); } else if (!method_name.compare(kCameraChannelApiGetMaxExposureOffset)) { - result->NotImplemented(); + HandleGetMaxExposureOffsetCall(method_call.arguments(), std::move(result)); } else if (!method_name.compare(kCameraChannelApiGetExposureOffsetStepSize)) { result->NotImplemented(); } else if (!method_name.compare(kCameraChannelApiSetExposureOffset)) { @@ -308,6 +314,34 @@ void CameraPlugin::HandleTakePictureCall( }); } +void CameraPlugin::HandleGetMinExposureOffsetCall( + const flutter::EncodableValue* message, + std::unique_ptr> result) { + if (!camera_) { + result->Error("Not found an active camera", + "Check for creating a camera device"); + return; + } + + // TODO: Implement exposure control support + double value = 0.0; + result->Success(flutter::EncodableValue(value)); +} + +void CameraPlugin::HandleGetMaxExposureOffsetCall( + const flutter::EncodableValue* message, + std::unique_ptr> result) { + if (!camera_) { + result->Error("Not found an active camera", + "Check for creating a camera device"); + return; + } + + // TODO: Implement exposure control support + double value = 0.0; + result->Success(flutter::EncodableValue(value)); +} + void CameraPlugin::HandleStartImageStreamCall( const flutter::EncodableValue* message, std::unique_ptr> result) { diff --git a/packages/camera/elinux/channels/method_channel_camera.cc b/packages/camera/elinux/channels/method_channel_camera.cc index 5f284d1..1d60948 100644 --- a/packages/camera/elinux/channels/method_channel_camera.cc +++ b/packages/camera/elinux/channels/method_channel_camera.cc @@ -8,7 +8,7 @@ #include namespace { -constexpr char kChannelName[] = "flutter.io/cameraPlugin/camera"; +constexpr char kChannelName[] = "plugins.flutter.io/camera/camera"; constexpr char kChannelMethodInitialized[] = "initialized"; }; // namespace diff --git a/packages/camera/elinux/channels/method_channel_device.cc b/packages/camera/elinux/channels/method_channel_device.cc index 3e85557..516433d 100644 --- a/packages/camera/elinux/channels/method_channel_device.cc +++ b/packages/camera/elinux/channels/method_channel_device.cc @@ -8,7 +8,7 @@ #include namespace { -constexpr char kChannelName[] = "flutter.io/cameraPlugin/device"; +constexpr char kChannelName[] = "plugins.flutter.io/camera/fromPlatform"; constexpr char kChannelMethodOrientationChanged[] = "orientation_changed"; constexpr char kOrientation[] = "orientation"; diff --git a/packages/camera/lib/camera_elinux.dart b/packages/camera/lib/camera_elinux.dart new file mode 100644 index 0000000..f8f6bcc --- /dev/null +++ b/packages/camera/lib/camera_elinux.dart @@ -0,0 +1,6 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// 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. + +export 'src/elinux_camera.dart'; diff --git a/packages/camera/lib/src/elinux_camera.dart b/packages/camera/lib/src/elinux_camera.dart new file mode 100644 index 0000000..02f4d45 --- /dev/null +++ b/packages/camera/lib/src/elinux_camera.dart @@ -0,0 +1,645 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// 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 'dart:async'; +import 'dart:math'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_transform/stream_transform.dart'; + +import 'type_conversion.dart'; +import 'utils.dart'; + +const MethodChannel _channel = + MethodChannel('plugins.flutter.io/camera'); + +/// The ELinux implementation of [CameraPlatform] that uses method channels. +class ELinuxCamera extends CameraPlatform { + /// Registers this class as the default instance of [CameraPlatform]. + static void registerWith() { + CameraPlatform.instance = ELinuxCamera(); + } + + final Map _channels = {}; + + /// The name of the channel that device events from the platform side are + /// sent on. + @visibleForTesting + static const String deviceEventChannelName = + 'plugins.flutter.io/camera/fromPlatform'; + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to camera events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController cameraEventStreamController = + StreamController.broadcast(); + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to general device events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + late final StreamController _deviceEventStreamController = + _createDeviceEventStreamController(); + + StreamController _createDeviceEventStreamController() { + // Set up the method handler lazily. + const MethodChannel channel = MethodChannel(deviceEventChannelName); + channel.setMethodCallHandler(_handleDeviceMethodCall); + return StreamController.broadcast(); + } + + // The stream to receive frames from the native code. + StreamSubscription? _platformImageStreamSubscription; + + // The stream for vending frames to platform interface clients. + StreamController? _frameStreamController; + + Stream _cameraEvents(int cameraId) => + cameraEventStreamController.stream + .where((CameraEvent event) => event.cameraId == cameraId); + + @override + Future> availableCameras() async { + try { + final List>? cameras = await _channel + .invokeListMethod>('availableCameras'); + + if (cameras == null) { + return []; + } + + return cameras.map((Map camera) { + return CameraDescription( + name: camera['name']! as String, + lensDirection: + parseCameraLensDirection(camera['lensFacing']! as String), + sensorOrientation: camera['sensorOrientation']! as int, + ); + }).toList(); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future createCamera( + CameraDescription cameraDescription, + ResolutionPreset? resolutionPreset, { + bool enableAudio = false, + }) async { + try { + final Map? reply = await _channel + .invokeMapMethod('create', { + 'cameraName': cameraDescription.name, + 'resolutionPreset': resolutionPreset != null + ? _serializeResolutionPreset(resolutionPreset) + : null, + 'enableAudio': enableAudio, + }); + + return reply!['cameraId']! as int; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future initializeCamera( + int cameraId, { + ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, + }) { + _channels.putIfAbsent(cameraId, () { + final MethodChannel channel = + MethodChannel('plugins.flutter.io/camera/camera$cameraId'); + channel.setMethodCallHandler( + (MethodCall call) => handleCameraMethodCall(call, cameraId)); + return channel; + }); + + final Completer completer = Completer(); + + onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { + completer.complete(); + }); + + _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': cameraId, + 'imageFormatGroup': imageFormatGroup.name(), + }, + ).catchError( + // TODO(srawlins): This should return a value of the future's type. This + // will fail upcoming analysis checks with + // https://github.com/flutter/flutter/issues/105750. + // ignore: body_might_complete_normally_catch_error + (Object error, StackTrace stackTrace) { + if (error is! PlatformException) { + // ignore: only_throw_errors + throw error; + } + completer.completeError( + CameraException(error.code, error.message), + stackTrace, + ); + }, + ); + + return completer.future; + } + + @override + Future dispose(int cameraId) async { + if (_channels.containsKey(cameraId)) { + final MethodChannel? cameraChannel = _channels[cameraId]; + cameraChannel?.setMethodCallHandler(null); + _channels.remove(cameraId); + } + + await _channel.invokeMethod( + 'dispose', + {'cameraId': cameraId}, + ); + } + + @override + Stream onCameraInitialized(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraResolutionChanged(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraClosing(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraError(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onVideoRecordedEvent(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onDeviceOrientationChanged() { + return _deviceEventStreamController.stream + .whereType(); + } + + @override + Future lockCaptureOrientation( + int cameraId, + DeviceOrientation orientation, + ) async { + await _channel.invokeMethod( + 'lockCaptureOrientation', + { + 'cameraId': cameraId, + 'orientation': serializeDeviceOrientation(orientation) + }, + ); + } + + @override + Future unlockCaptureOrientation(int cameraId) async { + await _channel.invokeMethod( + 'unlockCaptureOrientation', + {'cameraId': cameraId}, + ); + } + + @override + Future takePicture(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'takePicture', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future prepareForVideoRecording() => + _channel.invokeMethod('prepareForVideoRecording'); + + @override + Future startVideoRecording(int cameraId, + {Duration? maxVideoDuration}) async { + return startVideoCapturing( + VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); + } + + @override + Future startVideoCapturing(VideoCaptureOptions options) async { + await _channel.invokeMethod( + 'startVideoRecording', + { + 'cameraId': options.cameraId, + 'maxVideoDuration': options.maxDuration?.inMilliseconds, + 'enableStream': options.streamCallback != null, + }, + ); + + if (options.streamCallback != null) { + _installStreamController().stream.listen(options.streamCallback); + _startStreamListener(); + } + } + + @override + Future stopVideoRecording(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'stopVideoRecording', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); + + @override + Future resumeVideoRecording(int cameraId) => + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); + + @override + Stream onStreamedFrameAvailable(int cameraId, + {CameraImageStreamOptions? options}) { + _installStreamController(onListen: _onFrameStreamListen); + return _frameStreamController!.stream; + } + + StreamController _installStreamController( + {Function()? onListen}) { + _frameStreamController = StreamController( + onListen: onListen ?? () {}, + onPause: _onFrameStreamPauseResume, + onResume: _onFrameStreamPauseResume, + onCancel: _onFrameStreamCancel, + ); + return _frameStreamController!; + } + + void _onFrameStreamListen() { + _startPlatformStream(); + } + + Future _startPlatformStream() async { + await _channel.invokeMethod('startImageStream'); + _startStreamListener(); + } + + void _startStreamListener() { + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _platformImageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { + _frameStreamController! + .add(cameraImageFromPlatformData(imageData as Map)); + }); + } + + FutureOr _onFrameStreamCancel() async { + await _channel.invokeMethod('stopImageStream'); + await _platformImageStreamSubscription?.cancel(); + _platformImageStreamSubscription = null; + _frameStreamController = null; + } + + void _onFrameStreamPauseResume() { + throw CameraException('InvalidCall', + 'Pause and resume are not supported for onStreamedFrameAvailable'); + } + + @override + Future setFlashMode(int cameraId, FlashMode mode) => + _channel.invokeMethod( + 'setFlashMode', + { + 'cameraId': cameraId, + 'mode': _serializeFlashMode(mode), + }, + ); + + @override + Future setExposureMode(int cameraId, ExposureMode mode) => + _channel.invokeMethod( + 'setExposureMode', + { + 'cameraId': cameraId, + 'mode': serializeExposureMode(mode), + }, + ); + + @override + Future setExposurePoint(int cameraId, Point? point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + return _channel.invokeMethod( + 'setExposurePoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMinExposureOffset(int cameraId) async { + final double? minExposureOffset = await _channel.invokeMethod( + 'getMinExposureOffset', + {'cameraId': cameraId}, + ); + + return minExposureOffset!; + } + + @override + Future getMaxExposureOffset(int cameraId) async { + final double? maxExposureOffset = await _channel.invokeMethod( + 'getMaxExposureOffset', + {'cameraId': cameraId}, + ); + + return maxExposureOffset!; + } + + @override + Future getExposureOffsetStepSize(int cameraId) async { + final double? stepSize = await _channel.invokeMethod( + 'getExposureOffsetStepSize', + {'cameraId': cameraId}, + ); + + return stepSize!; + } + + @override + Future setExposureOffset(int cameraId, double offset) async { + final double? appliedOffset = await _channel.invokeMethod( + 'setExposureOffset', + { + 'cameraId': cameraId, + 'offset': offset, + }, + ); + + return appliedOffset!; + } + + @override + Future setFocusMode(int cameraId, FocusMode mode) => + _channel.invokeMethod( + 'setFocusMode', + { + 'cameraId': cameraId, + 'mode': serializeFocusMode(mode), + }, + ); + + @override + Future setFocusPoint(int cameraId, Point? point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + return _channel.invokeMethod( + 'setFocusPoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMaxZoomLevel(int cameraId) async { + final double? maxZoomLevel = await _channel.invokeMethod( + 'getMaxZoomLevel', + {'cameraId': cameraId}, + ); + + return maxZoomLevel!; + } + + @override + Future getMinZoomLevel(int cameraId) async { + final double? minZoomLevel = await _channel.invokeMethod( + 'getMinZoomLevel', + {'cameraId': cameraId}, + ); + + return minZoomLevel!; + } + + @override + Future setZoomLevel(int cameraId, double zoom) async { + try { + await _channel.invokeMethod( + 'setZoomLevel', + { + 'cameraId': cameraId, + 'zoom': zoom, + }, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future pausePreview(int cameraId) async { + await _channel.invokeMethod( + 'pausePreview', + {'cameraId': cameraId}, + ); + } + + @override + Future resumePreview(int cameraId) async { + await _channel.invokeMethod( + 'resumePreview', + {'cameraId': cameraId}, + ); + } + + @override + Future setDescriptionWhileRecording( + CameraDescription description) async { + await _channel.invokeMethod( + 'setDescriptionWhileRecording', + { + 'cameraName': description.name, + }, + ); + } + + @override + Widget buildPreview(int cameraId) { + return Texture(textureId: cameraId); + } + + /// Returns the flash mode as a String. + String _serializeFlashMode(FlashMode flashMode) { + switch (flashMode) { + case FlashMode.off: + return 'off'; + case FlashMode.auto: + return 'auto'; + case FlashMode.always: + return 'always'; + case FlashMode.torch: + return 'torch'; + } + // 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 'off'; + } + + /// Returns the resolution preset as a String. + String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + switch (resolutionPreset) { + 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'; + } + // 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 'max'; + } + + /// Converts messages received from the native platform into device events. + Future _handleDeviceMethodCall(MethodCall call) async { + switch (call.method) { + case 'orientation_changed': + final Map arguments = _getArgumentDictionary(call); + _deviceEventStreamController.add(DeviceOrientationChangedEvent( + deserializeDeviceOrientation(arguments['orientation']! as String))); + break; + default: + throw MissingPluginException(); + } + } + + /// Converts messages received from the native platform into camera events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + Future handleCameraMethodCall(MethodCall call, int cameraId) async { + switch (call.method) { + case 'initialized': + final Map arguments = _getArgumentDictionary(call); + cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + arguments['previewWidth']! as double, + arguments['previewHeight']! as double, + deserializeExposureMode(arguments['exposureMode']! as String), + arguments['exposurePointSupported']! as bool, + deserializeFocusMode(arguments['focusMode']! as String), + arguments['focusPointSupported']! as bool, + )); + break; + case 'resolution_changed': + final Map arguments = _getArgumentDictionary(call); + cameraEventStreamController.add(CameraResolutionChangedEvent( + cameraId, + arguments['captureWidth']! as double, + arguments['captureHeight']! as double, + )); + break; + case 'camera_closing': + cameraEventStreamController.add(CameraClosingEvent( + cameraId, + )); + break; + case 'video_recorded': + final Map arguments = _getArgumentDictionary(call); + cameraEventStreamController.add(VideoRecordedEvent( + cameraId, + XFile(arguments['path']! as String), + arguments['maxVideoDuration'] != null + ? Duration(milliseconds: arguments['maxVideoDuration']! as int) + : null, + )); + break; + case 'error': + final Map arguments = _getArgumentDictionary(call); + cameraEventStreamController.add(CameraErrorEvent( + cameraId, + arguments['description']! as String, + )); + break; + default: + throw MissingPluginException(); + } + } + + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } +} diff --git a/packages/camera/lib/src/type_conversion.dart b/packages/camera/lib/src/type_conversion.dart new file mode 100644 index 0000000..b4f0e42 --- /dev/null +++ b/packages/camera/lib/src/type_conversion.dart @@ -0,0 +1,49 @@ +// 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 'dart:typed_data'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; + +/// Converts method channel call [data] for `receivedImageStreamData` to a +/// [CameraImageData]. +CameraImageData cameraImageFromPlatformData(Map data) { + return CameraImageData( + format: _cameraImageFormatFromPlatformData(data['format']), + height: data['height'] as int, + width: data['width'] as int, + lensAperture: data['lensAperture'] as double?, + sensorExposureTime: data['sensorExposureTime'] as int?, + sensorSensitivity: data['sensorSensitivity'] as double?, + planes: List.unmodifiable( + (data['planes'] as List).map( + (dynamic planeData) => _cameraImagePlaneFromPlatformData( + planeData as Map)))); +} + +CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { + return CameraImageFormat(_imageFormatGroupFromPlatformData(data), raw: data); +} + +ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { + switch (data) { + case 35: // android.graphics.ImageFormat.YUV_420_888 + return ImageFormatGroup.yuv420; + case 256: // android.graphics.ImageFormat.JPEG + return ImageFormatGroup.jpeg; + case 17: // android.graphics.ImageFormat.NV21 + return ImageFormatGroup.nv21; + } + + return ImageFormatGroup.unknown; +} + +CameraImagePlane _cameraImagePlaneFromPlatformData(Map data) { + return CameraImagePlane( + bytes: data['bytes'] as Uint8List, + bytesPerPixel: data['bytesPerPixel'] as int?, + bytesPerRow: data['bytesPerRow'] as int, + height: data['height'] as int?, + width: data['width'] as int?); +} diff --git a/packages/camera/lib/src/utils.dart b/packages/camera/lib/src/utils.dart new file mode 100644 index 0000000..8d58f7f --- /dev/null +++ b/packages/camera/lib/src/utils.dart @@ -0,0 +1,56 @@ +// 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 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/services.dart'; + +/// Parses a string into a corresponding CameraLensDirection. +CameraLensDirection parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); +} + +/// Returns the device orientation as a String. +String serializeDeviceOrientation(DeviceOrientation orientation) { + switch (orientation) { + case DeviceOrientation.portraitUp: + return 'portraitUp'; + case DeviceOrientation.portraitDown: + return 'portraitDown'; + case DeviceOrientation.landscapeRight: + return 'landscapeRight'; + case DeviceOrientation.landscapeLeft: + return 'landscapeLeft'; + } + // 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 'portraitUp'; +} + +/// Returns the device orientation for a given String. +DeviceOrientation deserializeDeviceOrientation(String str) { + switch (str) { + case 'portraitUp': + return DeviceOrientation.portraitUp; + case 'portraitDown': + return DeviceOrientation.portraitDown; + case 'landscapeRight': + return DeviceOrientation.landscapeRight; + case 'landscapeLeft': + return DeviceOrientation.landscapeLeft; + default: + throw ArgumentError('"$str" is not a valid DeviceOrientation value'); + } +} diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index a99c52b..704ae59 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_elinux description: A Flutter plugin for getting information about and controlling the camera on eLinux. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.3.1 +version: 0.3.2 homepage: https://github.com/sony/flutter-elinux-plugins repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/camera @@ -11,6 +11,7 @@ environment: flutter: ">=3.3.0" dependencies: + camera_platform_interface: ^2.5.0 flutter: sdk: flutter @@ -19,3 +20,4 @@ flutter: platforms: elinux: pluginClass: CameraElinuxPlugin + dartPluginClass: ELinuxCamera