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 157 : hangupCallsIf(Call::SubcallSet&& calls, int errcode, T pred)
51 : {
52 185 : for (auto& call : calls) {
53 28 : if (not pred(call.get()))
54 14 : continue;
55 28 : dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
56 : }
57 157 : }
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 143 : hangupCalls(Call::SubcallSet&& callptr_list, int errcode)
64 : {
65 157 : hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
66 143 : }
67 :
68 : //==============================================================================
69 :
70 139 : Call::Call(const std::shared_ptr<Account>& account, const std::string& id, Call::CallType type)
71 139 : : id_(id)
72 139 : , type_(type)
73 139 : , account_(account)
74 417 : , timeoutTimer_(*Manager::instance().ioContext())
75 : {
76 139 : addStateListener([this](Call::CallState call_state, Call::ConnectionState cnx_state, int code) {
77 719 : checkPendingIM();
78 719 : runOnMainThread([callWkPtr = weak_from_this()] {
79 719 : if (auto call = callWkPtr.lock())
80 719 : call->checkAudio();
81 719 : });
82 :
83 : // if call just started ringing, schedule call timeout
84 719 : if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
85 43 : timeoutTimer_.expires_after(Manager::instance().getRingingTimeout());
86 43 : timeoutTimer_.async_wait([callWkPtr = weak_from_this()](const std::error_code& ec) {
87 43 : if (ec == asio::error::operation_aborted)
88 41 : 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 719 : if (!isSubcall()) {
101 552 : if (code == PJSIP_SC_TEMPORARILY_UNAVAILABLE) {
102 6 : reason_ = "no_device";
103 : }
104 552 : if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
105 65 : duration_start_ = clock::now();
106 487 : else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
107 178 : if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
108 69 : if (toUsername().find('/') == std::string::npos && getCallType() == CallType::OUTGOING) {
109 36 : if (auto* cm = jamiAccount->convModule(true))
110 36 : cm->addCallHistoryMessage(getPeerNumber(), getCallDuration().count(), reason_);
111 : }
112 69 : monitor();
113 89 : }
114 : }
115 : }
116 :
117 : // kill pending subcalls at disconnect
118 719 : if (call_state == CallState::OVER)
119 139 : hangupCalls(safePopSubcalls(), 0);
120 :
121 719 : return true;
122 : });
123 :
124 139 : time(×tamp_start_);
125 139 : }
126 :
127 139 : Call::~Call() {}
128 :
129 : void
130 149 : Call::removeCall(int code)
131 : {
132 149 : auto this_ = shared_from_this();
133 149 : Manager::instance().callFactory.removeCall(*this);
134 149 : setState(CallState::OVER, code);
135 149 : if (Recordable::isRecording())
136 0 : Recordable::stopRecording();
137 149 : if (auto account = account_.lock())
138 149 : account->detach(this_);
139 149 : parent_.reset();
140 149 : subcalls_.clear();
141 149 : }
142 :
143 : std::string
144 1083 : Call::getAccountId() const
145 : {
146 1083 : if (auto shared = account_.lock())
147 1083 : return shared->getAccountID();
148 2 : JAMI_ERR("No account detected");
149 2 : return {};
150 : }
151 :
152 : Call::ConnectionState
153 2678 : Call::getConnectionState() const
154 : {
155 2678 : std::lock_guard lock(callMutex_);
156 2677 : return connectionState_;
157 2678 : }
158 :
159 : Call::CallState
160 3196 : Call::getState() const
161 : {
162 3196 : std::lock_guard lock(callMutex_);
163 3196 : return callState_;
164 3196 : }
165 :
166 : bool
167 281 : 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 281 : if (newState == CallState::OVER)
175 139 : return true;
176 :
177 142 : switch (callState_) {
178 127 : case CallState::INACTIVE:
179 127 : switch (newState) {
180 127 : case CallState::ACTIVE:
181 : case CallState::BUSY:
182 : case CallState::PEER_BUSY:
183 : case CallState::MERROR:
184 127 : return true;
185 0 : default: // INACTIVE, HOLD
186 0 : return false;
187 : }
188 :
189 9 : case CallState::ACTIVE:
190 9 : switch (newState) {
191 9 : case CallState::BUSY:
192 : case CallState::PEER_BUSY:
193 : case CallState::HOLD:
194 : case CallState::MERROR:
195 9 : 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 3 : default: // MERROR
218 3 : return false;
219 : }
220 : }
221 :
222 : bool
223 830 : Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
224 : {
225 830 : std::unique_lock<std::recursive_mutex> lock(callMutex_);
226 830 : 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 830 : if (callState_ != call_state) {
235 281 : if (not validStateTransition(call_state)) {
236 3 : JAMI_ERR("[call:%s] invalid call state transition from %u to %u",
237 : id_.c_str(),
238 : (unsigned) callState_,
239 : (unsigned) call_state);
240 3 : return false;
241 : }
242 549 : } else if (connectionState_ == cnx_state)
243 108 : return true; // no changes as no-op
244 :
245 : // Emit client state only if changed
246 719 : auto old_client_state = getStateStr();
247 719 : callState_ = call_state;
248 719 : connectionState_ = cnx_state;
249 719 : auto new_client_state = getStateStr();
250 :
251 1738 : for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
252 1019 : if ((*it)(callState_, connectionState_, code))
253 994 : ++it;
254 : else
255 25 : it = stateChangedListeners_.erase(it);
256 : }
257 :
258 719 : if (old_client_state != new_client_state) {
259 602 : if (not parent_) {
260 459 : JAMI_DBG("[call:%s] emit client call state change %s, code %d", id_.c_str(), new_client_state.c_str(), code);
261 459 : lock.unlock();
262 459 : emitSignal<libjami::CallSignal::StateChange>(getAccountId(), id_, new_client_state, code);
263 : }
264 : }
265 :
266 719 : return true;
267 830 : }
268 :
269 : bool
270 179 : Call::setState(CallState call_state, signed code)
271 : {
272 179 : std::lock_guard lock(callMutex_);
273 358 : return setState(call_state, connectionState_, code);
274 179 : }
275 :
276 : bool
277 492 : Call::setState(ConnectionState cnx_state, signed code)
278 : {
279 492 : std::lock_guard lock(callMutex_);
280 984 : return setState(callState_, cnx_state, code);
281 492 : }
282 :
283 : std::string
284 2976 : Call::getStateStr() const
285 : {
286 : using namespace libjami::Call;
287 :
288 2976 : switch (getState()) {
289 1200 : case CallState::ACTIVE:
290 1200 : switch (getConnectionState()) {
291 313 : case ConnectionState::PROGRESSING:
292 313 : return StateEvent::CONNECTING;
293 :
294 281 : case ConnectionState::RINGING:
295 281 : return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
296 :
297 254 : case ConnectionState::DISCONNECTED:
298 254 : return StateEvent::HUNGUP;
299 :
300 352 : case ConnectionState::CONNECTED:
301 : default:
302 352 : return StateEvent::CURRENT;
303 : }
304 :
305 31 : case CallState::HOLD:
306 31 : if (getConnectionState() == ConnectionState::DISCONNECTED)
307 9 : return StateEvent::HUNGUP;
308 22 : 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 1259 : case CallState::INACTIVE:
317 1259 : switch (getConnectionState()) {
318 401 : case ConnectionState::PROGRESSING:
319 401 : return StateEvent::CONNECTING;
320 :
321 270 : case ConnectionState::RINGING:
322 270 : return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
323 :
324 0 : case ConnectionState::CONNECTED:
325 0 : return StateEvent::CURRENT;
326 :
327 588 : default:
328 588 : return StateEvent::INACTIVE;
329 : }
330 :
331 360 : case CallState::OVER:
332 360 : return StateEvent::OVER;
333 :
334 119 : case CallState::MERROR:
335 : default:
336 119 : return StateEvent::FAILURE;
337 : }
338 : }
339 :
340 : bool
341 0 : Call::toggleRecording()
342 : {
343 0 : const bool startRecording = Recordable::toggleRecording();
344 0 : return startRecording;
345 : }
346 :
347 : std::map<std::string, std::string>
348 69 : Call::getDetails() const
349 : {
350 69 : auto conference = conf_.lock();
351 : return {
352 69 : {libjami::Call::Details::CALL_TYPE, std::to_string((unsigned) type_)},
353 69 : {libjami::Call::Details::PEER_NUMBER, peerNumber_},
354 69 : {libjami::Call::Details::DISPLAY_NAME, peerDisplayName_},
355 138 : {libjami::Call::Details::CALL_STATE, getStateStr()},
356 138 : {libjami::Call::Details::CONF_ID, conference ? conference->getConfId() : ""},
357 138 : {libjami::Call::Details::TIMESTAMP_START, std::to_string(timestamp_start_)},
358 138 : {libjami::Call::Details::ACCOUNTID, getAccountId()},
359 69 : {libjami::Call::Details::TO_USERNAME, toUsername()},
360 138 : {libjami::Call::Details::AUDIO_MUTED, std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
361 138 : {libjami::Call::Details::VIDEO_MUTED, std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
362 138 : {libjami::Call::Details::AUDIO_ONLY, std::string(bool_to_str(not hasVideo()))},
363 1449 : };
364 69 : }
365 :
366 : void
367 5 : Call::onTextMessage(std::map<std::string, std::string>&& messages)
368 : {
369 5 : auto it = messages.find("application/confInfo+json");
370 5 : if (it != messages.end()) {
371 5 : setConferenceInfo(it->second);
372 5 : return;
373 : }
374 :
375 0 : it = messages.find("application/confOrder+json");
376 0 : if (it != messages.end()) {
377 0 : if (auto conf = conf_.lock())
378 0 : conf->onConfOrder(getCallId(), it->second);
379 0 : 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 44 : Call::peerHungup()
401 : {
402 44 : const auto state = getState();
403 44 : const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
404 44 : setState(ConnectionState::DISCONNECTED, aborted ? PJSIP_SC_REQUEST_TERMINATED : PJSIP_SC_DECLINE);
405 44 : }
406 :
407 : void
408 50 : Call::addSubCall(Call& subcall)
409 : {
410 50 : 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 50 : if (connectionState_ == ConnectionState::CONNECTED || connectionState_ == ConnectionState::DISCONNECTED
416 50 : || callState_ == CallState::OVER) {
417 0 : subcall.removeCall();
418 0 : return;
419 : }
420 :
421 50 : 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 50 : JAMI_DBG("[call:%s] add subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
427 50 : subcall.parent_ = getPtr(*this);
428 :
429 50 : for (const auto& msg : pendingOutMessages_)
430 0 : subcall.sendTextMessage(msg.first, msg.second);
431 :
432 50 : subcall.addStateListener(
433 167 : [sub = subcall.weak_from_this(),
434 : parent = weak_from_this()](Call::CallState new_state, Call::ConnectionState new_cstate, int code) {
435 167 : runOnMainThread([sub, parent, new_state, new_cstate, code]() {
436 167 : if (auto p = parent.lock()) {
437 138 : if (auto s = sub.lock()) {
438 123 : p->subcallStateChanged(*s, new_state, new_cstate, code);
439 138 : }
440 167 : }
441 167 : });
442 167 : return true;
443 : });
444 50 : }
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 123 : 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 123 : std::lock_guard lk {callMutex_};
460 123 : auto sit = subcalls_.find(getPtr(subcall));
461 123 : if (sit == subcalls_.end())
462 15 : return;
463 123 : }
464 :
465 : // We found a responding device: hangup all other subcalls and merge
466 108 : if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
467 14 : JAMI_DBG("[call:%s] subcall %s answered by peer", getCallId().c_str(), subcall.getCallId().c_str());
468 :
469 28 : hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
470 14 : merge(subcall);
471 14 : Manager::instance().peerAnsweredCall(*this);
472 14 : return;
473 : }
474 :
475 : // Hangup the call if any device hangup or send busy
476 94 : if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
477 47 : 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 90 : if (new_state >= CallState::BUSY) {
488 23 : 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 23 : JAMI_WARN("[call:%s] subcall %s failed", getCallId().c_str(), subcall.getCallId().c_str());
492 23 : std::lock_guard lk {callMutex_};
493 23 : subcalls_.erase(getPtr(subcall));
494 :
495 : // Parent call fails if last subcall is busy or failed
496 23 : 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 80 : JAMI_DEBUG("[call:{}] Subcalls remaining : {}", getCallId(), subcalls_.size());
509 : }
510 :
511 23 : return;
512 23 : }
513 :
514 : // Copy call/cnx states (forward only)
515 67 : if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
516 22 : setState(new_state);
517 : }
518 67 : if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
519 41 : and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
520 41 : 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 14 : Call::merge(Call& subcall)
528 : {
529 14 : JAMI_DBG("[call:%s] merge subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
530 :
531 : // Merge data
532 14 : pendingInMessages_ = std::move(subcall.pendingInMessages_);
533 14 : if (peerNumber_.empty())
534 0 : peerNumber_ = std::move(subcall.peerNumber_);
535 14 : peerDisplayName_ = std::move(subcall.peerDisplayName_);
536 14 : setState(subcall.getState(), subcall.getConnectionState());
537 :
538 14 : std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
539 14 : runOnMainThread([subCallWeak] {
540 14 : if (auto subcall = subCallWeak.lock())
541 14 : subcall->removeCall();
542 14 : });
543 14 : }
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 719 : Call::checkPendingIM()
550 : {
551 719 : std::lock_guard lk {callMutex_};
552 :
553 719 : auto state = getStateStr();
554 : // Let parent call handles IM after the merge
555 719 : if (not parent_) {
556 552 : if (state == libjami::Call::StateEvent::CURRENT) {
557 68 : for (const auto& msg : pendingInMessages_)
558 0 : Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), msg.first);
559 68 : pendingInMessages_.clear();
560 :
561 68 : std::weak_ptr<Call> callWkPtr = shared_from_this();
562 68 : runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
563 68 : if (auto call = callWkPtr.lock())
564 68 : for (const auto& msg : pending)
565 68 : call->sendTextMessage(msg.first, msg.second);
566 68 : });
567 68 : }
568 : }
569 719 : }
570 :
571 : /// Handle tones for RINGING and BUSY calls
572 : ///
573 : void
574 564 : Call::checkAudio()
575 : {
576 : using namespace libjami::Call;
577 :
578 564 : auto state = getStateStr();
579 564 : if (state == StateEvent::RINGING) {
580 62 : Manager::instance().peerRingingCall(*this);
581 502 : } else if (state == StateEvent::BUSY) {
582 0 : Manager::instance().callBusy(*this);
583 : }
584 564 : }
585 :
586 : // Helper to safely pop subcalls list
587 : Call::SubcallSet
588 157 : Call::safePopSubcalls()
589 : {
590 157 : std::lock_guard lk {callMutex_};
591 : // std::exchange is C++14
592 157 : auto old_value = std::move(subcalls_);
593 157 : subcalls_.clear();
594 314 : return old_value;
595 157 : }
596 :
597 : void
598 5 : Call::setConferenceInfo(const std::string& msg)
599 : {
600 5 : ConfInfo newInfo;
601 5 : Json::Value json;
602 5 : if (json::parse(msg, json)) {
603 5 : if (json.isObject()) {
604 : // new confInfo
605 5 : if (json.isMember("p")) {
606 5 : for (const auto& participantInfo : json["p"]) {
607 3 : ParticipantInfo pInfo;
608 3 : if (!participantInfo.isMember("uri"))
609 0 : continue;
610 3 : pInfo.fromJson(participantInfo);
611 3 : newInfo.emplace_back(pInfo);
612 3 : }
613 : }
614 5 : if (json.isMember("v")) {
615 5 : newInfo.v = json["v"].asInt();
616 5 : peerConfProtocol_ = newInfo.v;
617 : }
618 5 : if (json.isMember("w"))
619 5 : newInfo.w = json["w"].asInt();
620 5 : if (json.isMember("h"))
621 5 : 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 5 : std::lock_guard lk(confInfoMutex_);
636 5 : if (not isConferenceParticipant()) {
637 : // confID_ empty -> participant set confInfo with the received one
638 5 : confInfo_ = std::move(newInfo);
639 :
640 : // Create sink for each participant
641 : #ifdef ENABLE_VIDEO
642 5 : createSinks(confInfo_);
643 : #endif
644 : // Inform client that layout has changed
645 5 : 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 5 : }
650 5 : }
651 :
652 : void
653 0 : Call::sendConfOrder(const Json::Value& root)
654 : {
655 0 : std::map<std::string, std::string> messages;
656 0 : messages["application/confOrder+json"] = json::toString(root);
657 :
658 0 : auto w = getAccount();
659 0 : auto account = w.lock();
660 0 : if (account)
661 0 : sendTextMessage(messages, account->getFromUri());
662 0 : }
663 :
664 : void
665 5 : Call::sendConfInfo(const std::string& json)
666 : {
667 5 : std::map<std::string, std::string> messages;
668 5 : messages["application/confInfo+json"] = json;
669 :
670 5 : auto w = getAccount();
671 5 : auto account = w.lock();
672 5 : if (account)
673 5 : sendTextMessage(messages, account->getFromUri());
674 5 : }
675 :
676 : void
677 0 : Call::resetConfInfo()
678 : {
679 0 : sendConfInfo("{}");
680 0 : }
681 :
682 : } // namespace jami
|