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
|