[camera_windows] Allow retrying after initialization failure (#6119)
diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md
index 6883b55..a1e2a07 100644
--- a/packages/camera/camera_windows/CHANGELOG.md
+++ b/packages/camera/camera_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.0+4
+
+* Allows retrying camera initialization after error.
+
## 0.1.0+3
* Updates the README to better explain how to use the unendorsed package.
diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml
index 055313b..e9fe021 100644
--- a/packages/camera/camera_windows/pubspec.yaml
+++ b/packages/camera/camera_windows/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin for getting information about and controlling the camera on Windows.
repository: https://github.com/flutter/plugins/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.1.0+3
+version: 0.1.0+4
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp
index c21f8ab..617f98f 100644
--- a/packages/camera/camera_windows/windows/camera.cpp
+++ b/packages/camera/camera_windows/windows/camera.cpp
@@ -28,17 +28,17 @@
"Plugin disposed before request was handled");
}
-void CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar,
+bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
bool record_audio,
ResolutionPreset resolution_preset) {
auto capture_controller_factory =
std::make_unique<CaptureControllerFactoryImpl>();
- InitCamera(std::move(capture_controller_factory), texture_registrar,
- messenger, record_audio, resolution_preset);
+ return InitCamera(std::move(capture_controller_factory), texture_registrar,
+ messenger, record_audio, resolution_preset);
}
-void CameraImpl::InitCamera(
+bool CameraImpl::InitCamera(
std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger, bool record_audio,
@@ -47,8 +47,8 @@
messenger_ = messenger;
capture_controller_ =
capture_controller_factory->CreateCaptureController(this);
- capture_controller_->InitCaptureDevice(texture_registrar, device_id_,
- record_audio, resolution_preset);
+ return capture_controller_->InitCaptureDevice(
+ texture_registrar, device_id_, record_audio, resolution_preset);
}
bool CameraImpl::AddPendingResult(
diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h
index 6996231..429f41a 100644
--- a/packages/camera/camera_windows/windows/camera.h
+++ b/packages/camera/camera_windows/windows/camera.h
@@ -63,7 +63,9 @@
virtual camera_windows::CaptureController* GetCaptureController() = 0;
// Initializes this camera and its associated capture controller.
- virtual void InitCamera(flutter::TextureRegistrar* texture_registrar,
+ //
+ // Returns false if initialization fails.
+ virtual bool InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger,
bool record_audio,
ResolutionPreset resolution_preset) = 0;
@@ -116,7 +118,7 @@
camera_windows::CaptureController* GetCaptureController() override {
return capture_controller_.get();
}
- void InitCamera(flutter::TextureRegistrar* texture_registrar,
+ bool InitCamera(flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger, bool record_audio,
ResolutionPreset resolution_preset) override;
@@ -124,7 +126,9 @@
//
// This is a convenience method called by |InitCamera| but also used in
// tests.
- void InitCamera(
+ //
+ // Returns false if initialization fails.
+ bool InitCamera(
std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
flutter::TextureRegistrar* texture_registrar,
flutter::BinaryMessenger* messenger, bool record_audio,
diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp
index 3b795e0..5503d17 100644
--- a/packages/camera/camera_windows/windows/camera_plugin.cpp
+++ b/packages/camera/camera_windows/windows/camera_plugin.cpp
@@ -398,9 +398,11 @@
resolution_preset = ResolutionPreset::kAuto;
}
- camera->InitCamera(texture_registrar_, messenger_, *record_audio,
- resolution_preset);
- cameras_.push_back(std::move(camera));
+ bool initialized = camera->InitCamera(texture_registrar_, messenger_,
+ *record_audio, resolution_preset);
+ if (initialized) {
+ cameras_.push_back(std::move(camera));
+ }
}
}
diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp
index 084b036..6c89060 100644
--- a/packages/camera/camera_windows/windows/capture_controller.cpp
+++ b/packages/camera/camera_windows/windows/capture_controller.cpp
@@ -288,17 +288,19 @@
texture_handler_ = nullptr;
}
-void CaptureControllerImpl::InitCaptureDevice(
+bool CaptureControllerImpl::InitCaptureDevice(
flutter::TextureRegistrar* texture_registrar, const std::string& device_id,
bool record_audio, ResolutionPreset resolution_preset) {
assert(capture_controller_listener_);
if (IsInitialized()) {
- return capture_controller_listener_->OnCreateCaptureEngineFailed(
+ capture_controller_listener_->OnCreateCaptureEngineFailed(
"Capture device already initialized");
+ return false;
} else if (capture_engine_state_ == CaptureEngineState::kInitializing) {
- return capture_controller_listener_->OnCreateCaptureEngineFailed(
+ capture_controller_listener_->OnCreateCaptureEngineFailed(
"Capture device already initializing");
+ return false;
}
capture_engine_state_ = CaptureEngineState::kInitializing;
@@ -315,7 +317,7 @@
capture_controller_listener_->OnCreateCaptureEngineFailed(
"Failed to create camera");
ResetCaptureController();
- return;
+ return false;
}
media_foundation_started_ = true;
@@ -326,8 +328,10 @@
capture_controller_listener_->OnCreateCaptureEngineFailed(
"Failed to create camera");
ResetCaptureController();
- return;
+ return false;
}
+
+ return true;
}
void CaptureControllerImpl::TakePicture(const std::string& file_path) {
diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h
index 34e3781..0b7ab66 100644
--- a/packages/camera/camera_windows/windows/capture_controller.h
+++ b/packages/camera/camera_windows/windows/capture_controller.h
@@ -75,6 +75,9 @@
// Initializes the capture controller with the specified device id.
//
+ // Returns false if the capture controller could not be initialized
+ // or is already initialized.
+ //
// texture_registrar: Pointer to Flutter TextureRegistrar instance. Used to
// register texture for capture preview.
// device_id: A string that holds information of camera device id to
@@ -82,7 +85,7 @@
// record_audio: A boolean value telling if audio should be captured on
// video recording.
// resolution_preset: Maximum capture resolution height.
- virtual void InitCaptureDevice(TextureRegistrar* texture_registrar,
+ virtual bool InitCaptureDevice(TextureRegistrar* texture_registrar,
const std::string& device_id,
bool record_audio,
ResolutionPreset resolution_preset) = 0;
@@ -132,7 +135,7 @@
CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete;
// CaptureController
- void InitCaptureDevice(TextureRegistrar* texture_registrar,
+ bool InitCaptureDevice(TextureRegistrar* texture_registrar,
const std::string& device_id, bool record_audio,
ResolutionPreset resolution_preset) override;
uint32_t GetPreviewWidth() const override { return preview_frame_width_; }
diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
index 309268a..9cab069 100644
--- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
+++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
@@ -30,6 +30,41 @@
using ::testing::Pointee;
using ::testing::Return;
+void MockInitCamera(MockCamera* camera, bool success) {
+ EXPECT_CALL(*camera,
+ HasPendingResultByType(Eq(PendingResultType::kCreateCamera)))
+ .Times(1)
+ .WillOnce(Return(false));
+
+ EXPECT_CALL(*camera,
+ AddPendingResult(Eq(PendingResultType::kCreateCamera), _))
+ .Times(1)
+ .WillOnce([camera](PendingResultType type,
+ std::unique_ptr<MethodResult<>> result) {
+ camera->pending_result_ = std::move(result);
+ return true;
+ });
+
+ EXPECT_CALL(*camera, HasDeviceId(Eq(camera->device_id_)))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*camera, InitCamera)
+ .Times(1)
+ .WillOnce([camera, success](flutter::TextureRegistrar* texture_registrar,
+ flutter::BinaryMessenger* messenger,
+ bool record_audio,
+ ResolutionPreset resolution_preset) {
+ assert(camera->pending_result_);
+ if (success) {
+ camera->pending_result_->Success(EncodableValue(1));
+ return true;
+ } else {
+ camera->pending_result_->Error("camera_error", "InitCamera failed.");
+ return false;
+ }
+ });
+}
+
TEST(CameraPlugin, AvailableCamerasHandlerSuccessIfNoCameras) {
std::unique_ptr<MockTextureRegistrar> texture_registrar_ =
std::make_unique<MockTextureRegistrar>();
@@ -99,28 +134,7 @@
std::unique_ptr<MockCamera> camera =
std::make_unique<MockCamera>(MOCK_DEVICE_ID);
- EXPECT_CALL(*camera,
- HasPendingResultByType(Eq(PendingResultType::kCreateCamera)))
- .Times(1)
- .WillOnce(Return(false));
-
- EXPECT_CALL(*camera,
- AddPendingResult(Eq(PendingResultType::kCreateCamera), _))
- .Times(1)
- .WillOnce([cam = camera.get()](PendingResultType type,
- std::unique_ptr<MethodResult<>> result) {
- cam->pending_result_ = std::move(result);
- return true;
- });
- EXPECT_CALL(*camera, InitCamera)
- .Times(1)
- .WillOnce([cam = camera.get()](
- flutter::TextureRegistrar* texture_registrar,
- flutter::BinaryMessenger* messenger, bool record_audio,
- ResolutionPreset resolution_preset) {
- assert(cam->pending_result_);
- return cam->pending_result_->Success(EncodableValue(1));
- });
+ MockInitCamera(camera.get(), true);
// Move mocked camera to the factory to be passed
// for plugin with CreateCamera function.
@@ -185,34 +199,7 @@
std::unique_ptr<MockCamera> camera =
std::make_unique<MockCamera>(MOCK_DEVICE_ID);
- EXPECT_CALL(*camera,
- HasPendingResultByType(Eq(PendingResultType::kCreateCamera)))
- .Times(1)
- .WillOnce(Return(false));
-
- EXPECT_CALL(*camera,
- AddPendingResult(Eq(PendingResultType::kCreateCamera), _))
- .Times(1)
- .WillOnce([cam = camera.get()](PendingResultType type,
- std::unique_ptr<MethodResult<>> result) {
- cam->pending_result_ = std::move(result);
- return true;
- });
- EXPECT_CALL(*camera, InitCamera)
- .Times(1)
- .WillOnce([cam = camera.get()](
- flutter::TextureRegistrar* texture_registrar,
- flutter::BinaryMessenger* messenger, bool record_audio,
- ResolutionPreset resolution_preset) {
- assert(cam->pending_result_);
- return cam->pending_result_->Success(EncodableValue(1));
- });
-
- EXPECT_CALL(*camera, HasDeviceId(Eq(MOCK_DEVICE_ID)))
- .Times(1)
- .WillOnce([cam = camera.get()](std::string& device_id) {
- return cam->device_id_ == device_id;
- });
+ MockInitCamera(camera.get(), true);
// Move mocked camera to the factory to be passed
// for plugin with CreateCamera function.
@@ -246,6 +233,64 @@
std::move(second_create_result));
}
+TEST(CameraPlugin, CreateHandlerAllowsRetry) {
+ std::unique_ptr<MockMethodResult> first_create_result =
+ std::make_unique<MockMethodResult>();
+ std::unique_ptr<MockMethodResult> second_create_result =
+ std::make_unique<MockMethodResult>();
+ std::unique_ptr<MockTextureRegistrar> texture_registrar_ =
+ std::make_unique<MockTextureRegistrar>();
+ std::unique_ptr<MockBinaryMessenger> messenger_ =
+ std::make_unique<MockBinaryMessenger>();
+ std::unique_ptr<MockCameraFactory> camera_factory_ =
+ std::make_unique<MockCameraFactory>();
+
+ // The camera will fail initialization once and then succeed.
+ EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID))
+ .Times(2)
+ .WillOnce([](const std::string& device_id) {
+ std::unique_ptr<MockCamera> first_camera =
+ std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+
+ MockInitCamera(first_camera.get(), false);
+
+ return first_camera;
+ })
+ .WillOnce([](const std::string& device_id) {
+ std::unique_ptr<MockCamera> second_camera =
+ std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+
+ MockInitCamera(second_camera.get(), true);
+
+ return second_camera;
+ });
+
+ EXPECT_CALL(*first_create_result, ErrorInternal).Times(1);
+ EXPECT_CALL(*first_create_result, SuccessInternal).Times(0);
+
+ CameraPlugin plugin(texture_registrar_.get(), messenger_.get(),
+ std::move(camera_factory_));
+ EncodableMap args = {
+ {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)},
+ {EncodableValue("resolutionPreset"), EncodableValue(nullptr)},
+ {EncodableValue("enableAudio"), EncodableValue(true)},
+ };
+
+ plugin.HandleMethodCall(
+ flutter::MethodCall("create",
+ std::make_unique<EncodableValue>(EncodableMap(args))),
+ std::move(first_create_result));
+
+ EXPECT_CALL(*second_create_result, ErrorInternal).Times(0);
+ EXPECT_CALL(*second_create_result,
+ SuccessInternal(Pointee(EncodableValue(1))));
+
+ plugin.HandleMethodCall(
+ flutter::MethodCall("create",
+ std::make_unique<EncodableValue>(EncodableMap(args))),
+ std::move(second_create_result));
+}
+
TEST(CameraPlugin, InitializeHandlerCallStartPreview) {
int64_t mock_camera_id = 1234;
diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp
index 899c1fd..97e3ce1 100644
--- a/packages/camera/camera_windows/windows/test/camera_test.cpp
+++ b/packages/camera/camera_windows/windows/test/camera_test.cpp
@@ -23,6 +23,7 @@
using ::testing::Eq;
using ::testing::NiceMock;
using ::testing::Pointee;
+using ::testing::Return;
namespace test {
@@ -34,17 +35,57 @@
EXPECT_CALL(*capture_controller_factory, CreateCaptureController)
.Times(1)
- .WillOnce(
- []() { return std::make_unique<NiceMock<MockCaptureController>>(); });
+ .WillOnce([]() {
+ std::unique_ptr<NiceMock<MockCaptureController>> capture_controller =
+ std::make_unique<NiceMock<MockCaptureController>>();
+
+ EXPECT_CALL(*capture_controller, InitCaptureDevice)
+ .Times(1)
+ .WillOnce(Return(true));
+
+ return capture_controller;
+ });
EXPECT_TRUE(camera->GetCaptureController() == nullptr);
// Init camera with mock capture controller factory
- camera->InitCamera(std::move(capture_controller_factory),
- std::make_unique<MockTextureRegistrar>().get(),
- std::make_unique<MockBinaryMessenger>().get(), false,
- ResolutionPreset::kAuto);
+ bool result =
+ camera->InitCamera(std::move(capture_controller_factory),
+ std::make_unique<MockTextureRegistrar>().get(),
+ std::make_unique<MockBinaryMessenger>().get(), false,
+ ResolutionPreset::kAuto);
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(camera->GetCaptureController() != nullptr);
+}
+TEST(Camera, InitCameraReportsFailure) {
+ std::unique_ptr<CameraImpl> camera =
+ std::make_unique<CameraImpl>(MOCK_DEVICE_ID);
+ std::unique_ptr<MockCaptureControllerFactory> capture_controller_factory =
+ std::make_unique<MockCaptureControllerFactory>();
+
+ EXPECT_CALL(*capture_controller_factory, CreateCaptureController)
+ .Times(1)
+ .WillOnce([]() {
+ std::unique_ptr<NiceMock<MockCaptureController>> capture_controller =
+ std::make_unique<NiceMock<MockCaptureController>>();
+
+ EXPECT_CALL(*capture_controller, InitCaptureDevice)
+ .Times(1)
+ .WillOnce(Return(false));
+
+ return capture_controller;
+ });
+
+ EXPECT_TRUE(camera->GetCaptureController() == nullptr);
+
+ // Init camera with mock capture controller factory
+ bool result =
+ camera->InitCamera(std::move(capture_controller_factory),
+ std::make_unique<MockTextureRegistrar>().get(),
+ std::make_unique<MockBinaryMessenger>().get(), false,
+ ResolutionPreset::kAuto);
+ EXPECT_FALSE(result);
EXPECT_TRUE(camera->GetCaptureController() != nullptr);
}
diff --git a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp
index 7520af7..083f823 100644
--- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp
+++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp
@@ -59,8 +59,10 @@
.Times(1);
EXPECT_CALL(*engine, Initialize).Times(1);
- capture_controller->InitCaptureDevice(texture_registrar, MOCK_DEVICE_ID, true,
- ResolutionPreset::kAuto);
+ bool result = capture_controller->InitCaptureDevice(
+ texture_registrar, MOCK_DEVICE_ID, true, ResolutionPreset::kAuto);
+
+ EXPECT_TRUE(result);
// MockCaptureEngine::Initialize is called
EXPECT_TRUE(engine->initialized_);
@@ -214,6 +216,78 @@
engine = nullptr;
}
+TEST(CaptureController, InitCaptureEngineCanOnlyBeCalledOnce) {
+ ComPtr<MockCaptureEngine> engine = new MockCaptureEngine();
+ std::unique_ptr<MockCamera> camera =
+ std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+ std::unique_ptr<CaptureControllerImpl> capture_controller =
+ std::make_unique<CaptureControllerImpl>(camera.get());
+ std::unique_ptr<MockTextureRegistrar> texture_registrar =
+ std::make_unique<MockTextureRegistrar>();
+
+ uint64_t mock_texture_id = 1234;
+
+ // Init capture controller once with mocks and tests
+ MockInitCaptureController(capture_controller.get(), texture_registrar.get(),
+ engine.Get(), camera.get(), mock_texture_id);
+
+ // Init capture controller a second time.
+ EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(1);
+
+ bool result = capture_controller->InitCaptureDevice(
+ texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto);
+
+ EXPECT_FALSE(result);
+
+ capture_controller = nullptr;
+ camera = nullptr;
+ texture_registrar = nullptr;
+ engine = nullptr;
+}
+
+TEST(CaptureController, InitCaptureEngineReportsFailure) {
+ ComPtr<MockCaptureEngine> engine = new MockCaptureEngine();
+ std::unique_ptr<MockCamera> camera =
+ std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+ std::unique_ptr<CaptureControllerImpl> capture_controller =
+ std::make_unique<CaptureControllerImpl>(camera.get());
+ std::unique_ptr<MockTextureRegistrar> texture_registrar =
+ std::make_unique<MockTextureRegistrar>();
+
+ ComPtr<MockMediaSource> video_source = new MockMediaSource();
+ ComPtr<MockMediaSource> audio_source = new MockMediaSource();
+
+ capture_controller->SetCaptureEngine(
+ reinterpret_cast<IMFCaptureEngine*>(engine.Get()));
+ capture_controller->SetVideoSource(
+ reinterpret_cast<IMFMediaSource*>(video_source.Get()));
+ capture_controller->SetAudioSource(
+ reinterpret_cast<IMFMediaSource*>(audio_source.Get()));
+
+ EXPECT_CALL(*texture_registrar, RegisterTexture).Times(0);
+ EXPECT_CALL(*texture_registrar, UnregisterTexture).Times(0);
+ EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0);
+
+ EXPECT_CALL(*engine.Get(), Initialize)
+ .Times(1)
+ .WillOnce(Return(E_ACCESSDENIED));
+
+ EXPECT_CALL(*camera,
+ OnCreateCaptureEngineFailed(Eq("Failed to create camera")))
+ .Times(1);
+
+ bool result = capture_controller->InitCaptureDevice(
+ texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto);
+
+ EXPECT_FALSE(result);
+ EXPECT_FALSE(engine->initialized_);
+
+ capture_controller = nullptr;
+ camera = nullptr;
+ texture_registrar = nullptr;
+ engine = nullptr;
+}
+
TEST(CaptureController, StartPreviewStartsProcessingSamples) {
ComPtr<MockCaptureEngine> engine = new MockCaptureEngine();
std::unique_ptr<MockCamera> camera =
diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h
index 0781989..53101f5 100644
--- a/packages/camera/camera_windows/windows/test/mocks.h
+++ b/packages/camera/camera_windows/windows/test/mocks.h
@@ -182,7 +182,7 @@
MOCK_METHOD(camera_windows::CaptureController*, GetCaptureController, (),
(override));
- MOCK_METHOD(void, InitCamera,
+ MOCK_METHOD(bool, InitCamera,
(flutter::TextureRegistrar * texture_registrar,
flutter::BinaryMessenger* messenger, bool record_audio,
ResolutionPreset resolution_preset),
@@ -212,7 +212,7 @@
public:
~MockCaptureController() = default;
- MOCK_METHOD(void, InitCaptureDevice,
+ MOCK_METHOD(bool, InitCaptureDevice,
(flutter::TextureRegistrar * texture_registrar,
const std::string& device_id, bool record_audio,
ResolutionPreset resolution_preset),