Conference, ActiveSpeaker, Camera

This commit is contained in:
Julien Wadel 2024-03-25 19:06:23 +01:00
parent 2770076a44
commit 10427b5288
33 changed files with 1385 additions and 697 deletions

View file

@ -50,6 +50,7 @@
#include "core/login/LoginPage.hpp" #include "core/login/LoginPage.hpp"
#include "core/notifier/Notifier.hpp" #include "core/notifier/Notifier.hpp"
#include "core/participant/ParticipantDeviceCore.hpp" #include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceProxy.hpp"
#include "core/participant/ParticipantGui.hpp" #include "core/participant/ParticipantGui.hpp"
#include "core/participant/ParticipantProxy.hpp" #include "core/participant/ParticipantProxy.hpp"
#include "core/phone-number/PhoneNumber.hpp" #include "core/phone-number/PhoneNumber.hpp"
@ -217,6 +218,10 @@ void App::initCppInterfaces() {
qmlRegisterType<FPSCounter>(Constants::MainQmlUri, 1, 0, "FPSCounter"); qmlRegisterType<FPSCounter>(Constants::MainQmlUri, 1, 0, "FPSCounter");
qmlRegisterType<TimeZoneProxy>(Constants::MainQmlUri, 1, 0, "TimeZoneProxy"); qmlRegisterType<TimeZoneProxy>(Constants::MainQmlUri, 1, 0, "TimeZoneProxy");
qmlRegisterType<ParticipantDeviceGui>(Constants::MainQmlUri, 1, 0, "ParticipantDeviceGui");
qmlRegisterType<ParticipantDeviceProxy>(Constants::MainQmlUri, 1, 0, "ParticipantDeviceProxy");
LinphoneEnums::registerMetaTypes(); LinphoneEnums::registerMetaTypes();
} }

View file

@ -50,8 +50,9 @@ list(APPEND _LINPHONEAPP_SOURCES
core/participant/ParticipantCore.cpp core/participant/ParticipantCore.cpp
core/participant/ParticipantGui.cpp core/participant/ParticipantGui.cpp
core/participant/ParticipantDeviceCore.cpp core/participant/ParticipantDeviceCore.cpp
# core/participant/ParticipantDeviceList.cpp core/participant/ParticipantDeviceGui.cpp
# core/participant/ParticipantDeviceProxy.cpp core/participant/ParticipantDeviceList.cpp
core/participant/ParticipantDeviceProxy.cpp
core/participant/ParticipantList.cpp core/participant/ParticipantList.cpp
core/participant/ParticipantProxy.cpp core/participant/ParticipantProxy.cpp
) )

View file

@ -380,10 +380,15 @@ ConferenceGui *CallCore::getConferenceGui() const {
return mConference ? new ConferenceGui(mConference) : nullptr; return mConference ? new ConferenceGui(mConference) : nullptr;
} }
QSharedPointer<ConferenceCore> CallCore::getConferenceCore() const {
return mConference;
}
void CallCore::setConference(const QSharedPointer<ConferenceCore> &conference) { void CallCore::setConference(const QSharedPointer<ConferenceCore> &conference) {
if (mConference != conference) { if (mConference != conference) {
mConference = conference; mConference = conference;
mIsConference = (mConference != nullptr); mIsConference = (mConference != nullptr);
qDebug() << "[CallCore] Set conference : " << mConference;
emit conferenceChanged(); emit conferenceChanged();
} }
} }

View file

@ -103,6 +103,7 @@ public:
bool isConference() const; bool isConference() const;
ConferenceGui *getConferenceGui() const; ConferenceGui *getConferenceGui() const;
QSharedPointer<ConferenceCore> getConferenceCore() const;
void setConference(const QSharedPointer<ConferenceCore> &conference); void setConference(const QSharedPointer<ConferenceCore> &conference);
QString getLocalSas(); QString getLocalSas();

View file

