LCOV - code coverage report
Current view: top level - src - conference.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 719 1066 67.4 %
Date: 2024-03-28 08:00:27 Functions: 105 146 71.9 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
       5             :  *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
       6             :  *
       7             :  *  This program is free software; you can redistribute it and/or modify
       8             :  *  it under the terms of the GNU General Public License as published by
       9             :  *  the Free Software Foundation; either version 3 of the License, or
      10             :  *  (at your option) any later version.
      11             :  *
      12             :  *  This program is distributed in the hope that it will be useful,
      13             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             :  *  GNU General Public License for more details.
      16             :  *
      17             :  *  You should have received a copy of the GNU General Public License
      18             :  *  along with this program; if not, write to the Free Software
      19             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
      20             :  */
      21             : 
      22             : #include <regex>
      23             : #include <sstream>
      24             : 
      25             : #include "conference.h"
      26             : #include "manager.h"
      27             : #include "audio/audiolayer.h"
      28             : #include "jamidht/jamiaccount.h"
      29             : #include "string_utils.h"
      30             : #include "sip/siptransport.h"
      31             : 
      32             : #include "client/videomanager.h"
      33             : #include "tracepoint.h"
      34             : #ifdef ENABLE_VIDEO
      35             : #include "call.h"
      36             : #include "video/video_input.h"
      37             : #include "video/video_mixer.h"
      38             : #endif
      39             : 
      40             : #ifdef ENABLE_PLUGIN
      41             : #include "plugin/jamipluginmanager.h"
      42             : #endif
      43             : 
      44             : #include "call_factory.h"
      45             : 
      46             : #include "logger.h"
      47             : #include "jami/media_const.h"
      48             : #include "audio/ringbufferpool.h"
      49             : #include "sip/sipcall.h"
      50             : 
      51             : #include <opendht/thread_pool.h>
      52             : 
      53             : using namespace std::literals;
      54             : 
      55             : namespace jami {
      56             : 
      57          38 : Conference::Conference(const std::shared_ptr<Account>& account,
      58             :                        const std::string& confId,
      59             :                        bool attachHost,
      60          38 :                        const std::vector<MediaAttribute>& hostAttr)
      61          38 :     : id_(confId.empty() ? Manager::instance().callFactory.getNewCallID() : confId)
      62          38 :     , account_(account)
      63             : #ifdef ENABLE_VIDEO
      64          38 :     , videoEnabled_(account->isVideoEnabled())
      65         114 :     , attachHost_(attachHost)
      66             : #endif
      67             : {
      68             :     /** NOTE:
      69             :      *
      70             :      *** Handling mute state of the local host.
      71             :      *
      72             :      * When a call is added to a conference, the media source of the
      73             :      * call is set to the audio/video mixers output, and the host media
      74             :      * source (e.g. camera), is added as a source for the mixer.
      75             :      * Note that, by design, the mixers are never muted, but the mixer
      76             :      * can produce audio/video frames with no content (silence or black
      77             :      * video frames) if all the participants are muted.
      78             :      *
      79             :      * The mute state of the local host is set as follows:
      80             :      *
      81             :      * 1. If the video is disabled, the mute state is irrelevant.
      82             :      * 2. If the local is not attached, the mute state is irrelevant.
      83             :      * 3. When the conference is created from existing calls:
      84             :      *  the mute state is set to true if the local mute state of
      85             :      *  all participating calls are true.
      86             :      * 4. Attaching the local host to an existing conference:
      87             :      *  the audio and video is set to the default capture device
      88             :      *  (microphone and/or camera), and set to un-muted state.
      89             :      */
      90             : 
      91          38 :     JAMI_INFO("Create new conference %s", id_.c_str());
      92          38 :     if (hostAttr.empty()) {
      93           1 :         setLocalHostDefaultMediaSource();
      94             :     } else {
      95          37 :         hostSources_ = hostAttr;
      96          37 :         reportMediaNegotiationStatus();
      97             :     }
      98          38 :     duration_start_ = clock::now();
      99             : 
     100             : #ifdef ENABLE_VIDEO
     101          38 :     auto itVideo = std::find_if(hostSources_.begin(), hostSources_.end(), [&](auto attr) {
     102          70 :         return attr.type_ == MediaType::MEDIA_VIDEO;
     103             :     });
     104             :     // Only set host source if creating conference from joining calls
     105          38 :     auto hasVideo = videoEnabled_ && itVideo != hostSources_.end() && attachHost_;
     106          38 :     auto source = hasVideo ? itVideo->sourceUri_ : "";
     107          38 :     videoMixer_ = std::make_shared<video::VideoMixer>(id_, source, hasVideo);
     108          38 :     videoMixer_->setOnSourcesUpdated([this](std::vector<video::SourceInfo>&& infos) {
     109          69 :         runOnMainThread([w = weak(), infos = std::move(infos)] {
     110          67 :             auto shared = w.lock();
     111          67 :             if (!shared)
     112           0 :                 return;
     113          67 :             auto acc = std::dynamic_pointer_cast<JamiAccount>(shared->account_.lock());
     114          67 :             if (!acc)
     115           0 :                 return;
     116          67 :             ConfInfo newInfo;
     117             :             {
     118          67 :                 std::lock_guard lock(shared->confInfoMutex_);
     119          67 :                 newInfo.w = shared->confInfo_.w;
     120          67 :                 newInfo.h = shared->confInfo_.h;
     121          67 :                 newInfo.layout = shared->confInfo_.layout;
     122          67 :             }
     123          67 :             auto hostAdded = false;
     124             :             // Handle participants showing their video
     125         204 :             for (const auto& info : infos) {
     126         137 :                 std::string uri {};
     127         137 :                 bool isLocalMuted = false, isPeerRecording = false;
     128         137 :                 std::string deviceId {};
     129         137 :                 auto active = false;
     130         137 :                 if (!info.callId.empty()) {
     131         122 :                     std::string callId = info.callId;
     132         244 :                     if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(callId))) {
     133         122 :                         uri = call->getPeerNumber();
     134         122 :                         isLocalMuted = call->isPeerMuted();
     135         122 :                         isPeerRecording = call->isPeerRecording();
     136         122 :                         if (auto* transport = call->getTransport())
     137         122 :                             deviceId = transport->deviceId();
     138         122 :                     }
     139         122 :                     std::string_view peerId = string_remove_suffix(uri, '@');
     140         122 :                     auto isModerator = shared->isModerator(peerId);
     141         122 :                     auto isHandRaised = shared->isHandRaised(deviceId);
     142         122 :                     auto isModeratorMuted = shared->isMuted(callId);
     143         122 :                     auto isVoiceActive = shared->isVoiceActive(info.streamId);
     144         122 :                     if (auto videoMixer = shared->videoMixer_)
     145         122 :                         active = videoMixer->verifyActive(info.streamId);
     146         366 :                     newInfo.emplace_back(ParticipantInfo {std::move(uri),
     147             :                                                           deviceId,
     148         122 :                                                           std::move(info.streamId),
     149             :                                                           active,
     150         122 :                                                           info.x,
     151         122 :                                                           info.y,
     152         122 :                                                           info.w,
     153         122 :                                                           info.h,
     154         122 :                                                           !info.hasVideo,
     155             :                                                           isLocalMuted,
     156             :                                                           isModeratorMuted,
     157             :                                                           isModerator,
     158             :                                                           isHandRaised,
     159             :                                                           isVoiceActive,
     160             :                                                           isPeerRecording});
     161         122 :                 } else {
     162          15 :                     auto isModeratorMuted = false;
     163             :                     // If not local
     164          15 :                     auto streamInfo = shared->videoMixer_->streamInfo(info.source);
     165          15 :                     std::string streamId = streamInfo.streamId;
     166          15 :                     if (!streamId.empty()) {
     167             :                         // Retrieve calls participants
     168             :                         // TODO: this is a first version, we assume that the peer is not
     169             :                         // a master of a conference and there is only one remote
     170             :                         // In the future, we should retrieve confInfo from the call
     171             :                         // To merge layouts informations
     172           3 :                         isModeratorMuted = shared->isMuted(streamId);
     173           3 :                         if (auto videoMixer = shared->videoMixer_)
     174           3 :                             active = videoMixer->verifyActive(streamId);
     175           3 :                         if (auto call = std::dynamic_pointer_cast<SIPCall>(
     176           6 :                                 getCall(streamInfo.callId))) {
     177           0 :                             uri = call->getPeerNumber();
     178           0 :                             isLocalMuted = call->isPeerMuted();
     179           0 :                             isPeerRecording = call->isPeerRecording();
     180           0 :                             if (auto* transport = call->getTransport())
     181           0 :                                 deviceId = transport->deviceId();
     182           3 :                         }
     183             :                     } else {
     184          12 :                         streamId = sip_utils::streamId("", sip_utils::DEFAULT_VIDEO_STREAMID);
     185          12 :                         if (auto videoMixer = shared->videoMixer_)
     186          12 :                             active = videoMixer->verifyActive(streamId);
     187             :                     }
     188          15 :                     std::string_view peerId = string_remove_suffix(uri, '@');
     189          15 :                     auto isModerator = shared->isModerator(peerId);
     190          15 :                     if (uri.empty() && !hostAdded) {
     191          14 :                         hostAdded = true;
     192          14 :                         peerId = "host"sv;
     193          14 :                         deviceId = acc->currentDeviceId();
     194          14 :                         isLocalMuted = shared->isMediaSourceMuted(MediaType::MEDIA_AUDIO);
     195          14 :                         isPeerRecording = shared->isRecording();
     196             :                     }
     197          15 :                     auto isHandRaised = shared->isHandRaised(deviceId);
     198          15 :                     auto isVoiceActive = shared->isVoiceActive(streamId);
     199          45 :                     newInfo.emplace_back(ParticipantInfo {std::move(uri),
     200             :                                                           deviceId,
     201          15 :                                                           std::move(streamId),
     202             :                                                           active,
     203          15 :                                                           info.x,
     204          15 :                                                           info.y,
     205          15 :                                                           info.w,
     206          15 :                                                           info.h,
     207          15 :                                                           !info.hasVideo,
     208             :                                                           isLocalMuted,
     209             :                                                           isModeratorMuted,
     210             :                                                           isModerator,
     211             :                                                           isHandRaised,
     212             :                                                           isVoiceActive,
     213             :                                                           isPeerRecording});
     214          15 :                 }
     215         137 :             }
     216          67 :             if (auto videoMixer = shared->videoMixer_) {
     217          67 :                 newInfo.h = videoMixer->getHeight();
     218          67 :                 newInfo.w = videoMixer->getWidth();
     219          67 :             }
     220          67 :             if (!hostAdded) {
     221          53 :                 ParticipantInfo pi;
     222          53 :                 pi.videoMuted = true;
     223          53 :                 pi.audioLocalMuted = shared->isMediaSourceMuted(MediaType::MEDIA_AUDIO);
     224          53 :                 pi.isModerator = true;
     225          53 :                 newInfo.emplace_back(pi);
     226          53 :             }
     227             : 
     228          67 :             shared->updateConferenceInfo(std::move(newInfo));
     229          67 :         });
     230          67 :     });
     231             : 
     232          38 :     if (attachHost && itVideo == hostSources_.end()) {
     233             :         // If no video, we still want to attach outself
     234           6 :         videoMixer_->addAudioOnlySource("", "host_audio_0");
     235             :     }
     236          38 :     if (attachHost) {
     237          37 :         setState(State::ACTIVE_ATTACHED);
     238             :     }
     239          38 :     auto conf_res = split_string_to_unsigned(jami::Manager::instance()
     240          38 :                                                  .videoPreferences.getConferenceResolution(),
     241          38 :                                              'x');
     242          38 :     if (conf_res.size() == 2u) {
     243             : #if defined(__APPLE__) && TARGET_OS_MAC
     244             :         videoMixer_->setParameters(conf_res[0], conf_res[1], AV_PIX_FMT_NV12);
     245             : #else
     246          38 :         videoMixer_->setParameters(conf_res[0], conf_res[1]);
     247             : #endif
     248             :     } else {
     249           0 :         JAMI_ERR("Conference resolution is invalid");
     250             :     }
     251             : #endif
     252             : 
     253          38 :     parser_.onVersion([&](uint32_t) {}); // TODO
     254          44 :     parser_.onCheckAuthorization([&](std::string_view peerId) { return isModerator(peerId); });
     255          38 :     parser_.onHangupParticipant([&](const auto& accountUri, const auto& deviceId) {
     256           0 :         hangupParticipant(accountUri, deviceId);
     257           0 :     });
     258          43 :     parser_.onRaiseHand([&](const auto& deviceId, bool state) { setHandRaised(deviceId, state); });
     259          38 :     parser_.onSetActiveStream(
     260           0 :         [&](const auto& streamId, bool state) { setActiveStream(streamId, state); });
     261          38 :     parser_.onMuteStreamAudio(
     262           0 :         [&](const auto& accountUri, const auto& deviceId, const auto& streamId, bool state) {
     263           0 :             muteStream(accountUri, deviceId, streamId, state);
     264           0 :         });
     265          38 :     parser_.onSetLayout([&](int layout) { setLayout(layout); });
     266             : 
     267             :     // Version 0, deprecated
     268          38 :     parser_.onKickParticipant([&](const auto& participantId) { hangupParticipant(participantId); });
     269          38 :     parser_.onSetActiveParticipant(
     270           0 :         [&](const auto& participantId) { setActiveParticipant(participantId); });
     271          38 :     parser_.onMuteParticipant(
     272           0 :         [&](const auto& participantId, bool state) { muteParticipant(participantId, state); });
     273          38 :     parser_.onRaiseHandUri([&](const auto& uri, bool state) {
     274           0 :         if (auto call = std::dynamic_pointer_cast<SIPCall>(getCallFromPeerID(uri)))
     275           0 :             if (auto* transport = call->getTransport())
     276           0 :                 setHandRaised(std::string(transport->deviceId()), state);
     277           0 :     });
     278             : 
     279          38 :     parser_.onVoiceActivity(
     280           0 :         [&](const auto& streamId, bool state) { setVoiceActivity(streamId, state); });
     281             :     jami_tracepoint(conference_begin, id_.c_str());
     282          38 : }
     283             : 
     284          38 : Conference::~Conference()
     285             : {
     286          38 :     JAMI_INFO("Destroying conference %s", id_.c_str());
     287             : 
     288             : #ifdef ENABLE_VIDEO
     289          42 :     foreachCall([&](auto call) {
     290           4 :         call->exitConference();
     291             :         // Reset distant callInfo
     292           4 :         call->resetConfInfo();
     293             :         // Trigger the SIP negotiation to update the resolution for the remaining call
     294             :         // ideally this sould be done without renegotiation
     295           8 :         call->switchInput(
     296           4 :             Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice());
     297             : 
     298             :         // Continue the recording for the call if the conference was recorded
     299           4 :         if (isRecording()) {
     300           0 :             JAMI_DEBUG("Stop recording for conf {:s}", getConfId());
     301           0 :             toggleRecording();
     302           0 :             if (not call->isRecording()) {
     303           0 :                 JAMI_DEBUG("Conference was recorded, start recording for conf {:s}",
     304             :                            call->getCallId());
     305           0 :                 call->toggleRecording();
     306             :             }
     307             :         }
     308             :         // Notify that the remaining peer is still recording after conference
     309           4 :         if (call->isPeerRecording())
     310           0 :             call->peerRecording(true);
     311           4 :     });
     312          38 :     if (videoMixer_) {
     313          38 :         auto& sink = videoMixer_->getSink();
     314          38 :         for (auto it = confSinksMap_.begin(); it != confSinksMap_.end();) {
     315           0 :             sink->detach(it->second.get());
     316           0 :             it->second->stop();
     317           0 :             it = confSinksMap_.erase(it);
     318             :         }
     319             :     }
     320             : #endif // ENABLE_VIDEO
     321             : #ifdef ENABLE_PLUGIN
     322             :     {
     323          38 :         std::lock_guard lk(avStreamsMtx_);
     324          38 :         jami::Manager::instance()
     325          38 :             .getJamiPluginManager()
     326          38 :             .getCallServicesManager()
     327          38 :             .clearCallHandlerMaps(getConfId());
     328          38 :         Manager::instance().getJamiPluginManager().getCallServicesManager().clearAVSubject(
     329             :             getConfId());
     330          38 :         confAVStreams.clear();
     331          38 :     }
     332             : #endif // ENABLE_PLUGIN
     333          38 :     if (shutdownCb_)
     334          14 :         shutdownCb_(getDuration().count());
     335             :     // do not propagate sharing from conf host to calls
     336          38 :     closeMediaPlayer(mediaPlayerId_);
     337             :     jami_tracepoint(conference_end, id_.c_str());
     338          38 : }
     339             : 
     340             : Conference::State
     341         785 : Conference::getState() const
     342             : {
     343         785 :     return confState_;
     344             : }
     345             : 
     346             : void
     347          64 : Conference::setState(State state)
     348             : {
     349         192 :     JAMI_DEBUG("[conf {:s}] Set state to [{:s}] (was [{:s}])",
     350             :                id_,
     351             :                getStateStr(state),
     352             :                getStateStr());
     353             : 
     354          64 :     confState_ = state;
     355          64 : }
     356             : 
     357             : void
     358           5 : Conference::setLocalHostDefaultMediaSource()
     359             : {
     360           5 :     hostSources_.clear();
     361             :     // Setup local audio source
     362           5 :     MediaAttribute audioAttr;
     363           5 :     if (confState_ == State::ACTIVE_ATTACHED) {
     364             :         audioAttr
     365           5 :             = {MediaType::MEDIA_AUDIO, false, false, true, {}, sip_utils::DEFAULT_AUDIO_STREAMID};
     366             :     }
     367             : 
     368          15 :     JAMI_DEBUG("[conf {:s}] Setting local host audio source to [{:s}]", id_, audioAttr.toString());
     369           5 :     hostSources_.emplace_back(audioAttr);
     370             : 
     371             : #ifdef ENABLE_VIDEO
     372           5 :     if (isVideoEnabled()) {
     373           5 :         MediaAttribute videoAttr;
     374             :         // Setup local video source
     375           5 :         if (confState_ == State::ACTIVE_ATTACHED) {
     376             :             videoAttr
     377             :                 = {MediaType::MEDIA_VIDEO,
     378             :                    false,
     379             :                    false,
     380             :                    true,
     381          10 :                    Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice(),
     382           5 :                    sip_utils::DEFAULT_VIDEO_STREAMID};
     383             :         }
     384          15 :         JAMI_DEBUG("[conf {:s}] Setting local host video source to [{:s}]",
     385             :                    id_,
     386             :                    videoAttr.toString());
     387           5 :         hostSources_.emplace_back(videoAttr);
     388           5 :     }
     389             : #endif
     390             : 
     391           5 :     reportMediaNegotiationStatus();
     392           5 : }
     393             : 
     394             : void
     395          45 : Conference::reportMediaNegotiationStatus()
     396             : {
     397          45 :     emitSignal<libjami::CallSignal::MediaNegotiationStatus>(
     398          45 :         getConfId(),
     399             :         libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS,
     400          90 :         currentMediaList());
     401          45 : }
     402             : 
     403             : std::vector<std::map<std::string, std::string>>
     404          52 : Conference::currentMediaList() const
     405             : {
     406          52 :     return MediaAttribute::mediaAttributesToMediaMaps(hostSources_);
     407             : }
     408             : 
     409             : #ifdef ENABLE_PLUGIN
     410             : void
     411          80 : Conference::createConfAVStreams()
     412             : {
     413          80 :     std::string accountId = getAccountId();
     414             : 
     415           0 :     auto audioMap = [](const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
     416           0 :         return std::static_pointer_cast<AudioFrame>(m)->pointer();
     417             :     };
     418             : 
     419             :     // Preview and Received
     420          80 :     if ((audioMixer_ = jami::getAudioInput(getConfId()))) {
     421          80 :         auto audioSubject = std::make_shared<MediaStreamSubject>(audioMap);
     422          80 :         StreamData previewStreamData {getConfId(), false, StreamType::audio, getConfId(), accountId};
     423          80 :         createConfAVStream(previewStreamData, *audioMixer_, audioSubject);
     424          80 :         StreamData receivedStreamData {getConfId(), true, StreamType::audio, getConfId(), accountId};
     425          80 :         createConfAVStream(receivedStreamData, *audioMixer_, audioSubject);
     426          80 :     }
     427             : 
     428             : #ifdef ENABLE_VIDEO
     429             : 
     430          80 :     if (videoMixer_) {
     431             :         // Review
     432          80 :         auto receiveSubject = std::make_shared<MediaStreamSubject>(pluginVideoMap_);
     433          80 :         StreamData receiveStreamData {getConfId(), true, StreamType::video, getConfId(), accountId};
     434          80 :         createConfAVStream(receiveStreamData, *videoMixer_, receiveSubject);
     435             : 
     436             :         // Preview
     437          80 :         if (auto videoPreview = videoMixer_->getVideoLocal()) {
     438           0 :             auto previewSubject = std::make_shared<MediaStreamSubject>(pluginVideoMap_);
     439             :             StreamData previewStreamData {getConfId(),
     440             :                                           false,
     441           0 :                                           StreamType::video,
     442             :                                           getConfId(),
     443           0 :                                           accountId};
     444           0 :             createConfAVStream(previewStreamData, *videoPreview, previewSubject);
     445          80 :         }
     446          80 :     }
     447             : #endif // ENABLE_VIDEO
     448          80 : }
     449             : 
     450             : void
     451         240 : Conference::createConfAVStream(const StreamData& StreamData,
     452             :                                AVMediaStream& streamSource,
     453             :                                const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject,
     454             :                                bool force)
     455             : {
     456         240 :     std::lock_guard lk(avStreamsMtx_);
     457         480 :     const std::string AVStreamId = StreamData.id + std::to_string(static_cast<int>(StreamData.type))
     458         480 :                                    + std::to_string(StreamData.direction);
     459         240 :     auto it = confAVStreams.find(AVStreamId);
     460         240 :     if (!force && it != confAVStreams.end())
     461         126 :         return;
     462             : 
     463         114 :     confAVStreams.erase(AVStreamId);
     464         114 :     confAVStreams[AVStreamId] = mediaStreamSubject;
     465         114 :     streamSource.attachPriorityObserver(mediaStreamSubject);
     466         114 :     jami::Manager::instance()
     467         114 :         .getJamiPluginManager()
     468         114 :         .getCallServicesManager()
     469         114 :         .createAVSubject(StreamData, mediaStreamSubject);
     470         366 : }
     471             : #endif // ENABLE_PLUGIN
     472             : 
     473             : void
     474         146 : Conference::setLocalHostMuteState(MediaType type, bool muted)
     475             : {
     476         422 :     for (auto& source : hostSources_)
     477         276 :         if (source.type_ == type)
     478         145 :             source.muted_ = muted;
     479         146 : }
     480             : 
     481             : bool
     482         460 : Conference::isMediaSourceMuted(MediaType type) const
     483             : {
     484         460 :     if (getState() != State::ACTIVE_ATTACHED) {
     485             :         // Assume muted if not attached.
     486           1 :         return true;
     487             :     }
     488             : 
     489         459 :     if (type != MediaType::MEDIA_AUDIO and type != MediaType::MEDIA_VIDEO) {
     490           0 :         JAMI_ERR("Unsupported media type");
     491           0 :         return true;
     492             :     }
     493             : 
     494             :     // if one is muted, then consider that all are
     495        1265 :     for (const auto& source : hostSources_) {
     496         835 :         if (source.muted_ && source.type_ == type)
     497          29 :             return true;
     498         806 :         if (source.type_ == MediaType::MEDIA_NONE) {
     499           0 :             JAMI_WARN("The host source for %s is not set. The mute state is meaningless",
     500             :                       source.mediaTypeToString(source.type_));
     501             :             // Assume muted if the media is not present.
     502           0 :             return true;
     503             :         }
     504             :     }
     505         430 :     return false;
     506             : }
     507             : 
     508             : void
     509          80 : Conference::takeOverMediaSourceControl(const std::string& callId)
     510             : {
     511          80 :     auto call = getCall(callId);
     512          80 :     if (not call) {
     513           0 :         JAMI_ERR("No call matches participant %s", callId.c_str());
     514           0 :         return;
     515             :     }
     516             : 
     517          80 :     auto account = call->getAccount().lock();
     518          80 :     if (not account) {
     519           0 :         JAMI_ERR("No account detected for call %s", callId.c_str());
     520           0 :         return;
     521             :     }
     522             : 
     523          80 :     auto mediaList = call->getMediaAttributeList();
     524             : 
     525          80 :     std::vector<MediaType> mediaTypeList {MediaType::MEDIA_AUDIO, MediaType::MEDIA_VIDEO};
     526             : 
     527         240 :     for (auto mediaType : mediaTypeList) {
     528             :         // Try to find a media with a valid source type
     529         225 :         auto check = [mediaType](auto const& mediaAttr) {
     530         225 :             return (mediaAttr.type_ == mediaType);
     531         160 :         };
     532             : 
     533         160 :         auto iter = std::find_if(mediaList.begin(), mediaList.end(), check);
     534             : 
     535         160 :         if (iter == mediaList.end()) {
     536             :             // Nothing to do if the call does not have a stream with
     537             :             // the requested media.
     538          45 :             JAMI_DEBUG("[Call: {:s}] Does not have an active [{:s}] media source",
     539             :                        callId,
     540             :                        MediaAttribute::mediaTypeToString(mediaType));
     541          15 :             continue;
     542          15 :         }
     543             : 
     544         145 :         if (getState() == State::ACTIVE_ATTACHED) {
     545             :             // To mute the local source, all the sources of the participating
     546             :             // calls must be muted. If it's the first participant, just use
     547             :             // its mute state.
     548         145 :             if (participants_.size() == 1) {
     549          69 :                 setLocalHostMuteState(iter->type_, iter->muted_);
     550             :             } else {
     551          76 :                 setLocalHostMuteState(iter->type_, iter->muted_ or isMediaSourceMuted(iter->type_));
     552             :             }
     553             :         }
     554             : 
     555             :         // Un-mute media in the call. The mute/un-mute state will be handled
     556             :         // by the conference/mixer from now on.
     557         145 :         iter->muted_ = false;
     558             :     }
     559             : 
     560             :     // Update the media states in the newly added call.
     561          80 :     call->requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList));
     562             : 
     563             :     // Notify the client
     564         240 :     for (auto mediaType : mediaTypeList) {
     565         160 :         if (mediaType == MediaType::MEDIA_AUDIO) {
     566          80 :             bool muted = isMediaSourceMuted(MediaType::MEDIA_AUDIO);
     567          80 :             JAMI_WARN("Take over [AUDIO] control from call %s - current local source state [%s]",
     568             :                       callId.c_str(),
     569             :                       muted ? "muted" : "un-muted");
     570          80 :             emitSignal<libjami::CallSignal::AudioMuted>(id_, muted);
     571             :         } else {
     572          80 :             bool muted = isMediaSourceMuted(MediaType::MEDIA_VIDEO);
     573          80 :             JAMI_WARN("Take over [VIDEO] control from call %s - current local source state [%s]",
     574             :                       callId.c_str(),
     575             :                       muted ? "muted" : "un-muted");
     576          80 :             emitSignal<libjami::CallSignal::VideoMuted>(id_, muted);
     577             :         }
     578             :     }
     579          80 : }
     580             : 
     581             : bool
     582           3 : Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
     583             : {
     584           3 :     if (getState() != State::ACTIVE_ATTACHED) {
     585           0 :         JAMI_ERROR("[conf {}] Request media change can be performed only in attached mode",
     586             :                    getConfId());
     587           0 :         return false;
     588             :     }
     589             : 
     590           9 :     JAMI_DEBUG("[conf {:s}] Request media change", getConfId());
     591             : 
     592           3 :     auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, false);bool hasFileSharing {false};
     593             : 
     594           9 :     for (const auto& media : mediaAttrList) {
     595           6 :         if (!media.enabled_ || media.sourceUri_.empty())
     596           6 :             continue;
     597             : 
     598             :         // Supported MRL schemes
     599           3 :         static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
     600             : 
     601           3 :         const auto pos = media.sourceUri_.find(sep);
     602           3 :         if (pos == std::string::npos)
     603           3 :             continue;
     604             : 
     605           0 :         const auto prefix = media.sourceUri_.substr(0, pos);
     606           0 :         if ((pos + sep.size()) >= media.sourceUri_.size())
     607           0 :             continue;
     608             : 
     609           0 :         if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
     610           0 :             hasFileSharing = true;
     611           0 :             mediaPlayerId_ = media.sourceUri_;
     612           0 :             createMediaPlayer(mediaPlayerId_);
     613             :         }
     614           0 :     }
     615             : 
     616           3 :     if (!hasFileSharing) {
     617           3 :         closeMediaPlayer(mediaPlayerId_);
     618           3 :         mediaPlayerId_ = "";
     619             :     }
     620             : 
     621           9 :     for (auto const& mediaAttr : mediaAttrList) {
     622          18 :         JAMI_DEBUG("[conf {:s}] New requested media: {:s}", getConfId(), mediaAttr.toString(true));
     623             :     }
     624             : 
     625           3 :     std::vector<std::string> newVideoInputs;
     626           9 :     for (auto const& mediaAttr : mediaAttrList) {
     627             :         // Find media
     628           6 :         auto oldIdx = std::find_if(hostSources_.begin(), hostSources_.end(), [&](auto oldAttr) {
     629           9 :             return oldAttr.sourceUri_ == mediaAttr.sourceUri_
     630           4 :                    && oldAttr.type_ == mediaAttr.type_
     631          13 :                    && oldAttr.label_ == mediaAttr.label_;
     632             :         });
     633             :         // If video, add to newVideoInputs
     634             :         // NOTE: For now, only supports video
     635           6 :         if (mediaAttr.type_ == MediaType::MEDIA_VIDEO)
     636           3 :             newVideoInputs.emplace_back(mediaAttr.sourceUri_);
     637           6 :         if (oldIdx != hostSources_.end()) {
     638             :             // Check if muted status changes
     639           4 :             if (mediaAttr.muted_ != oldIdx->muted_) {
     640             :                 // If the current media source is muted, just call un-mute, it
     641             :                 // will set the new source as input.
     642           2 :                 muteLocalHost(mediaAttr.muted_,
     643           1 :                               mediaAttr.type_ == MediaType::MEDIA_AUDIO
     644             :                                   ? libjami::Media::Details::MEDIA_TYPE_AUDIO
     645             :                                   : libjami::Media::Details::MEDIA_TYPE_VIDEO);
     646             :             }
     647             :         }
     648             :     }
     649             : 
     650             : #ifdef ENABLE_VIDEO
     651           3 :     if (videoMixer_)
     652           3 :         videoMixer_->switchInputs(newVideoInputs);
     653             : #endif
     654           3 :     hostSources_ = mediaAttrList; // New medias
     655           3 :     if (!isMuted("host"sv) && !isMediaSourceMuted(MediaType::MEDIA_AUDIO))
     656           2 :         bindHost();
     657             : 
     658             :     // It's host medias, so no need to negotiate anything, but inform the client.
     659           3 :     reportMediaNegotiationStatus();
     660           3 :     return true;
     661           3 : }
     662             : 
     663             : void
     664           2 : Conference::handleMediaChangeRequest(const std::shared_ptr<Call>& call,
     665             :                                      const std::vector<libjami::MediaMap>& remoteMediaList)
     666             : {
     667           6 :     JAMI_DEBUG("Conf [{:s}] Answer to media change request", getConfId());
     668           2 :     auto currentMediaList = hostSources_;
     669             : 
     670             : #ifdef ENABLE_VIDEO
     671             :     // If the new media list has video, remove the participant from audioonlylist.
     672             :     auto remoteHasVideo
     673           2 :         = MediaAttribute::hasMediaType(MediaAttribute::buildMediaAttributesList(remoteMediaList,
     674             :                                                                                 false),
     675             :                                        MediaType::MEDIA_VIDEO);
     676           2 :     if (videoMixer_ && remoteHasVideo) {
     677           2 :         auto callId = call->getCallId();
     678           4 :         videoMixer_->removeAudioOnlySource(
     679           4 :             callId, std::string(sip_utils::streamId(callId, sip_utils::DEFAULT_AUDIO_STREAMID)));
     680           2 :     }
     681             : #endif
     682             : 
     683           2 :     auto remoteList = remoteMediaList;
     684           7 :     for (auto it = remoteList.begin(); it != remoteList.end();) {
     685          15 :         if (it->at(libjami::Media::MediaAttributeKey::MUTED) == TRUE_STR
     686          15 :             or it->at(libjami::Media::MediaAttributeKey::ENABLED) == FALSE_STR) {
     687           0 :             it = remoteList.erase(it);
     688             :         } else {
     689           5 :             ++it;
     690             :         }
     691             :     }
     692             :     // Create minimum media list (ignore muted and disabled medias)
     693           2 :     std::vector<libjami::MediaMap> newMediaList;
     694           2 :     newMediaList.reserve(remoteMediaList.size());
     695           6 :     for (auto const& media : currentMediaList) {
     696           4 :         if (media.enabled_ and not media.muted_)
     697           4 :             newMediaList.emplace_back(MediaAttribute::toMediaMap(media));
     698             :     }
     699           3 :     for (auto idx = newMediaList.size(); idx < remoteMediaList.size(); idx++)
     700           1 :         newMediaList.emplace_back(remoteMediaList[idx]);
     701             : 
     702             :     // NOTE:
     703             :     // Since this is a conference, newly added media will be also
     704             :     // accepted.
     705             :     // This also means that if original call was an audio-only call,
     706             :     // the local camera will be enabled, unless the video is disabled
     707             :     // in the account settings.
     708           2 :     call->answerMediaChangeRequest(newMediaList);
     709           2 :     call->enterConference(shared_from_this());
     710           2 : }
     711             : 
     712             : void
     713          80 : Conference::addParticipant(const std::string& participant_id)
     714             : {
     715         240 :     JAMI_DEBUG("Adding call {:s} to conference {:s}", participant_id, id_);
     716             : 
     717             :     jami_tracepoint(conference_add_participant, id_.c_str(), participant_id.c_str());
     718             : 
     719             :     {
     720          80 :         std::lock_guard lk(participantsMtx_);
     721          80 :         if (!participants_.insert(participant_id).second)
     722           0 :             return;
     723          80 :     }
     724             : 
     725         160 :     if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(participant_id))) {
     726             :         // Check if participant was muted before conference
     727          80 :         if (call->isPeerMuted())
     728           0 :             participantsMuted_.emplace(call->getCallId());
     729             : 
     730             :         // NOTE:
     731             :         // When a call joins a conference, the media source of the call
     732             :         // will be set to the output of the conference mixer.
     733          80 :         takeOverMediaSourceControl(participant_id);
     734          80 :         auto w = call->getAccount();
     735          80 :         auto account = w.lock();
     736          80 :         if (account) {
     737             :             // Add defined moderators for the account link to the call
     738          80 :             for (const auto& mod : account->getDefaultModerators()) {
     739           0 :                 moderators_.emplace(mod);
     740          80 :             }
     741             : 
     742             :             // Check for localModeratorsEnabled preference
     743          80 :             if (account->isLocalModeratorsEnabled() && not localModAdded_) {
     744          38 :                 auto accounts = jami::Manager::instance().getAllAccounts<JamiAccount>();
     745         174 :                 for (const auto& account : accounts) {
     746         136 :                     moderators_.emplace(account->getUsername());
     747             :                 }
     748          38 :                 localModAdded_ = true;
     749          38 :             }
     750             : 
     751             :             // Check for allModeratorEnabled preference
     752          80 :             if (account->isAllModerators())
     753          80 :                 moderators_.emplace(getRemoteId(call));
     754             :         }
     755             : #ifdef ENABLE_VIDEO
     756             :         // In conference, if a participant joins with an audio only
     757             :         // call, it must be listed in the audioonlylist.
     758          80 :         auto mediaList = call->getMediaAttributeList();
     759          80 :         if (call->peerUri().find("swarm:") != 0) { // We're hosting so it's already ourself.
     760          69 :             if (videoMixer_ && not MediaAttribute::hasMediaType(mediaList, MediaType::MEDIA_VIDEO)) {
     761          26 :                 videoMixer_->addAudioOnlySource(call->getCallId(),
     762          26 :                                                 sip_utils::streamId(call->getCallId(),
     763             :                                                                     sip_utils::DEFAULT_AUDIO_STREAMID));
     764             :             }
     765             :         }
     766          80 :         call->enterConference(shared_from_this());
     767             :         // Continue the recording for the conference if one participant was recording
     768          80 :         if (call->isRecording()) {
     769           0 :             JAMI_DEBUG("Stop recording for call {:s}", call->getCallId());
     770           0 :             call->toggleRecording();
     771           0 :             if (not this->isRecording()) {
     772           0 :                 JAMI_DEBUG("One participant was recording, start recording for conference {:s}",
     773             :                            getConfId());
     774           0 :                 this->toggleRecording();
     775             :             }
     776             :         }
     777             : #endif // ENABLE_VIDEO
     778          80 :     } else
     779          80 :         JAMI_ERR("no call associate to participant %s", participant_id.c_str());
     780             : #ifdef ENABLE_PLUGIN
     781          80 :     createConfAVStreams();
     782             : #endif
     783             : }
     784             : 
     785             : void
     786           0 : Conference::setActiveParticipant(const std::string& participant_id)
     787             : {
     788             : #ifdef ENABLE_VIDEO
     789           0 :     if (!videoMixer_)
     790           0 :         return;
     791           0 :     if (isHost(participant_id)) {
     792           0 :         videoMixer_->setActiveStream(sip_utils::streamId("", sip_utils::DEFAULT_VIDEO_STREAMID));
     793           0 :         return;
     794             :     }
     795           0 :     if (auto call = getCallFromPeerID(participant_id)) {
     796           0 :         videoMixer_->setActiveStream(
     797           0 :             sip_utils::streamId(call->getCallId(), sip_utils::DEFAULT_VIDEO_STREAMID));
     798           0 :         return;
     799           0 :     }
     800             : 
     801           0 :     auto remoteHost = findHostforRemoteParticipant(participant_id);
     802           0 :     if (not remoteHost.empty()) {
     803             :         // This logic will be handled client side
     804           0 :         JAMI_WARN("Change remote layout is not supported");
     805           0 :         return;
     806             :     }
     807             :     // Unset active participant by default
     808           0 :     videoMixer_->resetActiveStream();
     809             : #endif
     810             : }
     811             : 
     812             : void
     813           1 : Conference::setActiveStream(const std::string& streamId, bool state)
     814             : {
     815             : #ifdef ENABLE_VIDEO
     816           1 :     if (!videoMixer_)
     817           0 :         return;
     818           1 :     if (state)
     819           1 :         videoMixer_->setActiveStream(streamId);
     820             :     else
     821           0 :         videoMixer_->resetActiveStream();
     822             : #endif
     823             : }
     824             : 
     825             : void
     826           0 : Conference::setLayout(int layout)
     827             : {
     828             : #ifdef ENABLE_VIDEO
     829           0 :     if (layout < 0 || layout > 2) {
     830           0 :         JAMI_ERR("Unknown layout %u", layout);
     831           0 :         return;
     832             :     }
     833           0 :     if (!videoMixer_)
     834           0 :         return;
     835             :     {
     836           0 :         std::lock_guard lk(confInfoMutex_);
     837           0 :         confInfo_.layout = layout;
     838           0 :     }
     839           0 :     videoMixer_->setVideoLayout(static_cast<video::Layout>(layout));
     840             : #endif
     841             : }
     842             : 
     843             : std::vector<std::map<std::string, std::string>>
     844         269 : ConfInfo::toVectorMapStringString() const
     845             : {
     846         269 :     std::vector<std::map<std::string, std::string>> infos;
     847         269 :     infos.reserve(size());
     848        1075 :     for (const auto& info : *this)
     849         806 :         infos.emplace_back(info.toMap());
     850         269 :     return infos;
     851           0 : }
     852             : 
     853             : std::string
     854         186 : ConfInfo::toString() const
     855             : {
     856         186 :     Json::Value val = {};
     857         753 :     for (const auto& info : *this) {
     858         566 :         val["p"].append(info.toJson());
     859             :     }
     860         184 :     val["w"] = w;
     861         186 :     val["h"] = h;
     862         186 :     val["v"] = v;
     863         186 :     val["layout"] = layout;
     864         557 :     return Json::writeString(Json::StreamWriterBuilder {}, val);
     865         184 : }
     866             : 
     867             : void
     868          87 : Conference::sendConferenceInfos()
     869             : {
     870             :     // Inform calls that the layout has changed
     871          87 :     foreachCall([&](auto call) {
     872             :         // Produce specific JSON for each participant (2 separate accounts can host ...
     873             :         // a conference on a same device, the conference is not link to one account).
     874         186 :         auto w = call->getAccount();
     875         186 :         auto account = w.lock();
     876         186 :         if (!account)
     877           0 :             return;
     878             : 
     879         558 :         dht::ThreadPool::io().run(
     880         371 :             [call,
     881         186 :              confInfo = getConfInfoHostUri(account->getUsername() + "@ring.dht",
     882         186 :                                            call->getPeerNumber())] {
     883         186 :                 call->sendConfInfo(confInfo.toString());
     884             :             });
     885         186 :     });
     886             : 
     887          87 :     auto confInfo = getConfInfoHostUri("", "");
     888             : #ifdef ENABLE_VIDEO
     889          87 :     createSinks(confInfo);
     890             : #endif
     891             : 
     892             :     // Inform client that layout has changed
     893          87 :     jami::emitSignal<libjami::CallSignal::OnConferenceInfosUpdated>(id_,
     894             :                                                                     confInfo
     895         174 :                                                                         .toVectorMapStringString());
     896          87 : }
     897             : 
     898             : #ifdef ENABLE_VIDEO
     899             : void
     900          87 : Conference::createSinks(const ConfInfo& infos)
     901             : {
     902          87 :     std::lock_guard lk(sinksMtx_);
     903          87 :     if (!videoMixer_)
     904           0 :         return;
     905          87 :     auto& sink = videoMixer_->getSink();
     906         261 :     Manager::instance().createSinkClients(getConfId(),
     907             :                                           infos,
     908             :                                           {std::static_pointer_cast<video::VideoFrameActiveWriter>(
     909             :                                               sink)},
     910          87 :                                           confSinksMap_);
     911          87 : }
     912             : #endif
     913             : 
     914             : void
     915          77 : Conference::removeParticipant(const std::string& participant_id)
     916             : {
     917         231 :     JAMI_DEBUG("Remove call {:s} in conference {:s}", participant_id, id_);
     918             :     {
     919          77 :         std::lock_guard lk(participantsMtx_);
     920          77 :         if (!participants_.erase(participant_id))
     921           1 :             return;
     922          77 :     }
     923         152 :     if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(participant_id))) {
     924          76 :         const auto& peerId = getRemoteId(call);
     925          76 :         participantsMuted_.erase(call->getCallId());
     926          76 :         if (auto* transport = call->getTransport())
     927          65 :             handsRaised_.erase(std::string(transport->deviceId()));
     928          76 :         if (videoMixer_) {
     929         215 :             for (auto const& rtpSession : call->getRtpSessionList()) {
     930         139 :                 if (rtpSession->getMediaType() == MediaType::MEDIA_AUDIO)
     931          76 :                     videoMixer_->removeAudioOnlySource(participant_id, rtpSession->streamId());
     932         139 :                 if (videoMixer_->verifyActive(rtpSession->streamId()))
     933           1 :                     videoMixer_->resetActiveStream();
     934          76 :             }
     935             :         }
     936             : 
     937             : #ifdef ENABLE_VIDEO
     938          76 :         auto sinkId = getConfId() + peerId;
     939          76 :         call->exitConference();
     940          76 :         if (call->isPeerRecording())
     941           0 :             call->peerRecording(false);
     942             : #endif // ENABLE_VIDEO
     943         152 :     }
     944             : }
     945             : 
     946             : void
     947          12 : Conference::attachLocalParticipant()
     948             : {
     949          36 :     JAMI_LOG("Attach local participant to conference {}", id_);
     950             : 
     951          12 :     if (getState() == State::ACTIVE_DETACHED) {
     952           0 :         setState(State::ACTIVE_ATTACHED);
     953           0 :         setLocalHostDefaultMediaSource();
     954             : 
     955           0 :         bindHost();
     956             : 
     957             : #ifdef ENABLE_VIDEO
     958           0 :         if (videoMixer_) {
     959           0 :             std::vector<std::string> videoInputs;
     960           0 :             for (const auto& source : hostSources_) {
     961           0 :                 if (source.type_ == MediaType::MEDIA_VIDEO)
     962           0 :                     videoInputs.emplace_back(source.sourceUri_);
     963             :             }
     964           0 :             videoMixer_->switchInputs(videoInputs);
     965           0 :         }
     966             : #endif
     967             :     } else {
     968          12 :         JAMI_WARN(
     969             :             "Invalid conference state in attach participant: current \"%s\" - expected \"%s\"",
     970             :             getStateStr(),
     971             :             "ACTIVE_DETACHED");
     972             :     }
     973          12 : }
     974             : 
     975             : void
     976           4 : Conference::detachLocalParticipant()
     977             : {
     978           4 :     JAMI_INFO("Detach local participant from conference %s", id_.c_str());
     979           4 :     if (getState() == State::ACTIVE_ATTACHED) {
     980           4 :         unbindHost();
     981             : 
     982             : #ifdef ENABLE_VIDEO
     983           4 :         if (videoMixer_)
     984           4 :             videoMixer_->stopInputs();
     985             : #endif
     986             :     } else {
     987           0 :         JAMI_WARN(
     988             :             "Invalid conference state in detach participant: current \"%s\" - expected \"%s\"",
     989             :             getStateStr(),
     990             :             "ACTIVE_ATTACHED");
     991           0 :         return;
     992             :     }
     993             : 
     994           4 :     setLocalHostDefaultMediaSource();
     995           4 :     setState(State::ACTIVE_DETACHED);
     996             : }
     997             : 
     998             : void
     999         150 : Conference::bindParticipant(const std::string& participant_id)
    1000             : {
    1001         450 :     JAMI_LOG("Bind participant {} to conference {}", participant_id, id_);
    1002             : 
    1003         150 :     auto& rbPool = Manager::instance().getRingBufferPool();
    1004             : 
    1005             :     // Bind each of the new participant's audio streams to each of the other participants audio streams
    1006         150 :     if (auto participantCall = getCall(participant_id)) {
    1007         149 :         auto participantStreams = participantCall->getAudioStreams();
    1008         298 :         for (auto stream : participantStreams) {
    1009         414 :             for (const auto& other : getParticipantList()) {
    1010         265 :                 auto otherCall = other != participant_id ? getCall(other) : nullptr;
    1011         265 :                 if (otherCall) {
    1012         117 :                     auto otherStreams = otherCall->getAudioStreams();
    1013         234 :                     for (auto otherStream : otherStreams) {
    1014         117 :                         if (isMuted(other))
    1015           0 :                             rbPool.bindHalfDuplexOut(otherStream.first, stream.first);
    1016             :                         else
    1017         117 :                             rbPool.bindRingbuffers(stream.first, otherStream.first);
    1018             : 
    1019         117 :                         rbPool.flush(otherStream.first);
    1020         117 :                     }
    1021         117 :                 }
    1022         414 :             }
    1023             : 
    1024             :             // Bind local participant to other participants only if the
    1025             :             // local is attached to the conference.
    1026         149 :             if (getState() == State::ACTIVE_ATTACHED) {
    1027         149 :                 if (isMediaSourceMuted(MediaType::MEDIA_AUDIO))
    1028          12 :                     rbPool.bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, stream.first);
    1029             :                 else
    1030         137 :                     rbPool.bindRingbuffers(stream.first, RingBufferPool::DEFAULT_ID);
    1031         149 :                 rbPool.flush(RingBufferPool::DEFAULT_ID);
    1032             :             }
    1033         149 :         }
    1034         299 :     }
    1035         150 : }
    1036             : 
    1037             : void
    1038           2 : Conference::unbindParticipant(const std::string& participant_id)
    1039             : {
    1040           2 :     JAMI_INFO("Unbind participant %s from conference %s", participant_id.c_str(), id_.c_str());
    1041           2 :     if (auto call = getCall(participant_id)) {
    1042           2 :         auto medias = call->getAudioStreams();
    1043           2 :         auto& rbPool = Manager::instance().getRingBufferPool();
    1044           4 :         for (const auto& [id, muted] : medias) {
    1045           2 :             rbPool.unBindAllHalfDuplexOut(id);
    1046             :         }
    1047           4 :     }
    1048           2 : }
    1049             : 
    1050             : void
    1051           2 : Conference::bindHost()
    1052             : {
    1053           6 :     JAMI_LOG("Bind host to conference {}", id_);
    1054             : 
    1055           2 :     auto& rbPool = Manager::instance().getRingBufferPool();
    1056             : 
    1057           6 :     for (const auto& item : getParticipantList()) {
    1058           4 :         if (auto call = getCall(item)) {
    1059           4 :             auto medias = call->getAudioStreams();
    1060           8 :             for (const auto& [id, muted] : medias) {
    1061          14 :                 for (const auto& source : hostSources_) {
    1062          10 :                     if (source.type_ == MediaType::MEDIA_AUDIO) {
    1063           4 :                         if (source.label_ == sip_utils::DEFAULT_AUDIO_STREAMID) {
    1064           4 :                             if (muted)
    1065           0 :                                 rbPool.bindHalfDuplexOut(id, RingBufferPool::DEFAULT_ID);
    1066             :                             else
    1067           4 :                                 rbPool.bindRingbuffers(id, RingBufferPool::DEFAULT_ID);
    1068             :                         } else {
    1069           0 :                             auto buffer = source.sourceUri_;
    1070           0 :                             static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
    1071           0 :                             const auto pos = source.sourceUri_.find(sep);
    1072           0 :                             if (pos != std::string::npos)
    1073           0 :                                 buffer = source.sourceUri_.substr(pos + sep.size());
    1074             : 
    1075           0 :                             if (muted)
    1076           0 :                                 rbPool.bindHalfDuplexOut(id, buffer);
    1077             :                             else
    1078           0 :                                 rbPool.bindRingbuffers(id, buffer);
    1079           0 :                         }
    1080             :                     }
    1081             :                 }
    1082           4 :                 rbPool.flush(id);
    1083             :             }
    1084           8 :         }
    1085           2 :     }
    1086           2 :     rbPool.flush(RingBufferPool::DEFAULT_ID);
    1087           2 : }
    1088             : 
    1089             : 
    1090             : void
    1091           5 : Conference::unbindHost()
    1092             : {
    1093           5 :     JAMI_INFO("Unbind host from conference %s", id_.c_str());
    1094          14 :     for (const auto& source : hostSources_) {
    1095           9 :         if (source.type_ == MediaType::MEDIA_AUDIO) {
    1096           5 :             if (source.label_ == sip_utils::DEFAULT_AUDIO_STREAMID) {
    1097           5 :                 Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(RingBufferPool::DEFAULT_ID);
    1098             :             } else {
    1099           0 :                 auto buffer = source.sourceUri_;
    1100           0 :                 static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
    1101           0 :                 const auto pos = source.sourceUri_.find(sep);
    1102           0 :                 if (pos != std::string::npos)
    1103           0 :                     buffer = source.sourceUri_.substr(pos + sep.size());
    1104             : 
    1105           0 :                 Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(buffer);
    1106           0 :             }
    1107             :         }
    1108             :     }
    1109           5 : }
    1110             : 
    1111             : ParticipantSet
    1112         420 : Conference::getParticipantList() const
    1113             : {
    1114         420 :     std::lock_guard lk(participantsMtx_);
    1115         840 :     return participants_;
    1116         420 : }
    1117             : 
    1118             : bool
    1119           2 : Conference::toggleRecording()
    1120             : {
    1121           2 :     bool newState = not isRecording();
    1122           2 :     if (newState)
    1123           1 :         initRecorder(recorder_);
    1124           1 :     else if (recorder_)
    1125           1 :         deinitRecorder(recorder_);
    1126             : 
    1127             :     // Notify each participant
    1128           6 :     foreachCall([&](auto call) { call->updateRecState(newState); });
    1129             : 
    1130           2 :     auto res = Recordable::toggleRecording();
    1131           2 :     updateRecording();
    1132           2 :     return res;
    1133             : }
    1134             : 
    1135             : std::string
    1136         169 : Conference::getAccountId() const
    1137             : {
    1138         169 :     if (auto account = getAccount())
    1139         169 :         return account->getAccountID();
    1140           0 :     return {};
    1141             : }
    1142             : 
    1143             : void
    1144           0 : Conference::switchInput(const std::string& input)
    1145             : {
    1146             : #ifdef ENABLE_VIDEO
    1147           0 :     JAMI_DEBUG("[Conf:{:s}] Setting video input to {:s}", id_, input);
    1148           0 :     std::vector<MediaAttribute> newSources;
    1149           0 :     auto firstVideo = true;
    1150             :     // Rewrite hostSources (remove all except one video input)
    1151             :     // This method is replaced by requestMediaChange
    1152           0 :     for (auto& source : hostSources_) {
    1153           0 :         if (source.type_ == MediaType::MEDIA_VIDEO) {
    1154           0 :             if (firstVideo) {
    1155           0 :                 firstVideo = false;
    1156           0 :                 source.sourceUri_ = input;
    1157           0 :                 newSources.emplace_back(source);
    1158             :             }
    1159             :         } else {
    1160           0 :             newSources.emplace_back(source);
    1161             :         }
    1162             :     }
    1163             : 
    1164             :     // Done if the video is disabled
    1165           0 :     if (not isVideoEnabled())
    1166           0 :         return;
    1167             : 
    1168           0 :     if (auto mixer = videoMixer_) {
    1169           0 :         mixer->switchInputs({input});
    1170             : #ifdef ENABLE_PLUGIN
    1171             :         // Preview
    1172           0 :         if (auto videoPreview = mixer->getVideoLocal()) {
    1173           0 :             auto previewSubject = std::make_shared<MediaStreamSubject>(pluginVideoMap_);
    1174             :             StreamData previewStreamData {getConfId(),
    1175             :                                           false,
    1176           0 :                                           StreamType::video,
    1177             :                                           getConfId(),
    1178           0 :                                           getAccountId()};
    1179           0 :             createConfAVStream(previewStreamData, *videoPreview, previewSubject, true);
    1180           0 :         }
    1181             : #endif
    1182           0 :     }
    1183             : #endif
    1184           0 : }
    1185             : 
    1186             : bool
    1187          87 : Conference::isVideoEnabled() const
    1188             : {
    1189          87 :     if (auto shared = account_.lock())
    1190          87 :         return shared->isVideoEnabled();
    1191           0 :     return false;
    1192             : }
    1193             : 
    1194             : #ifdef ENABLE_VIDEO
    1195             : std::shared_ptr<video::VideoMixer>
    1196         120 : Conference::getVideoMixer()
    1197             : {
    1198         120 :     return videoMixer_;
    1199             : }
    1200             : 
    1201             : std::string
    1202           0 : Conference::getVideoInput() const
    1203             : {
    1204           0 :     for (const auto& source : hostSources_) {
    1205           0 :         if (source.type_ == MediaType::MEDIA_VIDEO)
    1206           0 :             return source.sourceUri_;
    1207             :     }
    1208           0 :     return {};
    1209             : }
    1210             : #endif
    1211             : 
    1212             : void
    1213           1 : Conference::initRecorder(std::shared_ptr<MediaRecorder>& rec)
    1214             : {
    1215             : #ifdef ENABLE_VIDEO
    1216             :     // Video
    1217           1 :     if (videoMixer_) {
    1218           1 :         if (auto ob = rec->addStream(videoMixer_->getStream("v:mixer"))) {
    1219           1 :             videoMixer_->attach(ob);
    1220             :         }
    1221             :     }
    1222             : #endif
    1223             : 
    1224             :     // Audio
    1225             :     // Create ghost participant for ringbufferpool
    1226           1 :     auto& rbPool = Manager::instance().getRingBufferPool();
    1227           1 :     ghostRingBuffer_ = rbPool.createRingBuffer(getConfId());
    1228             : 
    1229             :     // Bind it to ringbufferpool in order to get the all mixed frames
    1230           1 :     bindParticipant(getConfId());
    1231             : 
    1232             :     // Add stream to recorder
    1233           1 :     audioMixer_ = jami::getAudioInput(getConfId());
    1234           1 :     if (auto ob = rec->addStream(audioMixer_->getInfo("a:mixer"))) {
    1235           1 :         audioMixer_->attach(ob);
    1236             :     }
    1237           1 : }
    1238             : 
    1239             : void
    1240           1 : Conference::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
    1241             : {
    1242             : #ifdef ENABLE_VIDEO
    1243             :     // Video
    1244           1 :     if (videoMixer_) {
    1245           1 :         if (auto ob = rec->getStream("v:mixer")) {
    1246           1 :             videoMixer_->detach(ob);
    1247             :         }
    1248             :     }
    1249             : #endif
    1250             : 
    1251             :     // Audio
    1252           1 :     if (auto ob = rec->getStream("a:mixer"))
    1253           1 :         audioMixer_->detach(ob);
    1254           1 :     audioMixer_.reset();
    1255           1 :     Manager::instance().getRingBufferPool().unBindAll(getConfId());
    1256           1 :     ghostRingBuffer_.reset();
    1257           1 : }
    1258             : 
    1259             : void
    1260           6 : Conference::onConfOrder(const std::string& callId, const std::string& confOrder)
    1261             : {
    1262             :     // Check if the peer is a master
    1263           6 :     if (auto call = getCall(callId)) {
    1264           6 :         const auto& peerId = getRemoteId(call);
    1265           6 :         std::string err;
    1266           6 :         Json::Value root;
    1267           6 :         Json::CharReaderBuilder rbuilder;
    1268           6 :         auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
    1269           6 :         if (!reader->parse(confOrder.c_str(), confOrder.c_str() + confOrder.size(), &root, &err)) {
    1270           0 :             JAMI_WARN("Couldn't parse conference order from %s", peerId.c_str());
    1271           0 :             return;
    1272             :         }
    1273             : 
    1274           6 :         parser_.initData(std::move(root), peerId);
    1275           6 :         parser_.parse();
    1276          36 :     }
    1277             : }
    1278             : 
    1279             : std::shared_ptr<Call>
    1280         893 : Conference::getCall(const std::string& callId)
    1281             : {
    1282         893 :     return Manager::instance().callFactory.getCall(callId);
    1283             : }
    1284             : 
    1285             : bool
    1286         149 : Conference::isModerator(std::string_view uri) const
    1287             : {
    1288         149 :     return moderators_.find(uri) != moderators_.end() or isHost(uri);
    1289             : }
    1290             : 
    1291             : bool
    1292         175 : Conference::isHandRaised(std::string_view deviceId) const
    1293             : {
    1294         175 :     return isHostDevice(deviceId) ? handsRaised_.find("host"sv) != handsRaised_.end()
    1295         175 :                                   : handsRaised_.find(deviceId) != handsRaised_.end();
    1296             : }
    1297             : 
    1298             : void
    1299           7 : Conference::setHandRaised(const std::string& deviceId, const bool& state)
    1300             : {
    1301           7 :     if (isHostDevice(deviceId)) {
    1302           0 :         auto isPeerRequiringAttention = isHandRaised("host"sv);
    1303           0 :         if (state and not isPeerRequiringAttention) {
    1304           0 :             JAMI_DBG("Raise host hand");
    1305           0 :             handsRaised_.emplace("host"sv);
    1306           0 :             updateHandsRaised();
    1307           0 :         } else if (not state and isPeerRequiringAttention) {
    1308           0 :             JAMI_DBG("Lower host hand");
    1309           0 :             handsRaised_.erase("host");
    1310           0 :             updateHandsRaised();
    1311             :         }
    1312             :     } else {
    1313          12 :         for (const auto& p : getParticipantList()) {
    1314          24 :             if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(p))) {
    1315          12 :                 auto isPeerRequiringAttention = isHandRaised(deviceId);
    1316          12 :                 std::string callDeviceId;
    1317          12 :                 if (auto* transport = call->getTransport())
    1318          12 :                     callDeviceId = transport->deviceId();
    1319          12 :                 if (deviceId == callDeviceId) {
    1320           7 :                     if (state and not isPeerRequiringAttention) {
    1321          12 :                         JAMI_DEBUG("Raise {:s} hand", deviceId);
    1322           4 :                         handsRaised_.emplace(deviceId);
    1323           4 :                         updateHandsRaised();
    1324           7 :                     } else if (not state and isPeerRequiringAttention) {
    1325           9 :                         JAMI_DEBUG("Remove {:s} raised hand", deviceId);
    1326           3 :                         handsRaised_.erase(deviceId);
    1327           3 :                         updateHandsRaised();
    1328             :                     }
    1329           7 :                     return;
    1330             :                 }
    1331          24 :             }
    1332           7 :         }
    1333           0 :         JAMI_WARN("Fail to raise %s hand (participant not found)", deviceId.c_str());
    1334             :     }
    1335             : }
    1336             : 
    1337             : bool
    1338         137 : Conference::isVoiceActive(std::string_view streamId) const
    1339             : {
    1340         137 :     return streamsVoiceActive.find(streamId) != streamsVoiceActive.end();
    1341             : }
    1342             : 
    1343             : void
    1344           0 : Conference::setVoiceActivity(const std::string& streamId, const bool& newState)
    1345             : {
    1346             :     // verify that streamID exists in our confInfo
    1347           0 :     bool exists = false;
    1348           0 :     for (auto& participant : confInfo_) {
    1349           0 :         if (participant.sinkId == streamId) {
    1350           0 :             exists = true;
    1351           0 :             break;
    1352             :         }
    1353             :     }
    1354             : 
    1355           0 :     if (!exists) {
    1356           0 :         JAMI_ERR("participant not found with streamId: %s", streamId.c_str());
    1357           0 :         return;
    1358             :     }
    1359             : 
    1360           0 :     auto previousState = isVoiceActive(streamId);
    1361             : 
    1362           0 :     if (previousState == newState) {
    1363             :         // no change, do not send out updates
    1364           0 :         return;
    1365             :     }
    1366             : 
    1367           0 :     if (newState and not previousState) {
    1368             :         // voice going from inactive to active
    1369           0 :         streamsVoiceActive.emplace(streamId);
    1370           0 :         updateVoiceActivity();
    1371           0 :         return;
    1372             :     }
    1373             : 
    1374           0 :     if (not newState and previousState) {
    1375             :         // voice going from active to inactive
    1376           0 :         streamsVoiceActive.erase(streamId);
    1377           0 :         updateVoiceActivity();
    1378           0 :         return;
    1379             :     }
    1380             : }
    1381             : 
    1382             : void
    1383           1 : Conference::setModerator(const std::string& participant_id, const bool& state)
    1384             : {
    1385           2 :     for (const auto& p : getParticipantList()) {
    1386           2 :         if (auto call = getCall(p)) {
    1387           2 :             auto isPeerModerator = isModerator(participant_id);
    1388           2 :             if (participant_id == getRemoteId(call)) {
    1389           1 :                 if (state and not isPeerModerator) {
    1390           0 :                     JAMI_DEBUG("Add {:s} as moderator", participant_id);
    1391           0 :                     moderators_.emplace(participant_id);
    1392           0 :                     updateModerators();
    1393           1 :                 } else if (not state and isPeerModerator) {
    1394           3 :                     JAMI_DEBUG("Remove {:s} as moderator", participant_id);
    1395           1 :                     moderators_.erase(participant_id);
    1396           1 :                     updateModerators();
    1397             :                 }
    1398           1 :                 return;
    1399             :             }
    1400           2 :         }
    1401           1 :     }
    1402           0 :     JAMI_WARN("Fail to set %s as moderator (participant not found)", participant_id.c_str());
    1403             : }
    1404             : 
    1405             : void
    1406           1 : Conference::updateModerators()
    1407             : {
    1408           1 :     std::lock_guard lk(confInfoMutex_);
    1409           5 :     for (auto& info : confInfo_) {
    1410           4 :         info.isModerator = isModerator(string_remove_suffix(info.uri, '@'));
    1411             :     }
    1412           1 :     sendConferenceInfos();
    1413           1 : }
    1414             : 
    1415             : void
    1416           7 : Conference::updateHandsRaised()
    1417             : {
    1418           7 :     std::lock_guard lk(confInfoMutex_);
    1419          33 :     for (auto& info : confInfo_)
    1420          26 :         info.handRaised = isHandRaised(info.device);
    1421           7 :     sendConferenceInfos();
    1422           7 : }
    1423             : 
    1424             : void
    1425           0 : Conference::updateVoiceActivity()
    1426             : {
    1427           0 :     std::lock_guard lk(confInfoMutex_);
    1428             : 
    1429             :     // streamId is actually sinkId
    1430           0 :     for (ParticipantInfo& participantInfo : confInfo_) {
    1431             :         bool newActivity;
    1432             : 
    1433           0 :         if (auto call = getCallWith(std::string(string_remove_suffix(participantInfo.uri, '@')),
    1434           0 :                                     participantInfo.device)) {
    1435             :             // if this participant is in a direct call with us
    1436             :             // grab voice activity info directly from the call
    1437           0 :             newActivity = call->hasPeerVoice();
    1438             :         } else {
    1439             :             // check for it
    1440           0 :             newActivity = isVoiceActive(participantInfo.sinkId);
    1441           0 :         }
    1442             : 
    1443           0 :         if (participantInfo.voiceActivity != newActivity) {
    1444           0 :             participantInfo.voiceActivity = newActivity;
    1445             :         }
    1446             :     }
    1447           0 :     sendConferenceInfos(); // also emits signal to client
    1448           0 : }
    1449             : 
    1450             : void
    1451         127 : Conference::foreachCall(const std::function<void(const std::shared_ptr<Call>& call)>& cb)
    1452             : {
    1453         321 :     for (const auto& p : getParticipantList())
    1454         194 :         if (auto call = getCall(p))
    1455         321 :             cb(call);
    1456         127 : }
    1457             : 
    1458             : bool
    1459         260 : Conference::isMuted(std::string_view callId) const
    1460             : {
    1461         260 :     return participantsMuted_.find(callId) != participantsMuted_.end();
    1462             : }
    1463             : 
    1464             : void
    1465           3 : Conference::muteStream(const std::string& accountUri,
    1466             :                        const std::string& deviceId,
    1467             :                        const std::string&,
    1468             :                        const bool& state)
    1469             : {
    1470           6 :     if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account_.lock())) {
    1471           3 :         if (accountUri == acc->getUsername() && deviceId == acc->currentDeviceId()) {
    1472           0 :             muteHost(state);
    1473           3 :         } else if (auto call = getCallWith(accountUri, deviceId)) {
    1474           3 :             muteCall(call->getCallId(), state);
    1475             :         } else {
    1476           0 :             JAMI_WARN("No call with %s - %s", accountUri.c_str(), deviceId.c_str());
    1477           3 :         }
    1478           3 :     }
    1479           3 : }
    1480             : 
    1481             : void
    1482           0 : Conference::muteHost(bool state)
    1483             : {
    1484           0 :     auto isHostMuted = isMuted("host"sv);
    1485           0 :     if (state and not isHostMuted) {
    1486           0 :         participantsMuted_.emplace("host"sv);
    1487           0 :         if (not isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
    1488           0 :             JAMI_DBG("Mute host");
    1489           0 :             unbindHost();
    1490             :         }
    1491           0 :     } else if (not state and isHostMuted) {
    1492           0 :         participantsMuted_.erase("host");
    1493           0 :         if (not isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
    1494           0 :             JAMI_DBG("Unmute host");
    1495           0 :             bindHost();
    1496             :         }
    1497             :     }
    1498           0 :     updateMuted();
    1499           0 : }
    1500             : 
    1501             : void
    1502           3 : Conference::muteCall(const std::string& callId, bool state)
    1503             : {
    1504           3 :     auto isPartMuted = isMuted(callId);
    1505           3 :     if (state and not isPartMuted) {
    1506           6 :         JAMI_DEBUG("Mute participant {:s}", callId);
    1507           2 :         participantsMuted_.emplace(callId);
    1508           2 :         unbindParticipant(callId);
    1509           2 :         updateMuted();
    1510           3 :     } else if (not state and isPartMuted) {
    1511           3 :         JAMI_DEBUG("Unmute participant {:s}", callId);
    1512           1 :         participantsMuted_.erase(callId);
    1513           1 :         bindParticipant(callId);
    1514           1 :         updateMuted();
    1515             :     }
    1516           3 : }
    1517             : 
    1518             : void
    1519           0 : Conference::muteParticipant(const std::string& participant_id, const bool& state)
    1520             : {
    1521             :     // Prioritize remote mute, otherwise the mute info is lost during
    1522             :     // the conference merge (we don't send back info to remoteHost,
    1523             :     // cf. getConfInfoHostUri method)
    1524             : 
    1525             :     // Transfert remote participant mute
    1526           0 :     auto remoteHost = findHostforRemoteParticipant(participant_id);
    1527           0 :     if (not remoteHost.empty()) {
    1528           0 :         if (auto call = getCallFromPeerID(string_remove_suffix(remoteHost, '@'))) {
    1529           0 :             auto w = call->getAccount();
    1530           0 :             auto account = w.lock();
    1531           0 :             if (!account)
    1532           0 :                 return;
    1533           0 :             Json::Value root;
    1534           0 :             root["muteParticipant"] = participant_id;
    1535           0 :             root["muteState"] = state ? TRUE_STR : FALSE_STR;
    1536           0 :             call->sendConfOrder(root);
    1537           0 :             return;
    1538           0 :         }
    1539             :     }
    1540             : 
    1541             :     // NOTE: For now we have no way to mute only one stream
    1542           0 :     if (isHost(participant_id))
    1543           0 :         muteHost(state);
    1544           0 :     else if (auto call = getCallFromPeerID(participant_id))
    1545           0 :         muteCall(call->getCallId(), state);
    1546             : }
    1547             : 
    1548             : void
    1549           8 : Conference::updateRecording()
    1550             : {
    1551           8 :     std::lock_guard lk(confInfoMutex_);
    1552          30 :     for (auto& info : confInfo_) {
    1553          22 :         if (info.uri.empty()) {
    1554           8 :             info.recording = isRecording();
    1555          42 :         } else if (auto call = getCallWith(std::string(string_remove_suffix(info.uri, '@')),
    1556          42 :                                            info.device)) {
    1557          14 :             info.recording = call->isPeerRecording();
    1558          14 :         }
    1559             :     }
    1560           8 :     sendConferenceInfos();
    1561           8 : }
    1562             : 
    1563             : void
    1564           4 : Conference::updateMuted()
    1565             : {
    1566           4 :     std::lock_guard lk(confInfoMutex_);
    1567          15 :     for (auto& info : confInfo_) {
    1568          11 :         if (info.uri.empty()) {
    1569           4 :             info.audioModeratorMuted = isMuted("host"sv);
    1570           4 :             info.audioLocalMuted = isMediaSourceMuted(MediaType::MEDIA_AUDIO);
    1571          21 :         } else if (auto call = getCallWith(std::string(string_remove_suffix(info.uri, '@')),
    1572          21 :                                            info.device)) {
    1573           7 :             info.audioModeratorMuted = isMuted(call->getCallId());
    1574           7 :             info.audioLocalMuted = call->isPeerMuted();
    1575           7 :         }
    1576             :     }
    1577           4 :     sendConferenceInfos();
    1578           4 : }
    1579             : 
    1580             : ConfInfo
    1581         273 : Conference::getConfInfoHostUri(std::string_view localHostURI, std::string_view destURI)
    1582             : {
    1583         273 :     ConfInfo newInfo = confInfo_;
    1584             : 
    1585        1097 :     for (auto it = newInfo.begin(); it != newInfo.end();) {
    1586         824 :         bool isRemoteHost = remoteHosts_.find(it->uri) != remoteHosts_.end();
    1587         824 :         if (it->uri.empty() and not destURI.empty()) {
    1588             :             // fill the empty uri with the local host URI, let void for local client
    1589         188 :             it->uri = localHostURI;
    1590             :         }
    1591         824 :         if (isRemoteHost) {
    1592             :             // Don't send back the ParticipantInfo for remote Host
    1593             :             // For other than remote Host, the new info is in remoteHosts_
    1594           0 :             it = newInfo.erase(it);
    1595             :         } else {
    1596         824 :             ++it;
    1597             :         }
    1598             :     }
    1599             :     // Add remote Host info
    1600         273 :     for (const auto& [hostUri, confInfo] : remoteHosts_) {
    1601             :         // Add remote info for remote host destination
    1602             :         // Example: ConfA, ConfB & ConfC
    1603             :         // ConfA send ConfA and ConfB for ConfC
    1604             :         // ConfA send ConfA and ConfC for ConfB
    1605             :         // ...
    1606           0 :         if (destURI != hostUri)
    1607           0 :             newInfo.insert(newInfo.end(), confInfo.begin(), confInfo.end());
    1608             :     }
    1609         273 :     return newInfo;
    1610           0 : }
    1611             : 
    1612             : bool
    1613          15 : Conference::isHost(std::string_view uri) const
    1614             : {
    1615          15 :     if (uri.empty())
    1616          13 :         return true;
    1617             : 
    1618             :     // Check if the URI is a local URI (AccountID) for at least one of the subcall
    1619             :     // (a local URI can be in the call with another device)
    1620           8 :     for (const auto& p : getParticipantList()) {
    1621           6 :         if (auto call = getCall(p)) {
    1622          12 :             if (auto account = call->getAccount().lock()) {
    1623           6 :                 if (account->getUsername() == uri)
    1624           0 :                     return true;
    1625           6 :             }
    1626           6 :         }
    1627           2 :     }
    1628           2 :     return false;
    1629             : }
    1630             : 
    1631             : bool
    1632         182 : Conference::isHostDevice(std::string_view deviceId) const
    1633             : {
    1634         364 :     if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account_.lock()))
    1635         182 :         return deviceId == acc->currentDeviceId();
    1636           0 :     return false;
    1637             : }
    1638             : 
    1639             : void
    1640          67 : Conference::updateConferenceInfo(ConfInfo confInfo)
    1641             : {
    1642          67 :     std::lock_guard lk(confInfoMutex_);
    1643          67 :     confInfo_ = std::move(confInfo);
    1644          67 :     sendConferenceInfos();
    1645          67 : }
    1646             : 
    1647             : void
    1648           1 : Conference::hangupParticipant(const std::string& accountUri, const std::string& deviceId)
    1649             : {
    1650           2 :     if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account_.lock())) {
    1651           1 :         if (deviceId.empty()) {
    1652             :             // If deviceId is empty, hangup all calls with device
    1653           0 :             while (auto call = getCallFromPeerID(accountUri)) {
    1654           0 :                 Manager::instance().hangupCall(acc->getAccountID(), call->getCallId());
    1655           0 :             }
    1656           1 :             return;
    1657             :         } else {
    1658           1 :             if (accountUri == acc->getUsername() && deviceId == acc->currentDeviceId()) {
    1659           0 :                 Manager::instance().detachLocalParticipant(shared_from_this());
    1660           0 :                 return;
    1661           1 :             } else if (auto call = getCallWith(accountUri, deviceId)) {
    1662           1 :                 Manager::instance().hangupCall(acc->getAccountID(), call->getCallId());
    1663           1 :                 return;
    1664           1 :             }
    1665             :         }
    1666             :         // Else, it may be a remote host
    1667           0 :         auto remoteHost = findHostforRemoteParticipant(accountUri, deviceId);
    1668           0 :         if (remoteHost.empty()) {
    1669           0 :             JAMI_WARN("Can't hangup %s, peer not found", accountUri.c_str());
    1670           0 :             return;
    1671             :         }
    1672           0 :         if (auto call = getCallFromPeerID(string_remove_suffix(remoteHost, '@'))) {
    1673             :             // Forward to the remote host.
    1674           0 :             libjami::hangupParticipant(acc->getAccountID(), call->getCallId(), accountUri, deviceId);
    1675           0 :         }
    1676           1 :     }
    1677             : }
    1678             : 
    1679             : void
    1680           1 : Conference::muteLocalHost(bool is_muted, const std::string& mediaType)
    1681             : {
    1682           1 :     if (mediaType.compare(libjami::Media::Details::MEDIA_TYPE_AUDIO) == 0) {
    1683           1 :         if (is_muted == isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
    1684           0 :             JAMI_DEBUG("Local audio source already in [{:s}] state",
    1685             :                        is_muted ? "muted" : "un-muted");
    1686           0 :             return;
    1687             :         }
    1688             : 
    1689           1 :         auto isHostMuted = isMuted("host"sv);
    1690           1 :         if (is_muted and not isMediaSourceMuted(MediaType::MEDIA_AUDIO) and not isHostMuted) {
    1691           1 :             JAMI_DBG("Muting local audio source");
    1692           1 :             unbindHost();
    1693           0 :         } else if (not is_muted and isMediaSourceMuted(MediaType::MEDIA_AUDIO) and not isHostMuted) {
    1694           0 :             JAMI_DBG("Un-muting local audio source");
    1695           0 :             bindHost();
    1696             :         }
    1697           1 :         setLocalHostMuteState(MediaType::MEDIA_AUDIO, is_muted);
    1698           1 :         updateMuted();
    1699           1 :         emitSignal<libjami::CallSignal::AudioMuted>(id_, is_muted);
    1700           1 :         return;
    1701           0 :     } else if (mediaType.compare(libjami::Media::Details::MEDIA_TYPE_VIDEO) == 0) {
    1702             : #ifdef ENABLE_VIDEO
    1703           0 :         if (not isVideoEnabled()) {
    1704           0 :             JAMI_ERR("Cant't mute, the video is disabled!");
    1705           0 :             return;
    1706             :         }
    1707             : 
    1708           0 :         if (is_muted == isMediaSourceMuted(MediaType::MEDIA_VIDEO)) {
    1709           0 :             JAMI_DEBUG("Local video source already in [{:s}] state",
    1710             :                        is_muted ? "muted" : "un-muted");
    1711           0 :             return;
    1712             :         }
    1713           0 :         setLocalHostMuteState(MediaType::MEDIA_VIDEO, is_muted);
    1714           0 :         if (is_muted) {
    1715           0 :             if (auto mixer = videoMixer_) {
    1716           0 :                 JAMI_DBG("Muting local video sources");
    1717           0 :                 mixer->stopInputs();
    1718           0 :             }
    1719             :         } else {
    1720           0 :             if (auto mixer = videoMixer_) {
    1721           0 :                 JAMI_DBG("Un-muting local video sources");
    1722           0 :                 std::vector<std::string> videoInputs;
    1723           0 :                 for (const auto& source : hostSources_) {
    1724           0 :                     if (source.type_ == MediaType::MEDIA_VIDEO)
    1725           0 :                         videoInputs.emplace_back(source.sourceUri_);
    1726             :                 }
    1727           0 :                 mixer->switchInputs(videoInputs);
    1728           0 :             }
    1729             :         }
    1730           0 :         emitSignal<libjami::CallSignal::VideoMuted>(id_, is_muted);
    1731           0 :         return;
    1732             : #endif
    1733             :     }
    1734             : }
    1735             : 
    1736             : #ifdef ENABLE_VIDEO
    1737             : void
    1738           0 : Conference::resizeRemoteParticipants(ConfInfo& confInfo, std::string_view peerURI)
    1739             : {
    1740           0 :     int remoteFrameHeight = confInfo.h;
    1741           0 :     int remoteFrameWidth = confInfo.w;
    1742             : 
    1743           0 :     if (remoteFrameHeight == 0 or remoteFrameWidth == 0) {
    1744             :         // get the size of the remote frame from receiveThread
    1745             :         // if the one from confInfo is empty
    1746           0 :         if (auto call = std::dynamic_pointer_cast<SIPCall>(
    1747           0 :                 getCallFromPeerID(string_remove_suffix(peerURI, '@')))) {
    1748           0 :             for (auto const& videoRtp : call->getRtpSessionList(MediaType::MEDIA_VIDEO)) {
    1749           0 :                 auto recv = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)
    1750           0 :                                 ->getVideoReceive();
    1751           0 :                 remoteFrameHeight = recv->getHeight();
    1752           0 :                 remoteFrameWidth = recv->getWidth();
    1753             :                 // NOTE: this may be not the behavior we want, but this is only called
    1754             :                 // when we receive conferences informations from a call, so the peer is
    1755             :                 // mixing the video and send only one stream, so we can break here
    1756           0 :                 break;
    1757           0 :             }
    1758           0 :         }
    1759             :     }
    1760             : 
    1761           0 :     if (remoteFrameHeight == 0 or remoteFrameWidth == 0) {
    1762           0 :         JAMI_WARN("Remote frame size not found.");
    1763           0 :         return;
    1764             :     }
    1765             : 
    1766             :     // get the size of the local frame
    1767           0 :     ParticipantInfo localCell;
    1768           0 :     for (const auto& p : confInfo_) {
    1769           0 :         if (p.uri == peerURI) {
    1770           0 :             localCell = p;
    1771           0 :             break;
    1772             :         }
    1773             :     }
    1774             : 
    1775           0 :     const float zoomX = (float) remoteFrameWidth / localCell.w;
    1776           0 :     const float zoomY = (float) remoteFrameHeight / localCell.h;
    1777             :     // Do the resize for each remote participant
    1778           0 :     for (auto& remoteCell : confInfo) {
    1779           0 :         remoteCell.x = remoteCell.x / zoomX + localCell.x;
    1780           0 :         remoteCell.y = remoteCell.y / zoomY + localCell.y;
    1781           0 :         remoteCell.w = remoteCell.w / zoomX;
    1782           0 :         remoteCell.h = remoteCell.h / zoomY;
    1783             :     }
    1784           0 : }
    1785             : #endif
    1786             : 
    1787             : void
    1788           0 : Conference::mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI)
    1789             : {
    1790           0 :     if (newInfo.empty()) {
    1791           0 :         JAMI_DBG("confInfo empty, remove remoteHost");
    1792           0 :         std::lock_guard lk(confInfoMutex_);
    1793           0 :         remoteHosts_.erase(peerURI);
    1794           0 :         sendConferenceInfos();
    1795           0 :         return;
    1796           0 :     }
    1797             : 
    1798             : #ifdef ENABLE_VIDEO
    1799           0 :     resizeRemoteParticipants(newInfo, peerURI);
    1800             : #endif
    1801             : 
    1802           0 :     bool updateNeeded = false;
    1803           0 :     auto it = remoteHosts_.find(peerURI);
    1804           0 :     if (it != remoteHosts_.end()) {
    1805             :         // Compare confInfo before update
    1806           0 :         if (it->second != newInfo) {
    1807           0 :             it->second = newInfo;
    1808           0 :             updateNeeded = true;
    1809             :         } else
    1810           0 :             JAMI_WARN("No change in confInfo, don't update");
    1811             :     } else {
    1812           0 :         remoteHosts_.emplace(peerURI, newInfo);
    1813           0 :         updateNeeded = true;
    1814             :     }
    1815             :     // Send confInfo only if needed to avoid loops
    1816             : #ifdef ENABLE_VIDEO
    1817           0 :     if (updateNeeded and videoMixer_) {
    1818             :         // Trigger the layout update in the mixer because the frame resolution may
    1819             :         // change from participant to conference and cause a mismatch between
    1820             :         // confInfo layout and rendering layout.
    1821           0 :         videoMixer_->updateLayout();
    1822             :     }
    1823             : #endif
    1824             : }
    1825             : 
    1826             : std::string_view
    1827           0 : Conference::findHostforRemoteParticipant(std::string_view uri, std::string_view deviceId)
    1828             : {
    1829           0 :     for (const auto& host : remoteHosts_) {
    1830           0 :         for (const auto& p : host.second) {
    1831           0 :             if (uri == string_remove_suffix(p.uri, '@') && (deviceId == "" || deviceId == p.device))
    1832           0 :                 return host.first;
    1833             :         }
    1834             :     }
    1835           0 :     return "";
    1836             : }
    1837             : 
    1838             : std::shared_ptr<Call>
    1839           0 : Conference::getCallFromPeerID(std::string_view peerID)
    1840             : {
    1841           0 :     for (const auto& p : getParticipantList()) {
    1842           0 :         auto call = getCall(p);
    1843           0 :         if (call && getRemoteId(call) == peerID) {
    1844           0 :             return call;
    1845             :         }
    1846           0 :     }
    1847           0 :     return nullptr;
    1848             : }
    1849             : 
    1850             : std::shared_ptr<Call>
    1851          25 : Conference::getCallWith(const std::string& accountUri, const std::string& deviceId)
    1852             : {
    1853          39 :     for (const auto& p : getParticipantList()) {
    1854          78 :         if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(p))) {
    1855          39 :             auto* transport = call->getTransport();
    1856          64 :             if (accountUri == string_remove_suffix(call->getPeerNumber(), '@') && transport
    1857          64 :                 && deviceId == transport->deviceId()) {
    1858          25 :                 return call;
    1859             :             }
    1860          39 :         }
    1861          25 :     }
    1862           0 :     return {};
    1863             : }
    1864             : 
    1865             : std::string
    1866         164 : Conference::getRemoteId(const std::shared_ptr<jami::Call>& call) const
    1867             : {
    1868         164 :     if (auto* transport = std::dynamic_pointer_cast<SIPCall>(call)->getTransport())
    1869         142 :         if (auto cert = transport->getTlsInfos().peerCert)
    1870         140 :             if (cert->issuer)
    1871         142 :                 return cert->issuer->getId().toString();
    1872          24 :     return {};
    1873             : }
    1874             : 
    1875             : void
    1876           1 : Conference::stopRecording()
    1877             : {
    1878           1 :     Recordable::stopRecording();
    1879           1 :     updateRecording();
    1880           1 : }
    1881             : 
    1882             : bool
    1883           1 : Conference::startRecording(const std::string& path)
    1884             : {
    1885           1 :     auto res = Recordable::startRecording(path);
    1886           1 :     updateRecording();
    1887           1 :     return res;
    1888             : }
    1889             : 
    1890             : } // namespace jami

Generated by: LCOV version 1.14