From f5bc928885d9aa69a94b7436fac789e9d46ac1bd Mon Sep 17 00:00:00 2001
From: agsantos <aline.gondimsantos@savoirfairelinux.com>
Date: Tue, 20 Jul 2021 17:37:42 -0300
Subject: [PATCH] video split: implement host layouts

GitLab: client-qt#476
Change-Id: I23723bf6fcca42ba2fc5cbca29e408facff7e337
---
 src/api/call.h                  |   4 +-
 src/api/callparticipantsmodel.h |  84 ++++++++++++++++-----
 src/callparticipantsmodel.cpp   | 130 ++++++++++++++++++++++----------
 src/newcallmodel.cpp            |  42 ++++++-----
 4 files changed, 184 insertions(+), 76 deletions(-)

diff --git a/src/api/call.h b/src/api/call.h
index 54741a96..628c07c2 100644
--- a/src/api/call.h
+++ b/src/api/call.h
@@ -133,8 +133,8 @@ struct Info
     Type type = Type::INVALID;
     QString peerUri;
     bool isOutgoing;
-    bool audioMuted = false; // this flag is used to check main audio status
-    bool videoMuted = false; // this flag is used to check main video status
+    bool audioMuted = false; // this flag is used to check device audio status
+    bool videoMuted = false; // this flag is used to check device video status
     bool isAudioOnly = false;
     Layout layout = Layout::GRID;
     VectorMapStringString mediaList = {};
diff --git a/src/api/callparticipantsmodel.h b/src/api/callparticipantsmodel.h
index 9abc1468..a699beb8 100644
--- a/src/api/callparticipantsmodel.h
+++ b/src/api/callparticipantsmodel.h
@@ -29,39 +29,63 @@
 #include <QJsonObject>
 
 #include "typedefs.h"
