LCOV - code coverage report
Current view: top level - src - conference.h (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 69 100 69.0 %
Date: 2024-12-21 08:56:24 Functions: 11 16 68.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 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             : 
      52             : #ifdef ENABLE_VIDEO
      53             : namespace video {
      54             : class VideoMixer;
      55             : }
      56             : #endif
      57             : 
      58             : // info for a stream
      59             : struct ParticipantInfo
      60             : {
      61             :     std::string uri;
      62             :     std::string device;
      63             :     std::string sinkId; // stream ID
      64             :     bool active {false};
      65             :     int x {0};
      66             :     int y {0};
      67             :     int w {0};
      68             :     int h {0};
      69             :     bool videoMuted {false};
      70             :     bool audioLocalMuted {false};
      71             :     bool audioModeratorMuted {false};
      72             :     bool isModerator {false};
      73             :     bool handRaised {false};
      74             :     bool voiceActivity {false};
      75             :     bool recording {false};
      76             : 
      77         570 :     void fromJson(const Json::Value& v)
      78             :     {
      79         570 :         uri = v["uri"].asString();
      80         570 :         device = v["device"].asString();
      81         570 :         sinkId = v["sinkId"].asString();
      82         570 :         active = v["active"].asBool();
      83         570 :         x = v["x"].asInt();
      84         570 :         y = v["y"].asInt();
      85         570 :         w = v["w"].asInt();
      86         570 :         h = v["h"].asInt();
      87         570 :         videoMuted = v["videoMuted"].asBool();
      88         570 :         audioLocalMuted = v["audioLocalMuted"].asBool();
      89         570 :         audioModeratorMuted = v["audioModeratorMuted"].asBool();
      90         570 :         isModerator = v["isModerator"].asBool();
      91         570 :         handRaised = v["handRaised"].asBool();
      92         570 :         voiceActivity = v["voiceActivity"].asBool();
      93         570 :         recording = v["recording"].asBool();
      94         570 :     }
      95             : 
      96         576 :     Json::Value toJson() const
      97             :     {
      98         576 :         Json::Value val;
      99         579 :         val["uri"] = uri;
     100         578 :         val["device"] = device;
     101         578 :         val["sinkId"] = sinkId;
     102         579 :         val["active"] = active;
     103         576 :         val["x"] = x;
     104         579 :         val["y"] = y;
     105         578 :         val["w"] = w;
     106         576 :         val["h"] = h;
     107         577 :         val["videoMuted"] = videoMuted;
     108         579 :         val["audioLocalMuted"] = audioLocalMuted;
     109         579 :         val["audioModeratorMuted"] = audioModeratorMuted;
     110         578 :         val["isModerator"] = isModerator;
     111         578 :         val["handRaised"] = handRaised;
     112         579 :         val["voiceActivity"] = voiceActivity;
     113         579 :         val["recording"] = recording;
     114         577 :         return val;
     115           0 :     }
     116             : 
     117         834 :     std::map<std::string, std::string> toMap() const
     118             :     {
     119         834 :         return {{"uri", uri},
     120         834 :                 {"device", device},
     121         834 :                 {"sinkId", sinkId},
     122         834 :                 {"active", active ? "true" : "false"},
     123        1668 :                 {"x", std::to_string(x)},
     124        1668 :                 {"y", std::to_string(y)},
     125        1668 :                 {"w", std::to_string(w)},
     126        1668 :                 {"h", std::to_string(h)},
     127         834 :                 {"videoMuted", videoMuted ? "true" : "false"},
     128         834 :                 {"audioLocalMuted", audioLocalMuted ? "true" : "false"},
     129         834 :                 {"audioModeratorMuted", audioModeratorMuted ? "true" : "false"},
     130         834 :                 {"isModerator", isModerator ? "true" : "false"},
     131         834 :                 {"handRaised", handRaised ? "true" : "false"},
     132         834 :                 {"voiceActivity", voiceActivity ? "true" : "false"},
     133       23352 :                 {"recording", recording ? "true" : "false"}};
     134             :     }
     135             : 
     136           0 :     friend bool operator==(const ParticipantInfo& p1, const ParticipantInfo& p2)
     137             :     {
     138           0 :         return p1.uri == p2.uri and p1.device == p2.device and p1.sinkId == p2.sinkId
     139           0 :                and p1.active == p2.active and p1.x == p2.x and p1.y == p2.y and p1.w == p2.w
     140           0 :                and p1.h == p2.h and p1.videoMuted == p2.videoMuted
     141           0 :                and p1.audioLocalMuted == p2.audioLocalMuted
     142           0 :                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)
     148             :     {
     149             :         return !(p1 == p2);
     150             :     }
     151             : };
     152             : 
     153             : struct ConfInfo : public std::vector<ParticipantInfo>
     154             : {
     155             :     int h {0};
     156             :     int w {0};
     157             :     int v {1}; // Supported conference protocol version
     158             :     int layout {0};
     159             : 
     160           0 :     friend bool operator==(const ConfInfo& c1, const ConfInfo& c2)
     161             :     {
     162           0 :         if (c1.h != c2.h or c1.w != c2.w)
     163           0 :             return false;
     164           0 :         if (c1.size() != c2.size())
     165           0 :             return false;
     166             : 
     167           0 :         for (auto& p1 : c1) {
     168           0 :             auto it = std::find_if(c2.begin(), c2.end(), [&p1](const ParticipantInfo& p2) {
     169           0 :                 return p1 == p2;
     170             :             });
     171           0 :             if (it != c2.end())
     172           0 :                 continue;
     173             :             else
     174           0 :                 return false;
     175             :         }
     176           0 :         return true;
     177             :     }
     178             : 
     179           0 :     friend bool operator!=(const ConfInfo& c1, const ConfInfo& c2) { return !(c1 == c2); }
     180             : 
     181             :     std::vector<std::map<std::string, std::string>> toVectorMapStringString() const;
     182             :     std::string toString() const;
     183             : };
     184             : 
     185             : using CallIdSet = std::set<std::string>;
     186             : using clock = std::chrono::steady_clock;
     187             : 
     188             : class Conference : public Recordable, public std::enable_shared_from_this<Conference>
     189             : {
     190             : public:
     191             :     enum class State { ACTIVE_ATTACHED, ACTIVE_DETACHED, HOLD };
     192             : 
     193             :     /**
     194             :      * Constructor for this class, increment static counter
     195             :      */
     196             :     explicit Conference(const std::shared_ptr<Account>&,
     197             :                         const std::string& confId = "");
     198             : 
     199             :     /**
     200             :      * Destructor for this class, decrement static counter
     201             :      */
     202             :     ~Conference();
     203             : 
     204             :     /**
     205             :      * Return the conference id
     206             :      */
     207        1823 :     const std::string& getConfId() const { return id_; }
     208             : 
     209         260 :     std::shared_ptr<Account> getAccount() const { return account_.lock(); }
     210             : 
     211             :     std::string getAccountId() const;
     212             : 
     213             :     /**
     214             :      * Return the current conference state
     215             :      */
     216             :     State getState() const;
     217             : 
     218             :     /**
     219             :      * Set conference state
     220             :      */
     221             :     void setState(State state);
     222             : 
     223             :     /**
     224             :      * Set a callback that will be called when the conference will be destroyed
     225             :      */
     226          14 :     void onShutdown(std::function<void(int)> cb) { shutdownCb_ = std::move(cb); }
     227             : 
     228             :     /**
     229             :      * Return a string description of the conference state
     230             :      */
     231         292 :     static constexpr const char* getStateStr(State state)
     232             :     {
     233         292 :         switch (state) {
     234         175 :         case State::ACTIVE_ATTACHED:
     235         175 :             return "ACTIVE_ATTACHED";
     236         117 :         case State::ACTIVE_DETACHED:
     237         117 :             return "ACTIVE_DETACHED";
     238           0 :         case State::HOLD:
     239           0 :             return "HOLD";
     240           0 :         default:
     241           0 :             return "";
     242             :         }
     243             :     }
     244             : 
     245         208 :     const char* getStateStr() const { return getStateStr(confState_); }
     246             : 
     247             :     /**
     248             :      * Set the mute state of the local host
     249             :      */
     250             :     void setLocalHostMuteState(MediaType type, bool muted);
     251             : 
     252             :     /**
     253             :      * Get the mute state of the local host
     254             :      */
     255             :     bool isMediaSourceMuted(MediaType type) const;
     256             : 
     257             :     /**
     258             :      * Process a media change request.
     259             :      * Used to change the media attributes of the host.
     260             :      *
     261             :      * @param remoteMediaList new media list from the remote
     262             :      * @return true on success
     263             :      */
     264             :     bool requestMediaChange(const std::vector<libjami::MediaMap>& mediaList);
     265             : 
     266             :     /**
     267             :      * Process incoming media change request.
     268             :      *
     269             :      * @param callId the call ID
     270             :      * @param remoteMediaList new media list from the remote
     271             :      */
     272             :     void handleMediaChangeRequest(const std::shared_ptr<Call>& call,
     273             :                                   const std::vector<libjami::MediaMap>& remoteMediaList);
     274             : 
     275             :     /**
     276             :      * Add a new subcall to the conference
     277             :      */
     278             :     void addSubCall(const std::string& callId);
     279             : 
     280             :     /**
     281             :      * Remove a subcall from the conference
     282             :      */
     283             :     void removeSubCall(const std::string& callId);
     284             : 
     285             :     /**
     286             :      * Attach host
     287             :      */
     288             :     void attachHost(const std::vector<libjami::MediaMap>& mediaList = {});
     289             : 
     290             :     /**
     291             :      * Detach local audio/video from the conference
     292             :      */
     293             :     void detachHost();
     294             : 
     295             :     /**
     296             :      * Get the participant list for this conference
     297             :      */
     298             :     CallIdSet getSubCalls() const;
     299             : 
     300             :     /**
     301             :      * Start/stop recording toggle
     302             :      */
     303             :     bool toggleRecording() override;
     304             : 
     305             :     void switchInput(const std::string& input);
     306             :     void setActiveParticipant(const std::string& participant_id);
     307             :     void setActiveStream(const std::string& streamId, bool state);
     308             :     void setLayout(int layout);
     309             : 
     310             :     void onConfOrder(const std::string& callId, const std::string& order);
     311             : 
     312             :     bool isVideoEnabled() const;
     313             : 
     314             : #ifdef ENABLE_VIDEO
     315             :     void createSinks(const ConfInfo& infos);
     316             :     std::shared_ptr<video::VideoMixer> getVideoMixer();
     317             :     std::string getVideoInput() const;
     318             : #endif
     319             : 
     320           0 :     std::vector<std::map<std::string, std::string>> getConferenceInfos() const
     321             :     {
     322           0 :         std::lock_guard lk(confInfoMutex_);
     323           0 :         return confInfo_.toVectorMapStringString();
     324           0 :     }
     325             : 
     326             :     void updateConferenceInfo(ConfInfo confInfo);
     327             :     void setModerator(const std::string& uri, const bool& state);
     328             :     void hangupParticipant(const std::string& accountUri, const std::string& deviceId = "");
     329             :     void setHandRaised(const std::string& uri, const bool& state);
     330             :     void setVoiceActivity(const std::string& streamId, const bool& newState);
     331             : 
     332             :     void muteParticipant(const std::string& uri, const bool& state);
     333             :     void muteLocalHost(bool is_muted, const std::string& mediaType);
     334             :     bool isRemoteParticipant(const std::string& uri);
     335             :     void mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI);
     336             : 
     337             :     /**
     338             :      * The client shows one tile per stream (video/audio related to a media)
     339             :      * @note for now, in conferences we can only mute the audio of a call
     340             :      * @todo add a track (audio OR video) parameter to know what we want to mute
     341             :      * @param accountUri        Account of the stream
     342             :      * @param deviceId          Device of the stream
     343             :      * @param streamId          Stream to mute
     344             :      * @param state             True to mute, false to unmute
     345             :      */
     346             :     void muteStream(const std::string& accountUri,
     347             :                     const std::string& deviceId,
     348             :                     const std::string& streamId,
     349             :                     const bool& state);
     350             :     void updateMuted();
     351             :     void updateRecording();
     352             : 
     353             :     void updateVoiceActivity();
     354             : 
     355             :     std::shared_ptr<Call> getCallFromPeerID(std::string_view peerId);
     356             : 
     357             :     /**
     358             :      * Announce to the client that medias are successfully negotiated
     359             :      */
     360             :     void reportMediaNegotiationStatus();
     361             : 
     362             :     /**
     363             :      * Retrieve current medias list
     364             :      * @return current medias
     365             :      */
     366             :     std::vector<libjami::MediaMap> currentMediaList() const;
     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          14 :                    : std::chrono::duration_cast<std::chrono::milliseconds>(clock::now()
     380          28 :                                                                            - duration_start_);
     381             :     }
     382             : 
     383             : private:
     384          76 :     std::weak_ptr<Conference> weak()
     385             :     {
     386          76 :         return std::static_pointer_cast<Conference>(shared_from_this());
     387             :     }
     388             : 
     389             :     static std::shared_ptr<Call> getCall(const std::string& callId);
     390             :     bool isModerator(std::string_view uri) const;
     391             :     bool isHandRaised(std::string_view uri) const;
     392             :     bool isVoiceActive(std::string_view uri) const;
     393             :     void updateModerators();
     394             :     void updateHandsRaised();
     395             :     void muteHost(bool state);
     396             :     void muteCall(const std::string& callId, bool state);
     397             : 
     398             :     void foreachCall(const std::function<void(const std::shared_ptr<Call>& call)>& cb);
     399             : 
     400             :     std::string id_;
     401             :     std::weak_ptr<Account> account_;
     402             :     State confState_ {State::ACTIVE_DETACHED};
     403             :     mutable std::mutex subcallsMtx_ {};
     404             :     CallIdSet subCalls_;
     405             :     std::string mediaPlayerId_ {};
     406             : 
     407             :     mutable std::mutex confInfoMutex_ {};
     408             :     ConfInfo confInfo_ {};
     409             : 
     410             :     void sendConferenceInfos();
     411             :     std::shared_ptr<RingBuffer> ghostRingBuffer_;
     412             : 
     413             : #ifdef ENABLE_VIDEO
     414             :     bool videoEnabled_;
     415             :     std::shared_ptr<video::VideoMixer> videoMixer_;
     416             :     std::map<std::string, std::shared_ptr<video::SinkClient>> confSinksMap_ {};
     417             : #endif
     418             : 
     419             :     std::shared_ptr<jami::AudioInput> audioMixer_;
     420             :     std::set<std::string, std::less<>> moderators_ {};
     421             :     std::set<std::string, std::less<>> participantsMuted_ {};
     422             :     std::set<std::string, std::less<>> handsRaised_;
     423             : 
     424             :     // stream IDs
     425             :     std::set<std::string, std::less<>> streamsVoiceActive {};
     426             : 
     427             :     void initRecorder(std::shared_ptr<MediaRecorder>& rec);
     428             :     void deinitRecorder(std::shared_ptr<MediaRecorder>& rec);
     429             : 
     430             :     bool isMuted(std::string_view uri) const;
     431             : 
     432             :     ConfInfo getConfInfoHostUri(std::string_view localHostURI, std::string_view destURI);
     433             :     bool isHost(std::string_view uri) const;
     434             :     bool isHostDevice(std::string_view deviceId) const;
     435             : 
     436             :     /**
     437             :      * If the local host is participating in the conference (attached
     438             :      * mode ), this variable will hold the media source states
     439             :      * of the local host.
     440             :      */
     441             :     std::vector<MediaAttribute> hostSources_;
     442             :     // Because host doesn't have a call, we need to store the audio inputs
     443             :     std::map<std::string, std::shared_ptr<jami::AudioInput>> hostAudioInputs_;
     444             : 
     445             :     bool localModAdded_ {false};
     446             : 
     447             :     std::map<std::string, ConfInfo> remoteHosts_;
     448             : #ifdef ENABLE_VIDEO
     449             :     void resizeRemoteParticipants(ConfInfo& confInfo, std::string_view peerURI);
     450             : #endif
     451             :     std::string_view findHostforRemoteParticipant(std::string_view uri,
     452             :                                                   std::string_view deviceId = "");
     453             : 
     454             :     std::shared_ptr<Call> getCallWith(const std::string& accountUri, const std::string& deviceId);
     455             : 
     456             :     std::mutex sinksMtx_ {};
     457             : 
     458             : #ifdef ENABLE_PLUGIN
     459             :     /**
     460             :      * Call Streams and some typedefs
     461             :      */
     462             :     using AVMediaStream = Observable<std::shared_ptr<MediaFrame>>;
     463             :     using MediaStreamSubject = PublishMapSubject<std::shared_ptr<MediaFrame>, AVFrame*>;
     464             : 
     465             : #ifdef ENABLE_VIDEO
     466             :     /**
     467             :      *   Map: maps the VideoFrame to an AVFrame
     468             :      **/
     469             :     std::function<AVFrame*(const std::shared_ptr<jami::MediaFrame>&)> pluginVideoMap_ =
     470        3429 :         [](const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
     471        3429 :         return std::static_pointer_cast<VideoFrame>(m)->pointer();
     472             :     };
     473             : #endif // ENABLE_VIDEO
     474             : 
     475             :     /**
     476             :      * @brief createConfAVStream
     477             :      * Creates a conf AV stream like video input, video receive, audio input or audio receive
     478             :      * @param StreamData
     479             :      * @param streamSource
     480             :      * @param mediaStreamSubject
     481             :      */
     482             :     void createConfAVStream(const StreamData& StreamData,
     483             :                             AVMediaStream& streamSource,
     484             :                             const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject,
     485             :                             bool force = false);
     486             :     /**
     487             :      * @brief createConfAVStreams
     488             :      * Creates all Conf AV Streams (2 if audio, 4 if audio video)
     489             :      */
     490             :     void createConfAVStreams();
     491             : 
     492             :     std::mutex avStreamsMtx_ {};
     493             :     std::map<std::string, std::shared_ptr<MediaStreamSubject>> confAVStreams;
     494             : #endif // ENABLE_PLUGIN
     495             : 
     496             :     ConfProtocolParser parser_;
     497             :     std::string getRemoteId(const std::shared_ptr<jami::Call>& call) const;
     498             : 
     499             :     std::function<void(int)> shutdownCb_;
     500             :     clock::time_point duration_start_;
     501             : 
     502             :     /**
     503             :      * Initialize sources for host (takes default camera/audio)
     504             :      */
     505             :     void initSourcesForHost();
     506             : 
     507             :     /**
     508             :      * Take over media control from the call.
     509             :      * When a call joins a conference, the media control (mainly mute/un-mute
     510             :      * state of the local media source) will be handled by the conference and
     511             :      * the mixer.
     512             :      */
     513             :     void takeOverMediaSourceControl(const std::string& callId);
     514             : 
     515             :     /**
     516             :      * Bind host's audio
     517             :      */
     518             :     void bindHostAudio();
     519             : 
     520             :     /**
     521             :      * Unbind host's audio
     522             :      */
     523             :     void unbindHostAudio();
     524             : 
     525             :     /**
     526             :      * Bind call's audio to the conference
     527             :      */
     528             :     void bindSubCallAudio(const std::string& callId);
     529             : 
     530             :     /**
     531             :      * Unbind call's audio from the conference
     532             :      */
     533             :     void unbindSubCallAudio(const std::string& callId);
     534             : };
     535             : 
     536             : } // namespace jami

Generated by: LCOV version 1.14