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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,9 +28,14 @@
#include "core/App.hpp"
#include "core/call/CallCore.hpp"
#include "core/call/CallGui.hpp"
#include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
DEFINE_ABSTRACT_OBJECT(CameraGui)
QMutex CameraGui::mPreviewCounterMutex;
int CameraGui::mPreviewCounter = 0;
// =============================================================================
CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) {
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)
CameraGui::~CameraGui() {
mustBeInMainThread("~" + getClassName());
mRefreshTimer.stop();
App::postModelSync([this]() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); });
}
QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const {
QQuickFramebufferObject::Renderer *renderer = NULL;
// 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: {
}
}
auto renderer = createRenderer(false);
if (!renderer) {
qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to Dummy, " << getSourceLocation();
QTimer::singleShot(1, this, &CameraGui::isNotReady);
renderer = new CameraDummy(); // Used to fill a renderer to avoid pushing a NULL.
QTimer::singleShot(1000, this, &CameraGui::requestNewRenderer);
@ -84,6 +65,88 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const {
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 {
return mIsReady;
}
@ -100,6 +163,21 @@ void CameraGui::isNotReady() {
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 {
return mCallGui;
}
@ -107,11 +185,70 @@ CallGui *CameraGui::getCallGui() const {
void CameraGui::setCallGui(CallGui *callGui) {
if (mCallGui != callGui) {
mCallGui = callGui;
qDebug() << "Set Call " << 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 {
if (mCallGui != nullptr) return Call;
else return CorePreview;
return mWindowIdLocation;
}
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 <QTimer>
#include "core/participant/ParticipantDeviceGui.hpp"
// =============================================================================
class CallGui;
class CameraGui : public QQuickFramebufferObject, public AbstractObject {
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:
CameraGui(QQuickItem *parent = Q_NULLPTR);
virtual ~CameraGui();
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;
void setIsReady(bool isReady);
void isReady();
void isNotReady();
bool getIsPreview() const;
void setIsPreview(bool status);
CallGui *getCallGui() const;
void setCallGui(CallGui *callGui);
typedef enum { None = -1, CorePreview = 0, Call, Device, Player, Core } WindowIdLocation;
ParticipantDeviceGui *getParticipantDeviceGui() const;
void setParticipantDeviceGui(ParticipantDeviceGui *participantDeviceGui);
QString getQmlName() const;
void setQmlName(const QString &name);
WindowIdLocation getSourceLocation() const;
void setWindowIdLocation(const WindowIdLocation &location);
void activatePreview();
void deactivatePreview();
void updateWindowIdLocation();
void removeParticipantDeviceModel();
void removeCallModel();
void removeLinphonePlayer();
signals:
void requestNewRenderer();
void isReadyChanged(bool isReady);
void callGuiChanged(CallGui *callGui);
void isPreviewChanged(bool isPreview);
void isReadyChanged();
void participantDeviceGuiChanged(ParticipantDeviceGui *participantDeviceGui);
void videoDefinitionChanged();
// void linphonePlayerChanged(SoundPlayer * linphonePlayer);
void qmlNameChanged();
private:
bool mIsPreview = false;
bool mIsReady = false;
QTimer mRefreshTimer;
int mMaxFps = 30;
QVariantMap mLastVideoDefinition;
QTimer mLastVideoDefinitionChecker;
CallGui *mCallGui = nullptr;
ParticipantDeviceGui *mParticipantDeviceGui = nullptr;
QString mQmlName;
WindowIdLocation mWindowIdLocation = None;
mutable bool mIsWindowIdSet = false;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -20,6 +20,7 @@
#include "ConferenceCore.hpp"
#include "core/App.hpp"
#include "model/conference/ConferenceModel.hpp"
#include "tool/Utils.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);
// Should be call from model Thread
mustBeInLinphoneThread(getClassName());
mConferenceModel = ConferenceModel::create(conference);
mSubject = Utils::coreStringToAppString(conference->getSubject());
}
ConferenceCore::~ConferenceCore() {
@ -45,6 +47,12 @@ ConferenceCore::~ConferenceCore() {
void ConferenceCore::setSelf(QSharedPointer<ConferenceCore> me) {
mConferenceModelConnection = QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>>(
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->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); });
// });
@ -79,3 +87,23 @@ void ConferenceCore::setIsReady(bool state) {
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_
#define CONFERENCE_CORE_H_
#include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
#include "model/conference/ConferenceModel.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/thread/SafeConnection.hpp"
@ -38,6 +40,7 @@ public:
// Q_PROPERTY(ParticipantModel* localParticipant READ getLocalParticipant NOTIFY localParticipantChanged)
Q_PROPERTY(bool isReady MEMBER mIsReady WRITE setIsReady NOTIFY isReadyChanged)
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
static QSharedPointer<ConferenceCore> create(const std::shared_ptr<linphone::Conference> &conference);
@ -55,19 +58,26 @@ public:
// std::list<std::shared_ptr<linphone::Participant>>
// getParticipantList() const; // SDK exclude me. We want to get ALL participants.
int getParticipantDeviceCount() const;
ParticipantDeviceCore *getActiveSpeaker() const;
ParticipantDeviceGui *getActiveSpeakerGui() const;
void setActiveSpeaker(const QSharedPointer<ParticipantDeviceCore> &device);
void setIsReady(bool state);
std::shared_ptr<ConferenceModel> getModel() const;
//---------------------------------------------------------------------------
signals:
void subjectChanged();
void isReadyChanged();
void participantDeviceCountChanged();
void activeSpeakerChanged();
private:
QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>> mConferenceModelConnection;
std::shared_ptr<ConferenceModel> mConferenceModel;
QSharedPointer<ParticipantDeviceCore> mActiveSpeaker;
bool mIsReady = false;
QString mSubject;

View file

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

View file

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

View file

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

View file

@ -74,6 +74,7 @@ public:
LinphoneEnums::ParticipantDeviceState getState() const;
std::shared_ptr<linphone::ParticipantDevice> getDevice();
std::shared_ptr<ParticipantDeviceModel> getModel() const;
void setPaused(bool paused);
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
* (see https://www.linphone.org).
@ -20,21 +20,14 @@
#include "ParticipantDeviceList.hpp"
#include "core/App.hpp"
#include "core/participant/ParticipantCore.hpp"
#include "core/participant/ParticipantDeviceCore.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
#include <QQmlApplicationEngine>
#include <algorithm>
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() {
auto model = QSharedPointer<ParticipantDeviceList>(new ParticipantDeviceList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
@ -42,302 +35,112 @@ QSharedPointer<ParticipantDeviceList> ParticipantDeviceList::create() {
return model;
}
ParticipantDeviceList::ParticipantDeviceList(const std::shared_ptr<linphone::Participant> &participant, QObject *parent)
: ListProxy(parent) {
std::list<std::shared_ptr<linphone::ParticipantDevice>> devices = participant->getDevices();
for (auto device : devices) {
auto deviceModel = ParticipantDeviceCore::create(device, isMe(device));
// connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(),
// &ParticipantDeviceCore::onSecurityLevelChanged);
connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this,
&ParticipantDeviceList::onParticipantDeviceSpeaking);
mList << deviceModel;
}
mInitialized = true;
QSharedPointer<ParticipantDeviceList>
ParticipantDeviceList::create(const std::shared_ptr<ConferenceModel> &conferenceModel) {
auto model = create();
model->setConferenceModel(conferenceModel);
return model;
}
ParticipantDeviceList::ParticipantDeviceList(QObject *parent) {
mustBeInMainThread(getClassName());
ParticipantDeviceList::ParticipantDeviceList() {
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() {
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::initConferenceModel() {
// if (!mInitialized && mCallModel) {
// auto conferenceModel = mCallModel->getConferenceSharedModel();
// if (conferenceModel) {
// updateDevices(conferenceModel->getConference()->getMe()->getDevices(), true);
// updateDevices(conferenceModel->getConference()->getParticipantDeviceList(), false);
// qDebug() << "Conference have " << mList.size() << " devices";
// connect(conferenceModel.get(), &ConferenceModel::activeSpeakerParticipantDevice, this,
// &ParticipantDeviceList::onActiveSpeakerParticipantDevice);
// connect(conferenceModel.get(), &ConferenceModel::participantAdded, this,
// &ParticipantDeviceList::onParticipantAdded);
// connect(conferenceModel.get(), &ConferenceModel::participantRemoved, this,
// &ParticipantDeviceList::onParticipantRemoved);
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceAdded, this,
// &ParticipantDeviceList::onParticipantDeviceAdded);
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceRemoved, this,
// &ParticipantDeviceList::onParticipantDeviceRemoved);
// connect(conferenceModel.get(), &ConferenceModel::conferenceStateChanged, this,
// &ParticipantDeviceList::onConferenceStateChanged);
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceMediaCapabilityChanged, this,
// &ParticipantDeviceList::onParticipantDeviceMediaCapabilityChanged);
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceMediaAvailabilityChanged, this,
// &ParticipantDeviceList::onParticipantDeviceMediaAvailabilityChanged);
// connect(conferenceModel.get(), &ConferenceModel::participantDeviceIsSpeakingChanged, this,
// &ParticipantDeviceList::onParticipantDeviceIsSpeakingChanged);
// mActiveSpeaker = get(conferenceModel->getConference()->getActiveSpeakerParticipantDevice());
// mInitialized = true;
// }
// }
}
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);
if (mConferenceModelConnection) mConferenceModelConnection->disconnect();
mConferenceModelConnection = QSharedPointer<SafeConnection<ParticipantDeviceList, ConferenceModel>>(
new SafeConnection<ParticipantDeviceList, ConferenceModel>(me, mConferenceModel), &QObject::deleteLater);
if (mConferenceModel) {
mConferenceModelConnection->makeConnectToModel(
&ConferenceModel::participantDeviceAdded,
[this](const std::shared_ptr<linphone::ParticipantDevice> &device) {
auto deviceCore = ParticipantDeviceCore::create(device);
mConferenceModelConnection->invokeToCore([this, deviceCore]() {
qDebug() << "[ParticipantDeviceList] : add a device";
this->add(deviceCore);
});
});
mConferenceModelConnection->makeConnectToModel(
&ConferenceModel::conferenceStateChanged, [this](linphone::Conference::State state) {
qDebug() << "[ParticipantDeviceList] new state = " << (int)state;
if (state == linphone::Conference::State::Created) {
qDebug() << "[ParticipantDeviceList] : build devices";
auto devices = buildDevices(mConferenceModel);
mConferenceModelConnection->invokeToCore([this, devices]() {
qDebug() << "[ParticipantDeviceList] : set devices" << devices.size();
setDevices(devices);
});
}
});
mConferenceModelConnection->makeConnectToCore(
&ParticipantDeviceList::lSetConferenceModel,
[this](const std::shared_ptr<ConferenceModel> &conferenceModel) {
mConferenceModelConnection->invokeToCore(
[this, conferenceModel]() { setConferenceModel(conferenceModel); });
});
}
}
bool ParticipantDeviceList::add(const QSharedPointer<ParticipantDeviceCore> &deviceToAdd) {
auto deviceToAddAddr = deviceToAdd->getAddress();
int row = 0;
qDebug() << "Adding device " << deviceToAdd->getAddress();
for (auto item : mList) {
auto deviceCore = item.objectCast<ParticipantDeviceCore>();
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() {
QVariant ParticipantDeviceList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole)
return QVariant::fromValue(new ParticipantDeviceGui(mList[row].objectCast<ParticipantDeviceCore>()));
return QVariant();
}

View file

@ -22,70 +22,39 @@
#define PARTICIPANT_DEVICE_LIST_H_
#include "../proxy/ListProxy.hpp"
#include "core/call/CallCore.hpp"
#include "core/participant/ParticipantDeviceCore.hpp"
#include <QDateTime>
#include <QObject>
#include <QString>
#include "ParticipantDeviceCore.hpp"
#include "model/conference/ConferenceModel.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
class ParticipantDeviceList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<ParticipantDeviceList> create(const std::shared_ptr<linphone::Participant> &participant);
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(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 initConferenceModel();
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();
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void activeSpeakerChanged();
void securityLevelChanged(std::shared_ptr<const linphone::Address> device);
void participantSpeaking(ParticipantDeviceCore *speakingDevice);
void conferenceCreated();
void meChanged();
void lSetConferenceModel(const std::shared_ptr<ConferenceModel> &conferenceModel);
private:
CallCore *mCallCore = nullptr;
QSharedPointer<ParticipantDeviceCore> mActiveSpeaker;
// QList<ParticipantDeviceCore*> mActiveSpeakers;// First item is last speaker
bool mInitialized = false;
QSharedPointer<SafeConnection<ParticipantDeviceList, CallModel>> mModelConnection;
std::shared_ptr<ConferenceModel> mConferenceModel;
QSharedPointer<SafeConnection<ParticipantDeviceList, ConferenceModel>> mConferenceModelConnection;
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
* (see https://www.linphone.org).
@ -26,96 +26,71 @@
// =============================================================================
DEFINE_ABSTRACT_OBJECT(ParticipantDeviceProxy)
ParticipantDeviceProxy::ParticipantDeviceProxy(QObject *parent) : SortFilterProxy(parent) {
mDeleteSourceModel = true;
mList = ParticipantDeviceList::create();
setSourceModel(mList.get());
mParticipants = ParticipantDeviceList::create();
connect(mParticipants.get(), &ParticipantDeviceList::countChanged, this, &ParticipantDeviceProxy::meChanged);
setSourceModel(mParticipants.get());
sort(0); //, Qt::DescendingOrder);
}
ParticipantDeviceProxy::~ParticipantDeviceProxy() {
setSourceModel(nullptr);
}
bool ParticipantDeviceProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
const QModelIndex index = mList->index(sourceRow, 0, sourceParent);
const ParticipantDeviceCore *device = index.data().value<ParticipantDeviceCore *>();
return device && (isShowMe() /*|| !(device->isMe() && device->isLocal())*/);
CallGui *ParticipantDeviceProxy::getCurrentCall() const {
return mCurrentCall;
}
bool ParticipantDeviceProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const {
const ParticipantDeviceCore *deviceA = sourceModel()->data(left).value<ParticipantDeviceCore *>();
const ParticipantDeviceCore *deviceB = sourceModel()->data(right).value<ParticipantDeviceCore *>();
// 'me' at end (for grid).
return /*deviceB->isLocal() || !deviceA->isLocal() && deviceB->isMe() ||*/ left.row() < right.row();
}
//---------------------------------------------------------------------------------
ParticipantDeviceCore *ParticipantDeviceProxy::getAt(int row) {
if (row >= 0) {
QModelIndex sourceIndex = mapToSource(this->index(row, 0));
return sourceModel()->data(sourceIndex).value<ParticipantDeviceCore *>();
} else return nullptr;
}
ParticipantDeviceCore *ParticipantDeviceProxy::getActiveSpeakerModel() {
return mList->getActiveSpeakerModel();
}
CallModel *ParticipantDeviceProxy::getCallModel() const {
return mCallModel;
}
ParticipantDeviceCore *ParticipantDeviceProxy::getMe() const {
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::setCurrentCall(CallGui *call) {
qDebug() << "[ParticipantDeviceProxy] set current call " << this << " => " << call;
if (mCurrentCall != call) {
CallCore *callCore = nullptr;
if (mCurrentCall) {
callCore = mCurrentCall->getCore();
if (callCore) callCore->disconnect(mParticipants.get());
callCore = nullptr;
}
mCurrentCall = call;
if (mCurrentCall) callCore = mCurrentCall->getCore();
if (callCore) {
connect(callCore, &CallCore::conferenceChanged, mParticipants.get(), [this]() {
auto conference = mCurrentCall->getCore()->getConferenceCore();
qDebug() << "[ParticipantDeviceProxy] set conference " << this << " => " << conference;
mParticipants->setConferenceModel(conference ? conference->getModel() : nullptr);
// mParticipants->lSetConferenceModel(conference ? conference->getModel() : nullptr);
});
auto conference = callCore->getConferenceCore();
qDebug() << "[ParticipantDeviceProxy] set conference " << this << " => " << conference;
mParticipants->setConferenceModel(conference ? conference->getModel() : nullptr);
// mParticipants->lSetConferenceModel(conference ? conference->getModel() : nullptr);
}
emit currentCallChanged();
}
}
void ParticipantDeviceProxy::onCountChanged() {
qDebug() << "Count changed : " << getCount();
ParticipantDeviceGui *ParticipantDeviceProxy::getMe() const {
auto core = mParticipants->getMe();
if (!core) return nullptr;
else {
return new ParticipantDeviceGui(core);
}
}
void ParticipantDeviceProxy::onParticipantSpeaking(ParticipantDeviceCore *speakingDevice) {
emit participantSpeaking(speakingDevice);
void ParticipantDeviceProxy::setMe(ParticipantDeviceGui *me) {
}
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
* (see https://www.linphone.org).
@ -21,64 +21,43 @@
#ifndef PARTICIPANT_DEVICE_PROXY_MODEL_H_
#define PARTICIPANT_DEVICE_PROXY_MODEL_H_
#include <linphone++/linphone.hh>
// =============================================================================
#include "../proxy/SortFilterProxy.hpp"
#include <QDateTime>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include "core/call/CallGui.hpp"
#include "core/participant/ParticipantDeviceGui.hpp"
#include "tool/AbstractObject.hpp"
class ParticipantDeviceList;
class ParticipantDeviceCore;
class ParticipantCore;
class CallModel;
class ParticipantDeviceGui;
class ParticipantDeviceProxy : public SortFilterProxy {
class ParticipantDeviceProxy : public SortFilterProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(CallGui *currentCall READ getCurrentCall WRITE setCurrentCall NOTIFY currentCallChanged)
Q_PROPERTY(ParticipantDeviceGui *me READ getMe WRITE setMe NOTIFY meChanged)
public:
Q_PROPERTY(CallModel *callModel READ getCallModel WRITE setCallModel NOTIFY callModelChanged)
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(QObject *parent = Q_NULLPTR);
~ParticipantDeviceProxy();
Q_INVOKABLE ParticipantDeviceCore *getAt(int row);
ParticipantDeviceCore *getActiveSpeakerModel();
ParticipantDeviceCore *getMe() const;
CallGui *getCurrentCall() const;
void setCurrentCall(CallGui *callGui);
CallModel *getCallModel() const;
bool isShowMe() const;
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();
ParticipantDeviceGui *getMe() const;
void setMe(ParticipantDeviceGui *me);
protected:
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
CallModel *mCallModel;
bool mShowMe = true;
signals:
void lUpdate();
void currentCallChanged();
void meChanged();
QSharedPointer<ParticipantDeviceList> mList;
private:
QString mSearchText;
CallGui *mCurrentCall = nullptr;
QSharedPointer<ParticipantDeviceList> mParticipants;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -24,13 +24,20 @@
#include "core/path/Paths.hpp"
#include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
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)
: ::Listener<linphone::Conference, linphone::ConferenceListener>(conference, parent) {
mustBeInLinphoneThread(getClassName());
qDebug() << "[ConferenceModel] new" << this;
qDebug() << "[ConferenceModel] new" << this << conference.get();
}
ConferenceModel::~ConferenceModel() {
@ -136,7 +143,8 @@ void ConferenceModel::onActiveSpeakerParticipantDevice(
const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
qDebug() << "onActiveSpeakerParticipantDevice: " << participantDevice->getAddress()->asString().c_str();
emit activeSpeakerParticipantDevice(participantDevice);
emit activeSpeakerParticipantDevice(conference->getActiveSpeakerParticipantDevice());
}
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:
ConferenceModel(const std::shared_ptr<linphone::Conference> &conference, QObject *parent = nullptr);
~ConferenceModel();
static std::shared_ptr<ConferenceModel> create(const std::shared_ptr<linphone::Conference> &conference);
void terminate();
@ -105,11 +106,11 @@ private:
const std::shared_ptr<const linphone::AudioDevice> &audioDevice) override;
signals:
void activeSpeakerParticipantDevice(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void participantAdded(const std::shared_ptr<const linphone::Participant> &participant);
void activeSpeakerParticipantDevice(const std::shared_ptr<linphone::ParticipantDevice> &participantDevice);
void participantAdded(const std::shared_ptr<linphone::Participant> &participant);
void participantRemoved(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 participantDeviceStateChanged(const std::shared_ptr<linphone::Conference> &conference,
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);
*/
}
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::FriendPhoneNumber> makeLinphoneNumber(const QString &label, const QString &number);
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(QString address);

View file

@ -1195,18 +1195,7 @@ int Utils::getYear(const QDate &date) {
bool Utils::isMe(const QString &address) {
bool isMe = false;
App::postModelSync([&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;
}
});
App::postModelSync([&isMe, address]() { isMe = ToolModel::isMe(address); });
return isMe;
}
// 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);
}
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>
void invokeToModel(Func &&callable, Args &&...args) {
if (!tryLock()) return;

View file

@ -28,11 +28,12 @@ Window {
if (call && !conferenceInfo) middleItemStackView.replace(inCallItem)
}
Component.onCompleted: if (call && !conferenceInfo) middleItemStackView.replace(inCallItem)
property var callObj
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."))
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.fillHeight: true
Layout.margins: 20 * DefaultStyle.dp
}
OngoingCallRightPanel {
id: rightPanel
@ -606,6 +608,10 @@ Window {
onJoinConfRequested: mainWindow.joinConference(cameraEnabled)
}
}
Component {
id: inCallItem
Control.Control {
@ -614,163 +620,18 @@ Window {
// implicitHeight: parent.height
Layout.fillHeight: true
Layout.leftMargin: 10 * DefaultStyle.dp
Layout.rightMargin: 10 * DefaultStyle.dp
Layout.alignment: Qt.AlignCenter
/*
background: Rectangle {
anchors.fill: parent
color: DefaultStyle.grey_600
radius: 15 * DefaultStyle.dp
}
contentItem: Item {
id: centerItem
anchors.fill: parent
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
}
}*/
contentItem: CallLayout{
call: mainWindow.call
callTerminatedByUser: mainWindow.callTerminatedByUser
}
}
}

View file

@ -210,13 +210,14 @@ Item {
background: Item{}
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
property var callObj
contentItem: Image {
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
source: AppIcons.phone
}
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/MainLayout.qml
view/Layout/Call/ActiveSpeakerLayout.qml
view/Layout/Call/CallLayout.qml
#view/Layout/Call/GridLayout.qml
view/Layout/Conference/IncallGrid.qml
view/Layout/Contact/ContactLayout.qml
view/Layout/Meeting/AddParticipantsLayout.qml
@ -86,6 +90,8 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Page/Main/ContactPage.qml
view/Page/Main/MeetingPage.qml
view/Tool/utils.js
# Prototypes
view/Prototype/PhoneNumberPrototype.qml

View file

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

View file

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