clang-format

This commit is contained in:
trilene 2020-08-01 14:31:10 -04:00
parent f14d141cb5
commit e3e7595bab
13 changed files with 918 additions and 852 deletions

View File

@ -33,8 +33,7 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
layout_ = new QHBoxLayout(this); layout_ = new QHBoxLayout(this);
layout_->setSpacing(widgetMargin); layout_->setSpacing(widgetMargin);
layout_->setContentsMargins( layout_->setContentsMargins(2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
QFont labelFont; QFont labelFont;
labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1); labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1);
@ -70,8 +69,7 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
layout_->addSpacing(18); layout_->addSpacing(18);
timer_ = new QTimer(this); timer_ = new QTimer(this);
connect(timer_, &QTimer::timeout, this, connect(timer_, &QTimer::timeout, this, [this]() {
[this](){
auto seconds = QDateTime::currentSecsSinceEpoch() - callStartTime_; auto seconds = QDateTime::currentSecsSinceEpoch() - callStartTime_;
int s = seconds % 60; int s = seconds % 60;
int m = (seconds / 60) % 60; int m = (seconds / 60) % 60;
@ -84,7 +82,8 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
durationLabel_->setText(buf); durationLabel_->setText(buf);
}); });
connect(&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &ActiveCallBar::update); connect(
&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &ActiveCallBar::update);
} }
void void
@ -103,14 +102,12 @@ ActiveCallBar::setMuteIcon(bool muted)
} }
void void
ActiveCallBar::setCallParty( ActiveCallBar::setCallParty(const QString &userid,
const QString &userid,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl) const QString &avatarUrl)
{ {
callPartyLabel_->setText(" " + callPartyLabel_->setText(" " + (displayName.isEmpty() ? userid : displayName) + " ");
(displayName.isEmpty() ? userid : displayName) + " ");
if (!avatarUrl.isEmpty()) if (!avatarUrl.isEmpty())
avatar_->setImage(avatarUrl); avatar_->setImage(avatarUrl);
@ -142,8 +139,8 @@ ActiveCallBar::update(WebRTCSession::State state)
show(); show();
callStartTime_ = QDateTime::currentSecsSinceEpoch(); callStartTime_ = QDateTime::currentSecsSinceEpoch();
timer_->start(1000); timer_->start(1000);
stateLabel_->setPixmap(QIcon(":/icons/icons/ui/place-call.png"). stateLabel_->setPixmap(
pixmap(QSize(buttonSize_, buttonSize_))); QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(buttonSize_, buttonSize_)));
durationLabel_->setText("00:00"); durationLabel_->setText("00:00");
durationLabel_->show(); durationLabel_->show();
break; break;

View File

@ -19,8 +19,7 @@ public:
public slots: public slots:
void update(WebRTCSession::State); void update(WebRTCSession::State);
void setCallParty( void setCallParty(const QString &userid,
const QString &userid,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl); const QString &avatarUrl);

View File

@ -1,13 +1,13 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <cstdint>
#include <chrono> #include <chrono>
#include <cstdint>
#include <QMediaPlaylist> #include <QMediaPlaylist>
#include <QUrl> #include <QUrl>
#include "CallManager.h"
#include "Cache.h" #include "Cache.h"
#include "CallManager.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "Logging.h" #include "Logging.h"
#include "MainWindow.h" #include "MainWindow.h"
@ -34,52 +34,56 @@ getTurnURIs(const mtx::responses::TurnServer &turnServer);
} }
CallManager::CallManager(QSharedPointer<UserSettings> userSettings) CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
: QObject(), : QObject()
session_(WebRTCSession::instance()), , session_(WebRTCSession::instance())
turnServerTimer_(this), , turnServerTimer_(this)
settings_(userSettings) , settings_(userSettings)
{ {
qRegisterMetaType<std::vector<mtx::events::msg::CallCandidates::Candidate>>(); qRegisterMetaType<std::vector<mtx::events::msg::CallCandidates::Candidate>>();
qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>(); qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
qRegisterMetaType<mtx::responses::TurnServer>(); qRegisterMetaType<mtx::responses::TurnServer>();
connect(&session_, &WebRTCSession::offerCreated, this, connect(
[this](const std::string &sdp, &session_,
const std::vector<CallCandidates::Candidate> &candidates) &WebRTCSession::offerCreated,
{ this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_); nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_}); emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
QTimer::singleShot(timeoutms_, this, [this]() { QTimer::singleShot(timeoutms_, this, [this]() {
if (session_.state() == WebRTCSession::State::OFFERSENT) { if (session_.state() == WebRTCSession::State::OFFERSENT) {
hangUp(CallHangUp::Reason::InviteTimeOut); hangUp(CallHangUp::Reason::InviteTimeOut);
emit ChatPage::instance()->showNotification("The remote side failed to pick up."); emit ChatPage::instance()->showNotification(
"The remote side failed to pick up.");
} }
}); });
}); });
connect(&session_, &WebRTCSession::answerCreated, this, connect(
[this](const std::string &sdp, &session_,
const std::vector<CallCandidates::Candidate> &candidates) &WebRTCSession::answerCreated,
{ this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_); nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_);
emit newMessage(roomid_, CallAnswer{callid_, sdp, 0}); emit newMessage(roomid_, CallAnswer{callid_, sdp, 0});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
}); });
connect(&session_, &WebRTCSession::newICECandidate, this, connect(&session_,
[this](const CallCandidates::Candidate &candidate) &WebRTCSession::newICECandidate,
{ this,
[this](const CallCandidates::Candidate &candidate) {
nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_); nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_);
emit newMessage(roomid_, CallCandidates{callid_, {candidate}, 0}); emit newMessage(roomid_, CallCandidates{callid_, {candidate}, 0});
}); });
connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer); connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer);
connect(this, &CallManager::turnServerRetrieved, this, connect(this,
[this](const mtx::responses::TurnServer &res) &CallManager::turnServerRetrieved,
{ this,
[this](const mtx::responses::TurnServer &res) {
nhlog::net()->info("TURN server(s) retrieved from homeserver:"); nhlog::net()->info("TURN server(s) retrieved from homeserver:");
nhlog::net()->info("username: {}", res.username); nhlog::net()->info("username: {}", res.username);
nhlog::net()->info("ttl: {} seconds", res.ttl); nhlog::net()->info("ttl: {} seconds", res.ttl);
@ -95,21 +99,28 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
turnServerTimer_.setInterval(ttl * 1000 * 0.9); turnServerTimer_.setInterval(ttl * 1000 * 0.9);
}); });
connect(&session_, &WebRTCSession::stateChanged, this, connect(&session_, &WebRTCSession::stateChanged, this, [this](WebRTCSession::State state) {
[this](WebRTCSession::State state) { switch (state) {
if (state == WebRTCSession::State::DISCONNECTED) { case WebRTCSession::State::DISCONNECTED:
playRingtone("qrc:/media/media/callend.ogg", false); playRingtone("qrc:/media/media/callend.ogg", false);
} clear();
else if (state == WebRTCSession::State::ICEFAILED) { break;
case WebRTCSession::State::ICEFAILED: {
QString error("Call connection failed."); QString error("Call connection failed.");
if (turnURIs_.empty()) if (turnURIs_.empty())
error += " Your homeserver has no configured TURN server."; error += " Your homeserver has no configured TURN server.";
emit ChatPage::instance()->showNotification(error); emit ChatPage::instance()->showNotification(error);
hangUp(CallHangUp::Reason::ICEFailed); hangUp(CallHangUp::Reason::ICEFailed);
break;
}
default:
break;
} }
}); });
connect(&player_, &QMediaPlayer::mediaStatusChanged, this, connect(&player_,
&QMediaPlayer::mediaStatusChanged,
this,
[this](QMediaPlayer::MediaStatus status) { [this](QMediaPlayer::MediaStatus status) {
if (status == QMediaPlayer::LoadedMedia) if (status == QMediaPlayer::LoadedMedia)
player_.play(); player_.play();
@ -124,7 +135,8 @@ CallManager::sendInvite(const QString &roomid)
auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
if (roomInfo.member_count != 2) { if (roomInfo.member_count != 2) {
emit ChatPage::instance()->showNotification("Voice calls are limited to 1:1 rooms."); emit ChatPage::instance()->showNotification(
"Voice calls are limited to 1:1 rooms.");
return; return;
} }
@ -141,9 +153,12 @@ CallManager::sendInvite(const QString &roomid)
generateCallID(); generateCallID();
nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_); nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_);
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString())); std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front(); const RoomMember &callee =
emit newCallParty(callee.user_id, callee.display_name, members.front().user_id == utils::localUser() ? members.back() : members.front();
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.avatar_url)); emit newCallParty(callee.user_id,
callee.display_name,
QString::fromStdString(roomInfo.name),
QString::fromStdString(roomInfo.avatar_url));
playRingtone("qrc:/media/media/ringback.ogg", true); playRingtone("qrc:/media/media/ringback.ogg", true);
if (!session_.createOffer()) { if (!session_.createOffer()) {
emit ChatPage::instance()->showNotification("Problem setting up call."); emit ChatPage::instance()->showNotification("Problem setting up call.");
@ -152,7 +167,8 @@ CallManager::sendInvite(const QString &roomid)
} }
namespace { namespace {
std::string callHangUpReasonString(CallHangUp::Reason reason) std::string
callHangUpReasonString(CallHangUp::Reason reason)
{ {
switch (reason) { switch (reason) {
case CallHangUp::Reason::ICEFailed: case CallHangUp::Reason::ICEFailed:
@ -169,8 +185,8 @@ void
CallManager::hangUp(CallHangUp::Reason reason) CallManager::hangUp(CallHangUp::Reason reason)
{ {
if (!callid_.empty()) { if (!callid_.empty()) {
nhlog::ui()->debug("WebRTC: call id: {} - hanging up ({})", callid_, nhlog::ui()->debug(
callHangUpReasonString(reason)); "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason));
emit newMessage(roomid_, CallHangUp{callid_, 0, reason}); emit newMessage(roomid_, CallHangUp{callid_, 0, reason});
endCall(); endCall();
} }
@ -182,10 +198,11 @@ CallManager::onActiveCall()
return session_.state() != WebRTCSession::State::DISCONNECTED; return session_.state() != WebRTCSession::State::DISCONNECTED;
} }
void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) void
CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
{ {
if (handleEvent_<CallInvite>(event) || handleEvent_<CallCandidates>(event) if (handleEvent_<CallInvite>(event) || handleEvent_<CallCandidates>(event) ||
|| handleEvent_<CallAnswer>(event) || handleEvent_<CallHangUp>(event)) handleEvent_<CallAnswer>(event) || handleEvent_<CallHangUp>(event))
return; return;
} }
@ -205,26 +222,28 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
{ {
const char video[] = "m=video"; const char video[] = "m=video";
const std::string &sdp = callInviteEvent.content.sdp; const std::string &sdp = callInviteEvent.content.sdp;
bool isVideo = std::search(sdp.cbegin(), sdp.cend(), std::cbegin(video), std::cend(video) - 1, bool isVideo = std::search(sdp.cbegin(),
[](unsigned char c1, unsigned char c2) {return std::tolower(c1) == std::tolower(c2);}) sdp.cend(),
!= sdp.cend(); std::cbegin(video),
std::cend(video) - 1,
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend();
nhlog::ui()->debug(std::string("WebRTC: call id: {} - incoming ") + (isVideo ? "video" : "voice") + nhlog::ui()->debug(std::string("WebRTC: call id: {} - incoming ") +
" CallInvite from {}", callInviteEvent.content.call_id, callInviteEvent.sender); (isVideo ? "video" : "voice") + " CallInvite from {}",
callInviteEvent.content.call_id,
callInviteEvent.sender);
if (callInviteEvent.content.call_id.empty()) if (callInviteEvent.content.call_id.empty())
return; return;
if (isVideo) {
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
CallHangUp{callInviteEvent.content.call_id, 0, CallHangUp::Reason::InviteTimeOut});
return;
}
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id); auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
if (onActiveCall() || roomInfo.member_count != 2) { if (onActiveCall() || roomInfo.member_count != 2 || isVideo) {
emit newMessage(QString::fromStdString(callInviteEvent.room_id), emit newMessage(QString::fromStdString(callInviteEvent.room_id),
CallHangUp{callInviteEvent.content.call_id, 0, CallHangUp::Reason::InviteTimeOut}); CallHangUp{callInviteEvent.content.call_id,
0,
CallHangUp::Reason::InviteTimeOut});
return; return;
} }
@ -236,23 +255,24 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id)); std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller = const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front(); members.front().user_id == utils::localUser() ? members.back() : members.front();
emit newCallParty(caller.user_id, caller.display_name, emit newCallParty(caller.user_id,
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.avatar_url)); caller.display_name,
QString::fromStdString(roomInfo.name),
QString::fromStdString(roomInfo.avatar_url));
auto dialog = new dialogs::AcceptCall( auto dialog = new dialogs::AcceptCall(caller.user_id,
caller.user_id,
caller.display_name, caller.display_name,
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.name),
QString::fromStdString(roomInfo.avatar_url), QString::fromStdString(roomInfo.avatar_url),
MainWindow::instance()); MainWindow::instance());
connect(dialog, &dialogs::AcceptCall::accept, this, connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent]() {
[this, callInviteEvent](){
MainWindow::instance()->hideOverlay(); MainWindow::instance()->hideOverlay();
answerInvite(callInviteEvent.content);}); answerInvite(callInviteEvent.content);
connect(dialog, &dialogs::AcceptCall::reject, this, });
[this](){ connect(dialog, &dialogs::AcceptCall::reject, this, [this]() {
MainWindow::instance()->hideOverlay(); MainWindow::instance()->hideOverlay();
hangUp();}); hangUp();
});
MainWindow::instance()->showSolidOverlayModal(dialog); MainWindow::instance()->showSolidOverlayModal(dialog);
} }
@ -286,13 +306,15 @@ CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent)
return; return;
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}", nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}",
callCandidatesEvent.content.call_id, callCandidatesEvent.sender); callCandidatesEvent.content.call_id,
callCandidatesEvent.sender);
if (callid_ == callCandidatesEvent.content.call_id) { if (callid_ == callCandidatesEvent.content.call_id) {
if (onActiveCall()) if (onActiveCall())
session_.acceptICECandidates(callCandidatesEvent.content.candidates); session_.acceptICECandidates(callCandidatesEvent.content.candidates);
else { else {
// CallInvite has been received and we're awaiting localUser to accept or reject the call // CallInvite has been received and we're awaiting localUser to accept or
// reject the call
for (const auto &c : callCandidatesEvent.content.candidates) for (const auto &c : callCandidatesEvent.content.candidates)
remoteICECandidates_.push_back(c); remoteICECandidates_.push_back(c);
} }
@ -303,7 +325,8 @@ void
CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent) CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
{ {
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}", nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}",
callAnswerEvent.content.call_id, callAnswerEvent.sender); callAnswerEvent.content.call_id,
callAnswerEvent.sender);
if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() && if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() &&
callid_ == callAnswerEvent.content.call_id) { callid_ == callAnswerEvent.content.call_id) {
@ -326,7 +349,8 @@ void
CallManager::handleEvent(const RoomEvent<CallHangUp> &callHangUpEvent) CallManager::handleEvent(const RoomEvent<CallHangUp> &callHangUpEvent)
{ {
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}", nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}",
callHangUpEvent.content.call_id, callHangUpReasonString(callHangUpEvent.content.reason), callHangUpEvent.content.call_id,
callHangUpReasonString(callHangUpEvent.content.reason),
callHangUpEvent.sender); callHangUpEvent.sender);
if (callid_ == callHangUpEvent.content.call_id) { if (callid_ == callHangUpEvent.content.call_id) {
@ -344,15 +368,21 @@ CallManager::generateCallID()
} }
void void
CallManager::endCall() CallManager::clear()
{ {
stopRingtone();
session_.end();
roomid_.clear(); roomid_.clear();
callid_.clear(); callid_.clear();
remoteICECandidates_.clear(); remoteICECandidates_.clear();
} }
void
CallManager::endCall()
{
stopRingtone();
clear();
session_.end();
}
void void
CallManager::refreshTurnServer() CallManager::refreshTurnServer()
{ {
@ -378,7 +408,8 @@ CallManager::playRingtone(const QString &ringtone, bool repeat)
{ {
static QMediaPlaylist playlist; static QMediaPlaylist playlist;
playlist.clear(); playlist.clear();
playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop : QMediaPlaylist::CurrentItemOnce); playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop
: QMediaPlaylist::CurrentItemOnce);
playlist.addMedia(QUrl(ringtone)); playlist.addMedia(QUrl(ringtone));
player_.setVolume(100); player_.setVolume(100);
player_.setPlaylist(&playlist); player_.setPlaylist(&playlist);
@ -401,22 +432,22 @@ getTurnURIs(const mtx::responses::TurnServer &turnServer)
if (auto c = uri.find(':'); c == std::string::npos) { if (auto c = uri.find(':'); c == std::string::npos) {
nhlog::ui()->error("Invalid TURN server uri: {}", uri); nhlog::ui()->error("Invalid TURN server uri: {}", uri);
continue; continue;
} } else {
else {
std::string scheme = std::string(uri, 0, c); std::string scheme = std::string(uri, 0, c);
if (scheme != "turn" && scheme != "turns") { if (scheme != "turn" && scheme != "turns") {
nhlog::ui()->error("Invalid TURN server uri: {}", uri); nhlog::ui()->error("Invalid TURN server uri: {}", uri);
continue; continue;
} }
QString encodedUri = QString::fromStdString(scheme) + "://" + QString encodedUri =
QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) + ":" + QString::fromStdString(scheme) + "://" +
QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) + "@" + QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) +
QString::fromStdString(std::string(uri, ++c)); ":" +
QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) +
"@" + QString::fromStdString(std::string(uri, ++c));
ret.push_back(encodedUri.toStdString()); ret.push_back(encodedUri.toStdString());
} }
} }
return ret; return ret;
} }
} }

