From a93e646ce44e881f080168a5ad852843e81d76f5 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Wed, 6 Dec 2023 16:55:26 +0100 Subject: [PATCH] Feature : Video support for one-one call. - Set Mediastreamer plugin folder. - CameraGui component to manage video. - Sticker component to switch between initials/avatar and video. - Remote video detection on Call. - Fix binary shader files to support at least Qt 6.4. - Use MSQOgl Mediatsreamer2 filter and activate video capabilities. - Add a preview on Call view. --- Linphone/CMakeLists.txt | 15 +-- Linphone/core/App.cpp | 4 +- Linphone/core/CMakeLists.txt | 2 + Linphone/core/call/CallCore.cpp | 18 ++++ Linphone/core/call/CallCore.hpp | 9 ++ Linphone/core/camera/CameraDummy.cpp | 41 +++++++ Linphone/core/camera/CameraDummy.hpp | 37 +++++++ Linphone/core/camera/CameraGui.cpp | 114 ++++++++++++++++++++ Linphone/core/camera/CameraGui.hpp | 70 ++++++++++++ Linphone/data/shaders/roundEffect.frag.qsb | Bin 1627 -> 1620 bytes Linphone/data/shaders/roundEffect.vert.qsb | Bin 1455 -> 1170 bytes Linphone/model/call/CallModel.cpp | 6 ++ Linphone/model/call/CallModel.hpp | 1 + Linphone/model/core/CoreModel.cpp | 12 +++ Linphone/model/listener/Listener.hpp | 4 + Linphone/view/App/CallsWindow.qml | 20 +++- Linphone/view/CMakeLists.txt | 3 + Linphone/view/Item/Contact/Avatar.qml | 17 ++- Linphone/view/Item/Contact/Sticker.qml | 57 ++++++++++ Linphone/view/Prototype/CameraPrototype.qml | 79 ++++++++++++++ 20 files changed, 493 insertions(+), 16 deletions(-) create mode 100644 Linphone/core/camera/CameraDummy.cpp create mode 100644 Linphone/core/camera/CameraDummy.hpp create mode 100644 Linphone/core/camera/CameraGui.cpp create mode 100644 Linphone/core/camera/CameraGui.hpp create mode 100644 Linphone/view/Item/Contact/Sticker.qml create mode 100644 Linphone/view/Prototype/CameraPrototype.qml diff --git a/Linphone/CMakeLists.txt b/Linphone/CMakeLists.txt index 3dc6b9740..a492a5954 100644 --- a/Linphone/CMakeLists.txt +++ b/Linphone/CMakeLists.txt @@ -41,15 +41,16 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG -DQT_QML_DEBUG -DQT_ set(CMAKE_INCLUDE_CURRENT_DIR ON)#useful for config.h include(application_info.cmake) -if(APPLE) - if(MS2_PLUGINS_LOCATION) - set(MSPLUGINS_DIR ${MS2_PLUGINS_LOCATION}) - else() - set(MSPLUGINS_DIR "Frameworks/mediastreamer2.framework/Versions/A/Libraries") - endif() + + +if(MEDIASTREAMER2_PLUGINS_LOCATION) + set(MSPLUGINS_DIR ${MEDIASTREAMER2_PLUGINS_LOCATION}) +elseif(APPLE) + set(MSPLUGINS_DIR "Frameworks/mediastreamer2.framework/Versions/A/Libraries") else() - set(MSPLUGINS_DIR "plugins/mediastreamer") + set(MSPLUGINS_DIR "${CMAKE_INSTALL_LIBDIR}/mediastreamer/plugins") endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config.h") if(${Qt6_VERSION} VERSION_LESS "6.3.0") diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 1ae55bca2..05bc5706e 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -36,6 +36,7 @@ #include "core/account/AccountProxy.hpp" #include "core/call/CallCore.hpp" #include "core/call/CallGui.hpp" +#include "core/camera/CameraGui.hpp" #include "core/friend/FriendCore.hpp" #include "core/friend/FriendGui.hpp" #include "core/logger/QtLogger.hpp" @@ -123,6 +124,7 @@ void App::init() { }, Qt::QueuedConnection); mEngine->load(url); + // mEngine->load(u"qrc:/Linphone/view/Prototype/CameraPrototype.qml"_qs); } void App::initCppInterfaces() { @@ -150,7 +152,7 @@ void App::initCppInterfaces() { qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "MagicSearchProxy"); - + qmlRegisterType(Constants::MainQmlUri, 1, 0, "CameraGui"); LinphoneEnums::registerMetaTypes(); } diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index dd63f0bc9..5778f1717 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -6,6 +6,8 @@ list(APPEND _LINPHONEAPP_SOURCES core/App.cpp core/call/CallCore.cpp core/call/CallGui.cpp + core/camera/CameraGui.cpp + core/camera/CameraDummy.cpp core/friend/FriendCore.cpp core/friend/FriendGui.cpp core/logger/QtLogger.cpp diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp index 8c6667f63..dabf89991 100644 --- a/Linphone/core/call/CallCore.cpp +++ b/Linphone/core/call/CallCore.cpp @@ -75,6 +75,9 @@ void CallCore::setSelf(QSharedPointer me) { mAccountModelConnection->makeConnectToModel(&CallModel::microphoneMutedChanged, [this](bool isMuted) { mAccountModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); }); }); + mAccountModelConnection->makeConnectToModel(&CallModel::remoteVideoEnabledChanged, [this](bool enabled) { + mAccountModelConnection->invokeToCore([this, enabled]() { setRemoteVideoEnabled(enabled); }); + }); // mAccountModelConnection->makeConnect(this, &CallCore::lSetSpeakerMuted, [this](bool isMuted) { // mAccountModelConnection->invokeToModel([this, isMuted]() { mCallModel->setSpeakerMuted(isMuted); }); // }); @@ -243,6 +246,17 @@ void CallCore::setPeerSecured(bool secured) { } } +bool CallCore::getRemoteVideoEnabled() const { + return mRemoteVideoEnabled; +} + +void CallCore::setRemoteVideoEnabled(bool enabled) { + if (mRemoteVideoEnabled != enabled) { + mRemoteVideoEnabled = enabled; + emit remoteVideoEnabledChanged(mRemoteVideoEnabled); + } +} + LinphoneEnums::CallState CallCore::getTransferState() const { return mTransferState; } @@ -254,3 +268,7 @@ void CallCore::setTransferState(LinphoneEnums::CallState state, const QString &m emit transferStateChanged(); } } + +std::shared_ptr CallCore::getModel() const { + return mCallModel; +} diff --git a/Linphone/core/call/CallCore.hpp b/Linphone/core/call/CallCore.hpp index 48a1d7646..dfc32713e 100644 --- a/Linphone/core/call/CallCore.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -42,6 +42,8 @@ class CallCore : public QObject, public AbstractObject { Q_PROPERTY(bool paused READ getPaused WRITE lSetPaused NOTIFY pausedChanged) Q_PROPERTY(QString peerAddress MEMBER mPeerAddress CONSTANT) Q_PROPERTY(bool peerSecured READ getPeerSecured WRITE setPeerSecured NOTIFY peerSecuredChanged) + Q_PROPERTY( + bool remoteVideoEnabled READ getRemoteVideoEnabled WRITE setRemoteVideoEnabled NOTIFY remoteVideoEnabledChanged) Q_PROPERTY(LinphoneEnums::CallState transferState READ getTransferState NOTIFY transferStateChanged) public: @@ -78,9 +80,14 @@ public: bool getPeerSecured() const; void setPeerSecured(bool secured); + bool getRemoteVideoEnabled() const; + void setRemoteVideoEnabled(bool enabled); + LinphoneEnums::CallState getTransferState() const; void setTransferState(LinphoneEnums::CallState state, const QString &message); + std::shared_ptr getModel() const; + signals: void statusChanged(LinphoneEnums::CallStatus status); void stateChanged(LinphoneEnums::CallState state); @@ -93,6 +100,7 @@ signals: void pausedChanged(); void transferStateChanged(); void peerSecuredChanged(); + void remoteVideoEnabledChanged(bool remoteVideoEnabled); // Linphone commands void lAccept(bool withVideo); // Accept an incoming call @@ -137,6 +145,7 @@ private: bool mMicrophoneMuted; bool mCameraEnabled; bool mPaused = false; + bool mRemoteVideoEnabled = false; QSharedPointer> mAccountModelConnection; DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/core/camera/CameraDummy.cpp b/Linphone/core/camera/CameraDummy.cpp new file mode 100644 index 000000000..6b2d8aee0 --- /dev/null +++ b/Linphone/core/camera/CameraDummy.cpp @@ -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 . + */ + +#include +#include +#include +#include + +#include "CameraDummy.hpp" + +// ============================================================================= + +CameraDummy::CameraDummy() { +} + +QOpenGLFramebufferObject *CameraDummy::createFramebufferObject(const QSize &size) { + return new QOpenGLFramebufferObject(size); +} + +void CameraDummy::render() { +} + +void CameraDummy::synchronize(QQuickFramebufferObject *item) { +} diff --git a/Linphone/core/camera/CameraDummy.hpp b/Linphone/core/camera/CameraDummy.hpp new file mode 100644 index 000000000..f04d6637e --- /dev/null +++ b/Linphone/core/camera/CameraDummy.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CAMERA_DUMMY_H_ +#define CAMERA_DUMMY_H_ + +#include +#include + +// ============================================================================= + +class CameraDummy : public QQuickFramebufferObject::Renderer { +public: + CameraDummy(); + QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override; + void render() override; + void synchronize(QQuickFramebufferObject *item) override; +}; + +#endif diff --git a/Linphone/core/camera/CameraGui.cpp b/Linphone/core/camera/CameraGui.cpp new file mode 100644 index 000000000..6bad6c8b3 --- /dev/null +++ b/Linphone/core/camera/CameraGui.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "CameraDummy.hpp" +#include "CameraGui.hpp" +#include "core/App.hpp" +#include "core/call/CallCore.hpp" +#include "core/call/CallGui.hpp" + +DEFINE_ABSTRACT_OBJECT(CameraGui) + +// ============================================================================= +CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) { + mustBeInMainThread(getClassName()); + // The fbo content must be y-mirrored because the ms rendering is y-inverted. + setMirrorVertically(true); + mRefreshTimer.setInterval(1000 / mMaxFps); + connect(&mRefreshTimer, &QTimer::timeout, this, &QQuickFramebufferObject::update, Qt::QueuedConnection); + + mRefreshTimer.start(); +} +CameraGui::~CameraGui() { + mustBeInMainThread("~" + getClassName()); +} + +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: { + } + } + if (!renderer) { + 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); + } else QTimer::singleShot(1, this, &CameraGui::isReady); // Hack because of constness of createRenderer() + return renderer; +} + +bool CameraGui::getIsReady() const { + return mIsReady; +} +void CameraGui::setIsReady(bool isReady) { + if (mIsReady != isReady) { + mIsReady = isReady; + emit isReadyChanged(mIsReady); + } +} +void CameraGui::isReady() { + setIsReady(true); +} +void CameraGui::isNotReady() { + setIsReady(false); +} + +CallGui *CameraGui::getCallGui() const { + return mCallGui; +} + +void CameraGui::setCallGui(CallGui *callGui) { + if (mCallGui != callGui) { + mCallGui = callGui; + emit callGuiChanged(mCallGui); + } +} + +CameraGui::WindowIdLocation CameraGui::getSourceLocation() const { + if (mCallGui != nullptr) return Call; + else return CorePreview; +} diff --git a/Linphone/core/camera/CameraGui.hpp b/Linphone/core/camera/CameraGui.hpp new file mode 100644 index 000000000..39764e2d8 --- /dev/null +++ b/Linphone/core/camera/CameraGui.hpp @@ -0,0 +1,70 @@ +/* + * 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 . + */ + +#ifndef CAMERA_GUI_H_ +#define CAMERA_GUI_H_ + +#include + +#include "tool/AbstractObject.hpp" +#include +#include +#include + +// ============================================================================= + +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); + +public: + CameraGui(QQuickItem *parent = Q_NULLPTR); + virtual ~CameraGui(); + QQuickFramebufferObject::Renderer *createRenderer() const override; + + bool getIsReady() const; + void setIsReady(bool isReady); + void isReady(); + void isNotReady(); + + CallGui *getCallGui() const; + void setCallGui(CallGui *callGui); + + typedef enum { None = -1, CorePreview = 0, Call, Device, Player, Core } WindowIdLocation; + WindowIdLocation getSourceLocation() const; + +signals: + void requestNewRenderer(); + void isReadyChanged(bool isReady); + void callGuiChanged(CallGui *callGui); + +private: + bool mIsReady = false; + QTimer mRefreshTimer; + int mMaxFps = 30; + CallGui *mCallGui = nullptr; + + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/data/shaders/roundEffect.frag.qsb b/Linphone/data/shaders/roundEffect.frag.qsb index e074217ab529431a0875d6252a9b69c2dc350a12..3fb5a8a2a9649bfb9cb9191a7c96c62400a67648 100644 GIT binary patch delta 1567 zcmV+)2H^SI4AcyN000z8c%1E6TW{1x6rOBuu#gfU+)Htj7QC<+XS<;^A%QBPfCLI+ z5v{a}EXN*i-0s?oJsUPckt+4QkNqM21AXjs|4m=2_MDkx@2qoCRcI?!oyhi_Gw1T1 z%gn687@J^>jlnYlPnmfvhQEaQK$YNOztdpD1Eo)~03=<1)?%xm)0TS5)?r!`Y?Jjs z6K4*qu{z8iX)>eI@+&4_^(j~vU^QfY;2tB1`SbFhp80C1bzJhYg~+$<|ffh84xWJ_ax*W4Icv){62ir zcw(i(x9SUjHm<)XGo_rLV3QIzJ{>oV7*p9o!D)~~{v_If8S+0MACG`Oo1u{Ocuvpd zG6UWuQyI^ABP4fU8-?{7v_3+#tzkb6R!eeyRAvI_ zXJLj3HsmuV@lI0G&jEdu<|SG$lWpWL5q*q!H-JWe*(v4jUKQdyD)X**Q)F|74ffD^ zc%NZR^2Q`qm6J0hGeLgO<#?6fb5d9F&H#OZV#shHeNn@GiRKd;&MYq(eMM5=-b4AT zln2Ch0irNTx7fHDeF^edfv-vat`omPc&|%8YT)`UruOX&ts`v{uL_=#Un7kAlf`zM zeBXvze3jT>52F6Bz!@jI7{d{YC8MVc^l6R8xUUhtO!YrbH9ihp%)trry-I6X-xctD zgw~cRpC`%pFGQar9?DJ={W&1T+PqKoyr}74CjN1-lJ)2a<;g{`fw5wv4hEbU>35irZC zNP3b9Hggi*`b=3dS4;yCe<8Hc`sc`+*lNV$LU!&Df{^5aGx zH>G@4>YBE-P^svceBKr=NC&S&Uu>DEG_>xvyIZ)PcH&rUrGj_OPS`iSHXt+sx`jbi z4coG-#=K=$Xo`M}o65SRs)lA7pwtb)`(PK|qL=X6nhB9sRcs{HYCB&fZfngIZqwcX zS#%8YGL$-mg5Km*f~2y{2x?ZHsHQE^Z0w^MM*HoxX0w;Vo^Hop zG|)qv9Uf^u)y&K+!S7Mf%<8(afo&Jz=8;if#ZH2CbvQ~q)%-5@)d8f=pJp>VxRp?L z+Q3=^W*8+Cv7|sHzdAEm_$(W5c9#y>&0%EGhE|ckv)e0hQ?a=Cc6ZpGlN$yq5ygjN RpZ{bP_R^uU><^R0R2(r*BgOy# delta 1574 zcmV+>2HE-44BHHU000z-c%1E6TW{1x6rOBuu#gfU+)Htj7QC<+C*4q*kU*7CKmrA^ zh*nxfmSc}MZg=g)o(&tJNR|5D$NrH1fj;)R|E4chd(OKL1p0pZhj!-z4JMiiU|Ipj~G4VWSS1M=|z=+hYrNss6B zTrSh+O)^!Aj5k7ZcQsy#c0c!NQLvKr=m_QUBIVhv)>E0OS7-u|W8xgDOQVH_Frwq2g%J;5VM z-hbq5+^ajOa3kMMe8=ZWxZ{d&e}|{Wl9BKi-7w+hPQtxV%5R6w?T!(&VplBhV}}ti z%c)4Zo-nq$LHSkLfYEkE&3GZ4$F4}i-m>IL3&`B)xLzoBbuRate7U>_rn*rW#7P@% z8O}mY<0yxz+wQb@vaoKXi3h7YZn7IjO=E|93x*fRiI2=U^bO35Ss_5me~lle!dQ_X zH}beC<*QQHw5`QTMaSgxmT*Bjcp3U)+eD>-b+^^o#`Uxv$6`AbykoY*p6Rs!p$X6} z4616_mR&XGExSTf^kZCC)+JRnG}8d3W(eK`yYLp>gfFa{5NTD#MpDg|^F`t|*InT@ z>`jnG#~?35sX-{{O-?1qe@ex;ipjL}-4%^z5(KX;zFNs|jQA-fy}oHuWE z=(oV?_1?D8A$^^Gf2TWnrw6jtgGxrAyTNV`2HKsXR1FWcu~fB8y-o)=zeFLsAH6|X zJ(x!N8uro5z^?5l^G^`&<}#t*+0F+ifn~7W{Pce_P?!YPru(!?u3|U)J_2 zoVqDqn~~e*DWr@WQft`Hw0!UXrQ@E=A!-{VIjHFy4G+JjUD@0mq8UcR_FALSO<_;B zVlV3Jq0KgrG@oi_W|rXhC}?JN-Q2{si*WPEs4rtD!MZveC7x=27yIe}Qs+;jksaJh zC_8OntqwDcC5c#4ppsvm87zEOj8}Whui4FEWYLCJk-xLsD{xb>wDfj&*uIk)1}YW{ Yh-1jVxC(pewX%%pqK+JY0Fn4qFjeC!nE(I) diff --git a/Linphone/data/shaders/roundEffect.vert.qsb b/Linphone/data/shaders/roundEffect.vert.qsb index 0951efeecf99fb407f08d2cf2ef1a0ce684026dd..b16d3443ae0117dd0f8450ac9d6289f98ef0a784 100644 GIT binary patch literal 1170 zcmV;D1a12O01k0@ob6X_ZyQArUcV&87t*9jnzYcCOI=kqM8>fkglZfGrHEE)Gc~gC zJYjE{U=BNB2^+wSvoq$i5zLNR2!9dlu`1bSey71E#|B}XgJ%RY^k*!OOA9!%!PLZwO}4AWN~Czuj|PcxwP0NICzB64`6H zXy9rc6fSAkx(wMJ5$+D*V!rp`{n*GK2=HC1@Tr15BK3NeBP#5I6fhHjqD$QyiWFOGH=DJ##pDA z8x;Qz#oVAh%~6akijkohTT-vaJ&?BvGcRMPz1gN1h<}gj-ywXI?;D(akaDNwhZJj- z>>sjm58?VQ<%a8@K}}gUJ}s#K1vJ^*U4hEhrR#BV-LP&NmX!p&A4Ws;wVm1lXs+Do zngD+R z?U@bSd>{7vu`fzqwQMp+mc-taX}zrIgspG|_XyvttTC~avOB4q$Iv9A$uw>I>Fbwo z&Yj~ZjAQ3zaG40IcG?3{KKtsM<5ug<^QuwUyr_ohzA9`^tX3kXf>Biq0!`2XHmBzX zIXAj zD4H4NE5)2_m3t@!Wn^k9lYC6H;Eb*2mby_M5?>@yFcsv+Ja~EfdZm%wEAqe_Bt75$ zV(1HZ;K0wz0q-7KV|~nry-)F@^6ep9Ix*hEfjjhLI7GfJy&7}wvGkb!rMryWG0Q4u zKCd;~?IeafVGw#jwcP0}Y|nQ4lBQ@hgU*9RhpgRBzc5Nu5$2vW(|*6KgIy`Gv=lWf z>EJfU40RiII(txE0Ae7?dx~8T>*_j`S(MW+1`UW$(|&iO@(Yb#;K9Ns4rkhH`nFci kW=B&$g{qCPkCtQ>bc0Xb;`#@Z`e$vn*=E++A5ZEz-RAs5m;e9( literal 1455 zcmV;g1yK3`03ND%ob6fNPuo@$ze)HgrW6Vk+N~d^VJs;u3kcS!X;3RVt(A(X3PY13 zWQD|G-V&SI*AP}A?PYI!*-QV*{Q=vsMS~tZEt0K&-XdMdyZ`iV{C}Y zm4R!Py+hFd(4xKR8hoY0s z3^J6l&=?}YHF%O4Drl@0rYI9UF%p^#uwAILA$A5^Vk@wFr0ArTLe^Oo?k*sLg$$F% z14Vl**i4ei2HOXt8klf)L?IspD4fxJbNJYjZ8G=vN=N=<@6T2;%&{ScEdOu%&$?Lj(3dq(sy(dgqb>xY~=2EWr}-%t7homKn>JSbcoO~6Z$-UISGO@1-oYjD4< zLf=&Cn*gh{kY_}WxSxXE0Ni!Bra>QL50Fd;`U2I1{1VYa#6!78`2})K!3F(|kUuNDa|4-mxUz7>| zi-h4SNQa1i2pSmoQ_AHE=}!sVB)?2}-=g@xgUtwGah>?+XNG!ag8X2d+obn5Wc{@q z(Y|1ASTvg;=-X0f^9vHZ*=%ZJf6{EuvJY=Qk)9=bkbRt2T<>~bklTi zOy(4fKd1eGBA=%m&ieeL56t^Bs_`LESWiyTpHuYmB>NToB7KK!^NQ_)VmnXq7DzYY zd4oiqh2IIO;p~CwdX{7Bi^Za*dEE7DT;KH@+DlD`)vEGk{RKDHtK7BPCBbVnohnD> z#(uSC@uQM^M)jhK8J^jF}E$KDKaqA_m%E8aB&rQii&wi6z zTV|WDp++&LB7$$5E;rlg(Xr|}R+7t8e!{I@PIQFwy{7TZ@hpxNmNBnFSd4y0^?j^q zJz#<>_1$@bDU!$(43L41>Hfb6#*_;`qk0e;R8`qw65wqXF10V0&OG36w=V;jVtsk` zVLGpU`6}v5_E!Y>%4q}c_`Cyb%kGV`l)b|)e5G{Z{j&=Su+C4j;kV0D)N7fY8sN|X zJA~t&?wWg+$4xi4TMY7cokOEl{n2q{mCNmAOwNdsyQc%ZKRJztXYyRFm{-9kO*(*> z>T+J52%Kh>TQ%~p=3nX%g$LBhOaipiquRmLIh)U(Jl{6fT*vc_C-%P2soJ;&q`dXb z_iGy)&mR?)!jhsYs*AF)gjgFsj|HQudIai#19UdG6>yG&MuXYl#((Ixmfy^UB`L@Dyf`8CmtOw z*rgj_OWi1j#N@tf$ATR98a#NQW&ZfU22T}qJSpGSKq}aT56ej5?)x>dZ~l%r(*qu* z-l?Ya(Rq0e^s(rF%<-|XkhSbu)32NQ{g%nAO~d2$rnS4OMSRa{)xX3ag&$Vo`^CdA zX1m%lJ(y`z7gnW&PonUt@milnWR6)DG4rH`uT*>w9#KuFW*752J3X7eQrV|1Ds_3c z>|V27se~W!xmbk60ax`G^Fgo!=`1Wo?s7qJ-NFomHrm-)fa+`@Z2{gK&JxJw(^+JZ z55F-3hj^9sm&YG`r{_SHUon|8? zy)IO(yYFMW#cafD1GLmPM_lFbwHh_{Ye)DuL`HusjeQ+;$zxB=6Xi+vjAF$71S@VY J*1sC~^7geT>GS{q diff --git a/Linphone/model/call/CallModel.cpp b/Linphone/model/call/CallModel.cpp index cd4321af1..4c9e48e12 100644 --- a/Linphone/model/call/CallModel.cpp +++ b/Linphone/model/call/CallModel.cpp @@ -146,6 +146,12 @@ void CallModel::onInfoMessageReceived(const std::shared_ptr &cal void CallModel::onStateChanged(const std::shared_ptr &call, linphone::Call::State state, const std::string &message) { + if (state == linphone::Call::State::StreamsRunning) { + // After UpdatedByRemote, video direction could be changed. + auto params = call->getRemoteParams(); + emit remoteVideoEnabledChanged(params && params->videoEnabled()); + emit cameraEnabledChanged(call->cameraEnabled()); + } emit stateChanged(state, message); } diff --git a/Linphone/model/call/CallModel.hpp b/Linphone/model/call/CallModel.hpp index 50ab567b6..a9748bf9a 100644 --- a/Linphone/model/call/CallModel.hpp +++ b/Linphone/model/call/CallModel.hpp @@ -55,6 +55,7 @@ signals: void cameraEnabledChanged(bool enabled); void durationChanged(int); void pausedChanged(bool paused); + void remoteVideoEnabledChanged(bool remoteVideoEnabled); private: QTimer mDurationTimer; diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index 4550ae7bf..f5d514c05 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -78,6 +78,18 @@ void CoreModel::start() { setPathsAfterCreation(); mCore->enableFriendListSubscription(true); mCore->enableRecordAware(true); + mCore->setVideoDisplayFilter("MSQOGL"); + mCore->usePreviewWindow(true); + // Force capture/display. + // Useful if the app was built without video support. + // (The capture/display attributes are reset by the core in this case.) + auto config = mCore->getConfig(); + if (mCore->videoSupported()) { + config->setInt("video", "capture", 1); + config->setInt("video", "display", 1); + } + mCore->enableVideoPreview(false); // SDK doesn't write the state in configuration if not ready. + config->setInt("video", "show_local", 0); // So : write ourself to turn off camera before starting the core. mCore->start(); setPathAfterStart(); mIterateTimer->start(); diff --git a/Linphone/model/listener/Listener.hpp b/Linphone/model/listener/Listener.hpp index 64399febe..dcd51838d 100644 --- a/Linphone/model/listener/Listener.hpp +++ b/Linphone/model/listener/Listener.hpp @@ -60,6 +60,10 @@ public: if (mMonitor && mSelf) mMonitor->addListener(self); } + std::shared_ptr getMonitor() const { + return mMonitor; + } + protected: std::shared_ptr mMonitor; std::shared_ptr mSelf = nullptr; diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index fc9e8928d..1b4880ad5 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -312,6 +312,11 @@ Window { ColumnLayout { anchors.centerIn: parent spacing: 2 + Sticker{ + Layout.fillHeight: true + Layout.fillWidth: true + call: mainWindow.call + } // Avatar { // Layout.alignment: Qt.AlignCenter // visible: mainWindow.isInContactList @@ -505,7 +510,6 @@ Window { || mainWindow.callState == LinphoneEnums.CallState.IncomingReceived ? bottomButtonsLayout.columns - 1 : 0 BottomButton { - enabled: false enabledIcon: AppIcons.videoCamera disabledIcon: AppIcons.videoCameraSlash checked: !mainWindow.call.core.cameraEnabled @@ -527,5 +531,15 @@ Window { } } } - -} \ No newline at end of file + Sticker{ + height: 100 + width: 100 + anchors.right: parent.right + anchors.bottom: parent.bottom + visible: mainWindow.call.core.cameraEnabled + AccountProxy{ + id: accounts + } + account: accounts.defaultAccount + } +} diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index c0ee430b9..dc1cdd7a2 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -17,6 +17,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/BusyIndicator.qml view/Item/Button.qml + view/Item/Carousel.qml view/Item/CheckBox.qml view/Item/ComboBox.qml @@ -24,6 +25,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Contact/Avatar.qml view/Item/Contact/Contact.qml view/Item/Contact/ContactDescription.qml + view/Item/Contact/Sticker.qml view/Item/DesktopPopup.qml view/Item/DigitInput.qml @@ -59,6 +61,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Prototype/PhoneNumberPrototype.qml view/Prototype/AccountsPrototype.qml view/Prototype/CallPrototype.qml + view/Prototype/CameraPrototype.qml view/Prototype/FriendPrototype.qml view/Prototype/ItemPrototype.qml ) diff --git a/Linphone/view/Item/Contact/Avatar.qml b/Linphone/view/Item/Contact/Avatar.qml index 8ceb5c9f0..97e94b632 100644 --- a/Linphone/view/Item/Contact/Avatar.qml +++ b/Linphone/view/Item/Contact/Avatar.qml @@ -6,17 +6,24 @@ import QtQuick.Effects import Linphone import UtilsCpp -// Avatar using initial of the username in case -// they don't have any profile picture +// Fill contact, account or call +// Initials will be displayed if there isn't any avatar. +// TODO : get FriendGui from Call. StackView{ id: mainItem - property FriendGui contact - property AccountGui account - property string address: account ? account.core.identityAddress : '' + property AccountGui account: null + property FriendGui contact: null + property CallGui call: null + property string address: account + ? account.core.identityAddress + : call + ? call.core.peerAddress + : '' property var displayNameObj: UtilsCpp.getDisplayName(address) property bool haveAvatar: (account && account.core.pictureUri ) || (contact && contact.core.pictureUri) + onHaveAvatarChanged: replace(haveAvatar ? avatar : initials, StackView.Immediate) initialItem: haveAvatar ? avatar : initials diff --git a/Linphone/view/Item/Contact/Sticker.qml b/Linphone/view/Item/Contact/Sticker.qml new file mode 100644 index 000000000..68ec1d2bd --- /dev/null +++ b/Linphone/view/Item/Contact/Sticker.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +// Display a sticker from a call or from an account. +// The Avatar is shown while the camera become available. +// The loader restart in case of resetting the renderer. This allow to display the avatar while loading. + +// TODO: sizes, colors, decorations +Rectangle{ + id: mainItem + height: 300 + width: 200 + property CallGui call: null + property AccountGui account: null + color: 'gray' + Avatar{ + anchors.centerIn: parent + height: 100 + width: height + account: mainItem.account + call: mainItem.call + visible: !cameraLoader.active || cameraLoader.status != Loader.Ready || !cameraLoader.item.isReady + } + Loader{ + id: cameraLoader + anchors.fill: parent + Timer{ + id: resetTimer + interval: 1 + onTriggered: {cameraLoader.active=false; cameraLoader.active=true;} + } + active: mainItem.visible && (!call || call.core.remoteVideoEnabled) + sourceComponent: cameraComponent + } + Component{ + id: cameraComponent + Item{ + height: cameraLoader.height + width: cameraLoader.width + property bool isReady: cameraItem.visible + CameraGui{ + id: cameraItem + anchors.fill: parent + visible: isReady + call: mainItem.call + + onRequestNewRenderer: { + console.log("Request new renderer") + resetTimer.restart() + } + } + } + } +} diff --git a/Linphone/view/Prototype/CameraPrototype.qml b/Linphone/view/Prototype/CameraPrototype.qml new file mode 100644 index 000000000..f76c8df1b --- /dev/null +++ b/Linphone/view/Prototype/CameraPrototype.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +Window{ + id: mainItem + height: 400 + width: 800 + visible: true + Rectangle{ + anchors.centerIn: parent + height: 300 + width: 200 + color: 'gray' + Avatar{ + anchors.centerIn: parent + height: 100 + width: height + address: 'sip:jul@toto.com' + } + Loader{ + id: cameraLoader + anchors.fill: parent + Timer{ + id: resetTimer + interval: 1 + onTriggered: {cameraLoader.active=false; cameraLoader.active=true;} + } + active: true + sourceComponent: cameraComponent + } + Component{ + id: cameraComponent + Rectangle{ + height: cameraLoader.height + width: cameraLoader.width + color: 'red' + CameraGui{ + id: cameraItem + anchors.fill: parent + visible: isReady + onVisibleChanged: console.log('Ready?'+visible) + + onRequestNewRenderer: { + console.log("Request new renderer") + resetTimer.restart() + } + } + } + } + } + + /* + Control.StackView{ + id: stackView + anchors.fill: parent + initialItem: cameraComponent + + Component{ + id: avatarComponent + Avatar{ + } + } + Component{ + id: cameraComponent + CameraGui{ + id: cameraItem + onRequestNewRenderer: { + console.log("Request new renderer") + stackView.replace(cameraComponent, Control.StackView.Immediate) + + } + } + } + } + */ +}