/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). * * 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 . */ #include "CallModel.hpp" #include #include #include #include #include "app/App.hpp" #include "components/calls/CallsListModel.hpp" #include "components/chat-room/ChatRoomModel.hpp" #include "components/conference/ConferenceModel.hpp" #include "components/contact/ContactModel.hpp" #include "components/contacts/ContactsListModel.hpp" #include "components/core/CoreHandlers.hpp" #include "components/core/CoreManager.hpp" #include "components/notifier/Notifier.hpp" #include "components/settings/AccountSettingsModel.hpp" #include "components/settings/SettingsModel.hpp" #include "components/timeline/TimelineListModel.hpp" #include "utils/MediastreamerUtils.hpp" #include "utils/Utils.hpp" #include "linphone/api/c-search-result.h" // ============================================================================= using namespace std; namespace { constexpr char AutoAnswerObjectName[] = "auto-answer-timer"; } CallModel::CallModel (shared_ptr call){ mCall = call; if(mCall) mCall->setData("call-model", *this); updateIsInConference(); CoreManager *coreManager = CoreManager::getInstance(); // Deal with auto-answer. if (!isOutgoing()) { SettingsModel *settings = coreManager->getSettingsModel(); if (settings->getAutoAnswerStatus()) { QTimer *timer = new QTimer(this); timer->setInterval(settings->getAutoAnswerDelay()); timer->setSingleShot(true); timer->setObjectName(AutoAnswerObjectName); QObject::connect(timer, &QTimer::timeout, this, &CallModel::acceptWithAutoAnswerDelay); timer->start(); } } CoreHandlers *coreHandlers = coreManager->getHandlers().get(); QObject::connect(coreHandlers, &CoreHandlers::callStateChanged, this, &CallModel::handleCallStateChanged ); QObject::connect(coreHandlers, &CoreHandlers::callEncryptionChanged, this, &CallModel::handleCallEncryptionChanged ); // Update fields and make a search to know to who the call belong mMagicSearch = CoreManager::getInstance()->getCore()->createMagicSearch(); mSearch = std::make_shared(this); QObject::connect(mSearch.get(), SIGNAL(searchReceived(std::list> )), this, SLOT(searchReceived(std::list>))); mMagicSearch->addListener(mSearch); if(mCall) { mRemoteAddress = mCall->getRemoteAddress()->clone(); if(mCall->getConference()) mConferenceModel = ConferenceModel::create(mCall->getConference()); } mMagicSearch->getContactListFromFilterAsync(mRemoteAddress->getUsername(),mRemoteAddress->getDomain()); } CallModel::~CallModel () { mMagicSearch->removeListener(mSearch); if(mCall) mCall->unsetData("call-model"); } // ----------------------------------------------------------------------------- QString CallModel::getPeerAddress () const { return Utils::coreStringToAppString(mRemoteAddress->asStringUriOnly()); } QString CallModel::getLocalAddress () const { return mCall ? Utils::coreStringToAppString(mCall->getCallLog()->getLocalAddress()->asStringUriOnly()) : ""; } QString CallModel::getFullPeerAddress () const { return Utils::coreStringToAppString(mRemoteAddress->asString()); } QString CallModel::getFullLocalAddress () const { return mCall ? Utils::coreStringToAppString(mCall->getCallLog()->getLocalAddress()->asString()) : ""; } // ----------------------------------------------------------------------------- ContactModel *CallModel::getContactModel() const{ QString cleanedAddress = mCall ? Utils::cleanSipAddress(Utils::coreStringToAppString(mCall->getRemoteAddress()->asString())) : ""; return CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(cleanedAddress).get(); } ChatRoomModel * CallModel::getChatRoomModel() const{ if(mCall && mCall->getCallLog()->getCallId() != "") { auto currentParams = mCall->getCurrentParams(); bool isEncrypted = currentParams->getMediaEncryption() != linphone::MediaEncryption::None; SettingsModel * settingsModel = CoreManager::getInstance()->getSettingsModel(); if( mCall->getChatRoom() && (settingsModel->getSecureChatEnabled() && (!settingsModel->getStandardChatEnabled() || (settingsModel->getStandardChatEnabled() && isEncrypted)) )){ std::shared_ptr core = CoreManager::getInstance()->getCore(); std::shared_ptr dbParams = mCall->getChatRoom()->getCurrentParams(); std::shared_ptr params = core->createDefaultChatRoomParams(); auto callLog = mCall->getCallLog(); auto callLocalAddress = callLog->getLocalAddress(); std::list> participants; std::shared_ptr chatRoom; // Copy parameters params->setBackend(dbParams->getBackend()); params->setEncryptionBackend(dbParams->getEncryptionBackend()); params->enableEncryption(dbParams->encryptionEnabled()); params->enableGroup(dbParams->groupEnabled()); params->enableRtt(dbParams->rttEnabled()); params->setSubject(dbParams->getSubject()); params->enableEncryption(true); std::list> chatRoomParticipants = mCall->getChatRoom()->getParticipants(); for(auto p : chatRoomParticipants){ participants.push_back(p->getAddress()->clone()); } chatRoom = core->searchChatRoom(params, callLocalAddress , nullptr , participants); if(chatRoom) return CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoom, true).get(); } return CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(mCall->getChatRoom(), true).get(); }else return nullptr; } ConferenceModel * CallModel::getConferenceModel(){ return getConferenceSharedModel().get(); } QSharedPointer CallModel::getConferenceSharedModel(){ if(mCall->getConference() && !mConferenceModel){ mConferenceModel = ConferenceModel::create(mCall->getConference()); emit conferenceModelChanged(); } return mConferenceModel; } bool CallModel::isConference () const{ return mCall->getConference() != nullptr; } // ----------------------------------------------------------------------------- void CallModel::setRecordFile (const shared_ptr &callParams) { callParams->setRecordFile(Utils::appStringToCoreString( CoreManager::getInstance()->getSettingsModel()->getSavedCallsFolder() .append(generateSavedFilename()) .append(".mkv") )); } void CallModel::setRecordFile (const shared_ptr &callParams, const QString &to) { const QString from( Utils::coreStringToAppString( CoreManager::getInstance()->getAccountSettingsModel()->getUsedSipAddress()->getUsername() ) ); callParams->setRecordFile(Utils::appStringToCoreString( CoreManager::getInstance()->getSettingsModel()->getSavedCallsFolder() .append(generateSavedFilename(from, to)) .append(".mkv") )); } // ----------------------------------------------------------------------------- void CallModel::updateStats (const shared_ptr &callStats) { switch (callStats->getType()) { case linphone::StreamType::Text: case linphone::StreamType::Unknown: break; case linphone::StreamType::Audio: updateStats(callStats, mAudioStats); break; case linphone::StreamType::Video: updateStats(callStats, mVideoStats); break; } emit statsUpdated(); } // ----------------------------------------------------------------------------- float CallModel::getSpeakerVolumeGain () const { float gain = mCall ? mCall->getSpeakerVolumeGain() : 0; if( gain < 0) gain = CoreManager::getInstance()->getSettingsModel()->getPlaybackGain(); return gain; } void CallModel::setSpeakerVolumeGain (float volume) { Q_ASSERT(volume >= 0.0f && volume <= 1.0f); if( mCall && mCall->getSpeakerVolumeGain() >= 0) mCall->setSpeakerVolumeGain(volume); else CoreManager::getInstance()->getSettingsModel()->setPlaybackGain(volume); emit speakerVolumeGainChanged(getSpeakerVolumeGain()); } float CallModel::getMicroVolumeGain () const { float gain = mCall ? mCall->getMicrophoneVolumeGain() : 0.0; if( gain < 0) gain = CoreManager::getInstance()->getSettingsModel()->getCaptureGain(); return gain; } void CallModel::setMicroVolumeGain (float volume) { Q_ASSERT(volume >= 0.0f && volume <= 1.0f); if(mCall && mCall->getMicrophoneVolumeGain() >= 0) mCall->setMicrophoneVolumeGain(volume); else CoreManager::getInstance()->getSettingsModel()->setCaptureGain(volume); emit microVolumeGainChanged(getMicroVolumeGain()); } // ----------------------------------------------------------------------------- void CallModel::notifyCameraFirstFrameReceived (unsigned int width, unsigned int height) { if (mNotifyCameraFirstFrameReceived) { mNotifyCameraFirstFrameReceived = false; emit cameraFirstFrameReceived(width, height); } } // ----------------------------------------------------------------------------- void CallModel::accept () { accept(false); } void CallModel::acceptWithVideo () { accept(true); } void CallModel::terminate () { CoreManager *core = CoreManager::getInstance(); core->lockVideoRender(); if(mCall) mCall->terminate(); core->unlockVideoRender(); } // ----------------------------------------------------------------------------- void CallModel::askForTransfer () { CoreManager::getInstance()->getCallsListModel()->askForTransfer(this); } void CallModel::askForAttendedTransfer () { CoreManager::getInstance()->getCallsListModel()->askForAttendedTransfer(this); } bool CallModel::transferTo (const QString &sipAddress) { bool failure = mCall ? !!mCall->transferTo(Utils::interpretUrl(sipAddress)) : false; if (failure) qWarning() << QStringLiteral("Unable to transfer: `%1`.").arg(sipAddress); return !failure; } bool CallModel::transferToAnother (const QString &peerAddress) { qInfo() << QStringLiteral("Transferring to another: `%1`.").arg(peerAddress); CallModel *transferCallModel = CoreManager::getInstance()->getCallsListModel()->findCallModelFromPeerAddress(peerAddress); if (transferCallModel == nullptr) { qWarning() << QStringLiteral("Unable to transfer to another: `%1` (peer not found)").arg(peerAddress); return false; } bool failure = mCall ? !!transferCallModel->mCall->transferToAnother(mCall) : false; if (failure) qWarning() << QStringLiteral("Unable to transfer to another: `%1` (transfer failed)").arg(peerAddress); return !failure; } // ----------------------------------------------------------------------------- void CallModel::acceptVideoRequest () { if(mCall) { shared_ptr params = CoreManager::getInstance()->getCore()->createCallParams(mCall); params->enableVideo(true); mCall->acceptUpdate(params); } } void CallModel::rejectVideoRequest () { if(mCall) { shared_ptr params = CoreManager::getInstance()->getCore()->createCallParams(mCall); params->enableVideo(false); mCall->acceptUpdate(params); } } void CallModel::takeSnapshot () { static QString oldName; QString newName(generateSavedFilename().append(".jpg")); if (newName == oldName) { qWarning() << QStringLiteral("Unable to take snapshot. Wait one second."); return; } oldName = newName; qInfo() << QStringLiteral("Take snapshot of call:") << this; const QString filePath(CoreManager::getInstance()->getSettingsModel()->getSavedScreenshotsFolder().append(newName)); if(mCall) mCall->takeVideoSnapshot(Utils::appStringToCoreString(filePath)); App::getInstance()->getNotifier()->notifySnapshotWasTaken(filePath); } void CallModel::startRecording () { if (mRecording) return; qInfo() << QStringLiteral("Start recording call:") << this; if(mCall) mCall->startRecording(); mRecording = true; emit recordingChanged(true); } void CallModel::stopRecording () { if (!mRecording) return; qInfo() << QStringLiteral("Stop recording call:") << this; mRecording = false; if(mCall) { mCall->stopRecording(); App::getInstance()->getNotifier()->notifyRecordingCompleted( Utils::coreStringToAppString(mCall->getParams()->getRecordFile()) ); } emit recordingChanged(false); } // ----------------------------------------------------------------------------- void CallModel::handleCallEncryptionChanged (const shared_ptr &call) { if (call == mCall) emit securityUpdated(); } void CallModel::handleCallStateChanged (const shared_ptr &call, linphone::Call::State state) { if (call != mCall) return; updateIsInConference(); switch (state) { case linphone::Call::State::Error: case linphone::Call::State::End: setCallErrorFromReason(call->getReason()); stopAutoAnswerTimer(); stopRecording(); mPausedByRemote = false; break; case linphone::Call::State::StreamsRunning: { if (!mWasConnected && CoreManager::getInstance()->getSettingsModel()->getAutomaticallyRecordCalls()) { startRecording(); mWasConnected = true; } mPausedByRemote = false; break; } case linphone::Call::State::Connected: case linphone::Call::State::Referred: case linphone::Call::State::Released: mPausedByRemote = false; break; case linphone::Call::State::PausedByRemote: mNotifyCameraFirstFrameReceived = true; mPausedByRemote = true; break; case linphone::Call::State::Pausing: mNotifyCameraFirstFrameReceived = true; mPausedByUser = true; break; case linphone::Call::State::Resuming: mPausedByUser = false; break; case linphone::Call::State::UpdatedByRemote: qWarning() << "UpdatedByRemote :" << (mCall ? mCall->getCurrentParams()->videoEnabled() + QString(" ")+mCall->getRemoteParams()->videoEnabled() : " call NULL"); if (mCall && !mCall->getCurrentParams()->videoEnabled() && mCall->getRemoteParams()->videoEnabled()) { mCall->deferUpdate(); emit videoRequested(); } break; case linphone::Call::State::Idle: case linphone::Call::State::IncomingReceived: case linphone::Call::State::OutgoingInit: case linphone::Call::State::OutgoingProgress: case linphone::Call::State::OutgoingRinging: case linphone::Call::State::OutgoingEarlyMedia: case linphone::Call::State::Paused: case linphone::Call::State::IncomingEarlyMedia: case linphone::Call::State::Updating: case linphone::Call::State::EarlyUpdatedByRemote: case linphone::Call::State::EarlyUpdating: break; } emit statusChanged(getStatus()); } // ----------------------------------------------------------------------------- void CallModel::accept (bool withVideo) { stopAutoAnswerTimer(); CoreManager *coreManager = CoreManager::getInstance(); QQuickWindow *callsWindow = App::getInstance()->getCallsWindow(); if (callsWindow) { if (coreManager->getSettingsModel()->getKeepCallsWindowInBackground()) { if (!callsWindow->isVisible()) callsWindow->showMinimized(); } else App::smartShowWindow(callsWindow); } qApp->processEvents(); // Process GUI events before accepting in order to be synchronized with Call objects and be ready to get SDK events shared_ptr core = coreManager->getCore(); if(mCall) { shared_ptr params = core->createCallParams(mCall); params->enableVideo(withVideo); setRecordFile(params); auto localAddress = mCall->getCallLog()->getLocalAddress(); std::list> proxies = core->getProxyConfigList() ; for(auto proxy : proxies){ if(proxy->getIdentityAddress()->weakEqual(localAddress)) { params->setProxyConfig(proxy); break; } } mCall->acceptWithParams(params); } } // ----------------------------------------------------------------------------- void CallModel::updateIsInConference () { if (mIsInConference != (mCall && mCall->getCurrentParams()->getLocalConferenceMode() )) { mIsInConference = !mIsInConference; } emit isInConferenceChanged(mIsInConference); } // ----------------------------------------------------------------------------- void CallModel::stopAutoAnswerTimer () const { QTimer *timer = findChild(AutoAnswerObjectName, Qt::FindDirectChildrenOnly); if (timer) { timer->stop(); timer->deleteLater(); } } // ----------------------------------------------------------------------------- CallModel::CallStatus CallModel::getStatus () const { if(mCall){ switch (mCall->getState()) { case linphone::Call::State::Connected: case linphone::Call::State::StreamsRunning: return CallStatusConnected; case linphone::Call::State::End: case linphone::Call::State::Error: case linphone::Call::State::Referred: case linphone::Call::State::Released: return CallStatusEnded; case linphone::Call::State::Paused: case linphone::Call::State::PausedByRemote: case linphone::Call::State::Pausing: case linphone::Call::State::Resuming: return CallStatusPaused; case linphone::Call::State::Updating: case linphone::Call::State::UpdatedByRemote: return mPausedByRemote ? CallStatusPaused : CallStatusConnected; case linphone::Call::State::EarlyUpdatedByRemote: case linphone::Call::State::EarlyUpdating: case linphone::Call::State::Idle: case linphone::Call::State::IncomingEarlyMedia: case linphone::Call::State::IncomingReceived: case linphone::Call::State::OutgoingEarlyMedia: case linphone::Call::State::OutgoingInit: case linphone::Call::State::OutgoingProgress: case linphone::Call::State::OutgoingRinging: break; } return mCall->getDir() == linphone::Call::Dir::Incoming ? CallStatusIncoming : CallStatusOutgoing; }else return CallStatusIdle; } // ----------------------------------------------------------------------------- void CallModel::acceptWithAutoAnswerDelay () { CoreManager *coreManager = CoreManager::getInstance(); SettingsModel *settingsModel = coreManager->getSettingsModel(); // Use auto-answer if activated and it's the only call. if (settingsModel->getAutoAnswerStatus() && coreManager->getCore()->getCallsNb() == 1) { if (mCall && mCall->getRemoteParams()->videoEnabled() && settingsModel->getAutoAnswerVideoStatus() && settingsModel->getVideoSupported()) acceptWithVideo(); else accept(); } } // ----------------------------------------------------------------------------- QString CallModel::getCallError () const { return mCallError; } void CallModel::setCallErrorFromReason (linphone::Reason reason) { switch (reason) { case linphone::Reason::Declined: mCallError = tr("callErrorDeclined"); break; case linphone::Reason::NotFound: mCallError = tr("callErrorNotFound"); break; case linphone::Reason::Busy: mCallError = tr("callErrorBusy"); break; case linphone::Reason::NotAcceptable: mCallError = tr("callErrorNotAcceptable"); break; default: break; } if (!mCallError.isEmpty()) qInfo() << QStringLiteral("Call terminated with error (%1):").arg(mCallError) << this; emit callErrorChanged(mCallError); } // ----------------------------------------------------------------------------- int CallModel::getDuration () const { return mCall ? mCall->getDuration() : 0; } float CallModel::getQuality () const { return mCall ? mCall->getCurrentQuality() : 0.0; } // ----------------------------------------------------------------------------- float CallModel::getSpeakerVu () const { if (mCall && mCall->getState() == linphone::Call::State::StreamsRunning) return MediastreamerUtils::computeVu(mCall->getPlayVolume()); return 0.0; } float CallModel::getMicroVu () const { if (mCall && mCall->getState() == linphone::Call::State::StreamsRunning) return MediastreamerUtils::computeVu(mCall->getRecordVolume()); return 0.0; } // ----------------------------------------------------------------------------- bool CallModel::getSpeakerMuted () const { return mCall && mCall->getSpeakerMuted(); } void CallModel::setSpeakerMuted (bool status) { if (status == getSpeakerMuted()) return; if(mCall) mCall->setSpeakerMuted(status); emit speakerMutedChanged(getSpeakerMuted()); } // ----------------------------------------------------------------------------- bool CallModel::getMicroMuted () const { return mCall && mCall->getMicrophoneMuted(); } void CallModel::setMicroMuted (bool status) { if (status == getMicroMuted()) return; if(mCall) mCall->setMicrophoneMuted(status); emit microMutedChanged(getMicroMuted()); } // ----------------------------------------------------------------------------- bool CallModel::getPausedByUser () const { return mPausedByUser; } void CallModel::setPausedByUser (bool status) { if(mCall){ switch (mCall->getState()) { case linphone::Call::State::Connected: case linphone::Call::State::StreamsRunning: case linphone::Call::State::Paused: case linphone::Call::State::PausedByRemote: break; default: return; } if (status) { if (!mPausedByUser) mCall->pause(); return; } if (mPausedByUser) mCall->resume(); } } // ----------------------------------------------------------------------------- bool CallModel::getRemoteVideoEnabled () const { shared_ptr params = mCall->getRemoteParams(); return params && params->videoEnabled(); } bool CallModel::getVideoEnabled () const { if(mCall){ shared_ptr params = mCall->getCurrentParams(); return params && params->videoEnabled() && getStatus() == CallStatusConnected && mCall->getState() == linphone::Call::State::StreamsRunning; }else return true; } void CallModel::setVideoEnabled (bool status) { shared_ptr core = CoreManager::getInstance()->getCore(); if (!core->videoSupported()) { qWarning() << QStringLiteral("Unable to update video call property. (Video not supported.)"); return; } if(mCall) { switch (mCall->getState()) { case linphone::Call::State::Connected: case linphone::Call::State::StreamsRunning: break; default: { qWarning() << "Cannot set Video mode because of call status : " << (int)mCall->getState() << " is not in {" <<(int)linphone::Call::State::Connected << ", " <<(int)linphone::Call::State::StreamsRunning << "}"; return; } } if (status == getVideoEnabled()) return; shared_ptr params = core->createCallParams(mCall); params->enableVideo(status); mCall->update(params); } } // ----------------------------------------------------------------------------- bool CallModel::getUpdating () const { if(mCall) { switch (mCall->getState()) { case linphone::Call::State::Connected: case linphone::Call::State::StreamsRunning: case linphone::Call::State::Paused: case linphone::Call::State::PausedByRemote: return false; default: break; } } return true; } bool CallModel::getRecording () const { return mRecording; } // ----------------------------------------------------------------------------- void CallModel::sendDtmf (const QString &dtmf) { const char key = dtmf.constData()[0].toLatin1(); qInfo() << QStringLiteral("Send dtmf: `%1`.").arg(key); if(mCall) mCall->sendDtmf(key); CoreManager::getInstance()->getCore()->playDtmf(key, DtmfSoundDelay); } // ----------------------------------------------------------------------------- void CallModel::verifyAuthenticationToken (bool verify) { if(mCall) mCall->setAuthenticationTokenVerified(verify); emit securityUpdated(); } // ----------------------------------------------------------------------------- void CallModel::updateStreams () { if(mCall) mCall->update(nullptr); } void CallModel::toggleSpeakerMute(){ setSpeakerMuted(!getSpeakerMuted()); } // ----------------------------------------------------------------------------- // Set remote display name when a search has been done void CallModel::searchReceived(std::list> results){ bool found = false; for(auto it = results.begin() ; it != results.end() && !found ; ++it){ if((*it)->getFriend()){ if((*it)->getFriend()->getAddress()->weakEqual(mRemoteAddress)){ setRemoteDisplayName((*it)->getFriend()->getName()); found = true; } }else{ if((*it)->getAddress()->weakEqual(mRemoteAddress)){ std::string newDisplayName = (*it)->getAddress()->getDisplayName(); if(!newDisplayName.empty())// Override only if there is one setRemoteDisplayName(newDisplayName); found = true; } } } } void CallModel::callEnded(){ if(mCall){ ChatRoomModel * model = getChatRoomModel(); if(model){ model->callEnded(mCall); }else{// No chat rooms have been associated for this call. Search one in current chat room list shared_ptr core = CoreManager::getInstance()->getCore(); std::shared_ptr params = core->createDefaultChatRoomParams(); std::list> participants; auto chatRoom = core->searchChatRoom(params, mCall->getCallLog()->getLocalAddress() , mCall->getRemoteAddress() , participants); QSharedPointer chatRoomModel= CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoom, false); if(chatRoomModel) chatRoomModel->callEnded(mCall); } } } void CallModel::setRemoteDisplayName(const std::string& name){ mRemoteAddress->setDisplayName(name); if(mCall) { auto callLog = mCall->getCallLog(); if(name!= "") { auto core = CoreManager::getInstance()->getCore(); auto address = Utils::interpretUrl(getFullPeerAddress()); callLog->setRemoteAddress(address); } } emit fullPeerAddressChanged(); ChatRoomModel * model = getChatRoomModel(); if(model) model->emitFullPeerAddressChanged(); } QString CallModel::getTransferAddress () const { return mTransferAddress; } void CallModel::setTransferAddress (const QString &transferAddress) { mTransferAddress = transferAddress; emit transferAddressChanged(mTransferAddress); } void CallModel::prepareTransfert(shared_ptr call, const QString& transfertAddress){ if( call && transfertAddress != ""){ CallModel * model = &call->getData("call-model"); model->setTransferAddress(transfertAddress); } } std::shared_ptr CallModel::getRemoteAddress()const{ return mRemoteAddress; } // ----------------------------------------------------------------------------- CallModel::CallEncryption CallModel::getEncryption () const { if(mCall) return static_cast(mCall->getCurrentParams()->getMediaEncryption()); else return CallEncryptionNone; } bool CallModel::isSecured () const { if(mCall){ shared_ptr params = mCall->getCurrentParams(); linphone::MediaEncryption encryption = params->getMediaEncryption(); return ( encryption == linphone::MediaEncryption::ZRTP && mCall->getAuthenticationTokenVerified() ) || encryption == linphone::MediaEncryption::SRTP || encryption == linphone::MediaEncryption::DTLS; }else return false; } // ----------------------------------------------------------------------------- QString CallModel::getLocalSas () const { if(mCall){ QString token = Utils::coreStringToAppString(mCall->getAuthenticationToken()); return mCall->getDir() == linphone::Call::Dir::Incoming ? token.left(2).toUpper() : token.right(2).toUpper(); }else return ""; } QString CallModel::getRemoteSas () const { if(mCall){ QString token = Utils::coreStringToAppString(mCall->getAuthenticationToken()); return mCall->getDir() != linphone::Call::Dir::Incoming ? token.left(2).toUpper() : token.right(2).toUpper(); }else return ""; } // ----------------------------------------------------------------------------- QString CallModel::getSecuredString () const { if(mCall){ switch (mCall->getCurrentParams()->getMediaEncryption()) { case linphone::MediaEncryption::SRTP: return QStringLiteral("SRTP"); case linphone::MediaEncryption::ZRTP: return QStringLiteral("ZRTP"); case linphone::MediaEncryption::DTLS: return QStringLiteral("DTLS"); case linphone::MediaEncryption::None: break; } } return QString(""); } // ----------------------------------------------------------------------------- QVariantList CallModel::getAudioStats () const { return mAudioStats; } QVariantList CallModel::getVideoStats () const { return mVideoStats; } // ----------------------------------------------------------------------------- static inline QVariantMap createStat (const QString &key, const QString &value) { QVariantMap m; m["key"] = key; m["value"] = value; return m; } void CallModel::updateStats (const shared_ptr &callStats, QVariantList &statsList) { if(mCall){ shared_ptr params = mCall->getCurrentParams(); shared_ptr payloadType; switch (callStats->getType()) { case linphone::StreamType::Audio: payloadType = params->getUsedAudioPayloadType(); break; case linphone::StreamType::Video: payloadType = params->getUsedVideoPayloadType(); break; default: return; } QString family; switch (callStats->getIpFamilyOfRemote()) { case linphone::AddressFamily::Inet: family = QStringLiteral("IPv4"); break; case linphone::AddressFamily::Inet6: family = QStringLiteral("IPv6"); break; default: family = QStringLiteral("Unknown"); break; } statsList.clear(); statsList << createStat(tr("callStatsCodec"), payloadType ? QStringLiteral("%1 / %2kHz").arg(Utils::coreStringToAppString(payloadType->getMimeType())).arg(payloadType->getClockRate() / 1000) : QString("")); statsList << createStat(tr("callStatsUploadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getUploadBandwidth()))); statsList << createStat(tr("callStatsDownloadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getDownloadBandwidth()))); statsList << createStat(tr("callStatsIceState"), iceStateToString(callStats->getIceState())); statsList << createStat(tr("callStatsIpFamily"), family); statsList << createStat(tr("callStatsSenderLossRate"), QStringLiteral("%1 %").arg(static_cast(callStats->getSenderLossRate()))); statsList << createStat(tr("callStatsReceiverLossRate"), QStringLiteral("%1 %").arg(static_cast(callStats->getReceiverLossRate()))); switch (callStats->getType()) { case linphone::StreamType::Audio: statsList << createStat(tr("callStatsJitterBuffer"), QStringLiteral("%1 ms").arg(callStats->getJitterBufferSizeMs())); break; case linphone::StreamType::Video: { statsList << createStat(tr("callStatsEstimatedDownloadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getEstimatedDownloadBandwidth()))); const QString sentVideoDefinitionName = Utils::coreStringToAppString(params->getSentVideoDefinition()->getName()); const QString sentVideoDefinition = QStringLiteral("%1x%2") .arg(params->getSentVideoDefinition()->getWidth()) .arg(params->getSentVideoDefinition()->getHeight()); statsList << createStat(tr("callStatsSentVideoDefinition"), sentVideoDefinition == sentVideoDefinitionName ? sentVideoDefinition : QStringLiteral("%1 (%2)").arg(sentVideoDefinition).arg(sentVideoDefinitionName)); const QString receivedVideoDefinitionName = Utils::coreStringToAppString(params->getReceivedVideoDefinition()->getName()); const QString receivedVideoDefinition = QString("%1x%2") .arg(params->getReceivedVideoDefinition()->getWidth()) .arg(params->getReceivedVideoDefinition()->getHeight()); statsList << createStat(tr("callStatsReceivedVideoDefinition"), receivedVideoDefinition == receivedVideoDefinitionName ? receivedVideoDefinition : QString("%1 (%2)").arg(receivedVideoDefinition).arg(receivedVideoDefinitionName)); statsList << createStat(tr("callStatsReceivedFramerate"), QStringLiteral("%1 FPS").arg(static_cast(params->getReceivedFramerate()))); statsList << createStat(tr("callStatsSentFramerate"), QStringLiteral("%1 FPS").arg(static_cast(params->getSentFramerate()))); } break; default: break; } } } // ----------------------------------------------------------------------------- QString CallModel::iceStateToString (linphone::IceState state) const { switch (state) { case linphone::IceState::NotActivated: return tr("iceStateNotActivated"); case linphone::IceState::Failed: return tr("iceStateFailed"); case linphone::IceState::InProgress: return tr("iceStateInProgress"); case linphone::IceState::ReflexiveConnection: return tr("iceStateReflexiveConnection"); case linphone::IceState::HostConnection: return tr("iceStateHostConnection"); case linphone::IceState::RelayConnection: return tr("iceStateRelayConnection"); } return tr("iceStateInvalid"); } // ----------------------------------------------------------------------------- QString CallModel::generateSavedFilename () const { const shared_ptr callLog(mCall->getCallLog()); return generateSavedFilename( Utils::coreStringToAppString(callLog->getFromAddress()->getUsername()), Utils::coreStringToAppString(callLog->getToAddress()->getUsername()) ); } QString CallModel::generateSavedFilename (const QString &from, const QString &to) { auto escape = [](const QString &str) { constexpr char ReservedCharacters[] = "[<|>|:|\"|/|\\\\|\\?|\\*|\\+|\\|]+"; static QRegularExpression regexp(ReservedCharacters); return QString(str).replace(regexp, ""); }; return QStringLiteral("%1_%2_%3") .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss")) .arg(escape(from)) .arg(escape(to)); }