Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion packages/camera/camera_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
## NEXT
## 0.2.7+1

* Restores Support for streaming frames.
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.

## 0.2.6+2

* Restores support for streaming frames.
* Fixes compile errors under strict standards mode.
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.

Expand Down
35 changes: 35 additions & 0 deletions packages/camera/camera_windows/lib/camera_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';

import 'src/messages.g.dart';
import 'src/type_conversion.dart';

/// An implementation of [CameraPlatform] for Windows.
class CameraWindows extends CameraPlatform {
Expand Down Expand Up @@ -234,6 +235,40 @@ class CameraWindows extends CameraPlatform {
'resumeVideoRecording() is not supported due to Win32 API limitations.');
}

@override
bool supportsImageStreaming() => true;

@override
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) {
late StreamController<CameraImageData> controller;
StreamSubscription<dynamic>? subscription;

controller = StreamController<CameraImageData>(
onListen: () async {
final String eventChannelName =
await _hostApi.startImageStream(cameraId);
final EventChannel imageStreamChannel =
EventChannel(eventChannelName);
subscription = imageStreamChannel.receiveBroadcastStream().listen(
(dynamic image) => controller.add(
cameraImageFromPlatformData(image as Map<dynamic, dynamic>)));
},
onPause: _onFrameStreamPauseResume,
onResume: _onFrameStreamPauseResume,
onCancel: () async {
// Cancelling the subscription stops the image capture on the native side.
await subscription?.cancel();
});

return controller.stream;
}

void _onFrameStreamPauseResume() {
throw CameraException('InvalidCall',
'Pause and resume are not supported for onStreamedFrameAvailable');
}

@override
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
// TODO(jokerttu): Implement flash mode support, https://github.com/flutter/flutter/issues/97537.
Expand Down
76 changes: 75 additions & 1 deletion packages/camera/camera_windows/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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.
// Autogenerated from Pigeon (v22.6.0), do not edit directly.
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

Expand Down Expand Up @@ -108,6 +108,43 @@ class PlatformSize {
}
}

/// A representation of frame data from the camera preview stream.
class PlatformFrameData {
PlatformFrameData({
required this.data,
required this.width,
required this.height,
required this.length,
});

List<int> data;

int width;

int height;

int length;

Object encode() {
return <Object?>[
data,
width,
height,
length,
];
}

static PlatformFrameData decode(Object result) {
result as List<Object?>;
return PlatformFrameData(
data: (result[0] as List<Object?>?)!.cast<int>(),
width: result[1]! as int,
height: result[2]! as int,
length: result[3]! as int,
);
}
}

class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
Expand All @@ -124,6 +161,9 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is PlatformSize) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is PlatformFrameData) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
Expand All @@ -139,6 +179,8 @@ class _PigeonCodec extends StandardMessageCodec {
return PlatformMediaSettings.decode(readValue(buffer)!);
case 131:
return PlatformSize.decode(readValue(buffer)!);
case 132:
return PlatformFrameData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
Expand Down Expand Up @@ -362,6 +404,38 @@ class CameraApi {
}
}

/// Starts the image stream for the given camera.
/// Returns the name of the [EventChannel] used to deliver the images.
/// Cancelling the subscription to the channel stops the capture.
Future<String> startImageStream(int cameraId) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as String?)!;
}
}

/// Starts the preview stream for the given camera.
Future<void> pausePreview(int cameraId) async {
final String pigeonVar_channelName =
Expand Down
25 changes: 25 additions & 0 deletions packages/camera/camera_windows/lib/src/type_conversion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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<dynamic, dynamic> data) {
return CameraImageData(
format: const CameraImageFormat(ImageFormatGroup.bgra8888, raw: 0),
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: <CameraImagePlane>[
CameraImagePlane(
bytes: data['data'] as Uint8List,
bytesPerRow: (data['width'] as int) * 4,
)
]);
}
20 changes: 20 additions & 0 deletions packages/camera/camera_windows/pigeons/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ class PlatformSize {
final double height;
}

/// A representation of frame data from the camera preview stream.
class PlatformFrameData {
PlatformFrameData({
required this.data,
required this.width,
required this.height,
required this.length,
});

final List<int> data;
final int width;
final int height;
final int length;
}

