Skip to content

Commit

Permalink
Use a camera-specific EventChannel
Browse files Browse the repository at this point in the history
Instead of a global `EventSink` that is re-used for each stream, create
a dedicated `EventChannel` for each capture stream. The channel gets an
unique name like
`plugins.flutter.io/camera_android/imageStream/<cameraId>` for each
camera instance.

Addresses:
  flutter#7067 (comment)
  • Loading branch information
liff committed Dec 5, 2024
1 parent 2112590 commit 682407c
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 56 deletions.
14 changes: 10 additions & 4 deletions packages/camera/camera_windows/lib/camera_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class CameraWindows extends CameraPlatform {

@override
Future<void> dispose(int cameraId) async {
await _stopPlatformStream(cameraId);
await _hostApi.dispose(cameraId);

// Destroy method channel after camera is disposed to be able to handle last messages.
Expand Down Expand Up @@ -266,13 +267,14 @@ class CameraWindows extends CameraPlatform {
}

Future<void> _startPlatformStream(int cameraId) async {
_startStreamListener();
await _hostApi.startImageStream(cameraId);
_startStreamListener(cameraId);
}

void _startStreamListener() {
const EventChannel cameraEventChannel =
EventChannel('plugins.flutter.io/camera_android/imageStream');
void _startStreamListener(int cameraId) {
final eventChannelName =
'plugins.flutter.io/camera_windows/imageStream/$cameraId';
final EventChannel cameraEventChannel = EventChannel(eventChannelName);
_platformImageStreamSubscription =
cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) {
_frameStreamController!
Expand All @@ -281,6 +283,10 @@ class CameraWindows extends CameraPlatform {
}

FutureOr<void> _onFrameStreamCancel(int cameraId) async {
await _stopPlatformStream(cameraId);
}

Future<void> _stopPlatformStream(int cameraId) async {
await _hostApi.stopImageStream(cameraId);
await _platformImageStreamSubscription?.cancel();
_platformImageStreamSubscription = null;
Expand Down
54 changes: 22 additions & 32 deletions packages/camera/camera_windows/windows/camera_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cassert>
#include <chrono>
#include <memory>
#include <sstream>

#include "capture_device_info.h"
#include "com_heap_ptr.h"
Expand All @@ -35,10 +36,6 @@ namespace {

const std::string kPictureCaptureExtension = "jpeg";
const std::string kVideoCaptureExtension = "mp4";
constexpr char kFrameEventChannelName[] =
"plugins.flutter.io/camera_android/imageStream";

std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> event_sink;

// Builds CaptureDeviceInfo object from given device holding device name and id.
std::unique_ptr<CaptureDeviceInfo> GetDeviceInfo(IMFActivate* device) {
Expand Down Expand Up @@ -123,34 +120,12 @@ std::optional<std::string> GetFilePathForVideo() {
}
} // namespace

// a setter for the event sink helpful for testing.
void CameraPlugin::SetEventSink(
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> events) {
event_sink = std::move(events);
}

// static
void CameraPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows* registrar) {
std::unique_ptr<CameraPlugin> plugin = std::make_unique<CameraPlugin>(
registrar->texture_registrar(), registrar->messenger());

auto frameEventchannel = std::make_unique<flutter::EventChannel<>>(
registrar->messenger(), kFrameEventChannelName,
&flutter::StandardMethodCodec::GetInstance());

auto event_channel_handler =
std::make_unique<flutter::StreamHandlerFunctions<>>(
[plugin = plugin.get()](auto arguments, auto events) {
plugin->SetEventSink(std::move(events));
return nullptr;
},
[](auto arguments) {
event_sink.reset();
return nullptr;
});
frameEventchannel->SetStreamHandler(std::move(event_channel_handler));

CameraApi::SetUp(registrar->messenger(), plugin.get());

registrar->AddPlugin(std::move(plugin));
Expand Down Expand Up @@ -377,14 +352,29 @@ std::optional<FlutterError> CameraPlugin::StartImageStream(int64_t camera_id) {
return FlutterError("camera_error", "Camera not created");
}

if (!event_sink) {
return FlutterError("camera_error",
"Unable to make event channel from windows");
}

CaptureController* cc = camera->GetCaptureController();
assert(cc);
cc->StartImageStream(std::move(event_sink));

std::ostringstream event_channel_name;
event_channel_name << "plugins.flutter.io/camera_windows/imageStream/"
<< camera_id;

auto frame_event_channel =
flutter::EventChannel(messenger_, event_channel_name.str(),
&flutter::StandardMethodCodec::GetInstance());

auto event_channel_handler =
std::make_unique<flutter::StreamHandlerFunctions<>>(
[cc](auto arguments, auto events) {
cc->StartImageStream(std::move(events));
return nullptr;
},
[cc](auto arguments) {
cc->StopImageStream();
return nullptr;
});

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

return std::nullopt;
}
Expand Down
2 changes: 0 additions & 2 deletions packages/camera/camera_windows/windows/camera_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ class CameraPlugin : public flutter::Plugin,
public CameraApi,
public VideoCaptureDeviceEnumerator {
public:
void SetEventSink(
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> events);
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);

CameraPlugin(flutter::TextureRegistrar* texture_registrar,
Expand Down
30 changes: 12 additions & 18 deletions packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ using ::testing::_;
using ::testing::DoAll;
using ::testing::EndsWith;
using ::testing::Eq;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::Optional;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::Return;

void MockInitCamera(MockCamera* camera, bool success) {
EXPECT_CALL(*camera,
Expand Down Expand Up @@ -371,12 +371,10 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) {

EXPECT_CALL(*camera, GetCaptureController)
.Times(1)
.WillOnce([cam = camera.get()]() {
return cam->capture_controller_.get();
});
.WillOnce(
[cam = camera.get()]() { return cam->capture_controller_.get(); });

EXPECT_CALL(*capture_controller, StartImageStream)
.Times(1);
EXPECT_CALL(*capture_controller, StartImageStream).Times(1);

camera->camera_id_ = mock_camera_id;
camera->capture_controller_ = std::move(capture_controller);
Expand All @@ -385,10 +383,6 @@ TEST(CameraPlugin, StartImageStreamHandlerCallsStartImageStream) {
std::make_unique<MockBinaryMessenger>().get(),
std::make_unique<MockCameraFactory>());

// Set the event sink to a mocked event sink.
auto mock_event_sink = std::make_unique<MockEventSink>();
plugin.SetEventSink(std::move(mock_event_sink));

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

Expand Down Expand Up @@ -425,7 +419,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")));
EXPECT_THAT(plugin.StartImageStream(missing_camera_id),
Optional(Property("code", &FlutterError::code, "camera_error")));
}

TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) {
Expand All @@ -445,12 +440,10 @@ TEST(CameraPlugin, StopImageStreamHandlerCallsStopImageStream) {

EXPECT_CALL(*camera, GetCaptureController)
.Times(1)
.WillOnce([cam = camera.get()]() {
return cam->capture_controller_.get();
});
.WillOnce(
[cam = camera.get()]() { return cam->capture_controller_.get(); });

EXPECT_CALL(*capture_controller, StopImageStream)
.Times(1);
EXPECT_CALL(*capture_controller, StopImageStream).Times(1);

camera->camera_id_ = mock_camera_id;
camera->capture_controller_ = std::move(capture_controller);
Expand Down Expand Up @@ -495,7 +488,8 @@ TEST(CameraPlugin, StopImageStreamHandlerErrorOnInvalidCameraId) {
// Add mocked camera to plugins camera list.
plugin.AddCamera(std::move(camera));

EXPECT_THAT(plugin.StopImageStream(missing_camera_id), Optional(Property("code", &FlutterError::code, "camera_error")));
EXPECT_THAT(plugin.StopImageStream(missing_camera_id),
Optional(Property("code", &FlutterError::code, "camera_error")));
}

TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) {
Expand Down

0 comments on commit 682407c

Please sign in to comment.