Skip to content

Commit

Permalink
Use a StreamController in onStreamedFrameAvailable
Browse files Browse the repository at this point in the history
We need to have exception-throwing `onPause` and `onResume` callback,
as they are required to throw by the camera platform interface.
  • Loading branch information
liff committed Dec 4, 2024
1 parent b413cde commit 790add6
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 29 deletions.
36 changes: 24 additions & 12 deletions packages/camera/camera_windows/lib/camera_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,30 @@ class CameraWindows extends CameraPlatform {

@override
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) async* {
await _hostApi.startImageStream(cameraId);
final eventChannelName =
'plugins.flutter.io/camera_windows/imageStream/$cameraId';
final EventChannel imageStreamChannel = EventChannel(eventChannelName);
try {
await for (final frame in imageStreamChannel.receiveBroadcastStream()) {
yield cameraImageFromPlatformData(frame as Map<dynamic, dynamic>);
}
} finally {
await _hostApi.stopImageStream(cameraId);
}
{CameraImageStreamOptions? options}) {
late StreamController<CameraImageData> controller;

controller = StreamController<CameraImageData>(
onListen: () async {
final eventChannelName = await _hostApi.startImageStream(cameraId);
final EventChannel imageStreamChannel =
EventChannel(eventChannelName);
imageStreamChannel.receiveBroadcastStream().listen((dynamic image) =>
controller.add(
cameraImageFromPlatformData(image as Map<dynamic, dynamic>)));
},
onPause: _onFrameStreamPauseResume,
onResume: _onFrameStreamPauseResume,
onCancel: () async {
await _hostApi.stopImageStream(cameraId);
});

return controller.stream;
}

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

@override
Expand Down
10 changes: 8 additions & 2 deletions packages/camera/camera_windows/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ class CameraApi {
}

