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-04-19 08:05:40 Functions: 11 16 68.8 %

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

Generated by: LCOV version 1.14