diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt
index b15e7ee75..448fa75cc 100644
--- a/Linphone/core/CMakeLists.txt
+++ b/Linphone/core/CMakeLists.txt
@@ -14,6 +14,7 @@ list(APPEND _LINPHONEAPP_SOURCES
core/call-history/CallHistoryProxy.cpp
core/camera/CameraGui.cpp
core/camera/CameraDummy.cpp
+ core/camera/PreviewManager.cpp
core/fps-counter/FPSCounter.cpp
core/friend/FriendCore.cpp
core/friend/FriendGui.cpp
diff --git a/Linphone/core/camera/CameraGui.cpp b/Linphone/core/camera/CameraGui.cpp
index 4dd4bfc0d..f4a8acc25 100644
--- a/Linphone/core/camera/CameraGui.cpp
+++ b/Linphone/core/camera/CameraGui.cpp
@@ -25,6 +25,7 @@
#include "CameraDummy.hpp"
#include "CameraGui.hpp"
+#include "PreviewManager.hpp"
#include "core/App.hpp"
#include "core/call/CallCore.hpp"
#include "core/call/CallGui.hpp"
@@ -33,9 +34,6 @@
DEFINE_ABSTRACT_OBJECT(CameraGui)
-QMutex CameraGui::gPreviewCounterMutex;
-int CameraGui::gPreviewCounter = 0;
-
// =============================================================================
CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) {
mustBeInMainThread(getClassName());
@@ -52,7 +50,6 @@ CameraGui::~CameraGui() {
mustBeInMainThread("~" + getClassName());
mRefreshTimer.stop();
mIsDeleting = true;
- deactivatePreview();
setWindowIdLocation(None);
}
@@ -72,30 +69,15 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer(bool resetWindowId)
// A renderer is mandatory, we cannot wait async.
switch (getSourceLocation()) {
case CorePreview: {
- auto f = [qmlName = mQmlName, &renderer, resetWindowId]() {
- qInfo() << "[Camera] (" << qmlName << ") Setting Camera to Preview";
- auto coreModel = CoreModel::getInstance();
- if (coreModel) {
- auto core = coreModel->getCore();
- if (!core) return;
- if (resetWindowId) {
- renderer = (QQuickFramebufferObject::Renderer *)core->getNativePreviewWindowId();
- if (renderer) core->setNativePreviewWindowId(NULL);
- } else {
- renderer = (QQuickFramebufferObject::Renderer *)core->createNativePreviewWindowId();
- if (renderer) core->setNativePreviewWindowId(renderer);
- }
- }
- };
- if (mIsDeleting) {
- App::postModelBlock(f);
- } else App::postModelSync(f);
+ if (resetWindowId) PreviewManager::getInstance()->unsubscribe(this);
+ else renderer = PreviewManager::getInstance()->subscribe(this);
} break;
case Call: {
auto f = [qmlName = mQmlName, callGui = mCallGui, &renderer, resetWindowId]() {
auto call = callGui->getCore()->getModel()->getMonitor();
if (call) {
- qInfo() << "[Camera] (" << qmlName << ") Setting Camera to CallModel";
+ qInfo() << "[Camera] (" << qmlName << ") " << (resetWindowId ? "Resetting" : "Setting")
+ << " Camera to CallModel";
if (resetWindowId) {
renderer = (QQuickFramebufferObject::Renderer *)call->getNativeVideoWindowId();
if (renderer) call->setNativeVideoWindowId(NULL);
@@ -113,7 +95,8 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer(bool resetWindowId)
auto f = [qmlName = mQmlName, participantDeviceGui = mParticipantDeviceGui, &renderer, resetWindowId]() {
auto device = participantDeviceGui->getCore()->getModel()->getMonitor();
if (device) {
- qInfo() << "[Camera] (" << qmlName << ") Setting Camera to ParticipantDeviceModel";
+ qInfo() << "[Camera] (" << qmlName << ") " << (resetWindowId ? "Resetting" : "Setting")
+ << " Camera to ParticipantDeviceModel";
if (resetWindowId) {
} else {
renderer = (QQuickFramebufferObject::Renderer *)device->createNativeVideoWindowId();
@@ -217,27 +200,6 @@ CameraGui::WindowIdLocation CameraGui::getSourceLocation() const {
return mWindowIdLocation;
}
-void CameraGui::activatePreview() {
- gPreviewCounterMutex.lock();
- if (++gPreviewCounter == 1) {
- auto f = []() { CoreModel::getInstance()->getCore()->enableVideoPreview(true); };
- if (mIsDeleting) App::postModelBlock(f);
- else App::postModelSync(f);
- }
- gPreviewCounterMutex.unlock();
-}
-
-void CameraGui::deactivatePreview() {
- gPreviewCounterMutex.lock();
- if (getSourceLocation() == CorePreview) {
- if (--gPreviewCounter == 0) {
- auto f = []() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); };
- if (mIsDeleting) App::postModelBlock(f);
- else App::postModelSync(f);
- }
- }
- gPreviewCounterMutex.unlock();
-}
void CameraGui::setWindowIdLocation(const WindowIdLocation &location) {
if (mWindowIdLocation != location) {
qDebug() << log()
@@ -245,10 +207,10 @@ void CameraGui::setWindowIdLocation(const WindowIdLocation &location) {
.arg(mQmlName)
.arg(mWindowIdLocation)
.arg(location);
- if (mWindowIdLocation == CorePreview) deactivatePreview();
+ if (mWindowIdLocation == CorePreview) PreviewManager::getInstance()->unsubscribe(this);
resetWindowId(); // Location change: Reset old window ID.
mWindowIdLocation = location;
- if (mWindowIdLocation == CorePreview) activatePreview();
+ if (mWindowIdLocation == CorePreview) PreviewManager::getInstance()->subscribe(this);
update();
// if (mWindowIdLocation == WindowIdLocation::CorePreview) {
// mLastVideoDefinition =
diff --git a/Linphone/core/camera/CameraGui.hpp b/Linphone/core/camera/CameraGui.hpp
index 764b90de8..f45f4bfb8 100644
--- a/Linphone/core/camera/CameraGui.hpp
+++ b/Linphone/core/camera/CameraGui.hpp
@@ -55,9 +55,6 @@ public:
Q_INVOKABLE void resetWindowId() const; // const to be used from createRenderer()
void checkVideoDefinition();
- static QMutex gPreviewCounterMutex;
- static int gPreviewCounter;
-
bool getIsReady() const;
void setIsReady(bool isReady);
void isReady();
@@ -74,8 +71,6 @@ public:
WindowIdLocation getSourceLocation() const;
void setWindowIdLocation(const WindowIdLocation &location);
- void activatePreview();
- void deactivatePreview();
void updateWindowIdLocation();
void removeParticipantDeviceModel();
void removeCallModel();
diff --git a/Linphone/core/camera/PreviewManager.cpp b/Linphone/core/camera/PreviewManager.cpp
new file mode 100644
index 000000000..b3b70231a
--- /dev/null
+++ b/Linphone/core/camera/PreviewManager.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2010-2024 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-desktop
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+#include "../App.hpp"
+#include "PreviewManager.hpp"
+
+// =============================================================================
+PreviewManager *PreviewManager::gInstance = nullptr;
+
+PreviewManager::PreviewManager(QObject *parent) : QObject(parent) {
+}
+
+PreviewManager::~PreviewManager() {
+}
+
+PreviewManager *PreviewManager::getInstance() {
+ if (gInstance) return gInstance;
+ else {
+ gInstance = new PreviewManager();
+ return gInstance;
+ }
+}
+
+QQuickFramebufferObject::Renderer *PreviewManager::subscribe(const CameraGui *candidate) {
+ QQuickFramebufferObject::Renderer *renderer = nullptr;
+ mCounterMutex.lock();
+
+ if (mCandidates.size() == 0) {
+ activate();
+ }
+ auto itCandidate =
+ std::find_if(mCandidates.begin(), mCandidates.end(),
+ [candidate](const QPair &item) {
+ return item.first == candidate;
+ });
+ if (itCandidate == mCandidates.end()) {
+ connect(candidate, &QObject::destroyed, this, qOverload(&PreviewManager::unsubscribe));
+ mCandidates.append({candidate, nullptr});
+ itCandidate = mCandidates.end() - 1;
+ }
+ App::postModelSync([&renderer, isFirst = (itCandidate == mCandidates.begin())]() {
+ renderer =
+ (QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId();
+ if (isFirst) CoreModel::getInstance()->getCore()->setNativePreviewWindowId(renderer);
+ });
+ itCandidate->second = renderer;
+ mCounterMutex.unlock();
+ return renderer;
+}
+
+void PreviewManager::unsubscribe(const CameraGui *candidate) { // If nullptr, Use of sender()
+ mCounterMutex.lock();
+ auto itCandidate = std::find_if(mCandidates.begin(), mCandidates.end(),
+ [candidate = (candidate ? candidate : sender())](
+ const QPair &item) {
+ return item.first == candidate;
+ });
+ if (itCandidate != mCandidates.end()) {
+ disconnect(candidate, nullptr, this, nullptr);
+ if (mCandidates.size() == 1) {
+ mCandidates.erase(itCandidate);
+ deactivate();
+ } else if (mCandidates.begin() == itCandidate) {
+ mCandidates.erase(itCandidate);
+ qWarning() << "Update " << mCandidates.first().first->getQmlName();
+ App::postModelSync([renderer = mCandidates.first().second]() {
+ CoreModel::getInstance()->getCore()->setNativePreviewWindowId(renderer);
+ });
+ } else {
+ mCandidates.erase(itCandidate);
+ }
+ }
+ mCounterMutex.unlock();
+}
+
+void PreviewManager::unsubscribe(QObject *sender) {
+ unsubscribe(dynamic_cast(sender));
+}
+
+void PreviewManager::activate() {
+ App::postModelSync([]() { CoreModel::getInstance()->getCore()->enableVideoPreview(true); });
+}
+
+void PreviewManager::deactivate() {
+ App::postModelSync([]() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); });
+}
diff --git a/Linphone/core/camera/PreviewManager.hpp b/Linphone/core/camera/PreviewManager.hpp
new file mode 100644
index 000000000..b1870f71d
--- /dev/null
+++ b/Linphone/core/camera/PreviewManager.hpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-desktop
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef PREVIEW_MANAGER_H_
+#define PREVIEW_MANAGER_H_
+
+#include "CameraGui.hpp"
+#include
+#include
+#include
+#include
+
+// Manage the SDK preview as a singleton.
+// The goal is to process the limitation that only one preview can be displayed.
+// On asynchronized application, the destruction of a previous Preview can be done AFTER the creation on a new Preview
+// Sticker.
+
+// =============================================================================
+
+class PreviewManager : public QObject {
+ Q_OBJECT
+public:
+ PreviewManager(QObject *parent = nullptr);
+ virtual ~PreviewManager();
+
+ static PreviewManager *getInstance();
+
+ QQuickFramebufferObject::Renderer *subscribe(const CameraGui *candidate);
+ void unsubscribe(const CameraGui *candidate);
+
+ void activate();
+ void deactivate();
+public slots:
+ void unsubscribe(QObject *sender);
+
+private:
+ QMutex mCounterMutex;
+ QList> mCandidates;
+ static PreviewManager *gInstance;
+ QQuickFramebufferObject::Renderer *mPreviewRenderer = nullptr;
+};
+
+#endif