Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 8 additions & 0 deletions packages/camera/lib/new/camera.dart
Original file line number Diff line number Diff line change
@@ -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';
110 changes: 110 additions & 0 deletions packages/camera/lib/new/src/camera_controller.dart
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying!

Discussed offline, I'm thinking this is kind of a foot gun. Sounds like the current plugin behavior is to guard against this by disposing of multiple instances under the hood. I think we should probably maintain it here, but that doesn't need to block this PR.

/// 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<List<CameraDescription>> availableCameras() async {
throw UnimplementedError('$defaultTargetPlatform not supported');
}

/// Begins the flow of data between the inputs and outputs connected the camera instance.
Future<void> start() => configurator.start();

/// Stops the flow of data between the inputs and outputs connected the camera instance.
Future<void> stop() => configurator.stop();

/// Deallocate all resources and disables further use of the controller.
Future<void> 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',
);
}
}
17 changes: 17 additions & 0 deletions packages/camera/lib/new/src/camera_testing.dart
Original file line number Diff line number Diff line change
@@ -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;
}
38 changes: 38 additions & 0 deletions packages/camera/lib/new/src/common/camera_channel.dart
Original file line number Diff line number Diff line change
@@ -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<int, dynamic> callbacks = <int, CameraCallback>{};

static final MethodChannel channel = const 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);
}
}
54 changes: 54 additions & 0 deletions packages/camera/lib/new/src/common/camera_interface.dart
Original file line number Diff line number Diff line change
@@ -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<void> start();

/// Stops the flow of data between the inputs and outputs connected the camera instance.
Future<void> stop();

/// Dispose all resources and disables further use of this configurator.
Future<void> dispose();

/// Retrieves a valid texture Id to be used with a [Texture] widget.
Future<int> addPreviewTexture();
}
19 changes: 19 additions & 0 deletions packages/camera/lib/new/src/common/camera_mixins.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> asMap();
}
59 changes: 59 additions & 0 deletions packages/camera/lib/new/src/common/native_texture.dart
Original file line number Diff line number Diff line change
@@ -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<NativeTexture> allocate() async {
final int handle = CameraChannel.nextHandle++;

final int textureId = await CameraChannel.channel.invokeMethod<int>(
'$NativeTexture#allocate',
<String, dynamic>{'textureHandle': handle},
);

return NativeTexture._(handle: handle, textureId: textureId);
}

/// Deallocate this texture.
Future<void> release() {
if (_isClosed) return Future<void>.value();

_isClosed = true;
return CameraChannel.channel.invokeMethod<void>(
'$NativeTexture#release',
<String, dynamic>{'handle': _handle},
);
}

@override
Map<String, dynamic> asMap() {
return <String, dynamic>{'handle': _handle};
}
}
3 changes: 3 additions & 0 deletions packages/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
- Luigi Agosti <[email protected]>
Expand All @@ -17,6 +18,8 @@ dependencies:
sdk: flutter

dev_dependencies:
flutter_test:
sdk: flutter
path_provider: ^0.5.0
video_player: ^0.10.0

Expand Down
50 changes: 50 additions & 0 deletions packages/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
@@ -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<MethodCall> log = <MethodCall>[];

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, <Matcher>[
isMethodCall(
'$NativeTexture#allocate',
arguments: <String, dynamic>{'textureHandle': 0},
)
]);
});
});
});
}