@HostApi()
abstract class CameraApi {
/// Returns the names of all of the available capture devices.
Expand Down Expand Up @@ -70,6 +85,11 @@ abstract class CameraApi {
@async
String stopVideoRecording(int cameraId);

/// Starts the image stream for the given camera.
/// Returns the name of the [EventChannel] used to deliver the images.
/// Cancelling the subscription to the channel stops the capture.
String startImageStream(int cameraId);

/// Starts the preview stream for the given camera.
@async
void pausePreview(int cameraId);
Expand Down
4 changes: 2 additions & 2 deletions packages/camera/camera_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_windows
description: A Flutter plugin for getting information about and controlling the camera on Windows.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.2.6+2
version: 0.2.7+1

environment:
sdk: ^3.6.0
Expand All @@ -17,7 +17,7 @@ flutter:
dartPluginClass: CameraWindows

dependencies:
camera_platform_interface: ^2.6.0
camera_platform_interface: ^2.9.0
cross_file: ^0.3.1
flutter:
sdk: flutter
Expand Down
16 changes: 16 additions & 0 deletions packages/camera/camera_windows/test/camera_windows_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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:async/async.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_windows/camera_windows.dart';
Expand Down Expand Up @@ -181,6 +183,11 @@ void main() {
verify(mockApi.dispose(captureAny));
expect(verification.captured[0], cameraId);
});

test('Should report support for image streaming', () async {
final CameraWindows plugin = CameraWindows(api: MockCameraApi());
expect(plugin.supportsImageStreaming(), true);
});
});

group('Event Tests', () {
Expand Down Expand Up @@ -548,6 +555,15 @@ void main() {
verify(mockApi.resumePreview(captureAny));
expect(verification.captured[0], cameraId);
});

test('Should start the image stream when it is subscribed', () async {
final Stream<CameraImageData> stream =
plugin.onStreamedFrameAvailable(cameraId);

await stream.listen((CameraImageData frame) {}).cancel();

verify(mockApi.startImageStream(cameraId));
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,24 +166,15 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi {
) as _i4.Future<String>);

@override
_i4.Future<void> startImageStream(int? cameraId) => (super.noSuchMethod(
_i4.Future<String> startImageStream(int? cameraId) => (super.noSuchMethod(
Invocation.method(
#startImageStream,
[cameraId],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<void> stopImageStream(int? cameraId) => (super.noSuchMethod(
Invocation.method(
#stopImageStream,
[cameraId],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i4.Future<String>.value('imageStream/$cameraId'),
returnValueForMissingStub:
_i4.Future<String>.value('imageStream/$cameraId'),
) as _i4.Future<String>);

@override
_i4.Future<void> pausePreview(int? cameraId) => (super.noSuchMethod(
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera_windows/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ list(APPEND PLUGIN_SOURCES
"texture_handler.h"
"texture_handler.cpp"
"com_heap_ptr.h"
"task_runner.h"
"task_runner_window.h"
"task_runner_window.cpp"
)

add_library(${PLUGIN_NAME} SHARED
Expand Down Expand Up @@ -84,6 +87,7 @@ add_executable(${TEST_RUNNER}
test/camera_plugin_test.cpp
test/camera_test.cpp
test/capture_controller_test.cpp
test/task_runner_window_test.cpp
${PLUGIN_SOURCES}
)
apply_standard_settings(${TEST_RUNNER})
Expand Down
10 changes: 6 additions & 4 deletions packages/camera/camera_windows/windows/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,26 @@ CameraImpl::~CameraImpl() {

bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings) {
const PlatformMediaSettings& media_settings,
std::unique_ptr<TaskRunner> task_runner) {
auto capture_controller_factory =
std::make_unique<CaptureControllerFactoryImpl>();
return InitCamera(std::move(capture_controller_factory), texture_registrar,
messenger, media_settings);
messenger, media_settings, task_runner);
}

bool CameraImpl::InitCamera(
std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
const PlatformMediaSettings& media_settings) {
const PlatformMediaSettings& media_settings,
std::unique_ptr<TaskRunner> task_runner) {
assert(!device_id_.empty());
messenger_ = messenger;
capture_controller_ =
capture_controller_factory->CreateCaptureController(this);
return capture_controller_->InitCaptureDevice(texture_registrar, device_id_,
media_settings);
media_settings, task_runner);
}

bool CameraImpl::AddPendingVoidResult(
Expand Down
Loading