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