View File

@ -3,8 +3,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <QObject>
#include <QMediaPlayer> #include <QMediaPlayer>
#include <QObject>
#include <QSharedPointer> #include <QSharedPointer>
#include <QString> #include <QString>
#include <QTimer> #include <QTimer>
@ -27,7 +27,8 @@ public:
CallManager(QSharedPointer<UserSettings>); CallManager(QSharedPointer<UserSettings>);
void sendInvite(const QString &roomid); void sendInvite(const QString &roomid);
void hangUp(mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
bool onActiveCall(); bool onActiveCall();
void refreshTurnServer(); void refreshTurnServer();
@ -40,8 +41,7 @@ signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
void turnServerRetrieved(const mtx::responses::TurnServer &); void turnServerRetrieved(const mtx::responses::TurnServer &);
void newCallParty( void newCallParty(const QString &userid,
const QString &userid,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl); const QString &avatarUrl);
@ -68,6 +68,7 @@ private:
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &); void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
void answerInvite(const mtx::events::msg::CallInvite &); void answerInvite(const mtx::events::msg::CallInvite &);
void generateCallID(); void generateCallID();
void clear();
void endCall(); void endCall();
void playRingtone(const QString &ringtone, bool repeat); void playRingtone(const QString &ringtone, bool repeat);
void stopRingtone(); void stopRingtone();

