diff --git a/src/api/call.h b/src/api/call.h index 54741a966dab3bca1891bb42007561bfd7b6aba9..628c07c2630fb6c31e31b16aa394750b23063624 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 9abc14682cea7444782c5b578125967150842e4e..a699beb8ce15a8a2c3f7e8153894fb6c0979d1d8 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 c089011963d29296fe9fa1a3d69fba1abf3c9032..691d28597595885400fc7145f41d7410952750c3 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 6a16d522dce32af7349608aaab9a06e5ba2f85b6..2e05f08f41d0d7707c380d7a512fa15367d92a89 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); }