diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml index febb490e8aa03050f467ddcc37512cf3d3385f64..4c9acb4ddae1f01c3dac32c84488910c8615d729 100644 --- a/bin/dbus/cx.ring.Ring.CallManager.xml +++ b/bin/dbus/cx.ring.Ring.CallManager.xml @@ -258,6 +258,13 @@ <arg type="b" name="state" direction="in"/> </method> + <method name="muteParticipant" tp:name-for-bindings="muteParticipant"> + <tp:added version="9.7.0"/> + <arg type="s" name="confId" direction="in"/> + <arg type="s" name="peerId" direction="in"/> + <arg type="b" name="state" direction="in"/> + </method> + <method name="isConferenceParticipant" tp:name-for-bindings="isConferenceParticipant"> <arg type="s" name="callID" direction="in"/> <arg type="b" name="isParticipant" direction="out"/> diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp index 9ccbc714622d1cc0dc9ca95ab091a58fae4e2772..2fbe410b7d124f998a00df462158068beaf0b5fa 100644 --- a/bin/dbus/dbuscallmanager.cpp +++ b/bin/dbus/dbuscallmanager.cpp @@ -303,3 +303,10 @@ DBusCallManager::setModerator(const std::string& confId, const std::string& peer { DRing::setModerator(confId, peerId, state); } + +void +DBusCallManager::muteParticipant(const std::string& confId, const std::string& peerId, const bool& state) +{ + DRing::muteParticipant(confId, peerId, state); +} + diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h index ecbe7f64e2fc4dc2004253276dd0c2770cff57b0..5d27a1a294d806afdb6801162f76c074457e9253 100644 --- a/bin/dbus/dbuscallmanager.h +++ b/bin/dbus/dbuscallmanager.h @@ -102,6 +102,7 @@ class DRING_PUBLIC DBusCallManager : void startSmartInfo(const uint32_t& refreshTimeMs); void stopSmartInfo(); void setModerator(const std::string& confId, const std::string& peerId, const bool& state); + void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state); }; #endif // __RING_CALLMANAGER_H__ diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i index d0167c43c5320d7039a43b6776067e133cf93567..4a3de61d7d84f0d67f232c888389768d26d11434 100644 --- a/bin/jni/callmanager.i +++ b/bin/jni/callmanager.i @@ -93,6 +93,7 @@ std::string getConferenceId(const std::string& callID); std::map<std::string, std::string> getConferenceDetails(const std::string& callID); std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId); void setModerator(const std::string& confId, const std::string& peerId, const bool& state); +void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state); /* File Playback methods */ bool startRecordedFilePlayback(const std::string& filepath); diff --git a/bin/nodejs/callmanager.i b/bin/nodejs/callmanager.i index 4b5fdc40fcddf292dccfd22e06fe6e002370a0ae..12f10cad92c2ef908fb8853b7f64ab3553079c7f 100644 --- a/bin/nodejs/callmanager.i +++ b/bin/nodejs/callmanager.i @@ -91,6 +91,7 @@ std::string getConferenceId(const std::string& callID); std::map<std::string, std::string> getConferenceDetails(const std::string& callID); std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId); void setModerator(const std::string& confId, const std::string& peerId, const bool& state); +void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state); /* File Playback methods */ bool startRecordedFilePlayback(const std::string& filepath); diff --git a/configure.ac b/configure.ac index c59ca2726487eb598ba963eaaa81826087497af4..a1b6815223c345e72041d8e5a7710d76e033f48c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac for automake 1.9 and autoconf 2.59 dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([Jami Daemon],[9.6.0],[ring@gnu.org],[jami]) +AC_INIT([Jami Daemon],[9.7.0],[ring@gnu.org],[jami]) AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2020]]) AC_REVISION([$Revision$]) diff --git a/src/call.h b/src/call.h index 247783b18bbf91b7c021c2c6af407b24c7e851a0..2116c90ec0c8308cc1b75594b5a00f290ed9c08b 100644 --- a/src/call.h +++ b/src/call.h @@ -364,6 +364,9 @@ protected: /** Protect every attribute that can be changed by two threads */ mutable std::recursive_mutex callMutex_ {}; + mutable std::mutex confInfoMutex_ {}; + mutable ConfInfo confInfo_ {}; + private: bool validStateTransition(CallState newState); @@ -409,8 +412,6 @@ private: ///< MultiDevice: message received by subcall to merged yet MsgList pendingInMessages_; - mutable std::mutex confInfoMutex_ {}; - mutable ConfInfo confInfo_ {}; }; // Helpers diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp index bd7b63bc7dc4ce95e103ed4f42cae6302fdfd19d..77d0f6bfe4f646f9cba6a327f6d41573be056fd1 100644 --- a/src/client/callmanager.cpp +++ b/src/client/callmanager.cpp @@ -351,4 +351,11 @@ setModerator(const std::string& confId, jami::Manager::instance().setModerator(confId, peerId, state); } +void +muteParticipant(const std::string& confId, + const std::string& peerId, + const bool& state) +{ + jami::Manager::instance().muteParticipant(confId, peerId, state); +} } // namespace DRing diff --git a/src/conference.cpp b/src/conference.cpp index 46fb35a332137438000b0ce3b59784ac2d06dca2..982624117151e57693bc5ba0e0fddb3cbbd3e079 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -25,8 +25,8 @@ #include "conference.h" #include "manager.h" #include "audio/audiolayer.h" -#include "audio/ringbufferpool.h" #include "jamidht/jamiaccount.h" +#include "string_utils.h" #ifdef ENABLE_VIDEO #include "sip/sipcall.h" @@ -38,6 +38,10 @@ #include "call_factory.h" #include "logger.h" +#include "dring/media_const.h" +#include "audio/ringbufferpool.h" + +using namespace std::literals; namespace jami { @@ -91,11 +95,12 @@ Conference::Conference() and not videoMixer->getActiveParticipant()); // by default, local // is shown as active subCalls.erase(it->second); - auto partURI = uri; - auto separator = partURI.find('@'); - if (separator != std::string::npos) - partURI = partURI.substr(0, separator); + std::string_view partURI = uri; + partURI = string_remove_suffix(partURI, '@'); auto isModerator = shared->isModerator(partURI); + if (uri.empty()) + partURI = "host"; + auto isMuted = shared->isMuted(partURI); newInfo.emplace_back(ParticipantInfo {std::move(uri), "", active, @@ -104,7 +109,7 @@ Conference::Conference() info.w, info.h, !info.hasVideo, - false, + isMuted, isModerator}); } lk.unlock(); @@ -262,13 +267,11 @@ Conference::sendConferenceInfos() ConfInfo confInfo = std::move(getConfInfoHostUri(account->getUsername()+ "@ring.dht")); Json::Value jsonArray = {}; - std::vector<std::map<std::string, std::string>> toSend = {}; { std::lock_guard<std::mutex> lk2(confInfoMutex_); for (const auto& info : confInfo) { jsonArray.append(info.toJson()); } - toSend = confInfo.toVectorMapStringString(); } Json::StreamWriterBuilder builder = {}; @@ -386,6 +389,40 @@ Conference::bindParticipant(const std::string& participant_id) } } +void +Conference::unbindParticipant(const std::string& participant_id) +{ + JAMI_INFO("Unbind participant %s from conference %s", participant_id.c_str(), id_.c_str()); + Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(participant_id); +} + +void +Conference::bindHost() +{ + JAMI_INFO("Bind host to conference %s", id_.c_str()); + + auto& rbPool = Manager::instance().getRingBufferPool(); + + for (const auto& item : participants_) { + if (auto call = Manager::instance().getCallFromCallID(item)) { + std::string_view uri = call->getPeerNumber(); + uri = string_remove_suffix(uri, '@'); + if (isMuted(uri)) + continue; + rbPool.bindCallID(item, RingBufferPool::DEFAULT_ID); + rbPool.flush(RingBufferPool::DEFAULT_ID); + } + } +} + + +void +Conference::unbindHost() +{ + JAMI_INFO("Unbind host from conference %s", id_.c_str()); + Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(RingBufferPool::DEFAULT_ID); +} + const ParticipantSet& Conference::getParticipantList() const { @@ -498,12 +535,10 @@ 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); + std::string_view uri = call->getPeerNumber(); + uri = string_remove_suffix(uri, '@'); if (!isModerator(uri)) { - JAMI_WARN("Received conference order from a non master (%s)", uri.c_str()); + JAMI_WARN("Received conference order from a non master (%s)", uri.data()); return; } @@ -512,7 +547,7 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder) 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()); + JAMI_WARN("Couldn't parse conference order from %s", uri.data()); return; } if (root.isMember("layout")) { @@ -521,18 +556,21 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder) if (root.isMember("activeParticipant")) { setActiveParticipant(root["activeParticipant"].asString()); } + if (root.isMember("muteParticipant") and root.isMember("muteState")) { + muteParticipant(root["muteParticipant"].asString(), root["muteState"].asString() == "true"); + } } } bool -Conference::isModerator(const std::string& uri) const +Conference::isModerator(std::string_view uri) const { return std::find_if(moderators_.begin(), moderators_.end(), - [&uri](const std::string& moderator) { - return moderator.find(uri) != std::string::npos; + [&uri](std::string_view moderator) { + return moderator.find(uri) != std::string_view::npos; }) - != moderators_.end(); + != moderators_.end() or isHost(uri); } void @@ -540,17 +578,15 @@ Conference::setModerator(const std::string& uri, const bool& state) { for (const auto& p : participants_) { if (auto call = Manager::instance().callFactory.getCall<SIPCall>(p)) { - auto partURI = call->getPeerNumber(); - auto separator = partURI.find('@'); - if (separator != std::string::npos) - partURI = partURI.substr(0, separator); + std::string_view partURI = call->getPeerNumber(); + partURI = string_remove_suffix(partURI, '@'); if (partURI == uri) { if (state and not isModerator(uri)) { - JAMI_DBG("Add %s as moderator", partURI.c_str()); + JAMI_DBG("Add %s as moderator", partURI.data()); moderators_.emplace(uri); updateModerators(); } else if (not state and isModerator(uri)) { - JAMI_DBG("Remove %s as moderator", partURI.c_str()); + JAMI_DBG("Remove %s as moderator", partURI.data()); moderators_.erase(uri); updateModerators(); } @@ -568,17 +604,101 @@ Conference::updateModerators() std::lock_guard<std::mutex> lk2(confInfoMutex_); for (auto& info : confInfo_) { auto uri = info.uri; - auto separator = uri.find('@'); - if (separator != std::string::npos) - uri = uri.substr(0, separator); + uri = string_remove_suffix(uri, '@'); info.isModerator = isModerator(uri); } } sendConferenceInfos(); } +bool +Conference::isMuted(std::string_view uri) const +{ + return std::find_if(participantsMuted_.begin(), + participantsMuted_.end(), + [&uri](std::string_view pMuted) { + return pMuted.find(uri) != std::string_view::npos; + }) + != participantsMuted_.end(); +} + +void +Conference::muteParticipant(const std::string& uri, const bool& state, const std::string& mediaType) +{ + // Mute host + if (isHost(uri)) { + if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_AUDIO) == 0) { + if (state and not isMuted("host")) { + JAMI_DBG("Mute host"); + participantsMuted_.emplace("host"); + unbindHost(); + updateMuted(); + } else if (not state and isMuted("host")) { + JAMI_DBG("Unmute host"); + participantsMuted_.erase("host"); + bindHost(); + updateMuted(); + } + emitSignal<DRing::CallSignal::AudioMuted>(id_, state); + return; + } else if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_VIDEO) == 0) { +#ifdef ENABLE_VIDEO + if (state) { + if (auto mixer = getVideoMixer()) { + mixer->stopInput(); + } + } else { + if (auto mixer = getVideoMixer()) { + mixer->switchInput(mediaInput_); + } + } + return; +#endif + } + } + + // Mute participant + for (const auto& p : participants_) { + std::string_view peerURI = string_remove_suffix(uri, '@'); + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(p)) { + std::string_view partURI = call->getPeerNumber(); + partURI = string_remove_suffix(partURI, '@'); + if (partURI == peerURI) { + if (state and not isMuted(partURI)) { + JAMI_DBG("Mute participant %s", partURI.data()); + participantsMuted_.emplace(std::string(partURI)); + unbindParticipant(p); + updateMuted(); + } else if (not state and isMuted(partURI)) { + JAMI_DBG("Unmute participant %s", partURI.data()); + participantsMuted_.erase(std::string(partURI)); + bindParticipant(p); + updateMuted(); + } + return; + } + } + } +} + +void +Conference::updateMuted() +{ + { + std::lock_guard<std::mutex> lk2(confInfoMutex_); + for (auto& info : confInfo_) { + auto uri = string_remove_suffix(info.uri, '@'); + if (uri.empty()) + uri = "host"; + info.audioMuted = isMuted(uri); + } + } + sendConferenceInfos(); +} + + ConfInfo -Conference::getConfInfoHostUri(const std::string& uri) +Conference::getConfInfoHostUri(std::string_view uri) { ConfInfo newInfo = confInfo_; for (auto& info : newInfo) { @@ -590,6 +710,28 @@ Conference::getConfInfoHostUri(const std::string& uri) return newInfo; } +bool +Conference::isHost(std::string_view uri) const +{ + if (uri.empty()) + return true; + + // Check if the URI is a local URI (AccountID) for at least one of the subcall + // (a local URI can be in the call with another device) + for (const auto& p : participants_) { + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(p)) { + auto w = call->getAccount(); + auto account = w.lock(); + if (!account) + continue; + if (account->getUsername() == uri) { + return true; + } + } + } + return false; +} + void Conference::updateConferenceInfo(ConfInfo confInfo) { diff --git a/src/conference.h b/src/conference.h index de66bf937f6050f6dd8075428a714e575415f46a..0f7cb02b513bffbc392958e17a8a9b33918d6795 100644 --- a/src/conference.h +++ b/src/conference.h @@ -28,6 +28,7 @@ #include <string> #include <memory> #include <vector> +#include <string_view> #include "audio/audio_input.h" @@ -182,6 +183,21 @@ public: */ void bindParticipant(const std::string& participant_id); + /** + * Bind host to the conference + */ + void bindHost(); + + /** + * unbind a participant from the conference + */ + void unbindParticipant(const std::string& participant_id); + + /** + * unbind host from conference + */ + void unbindHost(); + /** * Get the participant list for this conference */ @@ -206,7 +222,6 @@ public: void detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame); void onConfOrder(const std::string& callId, const std::string& order); - void setModerator(const std::string& uri, const bool& state); #ifdef ENABLE_VIDEO std::shared_ptr<video::VideoMixer> getVideoMixer(); @@ -221,13 +236,17 @@ public: void updateConferenceInfo(ConfInfo confInfo); + void setModerator(const std::string& uri, const bool& state); + void muteParticipant(const std::string& uri, const bool& state, const std::string& mediaType = "MEDIA_TYPE_AUDIO"); + private: std::weak_ptr<Conference> weak() { return std::static_pointer_cast<Conference>(shared_from_this()); } - bool isModerator(const std::string& uri) const; + bool isModerator(std::string_view uri) const; + void updateModerators(); std::string id_; State confState_ {State::ACTIVE_ATTACHED}; @@ -248,13 +267,16 @@ private: std::shared_ptr<jami::AudioInput> audioMixer_; std::set<std::string> moderators_ {}; + std::set<std::string> participantsMuted_ {}; void initRecorder(std::shared_ptr<MediaRecorder>& rec); void deinitRecorder(std::shared_ptr<MediaRecorder>& rec); - void updateModerators(); + bool isMuted(std::string_view uri) const; + void updateMuted(); - ConfInfo getConfInfoHostUri(const std::string& uri); + ConfInfo getConfInfoHostUri(std::string_view uri); + bool isHost(std::string_view uri) const; }; } // namespace jami diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h index 0fb9873aa116006abe73eb91845eaadce0917447..c52d49e9b7153bdb3d6289ec20688363ad18221d 100644 --- a/src/dring/callmanager_interface.h +++ b/src/dring/callmanager_interface.h @@ -78,6 +78,7 @@ DRING_PUBLIC std::map<std::string, std::string> getConferenceDetails(const std:: DRING_PUBLIC std::vector<std::map<std::string, std::string>> getConferenceInfos( const std::string& confId); DRING_PUBLIC void setModerator(const std::string& confId, const std::string& peerId, const bool& state); +DRING_PUBLIC void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state); /* Statistic related methods */ DRING_PUBLIC void startSmartInfo(uint32_t refreshTimeMs); diff --git a/src/manager.cpp b/src/manager.cpp index b92a8b592b2b389953b8242ee223eae54a0012d2..b9baec1fcf0a6f2dedb1f6049d8a19b462ad0b33 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -3231,4 +3231,23 @@ Manager::setModerator(const std::string& confId, const std::string& peerId, cons JAMI_WARN("Fail to change moderator %s, conference %s not found", peerId.c_str(), confId.c_str()); } +void +Manager::muteParticipant(const std::string& confId, const std::string& participant, const bool& state) +{ + if (auto conf = getConferenceFromID(confId)) { + conf->muteParticipant(participant, state); + } else if (auto call = getCallFromCallID(confId)) { + std::map<std::string, std::string> messages; + Json::Value root; + root["muteParticipant"] = participant; + root["muteState"] = state ? TRUE_STR : FALSE_STR; + Json::StreamWriterBuilder wbuilder; + wbuilder["commentStyle"] = "None"; + wbuilder["indentation"] = ""; + auto output = Json::writeString(wbuilder, root); + messages["application/confOrder+json"] = output; + call->sendTextMessage(messages, call->getPeerDisplayName()); + } +} + } // namespace jami diff --git a/src/manager.h b/src/manager.h index 759f78fc2af6bc47aad3b5a86533492b60571646..7e83fd81977f6992a8b4057c247a46d6c36fcf2a 100644 --- a/src/manager.h +++ b/src/manager.h @@ -935,6 +935,7 @@ public: #endif void setModerator(const std::string& confId, const std::string& peerId, const bool& state); + void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state); private: Manager(); diff --git a/src/media/audio/ringbufferpool.cpp b/src/media/audio/ringbufferpool.cpp index 92429a7deaf4752446a3a834ef3895e0dece362b..4b5f094a2947142c0324a452d2834fc4cd744929 100644 --- a/src/media/audio/ringbufferpool.cpp +++ b/src/media/audio/ringbufferpool.cpp @@ -236,6 +236,27 @@ RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, const std::st removeReaderFromRingBuffer(rb, process_id); } +void +RingBufferPool::unBindAllHalfDuplexOut(const std::string& call_id) +{ + const auto& rb_call = getRingBuffer(call_id); + if (not rb_call) { + JAMI_ERR("No ringbuffer associated to call '%s'", call_id.c_str()); + return; + } + + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + auto bindings = getReadBindings(call_id); + if (not bindings) + return; + + const auto bindings_copy = *bindings; // temporary copy + for (const auto& rbuf : bindings_copy) { + removeReaderFromRingBuffer(rb_call, rbuf->getId()); + } +} + void RingBufferPool::unBindAll(const std::string& call_id) { diff --git a/src/media/audio/ringbufferpool.h b/src/media/audio/ringbufferpool.h index db0643a7202cf17a04c033c4189ba0c99d8afd66..c28a267dd1602210b23a98c7848d33287de21239 100644 --- a/src/media/audio/ringbufferpool.h +++ b/src/media/audio/ringbufferpool.h @@ -73,6 +73,8 @@ public: */ void unBindHalfDuplexOut(const std::string& process_id, const std::string& call_id); + void unBindAllHalfDuplexOut(const std::string& call_id); + void unBindAll(const std::string& call_id); bool waitForDataAvailable(const std::string& call_id, diff --git a/src/string_utils.cpp b/src/string_utils.cpp index 567102b8666666ebb6839898c46f92a64cc07932..6dad08fea90cecdea7b3bed273c9e9047567f94e 100644 --- a/src/string_utils.cpp +++ b/src/string_utils.cpp @@ -29,6 +29,7 @@ #include <stdexcept> #include <ios> #include <charconv> +#include <string_view> #ifdef _WIN32 #include <windows.h> #include <oleauto.h> @@ -136,4 +137,13 @@ string_replace(std::string& str, const std::string& from, const std::string& to) } } +std::string_view +string_remove_suffix(std::string_view str, char separator) +{ + auto it = str.find(separator); + if (it != std::string_view::npos) + str = str.substr(0, it); + return str; +} + } // namespace jami diff --git a/src/string_utils.h b/src/string_utils.h index 8f0bb07d8738d1e5d77b5b2f334ef8b9479c4f03..c172323cc87f68d90adf4b2a9c2342e8849e0e26 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -141,4 +141,6 @@ std::vector<unsigned> split_string_to_unsigned(const std::string& s, char sep); void string_replace(std::string& str, const std::string& from, const std::string& to); +std::string_view string_remove_suffix(std::string_view str, char separator); + } // namespace jami