View File

@ -460,8 +460,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
if (callManager_.onActiveCall()) { if (callManager_.onActiveCall()) {
callManager_.hangUp(); callManager_.hangUp();
} else { } else {
if (auto roomInfo = if (auto roomInfo = cache::singleRoomInfo(current_room_.toStdString());
cache::singleRoomInfo(current_room_.toStdString());
roomInfo.member_count != 2) { roomInfo.member_count != 2) {
showNotification("Voice calls are limited to 1:1 rooms."); showNotification("Voice calls are limited to 1:1 rooms.");
} else { } else {

View File

@ -72,12 +72,19 @@ struct CallType
template<class T> template<class T>
std::string operator()(const T &e) std::string operator()(const T &e)
{ {
if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>, T>) { if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>,
T>) {
const char video[] = "m=video"; const char video[] = "m=video";
const std::string &sdp = e.content.sdp; const std::string &sdp = e.content.sdp;
return std::search(sdp.cbegin(), sdp.cend(), std::cbegin(video), std::cend(video) - 1, return std::search(sdp.cbegin(),
[](unsigned char c1, unsigned char c2) {return std::tolower(c1) == std::tolower(c2);}) sdp.cend(),
!= sdp.cend() ? "video" : "voice"; std::cbegin(video),
std::cend(video) - 1,
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend()
? "video"
: "voice";
} }
return std::string(); return std::string();
} }

View File

@ -1,9 +1,10 @@
#include <cctype> #include <cctype>
#include "WebRTCSession.h"
#include "Logging.h" #include "Logging.h"
#include "WebRTCSession.h"
extern "C" { extern "C"
{
#include "gst/gst.h" #include "gst/gst.h"
#include "gst/sdp/sdp.h" #include "gst/sdp/sdp.h"
@ -13,26 +14,8 @@ extern "C" {
Q_DECLARE_METATYPE(WebRTCSession::State) Q_DECLARE_METATYPE(WebRTCSession::State)
namespace { WebRTCSession::WebRTCSession()
bool isoffering_; : QObject()
std::string localsdp_;
std::vector<mtx::events::msg::CallCandidates::Candidate> localcandidates_;
gboolean newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data);
GstWebRTCSessionDescription* parseSDP(const std::string &sdp, GstWebRTCSDPType type);
void generateOffer(GstElement *webrtc);
void setLocalDescription(GstPromise *promise, gpointer webrtc);
void addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, guint mlineIndex, gchar *candidate, gpointer G_GNUC_UNUSED);
gboolean onICEGatheringCompletion(gpointer timerid);
void iceConnectionStateChanged(GstElement *webrtcbin, GParamSpec *pspec G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED);
void createAnswer(GstPromise *promise, gpointer webrtc);
void addDecodeBin(GstElement *webrtc G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe);
void linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe);
std::string::const_iterator findName(const std::string &sdp, const std::string &name);
int getPayloadType(const std::string &sdp, const std::string &name);
}
WebRTCSession::WebRTCSession() : QObject()
{ {
qRegisterMetaType<WebRTCSession::State>(); qRegisterMetaType<WebRTCSession::State>();
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
@ -69,8 +52,18 @@ WebRTCSession::init(std::string *errorMessage)
// libnice [GLib]: nice // libnice [GLib]: nice
initialised_ = true; initialised_ = true;
std::string strError = gstVersion + ": Missing plugins: "; std::string strError = gstVersion + ": Missing plugins: ";
const gchar *needed[] = {"audioconvert", "audioresample", "autodetect", "dtls", "nice", const gchar *needed[] = {"audioconvert",
"opus", "playback", "rtpmanager", "srtp", "volume", "webrtc", nullptr}; "audioresample",
"autodetect",
"dtls",
"nice",
"opus",
"playback",
"rtpmanager",
"srtp",
"volume",
"webrtc",
nullptr};
GstRegistry *registry = gst_registry_get(); GstRegistry *registry = gst_registry_get();
for (guint i = 0; i < g_strv_length((gchar **)needed); i++) { for (guint i = 0; i < g_strv_length((gchar **)needed); i++) {
GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]); GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
@ -90,6 +83,246 @@ WebRTCSession::init(std::string *errorMessage)
return initialised_; return initialised_;
} }
namespace {
bool isoffering_;
std::string localsdp_;
std::vector<mtx::events::msg::CallCandidates::Candidate> localcandidates_;
gboolean
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data)
{
WebRTCSession *session = static_cast<WebRTCSession *>(user_data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
nhlog::ui()->error("WebRTC: end of stream");
session->end();
break;
case GST_MESSAGE_ERROR:
GError *error;
gchar *debug;
gst_message_parse_error(msg, &error, &debug);
nhlog::ui()->error(
"WebRTC: error from element {}: {}", GST_OBJECT_NAME(msg->src), error->message);
g_clear_error(&error);
g_free(debug);
session->end();
break;
default:
break;
}
return TRUE;
}
GstWebRTCSessionDescription *
parseSDP(const std::string &sdp, GstWebRTCSDPType type)
{
GstSDPMessage *msg;
gst_sdp_message_new(&msg);
if (gst_sdp_message_parse_buffer((guint8 *)sdp.c_str(), sdp.size(), msg) == GST_SDP_OK) {
return gst_webrtc_session_description_new(type, msg);
} else {
nhlog::ui()->error("WebRTC: failed to parse remote session description");
gst_object_unref(msg);
return nullptr;
}
}
void
setLocalDescription(GstPromise *promise, gpointer webrtc)
{
const GstStructure *reply = gst_promise_get_reply(promise);
gboolean isAnswer = gst_structure_id_has_field(reply, g_quark_from_string("answer"));
GstWebRTCSessionDescription *gstsdp = nullptr;
gst_structure_get(reply,
isAnswer ? "answer" : "offer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
&gstsdp,
nullptr);
gst_promise_unref(promise);
g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr);
gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp);
localsdp_ = std::string(sdp);
g_free(sdp);
gst_webrtc_session_description_free(gstsdp);
nhlog::ui()->debug(
"WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_);
}
void
createOffer(GstElement *webrtc)
{
// create-offer first, then set-local-description
GstPromise *promise =
gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
g_signal_emit_by_name(webrtc, "create-offer", nullptr, promise);
}
void
createAnswer(GstPromise *promise, gpointer webrtc)
{
// create-answer first, then set-local-description
gst_promise_unref(promise);
promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise);
}
gboolean
onICEGatheringCompletion(gpointer timerid)
{
*(guint *)(timerid) = 0;
if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
} else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT);
}
return FALSE;
}
void
addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
guint mlineIndex,
gchar *candidate,
gpointer G_GNUC_UNUSED)
{
nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate);
if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) {
emit WebRTCSession::instance().newICECandidate(
{"audio", (uint16_t)mlineIndex, candidate});
return;
}
localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate});
// GStreamer v1.16: webrtcbin's notify::ice-gathering-state triggers
// GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE too early. Fixed in v1.18. Use a 100ms timeout in
// the meantime
static guint timerid = 0;
if (timerid)
g_source_remove(timerid);
timerid = g_timeout_add(100, onICEGatheringCompletion, &timerid);
}
void
iceConnectionStateChanged(GstElement *webrtc,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data G_GNUC_UNUSED)
{
GstWebRTCICEConnectionState newState;
g_object_get(webrtc, "ice-connection-state", &newState, nullptr);
switch (newState) {
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING);
break;
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED);
break;
default:
break;
}
}
void
linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
{
GstCaps *caps = gst_pad_get_current_caps(newpad);
if (!caps)
return;
const gchar *name = gst_structure_get_name(gst_caps_get_structure(caps, 0));
gst_caps_unref(caps);
GstPad *queuepad = nullptr;
if (g_str_has_prefix(name, "audio")) {
nhlog::ui()->debug("WebRTC: received incoming audio stream");
GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
GstElement *resample = gst_element_factory_make("audioresample", nullptr);
GstElement *sink = gst_element_factory_make("autoaudiosink", nullptr);
gst_bin_add_many(GST_BIN(pipe), queue, convert, resample, sink, nullptr);
gst_element_sync_state_with_parent(queue);
gst_element_sync_state_with_parent(convert);
gst_element_sync_state_with_parent(resample);
gst_element_sync_state_with_parent(sink);
gst_element_link_many(queue, convert, resample, sink, nullptr);
queuepad = gst_element_get_static_pad(queue, "sink");
}
if (queuepad) {
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
else {
emit WebRTCSession::instance().stateChanged(
WebRTCSession::State::CONNECTED);
}
gst_object_unref(queuepad);
}
}
void
addDecodeBin(GstElement *webrtc G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
{
if (GST_PAD_DIRECTION(newpad) != GST_PAD_SRC)
return;
nhlog::ui()->debug("WebRTC: received incoming stream");
GstElement *decodebin = gst_element_factory_make("decodebin", nullptr);
g_signal_connect(decodebin, "pad-added", G_CALLBACK(linkNewPad), pipe);
gst_bin_add(GST_BIN(pipe), decodebin);
gst_element_sync_state_with_parent(decodebin);
GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, sinkpad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
gst_object_unref(sinkpad);
}
std::string::const_iterator
findName(const std::string &sdp, const std::string &name)
{
return std::search(
sdp.cbegin(),
sdp.cend(),
name.cbegin(),
name.cend(),
[](unsigned char c1, unsigned char c2) { return std::tolower(c1) == std::tolower(c2); });
}
int
getPayloadType(const std::string &sdp, const std::string &name)
{
// eg a=rtpmap:111 opus/48000/2
auto e = findName(sdp, name);
if (e == sdp.cend()) {
nhlog::ui()->error("WebRTC: remote offer - " + name + " attribute missing");
return -1;
}
if (auto s = sdp.rfind(':', e - sdp.cbegin()); s == std::string::npos) {
nhlog::ui()->error("WebRTC: remote offer - unable to determine " + name +
" payload type");
return -1;
} else {
++s;
try {
return std::stoi(std::string(sdp, s, e - sdp.cbegin() - s));
} catch (...) {
nhlog::ui()->error("WebRTC: remote offer - unable to determine " + name +
" payload type");
}
}
return -1;
}
}
bool bool
WebRTCSession::createOffer() WebRTCSession::createOffer()
{ {
@ -130,6 +363,38 @@ WebRTCSession::acceptOffer(const std::string &sdp)
return true; return true;
} }
bool
WebRTCSession::acceptAnswer(const std::string &sdp)
{
nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
if (state_ != State::OFFERSENT)
return false;
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
if (!answer) {
end();
return false;
}
g_signal_emit_by_name(webrtc_, "set-remote-description", answer, nullptr);
gst_webrtc_session_description_free(answer);
return true;
}
void
WebRTCSession::acceptICECandidates(
const std::vector<mtx::events::msg::CallCandidates::Candidate> &candidates)
{
if (state_ >= State::INITIATED) {
for (const auto &c : candidates) {
nhlog::ui()->debug(
"WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
g_signal_emit_by_name(
webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
}
}
}
bool bool
WebRTCSession::startPipeline(int opusPayloadType) WebRTCSession::startPipeline(int opusPayloadType)
{ {
@ -158,14 +423,15 @@ WebRTCSession::startPipeline(int opusPayloadType)
// generate the offer when the pipeline goes to PLAYING // generate the offer when the pipeline goes to PLAYING
if (isoffering_) if (isoffering_)
g_signal_connect(webrtc_, "on-negotiation-needed", G_CALLBACK(generateOffer), nullptr); g_signal_connect(
webrtc_, "on-negotiation-needed", G_CALLBACK(::createOffer), nullptr);
// on-ice-candidate is emitted when a local ICE candidate has been gathered // on-ice-candidate is emitted when a local ICE candidate has been gathered
g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr); g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr);
// capture ICE failure // capture ICE failure
g_signal_connect(webrtc_, "notify::ice-connection-state", g_signal_connect(
G_CALLBACK(iceConnectionStateChanged), nullptr); webrtc_, "notify::ice-connection-state", G_CALLBACK(iceConnectionStateChanged), nullptr);
// incoming streams trigger pad-added // incoming streams trigger pad-added
gst_element_set_state(pipe_, GST_STATE_READY); gst_element_set_state(pipe_, GST_STATE_READY);
@ -195,8 +461,10 @@ bool
WebRTCSession::createPipeline(int opusPayloadType) WebRTCSession::createPipeline(int opusPayloadType)
{ {
std::string pipeline("webrtcbin bundle-policy=max-bundle name=webrtcbin " std::string pipeline("webrtcbin bundle-policy=max-bundle name=webrtcbin "
"autoaudiosrc ! volume name=srclevel ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " "autoaudiosrc ! volume name=srclevel ! audioconvert ! "
"queue ! " RTP_CAPS_OPUS + std::to_string(opusPayloadType) + " ! webrtcbin."); "audioresample ! queue ! opusenc ! rtpopuspay ! "
"queue ! " RTP_CAPS_OPUS +
std::to_string(opusPayloadType) + " ! webrtcbin.");
webrtc_ = nullptr; webrtc_ = nullptr;
GError *error = nullptr; GError *error = nullptr;
@ -210,35 +478,6 @@ WebRTCSession::createPipeline(int opusPayloadType)
return true; return true;
} }
bool
WebRTCSession::acceptAnswer(const std::string &sdp)
{
nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
if (state_ != State::OFFERSENT)
return false;
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
if (!answer) {
end();
return false;
}
g_signal_emit_by_name(webrtc_, "set-remote-description", answer, nullptr);
gst_webrtc_session_description_free(answer);
return true;
}
void
WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &candidates)
{
if (state_ >= State::INITIATED) {
for (const auto &c : candidates) {
nhlog::ui()->debug("WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
}
}
}
bool bool
WebRTCSession::toggleMuteAudioSrc(bool &isMuted) WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
{ {
@ -270,221 +509,3 @@ WebRTCSession::end()
if (state_ != State::DISCONNECTED) if (state_ != State::DISCONNECTED)
emit stateChanged(State::DISCONNECTED); emit stateChanged(State::DISCONNECTED);
} }
namespace {
std::string::const_iterator findName(const std::string &sdp, const std::string &name)
{
return std::search(sdp.cbegin(), sdp.cend(), name.cbegin(), name.cend(),
[](unsigned char c1, unsigned char c2) {return std::tolower(c1) == std::tolower(c2);});
}
int getPayloadType(const std::string &sdp, const std::string &name)
{
// eg a=rtpmap:111 opus/48000/2
auto e = findName(sdp, name);
if (e == sdp.cend()) {
nhlog::ui()->error("WebRTC: remote offer - " + name + " attribute missing");
return -1;
}
if (auto s = sdp.rfind(':', e - sdp.cbegin()); s == std::string::npos) {
nhlog::ui()->error("WebRTC: remote offer - unable to determine " + name + " payload type");
return -1;
}
else {
++s;
try {
return std::stoi(std::string(sdp, s, e - sdp.cbegin() - s));
}
catch(...) {
nhlog::ui()->error("WebRTC: remote offer - unable to determine " + name + " payload type");
}
}
return -1;
}
gboolean
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data)
{
WebRTCSession *session = (WebRTCSession*)user_data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
nhlog::ui()->error("WebRTC: end of stream");
session->end();
break;
case GST_MESSAGE_ERROR:
GError *error;
gchar *debug;
gst_message_parse_error(msg, &error, &debug);
nhlog::ui()->error("WebRTC: error from element {}: {}", GST_OBJECT_NAME(msg->src), error->message);
g_clear_error(&error);
g_free(debug);
session->end();
break;
default:
break;
}
return TRUE;
}
GstWebRTCSessionDescription*
parseSDP(const std::string &sdp, GstWebRTCSDPType type)
{
GstSDPMessage *msg;
gst_sdp_message_new(&msg);
if (gst_sdp_message_parse_buffer((guint8*)sdp.c_str(), sdp.size(), msg) == GST_SDP_OK) {
return gst_webrtc_session_description_new(type, msg);
}
else {
nhlog::ui()->error("WebRTC: failed to parse remote session description");
gst_object_unref(msg);
return nullptr;
}
}
void
generateOffer(GstElement *webrtc)
{
// create-offer first, then set-local-description
GstPromise *promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
g_signal_emit_by_name(webrtc, "create-offer", nullptr, promise);
}
void
setLocalDescription(GstPromise *promise, gpointer webrtc)
{
const GstStructure *reply = gst_promise_get_reply(promise);
gboolean isAnswer = gst_structure_id_has_field(reply, g_quark_from_string("answer"));
GstWebRTCSessionDescription *gstsdp = nullptr;
gst_structure_get(reply, isAnswer ? "answer" : "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &gstsdp, nullptr);
gst_promise_unref(promise);
g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr);
gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp);
localsdp_ = std::string(sdp);
g_free(sdp);
gst_webrtc_session_description_free(gstsdp);
nhlog::ui()->debug("WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_);
}
void
addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, guint mlineIndex, gchar *candidate, gpointer G_GNUC_UNUSED)
{
nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate);
if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) {
emit WebRTCSession::instance().newICECandidate({"audio", (uint16_t)mlineIndex, candidate});
return;
}
localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate});
// GStreamer v1.16: webrtcbin's notify::ice-gathering-state triggers GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE too early
// fixed in v1.18
// use a 100ms timeout in the meantime
static guint timerid = 0;
if (timerid)
g_source_remove(timerid);
timerid = g_timeout_add(100, onICEGatheringCompletion, &timerid);
}
gboolean
onICEGatheringCompletion(gpointer timerid)
{
*(guint*)(timerid) = 0;
if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
}
else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT);
}
return FALSE;
}
void
iceConnectionStateChanged(GstElement *webrtc, GParamSpec *pspec G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED)
{
GstWebRTCICEConnectionState newState;
g_object_get(webrtc, "ice-connection-state", &newState, nullptr);
switch (newState) {
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING);
break;
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED);
break;
default:
break;
}
}
void
createAnswer(GstPromise *promise, gpointer webrtc)
{
// create-answer first, then set-local-description
gst_promise_unref(promise);
promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise);
}
void
addDecodeBin(GstElement *webrtc G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
{
if (GST_PAD_DIRECTION(newpad) != GST_PAD_SRC)
return;
nhlog::ui()->debug("WebRTC: received incoming stream");
GstElement *decodebin = gst_element_factory_make("decodebin", nullptr);
g_signal_connect(decodebin, "pad-added", G_CALLBACK(linkNewPad), pipe);
gst_bin_add(GST_BIN(pipe), decodebin);
gst_element_sync_state_with_parent(decodebin);
GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, sinkpad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
gst_object_unref(sinkpad);
}
void
linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
{
GstCaps *caps = gst_pad_get_current_caps(newpad);
if (!caps)
return;
const gchar *name = gst_structure_get_name(gst_caps_get_structure(caps, 0));
gst_caps_unref(caps);
GstPad *queuepad = nullptr;
if (g_str_has_prefix(name, "audio")) {
nhlog::ui()->debug("WebRTC: received incoming audio stream");
GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
GstElement *resample = gst_element_factory_make("audioresample", nullptr);
GstElement *sink = gst_element_factory_make("autoaudiosink", nullptr);
gst_bin_add_many(GST_BIN(pipe), queue, convert, resample, sink, nullptr);
gst_element_sync_state_with_parent(queue);
gst_element_sync_state_with_parent(convert);
gst_element_sync_state_with_parent(resample);
gst_element_sync_state_with_parent(sink);
gst_element_link_many(queue, convert, resample, sink, nullptr);
queuepad = gst_element_get_static_pad(queue, "sink");
}
if (queuepad) {
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
else {
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTED);
}
gst_object_unref(queuepad);
}
}
}

