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