blob: 7d72601b54d1190dea090a5f9018ee3bc7a11481 [file] [log] [blame]
// 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/services.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';
import 'messages.g.dart';
import 'type_conversion.dart';
import 'utils.dart';
/// The Android implementation of [CameraPlatform] that uses method channels.
class AndroidCamera extends CameraPlatform {
/// Creates a new [CameraPlatform] instance.
AndroidCamera({@visibleForTesting CameraApi? hostApi})
: _hostApi = hostApi ?? CameraApi();
/// Registers this class as the default instance of [CameraPlatform].
static void registerWith() {
CameraPlatform.instance = AndroidCamera();
}
final CameraApi _hostApi;
/// The name of the channel that device events from the platform side are
/// sent on.
@visibleForTesting
static const String deviceEventChannelName =
'plugins.flutter.io/camera_android/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<CameraEvent> cameraEventStreamController =
StreamController<CameraEvent>.broadcast();
/// Handler for device-level callbacks from the native side.
@visibleForTesting
late final HostDeviceMessageHandler hostHandler = HostDeviceMessageHandler();
/// Map of camera IDs to camera-level callback handlers listening to their
/// respective platform channels.
@visibleForTesting
final Map<int, HostCameraMessageHandler> hostCameraHandlers =
<int, HostCameraMessageHandler>{};
// The stream to receive frames from the native code.
StreamSubscription<dynamic>? _platformImageStreamSubscription;
// The stream for vending frames to platform interface clients.
StreamController<CameraImageData>? _frameStreamController;
Stream<CameraEvent> _cameraEvents(int cameraId) => cameraEventStreamController
.stream
.where((CameraEvent event) => event.cameraId == cameraId);
@override
Future<List<CameraDescription>> availableCameras() async {
try {
final List<PlatformCameraDescription> cameraDescriptions = await _hostApi
.getAvailableCameras();
return cameraDescriptions.map((
PlatformCameraDescription cameraDescription,
) {
return CameraDescription(
name: cameraDescription.name,
lensDirection: cameraLensDirectionFromPlatform(
cameraDescription.lensDirection,
),
sensorOrientation: cameraDescription.sensorOrientation,
);
}).toList();
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}
@override
Future<int> createCamera(
CameraDescription cameraDescription,
ResolutionPreset? resolutionPreset, {
bool enableAudio = false,
}) => createCameraWithSettings(
cameraDescription,
MediaSettings(resolutionPreset: resolutionPreset, enableAudio: enableAudio),
);
@override
Future<int> createCameraWithSettings(
CameraDescription cameraDescription,
MediaSettings? mediaSettings,
) async {
try {
return await _hostApi.create(
cameraDescription.name,
mediaSettingsToPlatform(mediaSettings),
);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}
@override
Future<void> initializeCamera(
int cameraId, {
ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown,
}) async {
hostCameraHandlers.putIfAbsent(
cameraId,
() => HostCameraMessageHandler(cameraId, cameraEventStreamController),
);
final Completer<void> completer = Completer<void>();
unawaited(
onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) {
completer.complete();
}),
);
try {
await _hostApi.initialize(imageFormatGroupToPlatform(imageFormatGroup));
} on PlatformException catch (e, s) {
completer.completeError(CameraException(e.code, e.message), s);
}
return completer.future;
}
@override
Future<void> dispose(int cameraId) async {
final HostCameraMessageHandler? handler = hostCameraHandlers.remove(
cameraId,
);
handler?.dispose();
await _hostApi.dispose();
}
@override
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) {
return _cameraEvents(cameraId).whereType<CameraInitializedEvent>();
}
@override
Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
return _cameraEvents(cameraId).whereType<CameraResolutionChangedEvent>();
}
@override
Stream<CameraClosingEvent> onCameraClosing(int cameraId) {
return _cameraEvents(cameraId).whereType<CameraClosingEvent>();
}
@override
Stream<CameraErrorEvent> onCameraError(int cameraId) {
return _cameraEvents(cameraId).whereType<CameraErrorEvent>();
}
@override
Stream<VideoRecordedEvent> onVideoRecordedEvent(int cameraId) {
return _cameraEvents(cameraId).whereType<VideoRecordedEvent>();
}
@override
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
return hostHandler.deviceEventStreamController.stream
.whereType<DeviceOrientationChangedEvent>();
}
@override
Future<void> lockCaptureOrientation(
int cameraId,
DeviceOrientation orientation,
) async {
await _hostApi.lockCaptureOrientation(
deviceOrientationToPlatform(orientation),
);
}
@override
Future<void> unlockCaptureOrientation(int cameraId) async {
await _hostApi.unlockCaptureOrientation();
}
@override
Future<XFile> takePicture(int cameraId) async {
final String path = await _hostApi.takePicture();
return XFile(path);
}
// This optimization is unnecessary on Android.
@override
Future<void> prepareForVideoRecording() async {}
@override
Future<void> startVideoRecording(
int cameraId, {
Duration? maxVideoDuration,
}) async {
// Ignore maxVideoDuration, as it is unimplemented and deprecated.
return startVideoCapturing(VideoCaptureOptions(cameraId));
}
@override
Future<void> startVideoCapturing(VideoCaptureOptions options) async {
await _hostApi.startVideoRecording(options.streamCallback != null);
if (options.streamCallback != null) {
_installStreamController().stream.listen(options.streamCallback);
_startStreamListener();
}
}
@override
Future<XFile> stopVideoRecording(int cameraId) async {
final String path = await _hostApi.stopVideoRecording();
return XFile(path);
}
@override
Future<void> pauseVideoRecording(int cameraId) =>
_hostApi.pauseVideoRecording();
@override
Future<void> resumeVideoRecording(int cameraId) =>
_hostApi.resumeVideoRecording();
@override
bool supportsImageStreaming() => true;
@override
Stream<CameraImageData> onStreamedFrameAvailable(
int cameraId, {
CameraImageStreamOptions? options,
}) {
_installStreamController(onListen: _onFrameStreamListen);
return _frameStreamController!.stream;
}
StreamController<CameraImageData> _installStreamController({
void Function()? onListen,
}) {
_frameStreamController = StreamController<CameraImageData>(
onListen: onListen ?? () {},
onPause: _onFrameStreamPauseResume,
onResume: _onFrameStreamPauseResume,
onCancel: _onFrameStreamCancel,
);
return _frameStreamController!;
}
void _onFrameStreamListen() {
_startPlatformStream();
}
Future<void> _startPlatformStream() async {
await _hostApi.startImageStream();
_startStreamListener();
}
void _startStreamListener() {
const EventChannel cameraEventChannel = EventChannel(
'plugins.flutter.io/camera_android/imageStream',
);
_platformImageStreamSubscription = cameraEventChannel
.receiveBroadcastStream()
.listen((dynamic imageData) {
_frameStreamController!.add(
cameraImageFromPlatformData(imageData as Map<dynamic, dynamic>),
);
});
}
FutureOr<void> _onFrameStreamCancel() async {
await _hostApi.stopImageStream();
await _platformImageStreamSubscription?.cancel();
_platformImageStreamSubscription = null;
_frameStreamController = null;
}
void _onFrameStreamPauseResume() {
throw CameraException(
'InvalidCall',
'Pause and resume are not supported for onStreamedFrameAvailable',
);
}
@override
Future<void> setFlashMode(int cameraId, FlashMode mode) =>
_hostApi.setFlashMode(flashModeToPlatform(mode));
@override
Future<void> setExposureMode(int cameraId, ExposureMode mode) =>
_hostApi.setExposureMode(exposureModeToPlatform(mode));
@override
Future<void> setExposurePoint(int cameraId, Point<double>? point) {
assert(point == null || point.x >= 0 && point.x <= 1);
assert(point == null || point.y >= 0 && point.y <= 1);
return _hostApi.setExposurePoint(pointToPlatform(point));
}
@override
Future<double> getMinExposureOffset(int cameraId) async {
return _hostApi.getMinExposureOffset();
}
@override
Future<double> getMaxExposureOffset(int cameraId) async {
return _hostApi.getMaxExposureOffset();
}
@override
Future<double> getExposureOffsetStepSize(int cameraId) async {
return _hostApi.getExposureOffsetStepSize();
}
@override
Future<double> setExposureOffset(int cameraId, double offset) async {
return _hostApi.setExposureOffset(offset);
}
@override
Future<void> setFocusMode(int cameraId, FocusMode mode) =>
_hostApi.setFocusMode(focusModeToPlatform(mode));
@override
Future<void> setFocusPoint(int cameraId, Point<double>? point) {
assert(point == null || point.x >= 0 && point.x <= 1);
assert(point == null || point.y >= 0 && point.y <= 1);
return _hostApi.setFocusPoint(pointToPlatform(point));
}
@override
Future<double> getMaxZoomLevel(int cameraId) async {
return _hostApi.getMaxZoomLevel();
}
@override
Future<double> getMinZoomLevel(int cameraId) async {
return _hostApi.getMinZoomLevel();
}
@override
Future<void> setZoomLevel(int cameraId, double zoom) async {
try {
await _hostApi.setZoomLevel(zoom);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}
@override
Future<void> pausePreview(int cameraId) async {
await _hostApi.pausePreview();
}
@override
Future<void> resumePreview(int cameraId) async {
await _hostApi.resumePreview();
}
@override
Future<void> setDescriptionWhileRecording(
CameraDescription description,
) async {
await _hostApi.setDescriptionWhileRecording(description.name);
}
@override
Widget buildPreview(int cameraId) {
return Texture(textureId: cameraId);
}
}
/// Handles callbacks from the platform host that are not camera-specific.
@visibleForTesting
class HostDeviceMessageHandler implements CameraGlobalEventApi {
/// Creates a new handler and registers it to listen to the global event platform channel.
HostDeviceMessageHandler() {
CameraGlobalEventApi.setUp(this);
}
/// The controller that broadcasts device events coming from the host platform.
final StreamController<DeviceEvent> deviceEventStreamController =
StreamController<DeviceEvent>.broadcast();
@override
void deviceOrientationChanged(PlatformDeviceOrientation orientation) {
deviceEventStreamController.add(
DeviceOrientationChangedEvent(deviceOrientationFromPlatform(orientation)),
);
}
}
/// Handles camera-specific callbacks from the platform host.
@visibleForTesting
class HostCameraMessageHandler implements CameraEventApi {
/// Creates a new handler and registers it to listen to its camera's platform channel.
HostCameraMessageHandler(this.cameraId, this.cameraEventStreamController) {
CameraEventApi.setUp(this, messageChannelSuffix: '$cameraId');
}
/// Removes this handler from its platform channel.
void dispose() {
CameraEventApi.setUp(null, messageChannelSuffix: '$cameraId');
}
/// The ID of the camera for which this handler listens for events.
final int cameraId;
/// The controller which broadcasts camera events from the host platform.
final StreamController<CameraEvent> cameraEventStreamController;
@override
void error(String message) {
cameraEventStreamController.add(CameraErrorEvent(cameraId, message));
}
@override
void initialized(PlatformCameraState initialState) {
cameraEventStreamController.add(
CameraInitializedEvent(
cameraId,
initialState.previewSize.width,
initialState.previewSize.height,
exposureModeFromPlatform(initialState.exposureMode),
initialState.exposurePointSupported,
focusModeFromPlatform(initialState.focusMode),
initialState.focusPointSupported,
),
);
}
@override
void closed() {
cameraEventStreamController.add(CameraClosingEvent(cameraId));
}
}