LCOV - code coverage report
Current view: top level - foo/src - conference.h (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 72 96 75.0 %
Date: 2026-01-22 10:39:23 Functions: 13 17 76.5 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : #pragma once
      18             : 
      19             : #ifdef HAVE_CONFIG_H
      20             : #include "config.h"
      21             : #endif
      22             : 
      23             : #include <chrono>
      24             : #include <set>
      25             : #include <string>
      26             : #include <memory>
      27             : #include <vector>
      28             : #include <string_view>
      29             : #include <map>
      30             : #include <functional>
      31             : 
      32             : #include "conference_protocol.h"
      33             : #include "media/audio/audio_input.h"
      34             : #include "media/media_attribute.h"
      35             : #include "media/recordable.h"
      36             : 
      37             : #ifdef ENABLE_PLUGIN
      38             : #include "plugin/streamdata.h"
      39             : #endif
      40             : 
      41             : #ifdef ENABLE_VIDEO
      42             : #include "media/video/sinkclient.h"
      43             : #endif
      44             : 
      45             : #include <json/json.h>
      46             : 
      47             : namespace jami {
      48             : 
      49             : class Call;
      50             : class Account;
      51             : class JamiAccount;
      52             : 
      53             : #ifdef ENABLE_VIDEO
      54             : namespace video {
      55             : class VideoMixer;
      56             : struct SourceInfo;
      57             : } // namespace video
      58             : #endif
      59             : 
      60             : // info for a stream
      61             : struct ParticipantInfo
      62             : {
      63             :     std::string uri;
      64             :     std::string device;
      65             :     std::string sinkId; // stream ID
      66             :     bool active {false};
      67             :     int x {0};
      68             :     int y {0};
      69             :     int w {0};
      70             :     int h {0};
      71             :     bool videoMuted {false};
      72             :     bool audioLocalMuted {false};
      73             :     bool audioModeratorMuted {false};
      74             :     bool isModerator {false};
      75             :     bool handRaised {false};
      76             :     bool voiceActivity {false};
      77             :     bool recording {false};
      78             : 
      79         616 :     void fromJson(const Json::Value& v)
      80             :     {
      81         616 :         uri = v["uri"].asString();
      82         616 :         device = v["device"].asString();
      83         616 :         sinkId = v["sinkId"].asString();
      84         616 :         active = v["active"].asBool();
      85         616 :         x = v["x"].asInt();
      86         616 :         y = v["y"].asInt();
      87         616 :         w = v["w"].asInt();
      88         616 :         h = v["h"].asInt();
      89         616 :         videoMuted = v["videoMuted"].asBool();
      90         616 :         audioLocalMuted = v["audioLocalMuted"].asBool();
      91         616 :         audioModeratorMuted = v["audioModeratorMuted"].asBool();
      92         616 :         isModerator = v["isModerator"].asBool();
      93         616 :         handRaised = v["handRaised"].asBool();
      94         616 :         voiceActivity = v["voiceActivity"].asBool();
      95         616 :         recording = v["recording"].asBool();
      96         616 :     }
      97             : 
      98         643 :     Json::Value toJson() const
      99             :     {
     100         643 :         Json::Value val;
     101         643 :         val["uri"] = uri;
     102         644 :         val["device"] = device;
     103         645 :         val["sinkId"] = sinkId;
     104         645 :         val["active"] = active;
     105         645 :         val["x"] = x;
     106         643 :         val["y"] = y;
     107         641 :         val["w"] = w;
     108         646 :         val["h"] = h;
     109         646 :         val["videoMuted"] = videoMuted;
     110         645 :         val["audioLocalMuted"] = audioLocalMuted;
     111         646 :         val["audioModeratorMuted"] = audioModeratorMuted;
     112         646 :         val["isModerator"] = isModerator;
     113         646 :         val["handRaised"] = handRaised;
     114         643 :         val["voiceActivity"] = voiceActivity;
     115         645 :         val["recording"] = recording;
     116         646 :         return val;
     117           0 :     }
     118             : 
     119        1061 :     std::map<std::string, std::string> toMap() const
     120             :     {
     121        1061 :         return {{"uri", uri},
     122        1061 :                 {"device", device},
     123        1061 :                 {"sinkId", sinkId},
     124        1060 :                 {"active", active ? "true" : "false"},
     125        2121 :                 {"x", std::to_string(x)},
     126        2122 :                 {"y", std::to_string(y)},
     127        2122 :                 {"w", std::to_string(w)},
     128        2122 :                 {"h", std::to_string(h)},
     129        1061 :                 {"videoMuted", videoMuted ? "true" : "false"},
     130        1061 :                 {"audioLocalMuted", audioLocalMuted ? "true" : "false"},
     131        1061 :                 {"audioModeratorMuted", audioModeratorMuted ? "true" : "false"},
     132        1060 :                 {"isModerator", isModerator ? "true" : "false"},
     133        1061 :                 {"handRaised", handRaised ? "true" : "false"},
     134        1061 :                 {"voiceActivity", voiceActivity ? "true" : "false"},
     135       29706 :                 {"recording", recording ? "true" : "false"}};
     136             :     }
     137             : 
     138           0 :     friend bool operator==(const ParticipantInfo& p1, const ParticipantInfo& p2)
     139             :     {
     140           0 :         return p1.uri == p2.uri and p1.device == p2.device and p1.sinkId == p2.sinkId and p1.active == p2.active
     141           0 :                and p1.x == p2.x and p1.y == p2.y and p1.w == p2.w and p1.h == p2.h and p1.videoMuted == p2.videoMuted
     142           0 :                and p1.audioLocalMuted == p2.audioLocalMuted and p1.audioModeratorMuted == p2.audioModeratorMuted
     143           0 :                and p1.isModerator == p2.isModerator and p1.handRaised == p2.handRaised
     144           0 :                and p1.voiceActivity == p2.voiceActivity and p1.recording == p2.recording;
     145             :     }
     146             : 
     147             :     friend bool operator!=(const ParticipantInfo& p1, const ParticipantInfo& p2) { return !(p1 == p2); }
     148             : };
     149             : 
     150             : struct ConfInfo : public std::vector<ParticipantInfo>
     151             : {
     152             :     int h {0};
     153             :     int w {0};
     154             :     int v {1}; // Supported conference protocol version
     155             :     int layout {0};
     156             : 
     157           0 :     friend bool operator==(const ConfInfo& c1, const ConfInfo& c2)
     158             :     {
     159           0 :         if (c1.h != c2.h or c1.w != c2.w)
     160           0 :             return false;
     161           0 :         if (c1.size() != c2.size())
     162           0 :             return false;
     163             : 
     164           0 :         for (auto& p1 : c1) {
     165           0 :             auto it = std::find_if(c2.begin(), c2.end(), [&p1](const ParticipantInfo& p2) { return p1 == p2; });
     166           0 :             if (it != c2.end())
     167           0 :                 continue;
     168             :             else
     169           0 :                 return false;
     170             :         }
     171           0 :         return true;
     172             :     }
     173             : 
     174           0 :     friend bool operator!=(const ConfInfo& c1, const ConfInfo& c2) { return !(c1 == c2); }
     175             : 
     176             :     std::vector<std::map<std::string, std::string>> toVectorMapStringString() const;
     177             :     std::string toString() const;
     178             : };
     179             : 
     180             : using CallIdSet = std::set<std::string>;
     181             : using clock = std::chrono::steady_clock;
     182             : 
     183             : class Conference : public Recordable, public std::enable_shared_from_this<Conference>
     184             : {
     185             : public:
     186             :     enum class State { ACTIVE_ATTACHED, ACTIVE_DETACHED, HOLD };
     187             : 
     188             :     /**
     189             :      * Constructor for this class, increment static counter
     190             :      */
     191             :     explicit Conference(const std::shared_ptr<Account>&, const std::string& confId = "");
     192             : 
     193             :     /**
     194             :      * Destructor for this class, decrement static counter
     195             :      */
     196             :     ~Conference();
     197             : 
     198             :     /**
     199             :      * Return the conference id
     200             :      */
     201        1928 :     const std::string& getConfId() const { return id_; }
     202             : 
     203         408 :     std::shared_ptr<Account> getAccount() const { return account_.lock(); }
     204             : 
     205             :     std::string getAccountId() const;
     206             : 
     207             :     /**
     208             :      * Return the current conference state
     209             :      */
     210             :     State getState() const;
     211             : 
     212             :     /**
     213             :      * Set conference state
     214             :      */
     215             :     void setState(State state);
     216             : 
     217             :     /**
     218             :      * Set a callback that will be called when the conference will be destroyed
     219             :      */
     220          14 :     void onShutdown(std::function<void(int)> cb) { shutdownCb_ = std::move(cb); }
     221             : 
     222             :     /**
     223             :      * Return a string description of the conference state
     224             :      */
     225         288 :     static constexpr const char* getStateStr(State state)
     226             :     {
     227         288 :         switch (state) {
     228         174 :         case State::ACTIVE_ATTACHED:
     229         174 :             return "ACTIVE_ATTACHED";
     230         114 :         case State::ACTIVE_DETACHED:
     231         114 :             return "ACTIVE_DETACHED";
     232           0 :         case State::HOLD:
     233           0 :             return "HOLD";
     234           0 :         default:
     235           0 :             return "";
     236             :         }
     237             :     }
     238             : 
     239         204 :     const char* getStateStr() const { return getStateStr(confState_); }
     240             : 
     241             :     /**
     242             :      * Set the mute state of the local host
     243             :      */
     244             :     void setLocalHostMuteState(MediaType type, bool muted);
     245             : 
     246             :     /**
     247             :      * Get the mute state of the local host
     248             :      */
     249             :     bool isMediaSourceMuted(MediaType type) const;
     250             : 
     251             :     /**
     252             :      * Process a media change request.
     253             :      * Used to change the media attributes of the host.
     254             :      *
     255             :      * @param remoteMediaList new media list from the remote
     256             :      * @return true on success
     257             :      */
     258             :     bool requestMediaChange(const std::vector<libjami::MediaMap>& mediaList);
     259             : 
     260             :     /**
     261             :      * Process incoming media change request.
     262             :      *
     263             :      * @param callId the call ID
     264             :      * @param remoteMediaList new media list from the remote
     265             :      */
     266             :     void handleMediaChangeRequest(const std::shared_ptr<Call>& call,
     267             :                                   const std::vector<libjami::MediaMap>& remoteMediaList);
     268             : 
     269             :     /**
     270             :      * Add a new subcall to the conference
     271             :      */
     272             :     void addSubCall(const std::string& callId);
     273             : 
     274             :     /**
     275             :      * Remove a subcall from the conference
     276             :      */
     277             :     void removeSubCall(const std::string& callId);
     278             : 
     279             :     /**
     280             :      * Attach host
     281             :      */
     282             :     void attachHost(const std::vector<libjami::MediaMap>& mediaList);
     283             : 
     284             :     /**
     285             :      * Detach local audio/video from the conference
     286             :      */
     287             :     void detachHost();
     288             : 
     289             :     /**
     290             :      * Get the participant list for this conference
     291             :      */
     292             :     CallIdSet getSubCalls() const;
     293             : 
     294             :     /**
     295             :      * Start/stop recording toggle
     296             :      */
     297             :     bool toggleRecording() override;
     298             : 
     299             :     void switchInput(const std::string& input);
     300             :     void setActiveParticipant(const std::string& participant_id);
     301             :     void setActiveStream(const std::string& streamId, bool state);
     302             :     void setLayout(int layout);
     303             : 
     304             :     void onConfOrder(const std::string& callId, const std::string& order);
     305             : 
     306             :     bool isVideoEnabled() const;
     307             : 
     308             : #ifdef ENABLE_VIDEO
     309             :     void createSinks(const ConfInfo& infos);
     310             :     std::shared_ptr<video::VideoMixer> getVideoMixer();
     311             :     std::string getVideoInput() const;
     312             : #endif
     313             : 
     314          64 :     std::vector<std::map<std::string, std::string>> getConferenceInfos() const
     315             :     {
     316          64 :         std::lock_guard lk(confInfoMutex_);
     317         128 :         return confInfo_.toVectorMapStringString();
     318          64 :     }
     319             : 
     320             :     void updateConferenceInfo(ConfInfo confInfo);
     321             :     void setModerator(const std::string& uri, const bool& state);
     322             :     void hangupParticipant(const std::string& accountUri, const std::string& deviceId = "");
     323             :     void setHandRaised(const std::string& uri, const bool& state);
     324             :     void setVoiceActivity(const std::string& streamId, const bool& newState);
     325             : 
     326             :     void muteParticipant(const std::string& uri, const bool& state);
     327             :     void muteLocalHost(bool is_muted, const std::string& mediaType);
     328             :     bool isRemoteParticipant(const std::string& uri);
     329             :     void mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI);
     330             : 
     331             :     /**
     332             :      * The client shows one tile per stream (video/audio related to a media)
     333             :      * @note for now, in conferences we can only mute the audio of a call
     334             :      * @todo add a track (audio OR video) parameter to know what we want to mute
     335             :      * @param accountUri        Account of the stream
     336             :      * @param deviceId          Device of the stream
     337             :      * @param streamId          Stream to mute
     338             :      * @param state             True to mute, false to unmute
     339             :      */
     340             :     void muteStream(const std::string& accountUri,
     341             :                     const std::string& deviceId,
     342             :                     const std::string& streamId,
     343             :                     const bool& state);
     344             :     void updateMuted();
     345             :     void updateRecording();
     346             : 
     347             :     void updateVoiceActivity();
     348             : 
     349             :     std::shared_ptr<Call> getCallFromPeerID(std::string_view peerId);
     350             : 
     351             :     /**
     352             :      * Announce to the client that medias are successfully negotiated
     353             :      */
     354             :     void reportMediaNegotiationStatus();
     355             : 
     356             :     /**
     357             :      * Retrieve current medias list
     358             :      * @return current medias
     359             :      */
     360             :     std::vector<libjami::MediaMap> currentMediaList() const;
     361             : 
     362             :     /**
     363             :      * Return the last media list before the host was detached
     364             :      * @return Last media list
     365             :      */
     366          12 :     std::vector<libjami::MediaMap> getLastMediaList() const { return lastMediaList_; }
     367             : 
     368             :     // Update layout if recording changes
     369             :     void stopRecording() override;
     370             :     bool startRecording(const std::string& path) override;
     371             : 
     372             :     /**
     373             :      * @return Conference duration in milliseconds
     374             :      */
     375          14 :     std::chrono::milliseconds getDuration() const
     376             :     {
     377          14 :         return duration_start_ == clock::time_point::min()
     378           0 :                    ? std::chrono::milliseconds::zero()
     379          28 :                    : std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - duration_start_);
     380             :     }
     381             : 
     382             : private:
     383          70 :     std::weak_ptr<Conference> weak() { return std::static_pointer_cast<Conference>(shared_from_this()); }
     384             : 
     385             :     static std::shared_ptr<Call> getCall(const std::string& callId);
     386             :     bool isModerator(std::string_view uri) const;
     387             :     bool isHandRaised(std::string_view uri) const;
     388             :     bool isVoiceActive(std::string_view uri) const;
     389             :     void updateModerators();
     390             :     void updateHandsRaised();
     391             :     void muteHost(bool state);
     392             :     void muteCall(const std::string& callId, bool state);
     393             : 
     394             :     void foreachCall(const std::function<void(const std::shared_ptr<Call>& call)>& cb);
     395             : 
     396             :     std::string id_;
     397             :     std::weak_ptr<Account> account_;
     398             :     State confState_ {State::ACTIVE_DETACHED};
     399             :     mutable std::mutex subcallsMtx_ {};
     400             :     CallIdSet subCalls_;
     401             :     std::string mediaPlayerId_ {};
     402             : 
     403             :     mutable std::mutex confInfoMutex_ {};
     404             :     ConfInfo confInfo_ {};
     405             : 
     406             :     void sendConferenceInfos();
     407             :     std::shared_ptr<RingBuffer> ghostRingBuffer_;
     408             : 
     409             : #ifdef ENABLE_VIDEO
     410             :     bool videoEnabled_;
     411             :     std::shared_ptr<video::VideoMixer> videoMixer_;
     412             :     std::map<std::string, std::shared_ptr<video::SinkClient>> confSinksMap_ {};
     413             : #endif
     414             : 
     415             :     std::shared_ptr<jami::AudioInput> audioMixer_;
     416             :     std::set<std::string, std::less<>> moderators_ {};
     417             :     std::set<std::string, std::less<>> participantsMuted_ {};
     418             :     std::set<std::string, std::less<>> handsRaised_;
     419             : 
     420             :     // stream IDs
     421             :     std::set<std::string, std::less<>> streamsVoiceActive {};
     422             : 
     423             :     void initRecorder(std::shared_ptr<MediaRecorder>& rec);
     424             :     void deinitRecorder(std::shared_ptr<MediaRecorder>& rec);
     425             : 
     426             :     bool isMuted(std::string_view uri) const;
     427             : 
     428             :     ConfInfo getConfInfoHostUri(std::string_view localHostURI, std::string_view destURI);
     429             :     bool isHost(std::string_view uri) const;
     430             :     bool isHostDevice(std::string_view deviceId) const;
     431             : 
     432             :     /**
     433             :      * If the local host is participating in the conference (attached
     434             :      * mode ), this variable will hold the media source states
     435             :      * of the local host.
     436             :      */
     437             :     std::vector<MediaAttribute> hostSources_;
     438             :     // Because host doesn't have a call, we need to store the audio inputs
     439             :     std::map<std::string, std::shared_ptr<jami::AudioInput>> hostAudioInputs_;
     440             : 
     441             :     // Last media list before detaching from a conference
     442             :     std::vector<libjami::MediaMap> lastMediaList_ = {};
     443             : 
     444             :     bool localModAdded_ {false};
     445             : 
     446             :     std::map<std::string, ConfInfo> remoteHosts_;
     447             : #ifdef ENABLE_VIDEO
     448             :     void resizeRemoteParticipants(ConfInfo& confInfo, std::string_view peerURI);
     449             : #endif
     450             :     std::string_view findHostforRemoteParticipant(std::string_view uri, std::string_view deviceId = "");
     451             : 
     452             :     std::shared_ptr<Call> getCallWith(const std::string& accountUri, const std::string& deviceId);
     453             : 
     454             :     std::mutex sinksMtx_ {};
     455             : 
     456             : #ifdef ENABLE_PLUGIN
     457             :     /**
     458             :      * Call Streams and some typedefs
     459             :      */
     460             :     using AVMediaStream = Observable<std::shared_ptr<MediaFrame>>;
     461             :     using MediaStreamSubject = PublishMapSubject<std::shared_ptr<MediaFrame>, AVFrame*>;
     462             : 
     463             : #ifdef ENABLE_VIDEO
     464             :     /**
     465             :      *   Map: maps the VideoFrame to an AVFrame
     466             :      **/
     467             :     std::function<AVFrame*(const std::shared_ptr<jami::MediaFrame>&)> pluginVideoMap_ =
     468        3307 :         [](const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
     469        3307 :         return std::static_pointer_cast<VideoFrame>(m)->pointer();
     470             :     };
     471             : #endif // ENABLE_VIDEO
     472             : 
     473             :     /**
     474             :      * @brief createConfAVStream
     475             :      * Creates a conf AV stream like video input, video receive, audio input or audio receive
     476             :      * @param StreamData
     477             :      * @param streamSource
     478             :      * @param mediaStreamSubject
     479             :      */
     480             :     void createConfAVStream(const StreamData& StreamData,
     481             :                             AVMediaStream& streamSource,
     482             :                             const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject,
     483             :                             bool force = false);
     484             :     /**
     485             :      * @brief createConfAVStreams
     486             :      * Creates all Conf AV Streams (2 if audio, 4 if audio video)
     487             :      */
     488             :     void createConfAVStreams();
     489             : 
     490             :     std::mutex avStreamsMtx_ {};
     491             :     std::map<std::string, std::shared_ptr<MediaStreamSubject>> confAVStreams;
     492             : #endif // ENABLE_PLUGIN
     493             : 
     494             :     ConfProtocolParser parser_;
     495             :     std::string getRemoteId(const std::shared_ptr<jami::Call>& call) const;
     496             : 
     497             :     std::function<void(int)> shutdownCb_;
     498             :     clock::time_point duration_start_;
     499             : 
     500             :     /**
     501             :      * Initialize sources for host (takes default camera/audio)
     502             :      */
     503             :     void initSourcesForHost();
     504             : 
     505             : #ifdef ENABLE_VIDEO
     506             :     /**
     507             :      * Setup video mixer and its callbacks.
     508             :      * Called during construction.
     509             :      */
     510             :     void setupVideoMixer();
     511             : 
     512             :     /**
     513             :      * Callback for when video sources are updated.
     514             :      * @param infos The updated source information
     515             :      */
     516             :     void onVideoSourcesUpdated(const std::vector<video::SourceInfo>& infos);
     517             : 
     518             :     /**
     519             :      * Create participant info for a remote call source.
     520             :      * @param info Source information from video mixer
     521             :      * @return Populated ParticipantInfo
     522             :      */
     523             :     ParticipantInfo createParticipantInfoFromRemoteSource(const video::SourceInfo& info);
     524             : 
     525             :     /**
     526             :      * Create participant info for a local/host source.
     527             :      * @param info Source information from video mixer
     528             :      * @param acc The JamiAccount for the host
     529             :      * @param hostAdded Output flag indicating if host was added
     530             :      * @return Populated ParticipantInfo
     531             :      */
     532             :     ParticipantInfo createParticipantInfoFromLocalSource(const video::SourceInfo& info,
     533             :                                                          const std::shared_ptr<JamiAccount>& acc,
     534             :                                                          bool& hostAdded);
     535             : #endif
     536             : 
     537             :     /**
     538             :      * Register all protocol message handlers.
     539             :      * Called during construction.
     540             :      */
     541             :     void registerProtocolHandlers();
     542             : 
     543             :     /**
     544             :      * Take over media control from the call.
     545             :      * When a call joins a conference, the media control (mainly mute/un-mute
     546             :      * state of the local media source) will be handled by the conference and
     547             :      * the mixer.
     548             :      */
     549             :     void takeOverMediaSourceControl(const std::string& callId);
     550             : 
     551             :     /**
     552             :      * Bind host's audio
     553             :      */
     554             :     void bindHostAudio();
     555             : 
     556             :     /**
     557             :      * Unbind host's audio
     558             :      */
     559             :     void unbindHostAudio();
     560             : 
     561             :     /**
     562             :      * Bind call's audio to the conference
     563             :      */
     564             :     void bindSubCallAudio(const std::string& callId);
     565             : 
     566             :     /**
     567             :      * Unbind call's audio from the conference
     568             :      */
     569             :     void unbindSubCallAudio(const std::string& callId);
     570             : 
     571             :     /**
     572             :      * Clear all participant data from the conference
     573             :      */
     574             :     void clearParticipantData(const std::string& callId);
     575             : 
     576             : #ifdef ENABLE_VIDEO
     577             :     /**
     578             :      * Make sure video is negotiated with all subcalls
     579             :      * When the conference needs video (host has video or any participant has video),
     580             :      * this method triggers renegotiation with subcalls that don't have video yet.
     581             :      * @param excludeCallId Optional call ID to exclude from renegotiation (e.g., the call that just added video)
     582             :      */
     583             :     void negotiateVideoWithSubcalls(const std::string& excludeCallId = "");
     584             : #endif
     585             : };
     586             : 
     587             : } // namespace jami

Generated by: LCOV version 1.14