+#include "call.h"
 
 namespace lrc {
 
 namespace api {
 class NewCallModel;
 
+namespace ParticipantsInfosStrings {
+const QString URI = "uri";
+const QString DEVICE = "device";
+const QString ACTIVE = "active";
+const QString AVATAR = "avatar";
+const QString X = "x";
+const QString Y = "y";
+const QString W = "w";
+const QString H = "h";
+const QString WIDTH = "widht";
+const QString HEIGHT = "height";
+const QString VIDEOMUTED = "videoMuted";
+const QString AUDIOLOCALMUTED = "audioLocalMuted";
+const QString AUDIOMODERATORMUTED = "audioModeratorMuted";
+const QString ISMODERATOR = "isModerator";
+const QString HANDRAISED = "handRaised";
+const QString SINKID = "sinkId";
+const QString BESTNAME = "bestName";
+const QString ISLOCAL = "isLocal";
+const QString ISCONTACT = "isContact";
+const QString CALLID = "callId";
+} // namespace ParticipantsInfosStrings
+
 struct ParticipantInfos
 {
     ParticipantInfos() {}
 
     ParticipantInfos(const MapStringString& infos, const QString& callId, const QString& peerId)
     {
-        uri = infos["uri"];
+        uri = infos[ParticipantsInfosStrings::URI];
         if (uri.lastIndexOf("@") > 0)
             uri.truncate(uri.lastIndexOf("@"));
         if (uri.isEmpty())
             uri = peerId;
-        device = infos["device"];
-        active = infos["active"] == "true";
-        x = infos["x"].toInt();
-        y = infos["y"].toInt();
-        width = infos["w"].toInt();
-        height = infos["h"].toInt();
-        videoMuted = infos["videoMuted"] == "true";
-        audioLocalMuted = infos["audioLocalMuted"] == "true";
-        audioModeratorMuted = infos["audioModeratorMuted"] == "true";
-        isModerator = infos["isModerator"] == "true";
-        handRaised = infos["handRaised"] == "true";
-
-        if (infos["sinkId"].isEmpty())
+        device = infos[ParticipantsInfosStrings::DEVICE];
+        active = infos[ParticipantsInfosStrings::ACTIVE] == "true";
+        x = infos[ParticipantsInfosStrings::X].toInt();
+        y = infos[ParticipantsInfosStrings::Y].toInt();
+        width = infos[ParticipantsInfosStrings::W].toInt();
+        height = infos[ParticipantsInfosStrings::H].toInt();
+        videoMuted = infos[ParticipantsInfosStrings::VIDEOMUTED] == "true";
+        audioLocalMuted = infos[ParticipantsInfosStrings::AUDIOLOCALMUTED] == "true";
+        audioModeratorMuted = infos[ParticipantsInfosStrings::AUDIOMODERATORMUTED] == "true";
+        isModerator = infos[ParticipantsInfosStrings::ISMODERATOR] == "true";
+        handRaised = infos[ParticipantsInfosStrings::HANDRAISED] == "true";
+
+        if (infos[ParticipantsInfosStrings::SINKID].isEmpty())
             sinkId = callId + uri + device;
         else
-            sinkId = infos["sinkId"];
+            sinkId = infos[ParticipantsInfosStrings::SINKID];
 
         bestName = "";
     }
@@ -115,6 +139,22 @@ public:
      */
     void update(const VectorMapStringString& infos);
 
+    /**
+     * Update conference layout value
+     */
+    void verifyLayout();
+
+    /**
+     * @param uri participant
+     * @return True if participant is a moderator
+     */
+    bool checkModerator(const QString& uri) const;
+
+    /**
+     * @return the conference layout
+     */
+    call::Layout getLayout() const { return hostLayout_; }
+
     /**
      * @param index participant index
      * @return informations of the participant in index
@@ -123,7 +163,7 @@ public:
 
 private:
     /**
-     * Filter the participants the might appear for the end user
+     * Filter the participants that might appear for the end user
      */
     void filterCandidates(const VectorMapStringString& infos);
 
@@ -136,11 +176,17 @@ private:
     // Participants ordered
     QMap<QString, ParticipantInfos> participants_;
     QList<QString> validUris_;
-    int idx_;
+    int idx_ = 0;
+
+    const NewCallModel& linked_;
+
+    // Protects changes into the paticipants_ variable
+    mutable std::mutex participantsMtx_ {};
+    // Protects calls to the update function
+    std::mutex updateMtx_ {};
 
-    const NewCallModel& linked;
-    std::mutex streamMtx_ {};
     const QString callId_;
+    call::Layout hostLayout_ = call::Layout::GRID;
 };
 } // end namespace api
 } // end namespace lrc
