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