From b6cd154350bb28a627d0d130a123d38b4ffde20e Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Jul 2019 14:27:11 -0700 Subject: [PATCH 1/5] Dart interface --- packages/camera/lib/new/camera.dart | 8 ++ .../camera/lib/new/src/camera_controller.dart | 110 ++++++++++++++++++ .../camera/lib/new/src/camera_testing.dart | 17 +++ .../lib/new/src/common/camera_channel.dart | 38 ++++++ .../lib/new/src/common/camera_interface.dart | 54 +++++++++ .../lib/new/src/common/camera_mixins.dart | 19 +++ .../lib/new/src/common/native_texture.dart | 59 ++++++++++ packages/camera/pubspec.yaml | 2 + packages/camera/test/camera_test.dart | 50 ++++++++ 9 files changed, 357 insertions(+) create mode 100644 packages/camera/lib/new/camera.dart create mode 100644 packages/camera/lib/new/src/camera_controller.dart create mode 100644 packages/camera/lib/new/src/camera_testing.dart create mode 100644 packages/camera/lib/new/src/common/camera_channel.dart create mode 100644 packages/camera/lib/new/src/common/camera_interface.dart create mode 100644 packages/camera/lib/new/src/common/camera_mixins.dart create mode 100644 packages/camera/lib/new/src/common/native_texture.dart create mode 100644 packages/camera/test/camera_test.dart diff --git a/packages/camera/lib/new/camera.dart b/packages/camera/lib/new/camera.dart new file mode 100644 index 000000000000..ab135079d2dd --- /dev/null +++ b/packages/camera/lib/new/camera.dart @@ -0,0 +1,8 @@ +// Copyright 2019 The Chromium 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/camera_controller.dart'; +export 'src/camera_testing.dart'; +export 'src/common/camera_interface.dart'; +export 'src/common/native_texture.dart'; diff --git a/packages/camera/lib/new/src/camera_controller.dart b/packages/camera/lib/new/src/camera_controller.dart new file mode 100644 index 000000000000..cd07dc50d243 --- /dev/null +++ b/packages/camera/lib/new/src/camera_controller.dart @@ -0,0 +1,110 @@ +// Copyright 2019 The Chromium 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 'package:flutter/foundation.dart'; + +import 'common/camera_interface.dart'; + +/// Controls a device camera. +/// +/// Use [CameraController.availableCameras] to get a list of available cameras. +/// +/// This class is used as a simple interface that works for Android and iOS. +/// +/// When using iOS, simultaneously calling [start] on two [CameraController]s +/// will throw a [PlatformException]. +/// +/// When using Android, simultaneously calling [start] on two +/// [CameraController]s may throw a [PlatformException] depending on the +/// hardware resources of the device. +class CameraController { + /// Default constructor. + /// + /// Use [CameraController.availableCameras] to get a list of available + /// cameras. + /// + /// This will choose the best [CameraConfigurator] for the current device. + factory CameraController({@required CameraDescription description}) { + assert(description != null); + return CameraController._( + description: description, + configurator: _createDefaultConfigurator(description), + api: _getCameraApi(description), + ); + } + + CameraController._({ + @required this.description, + @required this.configurator, + @required this.api, + }) : assert(description != null), + assert(configurator != null), + assert(api != null); + + /// Constructor for defining your own [CameraConfigurator]. + /// + /// Use [CameraController.availableCameras] to get a list of available + /// cameras. + factory CameraController.customConfigurator({ + @required CameraDescription description, + @required CameraConfigurator configurator, + }) { + return CameraController._( + description: description, + configurator: configurator, + api: _getCameraApi(description), + ); + } + + /// Details for the camera this controller accesses. + final CameraDescription description; + + /// Configurator used to control the camera. + final CameraConfigurator configurator; + + /// Api used by the [configurator]. + final CameraApi api; + + /// Retrieves a list of available cameras for the current device. + /// + /// This will choose the best [CameraAPI] for the current device. + static Future> availableCameras() async { + throw UnimplementedError('$defaultTargetPlatform not supported'); + } + + /// Begins the flow of data between the inputs and outputs connected the camera instance. + Future start() => configurator.start(); + + /// Stops the flow of data between the inputs and outputs connected the camera instance. + Future stop() => configurator.stop(); + + /// Deallocate all resources and disables further use of the controller. + Future dispose() => configurator.dispose(); + + static CameraConfigurator _createDefaultConfigurator( + CameraDescription description, + ) { + final CameraApi api = _getCameraApi(description); + switch (api) { + case CameraApi.android: + throw UnimplementedError(); + case CameraApi.iOS: + throw UnimplementedError(); + case CameraApi.supportAndroid: + throw UnimplementedError(); + } + + return null; + } + + static CameraApi _getCameraApi(CameraDescription description) { + throw ArgumentError.value( + description.runtimeType, + 'description.runtimeType', + 'Failed to get $CameraApi from', + ); + } +} diff --git a/packages/camera/lib/new/src/camera_testing.dart b/packages/camera/lib/new/src/camera_testing.dart new file mode 100644 index 000000000000..8022216ff8c8 --- /dev/null +++ b/packages/camera/lib/new/src/camera_testing.dart @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'common/camera_channel.dart'; + +@visibleForTesting +class CameraTesting { + CameraTesting._(); + + static final MethodChannel channel = CameraChannel.channel; + static int get nextHandle => CameraChannel.nextHandle; + static set nextHandle(int handle) => CameraChannel.nextHandle = handle; +} diff --git a/packages/camera/lib/new/src/common/camera_channel.dart b/packages/camera/lib/new/src/common/camera_channel.dart new file mode 100644 index 000000000000..0d090c1e2f81 --- /dev/null +++ b/packages/camera/lib/new/src/common/camera_channel.dart @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium 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:flutter/services.dart'; + +typedef CameraCallback = void Function(dynamic result); + +// Non exported class +class CameraChannel { + static final Map callbacks = {}; + + static final MethodChannel channel = MethodChannel( + 'flutter.plugins.io/camera', + )..setMethodCallHandler( + (MethodCall call) async { + assert(call.method == 'handleCallback'); + + final int handle = call.arguments['handle']; + if (callbacks[handle] != null) callbacks[handle](call.arguments); + }, + ); + + static int nextHandle = 0; + + static void registerCallback(int handle, CameraCallback callback) { + assert(handle != null); + assert(CameraCallback != null); + + assert(!callbacks.containsKey(handle)); + callbacks[handle] = callback; + } + + static void unregisterCallback(int handle) { + assert(handle != null); + callbacks.remove(handle); + } +} diff --git a/packages/camera/lib/new/src/common/camera_interface.dart b/packages/camera/lib/new/src/common/camera_interface.dart new file mode 100644 index 000000000000..8b5ca9d4e4a3 --- /dev/null +++ b/packages/camera/lib/new/src/common/camera_interface.dart @@ -0,0 +1,54 @@ +// Copyright 2019 The Chromium 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'; + +/// Available APIs compatible with [CameraController]. +enum CameraApi { + /// [Camera2](https://developer.android.com/reference/android/hardware/camera2/package-summary) + android, + + /// [AVFoundation](https://developer.apple.com/av-foundation/) + iOS, + + /// [Camera](https://developer.android.com/reference/android/hardware/Camera) + supportAndroid, +} + +/// Location of the camera on the device. +enum LensDirection { front, back, unknown } + +/// Abstract class used to create a common interface to describe a camera from different platform APIs. +/// +/// This provides information such as the [name] of the camera and [direction] +/// the lens face. +abstract class CameraDescription { + /// Location of the camera on the device. + LensDirection get direction; + + /// Identifier for this camera. + String get name; +} + +/// Abstract class used to create a common interface across platform APIs. +abstract class CameraConfigurator { + /// Texture id that can be used to send camera frames to a [Texture] widget. + /// + /// You must call [addPreviewTexture] first or this will only return null. + int get previewTextureId; + + /// Begins the flow of data between the inputs and outputs connected the camera instance. + /// + /// This will start updating the texture with id: [previewTextureId]. + Future start(); + + /// Stops the flow of data between the inputs and outputs connected the camera instance. + Future stop(); + + /// Dispose all resources and disables further use of this configurator. + Future dispose(); + + /// Retrieves a valid texture Id to be used with a [Texture] widget. + Future addPreviewTexture(); +} diff --git a/packages/camera/lib/new/src/common/camera_mixins.dart b/packages/camera/lib/new/src/common/camera_mixins.dart new file mode 100644 index 000000000000..bb27e4881d1f --- /dev/null +++ b/packages/camera/lib/new/src/common/camera_mixins.dart @@ -0,0 +1,19 @@ +// Copyright 2019 The Chromium 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_channel.dart'; + +mixin NativeMethodCallHandler { + /// Identifier for an object on the native side of the plugin. + /// + /// Only used internally and for debugging. + final int handle = CameraChannel.nextHandle++; +} + +mixin CameraMappable { + /// Creates a description of the object compatible with [PlatformChannel]s. + /// + /// Only used as an internal method and for debugging. + Map asMap(); +} diff --git a/packages/camera/lib/new/src/common/native_texture.dart b/packages/camera/lib/new/src/common/native_texture.dart new file mode 100644 index 000000000000..1deb7e3a10b6 --- /dev/null +++ b/packages/camera/lib/new/src/common/native_texture.dart @@ -0,0 +1,59 @@ +// Copyright 2019 The Chromium 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 'package:flutter/foundation.dart'; + +import 'camera_channel.dart'; +import 'camera_mixins.dart'; + +/// Used to allocate a buffer for displaying a preview camera texture. +/// +/// This is used to for a developer to have a control over the +/// `TextureRegistry.SurfaceTextureEntry` (Android) and FlutterTexture (iOS). +/// This gives direct access to the textureId and can be reused with separate +/// camera instances. +/// +/// The [textureId] can be passed to a [Texture] widget. +class NativeTexture with CameraMappable { + NativeTexture._({@required int handle, @required this.textureId}) + : _handle = handle, + assert(handle != null), + assert(textureId != null); + + final int _handle; + + bool _isClosed = false; + + /// Id that can be passed to a [Texture] widget. + final int textureId; + + static Future allocate() async { + final int handle = CameraChannel.nextHandle++; + + final int textureId = await CameraChannel.channel.invokeMethod( + '$NativeTexture#allocate', + {'textureHandle': handle}, + ); + + return NativeTexture._(handle: handle, textureId: textureId); + } + + /// Deallocate this texture. + Future release() { + if (_isClosed) return Future.value(); + + _isClosed = true; + return CameraChannel.channel.invokeMethod( + '$NativeTexture#release', + {'handle': _handle}, + ); + } + + @override + Map asMap() { + return {'handle': _handle}; + } +} diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 81a0e8f877a4..db465d4e1735 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -17,6 +17,8 @@ dependencies: sdk: flutter dev_dependencies: + flutter_test: + sdk: flutter path_provider: ^0.5.0 video_player: ^0.10.0 diff --git a/packages/camera/test/camera_test.dart b/packages/camera/test/camera_test.dart new file mode 100644 index 000000000000..ae6c38f9b1f9 --- /dev/null +++ b/packages/camera/test/camera_test.dart @@ -0,0 +1,50 @@ +// Copyright 2019 The Chromium 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:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:camera/new/src/camera_testing.dart'; +import 'package:camera/new/src/common/native_texture.dart'; + +void main() { + group('Camera', () { + final List log = []; + + setUpAll(() { + CameraTesting.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'NativeTexture#allocate': + return 15; + } + + throw ArgumentError.value( + methodCall.method, + 'methodCall.method', + 'No method found for', + ); + }); + }); + + setUp(() { + log.clear(); + CameraTesting.nextHandle = 0; + }); + + group('$NativeTexture', () { + test('allocate', () async { + final NativeTexture texture = await NativeTexture.allocate(); + + expect(texture.textureId, 15); + expect(log, [ + isMethodCall( + '$NativeTexture#allocate', + arguments: {'textureHandle': 0}, + ) + ]); + }); + }); + }); +} From bf80b44719782c959946ec2c05c0c45e8a547f24 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Jul 2019 14:43:33 -0700 Subject: [PATCH 2/5] Const --- packages/camera/lib/new/src/common/camera_channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/lib/new/src/common/camera_channel.dart b/packages/camera/lib/new/src/common/camera_channel.dart index 0d090c1e2f81..12036b85be74 100644 --- a/packages/camera/lib/new/src/common/camera_channel.dart +++ b/packages/camera/lib/new/src/common/camera_channel.dart @@ -10,7 +10,7 @@ typedef CameraCallback = void Function(dynamic result); class CameraChannel { static final Map callbacks = {}; - static final MethodChannel channel = MethodChannel( + static final MethodChannel channel = const MethodChannel( 'flutter.plugins.io/camera', )..setMethodCallHandler( (MethodCall call) async { From fe17c93070c4a8b838a90716dad356a6b404135f Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Jul 2019 14:44:15 -0700 Subject: [PATCH 3/5] publish_to --- packages/camera/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index db465d4e1735..9aab9b96633a 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -3,6 +3,7 @@ description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. version: 0.5.2+1 +publish_to: none authors: - Flutter Team - Luigi Agosti From 5fc0c1cdf1f021b16687f04dd896b897b3b90ab0 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Jul 2019 15:18:32 -0700 Subject: [PATCH 4/5] Added comment --- packages/camera/lib/new/src/camera_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/lib/new/src/camera_controller.dart b/packages/camera/lib/new/src/camera_controller.dart index cd07dc50d243..4ae6b472a3a7 100644 --- a/packages/camera/lib/new/src/camera_controller.dart +++ b/packages/camera/lib/new/src/camera_controller.dart @@ -97,7 +97,7 @@ class CameraController { throw UnimplementedError(); } - return null; + return null; // Unreachable code } static CameraApi _getCameraApi(CameraDescription description) { From f0be2215ad3210e8b5f5b0db58bc2e2dc1762565 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Jul 2019 15:27:40 -0700 Subject: [PATCH 5/5] Add under development note --- packages/camera/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/camera/README.md b/packages/camera/README.md index e0d66ef4cb34..4c23236cd0aa 100644 --- a/packages/camera/README.md +++ b/packages/camera/README.md @@ -4,6 +4,8 @@ A Flutter plugin for iOS and Android allowing access to the device cameras. +*Note*: This plugin is still under development, and some APIs might not be available yet. We are working on a refactor which can be followed here: [issue](https://github.com/flutter/flutter/issues/31225) + ## Features: * Display live camera preview in a widget.