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