View File

@ -14,9 +14,10 @@ class WebRTCSession : public QObject
Q_OBJECT Q_OBJECT
public: public:
enum class State { enum class State
ICEFAILED, {
DISCONNECTED, DISCONNECTED,
ICEFAILED,
INITIATING, INITIATING,
INITIATED, INITIATED,
OFFERSENT, OFFERSENT,
@ -46,8 +47,10 @@ public:
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; } void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
signals: signals:
void offerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&); void offerCreated(const std::string &sdp,
void answerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&); const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void answerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &); void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt

View File

@ -1,4 +1,5 @@
#include <QLabel> #include <QLabel>
#include <QPixmap>
#include <QPushButton> #include <QPushButton>
#include <QString> #include <QString>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -10,12 +11,12 @@
namespace dialogs { namespace dialogs {
AcceptCall::AcceptCall( AcceptCall::AcceptCall(const QString &caller,
const QString &caller,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl, const QString &avatarUrl,
QWidget *parent) : QWidget(parent) QWidget *parent)
: QWidget(parent)
{ {
setAutoFillBackground(true); setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
@ -48,11 +49,6 @@ AcceptCall::AcceptCall(
callerLabel->setFont(labelFont); callerLabel->setFont(labelFont);
callerLabel->setAlignment(Qt::AlignCenter); callerLabel->setAlignment(Qt::AlignCenter);
QLabel *voiceCallLabel = new QLabel("Voice Call", this);
labelFont.setPointSizeF(f.pointSizeF() * 1.1);
voiceCallLabel->setFont(labelFont);
voiceCallLabel->setAlignment(Qt::AlignCenter);
auto avatar = new Avatar(this, QFontMetrics(f).height() * 6); auto avatar = new Avatar(this, QFontMetrics(f).height() * 6);
if (!avatarUrl.isEmpty()) if (!avatarUrl.isEmpty())
avatar->setImage(avatarUrl); avatar->setImage(avatarUrl);
@ -60,7 +56,16 @@ AcceptCall::AcceptCall(
avatar->setLetter(utils::firstChar(roomName)); avatar->setLetter(utils::firstChar(roomName));
const int iconSize = 24; const int iconSize = 24;
auto buttonLayout = new QHBoxLayout(); QLabel *callTypeIndicator = new QLabel(this);
QPixmap callIndicator(":/icons/icons/ui/place-call.png");
callTypeIndicator->setPixmap(callIndicator.scaled(iconSize * 2, iconSize * 2));
QLabel *callTypeLabel = new QLabel("Voice Call", this);
labelFont.setPointSizeF(f.pointSizeF() * 1.1);
callTypeLabel->setFont(labelFont);
callTypeLabel->setAlignment(Qt::AlignCenter);
auto buttonLayout = new QHBoxLayout;
buttonLayout->setSpacing(20); buttonLayout->setSpacing(20);
acceptBtn_ = new QPushButton(tr("Accept"), this); acceptBtn_ = new QPushButton(tr("Accept"), this);
acceptBtn_->setDefault(true); acceptBtn_->setDefault(true);
@ -76,8 +81,9 @@ AcceptCall::AcceptCall(
if (displayNameLabel) if (displayNameLabel)
layout->addWidget(displayNameLabel, 0, Qt::AlignCenter); layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
layout->addWidget(callerLabel, 0, Qt::AlignCenter); layout->addWidget(callerLabel, 0, Qt::AlignCenter);
layout->addWidget(voiceCallLabel, 0, Qt::AlignCenter);
layout->addWidget(avatar, 0, Qt::AlignCenter); layout->addWidget(avatar, 0, Qt::AlignCenter);
layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter);
layout->addWidget(callTypeLabel, 0, Qt::AlignCenter);
layout->addLayout(buttonLayout); layout->addLayout(buttonLayout);
connect(acceptBtn_, &QPushButton::clicked, this, [this]() { connect(acceptBtn_, &QPushButton::clicked, this, [this]() {

View File

@ -12,8 +12,7 @@ class AcceptCall : public QWidget
Q_OBJECT Q_OBJECT
public: public:
AcceptCall( AcceptCall(const QString &caller,
const QString &caller,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl, const QString &avatarUrl,

View File

@ -10,12 +10,12 @@
namespace dialogs { namespace dialogs {
PlaceCall::PlaceCall( PlaceCall::PlaceCall(const QString &callee,
const QString &callee,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl, const QString &avatarUrl,
QWidget *parent) : QWidget(parent) QWidget *parent)
: QWidget(parent)
{ {
setAutoFillBackground(true); setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
@ -37,8 +37,10 @@ PlaceCall::PlaceCall(
avatar->setImage(avatarUrl); avatar->setImage(avatarUrl);
else else
avatar->setLetter(utils::firstChar(roomName)); avatar->setLetter(utils::firstChar(roomName));
const int iconSize = 24;
voiceBtn_ = new QPushButton(tr("Voice Call"), this); voiceBtn_ = new QPushButton(tr("Voice"), this);
voiceBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png"));
voiceBtn_->setIconSize(QSize(iconSize, iconSize));
voiceBtn_->setDefault(true); voiceBtn_->setDefault(true);
cancelBtn_ = new QPushButton(tr("Cancel"), this); cancelBtn_ = new QPushButton(tr("Cancel"), this);

View File

@ -12,8 +12,7 @@ class PlaceCall : public QWidget
Q_OBJECT Q_OBJECT
public: public:
PlaceCall( PlaceCall(const QString &callee,
const QString &callee,
const QString &displayName, const QString &displayName,
const QString &roomName, const QString &roomName,
const QString &avatarUrl, const QString &avatarUrl,

View File

@ -796,9 +796,11 @@ TimelineModel::internalAddEvents(
} else if (std::holds_alternative<mtx::events::RoomEvent< } else if (std::holds_alternative<mtx::events::RoomEvent<
mtx::events::msg::CallCandidates>>(e_) || mtx::events::msg::CallCandidates>>(e_) ||
std::holds_alternative< std::holds_alternative<
mtx::events::RoomEvent<mtx::events::msg::CallAnswer>>( e_) || mtx::events::RoomEvent<mtx::events::msg::CallAnswer>>(
e_) ||
std::holds_alternative< std::holds_alternative<
mtx::events::RoomEvent<mtx::events::msg::CallHangUp>>( e_)) { mtx::events::RoomEvent<mtx::events::msg::CallHangUp>>(
e_)) {
emit newCallEvent(e_); emit newCallEvent(e_);
} }
} }