/// Starts the image stream for the given camera.
Future<void> startImageStream(int cameraId) async {
/// Returns the name of the [EventChannel] used to deliver the images.
Future<String> startImageStream(int cameraId) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
Expand All @@ -382,8 +383,13 @@ class CameraApi {
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;
return (pigeonVar_replyList[0] as String?)!;
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/camera/camera_windows/pigeons/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ abstract class CameraApi {
String stopVideoRecording(int cameraId);

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

/// Stops the image stream for the given camera.
void stopImageStream(int cameraId);
Expand Down
9 changes: 7 additions & 2 deletions packages/camera/camera_windows/windows/camera_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ void CameraPlugin::StopVideoRecording(
}
}

std::optional<FlutterError> CameraPlugin::StartImageStream(int64_t camera_id) {
ErrorOr<std::string> CameraPlugin::StartImageStream(int64_t camera_id) {
Camera* camera = GetCameraByCameraId(camera_id);
if (!camera) {
return FlutterError("camera_error", "Camera not created");
Expand All @@ -355,6 +355,11 @@ std::optional<FlutterError> CameraPlugin::StartImageStream(int64_t camera_id) {
CaptureController* cc = camera->GetCaptureController();
assert(cc);

if (cc->IsStreaming()) {
return FlutterError("camera_error",
"Images from camera are already streaming");
}

std::ostringstream event_channel_name;
event_channel_name << "plugins.flutter.io/camera_windows/imageStream/"
<< camera_id;
Expand All @@ -376,7 +381,7 @@ std::optional<FlutterError> CameraPlugin::StartImageStream(int64_t camera_id) {

frame_event_channel.SetStreamHandler(std::move(event_channel_handler));

return std::nullopt;
return event_channel_name.str();
}

std::optional<FlutterError> CameraPlugin::StopImageStream(int64_t camera_id) {
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_windows/windows/camera_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class CameraPlugin : public flutter::Plugin,
void StopVideoRecording(
int64_t camera_id,
std::function<void(ErrorOr<std::string> reply)> result) override;
std::optional<FlutterError> StartImageStream(int64_t camera_id) override;
ErrorOr<std::string> StartImageStream(int64_t camera_id) override;
std::optional<FlutterError> StopImageStream(int64_t camera_id) override;
void TakePicture(
int64_t camera_id,
Expand Down
5 changes: 5 additions & 0 deletions packages/camera/camera_windows/windows/capture_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class CaptureController {
// Stops the current image streaming.
virtual void StopImageStream() = 0;

virtual bool IsStreaming() const = 0;

// Captures a still photo.
virtual void TakePicture(const std::string& file_path) = 0;
};
Expand Down Expand Up @@ -142,6 +144,9 @@ class CaptureControllerImpl : public CaptureController,
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> sink)
override;
void StopImageStream() override;
bool IsStreaming() const override {
return static_cast<bool>(image_stream_sink_);
}
void TakePicture(const std::string& file_path) override;

// CaptureEngineObserver
Expand Down
8 changes: 4 additions & 4 deletions packages/camera/camera_windows/windows/messages.g.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,14 +516,14 @@ void CameraApi::SetUp(flutter::BinaryMessenger* binary_messenger,
return;
}
const int64_t camera_id_arg = encodable_camera_id_arg.LongValue();
std::optional<FlutterError> output =
ErrorOr<std::string> output =
api->StartImageStream(camera_id_arg);
if (output.has_value()) {
reply(WrapError(output.value()));
if (output.has_error()) {
reply(WrapError(output.error()));
return;
}
EncodableList wrapped;
wrapped.push_back(EncodableValue());
wrapped.push_back(EncodableValue(std::move(output).TakeValue()));
reply(EncodableValue(std::move(wrapped)));
} catch (const std::exception& exception) {
reply(WrapError(exception.what()));
Expand Down
3 changes: 2 additions & 1 deletion packages/camera/camera_windows/windows/messages.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ class CameraApi {
int64_t camera_id,
std::function<void(ErrorOr<std::string> reply)> result) = 0;
// Starts the image stream for the given camera.
virtual std::optional<FlutterError> StartImageStream(int64_t camera_id) = 0;
// Returns the name of the [EventChannel] used to deliver the images.
virtual ErrorOr<std::string> StartImageStream(int64_t camera_id) = 0;
// Stops the image stream for the given camera.
virtual std::optional<FlutterError> StopImageStream(int64_t camera_id) = 0;
// Starts the preview stream for the given camera.
Expand Down
18 changes: 12 additions & 6 deletions packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) {
std::unique_ptr<MockCaptureController> capture_controller =
std::make_unique<MockCaptureController>();

std::unique_ptr<MockTextureRegistrar> texture_registrar =
std::make_unique<MockTextureRegistrar>();
std::unique_ptr<MockBinaryMessenger> messenger =
std::make_unique<MockBinaryMessenger>();

EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id)))
.Times(1)
.WillOnce([cam = camera.get()](int64_t camera_id) {
Expand All @@ -374,19 +379,20 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) {
.WillOnce(
[cam = camera.get()]() { return cam->capture_controller_.get(); });

EXPECT_CALL(*capture_controller, StartImageStream).Times(1);
EXPECT_CALL(*capture_controller, IsStreaming).WillRepeatedly(Return(false));

EXPECT_CALL(*messenger, SetMessageHandler).Times(1);

camera->camera_id_ = mock_camera_id;
camera->capture_controller_ = std::move(capture_controller);

MockCameraPlugin plugin(std::make_unique<MockTextureRegistrar>().get(),
std::make_unique<MockBinaryMessenger>().get(),
MockCameraPlugin plugin(texture_registrar.get(), messenger.get(),
std::make_unique<MockCameraFactory>());

// Add mocked camera to plugins camera list.
plugin.AddCamera(std::move(camera));

EXPECT_EQ(plugin.StartImageStream(mock_camera_id), std::nullopt);
EXPECT_FALSE(plugin.StartImageStream(mock_camera_id).has_error());
}

TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) {
Expand Down Expand Up @@ -419,8 +425,8 @@ TEST(CameraPlugin, StartImageStreamHandlerErrorOnInvalidCameraId) {
// Add mocked camera to plugins camera list.
plugin.AddCamera(std::move(camera));

EXPECT_THAT(plugin.StartImageStream(missing_camera_id),
Optional(Property("code", &FlutterError::code, "camera_error")));
auto result = plugin.StartImageStream(missing_camera_id);
EXPECT_TRUE(result.has_error());
}

TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) {
Expand Down
1 change: 1 addition & 0 deletions packages/camera/camera_windows/windows/test/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class MockCaptureController : public CaptureController {
(std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> sink),
(override));
MOCK_METHOD(void, StopImageStream, (), (override));
MOCK_METHOD(bool, IsStreaming, (), (const override));
MOCK_METHOD(void, TakePicture, (const std::string& file_path), (override));
};

Expand Down

0 comments on commit 790add6

Please sign in to comment.