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