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 'src/messages.g.dart';
class CameraWindows extends CameraPlatform {
CameraWindows({@visibleForTesting CameraApi? api})
: _hostApi = api ?? CameraApi();
static void registerWith() {
CameraPlatform.instance = CameraWindows();
}
final CameraApi _hostApi;
@visibleForTesting
final Map<int, HostCameraMessageHandler> hostCameraHandlers =
<int, HostCameraMessageHandler>{};
@visibleForTesting
final StreamController<CameraEvent> cameraEventStreamController =
StreamController<CameraEvent>.broadcast();
Stream<CameraEvent> _cameraEvents(int cameraId) => cameraEventStreamController
.stream
.where((CameraEvent event) => event.cameraId == cameraId);
@override
Future<List<CameraDescription>> availableCameras() async {
try {
final List<String> cameras = await _hostApi.getAvailableCameras();
return cameras.map((String cameraName) {
return CameraDescription(
name: cameraName,
lensDirection: CameraLensDirection.front,
sensorOrientation: 0,
);
}).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,
_pigeonMediaSettings(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 PlatformSize reply;
try {
reply = await _hostApi.initialize(cameraId);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
cameraEventStreamController.add(
CameraInitializedEvent(
cameraId,
reply.width,
reply.height,
ExposureMode.auto,
false,
FocusMode.auto,
false,
),
);
}
@override
Future<void> dispose(int cameraId) async {
await _hostApi.dispose(cameraId);
hostCameraHandlers.remove(cameraId)?.dispose();
}
@override
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) {
return _cameraEvents(cameraId).whereType<CameraInitializedEvent>();
}
@override
Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
return const Stream<CameraResolutionChangedEvent>.empty();
}
@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 Stream<DeviceOrientationChangedEvent>.value(
const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight),
);
}
@override
Future<void> lockCaptureOrientation(
int cameraId,
DeviceOrientation orientation,
) async {
throw UnimplementedError('lockCaptureOrientation() is not implemented.');
}
@override
Future<void> unlockCaptureOrientation(int cameraId) async {
throw UnimplementedError('unlockCaptureOrientation() is not implemented.');
}
@override
Future<XFile> takePicture(int cameraId) async {
final String path = await _hostApi.takePicture(cameraId);
return XFile(path);
}
@override
Future<void> prepareForVideoRecording() async {
}
@override
Future<void> startVideoRecording(
int cameraId, {
Duration? maxVideoDuration,
}) async {
return startVideoCapturing(VideoCaptureOptions(cameraId));
}
@override
Future<void> startVideoCapturing(VideoCaptureOptions options) async {
if (options.streamCallback != null || options.streamOptions != null) {
throw UnimplementedError(
'Streaming is not currently supported on Windows',
);
}
await _hostApi.startVideoRecording(options.cameraId);
}
@override
Future<XFile> stopVideoRecording(int cameraId) async {
final String path = await _hostApi.stopVideoRecording(cameraId);
return XFile(path);
}
@override
Future<void> pauseVideoRecording(int cameraId) async {
throw UnsupportedError(
'pauseVideoRecording() is not supported due to Win32 API limitations.',
);
}
@override
Future<void> resumeVideoRecording(int cameraId) async {
throw UnsupportedError(
'resumeVideoRecording() is not supported due to Win32 API limitations.',
);
}
@override
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
throw UnimplementedError('setFlashMode() is not implemented.');
}
@override
Future<void> setExposureMode(int cameraId, ExposureMode mode) async {
throw UnimplementedError('setExposureMode() is not implemented.');
}
@override
Future<void> setExposurePoint(int cameraId, Point<double>? point) async {
assert(point == null || point.x >= 0 && point.x <= 1);
assert(point == null || point.y >= 0 && point.y <= 1);
throw UnsupportedError(
'setExposurePoint() is not supported due to Win32 API limitations.',
);
}
@override
Future<double> getMinExposureOffset(int cameraId) async {
return 0.0;
}
@override
Future<double> getMaxExposureOffset(int cameraId) async {
return 0.0;
}
@override
Future<double> getExposureOffsetStepSize(int cameraId) async {
return 1.0;
}
@override
Future<double> setExposureOffset(int cameraId, double offset) async {
throw UnimplementedError('setExposureOffset() is not implemented.');
}
@override
Future<void> setFocusMode(int cameraId, FocusMode mode) async {
throw UnimplementedError('setFocusMode() is not implemented.');
}
@override
Future<void> setFocusPoint(int cameraId, Point<double>? point) async {
assert(point == null || point.x >= 0 && point.x <= 1);
assert(point == null || point.y >= 0 && point.y <= 1);
throw UnsupportedError(
'setFocusPoint() is not supported due to Win32 API limitations.',
);
}
@override
Future<double> getMinZoomLevel(int cameraId) async {
return 1.0;
}
@override
Future<double> getMaxZoomLevel(int cameraId) async {
return 1.0;
}
@override
Future<void> setZoomLevel(int cameraId, double zoom) async {
throw UnimplementedError('setZoomLevel() is not implemented.');
}
@override
Future<void> pausePreview(int cameraId) async {
await _hostApi.pausePreview(cameraId);
}
@override
Future<void> resumePreview(int cameraId) async {
await _hostApi.resumePreview(cameraId);
}
@override
Widget buildPreview(int cameraId) {
return Texture(textureId: cameraId);
}
PlatformMediaSettings _pigeonMediaSettings(MediaSettings? settings) {
return PlatformMediaSettings(
resolutionPreset: _pigeonResolutionPreset(settings?.resolutionPreset),
enableAudio: settings?.enableAudio ?? true,
framesPerSecond: settings?.fps,
videoBitrate: settings?.videoBitrate,
audioBitrate: settings?.audioBitrate,
);
}
PlatformResolutionPreset _pigeonResolutionPreset(
ResolutionPreset? resolutionPreset,
) {
if (resolutionPreset == null) {
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;
}
return PlatformResolutionPreset.max;
}
}
@visibleForTesting
class HostCameraMessageHandler implements CameraEventApi {
HostCameraMessageHandler(this.cameraId, this.streamController) {
CameraEventApi.setUp(this, messageChannelSuffix: cameraId.toString());
}
void dispose() {
CameraEventApi.setUp(null, messageChannelSuffix: cameraId.toString());
}
final int cameraId;
final StreamController<CameraEvent> streamController;
@override
void error(String message) {
streamController.add(CameraErrorEvent(cameraId, message));
}
@override
void cameraClosing() {
streamController.add(CameraClosingEvent(cameraId));
}
}