diff --git a/src/call.cpp b/src/call.cpp index db8736fcd6c038f8392827300b57f124fb09c3d3..919e6f830aec2098f1ef0c71cdf41a34ecc93b37 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -419,6 +419,13 @@ Call::onTextMessage(std::map<std::string, std::string>&& messages) return; } + it = messages.find("application/confOrder+json"); + if (it != messages.end()) { + if (auto conf = Manager::instance().getConferenceFromID(confID_)) + conf->onConfOrder(getCallId(), it->second); + return; + } + { std::lock_guard<std::recursive_mutex> lk {callMutex_}; if (parent_) { diff --git a/src/conference.cpp b/src/conference.cpp index 03e7f2cc14185aa92dc5034d944597d6f68e44c5..6bf29a15f7f6d7b753bc34092f87db229883aa7e 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -19,12 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <regex> #include <sstream> #include "conference.h" #include "manager.h" #include "audio/audiolayer.h" #include "audio/ringbufferpool.h" +#include "jamidht/jamiaccount.h" #ifdef ENABLE_VIDEO #include "sip/sipcall.h" @@ -47,6 +49,17 @@ Conference::Conference() { JAMI_INFO("Create new conference %s", id_.c_str()); + // TODO: For now, add all accounts on the same device as + // conference master. In the future, this should be + // retrieven with another way + auto accounts = jami::Manager::instance().getAllAccounts<JamiAccount>(); + moderators_.reserve(accounts.size()); + for (const auto& account : accounts) { + if (!account) + continue; + moderators_.emplace_back(account->getUsername()); + } + #ifdef ENABLE_VIDEO getVideoMixer()->setOnSourcesUpdated([this](const std::vector<video::SourceInfo>&& infos) { runOnMainThread([w = weak(), infos = std::move(infos)] { @@ -79,8 +92,15 @@ Conference::Conference() and not videoMixer->getActiveParticipant()); // by default, local // is shown as active subCalls.erase(it->second); - newInfo.emplace_back(ParticipantInfo { - std::move(uri), active, info.x, info.y, info.w, info.h, !info.hasVideo, false}); + newInfo.emplace_back(ParticipantInfo {std::move(uri), + active, + info.x, + info.y, + info.w, + info.h, + !info.hasVideo, + false, + shared->isModerator(uri)}); } lk.unlock(); // Handle participants not present in the video mixer @@ -88,7 +108,15 @@ Conference::Conference() std::string uri = ""; if (auto call = Manager::instance().callFactory.getCall<SIPCall>(subCall)) uri = call->getPeerNumber(); - ParticipantInfo {std::move(uri), false, 0, 0, 0, 0, true, false}; + ParticipantInfo {std::move(uri), + false, + 0, + 0, + 0, + 0, + true, + false, + shared->isModerator(uri)}; } { @@ -119,7 +147,8 @@ Conference::~Conference() JAMI_DBG("Stop recording for conf %s", getConfID().c_str()); this->toggleRecording(); if (not call->isRecording()) { - JAMI_DBG("Conference was recorded, start recording for conf %s", call->getCallId().c_str()); + JAMI_DBG("Conference was recorded, start recording for conf %s", + call->getCallId().c_str()); call->toggleRecording(); } } @@ -155,12 +184,12 @@ Conference::add(const std::string& participant_id) JAMI_DBG("Stop recording for call %s", call->getCallId().c_str()); call->toggleRecording(); if (not this->isRecording()) { - JAMI_DBG("One participant was recording, start recording for conference %s", getConfID().c_str()); + JAMI_DBG("One participant was recording, start recording for conference %s", + getConfID().c_str()); this->toggleRecording(); } } - } - else + } else JAMI_ERR("no call associate to participant %s", participant_id.c_str()); #endif // ENABLE_VIDEO } @@ -172,8 +201,9 @@ Conference::setActiveParticipant(const std::string& participant_id) if (!videoMixer_) return; for (const auto& item : participants_) { - if (participant_id == item) { - if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) { + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(item)) { + if (participant_id == item + || call->getPeerNumber().find(participant_id) != std::string::npos) { videoMixer_->setActiveParticipant(call->getVideoRtp().getVideoReceive().get()); return; } @@ -183,6 +213,24 @@ Conference::setActiveParticipant(const std::string& participant_id) videoMixer_->setActiveParticipant(nullptr); } +void +Conference::setLayout(int layout) +{ + switch (layout) { + case 0: + getVideoMixer()->setVideoLayout(video::Layout::GRID); + break; + case 1: + getVideoMixer()->setVideoLayout(video::Layout::ONE_BIG_WITH_SMALL); + break; + case 2: + getVideoMixer()->setVideoLayout(video::Layout::ONE_BIG); + break; + default: + break; + } +} + std::vector<std::map<std::string, std::string>> ConfInfo::toVectorMapStringString() const { @@ -434,4 +482,46 @@ Conference::deinitRecorder(std::shared_ptr<MediaRecorder>& rec) ghostRingBuffer_.reset(); } +void +Conference::onConfOrder(const std::string& callId, const std::string& confOrder) +{ + // Check if the peer is a master + if (auto call = Manager::instance().getCallFromCallID(callId)) { + auto uri = call->getPeerNumber(); + auto separator = uri.find('@'); + if (separator != std::string::npos) + uri = uri.substr(0, separator - 1); + if (!isModerator(uri)) { + JAMI_WARN("Received conference order from a non master (%s)", uri.c_str()); + return; + } + + std::string err; + Json::Value root; + Json::CharReaderBuilder rbuilder; + auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); + if (!reader->parse(confOrder.c_str(), confOrder.c_str() + confOrder.size(), &root, &err)) { + JAMI_WARN("Couldn't parse conference order from %s", uri.c_str()); + return; + } + if (root.isMember("layout")) { + setLayout(root["layout"].asUInt()); + } + if (root.isMember("activeParticipant")) { + setActiveParticipant(root["activeParticipant"].asString()); + } + } +} + +bool +Conference::isModerator(const std::string& uri) const +{ + return std::find_if(moderators_.begin(), + moderators_.end(), + [&uri](const std::string& master) { + return master.find(uri) != std::string::npos; + }) + != moderators_.end(); +} + } // namespace jami diff --git a/src/conference.h b/src/conference.h index 48955e1175b34a5c6a6f6317d4b740b5e550c32b..709274b236e0b28492c107e17426d61767efcdac 100644 --- a/src/conference.h +++ b/src/conference.h @@ -31,7 +31,6 @@ #include "audio/audio_input.h" - #include <json/json.h> #include "recordable.h" @@ -54,6 +53,7 @@ struct ParticipantInfo int h {0}; bool videoMuted {false}; bool audioMuted {false}; + bool isModerator {false}; void fromJson(const Json::Value& v) { @@ -65,6 +65,7 @@ struct ParticipantInfo h = v["h"].asInt(); videoMuted = v["videoMuted"].asBool(); audioMuted = v["audioMuted"].asBool(); + isModerator = v["isModerator"].asBool(); } Json::Value toJson() const @@ -78,6 +79,7 @@ struct ParticipantInfo val["h"] = h; val["videoMuted"] = videoMuted; val["audioMuted"] = audioMuted; + val["isModerator"] = isModerator; return val; } @@ -90,7 +92,8 @@ struct ParticipantInfo {"w", std::to_string(w)}, {"h", std::to_string(h)}, {"videoMuted", videoMuted ? "true" : "false"}, - {"audioMuted", audioMuted ? "true" : "false"}}; + {"audioMuted", audioMuted ? "true" : "false"}, + {"isModerator", isModerator ? "true" : "false"}}; } }; @@ -193,10 +196,13 @@ public: void switchInput(const std::string& input); void setActiveParticipant(const std::string& participant_id); + void setLayout(int layout); void attachVideo(Observable<std::shared_ptr<MediaFrame>>* frame, const std::string& callId); void detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame); + void onConfOrder(const std::string& callId, const std::string& order); + #ifdef ENABLE_VIDEO std::shared_ptr<video::VideoMixer> getVideoMixer(); std::string getVideoInput() const { return mediaInput_; } @@ -214,6 +220,8 @@ private: return std::static_pointer_cast<Conference>(shared_from_this()); } + bool isModerator(const std::string& uri) const; + std::string id_; State confState_ {State::ACTIVE_ATTACHED}; ParticipantSet participants_; @@ -232,6 +240,7 @@ private: #endif std::shared_ptr<jami::AudioInput> audioMixer_; + std::vector<std::string> moderators_ {}; void initRecorder(std::shared_ptr<MediaRecorder>& rec); void deinitRecorder(std::shared_ptr<MediaRecorder>& rec); diff --git a/src/manager.cpp b/src/manager.cpp index e1edd87eeeed3ff29521ce4373ac4d06b0fea8e2..cb6f8cd100636496233a62f946b7e8d1e603c92d 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1469,28 +1469,35 @@ void Manager::setConferenceLayout(const std::string& confId, int layout) { if (auto conf = getConferenceFromID(confId)) { - auto videoMixer = conf->getVideoMixer(); - switch (layout) { - case 0: - videoMixer->setVideoLayout(video::Layout::GRID); - break; - case 1: - videoMixer->setVideoLayout(video::Layout::ONE_BIG_WITH_SMALL); - break; - case 2: - videoMixer->setVideoLayout(video::Layout::ONE_BIG); - break; - default: - break; - } + conf->setLayout(layout); + } else if (auto call = getCallFromCallID(confId)) { + std::map<std::string, std::string> messages; + Json::Value root; + root["layout"] = layout; + Json::StreamWriterBuilder wbuilder; + wbuilder["commentStyle"] = "None"; + wbuilder["indentation"] = ""; + auto output = Json::writeString(wbuilder, root); + messages["application/confOrder+json"] = output; + call->sendTextMessage(messages, call->getPeerDisplayName()); } } void -Manager::setActiveParticipant(const std::string& confId, const std::string& callId) +Manager::setActiveParticipant(const std::string& confId, const std::string& participant) { if (auto conf = getConferenceFromID(confId)) { - conf->setActiveParticipant(callId); + conf->setActiveParticipant(participant); + } else if (auto call = getCallFromCallID(confId)) { + std::map<std::string, std::string> messages; + Json::Value root; + root["activeParticipant"] = participant; + Json::StreamWriterBuilder wbuilder; + wbuilder["commentStyle"] = "None"; + wbuilder["indentation"] = ""; + auto output = Json::writeString(wbuilder, root); + messages["application/confOrder+json"] = output; + call->sendTextMessage(messages, call->getPeerDisplayName()); } }