diff --git a/src/callparticipantsmodel.cpp b/src/callparticipantsmodel.cpp
index c0890119..691d2859 100644
--- a/src/callparticipantsmodel.cpp
+++ b/src/callparticipantsmodel.cpp
@@ -31,7 +31,7 @@ namespace api {
 CallParticipants::CallParticipants(const VectorMapStringString& infos,
                                    const QString& callId,
                                    const NewCallModel& linked)
-    : linked(linked)
+    : linked_(linked)
     , callId_(callId)
 {
     update(infos);
@@ -40,18 +40,24 @@ CallParticipants::CallParticipants(const VectorMapStringString& infos,
 QList<ParticipantInfos>
 CallParticipants::getParticipants() const
 {
+    std::lock_guard<std::mutex> lk(participantsMtx_);
     return participants_.values();
 }
 
 void
 CallParticipants::update(const VectorMapStringString& infos)
 {
+    std::lock_guard<std::mutex> lk(updateMtx_);
     validUris_.clear();
     filterCandidates(infos);
     validUris_.sort();
 
     idx_ = 0;
-    auto keys = participants_.keys();
+    QList<QString> keys {};
+    {
+        std::lock_guard<std::mutex> lk(participantsMtx_);
+        keys = participants_.keys();
+    }
     for (const auto& key : keys) {
         auto keyIdx = validUris_.indexOf(key);
         if (keyIdx < 0 || keyIdx >= validUris_.size())
@@ -65,44 +71,78 @@ CallParticipants::update(const VectorMapStringString& infos)
         addParticipant(candidates_[partUri]);
         idx_++;
     }
+
+    verifyLayout();
+}
+
+void
+CallParticipants::verifyLayout()
+{
+    std::lock_guard<std::mutex> lk(participantsMtx_);
+    auto it = std::find_if(participants_.begin(),
+                           participants_.end(),
+                           [](const lrc::api::ParticipantInfos& participant) -> bool {
+                               return participant.active;
+                           });
+
+    auto newLayout = call::Layout::GRID;
+    if (it != participants_.end())
+        if (participants_.size() == 1)
+            newLayout = call::Layout::ONE;
+        else
+            newLayout = call::Layout::ONE_WITH_SMALL;
+    else
+        newLayout = call::Layout::GRID;
+
+    if (newLayout != hostLayout_)
+        hostLayout_ = newLayout;
 }
 
 void
 CallParticipants::removeParticipant(int index)
 {
-    std::lock_guard<std::mutex> lk(streamMtx_);
-    auto it = participants_.begin() + index;
-    participants_.erase(it);
-    Q_EMIT linked.participantRemoved(callId_, idx_);
+    {
+        std::lock_guard<std::mutex> lk(participantsMtx_);
+        auto it = participants_.begin() + index;
+        participants_.erase(it);
+    }
+    Q_EMIT linked_.participantRemoved(callId_, idx_);
 }
 
 void
 CallParticipants::addParticipant(const ParticipantInfos& participant)
 {
-    std::lock_guard<std::mutex> lk(streamMtx_);
-    auto it = participants_.find(participant.uri);
-    if (it == participants_.end()) {
-        participants_.insert(participants_.begin() + idx_, participant.uri, participant);
-        Q_EMIT linked.participantAdded(callId_, idx_);
-    } else {
-        if (participant == (*it))
-            return;
-        (*it) = participant;
-        Q_EMIT linked.participantUpdated(callId_, idx_);
+    bool added {false};
+    {
+        std::lock_guard<std::mutex> lk(participantsMtx_);
+        auto it = participants_.find(participant.uri);
+        if (it == participants_.end()) {
+            participants_.insert(participants_.begin() + idx_, participant.uri, participant);
+            added = true;
+        } else {
+            if (participant == (*it))
+                return;
+            (*it) = participant;
+        }
     }
+    if (added)
+        Q_EMIT linked_.participantAdded(callId_, idx_);
+    else
+        Q_EMIT linked_.participantUpdated(callId_, idx_);
 }
 
 void
 CallParticipants::filterCandidates(const VectorMapStringString& infos)
 {
+    std::lock_guard<std::mutex> lk(participantsMtx_);
     candidates_.clear();
     for (const auto& candidate : infos) {
-        auto peerId = candidate["uri"];
+        auto peerId = candidate[ParticipantsInfosStrings::URI];
         peerId.truncate(peerId.lastIndexOf("@"));
         if (peerId.isEmpty()) {
-            for (const auto& accId : linked.owner.accountModel->getAccountList()) {
+            for (const auto& accId : linked_.owner.accountModel->getAccountList()) {
                 try {
-                    auto& accountInfo = linked.owner.accountModel->getAccountInfo(accId);
+                    auto& accountInfo = linked_.owner.accountModel->getAccountInfo(accId);
                     if (accountInfo.callModel->hasCall(callId_)) {
                         peerId = accountInfo.profileInfo.uri;
                     }
@@ -110,40 +150,54 @@ CallParticipants::filterCandidates(const VectorMapStringString& infos)
                 }
             }
         }
-        if (candidate["w"].toInt() != 0 && candidate["h"].toInt() != 0) {
+        if (candidate[ParticipantsInfosStrings::W].toInt() != 0
+            && candidate[ParticipantsInfosStrings::H].toInt() != 0) {
             validUris_.append(peerId);
             candidates_.insert(peerId, ParticipantInfos(candidate, callId_, peerId));
         }
     }
 }
 
+bool
+CallParticipants::checkModerator(const QString& uri) const
+{
+    std::lock_guard<std::mutex> lk(participantsMtx_);
+    return std::find_if(participants_.cbegin(),
+                        participants_.cend(),
+                        [&](auto participant) {
+                            return participant.uri == uri && participant.isModerator;
+                        })
+           != participants_.cend();
+}
+
 QJsonObject
 CallParticipants::toQJsonObject(uint index) const
 {
+    std::lock_guard<std::mutex> lk(participantsMtx_);
     if (index >= participants_.size())
         return {};
 
     QJsonObject ret;
     const auto& participant = participants_.begin() + index;
 
-    ret["uri"] = participant->uri;
-    ret["device"] = participant->device;
-    ret["sinkId"] = participant->sinkId;
-    ret["bestName"] = participant->bestName;
-    ret["avatar"] = participant->avatar;
-    ret["active"] = participant->active;
-    ret["x"] = participant->x;
-    ret["y"] = participant->y;
-    ret["width"] = participant->width;
-    ret["height"] = participant->height;
-    ret["audioLocalMuted"] = participant->audioLocalMuted;
-    ret["audioModeratorMuted"] = participant->audioModeratorMuted;
-    ret["videoMuted"] = participant->videoMuted;
-    ret["isModerator"] = participant->isModerator;
-    ret["islocal"] = participant->islocal;
-    ret["isContact"] = participant->isContact;
-    ret["handRaised"] = participant->handRaised;
-    ret["callId"] = callId_;
+    ret[ParticipantsInfosStrings::URI] = participant->uri;
+    ret[ParticipantsInfosStrings::DEVICE] = participant->device;
+    ret[ParticipantsInfosStrings::SINKID] = participant->sinkId;
+    ret[ParticipantsInfosStrings::BESTNAME] = participant->bestName;
+    ret[ParticipantsInfosStrings::AVATAR] = participant->avatar;
+    ret[ParticipantsInfosStrings::ACTIVE] = participant->active;
+    ret[ParticipantsInfosStrings::X] = participant->x;
+    ret[ParticipantsInfosStrings::Y] = participant->y;
+    ret[ParticipantsInfosStrings::WIDTH] = participant->width;
+    ret[ParticipantsInfosStrings::HEIGHT] = participant->height;
+    ret[ParticipantsInfosStrings::AUDIOLOCALMUTED] = participant->audioLocalMuted;
+    ret[ParticipantsInfosStrings::AUDIOMODERATORMUTED] = participant->audioModeratorMuted;
+    ret[ParticipantsInfosStrings::VIDEOMUTED] = participant->videoMuted;
+    ret[ParticipantsInfosStrings::ISMODERATOR] = participant->isModerator;
+    ret[ParticipantsInfosStrings::ISLOCAL] = participant->islocal;
+    ret[ParticipantsInfosStrings::ISCONTACT] = participant->isContact;
+    ret[ParticipantsInfosStrings::HANDRAISED] = participant->handRaised;
+    ret[ParticipantsInfosStrings::CALLID] = callId_;
 
     return ret;
 }
diff --git a/src/newcallmodel.cpp b/src/newcallmodel.cpp
index 6a16d522..2e05f08f 100644
--- a/src/newcallmodel.cpp
+++ b/src/newcallmodel.cpp
@@ -970,7 +970,9 @@ NewCallModelPimpl::initCallFromDaemon()
         callInfo->type = call::Type::DIALOG;
         VectorMapStringString infos = CallManager::instance().getConferenceInfos(linked.owner.id,
                                                                                  callId);
-        participantsModel.emplace(callId, std::make_shared<CallParticipants>(infos, callId, linked));
+        auto participantsPtr = std::make_shared<CallParticipants>(infos, callId, linked);
+        callInfo->layout = participantsPtr->getLayout();
+        participantsModel.emplace(callId, std::move(participantsPtr));
         calls.emplace(callId, std::move(callInfo));
         // NOTE/BUG: the videorenderer can't know that the client has restarted
         // So, for now, a user will have to manually restart the medias until
@@ -1013,7 +1015,10 @@ NewCallModelPimpl::initConferencesFromDaemon()
         callInfo->type = call::Type::CONFERENCE;
         VectorMapStringString infos = CallManager::instance().getConferenceInfos(linked.owner.id,
                                                                                  callId);
-        participantsModel.emplace(callId, std::make_shared<CallParticipants>(infos, callId, linked));
+        auto participantsPtr = std::make_shared<CallParticipants>(infos, callId, linked);
+        callInfo->layout = participantsPtr->getLayout();
+        participantsModel.emplace(callId, std::move(participantsPtr));
+
         calls.emplace(callId, std::move(callInfo));
     }
 }
@@ -1121,7 +1126,7 @@ NewCallModel::isModerator(const QString& confId, const QString& uri)
     auto participantsModel = pimpl_->participantsModel.find(confId);
     if (participantsModel == pimpl_->participantsModel.end()
         or participantsModel->second->getParticipants().size() == 0)
-        return false;
+        return true;
     auto ownerUri = owner.profileInfo.uri;
     auto uriToCheck = uri;
     if (uriToCheck.isEmpty()) {
@@ -1131,12 +1136,10 @@ NewCallModel::isModerator(const QString& confId, const QString& uri)
                            ? call->second->type == lrc::api::call::Type::CONFERENCE
                            : false;
     if (!isModerator && participantsModel->second->getParticipants().size() != 0) {
-        for (const auto& participant : participantsModel->second->getParticipants()) {
-            if (participant.uri == uriToCheck) {
-                isModerator = participant.isModerator;
-                break;
-            }
-        }
+        if (!uri.isEmpty())
+            isModerator = participantsModel->second->checkModerator(uri);
+        else
+            isModerator = participantsModel->second->checkModerator(owner.profileInfo.uri);
     }
     return isModerator;
 }
@@ -1478,13 +1481,18 @@ NewCallModelPimpl::slotOnConferenceInfosUpdated(const QString& confId,
     if (it == calls.end() or not it->second)
         return;
 
-    if (participantsModel.find(confId) == participantsModel.end())
-        participantsModel.emplace(confId, std::make_shared<CallParticipants>(infos, confId, linked));
+    auto participantIt = participantsModel.find(confId);
+    if (participantIt == participantsModel.end())
+        participantIt = participantsModel
+                            .emplace(confId,
+                                     std::make_shared<CallParticipants>(infos, confId, linked))
+                            .first;
     else
-        participantsModel[confId]->update(infos);
+        participantIt->second->update(infos);
+    it->second->layout = participantIt->second->getLayout();
 
     // if Jami, remove @ring.dht
-    for (auto& i : participantsModel[confId]->getParticipants()) {
+    for (auto& i : participantIt->second->getParticipants()) {
         i.uri.replace("@ring.dht", "");
         if (i.uri.isEmpty()) {
             if (it->second->type == call::Type::CONFERENCE) {
@@ -1495,6 +1503,7 @@ NewCallModelPimpl::slotOnConferenceInfosUpdated(const QString& confId,
         }
     }
 
+    Q_EMIT linked.callInfosChanged(linked.owner.id, confId);
     emit linked.onParticipantsChanged(confId);
 
     for (auto& info : infos) {
@@ -1544,7 +1553,9 @@ NewCallModelPimpl::slotConferenceCreated(const QString& accountId, const QString
 
     VectorMapStringString infos = CallManager::instance().getConferenceInfos(linked.owner.id,
                                                                              confId);
-    participantsModel[confId] = std::make_shared<CallParticipants>(infos, confId, linked);
+    auto participantsPtr = std::make_shared<CallParticipants>(infos, confId, linked);
+    callInfo->layout = participantsPtr->getLayout();
+    participantsModel[confId] = participantsPtr;
 
     calls[confId] = callInfo;
 
@@ -1622,9 +1633,6 @@ NewCallModelPimpl::onDecodingStarted(const QString& id,
                                      int width,
                                      int height)
 {
-    auto it = calls.find(id);
-    if (it == calls.end())
-        return;
     lrc.getAVModel().addRenderer(id, QSize(width, height), shmPath);
 }
 
-- 
GitLab