LCOV - code coverage report
Current view: top level - src - call.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 87.5 % 376 329
Test Date: 2026-07-06 08:25:38 Functions: 90.9 % 66 60

            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          477 : hangupCallsIf(Call::SubcallSet&& calls, int errcode, T pred)
      51              : {
      52          569 :     for (auto& call : calls) {
      53           92 :         if (not pred(call.get()))
      54           78 :             continue;
      55           28 :         dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
      56              :     }
      57          477 : }
      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          399 : hangupCalls(Call::SubcallSet&& callptr_list, int errcode)
      64              : {
      65          413 :     hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
      66          399 : }
      67              : 
      68              : //==============================================================================
      69              : 
      70          395 : Call::Call(const std::shared_ptr<Account>& account, const std::string& id, Call::CallType type)
      71          395 :     : id_(id)
      72          395 :     , type_(type)
      73          395 :     , account_(account)
      74         1185 :     , timeoutTimer_(*Manager::instance().ioContext())
      75              : {
      76          395 :     addStateListener([this](Call::CallState call_state, Call::ConnectionState cnx_state, int code) {
      77         2068 :         checkPendingIM();
      78         2068 :         runOnMainThread([callWkPtr = weak_from_this()] {
      79         2068 :             if (auto call = callWkPtr.lock())
      80         2068 :                 call->checkAudio();
      81         2068 :         });
      82              : 
      83              :         // if call just started ringing, schedule call timeout
      84         2068 :         if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
      85          110 :             timeoutTimer_.expires_after(Manager::instance().getRingingTimeout());
      86          110 :             timeoutTimer_.async_wait([callWkPtr = weak_from_this()](const std::error_code& ec) {
      87          110 :                 if (ec == asio::error::operation_aborted)
      88          107 :                     return;
      89            3 :                 if (auto callShPtr = callWkPtr.lock()) {
      90            3 :                     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            3 :                 }
      97              :             });
      98              :         }
      99              : 
     100         2068 :         if (!isSubcall()) {
     101         1453 :             if (code == PJSIP_SC_TEMPORARILY_UNAVAILABLE) {
     102            6 :                 reason_ = "no_device";
     103              :             }
     104         1453 :             if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
     105          193 :                 duration_start_ = clock::now();
     106         1260 :             else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
     107          217 :                 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
     108          197 :                     if (toUsername().find('/') == std::string::npos && getCallType() == CallType::OUTGOING) {
     109          100 :                         if (auto* cm = jamiAccount->convModule(true))
     110          100 :                             cm->addCallHistoryMessage(getPeerNumber(), getCallDuration().count(), reason_);
     111              :                     }
     112          197 :                     monitor();
     113          217 :                 }
     114              :             }
     115              :         }
     116              : 
     117              :         // kill pending subcalls at disconnect
     118         2068 :         if (call_state == CallState::OVER)
     119          395 :             hangupCalls(safePopSubcalls(), 0);
     120              : 
     121         2068 :         return true;
     122              :     });
     123              : 
     124          395 :     time(&timestamp_start_);
     125          395 : }
     126              : 
     127          395 : Call::~Call() {}
     128              : 
     129              : void
     130          406 : Call::removeCall(int code)
     131              : {
     132          406 :     auto this_ = shared_from_this();
     133          406 :     Manager::instance().callFactory.removeCall(*this);
     134          406 :     setState(CallState::OVER, code);
     135          406 :     if (Recordable::isRecording())
     136            3 :         Recordable::stopRecording();
     137          406 :     if (auto account = account_.lock())
     138          406 :         account->detach(this_);
     139          406 :     parent_.reset();
     140          406 :     subcalls_.clear();
     141          406 : }
     142              : 
     143              : std::string
     144         3258 : Call::getAccountId() const
     145              : {
     146         3258 :     if (auto shared = account_.lock())
     147         3258 :         return shared->getAccountID();
     148           12 :     JAMI_ERROR("No account detected");
     149            3 :     return {};
     150              : }
     151              : 
     152              : Call::ConnectionState
     153         7953 : Call::getConnectionState() const
     154              : {
     155         7953 :     std::lock_guard lock(callMutex_);
     156         7953 :     return connectionState_;
     157         7953 : }
     158              : 
     159              : Call::CallState
     160         9414 : Call::getState() const
     161              : {
     162         9414 :     std::lock_guard lock(callMutex_);
     163         9414 :     return callState_;
     164         9414 : }
     165              : 
     166              : bool
     167          799 : 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          799 :     if (newState == CallState::OVER)
     175          395 :         return true;
     176              : 
     177          404 :     switch (callState_) {
     178          383 :     case CallState::INACTIVE:
     179          383 :         switch (newState) {
     180          383 :         case CallState::ACTIVE:
     181              :         case CallState::BUSY:
     182              :         case CallState::PEER_BUSY:
     183              :         case CallState::MERROR:
     184          383 :             return true;
     185            0 :         default: // INACTIVE, HOLD
     186            0 :             return false;
     187              :         }
     188              : 
     189           16 :     case CallState::ACTIVE:
     190           16 :         switch (newState) {
     191           16 :         case CallState::BUSY:
     192              :         case CallState::PEER_BUSY:
     193              :         case CallState::HOLD:
     194              :         case CallState::MERROR:
     195           16 :             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            2 :     default: // MERROR
     218            2 :         return false;
     219              :     }
     220              : }
     221              : 
     222              : bool
     223         2273 : Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
     224              : {
     225         2273 :     std::unique_lock<std::recursive_mutex> lock(callMutex_);
     226         9089 :     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         2273 :     if (callState_ != call_state) {
     235          799 :         if (not validStateTransition(call_state)) {
     236            8 :             JAMI_ERROR("[call:{}] invalid call state transition from {} to {}",
     237              :                        id_,
     238              :                        (unsigned) callState_,
     239              :                        (unsigned) call_state);
     240            2 :             return false;
     241              :         }
     242         1474 :     } else if (connectionState_ == cnx_state)
     243          203 :         return true; // no changes as no-op
     244              : 
     245              :     // Emit client state only if changed
     246         2068 :     auto old_client_state = getStateStr();
     247         2068 :     callState_ = call_state;
     248         2068 :     connectionState_ = cnx_state;
     249         2068 :     auto new_client_state = getStateStr();
     250              : 
     251         5076 :     for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
     252         3008 :         if ((*it)(callState_, connectionState_, code))
     253         2919 :             ++it;
     254              :         else
     255           89 :             it = stateChangedListeners_.erase(it);
     256              :     }
     257              : 
     258         2068 :     if (old_client_state != new_client_state) {
     259         1698 :         if (not parent_) {
     260         4684 :             JAMI_LOG("[call:{}] emit client call state change {}, code {}", id_, new_client_state, code);
     261         1171 :             lock.unlock();
     262         1171 :             emitSignal<libjami::CallSignal::StateChange>(getAccountId(), id_, new_client_state, code);
     263              :         }
     264              :     }
     265              : 
     266         2068 :     return true;
     267         2273 : }
     268              : 
     269              : bool
     270          501 : Call::setState(CallState call_state, signed code)
     271              : {
     272          501 :     std::lock_guard lock(callMutex_);
     273         1002 :     return setState(call_state, connectionState_, code);
     274          501 : }
     275              : 
     276              : bool
     277         1285 : Call::setState(ConnectionState cnx_state, signed code)
     278              : {
     279         1285 :     std::lock_guard lock(callMutex_);
     280         2570 :     return setState(callState_, cnx_state, code);
     281         1285 : }
     282              : 
     283              : std::string
     284         8793 : Call::getStateStr() const
     285              : {
     286              :     using namespace libjami::Call;
     287              : 
     288         8793 :     switch (getState()) {
     289         3902 :     case CallState::ACTIVE:
     290         3902 :         switch (getConnectionState()) {
     291          893 :         case ConnectionState::PROGRESSING:
     292         1786 :             return StateEvent::CONNECTING;
     293              : 
     294          914 :         case ConnectionState::RINGING:
     295         1827 :             return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
     296              : 
     297          704 :         case ConnectionState::DISCONNECTED:
     298         1408 :             return StateEvent::HUNGUP;
     299              : 
     300         1391 :         case ConnectionState::CONNECTED:
     301              :         default:
     302         2782 :             return StateEvent::CURRENT;
     303              :         }
     304              : 
     305           39 :     case CallState::HOLD:
     306           39 :         if (getConnectionState() == ConnectionState::DISCONNECTED)
     307           24 :             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         3423 :     case CallState::INACTIVE:
     317         3423 :         switch (getConnectionState()) {
     318         1231 :         case ConnectionState::PROGRESSING:
     319         2462 :             return StateEvent::CONNECTING;
     320              : 
     321          589 :         case ConnectionState::RINGING:
     322         1178 :             return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
     323              : 
     324            0 :         case ConnectionState::CONNECTED:
     325            0 :             return StateEvent::CURRENT;
     326              : 
     327         1603 :         default:
     328         3206 :             return StateEvent::INACTIVE;
     329              :         }
     330              : 
     331         1023 :     case CallState::OVER:
     332         2046 :         return StateEvent::OVER;
     333              : 
     334          399 :     case CallState::MERROR:
     335              :     default:
     336          798 :         return StateEvent::FAILURE;
     337              :     }
     338              : }
     339              : 
     340              : bool
     341           15 : Call::toggleRecording()
     342              : {
     343           15 :     const bool startRecording = Recordable::toggleRecording();
     344           15 :     return startRecording;
     345              : }
     346              : 
     347              : std::map<std::string, std::string>
     348          371 : Call::getDetails() const
     349              : {
     350          371 :     auto conference = conf_.lock();
     351              :     return {
     352            0 :         {libjami::Call::Details::CALL_TYPE, std::to_string((unsigned) type_)},
     353          371 :         {libjami::Call::Details::PEER_NUMBER, peerNumber_},
     354          371 :         {libjami::Call::Details::DISPLAY_NAME, peerDisplayName_},
     355          371 :         {libjami::Call::Details::CALL_STATE, getStateStr()},
     356         1474 :         {libjami::Call::Details::CONF_ID, conference ? conference->getConfId() : ""},
     357          371 :         {libjami::Call::Details::TIMESTAMP_START, std::to_string(timestamp_start_)},
     358          371 :         {libjami::Call::Details::ACCOUNTID, getAccountId()},
     359          371 :         {libjami::Call::Details::TO_USERNAME, toUsername()},
     360          742 :         {libjami::Call::Details::AUDIO_MUTED, std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
     361          742 :         {libjami::Call::Details::VIDEO_MUTED, std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
     362          742 :         {libjami::Call::Details::AUDIO_ONLY, std::string(bool_to_str(not hasVideo()))},
     363         5194 :     };
     364         2226 : }
     365              : 
     366              : void
     367          242 : Call::onTextMessage(std::map<std::string, std::string>&& messages)
     368              : {
     369          242 :     auto it = messages.find("application/confInfo+json");
     370          242 :     if (it != messages.end()) {
     371          236 :         setConferenceInfo(it->second);
     372          242 :         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          106 : Call::peerHungup()
     401              : {
     402          106 :     const auto state = getState();
     403          106 :     const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
     404          106 :     setState(ConnectionState::DISCONNECTED, aborted ? PJSIP_SC_REQUEST_TERMINATED : PJSIP_SC_DECLINE);
     405          106 : }
     406              : 
     407              : void
     408          178 : Call::addSubCall(Call& subcall)
     409              : {
     410          178 :     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          178 :     if (connectionState_ == ConnectionState::CONNECTED || connectionState_ == ConnectionState::DISCONNECTED
     416          178 :         || callState_ == CallState::OVER) {
     417            0 :         subcall.removeCall();
     418            0 :         return;
     419              :     }
     420              : 
     421          178 :     if (not subcalls_.emplace(getPtr(subcall)).second) {
     422            0 :         JAMI_ERROR("[call:{}] add twice subcall {}", getCallId(), subcall.getCallId());
     423            0 :         return;
     424              :     }
     425              : 
     426          712 :     JAMI_LOG("[call:{}] add subcall {}", getCallId(), subcall.getCallId());
     427          178 :     subcall.parent_ = getPtr(*this);
     428              : 
     429          178 :     for (const auto& msg : pendingOutMessages_)
     430            0 :         subcall.sendTextMessage(msg.first, msg.second);
     431              : 
     432          178 :     subcall.addStateListener(
     433          356 :         [sub = subcall.weak_from_this(),
     434              :          parent = weak_from_this()](Call::CallState new_state, Call::ConnectionState new_cstate, int code) {
     435          615 :             runOnMainThread([sub, parent, new_state, new_cstate, code]() {
     436          615 :                 if (auto p = parent.lock()) {
     437          585 :                     if (auto s = sub.lock()) {
     438          502 :                         p->subcallStateChanged(*s, new_state, new_cstate, code);
     439          585 :                     }
     440          615 :                 }
     441          615 :             });
     442          615 :             return true;
     443              :         });
     444          178 : }
     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          502 : 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          502 :         std::lock_guard lk {callMutex_};
     460          502 :         auto sit = subcalls_.find(getPtr(subcall));
     461          502 :         if (sit == subcalls_.end())
     462           76 :             return;
     463          502 :     }
     464              : 
     465              :     // We found a responding device: hangup all other subcalls and merge
     466          426 :     if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
     467          312 :         JAMI_LOG("[call:{}] subcall {} answered by peer", getCallId(), subcall.getCallId());
     468              : 
     469          156 :         hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
     470           78 :         merge(subcall);
     471           78 :         Manager::instance().peerAnsweredCall(*this);
     472           78 :         return;
     473              :     }
     474              : 
     475              :     // Hangup the call if any device hangup or send busy
     476          348 :     if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
     477          175 :         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          344 :     if (new_state >= CallState::BUSY) {
     488           86 :         if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
     489            0 :             JAMI_WARNING("[call:{}] subcall {} busy", getCallId(), subcall.getCallId());
     490              :         else
     491          344 :             JAMI_WARNING("[call:{}] subcall {} failed", getCallId(), subcall.getCallId());
     492           86 :         std::lock_guard lk {callMutex_};
     493           86 :         subcalls_.erase(getPtr(subcall));
     494              : 
     495              :         // Parent call fails if last subcall is busy or failed
     496           86 :         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          336 :             JAMI_DEBUG("[call:{}] Subcalls remaining : {}", getCallId(), subcalls_.size());
     509              :         }
     510              : 
     511           86 :         return;
     512           86 :     }
     513              : 
     514              :     // Copy call/cnx states (forward only)
     515          258 :     if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
     516           86 :         setState(new_state);
     517              :     }
     518          258 :     if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
     519          169 :         and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
     520          169 :         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           78 : Call::merge(Call& subcall)
     528              : {
     529          312 :     JAMI_LOG("[call:{}] merge subcall {}", getCallId(), subcall.getCallId());
     530              : 
     531              :     // Merge data
     532           78 :     pendingInMessages_ = std::move(subcall.pendingInMessages_);
     533           78 :     if (peerNumber_.empty())
     534            0 :         peerNumber_ = std::move(subcall.peerNumber_);
     535           78 :     peerDisplayName_ = std::move(subcall.peerDisplayName_);
     536           78 :     setState(subcall.getState(), subcall.getConnectionState());
     537              : 
     538           78 :     std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
     539           78 :     runOnMainThread([subCallWeak] {
     540           78 :         if (auto subcall = subCallWeak.lock())
     541           78 :             subcall->removeCall();
     542           78 :     });
     543           78 : }
     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         2068 : Call::checkPendingIM()
     550              : {
     551         2068 :     std::lock_guard lk {callMutex_};
     552              : 
     553         2068 :     auto state = getStateStr();
     554              :     // Let parent call handles IM after the merge
     555         2068 :     if (not parent_) {
     556         1453 :         if (state == libjami::Call::StateEvent::CURRENT) {
     557          199 :             for (const auto& msg : pendingInMessages_)
     558            0 :                 Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), msg.first);
     559          199 :             pendingInMessages_.clear();
     560              : 
     561          199 :             std::weak_ptr<Call> callWkPtr = shared_from_this();
     562          199 :             runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
     563          199 :                 if (auto call = callWkPtr.lock())
     564          199 :                     for (const auto& msg : pending)
     565          199 :                         call->sendTextMessage(msg.first, msg.second);
     566          199 :             });
     567          199 :         }
     568              :     }
     569         2068 : }
     570              : 
     571              : /// Handle tones for RINGING and BUSY calls
     572              : ///
     573              : void
     574         1660 : Call::checkAudio()
     575              : {
     576              :     using namespace libjami::Call;
     577              : 
     578         1660 :     auto state = getStateStr();
     579         1660 :     if (state == StateEvent::RINGING) {
     580          191 :         Manager::instance().peerRingingCall(*this);
     581         1469 :     } else if (state == StateEvent::BUSY) {
     582            0 :         Manager::instance().callBusy(*this);
     583              :     }
     584         1660 : }
     585              : 
     586              : // Helper to safely pop subcalls list
     587              : Call::SubcallSet
     588          477 : Call::safePopSubcalls()
     589              : {
     590          477 :     std::lock_guard lk {callMutex_};
     591              :     // std::exchange is C++14
     592          477 :     auto old_value = std::move(subcalls_);
     593          477 :     subcalls_.clear();
     594          954 :     return old_value;
     595          477 : }
     596              : 
     597              : void
     598          236 : Call::setConferenceInfo(const std::string& msg)
     599              : {
     600          236 :     ConfInfo newInfo;
     601          236 :     Json::Value json;
     602          236 :     if (json::parse(msg, json)) {
     603          236 :         if (json.isObject()) {
     604              :             // new confInfo
     605          236 :             if (json.isMember("p")) {
     606         1509 :                 for (const auto& participantInfo : json["p"]) {
     607          640 :                     ParticipantInfo pInfo;
     608          640 :                     if (!participantInfo.isMember("uri"))
     609            0 :                         continue;
     610          640 :                     pInfo.fromJson(participantInfo);
     611          640 :                     newInfo.emplace_back(pInfo);
     612          640 :                 }
     613              :             }
     614          236 :             if (json.isMember("v")) {
     615          233 :                 newInfo.v = json["v"].asInt();
     616          233 :                 peerConfProtocol_ = newInfo.v;
     617              :             }
     618          236 :             if (json.isMember("w"))
     619          233 :                 newInfo.w = json["w"].asInt();
     620          236 :             if (json.isMember("h"))
     621          233 :                 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          236 :         std::lock_guard lk(confInfoMutex_);
     636          236 :         if (not isConferenceParticipant()) {
     637              :             // confID_ empty -> participant set confInfo with the received one
     638          236 :             confInfo_ = std::move(newInfo);
     639              : 
     640              :             // Create sink for each participant
     641              : #ifdef ENABLE_VIDEO
     642          236 :             createSinks(confInfo_);
     643              : #endif
     644              :             // Inform client that layout has changed
     645          236 :             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          236 :     }
     650          236 : }
     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          235 : Call::sendConfInfo(const std::string& json)
     666              : {
     667          235 :     std::map<std::string, std::string> messages;
     668          236 :     messages["application/confInfo+json"] = json;
     669              : 
     670          238 :     auto w = getAccount();
     671          236 :     auto account = w.lock();
     672          238 :     if (account)
     673          236 :         sendTextMessage(messages, account->getFromUri());
     674          238 : }
     675              : 
     676              : void
     677            4 : Call::resetConfInfo()
     678              : {
     679            4 :     sendConfInfo("{}");
     680            4 : }
     681              : 
     682              : } // namespace jami
        

Generated by: LCOV version 2.0-1