Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * Author: Yan Morin <yan.morin@savoirfairelinux.com>
5 : * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com>
6 : * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 3 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License
19 : * along with this program; if not, write to the Free Software
20 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 : */
22 :
23 : #include "call.h"
24 : #include "account.h"
25 : #include "jamidht/jamiaccount.h"
26 : #include "manager.h"
27 : #ifdef ENABLE_PLUGIN
28 : #include "plugin/jamipluginmanager.h"
29 : #include "plugin/streamdata.h"
30 : #endif
31 : #include "audio/ringbufferpool.h"
32 : #include "jami/call_const.h"
33 : #include "client/ring_signal.h"
34 : #include "connectivity/sip_utils.h"
35 : #include "map_utils.h"
36 : #include "call_factory.h"
37 : #include "string_utils.h"
38 : #include "enumclass_utils.h"
39 :
40 : #include "errno.h"
41 :
42 : #include <dhtnet/ip_utils.h>
43 : #include <opendht/thread_pool.h>
44 :
45 : #include <stdexcept>
46 : #include <system_error>
47 : #include <algorithm>
48 : #include <functional>
49 : #include <utility>
50 :
51 : namespace jami {
52 :
53 : /// Hangup many calls with same error code, filtered by a predicate
54 : ///
55 : /// For each call pointer given by iterating on given \a callptr_list
56 : /// calls the unary predicate \a pred with this call pointer and hangup the call with given error
57 : /// code \a errcode when the predicate return true.
58 : /// The predicate should have <code>bool(Call*) signature</code>.
59 : template<typename T>
60 : inline void
61 488 : hangupCallsIf(Call::SubcallSet&& calls, int errcode, T pred)
62 : {
63 580 : for (auto& call : calls) {
64 92 : if (not pred(call.get()))
65 78 : continue;
66 28 : dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
67 : }
68 488 : }
69 :
70 : /// Hangup many calls with same error code.
71 : ///
72 : /// Works as hangupCallsIf() with a predicate that always return true.
73 : inline void
74 410 : hangupCalls(Call::SubcallSet&& callptr_list, int errcode)
75 : {
76 424 : hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
77 410 : }
78 :
79 : //==============================================================================
80 :
81 406 : Call::Call(const std::shared_ptr<Account>& account,
82 : const std::string& id,
83 : Call::CallType type,
84 406 : const std::map<std::string, std::string>& details)
85 406 : : id_(id)
86 406 : , type_(type)
87 812 : , account_(account)
88 : {
89 406 : addStateListener([this](Call::CallState call_state,
90 : Call::ConnectionState cnx_state,
91 10381 : int code) {
92 2096 : checkPendingIM();
93 2096 : runOnMainThread([callWkPtr = weak()] {
94 2096 : if (auto call = callWkPtr.lock())
95 2096 : call->checkAudio();
96 2096 : });
97 :
98 : // if call just started ringing, schedule call timeout
99 2096 : if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
100 108 : auto timeout = Manager::instance().getRingingTimeout();
101 108 : JAMI_DBG("Scheduling call timeout in %d seconds", timeout);
102 :
103 216 : Manager::instance().scheduler().scheduleIn(
104 84 : [callWkPtr = weak()] {
105 84 : if (auto callShPtr = callWkPtr.lock()) {
106 3 : if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
107 1 : JAMI_DBG(
108 : "Call %s is still ringing after timeout, setting state to BUSY",
109 : callShPtr->getCallId().c_str());
110 1 : callShPtr->hangup(PJSIP_SC_BUSY_HERE);
111 1 : Manager::instance().callFailure(*callShPtr);
112 : }
113 84 : }
114 84 : },
115 216 : std::chrono::seconds(timeout));
116 : }
117 :
118 2096 : if (!isSubcall()) {
119 1481 : if (code == static_cast<int>(std::errc::no_such_device_or_address)) {
120 1 : reason_ = "no_device";
121 : }
122 1481 : if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
123 204 : duration_start_ = clock::now();
124 1277 : else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
125 456 : if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
126 208 : if (toUsername().find('/') == std::string::npos && getCallType() == CallType::OUTGOING) {
127 111 : if (auto cm = jamiAccount->convModule(true))
128 111 : cm->addCallHistoryMessage(getPeerNumber(), getCallDuration().count(), reason_);
129 : }
130 208 : monitor();
131 228 : }
132 : }
133 : }
134 :
135 : // kill pending subcalls at disconnect
136 2096 : if (call_state == CallState::OVER)
137 406 : hangupCalls(safePopSubcalls(), 0);
138 :
139 2096 : return true;
140 : });
141 :
142 406 : time(×tamp_start_);
143 406 : }
144 :
145 406 : Call::~Call() {}
146 :
147 : void
148 419 : Call::removeCall()
149 : {
150 419 : auto this_ = shared_from_this();
151 419 : Manager::instance().callFactory.removeCall(*this);
152 419 : setState(CallState::OVER);
153 419 : if (Recordable::isRecording())
154 3 : Recordable::stopRecording();
155 419 : if (auto account = account_.lock())
156 419 : account->detach(this_);
157 419 : }
158 :
159 : std::string
160 2589 : Call::getAccountId() const
161 : {
162 2589 : if (auto shared = account_.lock())
163 2589 : return shared->getAccountID();
164 4 : JAMI_ERR("No account detected");
165 4 : return {};
166 : }
167 :
168 : Call::ConnectionState
169 8039 : Call::getConnectionState() const
170 : {
171 8039 : std::lock_guard lock(callMutex_);
172 8038 : return connectionState_;
173 8039 : }
174 :
175 : Call::CallState
176 9708 : Call::getState() const
177 : {
178 9708 : std::lock_guard lock(callMutex_);
179 9707 : return callState_;
180 9708 : }
181 :
182 : bool
183 810 : Call::validStateTransition(CallState newState)
184 : {
185 : // Notice to developper:
186 : // - list only permitted transition (return true)
187 : // - let non permitted ones as default case (return false)
188 :
189 : // always permited
190 810 : if (newState == CallState::OVER)
191 406 : return true;
192 :
193 404 : switch (callState_) {
194 383 : case CallState::INACTIVE:
195 383 : switch (newState) {
196 383 : case CallState::ACTIVE:
197 : case CallState::BUSY:
198 : case CallState::PEER_BUSY:
199 : case CallState::MERROR:
200 383 : return true;
201 0 : default: // INACTIVE, HOLD
202 0 : return false;
203 : }
204 :
205 16 : case CallState::ACTIVE:
206 16 : switch (newState) {
207 16 : case CallState::BUSY:
208 : case CallState::PEER_BUSY:
209 : case CallState::HOLD:
210 : case CallState::MERROR:
211 16 : return true;
212 0 : default: // INACTIVE, ACTIVE
213 0 : return false;
214 : }
215 :
216 3 : case CallState::HOLD:
217 3 : switch (newState) {
218 3 : case CallState::ACTIVE:
219 : case CallState::MERROR:
220 3 : return true;
221 0 : default: // INACTIVE, HOLD, BUSY, PEER_BUSY, MERROR
222 0 : return false;
223 : }
224 :
225 0 : case CallState::BUSY:
226 0 : switch (newState) {
227 0 : case CallState::MERROR:
228 0 : return true;
229 0 : default: // INACTIVE, ACTIVE, HOLD, BUSY, PEER_BUSY
230 0 : return false;
231 : }
232 :
233 2 : default: // MERROR
234 2 : return false;
235 : }
236 : }
237 :
238 : bool
239 2309 : Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
240 : {
241 2309 : std::unique_lock<std::recursive_mutex> lock(callMutex_);
242 2309 : JAMI_DBG("[call:%s] state change %u/%u, cnx %u/%u, code %d",
243 : id_.c_str(),
244 : (unsigned) callState_,
245 : (unsigned) call_state,
246 : (unsigned) connectionState_,
247 : (unsigned) cnx_state,
248 : code);
249 :
250 2309 : if (callState_ != call_state) {
251 810 : if (not validStateTransition(call_state)) {
252 2 : JAMI_ERR("[call:%s] invalid call state transition from %u to %u",
253 : id_.c_str(),
254 : (unsigned) callState_,
255 : (unsigned) call_state);
256 2 : return false;
257 : }
258 1499 : } else if (connectionState_ == cnx_state)
259 211 : return true; // no changes as no-op
260 :
261 : // Emit client state only if changed
262 2096 : auto old_client_state = getStateStr();
263 2096 : callState_ = call_state;
264 2096 : connectionState_ = cnx_state;
265 2096 : auto new_client_state = getStateStr();
266 :
267 5132 : for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
268 3036 : if ((*it)(callState_, connectionState_, code))
269 2947 : ++it;
270 : else
271 89 : it = stateChangedListeners_.erase(it);
272 : }
273 :
274 2096 : if (old_client_state != new_client_state) {
275 1728 : if (not parent_) {
276 1201 : JAMI_DBG("[call:%s] emit client call state change %s, code %d",
277 : id_.c_str(),
278 : new_client_state.c_str(),
279 : code);
280 1201 : lock.unlock();
281 2402 : emitSignal<libjami::CallSignal::StateChange>(getAccountId(),
282 1201 : id_,
283 : new_client_state,
284 : code);
285 : }
286 : }
287 :
288 2096 : return true;
289 2309 : }
290 :
291 : bool
292 514 : Call::setState(CallState call_state, signed code)
293 : {
294 514 : std::lock_guard lock(callMutex_);
295 1028 : return setState(call_state, connectionState_, code);
296 514 : }
297 :
298 : bool
299 1310 : Call::setState(ConnectionState cnx_state, signed code)
300 : {
301 1310 : std::lock_guard lock(callMutex_);
302 2620 : return setState(callState_, cnx_state, code);
303 1310 : }
304 :
305 : std::string
306 8954 : Call::getStateStr() const
307 : {
308 : using namespace libjami::Call;
309 :
310 8954 : switch (getState()) {
311 3898 : case CallState::ACTIVE:
312 3898 : switch (getConnectionState()) {
313 890 : case ConnectionState::PROGRESSING:
314 890 : return StateEvent::CONNECTING;
315 :
316 907 : case ConnectionState::RINGING:
317 907 : return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
318 :
319 700 : case ConnectionState::DISCONNECTED:
320 700 : return StateEvent::HUNGUP;
321 :
322 1400 : case ConnectionState::CONNECTED:
323 : default:
324 1400 : return StateEvent::CURRENT;
325 : }
326 :
327 39 : case CallState::HOLD:
328 39 : if (getConnectionState() == ConnectionState::DISCONNECTED)
329 12 : return StateEvent::HUNGUP;
330 27 : return StateEvent::HOLD;
331 :
332 4 : case CallState::BUSY:
333 4 : return StateEvent::BUSY;
334 :
335 4 : case CallState::PEER_BUSY:
336 4 : return StateEvent::PEER_BUSY;
337 :
338 3521 : case CallState::INACTIVE:
339 3521 : switch (getConnectionState()) {
340 1221 : case ConnectionState::PROGRESSING:
341 1221 : return StateEvent::CONNECTING;
342 :
343 587 : case ConnectionState::RINGING:
344 587 : return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
345 :
346 59 : case ConnectionState::CONNECTED:
347 59 : return StateEvent::CURRENT;
348 :
349 1654 : default:
350 1654 : return StateEvent::INACTIVE;
351 : }
352 :
353 1087 : case CallState::OVER:
354 1087 : return StateEvent::OVER;
355 :
356 400 : case CallState::MERROR:
357 : default:
358 400 : return StateEvent::FAILURE;
359 : }
360 : }
361 :
362 : bool
363 17 : Call::toggleRecording()
364 : {
365 17 : const bool startRecording = Recordable::toggleRecording();
366 17 : return startRecording;
367 : }
368 :
369 : std::map<std::string, std::string>
370 395 : Call::getDetails() const
371 : {
372 395 : auto conference = conf_.lock();
373 : return {
374 395 : {libjami::Call::Details::CALL_TYPE, std::to_string((unsigned) type_)},
375 395 : {libjami::Call::Details::PEER_NUMBER, peerNumber_},
376 395 : {libjami::Call::Details::DISPLAY_NAME, peerDisplayName_},
377 790 : {libjami::Call::Details::CALL_STATE, getStateStr()},
378 790 : {libjami::Call::Details::CONF_ID, conference ? conference->getConfId() : ""},
379 790 : {libjami::Call::Details::TIMESTAMP_START, std::to_string(timestamp_start_)},
380 790 : {libjami::Call::Details::ACCOUNTID, getAccountId()},
381 395 : {libjami::Call::Details::TO_USERNAME, toUsername()},
382 : {libjami::Call::Details::AUDIO_MUTED,
383 790 : std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
384 : {libjami::Call::Details::VIDEO_MUTED,
385 790 : std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
386 790 : {libjami::Call::Details::AUDIO_ONLY, std::string(bool_to_str(not hasVideo()))},
387 8295 : };
388 395 : }
389 :
390 : void
391 190 : Call::onTextMessage(std::map<std::string, std::string>&& messages)
392 : {
393 190 : auto it = messages.find("application/confInfo+json");
394 190 : if (it != messages.end()) {
395 184 : setConferenceInfo(it->second);
396 184 : return;
397 : }
398 :
399 6 : it = messages.find("application/confOrder+json");
400 6 : if (it != messages.end()) {
401 6 : if (auto conf = conf_.lock())
402 6 : conf->onConfOrder(getCallId(), it->second);
403 0 : return;
404 : }
405 :
406 : {
407 0 : std::lock_guard lk {callMutex_};
408 0 : if (parent_) {
409 0 : pendingInMessages_.emplace_back(std::move(messages), "");
410 0 : return;
411 : }
412 0 : }
413 : #ifdef ENABLE_PLUGIN
414 0 : auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
415 0 : if (pluginChatManager.hasHandlers()) {
416 0 : pluginChatManager.publishMessage(
417 0 : std::make_shared<JamiMessage>(getAccountId(), getPeerNumber(), true, messages, false));
418 : }
419 : #endif
420 0 : Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), messages);
421 : }
422 :
423 : void
424 106 : Call::peerHungup()
425 : {
426 106 : const auto state = getState();
427 106 : const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
428 106 : setState(ConnectionState::DISCONNECTED, aborted ? ECONNABORTED : ECONNREFUSED);
429 106 : }
430 :
431 : void
432 178 : Call::addSubCall(Call& subcall)
433 : {
434 178 : std::lock_guard lk {callMutex_};
435 :
436 : // Add subCall only if call is not connected or terminated
437 : // Because we only want to addSubCall if the peer didn't answer
438 : // So till it's <= RINGING
439 178 : if (connectionState_ == ConnectionState::CONNECTED
440 178 : || connectionState_ == ConnectionState::DISCONNECTED || callState_ == CallState::OVER) {
441 0 : subcall.removeCall();
442 0 : return;
443 : }
444 :
445 178 : if (not subcalls_.emplace(getPtr(subcall)).second) {
446 0 : JAMI_ERR("[call:%s] add twice subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
447 0 : return;
448 : }
449 :
450 178 : JAMI_DBG("[call:%s] add subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
451 178 : subcall.parent_ = getPtr(*this);
452 :
453 178 : for (const auto& msg : pendingOutMessages_)
454 0 : subcall.sendTextMessage(msg.first, msg.second);
455 :
456 178 : subcall.addStateListener(
457 615 : [sub = subcall.weak(), parent = weak()](Call::CallState new_state,
458 : Call::ConnectionState new_cstate,
459 : int /* code */) {
460 615 : runOnMainThread([sub, parent, new_state, new_cstate]() {
461 615 : if (auto p = parent.lock()) {
462 607 : if (auto s = sub.lock()) {
463 505 : p->subcallStateChanged(*s, new_state, new_cstate);
464 607 : }
465 615 : }
466 615 : });
467 615 : return true;
468 : });
469 178 : }
470 :
471 : /// Called by a subcall when its states change (multidevice)
472 : ///
473 : /// Its purpose is to manage per device call and try to found the first responding.
474 : /// Parent call states are managed by these subcalls.
475 : /// \note this method may decrease the given \a subcall ref count.
476 : void
477 505 : Call::subcallStateChanged(Call& subcall, Call::CallState new_state, Call::ConnectionState new_cstate)
478 : {
479 : {
480 : // This condition happens when a subcall hangups/fails after removed from parent's list.
481 : // This is normal to keep parent_ != nullptr on the subcall, as it's the way to flag it
482 : // as an subcall and not a master call.
483 : // XXX: having a way to unsubscribe the state listener could be better than such test
484 505 : std::lock_guard lk {callMutex_};
485 505 : auto sit = subcalls_.find(getPtr(subcall));
486 505 : if (sit == subcalls_.end())
487 79 : return;
488 505 : }
489 :
490 : // We found a responding device: hangup all other subcalls and merge
491 426 : if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
492 78 : JAMI_DBG("[call:%s] subcall %s answered by peer",
493 : getCallId().c_str(),
494 : subcall.getCallId().c_str());
495 :
496 156 : hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
497 78 : merge(subcall);
498 78 : Manager::instance().peerAnsweredCall(*this);
499 78 : return;
500 : }
501 :
502 : // Hangup the call if any device hangup or send busy
503 348 : if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
504 175 : and new_cstate == ConnectionState::DISCONNECTED) {
505 4 : JAMI_WARN("[call:%s] subcall %s hangup by peer",
506 : getCallId().c_str(),
507 : subcall.getCallId().c_str());
508 4 : reason_ = new_state == CallState::ACTIVE ? "declined" : "busy";
509 4 : hangupCalls(safePopSubcalls(), 0);
510 4 : Manager::instance().peerHungupCall(*this);
511 4 : removeCall();
512 4 : return;
513 : }
514 :
515 : // Subcall is busy or failed
516 344 : if (new_state >= CallState::BUSY) {
517 86 : if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
518 0 : JAMI_WARN("[call:%s] subcall %s busy", getCallId().c_str(), subcall.getCallId().c_str());
519 : else
520 86 : JAMI_WARN("[call:%s] subcall %s failed",
521 : getCallId().c_str(),
522 : subcall.getCallId().c_str());
523 86 : std::lock_guard lk {callMutex_};
524 86 : subcalls_.erase(getPtr(subcall));
525 :
526 : // Parent call fails if last subcall is busy or failed
527 86 : if (subcalls_.empty()) {
528 2 : if (new_state == CallState::BUSY) {
529 0 : setState(CallState::BUSY,
530 : ConnectionState::DISCONNECTED,
531 : static_cast<int>(std::errc::device_or_resource_busy));
532 2 : } else if (new_state == CallState::PEER_BUSY) {
533 0 : setState(CallState::PEER_BUSY,
534 : ConnectionState::DISCONNECTED,
535 : static_cast<int>(std::errc::device_or_resource_busy));
536 : } else {
537 : // XXX: first idea was to use std::errc::host_unreachable, but it's not available on
538 : // some platforms like mingw.
539 2 : setState(CallState::MERROR,
540 : ConnectionState::DISCONNECTED,
541 : static_cast<int>(std::errc::io_error));
542 : }
543 2 : removeCall();
544 : } else {
545 84 : JAMI_DBG("[call:%s] remains %zu subcall(s)", getCallId().c_str(), subcalls_.size());
546 : }
547 :
548 86 : return;
549 86 : }
550 :
551 : // Copy call/cnx states (forward only)
552 258 : if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
553 86 : setState(new_state);
554 : }
555 258 : if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
556 169 : and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
557 169 : setState(new_cstate);
558 : }
559 : }
560 :
561 : /// Replace current call data with ones from the given \a subcall.
562 : /// Must be called while locked by subclass
563 : void
564 78 : Call::merge(Call& subcall)
565 : {
566 78 : JAMI_DBG("[call:%s] merge subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
567 :
568 : // Merge data
569 78 : pendingInMessages_ = std::move(subcall.pendingInMessages_);
570 78 : if (peerNumber_.empty())
571 0 : peerNumber_ = std::move(subcall.peerNumber_);
572 78 : peerDisplayName_ = std::move(subcall.peerDisplayName_);
573 78 : setState(subcall.getState(), subcall.getConnectionState());
574 :
575 78 : std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
576 78 : runOnMainThread([subCallWeak] {
577 78 : if (auto subcall = subCallWeak.lock())
578 78 : subcall->removeCall();
579 78 : });
580 78 : }
581 :
582 : /// Handle pending IM message
583 : ///
584 : /// Used in multi-device context to send pending IM when the master call is connected.
585 : void
586 2096 : Call::checkPendingIM()
587 : {
588 2096 : std::lock_guard lk {callMutex_};
589 :
590 2096 : auto state = getStateStr();
591 : // Let parent call handles IM after the merge
592 2096 : if (not parent_) {
593 1481 : if (state == libjami::Call::StateEvent::CURRENT) {
594 208 : for (const auto& msg : pendingInMessages_)
595 0 : Manager::instance().incomingMessage(getAccountId(),
596 : getCallId(),
597 : getPeerNumber(),
598 0 : msg.first);
599 208 : pendingInMessages_.clear();
600 :
601 208 : std::weak_ptr<Call> callWkPtr = shared_from_this();
602 208 : runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
603 208 : if (auto call = callWkPtr.lock())
604 208 : for (const auto& msg : pending)
605 208 : call->sendTextMessage(msg.first, msg.second);
606 208 : });
607 208 : }
608 : }
609 2096 : }
610 :
611 : /// Handle tones for RINGING and BUSY calls
612 : ///
613 : void
614 1706 : Call::checkAudio()
615 : {
616 : using namespace libjami::Call;
617 :
618 1706 : auto state = getStateStr();
619 1706 : if (state == StateEvent::RINGING) {
620 203 : Manager::instance().peerRingingCall(*this);
621 1503 : } else if (state == StateEvent::BUSY) {
622 1 : Manager::instance().callBusy(*this);
623 : }
624 1706 : }
625 :
626 : // Helper to safely pop subcalls list
627 : Call::SubcallSet
628 488 : Call::safePopSubcalls()
629 : {
630 488 : std::lock_guard lk {callMutex_};
631 : // std::exchange is C++14
632 488 : auto old_value = std::move(subcalls_);
633 488 : subcalls_.clear();
634 976 : return old_value;
635 488 : }
636 :
637 : void
638 184 : Call::setConferenceInfo(const std::string& msg)
639 : {
640 184 : ConfInfo newInfo;
641 184 : Json::Value json;
642 184 : std::string err;
643 184 : Json::CharReaderBuilder rbuilder;
644 184 : auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
645 184 : if (reader->parse(msg.data(), msg.data() + msg.size(), &json, &err)) {
646 184 : if (json.isObject()) {
647 : // new confInfo
648 184 : if (json.isMember("p")) {
649 742 : for (const auto& participantInfo : json["p"]) {
650 562 : ParticipantInfo pInfo;
651 562 : if (!participantInfo.isMember("uri"))
652 0 : continue;
653 562 : pInfo.fromJson(participantInfo);
654 562 : newInfo.emplace_back(pInfo);
655 562 : }
656 : }
657 184 : if (json.isMember("v")) {
658 180 : newInfo.v = json["v"].asInt();
659 180 : peerConfProtocol_ = newInfo.v;
660 : }
661 184 : if (json.isMember("w"))
662 180 : newInfo.w = json["w"].asInt();
663 184 : if (json.isMember("h"))
664 180 : newInfo.h = json["h"].asInt();
665 : } else {
666 : // old confInfo
667 0 : for (const auto& participantInfo : json) {
668 0 : ParticipantInfo pInfo;
669 0 : if (!participantInfo.isMember("uri"))
670 0 : continue;
671 0 : pInfo.fromJson(participantInfo);
672 0 : newInfo.emplace_back(pInfo);
673 0 : }
674 : }
675 : }
676 :
677 : {
678 184 : std::lock_guard lk(confInfoMutex_);
679 184 : if (not isConferenceParticipant()) {
680 : // confID_ empty -> participant set confInfo with the received one
681 184 : confInfo_ = std::move(newInfo);
682 :
683 : // Create sink for each participant
684 : #ifdef ENABLE_VIDEO
685 184 : createSinks(confInfo_);
686 : #endif
687 : // Inform client that layout has changed
688 184 : jami::emitSignal<libjami::CallSignal::OnConferenceInfosUpdated>(
689 368 : id_, confInfo_.toVectorMapStringString());
690 0 : } else if (auto conf = conf_.lock()) {
691 0 : conf->mergeConfInfo(newInfo, getPeerNumber());
692 0 : }
693 184 : }
694 184 : }
695 :
696 : void
697 6 : Call::sendConfOrder(const Json::Value& root)
698 : {
699 6 : std::map<std::string, std::string> messages;
700 6 : Json::StreamWriterBuilder wbuilder;
701 6 : wbuilder["commentStyle"] = "None";
702 6 : wbuilder["indentation"] = "";
703 6 : messages["application/confOrder+json"] = Json::writeString(wbuilder, root);
704 :
705 6 : auto w = getAccount();
706 6 : auto account = w.lock();
707 6 : if (account)
708 6 : sendTextMessage(messages, account->getFromUri());
709 6 : }
710 :
711 : void
712 189 : Call::sendConfInfo(const std::string& json)
713 : {
714 189 : std::map<std::string, std::string> messages;
715 186 : Json::StreamWriterBuilder wbuilder;
716 189 : wbuilder["commentStyle"] = "None";
717 187 : wbuilder["indentation"] = "";
718 187 : messages["application/confInfo+json"] = json;
719 :
720 189 : auto w = getAccount();
721 186 : auto account = w.lock();
722 187 : if (account)
723 185 : sendTextMessage(messages, account->getFromUri());
724 188 : }
725 :
726 : void
727 4 : Call::resetConfInfo()
728 : {
729 4 : sendConfInfo("{}");
730 4 : }
731 :
732 : } // namespace jami
|