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