Ring Daemon
Loading...
Searching...
No Matches
conference.h
Go to the documentation of this file.
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"
35#include "media/recordable.h"
36
37#ifdef ENABLE_PLUGIN
38#include "plugin/streamdata.h"
39#endif
40
41#ifdef ENABLE_VIDEO
43#endif
44
45#include <json/json.h>
46
47namespace jami {
48
49class Call;
50class Account;
51class JamiAccount;
52
53#ifdef ENABLE_VIDEO
54namespace video {
55class VideoMixer;
56struct SourceInfo;
57} // namespace video
58#endif
59
60// info for a stream
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 void fromJson(const Json::Value& v)
80 {
81 uri = v["uri"].asString();
82 device = v["device"].asString();
83 sinkId = v["sinkId"].asString();
84 active = v["active"].asBool();
85 x = v["x"].asInt();
86 y = v["y"].asInt();
87 w = v["w"].asInt();
88 h = v["h"].asInt();
89 videoMuted = v["videoMuted"].asBool();
90 audioLocalMuted = v["audioLocalMuted"].asBool();
91 audioModeratorMuted = v["audioModeratorMuted"].asBool();
92 isModerator = v["isModerator"].asBool();
93 handRaised = v["handRaised"].asBool();
94 voiceActivity = v["voiceActivity"].asBool();
95 recording = v["recording"].asBool();
96 }
97
98 Json::Value toJson() const
99 {
100 Json::Value val;
101 val["uri"] = uri;
102 val["device"] = device;
103 val["sinkId"] = sinkId;
104 val["active"] = active;
105 val["x"] = x;
106 val["y"] = y;
107 val["w"] = w;
108 val["h"] = h;
109 val["videoMuted"] = videoMuted;
110 val["audioLocalMuted"] = audioLocalMuted;
111 val["audioModeratorMuted"] = audioModeratorMuted;
112 val["isModerator"] = isModerator;
113 val["handRaised"] = handRaised;
114 val["voiceActivity"] = voiceActivity;
115 val["recording"] = recording;
116 return val;
117 }
118
119 std::map<std::string, std::string> toMap() const
120 {
121 return {{"uri", uri},
122 {"device", device},
123 {"sinkId", sinkId},
124 {"active", active ? "true" : "false"},
125 {"x", std::to_string(x)},
126 {"y", std::to_string(y)},
127 {"w", std::to_string(w)},
128 {"h", std::to_string(h)},
129 {"videoMuted", videoMuted ? "true" : "false"},
130 {"audioLocalMuted", audioLocalMuted ? "true" : "false"},
131 {"audioModeratorMuted", audioModeratorMuted ? "true" : "false"},
132 {"isModerator", isModerator ? "true" : "false"},
133 {"handRaised", handRaised ? "true" : "false"},
134 {"voiceActivity", voiceActivity ? "true" : "false"},
135 {"recording", recording ? "true" : "false"}};
136 }
137
138 friend bool operator==(const ParticipantInfo& p1, const ParticipantInfo& p2)
139 {
140 return p1.uri == p2.uri and p1.device == p2.device and p1.sinkId == p2.sinkId and p1.active == p2.active
141 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 and p1.audioLocalMuted == p2.audioLocalMuted and p1.audioModeratorMuted == p2.audioModeratorMuted
143 and p1.isModerator == p2.isModerator and p1.handRaised == p2.handRaised
144 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
150struct 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 friend bool operator==(const ConfInfo& c1, const ConfInfo& c2)
158 {
159 if (c1.h != c2.h or c1.w != c2.w)
160 return false;
161 if (c1.size() != c2.size())
162 return false;
163
164 for (auto& p1 : c1) {
165 auto it = std::find_if(c2.begin(), c2.end(), [&p1](const ParticipantInfo& p2) { return p1 == p2; });
166 if (it != c2.end())
167 continue;
168 else
169 return false;
170 }
171 return true;
172 }
173
174 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
180using CallIdSet = std::set<std::string>;
181using clock = std::chrono::steady_clock;
182
183class Conference : public Recordable, public std::enable_shared_from_this<Conference>
184{
185public:
187
191 explicit Conference(const std::shared_ptr<Account>&, const std::string& confId = "");
192
196 ~Conference();
197
201 const std::string& getConfId() const { return id_; }
202
203 std::shared_ptr<Account> getAccount() const { return account_.lock(); }
204
205 std::string getAccountId() const;
206
210 State getState() const;
211
215 void setState(State state);
216
220 void onShutdown(std::function<void(int)> cb) { shutdownCb_ = std::move(cb); }
221
225 static constexpr const char* getStateStr(State state)
226 {
227 switch (state) {
229 return "ACTIVE_ATTACHED";
231 return "ACTIVE_DETACHED";
232 case State::HOLD:
233 return "HOLD";
234 default:
235 return "";
236 }
237 }
238
239 const char* getStateStr() const { return getStateStr(confState_); }
240
244 void setLocalHostMuteState(MediaType type, bool muted);
245
249 bool isMediaSourceMuted(MediaType type) const;
250
258 bool requestMediaChange(const std::vector<libjami::MediaMap>& mediaList);
259
266 void handleMediaChangeRequest(const std::shared_ptr<Call>& call,
267 const std::vector<libjami::MediaMap>& remoteMediaList);
268
272 void addSubCall(const std::string& callId);
273
277 void removeSubCall(const std::string& callId);
278
282 void attachHost(const std::vector<libjami::MediaMap>& mediaList);
283
287 void detachHost();
288
292 CallIdSet getSubCalls() const;
293
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 std::vector<std::map<std::string, std::string>> getConferenceInfos() const
315 {
316 std::lock_guard lk(confInfoMutex_);
317 return confInfo_.toVectorMapStringString();
318 }
319
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
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
355
360 std::vector<libjami::MediaMap> currentMediaList() const;
361
366 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
375 std::chrono::milliseconds getDuration() const
376 {
377 return duration_start_ == clock::time_point::min()
378 ? std::chrono::milliseconds::zero()
379 : std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - duration_start_);
380 }
381
382private:
383 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
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
462
463#ifdef ENABLE_VIDEO
467 std::function<AVFrame*(const std::shared_ptr<jami::MediaFrame>&)> pluginVideoMap_ =
468 [](const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
469 return std::static_pointer_cast<VideoFrame>(m)->pointer();
470 };
471#endif // ENABLE_VIDEO
472
482 const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject,
483 bool force = false);
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
503 void initSourcesForHost();
504
505#ifdef ENABLE_VIDEO
510 void setupVideoMixer();
511
516 void onVideoSourcesUpdated(const std::vector<video::SourceInfo>& infos);
517
523 ParticipantInfo createParticipantInfoFromRemoteSource(const video::SourceInfo& info);
524
532 ParticipantInfo createParticipantInfoFromLocalSource(const video::SourceInfo& info,
533 const std::shared_ptr<JamiAccount>& acc,
534 bool& hostAdded);
535#endif
536
541 void registerProtocolHandlers();
542
549 void takeOverMediaSourceControl(const std::string& callId);
550
554 void bindHostAudio();
555
559 void unbindHostAudio();
560
564 void bindSubCallAudio(const std::string& callId);
565
569 void unbindSubCallAudio(const std::string& callId);
570
574 void clearParticipantData(const std::string& callId);
575
576#ifdef ENABLE_VIDEO
583 void negotiateVideoWithSubcalls(const std::string& excludeCallId = "");
584#endif
585};
586
587} // namespace jami
const char * getStateStr() const
Definition conference.h:239
std::vector< libjami::MediaMap > getLastMediaList() const
Return the last media list before the host was detached.
Definition conference.h:366
void setVoiceActivity(const std::string &streamId, const bool &newState)
std::string getAccountId() const
bool isVideoEnabled() const
void hangupParticipant(const std::string &accountUri, const std::string &deviceId="")
void muteLocalHost(bool is_muted, const std::string &mediaType)
std::chrono::milliseconds getDuration() const
Definition conference.h:375
bool startRecording(const std::string &path) override
Start recording.
const std::string & getConfId() const
Return the conference id.
Definition conference.h:201
void removeSubCall(const std::string &callId)
Remove a subcall from the conference.
std::vector< libjami::MediaMap > currentMediaList() const
Retrieve current medias list.
std::vector< std::map< std::string, std::string > > getConferenceInfos() const
Definition conference.h:314
void detachHost()
Detach local audio/video from the conference.
~Conference()
Destructor for this class, decrement static counter.
void mergeConfInfo(ConfInfo &newInfo, const std::string &peerURI)
void setActiveParticipant(const std::string &participant_id)
void handleMediaChangeRequest(const std::shared_ptr< Call > &call, const std::vector< libjami::MediaMap > &remoteMediaList)
Process incoming media change request.
void setLocalHostMuteState(MediaType type, bool muted)
Set the mute state of the local host.
void setState(State state)
Set conference state.
bool requestMediaChange(const std::vector< libjami::MediaMap > &mediaList)
Process a media change request.
void onConfOrder(const std::string &callId, const std::string &order)
void addSubCall(const std::string &callId)
Add a new subcall to the conference.
std::shared_ptr< Account > getAccount() const
Definition conference.h:203
void onShutdown(std::function< void(int)> cb)
Set a callback that will be called when the conference will be destroyed.
Definition conference.h:220
std::shared_ptr< Call > getCallFromPeerID(std::string_view peerId)
void setActiveStream(const std::string &streamId, bool state)
void setModerator(const std::string &uri, const bool &state)
static constexpr const char * getStateStr(State state)
Return a string description of the conference state.
Definition conference.h:225
void stopRecording() override
Stop recording.
bool toggleRecording() override
Start/stop recording toggle.
CallIdSet getSubCalls() const
Get the participant list for this conference.
void setHandRaised(const std::string &uri, const bool &state)
void attachHost(const std::vector< libjami::MediaMap > &mediaList)
Attach host.
void muteParticipant(const std::string &uri, const bool &state)
State getState() const
Return the current conference state.
void muteStream(const std::string &accountUri, const std::string &deviceId, const std::string &streamId, const bool &state)
The client shows one tile per stream (video/audio related to a media)
void updateConferenceInfo(ConfInfo confInfo)
void switchInput(const std::string &input)
bool isRemoteParticipant(const std::string &uri)
void setLayout(int layout)
void reportMediaNegotiationStatus()
Announce to the client that medias are successfully negotiated.
bool isMediaSourceMuted(MediaType type) const
Get the mute state of the local host.
std::set< std::string > CallIdSet
Definition conference.h:180
void emitSignal(Args... args)
Definition jami_signal.h:64
std::chrono::steady_clock clock
Definition conference.h:181
Contains information about an AV subject.
Definition streamdata.h:30
std::string toString() const
std::vector< std::map< std::string, std::string > > toVectorMapStringString() const
friend bool operator!=(const ConfInfo &c1, const ConfInfo &c2)
Definition conference.h:174
friend bool operator==(const ConfInfo &c1, const ConfInfo &c2)
Definition conference.h:157
Json::Value toJson() const
Definition conference.h:98
friend bool operator==(const ParticipantInfo &p1, const ParticipantInfo &p2)
Definition conference.h:138
std::map< std::string, std::string > toMap() const
Definition conference.h:119
friend bool operator!=(const ParticipantInfo &p1, const ParticipantInfo &p2)
Definition conference.h:147
void fromJson(const Json::Value &v)
Definition conference.h:79