LCOV - code coverage report
Current view: top level - src - call.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 86.7 % 376 326
Test Date: 2026-06-13 09:18:46 Functions: 87.9 % 66 58

            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(&timestamp_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
        

Generated by: LCOV version 2.0-1