@ -24,7 +24,6 @@
DEFINE_ABSTRACT_OBJECT(CallGui) DEFINE_ABSTRACT_OBJECT(CallGui)
CallGui::CallGui(QSharedPointer<CallCore> core) { CallGui::CallGui(QSharedPointer<CallCore> core) {
qDebug() << "[CallGui] new" << this;
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core; mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
@ -32,7 +31,6 @@ CallGui::CallGui(QSharedPointer<CallCore> core) {
CallGui::~CallGui() { CallGui::~CallGui() {
mustBeInMainThread("~" + getClassName()); mustBeInMainThread("~" + getClassName());
qDebug() << "[CallGui] delete" << this;
} }
CallCore *CallGui::getCore() const { CallCore *CallGui::getCore() const {

View file

@ -28,9 +28,14 @@
#include "core/App.hpp" #include "core/App.hpp"
#include "core/call/CallCore.hpp" #include "core/call/CallCore.hpp"
#include "core/call/CallGui.hpp" #include "core/call/CallGui.hpp"
#include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
DEFINE_ABSTRACT_OBJECT(CameraGui) DEFINE_ABSTRACT_OBJECT(CameraGui)
QMutex CameraGui::mPreviewCounterMutex;
int CameraGui::mPreviewCounter = 0;
// ============================================================================= // =============================================================================
CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) { CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) {
mustBeInMainThread(getClassName()); mustBeInMainThread(getClassName());
@ -45,38 +50,14 @@ CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) {
// TODO : Deactivate only if there are no previews to display (Could be open in settings and calls) // TODO : Deactivate only if there are no previews to display (Could be open in settings and calls)
CameraGui::~CameraGui() { CameraGui::~CameraGui() {
mustBeInMainThread("~" + getClassName()); mustBeInMainThread("~" + getClassName());
mRefreshTimer.stop();
App::postModelSync([this]() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); }); App::postModelSync([this]() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); });
} }
QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const { QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const {
QQuickFramebufferObject::Renderer *renderer = NULL; auto renderer = createRenderer(false);
// A renderer is mandatory, we cannot wait async.
switch (getSourceLocation()) {
case CorePreview:
App::postModelSync([this, &renderer]() {
auto coreModel = CoreModel::getInstance();
if (coreModel) {
auto core = coreModel->getCore();
if (!core) return;
core->enableVideoPreview(true);
renderer = (QQuickFramebufferObject::Renderer *)core->createNativePreviewWindowId();
if (renderer) core->setNativePreviewWindowId(renderer);
}
});
break;
case Call:
App::postModelSync([this, &renderer]() {
auto call = mCallGui->getCore()->getModel()->getMonitor();
if (call) {
// qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to CallModel";
renderer = (QQuickFramebufferObject::Renderer *)call->createNativeVideoWindowId();
if (renderer) call->setNativeVideoWindowId(renderer);
}
});
default: {
}
}
if (!renderer) { if (!renderer) {
qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to Dummy, " << getSourceLocation();
QTimer::singleShot(1, this, &CameraGui::isNotReady); QTimer::singleShot(1, this, &CameraGui::isNotReady);
renderer = new CameraDummy(); // Used to fill a renderer to avoid pushing a NULL. renderer = new CameraDummy(); // Used to fill a renderer to avoid pushing a NULL.
QTimer::singleShot(1000, this, &CameraGui::requestNewRenderer); QTimer::singleShot(1000, this, &CameraGui::requestNewRenderer);
@ -84,6 +65,88 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const {
return renderer; return renderer;
} }
QQuickFramebufferObject::Renderer *CameraGui::createRenderer(bool resetWindowId) const {
QQuickFramebufferObject::Renderer *renderer = NULL;
// A renderer is mandatory, we cannot wait async.
switch (getSourceLocation()) {
case CorePreview:
App::postModelSync([this, &renderer, resetWindowId]() {
qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to Preview";
auto coreModel = CoreModel::getInstance();
if (coreModel) {
auto core = coreModel->getCore();
if (!core) return;
core->enableVideoPreview(true);
if (resetWindowId) {
renderer = (QQuickFramebufferObject::Renderer *)core->getNativePreviewWindowId();
if (renderer) core->setNativePreviewWindowId(NULL);
} else {
renderer = (QQuickFramebufferObject::Renderer *)core->createNativePreviewWindowId();
if (renderer) core->setNativePreviewWindowId(renderer);
}
}
});
break;
case Call:
App::postModelSync([this, &renderer, resetWindowId]() {
auto call = mCallGui->getCore()->getModel()->getMonitor();
if (call) {
qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to CallModel";
if (resetWindowId) {
renderer = (QQuickFramebufferObject::Renderer *)call->getNativeVideoWindowId();
if (renderer) call->setNativeVideoWindowId(NULL);
} else {
renderer = (QQuickFramebufferObject::Renderer *)call->createNativeVideoWindowId();
if (renderer) call->setNativeVideoWindowId(renderer);
}
}
});
break;
case Device:
App::postModelSync([this, &renderer, resetWindowId]() {
auto device = mParticipantDeviceGui->getCore()->getModel()->getMonitor();
if (device) {
qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to ParticipantDeviceModel";
if (resetWindowId) {
} else {
renderer = (QQuickFramebufferObject::Renderer *)device->createNativeVideoWindowId();
if (renderer) device->setNativeVideoWindowId(renderer);
}
}
});
break;
default: {
}
}
return renderer;
}
void CameraGui::resetWindowId() const {
createRenderer(true);
}
void CameraGui::checkVideoDefinition() { /*
if (mWindowIdLocation == WindowIdLocation::CorePreview) {
auto videoDefinition = CoreManager::getInstance()->getSettingsModel()->getCurrentPreviewVideoDefinition();
if (videoDefinition["width"] != mLastVideoDefinition["width"] ||
videoDefinition["height"] != mLastVideoDefinition["height"]) {
mLastVideoDefinition = videoDefinition;
emit videoDefinitionChanged();
}
}*/
}
QString CameraGui::getQmlName() const {
return mQmlName;
}
void CameraGui::setQmlName(const QString &name) {
if (name != mQmlName) {
mQmlName = name;
emit qmlNameChanged();
}
}
bool CameraGui::getIsReady() const { bool CameraGui::getIsReady() const {
return mIsReady; return mIsReady;
} }
@ -100,6 +163,21 @@ void CameraGui::isNotReady() {
setIsReady(false); setIsReady(false);
} }
bool CameraGui::getIsPreview() const {
return mIsPreview;
}
void CameraGui::setIsPreview(bool status) {
if (mIsPreview != status) {
mIsPreview = status;
if (mIsPreview) activatePreview();
else deactivatePreview();
// updateWindowIdLocation();
update();
emit isPreviewChanged(status);
}
}
CallGui *CameraGui::getCallGui() const { CallGui *CameraGui::getCallGui() const {
return mCallGui; return mCallGui;
} }
@ -107,11 +185,70 @@ CallGui *CameraGui::getCallGui() const {
void CameraGui::setCallGui(CallGui *callGui) { void CameraGui::setCallGui(CallGui *callGui) {
if (mCallGui != callGui) { if (mCallGui != callGui) {
mCallGui = callGui; mCallGui = callGui;
qDebug() << "Set Call " << mCallGui;
emit callGuiChanged(mCallGui); emit callGuiChanged(mCallGui);
updateWindowIdLocation();
}
}
ParticipantDeviceGui *CameraGui::getParticipantDeviceGui() const {
return mParticipantDeviceGui;
}
void CameraGui::setParticipantDeviceGui(ParticipantDeviceGui *deviceGui) {
if (mParticipantDeviceGui != deviceGui) {
mParticipantDeviceGui = deviceGui;
qDebug() << "Set Device " << mParticipantDeviceGui;
// setIsPreview(mParticipantDeviceGui->getCore()->isLocal());
emit participantDeviceGuiChanged(mParticipantDeviceGui);
updateWindowIdLocation();
} }
} }
CameraGui::WindowIdLocation CameraGui::getSourceLocation() const { CameraGui::WindowIdLocation CameraGui::getSourceLocation() const {
if (mCallGui != nullptr) return Call; return mWindowIdLocation;
else return CorePreview; }
void CameraGui::activatePreview() {
mPreviewCounterMutex.lock();
setWindowIdLocation(WindowIdLocation::CorePreview);
if (++mPreviewCounter == 1) {
App::postModelSync([this]() {
auto coreModel = CoreModel::getInstance();
coreModel->getCore()->enableVideoPreview(true);
});
}
mPreviewCounterMutex.unlock();
}
void CameraGui::deactivatePreview() {
mPreviewCounterMutex.lock();
setWindowIdLocation(WindowIdLocation::None);
if (--mPreviewCounter == 0) {
App::postModelSync([this]() {
auto coreModel = CoreModel::getInstance();
coreModel->getCore()->enableVideoPreview(false);
});
mPreviewCounterMutex.unlock();
}
}
void CameraGui::setWindowIdLocation(const WindowIdLocation &location) {
if (mWindowIdLocation != location) {
qDebug() << "Update Window Id location from " << mWindowIdLocation << " to " << location;
resetWindowId(); // Location change: Reset old window ID.
mWindowIdLocation = location;
update();
// if (mWindowIdLocation == WindowIdLocation::CorePreview) {
// mLastVideoDefinition =
// CoreManager::getInstance()->getSettingsModel()->getCurrentPreviewVideoDefinition(); emit
// videoDefinitionChanged(); mLastVideoDefinitionChecker.start();
// } else mLastVideoDefinitionChecker.stop();
}
}
void CameraGui::updateWindowIdLocation() {
bool useDefaultWindow = true;
if (mCallGui) setWindowIdLocation(WindowIdLocation::Call);
else if (mParticipantDeviceGui && !mParticipantDeviceGui->getCore()->isLocal())
setWindowIdLocation(WindowIdLocation::Device);
else setWindowIdLocation(WindowIdLocation::CorePreview);
} }

View file

@ -28,41 +28,83 @@
#include <QQuickFramebufferObject> #include <QQuickFramebufferObject>
#include <QTimer> #include <QTimer>
#include "core/participant/ParticipantDeviceGui.hpp"
// ============================================================================= // =============================================================================
class CallGui; class CallGui;
class CameraGui : public QQuickFramebufferObject, public AbstractObject { class CameraGui : public QQuickFramebufferObject, public AbstractObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool isReady READ getIsReady NOTIFY isReadyChanged) Q_PROPERTY(CallGui *call READ getCallGui WRITE setCallGui NOTIFY callGuiChanged)
Q_PROPERTY(CallGui *call READ getCallGui WRITE setCallGui NOTIFY callGuiChanged); Q_PROPERTY(ParticipantDeviceGui *participantDevice READ getParticipantDeviceGui WRITE setParticipantDeviceGui NOTIFY
participantDeviceGuiChanged)
Q_PROPERTY(bool isPreview READ getIsPreview WRITE setIsPreview NOTIFY isPreviewChanged)
Q_PROPERTY(bool isReady READ getIsReady WRITE setIsReady NOTIFY isReadyChanged)
// Q_PROPERTY(SoundPlayer * linphonePlayer READ getLinphonePlayer WRITE setLinphonePlayer NOTIFY
// linphonePlayerChanged)
Q_PROPERTY(QString qmlName READ getQmlName WRITE setQmlName NOTIFY qmlNameChanged)
typedef enum { None = -1, CorePreview = 0, Call, Device, Player, Core } WindowIdLocation;
public: public:
CameraGui(QQuickItem *parent = Q_NULLPTR); CameraGui(QQuickItem *parent = Q_NULLPTR);
virtual ~CameraGui(); virtual ~CameraGui();
QQuickFramebufferObject::Renderer *createRenderer() const override; QQuickFramebufferObject::Renderer *createRenderer() const override;
QQuickFramebufferObject::Renderer *createRenderer(bool resetWindowid) const;
Q_INVOKABLE void resetWindowId() const; // const to be used from createRenderer()
void checkVideoDefinition();
static QMutex mPreviewCounterMutex;
static int mPreviewCounter;
bool getIsReady() const; bool getIsReady() const;
void setIsReady(bool isReady); void setIsReady(bool isReady);
void isReady(); void isReady();
void isNotReady(); void isNotReady();
bool getIsPreview() const;
void setIsPreview(bool status);
CallGui *getCallGui() const; CallGui *getCallGui() const;
void setCallGui(CallGui *callGui); void setCallGui(CallGui *callGui);
ParticipantDeviceGui *getParticipantDeviceGui() const;
typedef enum { None = -1, CorePreview = 0, Call, Device, Player, Core } WindowIdLocation; void setParticipantDeviceGui(ParticipantDeviceGui *participantDeviceGui);
QString getQmlName() const;
void setQmlName(const QString &name);
WindowIdLocation getSourceLocation() const; WindowIdLocation getSourceLocation() const;
void setWindowIdLocation(const WindowIdLocation &location);
void activatePreview();
void deactivatePreview();
void updateWindowIdLocation();
void removeParticipantDeviceModel();
void removeCallModel();
void removeLinphonePlayer();
signals: signals:
void requestNewRenderer(); void requestNewRenderer();
void isReadyChanged(bool isReady); void isReadyChanged(bool isReady);
void callGuiChanged(CallGui *callGui); void callGuiChanged(CallGui *callGui);
void isPreviewChanged(bool isPreview);
void isReadyChanged();
void participantDeviceGuiChanged(ParticipantDeviceGui *participantDeviceGui);
void videoDefinitionChanged();
// void linphonePlayerChanged(SoundPlayer * linphonePlayer);
void qmlNameChanged();
private: private:
bool mIsPreview = false;
bool mIsReady = false; bool mIsReady = false;
QTimer mRefreshTimer; QTimer mRefreshTimer;
int mMaxFps = 30; int mMaxFps = 30;
QVariantMap mLastVideoDefinition;
QTimer mLastVideoDefinitionChecker;
CallGui *mCallGui = nullptr; CallGui *mCallGui = nullptr;
ParticipantDeviceGui *mParticipantDeviceGui = nullptr;
QString mQmlName;
WindowIdLocation mWindowIdLocation = None;
mutable bool mIsWindowIdSet = false;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

@ -20,6 +20,7 @@
#include "ConferenceCore.hpp" #include "ConferenceCore.hpp"
#include "core/App.hpp" #include "core/App.hpp"
#include "model/conference/ConferenceModel.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp" #include "tool/thread/SafeConnection.hpp"
@ -35,6 +36,7 @@ ConferenceCore::ConferenceCore(const std::shared_ptr<linphone::Conference> &conf
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
// Should be call from model Thread // Should be call from model Thread
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
mConferenceModel = ConferenceModel::create(conference);
mSubject = Utils::coreStringToAppString(conference->getSubject()); mSubject = Utils::coreStringToAppString(conference->getSubject());
} }
ConferenceCore::~ConferenceCore() { ConferenceCore::~ConferenceCore() {
@ -45,6 +47,12 @@ ConferenceCore::~ConferenceCore() {
void ConferenceCore::setSelf(QSharedPointer<ConferenceCore> me) { void ConferenceCore::setSelf(QSharedPointer<ConferenceCore> me) {
mConferenceModelConnection = QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>>( mConferenceModelConnection = QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>>(
new SafeConnection<ConferenceCore, ConferenceModel>(me, mConferenceModel), &QObject::deleteLater); new SafeConnection<ConferenceCore, ConferenceModel>(me, mConferenceModel), &QObject::deleteLater);
mConferenceModelConnection->makeConnectToModel(
&ConferenceModel::activeSpeakerParticipantDevice,
[this](const std::shared_ptr<linphone::ParticipantDevice> &participantDevice) {
auto device = ParticipantDeviceCore::create(participantDevice);
mConferenceModelConnection->invokeToCore([this, device]() { setActiveSpeaker(device); });
});
// mCallModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneMuted, [this](bool isMuted) { // mCallModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneMuted, [this](bool isMuted) {
// mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); }); // mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); });
// }); // });
@ -79,3 +87,23 @@ void ConferenceCore::setIsReady(bool state) {
isReadyChanged(); isReadyChanged();
} }
} }
std::shared_ptr<ConferenceModel> ConferenceCore::getModel() const {
return mConferenceModel;
}
ParticipantDeviceCore *ConferenceCore::getActiveSpeaker() const {
return mActiveSpeaker.get();
}
ParticipantDeviceGui *ConferenceCore::getActiveSpeakerGui() const {
return new ParticipantDeviceGui(mActiveSpeaker);
}
void ConferenceCore::setActiveSpeaker(const QSharedPointer<ParticipantDeviceCore> &device) {
if (mActiveSpeaker != device) {
mActiveSpeaker = device;
qDebug() << "Changing active speaker to " << device->getAddress();
emit activeSpeakerChanged();
}
}

View file

@ -21,6 +21,8 @@
#ifndef CONFERENCE_CORE_H_ #ifndef CONFERENCE_CORE_H_
#define CONFERENCE_CORE_H_ #define CONFERENCE_CORE_H_
#include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
#include "model/conference/ConferenceModel.hpp" #include "model/conference/ConferenceModel.hpp"
#include "tool/LinphoneEnums.hpp" #include "tool/LinphoneEnums.hpp"
#include "tool/thread/SafeConnection.hpp" #include "tool/thread/SafeConnection.hpp"
@ -38,6 +40,7 @@ public:
// Q_PROPERTY(ParticipantModel* localParticipant READ getLocalParticipant NOTIFY localParticipantChanged) // Q_PROPERTY(ParticipantModel* localParticipant READ getLocalParticipant NOTIFY localParticipantChanged)
Q_PROPERTY(bool isReady MEMBER mIsReady WRITE setIsReady NOTIFY isReadyChanged) Q_PROPERTY(bool isReady MEMBER mIsReady WRITE setIsReady NOTIFY isReadyChanged)
Q_PROPERTY(int participantDeviceCount READ getParticipantDeviceCount NOTIFY participantDeviceCountChanged) Q_PROPERTY(int participantDeviceCount READ getParticipantDeviceCount NOTIFY participantDeviceCountChanged)
Q_PROPERTY(ParticipantDeviceGui *activeSpeaker READ getActiveSpeakerGui NOTIFY activeSpeakerChanged)
// Should be call from model Thread. Will be automatically in App thread after initialization // Should be call from model Thread. Will be automatically in App thread after initialization
static QSharedPointer<ConferenceCore> create(const std::shared_ptr<linphone::Conference> &conference); static QSharedPointer<ConferenceCore> create(const std::shared_ptr<linphone::Conference> &conference);
@ -55,19 +58,26 @@ public:
// std::list<std::shared_ptr<linphone::Participant>> // std::list<std::shared_ptr<linphone::Participant>>
// getParticipantList() const; // SDK exclude me. We want to get ALL participants. // getParticipantList() const; // SDK exclude me. We want to get ALL participants.
int getParticipantDeviceCount() const; int getParticipantDeviceCount() const;
ParticipantDeviceCore *getActiveSpeaker() const;
ParticipantDeviceGui *getActiveSpeakerGui() const;
void setActiveSpeaker(const QSharedPointer<ParticipantDeviceCore> &device);
void setIsReady(bool state); void setIsReady(bool state);
std::shared_ptr<ConferenceModel> getModel() const;
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
signals: signals:
void subjectChanged(); void subjectChanged();
void isReadyChanged(); void isReadyChanged();
void participantDeviceCountChanged(); void participantDeviceCountChanged();
void activeSpeakerChanged();
private: private:
QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>> mConferenceModelConnection; QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>> mConferenceModelConnection;
std::shared_ptr<ConferenceModel> mConferenceModel; std::shared_ptr<ConferenceModel> mConferenceModel;
QSharedPointer<ParticipantDeviceCore> mActiveSpeaker;
bool mIsReady = false; bool mIsReady = false;
QString mSubject; QString mSubject;

View file

@ -25,13 +25,11 @@
DEFINE_ABSTRACT_OBJECT(ConferenceGui) DEFINE_ABSTRACT_OBJECT(ConferenceGui)
ConferenceGui::ConferenceGui() { ConferenceGui::ConferenceGui() {
qDebug() << "[ConferenceGui] new" << this;
mCore = ConferenceCore::create(nullptr); mCore = ConferenceCore::create(nullptr);
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
} }
ConferenceGui::ConferenceGui(QSharedPointer<ConferenceCore> core) { ConferenceGui::ConferenceGui(QSharedPointer<ConferenceCore> core) {
qDebug() << "[ConferenceGui] new" << this;
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core; mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());

View file

@ -25,13 +25,11 @@
DEFINE_ABSTRACT_OBJECT(ConferenceInfoGui) DEFINE_ABSTRACT_OBJECT(ConferenceInfoGui)
ConferenceInfoGui::ConferenceInfoGui() { ConferenceInfoGui::ConferenceInfoGui() {
// qDebug() << "[ConferenceInfoGui] new" << this;
mCore = ConferenceInfoCore::create(nullptr); mCore = ConferenceInfoCore::create(nullptr);
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
} }
ConferenceInfoGui::ConferenceInfoGui(QSharedPointer<ConferenceInfoCore> core) { ConferenceInfoGui::ConferenceInfoGui(QSharedPointer<ConferenceInfoCore> core) {
// qDebug() << "[ConferenceInfoGui] new" << this;
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core; mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
@ -39,7 +37,6 @@ ConferenceInfoGui::ConferenceInfoGui(QSharedPointer<ConferenceInfoCore> core) {
ConferenceInfoGui::~ConferenceInfoGui() { ConferenceInfoGui::~ConferenceInfoGui() {
mustBeInMainThread("~" + getClassName()); mustBeInMainThread("~" + getClassName());
// qDebug() << "[ConferenceInfoGui] delete" << this;
} }
ConferenceInfoCore *ConferenceInfoGui::getCore() const { ConferenceInfoCore *ConferenceInfoGui::getCore() const {

View file

@ -20,6 +20,7 @@
#include "ParticipantDeviceCore.hpp" #include "ParticipantDeviceCore.hpp"
#include "core/App.hpp" #include "core/App.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
@ -31,7 +32,7 @@ ParticipantDeviceCore::create(std::shared_ptr<linphone::ParticipantDevice> devic
QSharedPointer<ParticipantDeviceCore>(new ParticipantDeviceCore(device, isMe, parent), &QObject::deleteLater); QSharedPointer<ParticipantDeviceCore>(new ParticipantDeviceCore(device, isMe, parent), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer); sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread()); sharedPointer->moveToThread(App::getInstance()->thread());
return nullptr; return sharedPointer;
} }
ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr<linphone::ParticipantDevice> &device, ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr<linphone::ParticipantDevice> &device,
@ -48,6 +49,8 @@ ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr<linphone::Par
mParticipantDeviceModel = Utils::makeQObject_ptr<ParticipantDeviceModel>(device); mParticipantDeviceModel = Utils::makeQObject_ptr<ParticipantDeviceModel>(device);
mParticipantDeviceModel->setSelf(mParticipantDeviceModel); mParticipantDeviceModel->setSelf(mParticipantDeviceModel);
mState = LinphoneEnums::fromLinphone(device->getState()); mState = LinphoneEnums::fromLinphone(device->getState());
qDebug() << "Address = " << Utils::coreStringToAppString(device->getAddress()->asStringUriOnly());
mIsLocal = ToolModel::findAccount(device->getAddress()) != nullptr; // TODO set local
// mCall = callModel; // mCall = callModel;
// if (mCall) connect(mCall, &CallModel::statusChanged, this, &ParticipantDeviceCore::onCallStatusChanged); // if (mCall) connect(mCall, &CallModel::statusChanged, this, &ParticipantDeviceCore::onCallStatusChanged);
mIsVideoEnabled = mParticipantDeviceModel->isVideoEnabled(); mIsVideoEnabled = mParticipantDeviceModel->isVideoEnabled();
@ -182,6 +185,10 @@ bool ParticipantDeviceCore::isLocal() const {
return mIsLocal; return mIsLocal;
} }
std::shared_ptr<ParticipantDeviceModel> ParticipantDeviceCore::getModel() const {
return mParticipantDeviceModel;
}
// void ParticipantDeviceCore::updateIsLocal() { // void ParticipantDeviceCore::updateIsLocal() {
// auto deviceAddress = mParticipantDeviceModel->getAddress(); // auto deviceAddress = mParticipantDeviceModel->getAddress();
// auto callAddress = mCall->getConferenceSharedModel()->getConference()->getMe()->getAddress(); // auto callAddress = mCall->getConferenceSharedModel()->getConference()->getMe()->getAddress();
@ -247,4 +254,4 @@ void ParticipantDeviceCore::onStreamAvailabilityChanged(
const std::shared_ptr<linphone::ParticipantDevice> &participantDevice, const std::shared_ptr<linphone::ParticipantDevice> &participantDevice,
bool available, bool available,
linphone::StreamType streamType) { linphone::StreamType streamType) {
} }

View file

@ -74,6 +74,7 @@ public:
LinphoneEnums::ParticipantDeviceState getState() const; LinphoneEnums::ParticipantDeviceState getState() const;
std::shared_ptr<linphone::ParticipantDevice> getDevice(); std::shared_ptr<linphone::ParticipantDevice> getDevice();
std::shared_ptr<ParticipantDeviceModel> getModel() const;
void setPaused(bool paused); void setPaused(bool paused);
void setIsSpeaking(bool speaking); void setIsSpeaking(bool speaking);

View file

@ -0,0 +1,41 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "ParticipantDeviceGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(ParticipantDeviceGui)
ParticipantDeviceGui::ParticipantDeviceGui(QObject *parent) : QObject(parent) {
mCore = ParticipantDeviceCore::create(nullptr);
}
ParticipantDeviceGui::ParticipantDeviceGui(QSharedPointer<ParticipantDeviceCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
}
ParticipantDeviceGui::~ParticipantDeviceGui() {
mustBeInMainThread("~" + getClassName());
}
ParticipantDeviceCore *ParticipantDeviceGui::getCore() const {
return mCore.get();
}

View file

@ -0,0 +1,42 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PARTICIPANT_DEVICE_GUI_H_
#define PARTICIPANT_DEVICE_GUI_H_
#include "ParticipantDeviceCore.hpp"
#include <QObject>
#include <QSharedPointer>
class ParticipantDeviceGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(ParticipantDeviceCore *core READ getCore CONSTANT)
public:
ParticipantDeviceGui(QSharedPointer<ParticipantDeviceCore> core);
ParticipantDeviceGui(QObject *parent = nullptr);
~ParticipantDeviceGui();
ParticipantDeviceCore *getCore() const;
QSharedPointer<ParticipantDeviceCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 Belledonne Communications SARL. * Copyright (c) 2024 Belledonne Communications SARL.
* *
* This file is part of linphone-desktop * This file is part of linphone-desktop
* (see https://www.linphone.org). * (see https://www.linphone.org).
@ -20,21 +20,14 @@
#include "ParticipantDeviceList.hpp" #include "ParticipantDeviceList.hpp"
#include "core/App.hpp" #include "core/App.hpp"
#include "core/participant/ParticipantCore.hpp" #include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <algorithm> #include <algorithm>
DEFINE_ABSTRACT_OBJECT(ParticipantDeviceList) DEFINE_ABSTRACT_OBJECT(ParticipantDeviceList)
QSharedPointer<ParticipantDeviceList>
ParticipantDeviceList::create(const std::shared_ptr<linphone::Participant> &participant) {
auto model = QSharedPointer<ParticipantDeviceList>(new ParticipantDeviceList(participant), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
model->setSelf(model);
return model;
}
QSharedPointer<ParticipantDeviceList> ParticipantDeviceList::create() { QSharedPointer<ParticipantDeviceList> ParticipantDeviceList::create() {
auto model = QSharedPointer<ParticipantDeviceList>(new ParticipantDeviceList(), &QObject::deleteLater); auto model = QSharedPointer<ParticipantDeviceList>(new ParticipantDeviceList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread()); model->moveToThread(App::getInstance()->thread());
@ -42,302 +35,112 @@ QSharedPointer<ParticipantDeviceList> ParticipantDeviceList::create() {
return model; return model;
} }
ParticipantDeviceList::ParticipantDeviceList(const std::shared_ptr<linphone::Participant> &participant, QObject *parent) QSharedPointer<ParticipantDeviceList>
: ListProxy(parent) { ParticipantDeviceList::create(const std::shared_ptr<ConferenceModel> &conferenceModel) {
std::list<std::shared_ptr<linphone::ParticipantDevice>> devices = participant->getDevices(); auto model = create();
for (auto device : devices) { model->setConferenceModel(conferenceModel);
auto deviceModel = ParticipantDeviceCore::create(device, isMe(device)); return model;
// connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(),
// &ParticipantDeviceCore::onSecurityLevelChanged);
connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this,
&ParticipantDeviceList::onParticipantDeviceSpeaking);
mList << deviceModel;
}
mInitialized = true;
} }
ParticipantDeviceList::ParticipantDeviceList(QObject *parent) { ParticipantDeviceList::ParticipantDeviceList() {
mustBeInMainThread(getClassName()); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
} }
// ParticipantDeviceList::ParticipantDeviceList(CallModel *callModel, QObject *parent) : ProxyListModel(parent) {
// if (callModel && callModel->isConference()) {
// mCallModel = callModel;
// connect(mCallModel, &CallModel::conferenceModelChanged, this, &ParticipantDeviceList::onConferenceModelChanged);
// initConferenceModel();
// }
// }
ParticipantDeviceList::~ParticipantDeviceList() { ParticipantDeviceList::~ParticipantDeviceList() {
mustBeInMainThread(getClassName()); }
QList<QSharedPointer<ParticipantDeviceCore>>
ParticipantDeviceList::buildDevices(const std::shared_ptr<ConferenceModel> &conferenceModel) const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
QList<QSharedPointer<ParticipantDeviceCore>> devices;
auto lDevices = conferenceModel->getMonitor()->getParticipantDeviceList();
bool haveMe = false;
for (auto device : lDevices) {
auto deviceCore = ParticipantDeviceCore::create(device);
devices << deviceCore;
if (deviceCore->isMe()) haveMe = true;
}
if (!haveMe) {
}
return devices;
}
QSharedPointer<ParticipantDeviceCore> ParticipantDeviceList::getMe() const {
if (mList.size() > 0) {
return mList[0].objectCast<ParticipantDeviceCore>();
} else return nullptr;
}
void ParticipantDeviceList::setDevices(QList<QSharedPointer<ParticipantDeviceCore>> devices) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
add(devices);
qDebug() << "[ParticipantDeviceList] : add " << devices.size() << " devices";
}
void ParticipantDeviceList::setConferenceModel(const std::shared_ptr<ConferenceModel> &conferenceModel) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
mConferenceModel = conferenceModel;
qDebug() << "[ParticipantDeviceList] : set Conference " << mConferenceModel.get();
if (mConferenceModelConnection->mCore.lock()) { // Unsure to get myself
auto oldConnect = mConferenceModelConnection->mCore; // Setself rebuild safepointer
setSelf(mConferenceModelConnection->mCore.mQData); // reset connections
oldConnect.unlock();
}
beginResetModel();
mList.clear();
endResetModel();
if (mConferenceModel) {
qDebug() << "[ParticipantDeviceList] : request devices";
mConferenceModelConnection->invokeToModel([this]() {
qDebug() << "[ParticipantDeviceList] : build devices";
auto devices = buildDevices(mConferenceModel);
mConferenceModelConnection->invokeToCore([this, devices]() {
qDebug() << "[ParticipantDeviceList] : set devices";
setDevices(devices);
});
});
}
} }
void ParticipantDeviceList::setSelf(QSharedPointer<ParticipantDeviceList> me) { void ParticipantDeviceList::setSelf(QSharedPointer<ParticipantDeviceList> me) {
} if (mConferenceModelConnection) mConferenceModelConnection->disconnect();
mConferenceModelConnection = QSharedPointer<SafeConnection<ParticipantDeviceList, ConferenceModel>>(
void ParticipantDeviceList::initConferenceModel() { new SafeConnection<ParticipantDeviceList, ConferenceModel>(me, mConferenceModel), &QObject::deleteLater);
// if (!mInitialized && mCallModel) { if (mConferenceModel) {
// auto conferenceModel = mCallModel->getConferenceSharedModel(); mConferenceModelConnection->makeConnectToModel(
// if (conferenceModel) { &ConferenceModel::participantDeviceAdded,
// updateDevices(conferenceModel->getConference()->getMe()->getDevices(), true); [this](const std::shared_ptr<linphone::ParticipantDevice> &device) {
// updateDevices(conferenceModel->getConference()->getParticipantDeviceList(), false); auto deviceCore = ParticipantDeviceCore::create(device);
mConferenceModelConnection->invokeToCore([this, deviceCore]() {
// qDebug() << "Conference have " << mList.size() << " devices"; qDebug() << "[ParticipantDeviceList] : add a device";
// connect(conferenceModel.get(), &ConferenceModel::activeSpeakerParticipantDevice, this, this->add(deviceCore);
// &ParticipantDeviceList::onActiveSpeakerParticipantDevice); });
// connect(conferenceModel.get(), &ConferenceModel::participantAdded, this, });
// &ParticipantDeviceList::onParticipantAdded); mConferenceModelConnection->makeConnectToModel(
// connect(conferenceModel.get(), &ConferenceModel::participantRemoved, this, &ConferenceModel::conferenceStateChanged, [this](linphone::Conference::State state) {
// &ParticipantDeviceList::onParticipantRemoved); qDebug() << "[ParticipantDeviceList] new state = " << (int)state;
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceAdded, this, if (state == linphone::Conference::State::Created) {
// &ParticipantDeviceList::onParticipantDeviceAdded); qDebug() << "[ParticipantDeviceList] : build devices";
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceRemoved, this, auto devices = buildDevices(mConferenceModel);
// &ParticipantDeviceList::onParticipantDeviceRemoved); mConferenceModelConnection->invokeToCore([this, devices]() {
// connect(conferenceModel.get(), &ConferenceModel::conferenceStateChanged, this, qDebug() << "[ParticipantDeviceList] : set devices" << devices.size();
// &ParticipantDeviceList::onConferenceStateChanged); setDevices(devices);
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceMediaCapabilityChanged, this, });
// &ParticipantDeviceList::onParticipantDeviceMediaCapabilityChanged); }
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceMediaAvailabilityChanged, this, });
// &ParticipantDeviceList::onParticipantDeviceMediaAvailabilityChanged); mConferenceModelConnection->makeConnectToCore(
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceIsSpeakingChanged, this, &ParticipantDeviceList::lSetConferenceModel,
// &ParticipantDeviceList::onParticipantDeviceIsSpeakingChanged); [this](const std::shared_ptr<ConferenceModel> &conferenceModel) {
// mActiveSpeaker = get(conferenceModel->getConference()->getActiveSpeakerParticipantDevice()); mConferenceModelConnection->invokeToCore(
// mInitialized = true; [this, conferenceModel]() { setConferenceModel(conferenceModel); });
// } });
// }
}
void ParticipantDeviceList::updateDevices(std::shared_ptr<linphone::Participant> participant) {
std::list<std::shared_ptr<linphone::ParticipantDevice>> devices = participant->getDevices();
bool meAdded = false;
beginResetModel();
qDebug() << "Update devices from participant";
mList.clear();
for (auto device : devices) {
bool addMe = isMe(device);
auto deviceModel = ParticipantDeviceCore::create(device, addMe);
// connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(),
// &ParticipantDeviceCore::onSecurityLevelChanged);
connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this,
&ParticipantDeviceList::onParticipantDeviceSpeaking);
mList << deviceModel;
if (addMe) meAdded = true;
}
endResetModel();
if (meAdded) emit meChanged();
}
void ParticipantDeviceList::updateDevices(const std::list<QSharedPointer<ParticipantDeviceCore>> &devices,
const bool &isMe) {
for (auto device : devices) {
add(device);
} }
} }
bool ParticipantDeviceList::add(const QSharedPointer<ParticipantDeviceCore> &deviceToAdd) { QVariant ParticipantDeviceList::data(const QModelIndex &index, int role) const {
auto deviceToAddAddr = deviceToAdd->getAddress(); int row = index.row();
int row = 0; if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
qDebug() << "Adding device " << deviceToAdd->getAddress(); if (role == Qt::DisplayRole)
for (auto item : mList) { return QVariant::fromValue(new ParticipantDeviceGui(mList[row].objectCast<ParticipantDeviceCore>()));
auto deviceCore = item.objectCast<ParticipantDeviceCore>(); return QVariant();
if (deviceCore == deviceToAdd) {
qDebug() << "Device already exist. Send video update event";
// deviceCore->updateVideoEnabled();
return false;
} else if (deviceToAddAddr == deviceCore->getAddress()) { // Address is the same (same device) but the model
// is using another linphone object. Replace it.
qDebug() << "Replacing device : Device exists but the model is using another linphone object.";
// deviceCore->updateVideoEnabled();
removeRow(row);
break;
}
++row;
}
bool addMe = isMe(deviceToAdd);
auto deviceModel = ParticipantDeviceCore::create(deviceToAdd, addMe);
// connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(),
// &ParticipantDeviceCore::onSecurityLevelChanged);
connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this,
&ParticipantDeviceList::onParticipantDeviceSpeaking);
ListProxy::add<ParticipantDeviceCore>(deviceModel);
qDebug() << "Device added. Count=" << mList.count();
QStringList debugDevices;
for (auto i : mList) {
auto item = i.objectCast<ParticipantDeviceCore>();
debugDevices.push_back(item->getAddress());
}
qDebug() << debugDevices.join("\n");
if (addMe) {
qDebug() << "Added a me device";
emit meChanged();
} else if (mList.size() == 1 ||
(mList.size() == 2 && isMe(mList.front().objectCast<ParticipantDeviceCore>()->getDevice()))) {
mActiveSpeaker = mList.back().objectCast<ParticipantDeviceCore>();
emit activeSpeakerChanged();
}
return true;
}
bool ParticipantDeviceList::remove(std::shared_ptr<const linphone::ParticipantDevice> deviceToRemove) {
int row = 0;
for (auto item : mList) {
auto device = item.objectCast<ParticipantDeviceCore>();
if (device->getDevice() == deviceToRemove) {
// device->updateVideoEnabled();
removeRow(row);
return true;
} else ++row;
}
return false;
}
QSharedPointer<ParticipantDeviceCore>
ParticipantDeviceList::get(std::shared_ptr<const linphone::ParticipantDevice> deviceToGet, int *index) {
int row = 0;
for (auto item : mList) {
auto device = item.objectCast<ParticipantDeviceCore>();
if (device->getDevice() == deviceToGet) {
if (index) *index = row;
return device;
} else ++row;
}
return nullptr;
}
QSharedPointer<ParticipantDeviceCore> ParticipantDeviceList::getMe(int *index) const {
int row = 0;
for (auto item : mList) {
auto device = item.objectCast<ParticipantDeviceCore>();
if (device->isMe() && device->isLocal()) {
if (index) *index = row;
return device;
} else ++row;
}
return nullptr;
}
ParticipantDeviceCore *ParticipantDeviceList::getActiveSpeakerModel() const {
return mActiveSpeaker.get();
}
bool ParticipantDeviceList::isMe(std::shared_ptr<linphone::ParticipantDevice> deviceToCheck) const {
// if (mCallModel) {
// auto devices = mCallModel->getConferenceSharedModel()->getConference()->getMe()->getDevices();
// auto deviceToCheckAddress = deviceToCheck->getAddress();
// for (auto device : devices) {
// if (deviceToCheckAddress == device->getAddress()) return true;
// }
// }
return false;
}
bool ParticipantDeviceList::isMeAlone() const {
for (auto item : mList) {
auto device = item.objectCast<ParticipantDeviceCore>();
if (!isMe(device->getDevice())) return false;
}
return true;
}
void ParticipantDeviceList::onConferenceModelChanged() {
if (!mInitialized) {
initConferenceModel();
}
}
void ParticipantDeviceList::onSecurityLevelChanged(std::shared_ptr<const linphone::Address> device) {
emit securityLevelChanged(device);
}
//----------------------------------------------------------------------------------------------------------
void ParticipantDeviceList::onParticipantAdded(const std::shared_ptr<const linphone::Participant> &participant) {
std::list<std::shared_ptr<linphone::ParticipantDevice>> devices = participant->getDevices();
if (devices.size() == 0)
qDebug() << "Participant has no device. It will not be added : "
<< participant->getAddress()->asString().c_str();
else
for (auto device : devices)
add(device);
}
void ParticipantDeviceList::onParticipantRemoved(const std::shared_ptr<const linphone::Participant> &participant) {
std::list<std::shared_ptr<linphone::ParticipantDevice>> devices = participant->getDevices();
for (auto device : devices)
remove(device);
}
void ParticipantDeviceList::onParticipantDeviceAdded(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
qDebug() << "Adding new device : " << mList.count();
// auto conferenceModel = mCallModel->getConferenceSharedModel();
std::list<std::shared_ptr<linphone::ParticipantDevice>> devices;
for (int i = 0; i < 2; ++i) {
// if (i == 0) devices = conferenceModel->getConference()->getParticipantDeviceList(); // Active devices.
// else devices = conferenceModel->getConference()->getMe()->getDevices();
for (auto realParticipantDevice : devices) {
if (realParticipantDevice == participantDevice) {
add(realParticipantDevice);
return;
}
}
}
qDebug() << "No participant device found from linphone::ParticipantDevice at onParticipantDeviceAdded";
}
void ParticipantDeviceList::onParticipantDeviceRemoved(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
qDebug() << "Removing participant device : " << mList.count();
if (!remove(participantDevice))
qDebug() << "No participant device found from linphone::ParticipantDevice at onParticipantDeviceRemoved";
}
void ParticipantDeviceList::onConferenceStateChanged(linphone::Conference::State newState) {
// if (newState == linphone::Conference::State::Created) {
// if (mCallModel && mCallModel->isConference()) {
// auto conferenceModel = mCallModel->getConferenceSharedModel();
// updateDevices(conferenceModel->getConference()->getMe()->getDevices(), true);
// updateDevices(conferenceModel->getConference()->getParticipantDeviceList(), false);
// }
// emit conferenceCreated();
// }
}
void ParticipantDeviceList::onParticipantDeviceMediaCapabilityChanged(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
// auto device = get(participantDevice);
// if (device) device->updateVideoEnabled();
// else onParticipantDeviceAdded(participantDevice);
// device = get(participantDevice);
// if (device && device->isMe()) { // Capability change for me. Update all videos.
// for (auto item : mList) {
// auto device = item.objectCast<ParticipantDeviceCore>();
// device->updateVideoEnabled();
// }
// }
}
void ParticipantDeviceList::onParticipantDeviceMediaAvailabilityChanged(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
// auto device = get(participantDevice);
// if (device) device->updateVideoEnabled();
// else onParticipantDeviceAdded(participantDevice);
}
void ParticipantDeviceList::onActiveSpeakerParticipantDevice(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
// auto device = get(participantDevice);
// if (device) {
// mActiveSpeaker = device;
// emit activeSpeakerChanged();
// }
}
void ParticipantDeviceList::onParticipantDeviceIsSpeakingChanged(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice, bool isSpeaking) {
auto device = get(participantDevice);
if (device) emit participantSpeaking(device.get());
}
void ParticipantDeviceList::onParticipantDeviceSpeaking() {
} }

View file

@ -22,70 +22,39 @@
#define PARTICIPANT_DEVICE_LIST_H_ #define PARTICIPANT_DEVICE_LIST_H_
#include "../proxy/ListProxy.hpp" #include "../proxy/ListProxy.hpp"
#include "core/call/CallCore.hpp" #include "ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceCore.hpp" #include "model/conference/ConferenceModel.hpp"
#include <QDateTime> #include "tool/AbstractObject.hpp"
#include <QObject> #include "tool/thread/SafeConnection.hpp"
#include <QString>
class ParticipantDeviceList : public ListProxy, public AbstractObject { class ParticipantDeviceList : public ListProxy, public AbstractObject {
Q_OBJECT Q_OBJECT
public: public:
static QSharedPointer<ParticipantDeviceList> create(const std::shared_ptr<linphone::Participant> &participant);
static QSharedPointer<ParticipantDeviceList> create(); static QSharedPointer<ParticipantDeviceList> create();
static QSharedPointer<ParticipantDeviceList> create(const std::shared_ptr<ConferenceModel> &conferenceModel);
ParticipantDeviceList(const std::shared_ptr<linphone::Participant> &participant, QObject *parent = nullptr); ParticipantDeviceList();
// ParticipantDeviceList(CallCore *callCore, QObject *parent = nullptr);
ParticipantDeviceList(QObject *parent = Q_NULLPTR);
~ParticipantDeviceList(); ~ParticipantDeviceList();
QList<QSharedPointer<ParticipantDeviceCore>>
buildDevices(const std::shared_ptr<ConferenceModel> &conferenceModel) const;
QSharedPointer<ParticipantDeviceCore> getMe() const;
void setDevices(QList<QSharedPointer<ParticipantDeviceCore>> devices);
void setConferenceModel(const std::shared_ptr<ConferenceModel> &conferenceModel);
void setSelf(QSharedPointer<ParticipantDeviceList> me); void setSelf(QSharedPointer<ParticipantDeviceList> me);
void initConferenceModel(); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void updateDevices(std::shared_ptr<linphone::Participant> participant);
void updateDevices(const std::list<QSharedPointer<ParticipantDeviceCore>> &devices, const bool &isMe);
bool add(const QSharedPointer<ParticipantDeviceCore> &deviceToAdd);
bool remove(std::shared_ptr<const linphone::ParticipantDevice> deviceToAdd);
QSharedPointer<ParticipantDeviceCore> get(std::shared_ptr<const linphone::ParticipantDevice> deviceToGet,
int *index = nullptr);
QSharedPointer<ParticipantDeviceCore> getMe(int *index = nullptr) const;
ParticipantDeviceCore *getActiveSpeakerModel() const;
bool isMe(std::shared_ptr<linphone::ParticipantDevice> device) const;
bool isMeAlone() const;
public slots:
void onActiveSpeakerParticipantDevice(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void onConferenceModelChanged();
void onSecurityLevelChanged(std::shared_ptr<const linphone::Address> device);
void onParticipantAdded(const std::shared_ptr<const linphone::Participant> &participant);
void onParticipantRemoved(const std::shared_ptr<const linphone::Participant> &participant);
void onParticipantDeviceAdded(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void onParticipantDeviceRemoved(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void onConferenceStateChanged(linphone::Conference::State newState);
void onParticipantDeviceMediaCapabilityChanged(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void onParticipantDeviceMediaAvailabilityChanged(
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void onParticipantDeviceIsSpeakingChanged(const std::shared_ptr<const linphone::ParticipantDevice> &device,
bool isSpeaking);
void onParticipantDeviceSpeaking();
signals: signals:
void activeSpeakerChanged(); void lSetConferenceModel(const std::shared_ptr<ConferenceModel> &conferenceModel);
void securityLevelChanged(std::shared_ptr<const linphone::Address> device);
void participantSpeaking(ParticipantDeviceCore *speakingDevice);
void conferenceCreated();
void meChanged();
private: private:
CallCore *mCallCore = nullptr; std::shared_ptr<ConferenceModel> mConferenceModel;
QSharedPointer<ParticipantDeviceCore> mActiveSpeaker; QSharedPointer<SafeConnection<ParticipantDeviceList, ConferenceModel>> mConferenceModelConnection;
// QList<ParticipantDeviceCore*> mActiveSpeakers;// First item is last speaker
bool mInitialized = false;
QSharedPointer<SafeConnection<ParticipantDeviceList, CallModel>> mModelConnection;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 Belledonne Communications SARL. * Copyright (c) 2024 Belledonne Communications SARL.
* *
* This file is part of linphone-desktop * This file is part of linphone-desktop
* (see https://www.linphone.org). * (see https://www.linphone.org).
@ -26,96 +26,71 @@
// ============================================================================= // =============================================================================
DEFINE_ABSTRACT_OBJECT(ParticipantDeviceProxy)
ParticipantDeviceProxy::ParticipantDeviceProxy(QObject *parent) : SortFilterProxy(parent) { ParticipantDeviceProxy::ParticipantDeviceProxy(QObject *parent) : SortFilterProxy(parent) {
mDeleteSourceModel = true; mParticipants = ParticipantDeviceList::create();
mList = ParticipantDeviceList::create(); connect(mParticipants.get(), &ParticipantDeviceList::countChanged, this, &ParticipantDeviceProxy::meChanged);
setSourceModel(mList.get());
setSourceModel(mParticipants.get());
sort(0); //, Qt::DescendingOrder);
} }
ParticipantDeviceProxy::~ParticipantDeviceProxy() { ParticipantDeviceProxy::~ParticipantDeviceProxy() {
setSourceModel(nullptr);
} }
bool ParticipantDeviceProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { CallGui *ParticipantDeviceProxy::getCurrentCall() const {
const QModelIndex index = mList->index(sourceRow, 0, sourceParent); return mCurrentCall;
const ParticipantDeviceCore *device = index.data().value<ParticipantDeviceCore *>();
return device && (isShowMe() /*|| !(device->isMe() && device->isLocal())*/);
} }
bool ParticipantDeviceProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { void ParticipantDeviceProxy::setCurrentCall(CallGui *call) {
const ParticipantDeviceCore *deviceA = sourceModel()->data(left).value<ParticipantDeviceCore *>(); qDebug() << "[ParticipantDeviceProxy] set current call " << this << " => " << call;
const ParticipantDeviceCore *deviceB = sourceModel()->data(right).value<ParticipantDeviceCore *>(); if (mCurrentCall != call) {
// 'me' at end (for grid). CallCore *callCore = nullptr;
return /*deviceB->isLocal() || !deviceA->isLocal() && deviceB->isMe() ||*/ left.row() < right.row(); if (mCurrentCall) {
} callCore = mCurrentCall->getCore();
//--------------------------------------------------------------------------------- if (callCore) callCore->disconnect(mParticipants.get());
callCore = nullptr;
ParticipantDeviceCore *ParticipantDeviceProxy::getAt(int row) { }
if (row >= 0) { mCurrentCall = call;
QModelIndex sourceIndex = mapToSource(this->index(row, 0)); if (mCurrentCall) callCore = mCurrentCall->getCore();
return sourceModel()->data(sourceIndex).value<ParticipantDeviceCore *>(); if (callCore) {
} else return nullptr; connect(callCore, &CallCore::conferenceChanged, mParticipants.get(), [this]() {
} auto conference = mCurrentCall->getCore()->getConferenceCore();
qDebug() << "[ParticipantDeviceProxy] set conference " << this << " => " << conference;
ParticipantDeviceCore *ParticipantDeviceProxy::getActiveSpeakerModel() { mParticipants->setConferenceModel(conference ? conference->getModel() : nullptr);
return mList->getActiveSpeakerModel(); // mParticipants->lSetConferenceModel(conference ? conference->getModel() : nullptr);
} });
auto conference = callCore->getConferenceCore();
CallModel *ParticipantDeviceProxy::getCallModel() const { qDebug() << "[ParticipantDeviceProxy] set conference " << this << " => " << conference;
return mCallModel; mParticipants->setConferenceModel(conference ? conference->getModel() : nullptr);
} // mParticipants->lSetConferenceModel(conference ? conference->getModel() : nullptr);
}
ParticipantDeviceCore *ParticipantDeviceProxy::getMe() const { emit currentCallChanged();
return mList->getMe().get();
}
bool ParticipantDeviceProxy::isShowMe() const {
return mShowMe;
}
void ParticipantDeviceProxy::connectTo(ParticipantDeviceList *model) {
connect(model, &ParticipantDeviceList::countChanged, this, &ParticipantDeviceProxy::onCountChanged);
connect(model, &ParticipantDeviceList::participantSpeaking, this, &ParticipantDeviceProxy::onParticipantSpeaking);
connect(model, &ParticipantDeviceList::conferenceCreated, this, &ParticipantDeviceProxy::conferenceCreated);
connect(model, &ParticipantDeviceList::meChanged, this, &ParticipantDeviceProxy::meChanged);
connect(model, &ParticipantDeviceList::activeSpeakerChanged, this, &ParticipantDeviceProxy::activeSpeakerChanged);
}
void ParticipantDeviceProxy::setCallModel(CallModel *callModel) {
setFilterType(1);
mCallModel = callModel;
deleteSourceModel();
auto newSourceModel = new ParticipantDeviceList(mCallModel);
connectTo(newSourceModel);
setSourceModel(newSourceModel);
mDeleteSourceModel = true;
sort(0);
emit countChanged();
emit meChanged();
}
// void ParticipantDeviceProxy::setParticipant(ParticipantCore *participantCore) {
// setFilterType(0);
// deleteSourceModel();
// auto newSourceModel = participant->getParticipantDevices().get();
// connectTo(newSourceModel);
// setSourceModel(newSourceModel);
// mDeleteSourceModel = false;
// sort(0);
// emit countChanged();
// emit meChanged();
// }
void ParticipantDeviceProxy::setShowMe(const bool &show) {
if (mShowMe != show) {
mShowMe = show;
emit showMeChanged();
invalidate();
} }
} }
void ParticipantDeviceProxy::onCountChanged() { ParticipantDeviceGui *ParticipantDeviceProxy::getMe() const {
qDebug() << "Count changed : " << getCount(); auto core = mParticipants->getMe();
if (!core) return nullptr;
else {
return new ParticipantDeviceGui(core);
}
} }
void ParticipantDeviceProxy::onParticipantSpeaking(ParticipantDeviceCore *speakingDevice) { void ParticipantDeviceProxy::setMe(ParticipantDeviceGui *me) {
emit participantSpeaking(speakingDevice); }
bool ParticipantDeviceProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
return true;
}
bool ParticipantDeviceProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const {
auto deviceA = sourceModel()->data(left).value<ParticipantDeviceGui *>()->getCore();
auto deviceB = sourceModel()->data(right).value<ParticipantDeviceGui *>()->getCore();
// auto deviceB = getItemAt<ParticipantDeviceList, ParticipantDeviceGui>(right.row())->getCore();
// return deviceB->isLocal() || !deviceA->isLocal() && deviceB->isMe() || left.row() < right.row();
return deviceB->isMe() || (!deviceB->isMe() && left.row() < right.row());
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 Belledonne Communications SARL. * Copyright (c) 2024 Belledonne Communications SARL.
* *
* This file is part of linphone-desktop * This file is part of linphone-desktop
* (see https://www.linphone.org). * (see https://www.linphone.org).
@ -21,64 +21,43 @@
#ifndef PARTICIPANT_DEVICE_PROXY_MODEL_H_ #ifndef PARTICIPANT_DEVICE_PROXY_MODEL_H_
#define PARTICIPANT_DEVICE_PROXY_MODEL_H_ #define PARTICIPANT_DEVICE_PROXY_MODEL_H_
#include <linphone++/linphone.hh>
// =============================================================================
#include "../proxy/SortFilterProxy.hpp" #include "../proxy/SortFilterProxy.hpp"
#include <QDateTime> #include "core/call/CallGui.hpp"
#include <QObject> #include "core/participant/ParticipantDeviceGui.hpp"
#include <QSharedPointer> #include "tool/AbstractObject.hpp"
#include <QString>
class ParticipantDeviceList; class ParticipantDeviceList;
class ParticipantDeviceCore; class ParticipantDeviceGui;
class ParticipantCore;
class CallModel;
class ParticipantDeviceProxy : public SortFilterProxy { class ParticipantDeviceProxy : public SortFilterProxy, public AbstractObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(CallGui *currentCall READ getCurrentCall WRITE setCurrentCall NOTIFY currentCallChanged)
Q_PROPERTY(ParticipantDeviceGui *me READ getMe WRITE setMe NOTIFY meChanged)
public: public:
Q_PROPERTY(CallModel *callModel READ getCallModel WRITE setCallModel NOTIFY callModelChanged) ParticipantDeviceProxy(QObject *parent = Q_NULLPTR);
Q_PROPERTY(bool showMe READ isShowMe WRITE setShowMe NOTIFY showMeChanged)
Q_PROPERTY(ParticipantDeviceCore *me READ getMe NOTIFY meChanged)
Q_PROPERTY(ParticipantDeviceCore *activeSpeaker READ getActiveSpeakerModel NOTIFY activeSpeakerChanged)
ParticipantDeviceProxy(QObject *parent = nullptr);
~ParticipantDeviceProxy(); ~ParticipantDeviceProxy();
Q_INVOKABLE ParticipantDeviceCore *getAt(int row); CallGui *getCurrentCall() const;
ParticipantDeviceCore *getActiveSpeakerModel(); void setCurrentCall(CallGui *callGui);
ParticipantDeviceCore *getMe() const;
CallModel *getCallModel() const; ParticipantDeviceGui *getMe() const;
bool isShowMe() const; void setMe(ParticipantDeviceGui *me);
void setCallModel(CallModel *callModel);
// void setParticipant(ParticipantCore *participantCore);
void setShowMe(const bool &show);
void connectTo(ParticipantDeviceList *model);
public slots:
void onCountChanged();
void onParticipantSpeaking(ParticipantDeviceCore *speakingDevice);
signals:
void activeSpeakerChanged();
void callModelChanged();
void showMeChanged();
void meChanged();
void participantSpeaking(ParticipantDeviceCore *speakingDevice);
void conferenceCreated();
protected: protected:
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
CallModel *mCallModel; signals:
bool mShowMe = true; void lUpdate();
void currentCallChanged();
void meChanged();
QSharedPointer<ParticipantDeviceList> mList; private:
QString mSearchText;
CallGui *mCurrentCall = nullptr;
QSharedPointer<ParticipantDeviceList> mParticipants;
DECLARE_ABSTRACT_OBJECT
}; };
#endif #endif

View file

@ -24,13 +24,20 @@
#include "core/path/Paths.hpp" #include "core/path/Paths.hpp"
#include "model/core/CoreModel.hpp" #include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(ConferenceModel) DEFINE_ABSTRACT_OBJECT(ConferenceModel)
std::shared_ptr<ConferenceModel> ConferenceModel::create(const std::shared_ptr<linphone::Conference> &conference) {
auto model = Utils::makeQObject_ptr<ConferenceModel>(conference);
model->setSelf(model);
return model;
}
ConferenceModel::ConferenceModel(const std::shared_ptr<linphone::Conference> &conference, QObject *parent) ConferenceModel::ConferenceModel(const std::shared_ptr<linphone::Conference> &conference, QObject *parent)
: ::Listener<linphone::Conference, linphone::ConferenceListener>(conference, parent) { : ::Listener<linphone::Conference, linphone::ConferenceListener>(conference, parent) {
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
qDebug() << "[ConferenceModel] new" << this; qDebug() << "[ConferenceModel] new" << this << conference.get();
} }
ConferenceModel::~ConferenceModel() { ConferenceModel::~ConferenceModel() {
@ -136,7 +143,8 @@ void ConferenceModel::onActiveSpeakerParticipantDevice(
const std::shared_ptr<linphone::Conference> &conference, const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) { const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
qDebug() << "onActiveSpeakerParticipantDevice: " << participantDevice->getAddress()->asString().c_str(); qDebug() << "onActiveSpeakerParticipantDevice: " << participantDevice->getAddress()->asString().c_str();
emit activeSpeakerParticipantDevice(participantDevice);
emit activeSpeakerParticipantDevice(conference->getActiveSpeakerParticipantDevice());
} }
void ConferenceModel::onParticipantAdded(const std::shared_ptr<linphone::Conference> &conference, void ConferenceModel::onParticipantAdded(const std::shared_ptr<linphone::Conference> &conference,

View file

@ -35,6 +35,7 @@ class ConferenceModel : public ::Listener<linphone::Conference, linphone::Confer
public: public:
ConferenceModel(const std::shared_ptr<linphone::Conference> &conference, QObject *parent = nullptr); ConferenceModel(const std::shared_ptr<linphone::Conference> &conference, QObject *parent = nullptr);
~ConferenceModel(); ~ConferenceModel();
static std::shared_ptr<ConferenceModel> create(const std::shared_ptr<linphone::Conference> &conference);
void terminate(); void terminate();
@ -105,11 +106,11 @@ private:
const std::shared_ptr<const linphone::AudioDevice> &audioDevice) override; const std::shared_ptr<const linphone::AudioDevice> &audioDevice) override;
signals: signals:
void activeSpeakerParticipantDevice(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice); void activeSpeakerParticipantDevice(const std::shared_ptr<linphone::ParticipantDevice> &participantDevice);
void participantAdded(const std::shared_ptr<const linphone::Participant> &participant); void participantAdded(const std::shared_ptr<linphone::Participant> &participant);
void participantRemoved(const std::shared_ptr<const linphone::Participant> &participant); void participantRemoved(const std::shared_ptr<const linphone::Participant> &participant);
void participantAdminStatusChanged(const std::shared_ptr<const linphone::Participant> &participant); void participantAdminStatusChanged(const std::shared_ptr<const linphone::Participant> &participant);
void participantDeviceAdded(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice); void participantDeviceAdded(const std::shared_ptr<linphone::ParticipantDevice> &participantDevice);
void participantDeviceRemoved(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice); void participantDeviceRemoved(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void participantDeviceStateChanged(const std::shared_ptr<linphone::Conference> &conference, void participantDeviceStateChanged(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &device, const std::shared_ptr<const linphone::ParticipantDevice> &device,

View file

@ -159,3 +159,43 @@ QSharedPointer<CallCore> ToolModel::createCall(const QString &sipAddress,
// CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); // CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
*/ */
} }
std::shared_ptr<linphone::Account> ToolModel::findAccount(const std::shared_ptr<const linphone::Address> &address) {
std::shared_ptr<linphone::Account> account;
for (auto item : CoreModel::getInstance()->getCore()->getAccountList()) {
if (item->getContactAddress()->weakEqual(address)) {
account = item;
break;
}
}
return account;
}
bool ToolModel::isMe(const QString &address) {
bool isMe = false;
auto linAddr = ToolModel::interpretUrl(address);
if (!CoreModel::getInstance()->getCore()->getDefaultAccount()) {
// for (auto &account : CoreModel::getInstance()->getCore()->getAccountList()) {
// if (account->getContactAddress()->weakEqual(linAddr)) return true;
// }
isMe = false;
} else {
auto accountAddr = CoreModel::getInstance()->getCore()->getDefaultAccount()->getContactAddress();
isMe = linAddr && accountAddr ? accountAddr->weakEqual(linAddr) : false;
}
return isMe;
}
bool ToolModel::isMe(const std::shared_ptr<const linphone::Address> &address) {
auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
if (!currentAccount) { // Default account is selected : Me is all local accounts.
return findAccount(address) != nullptr;
} else return address ? currentAccount->getContactAddress()->weakEqual(address) : false;
}
bool ToolModel::isLocal(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &device) {
auto deviceAddress = device->getAddress();
auto callAddress = conference->getMe()->getAddress();
auto gruuAddress = findAccount(callAddress)->getContactAddress();
return deviceAddress->equal(gruuAddress);
}

View file

@ -37,6 +37,11 @@ public:
static std::shared_ptr<linphone::Address> interpretUrl(const QString &address); static std::shared_ptr<linphone::Address> interpretUrl(const QString &address);
static std::shared_ptr<linphone::FriendPhoneNumber> makeLinphoneNumber(const QString &label, const QString &number); static std::shared_ptr<linphone::FriendPhoneNumber> makeLinphoneNumber(const QString &label, const QString &number);
static std::shared_ptr<linphone::AudioDevice> findAudioDevice(const QString &id); static std::shared_ptr<linphone::AudioDevice> findAudioDevice(const QString &id);
static std::shared_ptr<linphone::Account> findAccount(const std::shared_ptr<const linphone::Address> &address);
static bool isMe(const QString &address);
static bool isMe(const std::shared_ptr<const linphone::Address> &address);
static bool isLocal(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &device);
static QString getDisplayName(const std::shared_ptr<const linphone::Address> &address); static QString getDisplayName(const std::shared_ptr<const linphone::Address> &address);
static QString getDisplayName(QString address); static QString getDisplayName(QString address);

View file

@ -1195,18 +1195,7 @@ int Utils::getYear(const QDate &date) {
bool Utils::isMe(const QString &address) { bool Utils::isMe(const QString &address) {
bool isMe = false; bool isMe = false;
App::postModelSync([&isMe, address]() { App::postModelSync([&isMe, address]() { isMe = ToolModel::isMe(address); });
auto linAddr = ToolModel::interpretUrl(address);
if (!CoreModel::getInstance()->getCore()->getDefaultAccount()) {
// for (auto &account : CoreModel::getInstance()->getCore()->getAccountList()) {
// if (account->getContactAddress()->weakEqual(linAddr)) return true;
// }
isMe = false;
} else {
auto accountAddr = CoreModel::getInstance()->getCore()->getDefaultAccount()->getContactAddress();
isMe = linAddr && accountAddr ? accountAddr->weakEqual(linAddr) : false;
}
});
return isMe; return isMe;
} }
// QDateTime dateTime(QDateTime::fromString(date, "yyyy-MM-dd hh:mm:ss")); // QDateTime dateTime(QDateTime::fromString(date, "yyyy-MM-dd hh:mm:ss"));

View file

@ -97,6 +97,14 @@ public:
return connect(mCoreObject, signal, mModelObject, slot, Qt::DirectConnection); return connect(mCoreObject, signal, mModelObject, slot, Qt::DirectConnection);
} }
inline void disconnect() {
if (!tryLock()) // To avoid disconnections while being in call.
return; // Return to avoid to disconnect other connections than the pair.
QObject::disconnect(mModelObject, nullptr, mCoreObject, nullptr);
QObject::disconnect(mCoreObject, nullptr, mModelObject, nullptr);
unlock();
}
template <typename Func, typename... Args> template <typename Func, typename... Args>
void invokeToModel(Func &&callable, Args &&...args) { void invokeToModel(Func &&callable, Args &&...args) {
if (!tryLock()) return; if (!tryLock()) return;

View file

@ -28,11 +28,12 @@ Window {
if (call && !conferenceInfo) middleItemStackView.replace(inCallItem) if (call && !conferenceInfo) middleItemStackView.replace(inCallItem)
} }
Component.onCompleted: if (call && !conferenceInfo) middleItemStackView.replace(inCallItem) Component.onCompleted: if (call && !conferenceInfo) middleItemStackView.replace(inCallItem)
property var callObj
function joinConference(withVideo) { function joinConference(withVideo) {
if (!conferenceInfo || conferenceInfo.core.uri.length === 0) UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La conférence n'a pas pu démarrer en raison d'une erreur d'uri.")) if (!conferenceInfo || conferenceInfo.core.uri.length === 0) UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La conférence n'a pas pu démarrer en raison d'une erreur d'uri."))
else { else {
var callObj = UtilsCpp.createCall(conferenceInfo.core.uri, withVideo) callObj = UtilsCpp.createCall(conferenceInfo.core.uri, withVideo)
} }
} }
@ -358,6 +359,7 @@ Window {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.margins: 20 * DefaultStyle.dp Layout.margins: 20 * DefaultStyle.dp
} }
OngoingCallRightPanel { OngoingCallRightPanel {
id: rightPanel id: rightPanel
@ -606,6 +608,10 @@ Window {
onJoinConfRequested: mainWindow.joinConference(cameraEnabled) onJoinConfRequested: mainWindow.joinConference(cameraEnabled)
} }
} }
Component { Component {
id: inCallItem id: inCallItem
Control.Control { Control.Control {
@ -614,163 +620,18 @@ Window {
// implicitHeight: parent.height // implicitHeight: parent.height
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 10 * DefaultStyle.dp Layout.leftMargin: 10 * DefaultStyle.dp
Layout.rightMargin: 10 * DefaultStyle.dp Layout.rightMargin: 10 * DefaultStyle.dp
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
/*
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: DefaultStyle.grey_600 color: DefaultStyle.grey_600
radius: 15 * DefaultStyle.dp radius: 15 * DefaultStyle.dp
} }*/
contentItem: Item { contentItem: CallLayout{
id: centerItem call: mainWindow.call
anchors.fill: parent callTerminatedByUser: mainWindow.callTerminatedByUser
Text {
id: callTerminatedText
Connections {
target: mainWindow
onCallStateChanged: {
if (mainWindow.callState === LinphoneEnums.CallState.End) {
callTerminatedText.visible = true
}
}
}
visible: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 25 * DefaultStyle.dp
text: mainWindow.callTerminatedByUser ? qsTr("Vous avez terminé l'appel") : qsTr("Votre correspondant a terminé l'appel")
color: DefaultStyle.grey_0
z: 1
font {
pixelSize: 22 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
}
StackLayout {
id: centerLayout
currentIndex: 0
anchors.fill: parent
Connections {
target: mainWindow
onCallStateChanged: {
if (mainWindow.callState === LinphoneEnums.CallState.Error) {
centerLayout.currentIndex = 1
}
}
}
Sticker {
call: mainWindow.call
Layout.fillWidth: true
Layout.fillHeight: true
// visible: mainWindow.callState != LinphoneEnums.CallState.End
Connections {
target: mainWindow
onCallChanged: {
waitingTime.seconds = 0
waitingTimer.restart()
console.log("call changed", call, waitingTime.seconds)
}
}
Timer {
id: waitingTimer
interval: 1000
repeat: true
onTriggered: waitingTime.seconds += 1
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 30 * DefaultStyle.dp
visible: mainWindow.callState == LinphoneEnums.CallState.OutgoingInit
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingProgress
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingRinging
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingEarlyMedia
|| mainWindow.callState == LinphoneEnums.CallState.IncomingReceived
BusyIndicator {
indicatorColor: DefaultStyle.main2_100
Layout.alignment: Qt.AlignHCenter
}
Text {
id: waitingTime
property int seconds
text: UtilsCpp.formatElapsedTime(seconds)
color: DefaultStyle.grey_0
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: 30 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
Component.onCompleted: {
waitingTimer.restart()
}
}
}
}
ColumnLayout {
id: errorLayout
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
Layout.alignment: Qt.AlignCenter
Text {
text: qsTr(mainWindow.call.core.lastErrorMessage)
Layout.alignment: Qt.AlignCenter
color: DefaultStyle.grey_0
font.pixelSize: 40 * DefaultStyle.dp
}
}
}
Sticker {
id: preview
visible: mainWindow.callState != LinphoneEnums.CallState.End
&& mainWindow.callState != LinphoneEnums.CallState.Released
height: 180 * DefaultStyle.dp
width: 300 * DefaultStyle.dp
anchors.right: centerItem.right
anchors.bottom: centerItem.bottom
anchors.rightMargin: 10 * DefaultStyle.dp
anchors.bottomMargin: 10 * DefaultStyle.dp
AccountProxy{
id: accounts
}
account: accounts.defaultAccount
enablePersonalCamera: mainWindow.call.core.cameraEnabled
MovableMouseArea {
id: previewMouseArea
anchors.fill: parent
// visible: mainWindow.participantCount <= 2
movableArea: centerItem
margin: 10 * DefaultStyle.dp
function resetPosition(){
preview.anchors.right = centerItem.right
preview.anchors.bottom = centerItem.bottom
preview.anchors.rightMargin = previewMouseArea.margin
preview.anchors.bottomMargin = previewMouseArea.margin
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: preview
onDraggingChanged: if(dragging) {
preview.anchors.right = undefined
preview.anchors.bottom = undefined
}
onRequestResetPosition: resetPosition()
}
}
property int previousWidth
Component.onCompleted: {
previousWidth = width
}
onWidthChanged: {
if (width < previousWidth) {
previewMouseArea.updatePosition(0, 0)
} else {
previewMouseArea.updatePosition(width - previousWidth, 0)
}
previousWidth = width
}
} }
} }
} }

View file

@ -210,13 +210,14 @@ Item {
background: Item{} background: Item{}
Layout.preferredWidth: 24 * DefaultStyle.dp Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp Layout.preferredHeight: 24 * DefaultStyle.dp
property var callObj
contentItem: Image { contentItem: Image {
width: 24 * DefaultStyle.dp width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp height: 24 * DefaultStyle.dp
source: AppIcons.phone source: AppIcons.phone
} }
onClicked: { onClicked: {
var callObj = UtilsCpp.createCall(sipAddr) callObj = UtilsCpp.createCall(sipAddr.text)
} }
} }
} }

View file

@ -5,6 +5,10 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/App/Layout/LoginLayout.qml view/App/Layout/LoginLayout.qml
view/App/Layout/MainLayout.qml view/App/Layout/MainLayout.qml
view/Layout/Call/ActiveSpeakerLayout.qml
view/Layout/Call/CallLayout.qml
#view/Layout/Call/GridLayout.qml
view/Layout/Conference/IncallGrid.qml view/Layout/Conference/IncallGrid.qml
view/Layout/Contact/ContactLayout.qml view/Layout/Contact/ContactLayout.qml
view/Layout/Meeting/AddParticipantsLayout.qml view/Layout/Meeting/AddParticipantsLayout.qml
@ -86,6 +90,8 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Page/Main/ContactPage.qml view/Page/Main/ContactPage.qml
view/Page/Main/MeetingPage.qml view/Page/Main/MeetingPage.qml
view/Tool/utils.js view/Tool/utils.js
# Prototypes # Prototypes
view/Prototype/PhoneNumberPrototype.qml view/Prototype/PhoneNumberPrototype.qml

View file

@ -28,7 +28,7 @@ RowLayout {
id: accounts id: accounts
} }
account: accounts.defaultAccount account: accounts.defaultAccount
enablePersonalCamera: mainItem.cameraEnabled previewEnabled: mainItem.cameraEnabled
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -146,4 +146,4 @@ RowLayout {
} }
} }
} }
} }

View file

@ -16,13 +16,19 @@ Item {
width: 200 width: 200
property CallGui call: null property CallGui call: null
property AccountGui account: null property AccountGui account: null
property bool enablePersonalCamera: false property ParticipantDeviceGui participantDevice: null
onEnablePersonalCameraChanged: console.log ("enable camera", enablePersonalCamera) property bool previewEnabled: false
property color color: DefaultStyle.grey_600 property color color: DefaultStyle.grey_600
property int radius: 15 * DefaultStyle.dp property int radius: 15 * DefaultStyle.dp
property var peerAddressObj: call ? UtilsCpp.getDisplayName(call.core.peerAddress) : null property var peerAddressObj: participantDevice && participantDevice.core
property string peerAddress: peerAddressObj ? peerAddressObj.value : "" ? UtilsCpp.getDisplayName(participantDevice.core.address)
: call && call.core
? UtilsCpp.getDisplayName(call.core.peerAddress)
: null
property string peerAddress:peerAddressObj ? peerAddressObj.value : ""
property var identityAddress: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : null property var identityAddress: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : null
property bool cameraEnabled: previewEnabled
property string qmlName
Rectangle { Rectangle {
id: background id: background
@ -70,10 +76,7 @@ Item {
interval: 1 interval: 1
onTriggered: {cameraLoader.active=false; cameraLoader.active=true;} onTriggered: {cameraLoader.active=false; cameraLoader.active=true;}
} }
active: mainItem.visible && call active: mainItem.visible && mainItem.cameraEnabled
? call.core.remoteVideoEnabled && (mainItem.call.core.state != LinphoneEnums.CallState.End
&& mainItem.call.core.state != LinphoneEnums.CallState.Released)
: mainItem.enablePersonalCamera
onActiveChanged: console.log("camera active", active) onActiveChanged: console.log("camera active", active)
sourceComponent: cameraComponent sourceComponent: cameraComponent
} }
@ -88,7 +91,9 @@ Item {
anchors.fill: parent anchors.fill: parent
visible: isReady visible: isReady
call: mainItem.call call: mainItem.call
participantDevice: mainItem.participantDevice
isPreview: mainItem.previewEnabled
qmlName: mainItem.qmlName
onRequestNewRenderer: { onRequestNewRenderer: {
console.log("Request new renderer") console.log("Request new renderer")
resetTimer.restart() resetTimer.restart()
@ -103,7 +108,7 @@ Item {
anchors.leftMargin: 10 * DefaultStyle.dp anchors.leftMargin: 10 * DefaultStyle.dp
anchors.bottomMargin: 10 * DefaultStyle.dp anchors.bottomMargin: 10 * DefaultStyle.dp
width: implicitWidth width: implicitWidth
text: mainItem.peerAddress.length != 0 text: mainItem.peerAddress != ''
? mainItem.peerAddress ? mainItem.peerAddress
: mainItem.account && mainItem.identityAddress : mainItem.account && mainItem.identityAddress
? mainItem.identityAddress.value ? mainItem.identityAddress.value

View file

@ -0,0 +1,365 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import QtQml.Models
import QtQuick.Controls as Control
import Linphone
import EnumsToStringCpp 1.0
import UtilsCpp 1.0
import SettingsCpp 1.0
// =============================================================================
Item{
id: mainItem
property alias call: allDevices.currentCall
property ConferenceGui conference: call && call.core.conference || null
property var callState: call && call.core.state || undefined
property ParticipantDeviceProxy participantDevices : ParticipantDeviceProxy {
id: allDevices
}
onCallChanged: {
waitingTime.seconds = 0
waitingTimer.restart()
console.log("call changed", call, waitingTime.seconds)
}
RowLayout{
anchors.fill: parent
spacing: 16 * DefaultStyle.dp
Sticker {
id: activeSpeakerSticker
//call: mainItem.call
Layout.fillWidth: true
Layout.fillHeight: true
call: mainItem.call
participantDevice: mainItem.conference && mainItem.conference.core.activeSpeaker
property var address: participantDevice && participantDevice.core.address
onAddressChanged: console.log(address)
cameraEnabled: true
qmlName: 'AS'
Timer {
id: waitingTimer
interval: 1000
repeat: true
onTriggered: waitingTime.seconds += 1
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 30 * DefaultStyle.dp
visible: mainItem.callState === LinphoneEnums.CallState.OutgoingInit
|| mainItem.callState === LinphoneEnums.CallState.OutgoingProgress
|| mainItem.callState === LinphoneEnums.CallState.OutgoingRinging
|| mainItem.callState === LinphoneEnums.CallState.OutgoingEarlyMedia
|| mainItem.callState === LinphoneEnums.CallState.IncomingReceived
BusyIndicator {
indicatorColor: DefaultStyle.main2_100
Layout.alignment: Qt.AlignHCenter
}
Text {
id: waitingTime
property int seconds
text: UtilsCpp.formatElapsedTime(seconds)
color: DefaultStyle.grey_0
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: 30 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
Component.onCompleted: {
waitingTimer.restart()
}
}
}
}
ListView{
Layout.fillHeight: true
Layout.preferredWidth: 300 * DefaultStyle.dp
Layout.rightMargin: 10 * DefaultStyle.dp
Layout.bottomMargin: 10 * DefaultStyle.dp
visible: allDevices.count > 2
spacing: 15 * DefaultStyle.dp
model: allDevices
delegate:
Sticker {
visible: mainItem.callState != LinphoneEnums.CallState.End && mainItem.callState != LinphoneEnums.CallState.Released
&& modelData.core.address != activeSpeakerSticker.address
onVisibleChanged: console.log(modelData.core.address)
height: visible ? 180 * DefaultStyle.dp : 0
width: 300 * DefaultStyle.dp
qmlName: 'M_'+index
participantDevice: modelData
cameraEnabled: visible
Component.onCompleted: console.log(modelData.core.address)
//previewEnabled: mainItem.call.core.cameraEnabled
}
}
}
Sticker {
id: preview
visible: allDevices.count <= 2
height: 180 * DefaultStyle.dp
width: 300 * DefaultStyle.dp
anchors.right: mainItem.right
anchors.bottom: mainItem.bottom
anchors.rightMargin: 10 * DefaultStyle.dp
anchors.bottomMargin: 10 * DefaultStyle.dp
//participantDevice: allDevices.me
cameraEnabled: allDevices.count <= 2
previewEnabled: true
qmlName: 'P'
MovableMouseArea {
id: previewMouseArea
anchors.fill: parent
movableArea: mainItem
margin: 10 * DefaultStyle.dp
function resetPosition(){
preview.anchors.right = mainItem.right
preview.anchors.bottom = mainItem.bottom
preview.anchors.rightMargin = previewMouseArea.margin
preview.anchors.bottomMargin = previewMouseArea.margin
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: preview
onDraggingChanged: if(dragging) {
preview.anchors.right = undefined
preview.anchors.bottom = undefined
}
onRequestResetPosition: resetPosition()
}
}
}
/*
Sticker {
id: preview
visible: mainItem.callState != LinphoneEnums.CallState.End
&& mainItem.callState != LinphoneEnums.CallState.Released
height: 180 * DefaultStyle.dp
width: 300 * DefaultStyle.dp
anchors.right: mainItem.right
anchors.bottom: mainItem.bottom
anchors.rightMargin: 10 * DefaultStyle.dp
anchors.bottomMargin: 10 * DefaultStyle.dp
AccountProxy{
id: accounts
}
account: accounts.defaultAccount
previewEnabled: mainItem.call.core.cameraEnabled
MovableMouseArea {
id: previewMouseArea
anchors.fill: parent
// visible: mainItem.participantCount <= 2
movableArea: mainItem
margin: 10 * DefaultStyle.dp
function resetPosition(){
preview.anchors.right = mainItem.right
preview.anchors.bottom = mainItem.bottom
preview.anchors.rightMargin = previewMouseArea.margin
preview.anchors.bottomMargin = previewMouseArea.margin
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: preview
onDraggingChanged: if(dragging) {
preview.anchors.right = undefined
preview.anchors.bottom = undefined
}
onRequestResetPosition: resetPosition()
}
}
property int previousWidth
Component.onCompleted: {
previousWidth = width
}
onWidthChanged: {
if (width < previousWidth) {
previewMouseArea.updatePosition(0, 0)
} else {
previewMouseArea.updatePosition(width - previousWidth, 0)
}
previousWidth = width
}*/
/*
Item {
id: mainItem
property CallModel callModel
property bool isRightReducedLayout: false
property bool isLeftReducedLayout: false
property bool cameraEnabled: true
property bool isConference: callModel && callModel.isConference
property bool isConferenceReady: isConference && callModel.conferenceModel && callModel.conferenceModel.isReady
property int participantCount: isConference ? allDevices.count + 1 : 2 // +me. allDevices==0 if !conference
property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel {
id: allDevices
callModel: mainItem.callModel
showMe: false
onConferenceCreated: cameraView.resetCamera()
}
Sticker{
id: cameraView
anchors.fill: parent
anchors.leftMargin: isRightReducedLayout || isLeftReducedLayout? 30 : 140
anchors.rightMargin: isRightReducedLayout ? 10 : 140
cameraQmlName: 'AS'
callModel: mainItem.callModel
currentDevice: isPreview
? allDevices.me
: mainItem.isConference
? allDevices.activeSpeaker
: null
deactivateCamera: !mainItem.cameraEnabled || (isPreview && callModel.pausedByUser)
? true
: mainItem.isConference
? (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused) )
|| (!(callModel && callModel.cameraEnabled) && mainItem.participantCount == 1)
|| (currentDevice && !currentDevice.videoEnabled)// && mainItem.participantCount == 2)
|| !mainItem.isConferenceReady
: (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused || !callModel.videoEnabled) )
|| currentDevice && !currentDevice.videoEnabled
isPreview: !preview.visible && mainItem.participantCount == 1
onIsPreviewChanged: {cameraView.resetCamera() }
isCameraFromDevice: isPreview
isPaused: isPreview && callModel.pausedByUser
? false
: mainItem.isConference
? //callModel && callModel.pausedByUser && mainItem.participantCount != 2 ||
(currentDevice && currentDevice.isPaused)
: callModel && !callModel.pausedByUser && (callModel.status === CallModel.CallStatusPaused)
quickTransition: true
showCloseButton: false
showActiveSpeakerOverlay: false // This is an active speaker. We don't need to show the indicator.
showCustomButton: false
avatarStickerBackgroundColor: isPreview ? IncallStyle.container.avatar.stickerPreviewBackgroundColor.color : IncallStyle.container.avatar.stickerBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
}
Item{// Need an item to not override Sticker internal states. States are needed for changing anchors.
id: preview
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 30
anchors.bottomMargin: 15
height: visible ? miniViews.cellHeight : 0
width: 16 * height / 9
visible: mainItem.isConferenceReady && allDevices.count >= 1
|| (!mainItem.isConference && mainItem.callModel && mainItem.callModel.cameraEnabled)// use videoEnabled if we want to show the preview sticker
Loader{
anchors.fill: parent
anchors.margins: 3
sourceComponent:
Sticker{
id: previewSticker
cameraQmlName: 'AS_Preview'
deactivateCamera: !mainItem.cameraEnabled || !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled
currentDevice: allDevices.me
isPreview: true
callModel: mainItem.callModel
isCameraFromDevice: true
showCloseButton: false
showCustomButton: false
showAvatarBorder: true
avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerPreviewBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
}
active: parent.visible
}
MovableMouseArea{
id: dragger
anchors.fill: parent
visible: mainItem.participantCount <= 2
function resetPosition(){
preview.anchors.right = mainItem.right
preview.anchors.bottom = mainItem.bottom
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: preview
onDraggingChanged: if(dragging){
preview.anchors.right = undefined
preview.anchors.bottom = undefined
}
onRequestResetPosition: resetPosition()
}
}
Item{
id: miniViewArea
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: preview.top
anchors.rightMargin: 30
anchors.topMargin: 15
anchors.bottomMargin: 0
//---------------
width: 16 * miniViews.cellHeight / 9
visible: mainItem.isConferenceReady || !mainItem.isConference
property int heightLeft: parent.height - preview.height
onHeightLeftChanged: {Qt.callLater(miniViewArea.forceRefresh)}
function forceRefresh(){// Force a content refresh via margins. Qt is buggy when managing sizes in ListView.
++miniViewArea.anchors.topMargin
--miniViewArea.anchors.topMargin
}
ScrollableListView{
id: miniViews
property int cellHeight: 150
anchors.fill: parent
model : mainItem.isConference && mainItem.participantDevices.count > 1 ? mainItem.participantDevices : []
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
fitCacheToContent: false
property int oldCount : 0// Count changed can be called without a change... (bug?). Use oldCount to avoid it.
onCountChanged: {if(oldCount != count){ oldCount = count ; Qt.callLater(miniViewArea.forceRefresh)}}
Component.onCompleted: {Qt.callLater(miniViewArea.forceRefresh)}
delegate:Item{
height: visible ? miniViews.cellHeight + 15 : 0
width: visible ? miniViews.width : 0
visible: cameraView.currentDevice != modelData
clip:false
Sticker{
id: miniView
anchors.fill: parent
anchors.topMargin: 3
anchors.leftMargin: 3
anchors.rightMargin: 3
anchors.bottomMargin: 18
cameraQmlName: 'S_'+index
deactivateCamera: (!mainItem.isConferenceReady || !mainItem.isConference)
&& (index <0 || !mainItem.cameraEnabled || (!modelData.videoEnabled) || (callModel && callModel.pausedByUser) )
currentDevice: modelData.isPreview ? null : modelData
callModel: modelData.isPreview ? null : mainItem.callModel
isCameraFromDevice: mainItem.isConference
isPaused: currentDevice && currentDevice.isPaused
showCloseButton: false
showCustomButton: false
showAvatarBorder: true
avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
}
}
}
}
}
*/

View file

@ -0,0 +1,293 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import QtQml.Models
import QtQuick.Controls as Control
import Linphone
import EnumsToStringCpp 1.0
import UtilsCpp 1.0
import SettingsCpp 1.0
// =============================================================================
Item {
id: mainItem
anchors.fill: parent
property CallGui call
property bool callTerminatedByUser: false
readonly property var callState: call && call.core.state || undefined
onCallStateChanged: if (callState === LinphoneEnums.CallState.End) {
callTerminatedText.visible = true
}else if( callState === LinphoneEnums.CallState.Error) {
centerLayout.currentIndex = 1
}
Text {
id: callTerminatedText
visible: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 25 * DefaultStyle.dp
text: mainItem.callTerminatedByUser ? qsTr("Vous avez terminé l'appel") : qsTr("Votre correspondant a terminé l'appel")
color: DefaultStyle.grey_0
z: 1
font {
pixelSize: 22 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
}
StackLayout {
id: centerLayout
currentIndex: 0
anchors.fill: parent
Loader{
id: callLayout
Layout.fillWidth: true
Layout.fillHeight: true
sourceComponent:ActiveSpeakerLayout{
Layout.fillWidth: true
Layout.fillHeight: true
call: mainItem.call
}
}
ColumnLayout {
id: userNotFoundLayout
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
Layout.alignment: Qt.AlignCenter
Text {
text: qsTr(mainItem.call.core.lastErrorMessage)
Layout.alignment: Qt.AlignCenter
color: DefaultStyle.grey_0
font.pixelSize: 40 * DefaultStyle.dp
}
}
}
}
/*
Sticker {
id: preview
visible: mainItem.callState != LinphoneEnums.CallState.End
&& mainItem.callState != LinphoneEnums.CallState.Released
height: 180 * DefaultStyle.dp
width: 300 * DefaultStyle.dp
anchors.right: mainItem.right
anchors.bottom: mainItem.bottom
anchors.rightMargin: 10 * DefaultStyle.dp
anchors.bottomMargin: 10 * DefaultStyle.dp
AccountProxy{
id: accounts
}
account: accounts.defaultAccount
previewEnabled: mainItem.call.core.cameraEnabled
MovableMouseArea {
id: previewMouseArea
anchors.fill: parent
// visible: mainItem.participantCount <= 2
movableArea: mainItem
margin: 10 * DefaultStyle.dp
function resetPosition(){
preview.anchors.right = mainItem.right
preview.anchors.bottom = mainItem.bottom
preview.anchors.rightMargin = previewMouseArea.margin
preview.anchors.bottomMargin = previewMouseArea.margin
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: preview
onDraggingChanged: if(dragging) {
preview.anchors.right = undefined
preview.anchors.bottom = undefined
}
onRequestResetPosition: resetPosition()
}
}
property int previousWidth
Component.onCompleted: {
previousWidth = width
}
onWidthChanged: {
if (width < previousWidth) {
previewMouseArea.updatePosition(0, 0)
} else {
previewMouseArea.updatePosition(width - previousWidth, 0)
}
previousWidth = width
}*/
/*
Item {
id: mainItem
property CallModel callModel
property bool isRightReducedLayout: false
property bool isLeftReducedLayout: false
property bool cameraEnabled: true
property bool isConference: callModel && callModel.isConference
property bool isConferenceReady: isConference && callModel.conferenceModel && callModel.conferenceModel.isReady
property int participantCount: isConference ? allDevices.count + 1 : 2 // +me. allDevices==0 if !conference
property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel {
id: allDevices
callModel: mainItem.callModel
showMe: false
onConferenceCreated: cameraView.resetCamera()
}
Sticker{
id: cameraView
anchors.fill: parent
anchors.leftMargin: isRightReducedLayout || isLeftReducedLayout? 30 : 140
anchors.rightMargin: isRightReducedLayout ? 10 : 140
cameraQmlName: 'AS'
callModel: mainItem.callModel
currentDevice: isPreview
? allDevices.me
: mainItem.isConference
? allDevices.activeSpeaker
: null
deactivateCamera: !mainItem.cameraEnabled || (isPreview && callModel.pausedByUser)
? true
: mainItem.isConference
? (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused) )
|| (!(callModel && callModel.cameraEnabled) && mainItem.participantCount == 1)
|| (currentDevice && !currentDevice.videoEnabled)// && mainItem.participantCount == 2)
|| !mainItem.isConferenceReady
: (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused || !callModel.videoEnabled) )
|| currentDevice && !currentDevice.videoEnabled
isPreview: !preview.visible && mainItem.participantCount == 1
onIsPreviewChanged: {cameraView.resetCamera() }
isCameraFromDevice: isPreview
isPaused: isPreview && callModel.pausedByUser
? false
: mainItem.isConference
? //callModel && callModel.pausedByUser && mainItem.participantCount != 2 ||
(currentDevice && currentDevice.isPaused)
: callModel && !callModel.pausedByUser && (callModel.status === CallModel.CallStatusPaused)
quickTransition: true
showCloseButton: false
showActiveSpeakerOverlay: false // This is an active speaker. We don't need to show the indicator.
showCustomButton: false
avatarStickerBackgroundColor: isPreview ? IncallStyle.container.avatar.stickerPreviewBackgroundColor.color : IncallStyle.container.avatar.stickerBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
}
Item{// Need an item to not override Sticker internal states. States are needed for changing anchors.
id: preview
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 30
anchors.bottomMargin: 15
height: visible ? miniViews.cellHeight : 0
width: 16 * height / 9
visible: mainItem.isConferenceReady && allDevices.count >= 1
|| (!mainItem.isConference && mainItem.callModel && mainItem.callModel.cameraEnabled)// use videoEnabled if we want to show the preview sticker
Loader{
anchors.fill: parent
anchors.margins: 3
sourceComponent:
Sticker{
id: previewSticker
cameraQmlName: 'AS_Preview'
deactivateCamera: !mainItem.cameraEnabled || !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled
currentDevice: allDevices.me
isPreview: true
callModel: mainItem.callModel
isCameraFromDevice: true
showCloseButton: false
showCustomButton: false
showAvatarBorder: true
avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerPreviewBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
}
active: parent.visible
}
MovableMouseArea{
id: dragger
anchors.fill: parent
visible: mainItem.participantCount <= 2
function resetPosition(){
preview.anchors.right = mainItem.right
preview.anchors.bottom = mainItem.bottom
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: preview
onDraggingChanged: if(dragging){
preview.anchors.right = undefined
preview.anchors.bottom = undefined
}
onRequestResetPosition: resetPosition()
}
}
Item{
id: miniViewArea
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: preview.top
anchors.rightMargin: 30
anchors.topMargin: 15
anchors.bottomMargin: 0
//---------------
width: 16 * miniViews.cellHeight / 9
visible: mainItem.isConferenceReady || !mainItem.isConference
property int heightLeft: parent.height - preview.height
onHeightLeftChanged: {Qt.callLater(miniViewArea.forceRefresh)}
function forceRefresh(){// Force a content refresh via margins. Qt is buggy when managing sizes in ListView.
++miniViewArea.anchors.topMargin
--miniViewArea.anchors.topMargin
}
ScrollableListView{
id: miniViews
property int cellHeight: 150
anchors.fill: parent
model : mainItem.isConference && mainItem.participantDevices.count > 1 ? mainItem.participantDevices : []
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
fitCacheToContent: false
property int oldCount : 0// Count changed can be called without a change... (bug?). Use oldCount to avoid it.
onCountChanged: {if(oldCount != count){ oldCount = count ; Qt.callLater(miniViewArea.forceRefresh)}}
Component.onCompleted: {Qt.callLater(miniViewArea.forceRefresh)}
delegate:Item{
height: visible ? miniViews.cellHeight + 15 : 0
width: visible ? miniViews.width : 0
visible: cameraView.currentDevice != modelData
clip:false
Sticker{
id: miniView
anchors.fill: parent
anchors.topMargin: 3
anchors.leftMargin: 3
anchors.rightMargin: 3
anchors.bottomMargin: 18
cameraQmlName: 'S_'+index
deactivateCamera: (!mainItem.isConferenceReady || !mainItem.isConference)
&& (index <0 || !mainItem.cameraEnabled || (!modelData.videoEnabled) || (callModel && callModel.pausedByUser) )
currentDevice: modelData.isPreview ? null : modelData
callModel: modelData.isPreview ? null : mainItem.callModel
isCameraFromDevice: mainItem.isConference
isPaused: currentDevice && currentDevice.isPaused
showCloseButton: false
showCustomButton: false
showAvatarBorder: true
avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
}
}
}
}
}
*/

View file

@ -0,0 +1,67 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQml.Models 2.12
import QtGraphicalEffects 1.12
import Common 1.0
import Common.Styles 1.0
import Linphone 1.0
import LinphoneEnums 1.0
import UtilsCpp 1.0
import App.Styles 1.0
import ConstantsCpp 1.0
// Temp
import 'Incall.js' as Logic
import 'qrc:/ui/scripts/Utils/utils.js' as Utils
// =============================================================================
Mosaic {
id: grid
property alias callModel: participantDevices.callModel
property bool cameraEnabled: true
property int participantCount: gridModel.count
// On grid view, we limit the quality if there are enough participants// The vga mode has been activated from the factory rc
//onParticipantCountChanged: participantCount > ConstantsCpp.maxMosaicParticipants ? SettingsModel.setLimitedMosaicQuality() : SettingsModel.setHighMosaicQuality()
delegateModel: DelegateModel{
id: gridModel
property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel {
id: participantDevices
showMe: true
}
model: participantDevices
delegate: Item{
id: avatarCell
property ParticipantDeviceModel currentDevice: gridModel.participantDevices.getAt(index)
onCurrentDeviceChanged: {
if(index < 0) cameraView.enabled = false // this is a delegate destruction. We need to stop camera before Qt change its currentDevice (and then, let CameraView to delete wrong renderer)
}
height: grid.cellHeight - 10
width: grid.cellWidth - 10
Sticker{
id: cameraView
anchors.fill: parent
cameraQmlName: 'G_'+index
callModel: index >= 0 ? participantDevices.callModel : null // do this before to prioritize changing call on remove
deactivateCamera: index <0 || !grid.cameraEnabled || grid.callModel.pausedByUser
currentDevice: gridModel.participantDevices.getAt(index)
isCameraFromDevice: true
isPaused: !isPreview && avatarCell.currentDevice && avatarCell.currentDevice.isPaused
showCloseButton: false
showCustomButton: false
avatarStickerBackgroundColor: isPreview? IncallStyle.container.avatar.stickerPreviewBackgroundColor.color : IncallStyle.container.avatar.stickerBackgroundColor.color
avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color
//onCloseRequested: participantDevices.showMe = false
}
}
}
}