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 447 : hangupCallsIf(Call::SubcallSet&& calls, int errcode, T pred)
51 : {
52 534 : for (auto& call : calls) {
53 87 : if (not pred(call.get()))
54 72 : continue;
55 30 : dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
56 : }
57 447 : }
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 375 : hangupCalls(Call::SubcallSet&& callptr_list, int errcode)
64 : {
65 390 : hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
66 375 : }
67 :
68 : //==============================================================================
69 :
70 371 : Call::Call(const std::shared_ptr<Account>& account, const std::string& id, Call::CallType type)
71 371 : : id_(id)
72 371 : , type_(type)
73 371 : , account_(account)
74 1113 : , timeoutTimer_(*Manager::instance().ioContext())
75 : {
76 371 : addStateListener([this](Call::CallState call_state, Call::ConnectionState cnx_state, int code) {
77 1937 : checkPendingIM();
78 1937 : runOnMainThread([callWkPtr = weak_from_this()] {
79 1937 : if (auto call = callWkPtr.lock())
80 1937 : call->checkAudio();
81 1937 : });
82 :
83 : // if call just started ringing, schedule call timeout
84 1937 : if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
85 101 : timeoutTimer_.expires_after(Manager::instance().getRingingTimeout());
86 101 : timeoutTimer_.async_wait([callWkPtr = weak_from_this()](const std::error_code& ec) {
87 101 : if (ec == asio::error::operation_aborted)
88 99 : 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 1937 : if (!isSubcall()) {
101 1363 : if (code == PJSIP_SC_TEMPORARILY_UNAVAILABLE) {
102 6 : reason_ = "no_device";
103 : }
104 1363 : if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
105 181 : duration_start_ = clock::now();
106 1182 : else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
107 204 : if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
108 184 : 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 184 : monitor();
113 204 : }
114 : }
115 : }
116 :
117 : // kill pending subcalls at disconnect
118 1937 : if (call_state == CallState::OVER)
119 371 : hangupCalls(safePopSubcalls(), 0);
120 :
121 1937 : return true;
122 : });
123 :
124 371 : time(×tamp_start_);
125 371 : }
126 :
127 371 : Call::~Call() {}
128 :
129 : void
130 386 : Call::removeCall(int code)
131 : {
132 386 : auto this_ = shared_from_this();
133 386 : Manager::instance().callFactory.removeCall(*this);
134 386 : setState(CallState::OVER, code);
135 386 : if (Recordable::isRecording())
136 0 : Recordable::stopRecording();
137 386 : if (auto account = account_.lock())
138 386 : account->detach(this_);
139 386 : parent_.reset();
140 386 : subcalls_.clear();
141 386 : }
142 :
143 : std::string
144 3017 : Call::getAccountId() const
145 : {
146 3017 : if (auto shared = account_.lock())
147 3017 : return shared->getAccountID();
148 0 : JAMI_ERROR("No account detected");
149 0 : return {};
150 : }
151 :
152 : Call::ConnectionState
153 7437 : Call::getConnectionState() const
154 : {
155 7437 : std::lock_guard lock(callMutex_);
156 7437 : return connectionState_;
157 7437 : }
158 :
159 : Call::CallState
160 8800 : Call::getState() const
161 : {
162 8800 : std::lock_guard lock(callMutex_);
163 8800 : return callState_;
164 8800 : }
165 :
166 : bool
167 752 : 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 752 : if (newState == CallState::OVER)
175 371 : return true;
176 :
177 381 : switch (callState_) {
178 360 : case CallState::INACTIVE:
179 360 : switch (newState) {
180 360 : case CallState::ACTIVE:
181 : case CallState::BUSY:
182 : case CallState::PEER_BUSY:
183 : case CallState::MERROR:
184 360 : return true;
185 0 : default: // INACTIVE, HOLD
186 0 : return false;
187 : }
188 :
189 17 : case CallState::ACTIVE:
190 17 : switch (newState) {
191 17 : case CallState::BUSY:
192 : case CallState::PEER_BUSY:
193 : case CallState::HOLD:
194 : case CallState::MERROR:
195 17 : 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 1 : default: // MERROR
218 1 : return false;
219 : }
220 : }
221 :
222 : bool
223 2141 : Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
224 : {
225 2141 : std::unique_lock<std::recursive_mutex> lock(callMutex_);
226 8564 : JAMI_LOG("[call:{}] state change {}/{}, cnx {}/{}, code {}",
227 : id_,
228 : (unsigned) callState_,
229 : (unsigned) call_state,
230 : (unsigned) connectionState_,
231 : (unsigned) cnx_state,
232 : code);
233 :
234 2141 : if (callState_ != call_state) {
235 752 : if (not validStateTransition(call_state)) {
236 4 : JAMI_ERROR("[call:{}] invalid call state transition from {} to {}",
237 : id_,
238 : (unsigned) callState_,
239 : (unsigned) call_state);
240 1 : return false;
241 : }
242 1389 : } else if (connectionState_ == cnx_state)
243 203 : return true; // no changes as no-op
244 :
245 : // Emit client state only if changed
246 1937 : auto old_client_state = getStateStr();
247 1937 : callState_ = call_state;
248 1937 : connectionState_ = cnx_state;
249 1937 : auto new_client_state = getStateStr();
250 :
251 4754 : for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
252 2817 : if ((*it)(callState_, connectionState_, code))
253 2734 : ++it;
254 : else
255 83 : it = stateChangedListeners_.erase(it);
256 : }
257 :
258 1937 : if (old_client_state != new_client_state) {
259 1590 : if (not parent_) {
260 4393 : JAMI_LOG("[call:{}] emit client call state change {}, code {}", id_, new_client_state, code);
261 1099 : lock.unlock();
262 1099 : emitSignal<libjami::CallSignal::StateChange>(getAccountId(), id_, new_client_state, code);
263 : }
264 : }
265 :
266 1937 : return true;
267 2141 : }
268 :
269 : bool
270 475 : Call::setState(CallState call_state, signed code)
271 : {
272 475 : std::lock_guard lock(callMutex_);
273 950 : return setState(call_state, connectionState_, code);
274 475 : }
275 :
276 : bool
277 1211 : Call::setState(ConnectionState cnx_state, signed code)
278 : {
279 1211 : std::lock_guard lock(callMutex_);
280 2422 : return setState(callState_, cnx_state, code);
281 1210 : }
282 :
283 : std::string
284 8232 : Call::getStateStr() const
285 : {
286 : using namespace libjami::Call;
287 :
288 8232 : switch (getState()) {
289 3618 : case CallState::ACTIVE:
290 3618 : switch (getConnectionState()) {
291 839 : case ConnectionState::PROGRESSING:
292 1678 : return StateEvent::CONNECTING;
293 :
294 835 : case ConnectionState::RINGING:
295 1670 : return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
296 :
297 647 : case ConnectionState::DISCONNECTED:
298 1294 : return StateEvent::HUNGUP;
299 :
300 1297 : case ConnectionState::CONNECTED:
301 : default:
302 2594 : return StateEvent::CURRENT;
303 : }
304 :
305 40 : case CallState::HOLD:
306 40 : if (getConnectionState() == ConnectionState::DISCONNECTED)
307 26 : return StateEvent::HUNGUP;
308 54 : return StateEvent::HOLD;
309 :
310 3 : case CallState::BUSY:
311 6 : return StateEvent::BUSY;
312 :
313 4 : case CallState::PEER_BUSY:
314 8 : return StateEvent::PEER_BUSY;
315 :
316 3225 : case CallState::INACTIVE:
317 3225 : switch (getConnectionState()) {
318 1157 : case ConnectionState::PROGRESSING:
319 2314 : return StateEvent::CONNECTING;
320 :
321 560 : case ConnectionState::RINGING:
322 1120 : return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
323 :
324 0 : case ConnectionState::CONNECTED:
325 0 : return StateEvent::CURRENT;
326 :
327 1508 : default:
328 3016 : return StateEvent::INACTIVE;
329 : }
330 :
331 960 : case CallState::OVER:
332 1920 : return StateEvent::OVER;
333 :
334 382 : case CallState::MERROR:
335 : default:
336 764 : 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 346 : Call::getDetails() const
349 : {
350 346 : auto conference = conf_.lock();
351 : return {
352 0 : {libjami::Call::Details::CALL_TYPE, std::to_string((unsigned) type_)},
353 346 : {libjami::Call::Details::PEER_NUMBER, peerNumber_},
354 346 : {libjami::Call::Details::DISPLAY_NAME, peerDisplayName_},
355 346 : {libjami::Call::Details::CALL_STATE, getStateStr()},
356 1374 : {libjami::Call::Details::CONF_ID, conference ? conference->getConfId() : ""},
357 346 : {libjami::Call::Details::TIMESTAMP_START, std::to_string(timestamp_start_)},
358 346 : {libjami::Call::Details::ACCOUNTID, getAccountId()},
359 346 : {libjami::Call::Details::TO_USERNAME, toUsername()},
360 692 : {libjami::Call::Details::AUDIO_MUTED, std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
361 692 : {libjami::Call::Details::VIDEO_MUTED, std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
362 692 : {libjami::Call::Details::AUDIO_ONLY, std::string(bool_to_str(not hasVideo()))},
363 4844 : };
364 2076 : }
365 :
366 : void
367 243 : Call::onTextMessage(std::map<std::string, std::string>&& messages)
368 : {
369 243 : auto it = messages.find("application/confInfo+json");
370 243 : if (it != messages.end()) {
371 237 : setConferenceInfo(it->second);
372 243 : 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 166 : Call::addSubCall(Call& subcall)
409 : {
410 166 : 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 166 : if (connectionState_ == ConnectionState::CONNECTED || connectionState_ == ConnectionState::DISCONNECTED
416 166 : || callState_ == CallState::OVER) {
417 0 : subcall.removeCall();
418 0 : return;
419 : }
420 :
421 166 : if (not subcalls_.emplace(getPtr(subcall)).second) {
422 0 : JAMI_ERROR("[call:{}] add twice subcall {}", getCallId(), subcall.getCallId());
423 0 : return;
424 : }
425 :
426 664 : JAMI_LOG("[call:{}] add subcall {}", getCallId(), subcall.getCallId());
427 166 : subcall.parent_ = getPtr(*this);
428 :
429 166 : for (const auto& msg : pendingOutMessages_)
430 0 : subcall.sendTextMessage(msg.first, msg.second);
431 :
432 166 : subcall.addStateListener(
433 332 : [sub = subcall.weak_from_this(),
434 : parent = weak_from_this()](Call::CallState new_state, Call::ConnectionState new_cstate, int code) {
435 574 : runOnMainThread([sub, parent, new_state, new_cstate, code]() {
436 574 : if (auto p = parent.lock()) {
437 544 : if (auto s = sub.lock()) {
438 467 : p->subcallStateChanged(*s, new_state, new_cstate, code);
439 544 : }
440 574 : }
441 574 : });
442 574 : return true;
443 : });
444 166 : }
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 467 : 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 467 : std::lock_guard lk {callMutex_};
460 467 : auto sit = subcalls_.find(getPtr(subcall));
461 467 : if (sit == subcalls_.end())
462 71 : return;
463 467 : }
464 :
465 : // We found a responding device: hangup all other subcalls and merge
466 396 : if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
467 288 : JAMI_LOG("[call:{}] subcall {} answered by peer", getCallId(), subcall.getCallId());
468 :
469 144 : hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
470 72 : merge(subcall);
471 72 : Manager::instance().peerAnsweredCall(*this);
472 72 : return;
473 : }
474 :
475 : // Hangup the call if any device hangup or send busy
476 324 : if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
477 164 : and new_cstate == ConnectionState::DISCONNECTED) {
478 16 : JAMI_WARNING("[call:{}] subcall {} hangup by peer", getCallId(), subcall.getCallId());
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 320 : if (new_state >= CallState::BUSY) {
488 79 : if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
489 0 : JAMI_WARNING("[call:{}] subcall {} busy", getCallId(), subcall.getCallId());
490 : else
491 316 : JAMI_WARNING("[call:{}] subcall {} failed", getCallId(), subcall.getCallId());
492 79 : std::lock_guard lk {callMutex_};
493 79 : subcalls_.erase(getPtr(subcall));
494 :
495 : // Parent call fails if last subcall is busy or failed
496 79 : if (subcalls_.empty()) {
497 2 : if (new_state == CallState::BUSY) {
498 0 : setState(CallState::BUSY, ConnectionState::DISCONNECTED, PJSIP_SC_BUSY_HERE);
499 2 : } 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 2 : setState(CallState::MERROR, ConnectionState::DISCONNECTED, PJSIP_SC_INTERNAL_SERVER_ERROR);
504 : }
505 2 : Manager::instance().callFailure(*this);
506 2 : removeCall(code);
507 : } else {
508 308 : JAMI_DEBUG("[call:{}] Subcalls remaining : {}", getCallId(), subcalls_.size());
509 : }
510 :
511 79 : return;
512 79 : }
513 :
514 : // Copy call/cnx states (forward only)
515 241 : if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
516 80 : setState(new_state);
517 : }
518 241 : if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
519 158 : and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
520 158 : 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 72 : Call::merge(Call& subcall)
528 : {
529 288 : JAMI_LOG("[call:{}] merge subcall {}", getCallId(), subcall.getCallId());
530 :
531 : // Merge data
532 72 : pendingInMessages_ = std::move(subcall.pendingInMessages_);
533 72 : if (peerNumber_.empty())
534 0 : peerNumber_ = std::move(subcall.peerNumber_);
535 72 : peerDisplayName_ = std::move(subcall.peerDisplayName_);
536 72 : setState(subcall.getState(), subcall.getConnectionState());
537 :
538 72 : std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
539 72 : runOnMainThread([subCallWeak] {
540 72 : if (auto subcall = subCallWeak.lock())
541 72 : subcall->removeCall();
542 72 : });
543 72 : }
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 1937 : Call::checkPendingIM()
550 : {
551 1937 : std::lock_guard lk {callMutex_};
552 :
553 1937 : auto state = getStateStr();
554 : // Let parent call handles IM after the merge
555 1937 : if (not parent_) {
556 1363 : if (state == libjami::Call::StateEvent::CURRENT) {
557 184 : for (const auto& msg : pendingInMessages_)
558 0 : Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), msg.first);
559 184 : pendingInMessages_.clear();
560 :
561 184 : std::weak_ptr<Call> callWkPtr = shared_from_this();
562 184 : runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
563 184 : if (auto call = callWkPtr.lock())
564 184 : for (const auto& msg : pending)
565 184 : call->sendTextMessage(msg.first, msg.second);
566 184 : });
567 184 : }
568 : }
569 1937 : }
570 :
571 : /// Handle tones for RINGING and BUSY calls
572 : ///
573 : void
574 1543 : Call::checkAudio()
575 : {
576 : using namespace libjami::Call;
577 :
578 1543 : auto state = getStateStr();
579 1543 : if (state == StateEvent::RINGING) {
580 178 : Manager::instance().peerRingingCall(*this);
581 1365 : } else if (state == StateEvent::BUSY) {
582 0 : Manager::instance().callBusy(*this);
583 : }
584 1543 : }
585 :
586 : // Helper to safely pop subcalls list
587 : Call::SubcallSet
588 447 : Call::safePopSubcalls()
589 : {
590 447 : std::lock_guard lk {callMutex_};
591 : // std::exchange is C++14
592 447 : auto old_value = std::move(subcalls_);
593 447 : subcalls_.clear();
594 894 : return old_value;
595 447 : }
596 :
597 : void
598 237 : Call::setConferenceInfo(const std::string& msg)
599 : {
600 237 : ConfInfo newInfo;
601 237 : Json::Value json;
602 237 : if (json::parse(msg, json)) {
603 237 : if (json.isObject()) {
604 : // new confInfo
605 237 : if (json.isMember("p")) {
606 1517 : for (const auto& participantInfo : json["p"]) {
607 644 : ParticipantInfo pInfo;
608 644 : if (!participantInfo.isMember("uri"))
609 0 : continue;
610 644 : pInfo.fromJson(participantInfo);
611 644 : newInfo.emplace_back(pInfo);
612 644 : }
613 : }
614 237 : if (json.isMember("v")) {
615 234 : newInfo.v = json["v"].asInt();
616 234 : peerConfProtocol_ = newInfo.v;
617 : }
618 237 : if (json.isMember("w"))
619 234 : newInfo.w = json["w"].asInt();
620 237 : if (json.isMember("h"))
621 234 : 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 237 : std::lock_guard lk(confInfoMutex_);
636 237 : if (not isConferenceParticipant()) {
637 : // confID_ empty -> participant set confInfo with the received one
638 237 : confInfo_ = std::move(newInfo);
639 :
640 : // Create sink for each participant
641 : #ifdef ENABLE_VIDEO
642 237 : createSinks(confInfo_);
643 : #endif
644 : // Inform client that layout has changed
645 237 : 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 237 : }
650 237 : }
651 :
652 : void
653 6 : Call::sendConfOrder(const Json::Value& root)
654 : {
655 6 : std::map<std::string, std::string> messages;
656 18 : 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 241 : Call::sendConfInfo(const std::string& json)
666 : {
667 241 : std::map<std::string, std::string> messages;
668 239 : messages["application/confInfo+json"] = json;
669 :
670 241 : auto w = getAccount();
671 241 : auto account = w.lock();
672 240 : if (account)
673 240 : sendTextMessage(messages, account->getFromUri());
674 241 : }
675 :
676 : void
677 4 : Call::resetConfInfo()
678 : {
679 4 : sendConfInfo("{}");
680 4 : }
681 :
682 : } // namespace jami
|