38#include <dhtnet/ip_utils.h>
39#include <opendht/thread_pool.h>
42#include <system_error>
59 for (
auto& call :
calls) {
62 dht::ThreadPool::io().run([call = std::move(call),
errcode] { call->hangup(
errcode); });
78 const std::string&
id,
80 const std::map<std::string, std::string>&
details)
97 JAMI_DBG(
"Scheduling call timeout in %d seconds", timeout);
102 if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
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);
111 std::chrono::seconds(timeout));
115 if (code ==
static_cast<int>(std::errc::no_such_device_or_address)) {
138 time(×tamp_start_);
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_);
158Call::getAccountId()
const
160 if (
auto shared = account_.lock())
161 return shared->getAccountID();
167Call::getConnectionState()
const
169 std::lock_guard lock(callMutex_);
170 return connectionState_;
174Call::getState()
const
176 std::lock_guard lock(callMutex_);
181Call::validStateTransition(CallState newState)
188 if (newState == CallState::OVER)
191 switch (callState_) {
192 case CallState::INACTIVE:
194 case CallState::ACTIVE:
195 case CallState::BUSY:
196 case CallState::PEER_BUSY:
197 case CallState::MERROR:
203 case CallState::ACTIVE:
205 case CallState::BUSY:
206 case CallState::PEER_BUSY:
207 case CallState::HOLD:
208 case CallState::MERROR:
214 case CallState::HOLD:
216 case CallState::ACTIVE:
217 case CallState::MERROR:
223 case CallState::BUSY:
225 case CallState::MERROR:
239 std::unique_lock<std::recursive_mutex> lock(callMutex_);
240 JAMI_DBG(
"[call:%s] state change %u/%u, cnx %u/%u, code %d",
242 (
unsigned) callState_,
243 (
unsigned) call_state,
244 (
unsigned) connectionState_,
245 (
unsigned) cnx_state,
248 if (callState_ != call_state) {
249 if (not validStateTransition(call_state)) {
250 JAMI_ERR(
"[call:%s] invalid call state transition from %u to %u",
252 (
unsigned) callState_,
253 (
unsigned) call_state);
256 }
else if (connectionState_ == cnx_state)
260 auto old_client_state = getStateStr();
261 callState_ = call_state;
262 connectionState_ = cnx_state;
263 auto new_client_state = getStateStr();
265 for (
auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
266 if ((*it)(callState_, connectionState_, code))
269 it = stateChangedListeners_.erase(it);
272 if (old_client_state != new_client_state) {
274 JAMI_DBG(
"[call:%s] emit client call state change %s, code %d",
276 new_client_state.c_str(),
279 emitSignal<libjami::CallSignal::StateChange>(getAccountId(),
292 std::lock_guard lock(callMutex_);
293 return setState(call_state, connectionState_, code);
299 std::lock_guard lock(callMutex_);
300 return setState(callState_, cnx_state, code);
304Call::getStateStr()
const
308 switch (getState()) {
309 case CallState::ACTIVE:
310 switch (getConnectionState()) {
311 case ConnectionState::PROGRESSING:
312 return StateEvent::CONNECTING;
314 case ConnectionState::RINGING:
315 return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
317 case ConnectionState::DISCONNECTED:
318 return StateEvent::HUNGUP;
320 case ConnectionState::CONNECTED:
322 return StateEvent::CURRENT;
325 case CallState::HOLD:
326 if (getConnectionState() == ConnectionState::DISCONNECTED)
327 return StateEvent::HUNGUP;
328 return StateEvent::HOLD;
330 case CallState::BUSY:
331 return StateEvent::BUSY;
333 case CallState::PEER_BUSY:
334 return StateEvent::PEER_BUSY;
336 case CallState::INACTIVE:
337 switch (getConnectionState()) {
338 case ConnectionState::PROGRESSING:
339 return StateEvent::CONNECTING;
341 case ConnectionState::RINGING:
342 return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
344 case ConnectionState::CONNECTED:
345 return StateEvent::CURRENT;
348 return StateEvent::INACTIVE;
351 case CallState::OVER:
352 return StateEvent::OVER;
354 case CallState::MERROR:
356 return StateEvent::FAILURE;
361Call::toggleRecording()
363 const bool startRecording = Recordable::toggleRecording();
364 return startRecording;
367std::map<std::string, std::string>
368Call::getDetails()
const
370 auto conference = conf_.lock();
381 std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
383 std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
389Call::onTextMessage(std::map<std::string, std::string>&& messages)
391 auto it = messages.find(
"application/confInfo+json");
392 if (it != messages.end()) {
393 setConferenceInfo(it->second);
397 it = messages.find(
"application/confOrder+json");
398 if (it != messages.end()) {
399 if (
auto conf = conf_.lock())
400 conf->onConfOrder(getCallId(), it->second);
405 std::lock_guard lk {callMutex_};
407 pendingInMessages_.emplace_back(std::move(messages),
"");
412 auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
413 if (pluginChatManager.hasHandlers()) {
414 pluginChatManager.publishMessage(
415 std::make_shared<JamiMessage>(getAccountId(), getPeerNumber(),
true, messages,
false));
418 Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), messages);
424 const auto state = getState();
425 const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
426 setState(ConnectionState::DISCONNECTED, aborted ? ECONNABORTED : ECONNREFUSED);
432 std::lock_guard lk {callMutex_};
437 if (connectionState_ == ConnectionState::CONNECTED
438 || connectionState_ == ConnectionState::DISCONNECTED || callState_ == CallState::OVER) {
443 if (not subcalls_.emplace(
getPtr(subcall)).second) {
444 JAMI_ERR(
"[call:%s] add twice subcall %s", getCallId().c_str(), subcall.
getCallId().c_str());
448 JAMI_DBG(
"[call:%s] add subcall %s", getCallId().c_str(), subcall.
getCallId().c_str());
451 for (
const auto& msg : pendingOutMessages_)
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);
475Call::subcallStateChanged(Call& subcall, Call::CallState new_state, Call::ConnectionState new_cstate)
482 std::lock_guard lk {callMutex_};
483 auto sit = subcalls_.find(
getPtr(subcall));
484 if (sit == subcalls_.end())
489 if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
490 JAMI_DBG(
"[call:%s] subcall %s answered by peer",
492 subcall.getCallId().c_str());
494 hangupCallsIf(safePopSubcalls(), 0, [&](
const Call* call) {
return call != &subcall; });
496 Manager::instance().peerAnsweredCall(*
this);
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",
505 subcall.getCallId().c_str());
506 reason_ = new_state == CallState::ACTIVE ?
"declined" :
"busy";
508 Manager::instance().peerHungupCall(*
this);
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());
520 subcall.getCallId().c_str());
521 std::lock_guard lk {callMutex_};
522 subcalls_.erase(
getPtr(subcall));
525 if (subcalls_.empty()) {
526 if (new_state == CallState::BUSY) {
528 ConnectionState::DISCONNECTED,
529 static_cast<int>(std::errc::device_or_resource_busy));
530 }
else if (new_state == CallState::PEER_BUSY) {
532 ConnectionState::DISCONNECTED,
533 static_cast<int>(std::errc::device_or_resource_busy));
538 ConnectionState::DISCONNECTED,
539 static_cast<int>(std::errc::io_error));
543 JAMI_DBG(
"[call:%s] remains %zu subcall(s)", getCallId().c_str(), subcalls_.size());
550 if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
553 if (
static_cast<unsigned>(connectionState_) <
static_cast<unsigned>(new_cstate)
554 and
static_cast<unsigned>(new_cstate) <=
static_cast<unsigned>(ConnectionState::RINGING)) {
564 JAMI_DBG(
"[call:%s] merge subcall %s", getCallId().c_str(), subcall.
getCallId().c_str());
568 if (peerNumber_.empty())
573 std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
574 runOnMainThread([subCallWeak] {
575 if (
auto subcall = subCallWeak.lock())
584Call::checkPendingIM()
586 std::lock_guard lk {callMutex_};
588 auto state = getStateStr();
592 for (
const auto& msg : pendingInMessages_)
593 Manager::instance().incomingMessage(getAccountId(),
597 pendingInMessages_.clear();
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);
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);
626Call::safePopSubcalls()
628 std::lock_guard lk {callMutex_};
630 auto old_value = std::move(subcalls_);
636Call::setConferenceInfo(
const std::string& msg)
640 if (json::parse(msg, json)) {
641 if (json.isObject()) {
643 if (json.isMember(
"p")) {
644 for (
const auto& participantInfo : json[
"p"]) {
646 if (!participantInfo.isMember(
"uri"))
649 newInfo.emplace_back(pInfo);
652 if (json.isMember(
"v")) {
653 newInfo.
v = json[
"v"].asInt();
654 peerConfProtocol_ = newInfo.
v;
656 if (json.isMember(
"w"))
657 newInfo.
w = json[
"w"].asInt();
658 if (json.isMember(
"h"))
659 newInfo.
h = json[
"h"].asInt();
662 for (
const auto& participantInfo : json) {
664 if (!participantInfo.isMember(
"uri"))
667 newInfo.emplace_back(pInfo);
673 std::lock_guard lk(confInfoMutex_);
674 if (not isConferenceParticipant()) {
676 confInfo_ = std::move(newInfo);
680 createSinks(confInfo_);
684 id_, confInfo_.toVectorMapStringString());
685 }
else if (
auto conf = conf_.lock()) {
686 conf->mergeConfInfo(newInfo, getPeerNumber());
692Call::sendConfOrder(
const Json::Value& root)
694 std::map<std::string, std::string> messages;
695 messages[
"application/confOrder+json"] = json::toString(root);
697 auto w = getAccount();
698 auto account = w.lock();
700 sendTextMessage(messages, account->getFromUri());
704Call::sendConfInfo(
const std::string& json)
706 std::map<std::string, std::string> messages;
707 messages[
"application/confInfo+json"] = json;
709 auto w = getAccount();
710 auto account = w.lock();
712 sendTextMessage(messages, account->getFromUri());
Interface to protocol account (ex: SIPAccount) It can be enable on loading or activate after.
MsgList pendingInMessages_
const std::string & toUsername() const
Get "To" from the invite.
std::string peerNumber_
Number of the peer.
const std::string & getCallId() const
Return a reference on the call id.
std::set< std::shared_ptr< Call >, std::owner_less< std::shared_ptr< Call > > > SubcallSet
CallState getState() const
Get the call state of the call (protected by mutex)
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...
virtual void monitor() const =0
std::chrono::milliseconds getCallDuration() const
ConnectionState getConnectionState() const
Get the connection state of the call (protected by mutex)
CallType type_
Type of the call.
std::shared_ptr< Call > parent_
MultiDevice: list of attached subcall.
bool isSubcall() const
Return true if this call instance is a subcall (internal call for multi-device handling)
std::weak_ptr< Call > weak()
ConnectionState
Tell where we're at with the call.
std::string peerDisplayName_
Peer Display Name.
CallType getCallType() const
const std::string & getPeerNumber() const
Get the peer number (destination on outgoing) not protected by mutex (when created)
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.
virtual void removeCall()
std::weak_ptr< Account > getAccount() const
time_point duration_start_
void addStateListener(StateListenerCb &&listener)
static LIBJAMI_TEST_EXPORT Manager & instance()
ScheduledExecutor & scheduler()
int getRingingTimeout() const
Get ringing timeout (number of seconds after which a call will enter BUSY state if not answered).
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.
void setState(const std::string &accountID, const State migrationState)
void emitSignal(Args... args)
std::shared_ptr< Call > getPtr(Call &call)
Obtain a shared smart pointer of instance.
void hangupCallsIf(Call::SubcallSet &&calls, int errcode, T pred)
Hangup many calls with same error code, filtered by a predicate.
void hangupCalls(Call::SubcallSet &&callptr_list, int errcode)
Hangup many calls with same error code.
static void runOnMainThread(Callback &&cb)
static constexpr char CONF_ID[]
static constexpr char CALL_TYPE[]
static constexpr char TO_USERNAME[]
static constexpr char TIMESTAMP_START[]
static constexpr char VIDEO_MUTED[]
static constexpr char AUDIO_ONLY[]
static constexpr char CALL_STATE[]
static constexpr char DISPLAY_NAME[]
static constexpr char PEER_NUMBER[]
static constexpr char ACCOUNTID[]
static constexpr char AUDIO_MUTED[]
static constexpr char CURRENT[]
void fromJson(const Json::Value &v)