Skip to content
Snippets Groups Projects
Select Git revision
  • d05d90a82be6d5161e748944f076cf4799f8695c
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

sipaccount.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    currentcall.cpp 14.29 KiB
    /*
     * Copyright (C) 2022-2024 Savoir-faire Linux Inc.
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 3 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     */
    
    #include "currentcall.h"
    
    #include "callparticipantsmodel.h"
    
    #include <api/callparticipantsmodel.h>
    #include <api/devicemodel.h>
    
    CurrentCall::CurrentCall(LRCInstance* lrcInstance, QObject* parent)
        : QObject(parent)
        , lrcInstance_(lrcInstance)
    {
        participantsModel_ = qApp->property("CallParticipantsModel").value<CallParticipantsModel*>();
    
        connect(lrcInstance_,
                &LRCInstance::currentAccountIdChanged,
                this,
                &CurrentCall::onCurrentAccountIdChanged);
    
        connect(lrcInstance_,
                &LRCInstance::selectedConvUidChanged,
                this,
                &CurrentCall::onCurrentConvIdChanged);
    
        connect(&lrcInstance_->behaviorController(),
                &BehaviorController::showIncomingCallView,
                this,
                &CurrentCall::onShowIncomingCallView);
    
        try {
            auto& accInfo = lrcInstance_->getCurrentAccountInfo();
            set_isSIP(accInfo.profileInfo.type == profile::Type::SIP);
        } catch (const std::exception& e) {
            qWarning() << "Can't update current call type" << e.what();
        }
    
        connectModel();
    }
    
    void
    CurrentCall::updateId(QString callId)
    {
        auto convId = lrcInstance_->get_selectedConvUid();
        auto optConv = lrcInstance_->getCurrentConversationModel()->getConversationForUid(convId);
        if (!optConv.has_value()) {
            return;
        }
    
        // If the optional parameter callId is empty, then we've just
        // changed conversation selection and need to check the current
        // conv's callId for an existing call.
        // Otherwise, return if callId doesn't belong to this conversation.
        if (callId.isEmpty()) {
            callId = optConv->get().getCallId();
        } else if (optConv->get().getCallId() != callId) {
            return;
        }
    
        auto& accInfo = lrcInstance_->getCurrentAccountInfo();
        if (accInfo.profileInfo.type != lrc::api::profile::Type::SIP) {
            // Only setCurrentCall if call is actually answered
            try {
                auto callInfo = accInfo.callModel->getCall(callId);
                if (callInfo.status == call::Status::IN_PROGRESS
                    || callInfo.status == call::Status::PAUSED)
                    accInfo.callModel->setCurrentCall(callId);
            } catch (...) {
            }
        }
        // Set the current id_ if there is a call.
        auto hasCall = accInfo.callModel->hasCall(callId);
        set_id((hasCall ? callId : QString()));
    
        if (hasCall) {
            try {
                auto callInfo = accInfo.callModel->getCall(callId);
                participantsModel_->setParticipants(id_, getConferencesInfos());
                participantsModel_->setConferenceLayout(static_cast<int>(callInfo.layout), id_);
            } catch (...) {
            }
        }
    }
    
    void
    CurrentCall::updateCallStatus()
    {
        call::Status status {};
        auto callModel = lrcInstance_->getCurrentCallModel();
        if (callModel->hasCall(id_)) {
            auto callInfo = callModel->getCall(id_);
            status = callInfo.status;
        }
    
        set_status(status);
        set_isActive(status_ == call::Status::CONNECTED || status_ == call::Status::IN_PROGRESS
                     || status_ == call::Status::PAUSED);
        set_isPaused(status_ == call::Status::PAUSED);
    }
    
    void
    CurrentCall::updateParticipants()
    {
        auto callModel = lrcInstance_->getCurrentCallModel();
        QStringList uris;
        auto& participantsModel = callModel->getParticipantsInfos(id_);
        for (int index = 0; index < participantsModel.getParticipants().size(); index++) {
            auto participantInfo = participantsModel.toQJsonObject(index);
            uris.append(participantInfo[ParticipantsInfosStrings::URI].toString());
        }
        set_uris(uris);
        set_isConference(uris.size());
    }
    
    void
    CurrentCall::onParticipantAdded(const QString& callId, int index)
    {
        if (callId != id_)
            return;
        auto infos = getConferencesInfos();
        if (index < infos.size())
            participantsModel_->addParticipant(index, infos[index]);
    }
    
    void
    CurrentCall::onParticipantRemoved(const QString& callId, int index)
    {
        if (callId != id_)
            return;
        participantsModel_->removeParticipant(index);
    }
    
    void
    CurrentCall::onParticipantUpdated(const QString& callId, int index)
    {
        if (callId != id_)
            return;
        auto infos = getConferencesInfos();
        if (index < infos.size())
            participantsModel_->updateParticipant(index, infos[index]);
    }
    
    void
    CurrentCall::fillParticipantData(QJsonObject& participant) const
    {
        // TODO: getCurrentDeviceId should be part of CurrentAccount, and Current<thing>
        //       should be read accessible through LRCInstance ??
        auto getCurrentDeviceId = [](const account::Info& accInfo) -> QString {
            const auto& deviceList = accInfo.deviceModel->getAllDevices();
            auto devIt = std::find_if(std::cbegin(deviceList),
                                      std::cend(deviceList),
                                      [](const Device& dev) { return dev.isCurrent; });
            return devIt != deviceList.cend() ? devIt->id : QString();
        };
    
        auto& accInfo = lrcInstance_->getCurrentAccountInfo();
        using namespace lrc::api::ParticipantsInfosStrings;
    
        // If both the URI and the device Id match, we set the "is local" flag
        // used to filter out the participant.
        // TODO:
        //  - This filter should always be applied, and any local streams should render
        //    using local sinks. Local non-preview participants should have proxy participant
        //    items replaced into this model using their local sink Ids.
        //  - The app setting should remain and be used to control whether or not the preview
        //    sink partipcant is rendered.
        auto uri = participant[URI].toString();
        participant[ISLOCAL] = false;
        if (uri == accInfo.profileInfo.uri && participant[DEVICE] == getCurrentDeviceId(accInfo)) {
            participant[BESTNAME] = tr("Me");
            participant[ISLOCAL] = true;
        } else {
            try {
                participant[BESTNAME] = accInfo.contactModel->bestNameForContact(uri);
            } catch (...) {
            }
        }
    }
    
    QVariantList
    CurrentCall::getConferencesInfos() const
    {
        QVariantList map;
        try {
            auto callModel = lrcInstance_->getCurrentCallModel();
            auto& participantsModel = callModel->getParticipantsInfos(id_);
            for (int index = 0; index < participantsModel.getParticipants().size(); index++) {
                auto participant = participantsModel.toQJsonObject(index);
                fillParticipantData(participant);
                map.push_back(QVariant(participant));
            }
        } catch (...) {
        }
        return map;
    }
    
    void
    CurrentCall::updateCallInfo()
    {
        auto callModel = lrcInstance_->getCurrentCallModel();
        if (!callModel->hasCall(id_)) {
            return;
        }
    
        auto callInfo = callModel->getCall(id_);
    
        set_isOutgoing(callInfo.isOutgoing);
        set_isGrid(callInfo.layout == call::Layout::GRID);
        set_isAudioOnly(callInfo.isAudioOnly);
    
        bool isAudioMuted {};
        bool isVideoMuted {};
        bool isSharing {};
        QString sharingSource {};
        bool isCapturing {};
        QString previewId {};
        using namespace libjami::Media;
        if (callInfo.status != lrc::api::call::Status::ENDED) {
            for (const auto& media : callInfo.mediaList) {
                if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) {
                    if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY)
                        || media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) {
                        isSharing = true;
                        sharingSource = media[MediaAttributeKey::SOURCE];
                    }
                    if (media[MediaAttributeKey::ENABLED] == TRUE_STR
                        && media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) {
                        previewId = media[libjami::Media::MediaAttributeKey::SOURCE];
                    }
                    if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith(
                            libjami::Media::VideoProtocolPrefix::CAMERA)) {
                        isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR;
                        isCapturing = media[MediaAttributeKey::MUTED] == FALSE_STR;
                    }
                } else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) {
                    if (media[MediaAttributeKey::LABEL] == "audio_0") {
                        isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR;
                    }
                }
            }
        }
        set_previewId(previewId);
        set_isAudioMuted(isAudioMuted);
        set_isVideoMuted(isVideoMuted);
        set_isSharing(isSharing);
        set_sharingSource(sharingSource);
        set_isCapturing(isCapturing);
        set_isHandRaised(callModel->isHandRaised(id_));
        set_isModerator(callModel->isModerator(id_));
    
        QStringList recorders {};
        if (callModel->hasCall(id_)) {
            auto callInfo = callModel->getCall(id_);
            participantsModel_->setConferenceLayout(static_cast<int>(callInfo.layout), id_);
            recorders = callInfo.recordingPeers;
        }
        updateRecordingState(callModel->isRecording(id_));
        updateRemoteRecorders(recorders);
    }
    
    void
    CurrentCall::updateRemoteRecorders(const QStringList& recorders)
    {
        auto& accInfo = lrcInstance_->getCurrentAccountInfo();
        remoteRecorderNameList_.clear();
        Q_FOREACH (const auto& uri, recorders) {
            auto bestName = accInfo.contactModel->bestNameForContact(uri);
            if (!bestName.isEmpty()) {
                remoteRecorderNameList_.append(bestName);
            }
        }
    
        // Convenience flag.
        set_isRecordingRemotely(!remoteRecorderNameList_.isEmpty());
    
        Q_EMIT remoteRecorderNameListChanged();
    }
    
    void
    CurrentCall::updateRecordingState(bool state)
    {
        set_isRecordingLocally(state);
    }
    
    void
    CurrentCall::connectModel()
    {
        auto callModel = lrcInstance_->getCurrentCallModel();
        if (callModel == nullptr) {
            return;
        }
    
        connect(callModel,
                &CallModel::callStatusChanged,
                this,
                &CurrentCall::onCallStatusChanged,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::callInfosChanged,
                this,
                &CurrentCall::onCallInfosChanged,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::currentCallChanged,
                this,
                &CurrentCall::onCurrentCallChanged,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::participantsChanged,
                this,
                &CurrentCall::onParticipantsChanged,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::remoteRecordersChanged,
                this,
                &CurrentCall::onRemoteRecordersChanged,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::recordingStateChanged,
                this,
                &CurrentCall::onRecordingStateChanged,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::participantAdded,
                this,
                &CurrentCall::onParticipantAdded,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::participantRemoved,
                this,
                &CurrentCall::onParticipantRemoved,
                Qt::UniqueConnection);
        connect(callModel,
                &CallModel::participantUpdated,
                this,
                &CurrentCall::onParticipantUpdated,
                Qt::UniqueConnection);
    }
    
    void
    CurrentCall::onCurrentConvIdChanged()
    {
        updateId();
        updateCallStatus();
        updateParticipants();
        updateCallInfo();
    
        auto callModel = lrcInstance_->getCurrentCallModel();
        QStringList recorders {};
        if (callModel->hasCall(id_)) {
            auto callInfo = callModel->getCall(id_);
            recorders = callInfo.recordingPeers;
        }
        updateRecordingState(callModel->isRecording(id_));
        updateRemoteRecorders(recorders);
    }
    
    void
    CurrentCall::onCurrentAccountIdChanged()
    {
        try {
            auto& accInfo = lrcInstance_->getCurrentAccountInfo();
            set_isSIP(accInfo.profileInfo.type == profile::Type::SIP);
        } catch (const std::exception& e) {
            qWarning() << "Can't update current call type" << e.what();
        }
    
        connectModel();
    }
    
    void
    CurrentCall::onCallStatusChanged(const QString& callId, int code)
    {
        Q_UNUSED(code)
    
        if (id_ != callId) {
            return;
        }
    
        updateCallStatus();
    }
    
    void
    CurrentCall::onCallInfosChanged(const QString& accountId, const QString& callId)
    {
        if (id_ != callId) {
            return;
        }
    
        updateCallInfo();
    }
    
    void
    CurrentCall::onCurrentCallChanged(const QString& callId)
    {
        // If this status change's callId is not the current, it's possible that
        // the current value of id_ is stale, and needs to be updated after checking
        // the current conversation's getCallId(). Other slots need not do this, as the
        // id_ is updated in CurrentCall::updateId.
        if (id_ == callId) {
            return;
        }
    
        updateId(callId);
        updateCallStatus();
        updateParticipants();
        updateCallInfo();
    }
    
    void
    CurrentCall::onParticipantsChanged(const QString& callId)
    {
        if (id_ != callId) {
            return;
        }
    
        updateParticipants();
    }
    
    void
    CurrentCall::onRemoteRecordersChanged(const QString& callId, const QStringList& recorders)
    {
        if (id_ != callId) {
            return;
        }
    
        updateRemoteRecorders(recorders);
    }
    
    void
    CurrentCall::onRecordingStateChanged(const QString& callId, bool state)
    {
        if (id_ != callId) {
            return;
        }
    
        updateRecordingState(state);
    }
    
    void
    CurrentCall::onShowIncomingCallView(const QString& accountId, const QString& convUid)
    {
        if (accountId != lrcInstance_->get_currentAccountId()
            || convUid != lrcInstance_->get_selectedConvUid()) {
            return;
        }
    
        // Update the id in case the current conversation now has a call
        // that matches the current id.
        updateId();
        updateCallStatus();
        updateCallInfo();
    }