Ring Daemon 16.0.0
Loading...
Searching...
No Matches
call.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "call.h"
19#include "account.h"
20#include "jamidht/jamiaccount.h"
21#include "manager.h"
22#ifdef ENABLE_PLUGIN
24#include "plugin/streamdata.h"
25#endif
27#include "jami/call_const.h"
28#include "client/ring_signal.h"
30#include "map_utils.h"
31#include "call_factory.h"
32#include "string_utils.h"
33#include "enumclass_utils.h"
34
35#include "errno.h"
36#include "json_utils.h"
37
38#include <dhtnet/ip_utils.h>
39#include <opendht/thread_pool.h>
40
41#include <stdexcept>
42#include <system_error>
43#include <algorithm>
44#include <functional>
45#include <utility>
46
47namespace jami {
48
55template<typename T>
56inline void
58{
59 for (auto& call : calls) {
60 if (not pred(call.get()))
61 continue;
62 dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
63 }
64}
65
69inline void
71{
72 hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
73}
74
75//==============================================================================
76
77Call::Call(const std::shared_ptr<Account>& account,
78 const std::string& id,
79 Call::CallType type,
80 const std::map<std::string, std::string>& details)
81 : id_(id)
82 , type_(type)
83 , account_(account)
84{
87 int code) {
88 checkPendingIM();
90 if (auto call = callWkPtr.lock())
91 call->checkAudio();
92 });
93
94 // if call just started ringing, schedule call timeout
96 auto timeout = Manager::instance().getRingingTimeout();
97 JAMI_DBG("Scheduling call timeout in %d seconds", timeout);
98
100 [callWkPtr = weak()] {
101 if (auto callShPtr = callWkPtr.lock()) {
102 if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
103 JAMI_DBG(
104 "Call %s is still ringing after timeout, setting state to BUSY",
105 callShPtr->getCallId().c_str());
106 callShPtr->hangup(PJSIP_SC_BUSY_HERE);
107 Manager::instance().callFailure(*callShPtr);
108 }
109 }
110 },
111 std::chrono::seconds(timeout));
112 }
113
114 if (!isSubcall()) {
115 if (code == static_cast<int>(std::errc::no_such_device_or_address)) {
116 reason_ = "no_device";
117 }
118 if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
119 duration_start_ = clock::now();
121 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
122 if (toUsername().find('/') == std::string::npos && getCallType() == CallType::OUTGOING) {
123 if (auto cm = jamiAccount->convModule(true))
124 cm->addCallHistoryMessage(getPeerNumber(), getCallDuration().count(), reason_);
125 }
126 monitor();
127 }
128 }
129 }
130
131 // kill pending subcalls at disconnect
133 hangupCalls(safePopSubcalls(), 0);
134
135 return true;
136 });
137
138 time(&timestamp_start_);
139}
140
141Call::~Call() {}
142
143void
144Call::removeCall()
145{
146 auto this_ = shared_from_this();
147 Manager::instance().callFactory.removeCall(*this);
148 setState(CallState::OVER);
149 if (Recordable::isRecording())
150 Recordable::stopRecording();
151 if (auto account = account_.lock())
152 account->detach(this_);
153 parent_.reset();
154 subcalls_.clear();
155}
156
157std::string
158Call::getAccountId() const
159{
160 if (auto shared = account_.lock())
161 return shared->getAccountID();
162 JAMI_ERR("No account detected");
163 return {};
164}
165
167Call::getConnectionState() const
168{
169 std::lock_guard lock(callMutex_);
170 return connectionState_;
171}
172
174Call::getState() const
175{
176 std::lock_guard lock(callMutex_);
177 return callState_;
178}
179
180bool
181Call::validStateTransition(CallState newState)
182{
183 // Notice to developper:
184 // - list only permitted transition (return true)
185 // - let non permitted ones as default case (return false)
186
187 // always permited
188 if (newState == CallState::OVER)
189 return true;
190
191 switch (callState_) {
192 case CallState::INACTIVE:
193 switch (newState) {
194 case CallState::ACTIVE:
195 case CallState::BUSY:
196 case CallState::PEER_BUSY:
197 case CallState::MERROR:
198 return true;
199 default: // INACTIVE, HOLD
200 return false;
201 }
202
203 case CallState::ACTIVE:
204 switch (newState) {
205 case CallState::BUSY:
206 case CallState::PEER_BUSY:
207 case CallState::HOLD:
208 case CallState::MERROR:
209 return true;
210 default: // INACTIVE, ACTIVE
211 return false;
212 }
213
214 case CallState::HOLD:
215 switch (newState) {
216 case CallState::ACTIVE:
217 case CallState::MERROR:
218 return true;
219 default: // INACTIVE, HOLD, BUSY, PEER_BUSY, MERROR
220 return false;
221 }
222
223 case CallState::BUSY:
224 switch (newState) {
225 case CallState::MERROR:
226 return true;
227 default: // INACTIVE, ACTIVE, HOLD, BUSY, PEER_BUSY
228 return false;
229 }
230
231 default: // MERROR
232 return false;
233 }
234}
235
236bool
237Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
238{
239 std::unique_lock<std::recursive_mutex> lock(callMutex_);
240 JAMI_DBG("[call:%s] state change %u/%u, cnx %u/%u, code %d",
241 id_.c_str(),
242 (unsigned) callState_,
243 (unsigned) call_state,
244 (unsigned) connectionState_,
245 (unsigned) cnx_state,
246 code);
247
248 if (callState_ != call_state) {
249 if (not validStateTransition(call_state)) {
250 JAMI_ERR("[call:%s] invalid call state transition from %u to %u",
251 id_.c_str(),
252 (unsigned) callState_,
253 (unsigned) call_state);
254 return false;
255 }
256 } else if (connectionState_ == cnx_state)
257 return true; // no changes as no-op
258
259 // Emit client state only if changed
260 auto old_client_state = getStateStr();
261 callState_ = call_state;
262 connectionState_ = cnx_state;
263 auto new_client_state = getStateStr();
264
265 for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
266 if ((*it)(callState_, connectionState_, code))
267 ++it;
268 else
269 it = stateChangedListeners_.erase(it);
270 }
271
272 if (old_client_state != new_client_state) {
273 if (not parent_) {
274 JAMI_DBG("[call:%s] emit client call state change %s, code %d",
275 id_.c_str(),
276 new_client_state.c_str(),
277 code);
278 lock.unlock();
279 emitSignal<libjami::CallSignal::StateChange>(getAccountId(),
280 id_,
281 new_client_state,
282 code);
283 }
284 }
285
286 return true;
287}
288
289bool
290Call::setState(CallState call_state, signed code)
291{
292 std::lock_guard lock(callMutex_);
293 return setState(call_state, connectionState_, code);
294}
295
296bool
297Call::setState(ConnectionState cnx_state, signed code)
298{
299 std::lock_guard lock(callMutex_);
300 return setState(callState_, cnx_state, code);
301}
302
303std::string
304Call::getStateStr() const
305{
306 using namespace libjami::Call;
307
308 switch (getState()) {
309 case CallState::ACTIVE:
310 switch (getConnectionState()) {
311 case ConnectionState::PROGRESSING:
312 return StateEvent::CONNECTING;
313
314 case ConnectionState::RINGING:
315 return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
316
317 case ConnectionState::DISCONNECTED:
318 return StateEvent::HUNGUP;
319
320 case ConnectionState::CONNECTED:
321 default:
322 return StateEvent::CURRENT;
323 }
324
325 case CallState::HOLD:
326 if (getConnectionState() == ConnectionState::DISCONNECTED)
327 return StateEvent::HUNGUP;
328 return StateEvent::HOLD;
329
330 case CallState::BUSY:
331 return StateEvent::BUSY;
332
333 case CallState::PEER_BUSY:
334 return StateEvent::PEER_BUSY;
335
336 case CallState::INACTIVE:
337 switch (getConnectionState()) {
338 case ConnectionState::PROGRESSING:
339 return StateEvent::CONNECTING;
340
341 case ConnectionState::RINGING:
342 return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
343
344 case ConnectionState::CONNECTED:
345 return StateEvent::CURRENT;
346
347 default:
348 return StateEvent::INACTIVE;
349 }
350
351 case CallState::OVER:
352 return StateEvent::OVER;
353
354 case CallState::MERROR:
355 default:
356 return StateEvent::FAILURE;
357 }
358}
359
360bool
361Call::toggleRecording()
362{
363 const bool startRecording = Recordable::toggleRecording();
364 return startRecording;
365}
366
367std::map<std::string, std::string>
368Call::getDetails() const
369{
370 auto conference = conf_.lock();
371 return {
372 {libjami::Call::Details::CALL_TYPE, std::to_string((unsigned) type_)},
374 {libjami::Call::Details::DISPLAY_NAME, peerDisplayName_},
375 {libjami::Call::Details::CALL_STATE, getStateStr()},
376 {libjami::Call::Details::CONF_ID, conference ? conference->getConfId() : ""},
377 {libjami::Call::Details::TIMESTAMP_START, std::to_string(timestamp_start_)},
378 {libjami::Call::Details::ACCOUNTID, getAccountId()},
381 std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
383 std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
384 {libjami::Call::Details::AUDIO_ONLY, std::string(bool_to_str(not hasVideo()))},
385 };
386}
387
388void
389Call::onTextMessage(std::map<std::string, std::string>&& messages)
390{
391 auto it = messages.find("application/confInfo+json");
392 if (it != messages.end()) {
393 setConferenceInfo(it->second);
394 return;
395 }
396
397 it = messages.find("application/confOrder+json");
398 if (it != messages.end()) {
399 if (auto conf = conf_.lock())
400 conf->onConfOrder(getCallId(), it->second);
401 return;
402 }
403
404 {
405 std::lock_guard lk {callMutex_};
406 if (parent_) {
407 pendingInMessages_.emplace_back(std::move(messages), "");
408 return;
409 }
410 }
411#ifdef ENABLE_PLUGIN
412 auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
413 if (pluginChatManager.hasHandlers()) {
414 pluginChatManager.publishMessage(
415 std::make_shared<JamiMessage>(getAccountId(), getPeerNumber(), true, messages, false));
416 }
417#endif
418 Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), messages);
419}
420
421void
422Call::peerHungup()
423{
424 const auto state = getState();
425 const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
426 setState(ConnectionState::DISCONNECTED, aborted ? ECONNABORTED : ECONNREFUSED);
427}
428
429void
430Call::addSubCall(Call& subcall)
431{
432 std::lock_guard lk {callMutex_};
433
434 // Add subCall only if call is not connected or terminated
435 // Because we only want to addSubCall if the peer didn't answer
436 // So till it's <= RINGING
437 if (connectionState_ == ConnectionState::CONNECTED
438 || connectionState_ == ConnectionState::DISCONNECTED || callState_ == CallState::OVER) {
439 subcall.removeCall();
440 return;
441 }
442
443 if (not subcalls_.emplace(getPtr(subcall)).second) {
444 JAMI_ERR("[call:%s] add twice subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
445 return;
446 }
447
448 JAMI_DBG("[call:%s] add subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
449 subcall.parent_ = getPtr(*this);
450
451 for (const auto& msg : pendingOutMessages_)
452 subcall.sendTextMessage(msg.first, msg.second);
453
454 subcall.addStateListener(
455 [sub = subcall.weak(), parent = weak()](Call::CallState new_state,
456 Call::ConnectionState new_cstate,
457 int /* code */) {
458 runOnMainThread([sub, parent, new_state, new_cstate]() {
459 if (auto p = parent.lock()) {
460 if (auto s = sub.lock()) {
461 p->subcallStateChanged(*s, new_state, new_cstate);
462 }
463 }
464 });
465 return true;
466 });
467}
468
474void
475Call::subcallStateChanged(Call& subcall, Call::CallState new_state, Call::ConnectionState new_cstate)
476{
477 {
478 // This condition happens when a subcall hangups/fails after removed from parent's list.
479 // This is normal to keep parent_ != nullptr on the subcall, as it's the way to flag it
480 // as an subcall and not a master call.
481 // XXX: having a way to unsubscribe the state listener could be better than such test
482 std::lock_guard lk {callMutex_};
483 auto sit = subcalls_.find(getPtr(subcall));
484 if (sit == subcalls_.end())
485 return;
486 }
487
488 // We found a responding device: hangup all other subcalls and merge
489 if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
490 JAMI_DBG("[call:%s] subcall %s answered by peer",
491 getCallId().c_str(),
492 subcall.getCallId().c_str());
493
494 hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
495 merge(subcall);
496 Manager::instance().peerAnsweredCall(*this);
497 return;
498 }
499
500 // Hangup the call if any device hangup or send busy
501 if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
502 and new_cstate == ConnectionState::DISCONNECTED) {
503 JAMI_WARN("[call:%s] subcall %s hangup by peer",
504 getCallId().c_str(),
505 subcall.getCallId().c_str());
506 reason_ = new_state == CallState::ACTIVE ? "declined" : "busy";
507 hangupCalls(safePopSubcalls(), 0);
508 Manager::instance().peerHungupCall(*this);
509 removeCall();
510 return;
511 }
512
513 // Subcall is busy or failed
514 if (new_state >= CallState::BUSY) {
515 if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
516 JAMI_WARN("[call:%s] subcall %s busy", getCallId().c_str(), subcall.getCallId().c_str());
517 else
518 JAMI_WARN("[call:%s] subcall %s failed",
519 getCallId().c_str(),
520 subcall.getCallId().c_str());
521 std::lock_guard lk {callMutex_};
522 subcalls_.erase(getPtr(subcall));
523
524 // Parent call fails if last subcall is busy or failed
525 if (subcalls_.empty()) {
526 if (new_state == CallState::BUSY) {
527 setState(CallState::BUSY,
528 ConnectionState::DISCONNECTED,
529 static_cast<int>(std::errc::device_or_resource_busy));
530 } else if (new_state == CallState::PEER_BUSY) {
531 setState(CallState::PEER_BUSY,
532 ConnectionState::DISCONNECTED,
533 static_cast<int>(std::errc::device_or_resource_busy));
534 } else {
535 // XXX: first idea was to use std::errc::host_unreachable, but it's not available on
536 // some platforms like mingw.
537 setState(CallState::MERROR,
538 ConnectionState::DISCONNECTED,
539 static_cast<int>(std::errc::io_error));
540 }
541 removeCall();
542 } else {
543 JAMI_DBG("[call:%s] remains %zu subcall(s)", getCallId().c_str(), subcalls_.size());
544 }
545
546 return;
547 }
548
549 // Copy call/cnx states (forward only)
550 if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
551 setState(new_state);
552 }
553 if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
554 and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
555 setState(new_cstate);
556 }
557}
558
561void
562Call::merge(Call& subcall)
563{
564 JAMI_DBG("[call:%s] merge subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
565
566 // Merge data
567 pendingInMessages_ = std::move(subcall.pendingInMessages_);
568 if (peerNumber_.empty())
569 peerNumber_ = std::move(subcall.peerNumber_);
570 peerDisplayName_ = std::move(subcall.peerDisplayName_);
571 setState(subcall.getState(), subcall.getConnectionState());
572
573 std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
574 runOnMainThread([subCallWeak] {
575 if (auto subcall = subCallWeak.lock())
576 subcall->removeCall();
577 });
578}
579
583void
584Call::checkPendingIM()
585{
586 std::lock_guard lk {callMutex_};
587
588 auto state = getStateStr();
589 // Let parent call handles IM after the merge
590 if (not parent_) {
592 for (const auto& msg : pendingInMessages_)
593 Manager::instance().incomingMessage(getAccountId(),
594 getCallId(),
595 getPeerNumber(),
596 msg.first);
597 pendingInMessages_.clear();
598
599 std::weak_ptr<Call> callWkPtr = shared_from_this();
600 runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
601 if (auto call = callWkPtr.lock())
602 for (const auto& msg : pending)
603 call->sendTextMessage(msg.first, msg.second);
604 });
605 }
606 }
607}
608
611void
612Call::checkAudio()
613{
614 using namespace libjami::Call;
615
616 auto state = getStateStr();
617 if (state == StateEvent::RINGING) {
618 Manager::instance().peerRingingCall(*this);
619 } else if (state == StateEvent::BUSY) {
620 Manager::instance().callBusy(*this);
621 }
622}
623
624// Helper to safely pop subcalls list
625Call::SubcallSet
626Call::safePopSubcalls()
627{
628 std::lock_guard lk {callMutex_};
629 // std::exchange is C++14
630 auto old_value = std::move(subcalls_);
631 subcalls_.clear();
632 return old_value;
633}
634
635void
636Call::setConferenceInfo(const std::string& msg)
637{
638 ConfInfo newInfo;
639 Json::Value json;
640 if (json::parse(msg, json)) {
641 if (json.isObject()) {
642 // new confInfo
643 if (json.isMember("p")) {
644 for (const auto& participantInfo : json["p"]) {
645 ParticipantInfo pInfo;
646 if (!participantInfo.isMember("uri"))
647 continue;
648 pInfo.fromJson(participantInfo);
649 newInfo.emplace_back(pInfo);
650 }
651 }
652 if (json.isMember("v")) {
653 newInfo.v = json["v"].asInt();
654 peerConfProtocol_ = newInfo.v;
655 }
656 if (json.isMember("w"))
657 newInfo.w = json["w"].asInt();
658 if (json.isMember("h"))
659 newInfo.h = json["h"].asInt();
660 } else {
661 // old confInfo
662 for (const auto& participantInfo : json) {
663 ParticipantInfo pInfo;
664 if (!participantInfo.isMember("uri"))
665 continue;
666 pInfo.fromJson(participantInfo);
667 newInfo.emplace_back(pInfo);
668 }
669 }
670 }
671
672 {
673 std::lock_guard lk(confInfoMutex_);
674 if (not isConferenceParticipant()) {
675 // confID_ empty -> participant set confInfo with the received one
676 confInfo_ = std::move(newInfo);
677
678 // Create sink for each participant
679#ifdef ENABLE_VIDEO
680 createSinks(confInfo_);
681#endif
682 // Inform client that layout has changed
684 id_, confInfo_.toVectorMapStringString());
685 } else if (auto conf = conf_.lock()) {
686 conf->mergeConfInfo(newInfo, getPeerNumber());
687 }
688 }
689}
690
691void
692Call::sendConfOrder(const Json::Value& root)
693{
694 std::map<std::string, std::string> messages;
695 messages["application/confOrder+json"] = json::toString(root);
696
697 auto w = getAccount();
698 auto account = w.lock();
699 if (account)
700 sendTextMessage(messages, account->getFromUri());
701}
702
703void
704Call::sendConfInfo(const std::string& json)
705{
706 std::map<std::string, std::string> messages;
707 messages["application/confInfo+json"] = json;
708
709 auto w = getAccount();
710 auto account = w.lock();
711 if (account)
712 sendTextMessage(messages, account->getFromUri());
713}
714
715void
716Call::resetConfInfo()
717{
718 sendConfInfo("{}");
719}
720
721} // namespace jami
Interface to protocol account (ex: SIPAccount) It can be enable on loading or activate after.
std::string reason_
Definition call.h:522
MsgList pendingInMessages_
Definition call.h:536
const std::string & toUsername() const
Get "To" from the invite.
Definition call.h:171
std::string peerNumber_
Number of the peer.
Definition call.h:528
const std::string & getCallId() const
Return a reference on the call id.
Definition call.h:132
std::set< std::shared_ptr< Call >, std::owner_less< std::shared_ptr< Call > > > SubcallSet
Definition call.h:112
CallState getState() const
Get the call state of the call (protected by mutex)
Definition call.cpp:174
virtual void sendTextMessage(const std::map< std::string, std::string > &messages, const std::string &from)=0
Send a message to a call identified by its callid.
CallType
This determines if the call originated from the local user (OUTGOING) or from some remote peer (INCOM...
Definition call.h:120
virtual void monitor() const =0
std::chrono::milliseconds getCallDuration() const
Definition call.h:354
ConnectionState getConnectionState() const
Get the connection state of the call (protected by mutex)
Definition call.cpp:167
CallType type_
Type of the call.
Definition call.h:511
std::shared_ptr< Call > parent_
MultiDevice: list of attached subcall.
Definition call.h:476
bool isSubcall() const
Return true if this call instance is a subcall (internal call for multi-device handling)
Definition call.h:345
std::weak_ptr< Call > weak()
Definition call.h:124
CallState
The Call State.
Definition call.h:99
ConnectionState
Tell where we're at with the call.
Definition call.h:85
std::string peerDisplayName_
Peer Display Name.
Definition call.h:531
CallType getCallType() const
Definition call.h:144
const std::string & getPeerNumber() const
Get the peer number (destination on outgoing) not protected by mutex (when created)
Definition call.h:158
Call(const std::shared_ptr< Account > &account, const std::string &id, Call::CallType type, const std::map< std::string, std::string > &details={})
Constructor of a call.
Definition call.cpp:77
virtual void removeCall()
Definition call.cpp:144
std::weak_ptr< Account > getAccount() const
Definition call.h:141
time_point duration_start_
Definition call.h:491
void addStateListener(StateListenerCb &&listener)
Definition call.h:330
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
ScheduledExecutor & scheduler()
Definition manager.cpp:1706
int getRingingTimeout() const
Get ringing timeout (number of seconds after which a call will enter BUSY state if not answered).
Definition manager.cpp:2381
std::shared_ptr< Task > scheduleIn(std::function< void()> &&job, duration dt, const char *filename=CURRENT_FILENAME(), uint32_t linum=CURRENT_LINE())
Schedule job to be run after delay dt.
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARN(...)
Definition logger.h:217
void setState(const std::string &accountID, const State migrationState)
void emitSignal(Args... args)
Definition ring_signal.h:64
std::shared_ptr< Call > getPtr(Call &call)
Obtain a shared smart pointer of instance.
Definition call.h:549
void hangupCallsIf(Call::SubcallSet &&calls, int errcode, T pred)
Hangup many calls with same error code, filtered by a predicate.
Definition call.cpp:57
void hangupCalls(Call::SubcallSet &&callptr_list, int errcode)
Hangup many calls with same error code.
Definition call.cpp:70
static void runOnMainThread(Callback &&cb)
Definition manager.h:909
static constexpr char CONF_ID[]
Definition call_const.h:50
static constexpr char CALL_TYPE[]
Definition call_const.h:45
static constexpr char TO_USERNAME[]
Definition call_const.h:52
static constexpr char TIMESTAMP_START[]
Definition call_const.h:51
static constexpr char VIDEO_MUTED[]
Definition call_const.h:56
static constexpr char AUDIO_ONLY[]
Definition call_const.h:58
static constexpr char CALL_STATE[]
Definition call_const.h:49
static constexpr char DISPLAY_NAME[]
Definition call_const.h:48
static constexpr char PEER_NUMBER[]
Definition call_const.h:46
static constexpr char ACCOUNTID[]
Definition call_const.h:53
static constexpr char AUDIO_MUTED[]
Definition call_const.h:55
static constexpr char CURRENT[]
Definition call_const.h:31
void fromJson(const Json::Value &v)
Definition conference.h:77