Skip to content
Snippets Groups Projects
Commit 88cfa3a8 authored by trilene's avatar trilene
Browse files

Polish voice call UI

parent da9995fc
No related branches found
No related tags found
No related merge requests found
Showing
with 371 additions and 146 deletions
resources/icons/ui/end-call.png

643 B

resources/icons/ui/microphone-mute.png

1.13 KiB

resources/icons/ui/microphone-unmute.png

1.07 KiB

resources/icons/ui/place-call.png

759 B

...@@ -70,6 +70,11 @@ ...@@ -70,6 +70,11 @@
<file>icons/ui/mail-reply.png</file> <file>icons/ui/mail-reply.png</file>
<file>icons/ui/place-call.png</file>
<file>icons/ui/end-call.png</file>
<file>icons/ui/microphone-mute.png</file>
<file>icons/ui/microphone-unmute.png</file>
<file>icons/emoji-categories/people.png</file> <file>icons/emoji-categories/people.png</file>
<file>icons/emoji-categories/people@2x.png</file> <file>icons/emoji-categories/people@2x.png</file>
<file>icons/emoji-categories/nature.png</file> <file>icons/emoji-categories/nature.png</file>
......
#include <cstdio>
#include <QDateTime>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QIcon> #include <QIcon>
#include <QLabel> #include <QLabel>
#include <QString> #include <QString>
#include <QTimer>
#include "ActiveCallBar.h" #include "ActiveCallBar.h"
#include "ChatPage.h"
#include "Utils.h"
#include "WebRTCSession.h" #include "WebRTCSession.h"
#include "ui/Avatar.h"
#include "ui/FlatButton.h" #include "ui/FlatButton.h"
ActiveCallBar::ActiveCallBar(QWidget *parent) ActiveCallBar::ActiveCallBar(QWidget *parent)
...@@ -12,7 +19,7 @@ ActiveCallBar::ActiveCallBar(QWidget *parent) ...@@ -12,7 +19,7 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
{ {
setAutoFillBackground(true); setAutoFillBackground(true);
auto p = palette(); auto p = palette();
p.setColor(backgroundRole(), Qt::green); p.setColor(backgroundRole(), QColorConstants::Svg::limegreen);
setPalette(p); setPalette(p);
QFont f; QFont f;
...@@ -24,51 +31,126 @@ ActiveCallBar::ActiveCallBar(QWidget *parent) ...@@ -24,51 +31,126 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
setFixedHeight(contentHeight + widgetMargin); setFixedHeight(contentHeight + widgetMargin);
topLayout_ = new QHBoxLayout(this); layout_ = new QHBoxLayout(this);
topLayout_->setSpacing(widgetMargin); layout_->setSpacing(widgetMargin);
topLayout_->setContentsMargins( layout_->setContentsMargins(
2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin); 2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
topLayout_->setSizeConstraint(QLayout::SetMinimumSize);
QFont labelFont; QFont labelFont;
labelFont.setPointSizeF(labelFont.pointSizeF() * 1.2); labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1);
labelFont.setWeight(QFont::Medium); labelFont.setWeight(QFont::Medium);
avatar_ = new Avatar(this, QFontMetrics(f).height() * 2.5);
callPartyLabel_ = new QLabel(this); callPartyLabel_ = new QLabel(this);
callPartyLabel_->setFont(labelFont); callPartyLabel_->setFont(labelFont);
// TODO microphone mute/unmute icons stateLabel_ = new QLabel(this);
stateLabel_->setFont(labelFont);
durationLabel_ = new QLabel(this);
durationLabel_->setFont(labelFont);
durationLabel_->hide();
muteBtn_ = new FlatButton(this); muteBtn_ = new FlatButton(this);
QIcon muteIcon; setMuteIcon(false);
muteIcon.addFile(":/icons/icons/ui/do-not-disturb-rounded-sign.png");
muteBtn_->setIcon(muteIcon);
muteBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
muteBtn_->setToolTip(tr("Mute Mic"));
muteBtn_->setFixedSize(buttonSize_, buttonSize_); muteBtn_->setFixedSize(buttonSize_, buttonSize_);
muteBtn_->setCornerRadius(buttonSize_ / 2); muteBtn_->setCornerRadius(buttonSize_ / 2);
connect(muteBtn_, &FlatButton::clicked, this, [this]() { connect(muteBtn_, &FlatButton::clicked, this, [this](){
if (WebRTCSession::instance().toggleMuteAudioSrc(muted_)) { if (WebRTCSession::instance().toggleMuteAudioSrc(muted_))
QIcon icon; setMuteIcon(muted_);
if (muted_) {
muteBtn_->setToolTip("Unmute Mic");
icon.addFile(":/icons/icons/ui/round-remove-button.png");
} else {
muteBtn_->setToolTip("Mute Mic");
icon.addFile(":/icons/icons/ui/do-not-disturb-rounded-sign.png");
}
muteBtn_->setIcon(icon);
}
}); });
topLayout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft); layout_->addWidget(avatar_, 0, Qt::AlignLeft);
topLayout_->addWidget(muteBtn_, 0, Qt::AlignRight); layout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft);
layout_->addWidget(stateLabel_, 0, Qt::AlignLeft);
layout_->addWidget(durationLabel_, 0, Qt::AlignLeft);
layout_->addStretch();
layout_->addWidget(muteBtn_, 0, Qt::AlignCenter);
layout_->addSpacing(18);
timer_ = new QTimer(this);
connect(timer_, &QTimer::timeout, this,
[this](){
auto seconds = QDateTime::currentSecsSinceEpoch() - callStartTime_;
int s = seconds % 60;
int m = (seconds / 60) % 60;
int h = seconds / 3600;
char buf[12];
if (h)
snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d", h, m, s);
else
snprintf(buf, sizeof(buf), "%.2d:%.2d", m, s);
durationLabel_->setText(buf);
});
connect(&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &ActiveCallBar::update);
}
void
ActiveCallBar::setMuteIcon(bool muted)
{
QIcon icon;
if (muted) {
muteBtn_->setToolTip("Unmute Mic");
icon.addFile(":/icons/icons/ui/microphone-unmute.png");
} else {
muteBtn_->setToolTip("Mute Mic");
icon.addFile(":/icons/icons/ui/microphone-mute.png");
}
muteBtn_->setIcon(icon);
muteBtn_->setIconSize(QSize(buttonSize_, buttonSize_));
} }
void void
ActiveCallBar::setCallParty(const QString &userid, const QString &displayName) ActiveCallBar::setCallParty(
const QString &userid,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl)
{ {
if (!displayName.isEmpty() && displayName != userid) callPartyLabel_->setText(
callPartyLabel_->setText("Active Call: " + displayName + " (" + userid + ")"); (displayName.isEmpty() ? userid : displayName) + " -");
if (!avatarUrl.isEmpty())
avatar_->setImage(avatarUrl);
else else
callPartyLabel_->setText("Active Call: " + userid); avatar_->setLetter(utils::firstChar(roomName));
}
void
ActiveCallBar::update(WebRTCSession::State state)
{
switch (state) {
case WebRTCSession::State::INITIATING:
stateLabel_->setText("Initiating call...");
break;
case WebRTCSession::State::INITIATED:
stateLabel_->setText("Call initiated...");
break;
case WebRTCSession::State::OFFERSENT:
stateLabel_->setText("Calling...");
break;
case WebRTCSession::State::CONNECTING:
stateLabel_->setText("Connecting...");
break;
case WebRTCSession::State::CONNECTED:
callStartTime_ = QDateTime::currentSecsSinceEpoch();
timer_->start(1000);
stateLabel_->setText("Active call:");
durationLabel_->setText("00:00");
durationLabel_->show();
muteBtn_->show();
break;
case WebRTCSession::State::DISCONNECTED:
timer_->stop();
callPartyLabel_->setText(QString());
stateLabel_->setText(QString());
durationLabel_->setText(QString());
durationLabel_->hide();
setMuteIcon(false);
break;
default:
break;
}
} }
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
#include <QWidget> #include <QWidget>
#include "WebRTCSession.h"
class QHBoxLayout; class QHBoxLayout;
class QLabel; class QLabel;
class QString; class QTimer;
class Avatar;
class FlatButton; class FlatButton;
class ActiveCallBar : public QWidget class ActiveCallBar : public QWidget
...@@ -15,12 +18,24 @@ public: ...@@ -15,12 +18,24 @@ public:
ActiveCallBar(QWidget *parent = nullptr); ActiveCallBar(QWidget *parent = nullptr);
public slots: public slots:
void setCallParty(const QString &userid, const QString &displayName); void update(WebRTCSession::State);
void setCallParty(
const QString &userid,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl);
private: private:
QHBoxLayout *topLayout_ = nullptr; QHBoxLayout *layout_ = nullptr;
Avatar *avatar_ = nullptr;
QLabel *callPartyLabel_ = nullptr; QLabel *callPartyLabel_ = nullptr;
QLabel *stateLabel_ = nullptr;
QLabel *durationLabel_ = nullptr;
FlatButton *muteBtn_ = nullptr; FlatButton *muteBtn_ = nullptr;
int buttonSize_ = 32; int buttonSize_ = 22;
bool muted_ = false; bool muted_ = false;
qint64 callStartTime_ = 0;
QTimer *timer_ = nullptr;
void setMuteIcon(bool muted);
}; };
...@@ -68,9 +68,9 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings) ...@@ -68,9 +68,9 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
turnServerTimer_.setInterval(res.ttl * 1000 * 0.9); turnServerTimer_.setInterval(res.ttl * 1000 * 0.9);
}); });
connect(&session_, &WebRTCSession::pipelineChanged, this, connect(&session_, &WebRTCSession::stateChanged, this,
[this](bool started) { [this](WebRTCSession::State state) {
if (!started) if (state == WebRTCSession::State::DISCONNECTED)
playRingtone("qrc:/media/media/callend.ogg", false); playRingtone("qrc:/media/media/callend.ogg", false);
}); });
...@@ -87,9 +87,9 @@ CallManager::sendInvite(const QString &roomid) ...@@ -87,9 +87,9 @@ CallManager::sendInvite(const QString &roomid)
if (onActiveCall()) if (onActiveCall())
return; return;
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString())); auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
if (members.size() != 2) { if (roomInfo.member_count != 2) {
emit ChatPage::instance()->showNotification("Voice/Video calls are limited to 1:1 rooms"); emit ChatPage::instance()->showNotification("Voice calls are limited to 1:1 rooms.");
return; return;
} }
...@@ -105,11 +105,13 @@ CallManager::sendInvite(const QString &roomid) ...@@ -105,11 +105,13 @@ CallManager::sendInvite(const QString &roomid)
// TODO Add invite timeout // TODO Add invite timeout
generateCallID(); generateCallID();
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front(); const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front();
emit newCallParty(callee.user_id, callee.display_name); 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.");
endCall(); endCall();
} }
} }
...@@ -127,7 +129,7 @@ CallManager::hangUp() ...@@ -127,7 +129,7 @@ CallManager::hangUp()
bool bool
CallManager::onActiveCall() CallManager::onActiveCall()
{ {
return session_.isActive(); return session_.state() != WebRTCSession::State::DISCONNECTED;
} }
void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
...@@ -156,8 +158,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent) ...@@ -156,8 +158,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
if (callInviteEvent.content.call_id.empty()) if (callInviteEvent.content.call_id.empty())
return; return;
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id)); auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
if (onActiveCall() || members.size() != 2) { if (onActiveCall() || roomInfo.member_count != 2) {
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;
...@@ -168,10 +170,18 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent) ...@@ -168,10 +170,18 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
callid_ = callInviteEvent.content.call_id; callid_ = callInviteEvent.content.call_id;
remoteICECandidates_.clear(); remoteICECandidates_.clear();
const RoomMember &caller = members.front().user_id == utils::localUser() ? members.back() : members.front(); std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
emit newCallParty(caller.user_id, caller.display_name); const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front();
auto dialog = new dialogs::AcceptCall(caller.user_id, caller.display_name, MainWindow::instance()); emit newCallParty(caller.user_id, caller.display_name,
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.avatar_url));
auto dialog = new dialogs::AcceptCall(
caller.user_id,
caller.display_name,
QString::fromStdString(roomInfo.name),
QString::fromStdString(roomInfo.avatar_url),
MainWindow::instance());
connect(dialog, &dialogs::AcceptCall::accept, this, connect(dialog, &dialogs::AcceptCall::accept, this,
[this, callInviteEvent](){ [this, callInviteEvent](){
MainWindow::instance()->hideOverlay(); MainWindow::instance()->hideOverlay();
...@@ -198,7 +208,7 @@ CallManager::answerInvite(const CallInvite &invite) ...@@ -198,7 +208,7 @@ CallManager::answerInvite(const CallInvite &invite)
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : ""); session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
if (!session_.acceptOffer(invite.sdp)) { if (!session_.acceptOffer(invite.sdp)) {
emit ChatPage::instance()->showNotification("Problem setting up call"); emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp(); hangUp();
return; return;
} }
...@@ -232,6 +242,7 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent) ...@@ -232,6 +242,7 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() && if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() &&
callid_ == callAnswerEvent.content.call_id) { callid_ == callAnswerEvent.content.call_id) {
emit ChatPage::instance()->showNotification("Call answered on another device.");
stopRingtone(); stopRingtone();
MainWindow::instance()->hideOverlay(); MainWindow::instance()->hideOverlay();
return; return;
...@@ -240,7 +251,7 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent) ...@@ -240,7 +251,7 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) { if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) {
stopRingtone(); stopRingtone();
if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) { if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
emit ChatPage::instance()->showNotification("Problem setting up call"); emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp(); hangUp();
} }
} }
......
...@@ -36,7 +36,11 @@ signals: ...@@ -36,7 +36,11 @@ 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(const QString &userid, const QString& displayName); void newCallParty(
const QString &userid,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl);
private slots: private slots:
void retrieveTurnServer(); void retrieveTurnServer();
......
...@@ -138,13 +138,13 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) ...@@ -138,13 +138,13 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
connect( connect(
&callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty); &callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
connect(&WebRTCSession::instance(), connect(&WebRTCSession::instance(),
&WebRTCSession::pipelineChanged, &WebRTCSession::stateChanged,
this, this,
[this](bool callStarted) { [this](WebRTCSession::State state) {
if (callStarted) if (state == WebRTCSession::State::DISCONNECTED)
activeCallBar_->show();
else
activeCallBar_->hide(); activeCallBar_->hide();
else
activeCallBar_->show();
}); });
// Splitter // Splitter
...@@ -469,22 +469,28 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) ...@@ -469,22 +469,28 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
if (callManager_.onActiveCall()) { if (callManager_.onActiveCall()) {
callManager_.hangUp(); callManager_.hangUp();
} else { } else {
if (cache::singleRoomInfo(current_room_.toStdString()).member_count != 2) { if (auto roomInfo =
showNotification("Voice/Video calls are limited to 1:1 rooms"); cache::singleRoomInfo(current_room_.toStdString());
roomInfo.member_count != 2) {
showNotification("Voice calls are limited to 1:1 rooms.");
} else { } else {
std::vector<RoomMember> members( std::vector<RoomMember> members(
cache::getMembers(current_room_.toStdString())); cache::getMembers(current_room_.toStdString()));
const RoomMember &callee = const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() members.front().user_id == utils::localUser() ? members.back()
: members.front(); : members.front();
auto dialog = auto dialog = new dialogs::PlaceCall(
new dialogs::PlaceCall(callee.user_id, callee.display_name, MainWindow::instance()); callee.user_id,
callee.display_name,
QString::fromStdString(roomInfo.name),
QString::fromStdString(roomInfo.avatar_url),
MainWindow::instance());
connect(dialog, &dialogs::PlaceCall::voice, this, [this]() { connect(dialog, &dialogs::PlaceCall::voice, this, [this]() {
callManager_.sendInvite(current_room_); callManager_.sendInvite(current_room_);
}); });
connect(dialog, &dialogs::PlaceCall::video, this, [this]() { /*connect(dialog, &dialogs::PlaceCall::video, this, [this]() {
showNotification("Video calls not yet implemented"); showNotification("Video calls not yet implemented.");
}); });*/
utils::centerWidget(dialog, MainWindow::instance()); utils::centerWidget(dialog, MainWindow::instance());
dialog->show(); dialog->show();
} }
......
...@@ -31,7 +31,6 @@ ...@@ -31,7 +31,6 @@
#include "Logging.h" #include "Logging.h"
#include "TextInputWidget.h" #include "TextInputWidget.h"
#include "Utils.h" #include "Utils.h"
#include "WebRTCSession.h"
#include "ui/FlatButton.h" #include "ui/FlatButton.h"
#include "ui/LoadingIndicator.h" #include "ui/LoadingIndicator.h"
...@@ -455,9 +454,9 @@ TextInputWidget::TextInputWidget(QWidget *parent) ...@@ -455,9 +454,9 @@ TextInputWidget::TextInputWidget(QWidget *parent)
topLayout_->setContentsMargins(13, 1, 13, 0); topLayout_->setContentsMargins(13, 1, 13, 0);
callBtn_ = new FlatButton(this); callBtn_ = new FlatButton(this);
changeCallButtonState(false); changeCallButtonState(WebRTCSession::State::DISCONNECTED);
connect(&WebRTCSession::instance(), connect(&WebRTCSession::instance(),
&WebRTCSession::pipelineChanged, &WebRTCSession::stateChanged,
this, this,
&TextInputWidget::changeCallButtonState); &TextInputWidget::changeCallButtonState);
...@@ -664,17 +663,16 @@ TextInputWidget::paintEvent(QPaintEvent *) ...@@ -664,17 +663,16 @@ TextInputWidget::paintEvent(QPaintEvent *)
} }
void void
TextInputWidget::changeCallButtonState(bool callStarted) TextInputWidget::changeCallButtonState(WebRTCSession::State state)
{ {
// TODO Telephone and HangUp icons - co-opt the ones below for now
QIcon icon; QIcon icon;
if (callStarted) { if (state == WebRTCSession::State::DISCONNECTED) {
callBtn_->setToolTip(tr("Hang up"));
icon.addFile(":/icons/icons/ui/remove-symbol.png");
} else {
callBtn_->setToolTip(tr("Place a call")); callBtn_->setToolTip(tr("Place a call"));
icon.addFile(":/icons/icons/ui/speech-bubbles-comment-option.png"); icon.addFile(":/icons/icons/ui/place-call.png");
} else {
callBtn_->setToolTip(tr("Hang up"));
icon.addFile(":/icons/icons/ui/end-call.png");
} }
callBtn_->setIcon(icon); callBtn_->setIcon(icon);
callBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight)); callBtn_->setIconSize(QSize(ButtonHeight * 1.1, ButtonHeight * 1.1));
} }
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include <QTextEdit> #include <QTextEdit>
#include <QWidget> #include <QWidget>
#include "WebRTCSession.h"
#include "dialogs/PreviewUploadOverlay.h" #include "dialogs/PreviewUploadOverlay.h"
#include "emoji/PickButton.h" #include "emoji/PickButton.h"
#include "popups/SuggestionsPopup.h" #include "popups/SuggestionsPopup.h"
...@@ -149,7 +150,7 @@ public slots: ...@@ -149,7 +150,7 @@ public slots:
void openFileSelection(); void openFileSelection();
void hideUploadSpinner(); void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); } void focusLineEdit() { input_->setFocus(); }
void changeCallButtonState(bool callStarted); void changeCallButtonState(WebRTCSession::State);
private slots: private slots:
void addSelectedEmoji(const QString &emoji); void addSelectedEmoji(const QString &emoji);
......
...@@ -11,6 +11,8 @@ extern "C" { ...@@ -11,6 +11,8 @@ extern "C" {
#include "gst/webrtc/webrtc.h" #include "gst/webrtc/webrtc.h"
} }
Q_DECLARE_METATYPE(WebRTCSession::State)
namespace { namespace {
bool gisoffer; bool gisoffer;
std::string glocalsdp; std::string glocalsdp;
...@@ -29,6 +31,12 @@ std::string::const_iterator findName(const std::string &sdp, const std::string ...@@ -29,6 +31,12 @@ std::string::const_iterator findName(const std::string &sdp, const std::string
int getPayloadType(const std::string &sdp, const std::string &name); int getPayloadType(const std::string &sdp, const std::string &name);
} }
WebRTCSession::WebRTCSession() : QObject()
{
qRegisterMetaType<WebRTCSession::State>();
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
}
bool bool
WebRTCSession::init(std::string *errorMessage) WebRTCSession::init(std::string *errorMessage)
{ {
...@@ -54,14 +62,14 @@ WebRTCSession::init(std::string *errorMessage) ...@@ -54,14 +62,14 @@ WebRTCSession::init(std::string *errorMessage)
nhlog::ui()->info("Initialised " + gstVersion); nhlog::ui()->info("Initialised " + gstVersion);
// GStreamer Plugins: // GStreamer Plugins:
// Base: audioconvert, audioresample, opus, playback, videoconvert, volume // Base: audioconvert, audioresample, opus, playback, volume
// Good: autodetect, rtpmanager, vpx // Good: autodetect, rtpmanager, vpx
// Bad: dtls, srtp, webrtc // Bad: dtls, srtp, webrtc
// 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", "audioresample", "autodetect", "dtls", "nice",
"opus", "playback", "rtpmanager", "srtp", "videoconvert", "vpx", "volume", "webrtc", nullptr}; "opus", "playback", "rtpmanager", "srtp", "vpx", "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]);
...@@ -91,17 +99,19 @@ WebRTCSession::createOffer() ...@@ -91,17 +99,19 @@ WebRTCSession::createOffer()
} }
bool bool
WebRTCSession::acceptOffer(const std::string& sdp) WebRTCSession::acceptOffer(const std::string &sdp)
{ {
nhlog::ui()->debug("Received offer:\n{}", sdp); nhlog::ui()->debug("Received offer:\n{}", sdp);
if (state_ != State::DISCONNECTED)
return false;
gisoffer = false; gisoffer = false;
glocalsdp.clear(); glocalsdp.clear();
gcandidates.clear(); gcandidates.clear();
int opusPayloadType = getPayloadType(sdp, "opus"); int opusPayloadType = getPayloadType(sdp, "opus");
if (opusPayloadType == -1) { if (opusPayloadType == -1)
return false; return false;
}
GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER); GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
if (!offer) if (!offer)
...@@ -120,9 +130,11 @@ WebRTCSession::acceptOffer(const std::string& sdp) ...@@ -120,9 +130,11 @@ WebRTCSession::acceptOffer(const std::string& sdp)
bool bool
WebRTCSession::startPipeline(int opusPayloadType) WebRTCSession::startPipeline(int opusPayloadType)
{ {
if (isActive()) if (state_ != State::DISCONNECTED)
return false; return false;
emit stateChanged(State::INITIATING);
if (!createPipeline(opusPayloadType)) if (!createPipeline(opusPayloadType))
return false; return false;
...@@ -132,7 +144,12 @@ WebRTCSession::startPipeline(int opusPayloadType) ...@@ -132,7 +144,12 @@ WebRTCSession::startPipeline(int opusPayloadType)
nhlog::ui()->info("WebRTC: Setting STUN server: {}", stunServer_); nhlog::ui()->info("WebRTC: Setting STUN server: {}", stunServer_);
g_object_set(webrtc_, "stun-server", stunServer_.c_str(), nullptr); g_object_set(webrtc_, "stun-server", stunServer_.c_str(), nullptr);
} }
addTurnServers();
for (const auto &uri : turnServers_) {
nhlog::ui()->info("WebRTC: Setting TURN server: {}", uri);
gboolean udata;
g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
}
// generate the offer when the pipeline goes to PLAYING // generate the offer when the pipeline goes to PLAYING
if (gisoffer) if (gisoffer)
...@@ -152,16 +169,14 @@ WebRTCSession::startPipeline(int opusPayloadType) ...@@ -152,16 +169,14 @@ WebRTCSession::startPipeline(int opusPayloadType)
GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING); GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) { if (ret == GST_STATE_CHANGE_FAILURE) {
nhlog::ui()->error("WebRTC: unable to start pipeline"); nhlog::ui()->error("WebRTC: unable to start pipeline");
gst_object_unref(pipe_); end();
pipe_ = nullptr;
webrtc_ = nullptr;
return false; return false;
} }
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_)); GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
gst_bus_add_watch(bus, newBusMessage, this); gst_bus_add_watch(bus, newBusMessage, this);
gst_object_unref(bus); gst_object_unref(bus);
emit pipelineChanged(true); emit stateChanged(State::INITIATED);
return true; return true;
} }
...@@ -180,10 +195,7 @@ WebRTCSession::createPipeline(int opusPayloadType) ...@@ -180,10 +195,7 @@ WebRTCSession::createPipeline(int opusPayloadType)
if (error) { if (error) {
nhlog::ui()->error("WebRTC: Failed to parse pipeline: {}", error->message); nhlog::ui()->error("WebRTC: Failed to parse pipeline: {}", error->message);
g_error_free(error); g_error_free(error);
if (pipe_) { end();
gst_object_unref(pipe_);
pipe_ = nullptr;
}
return false; return false;
} }
return true; return true;
...@@ -193,7 +205,7 @@ bool ...@@ -193,7 +205,7 @@ bool
WebRTCSession::acceptAnswer(const std::string &sdp) WebRTCSession::acceptAnswer(const std::string &sdp)
{ {
nhlog::ui()->debug("WebRTC: Received sdp:\n{}", sdp); nhlog::ui()->debug("WebRTC: Received sdp:\n{}", sdp);
if (!isActive()) if (state_ != State::OFFERSENT)
return false; return false;
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER); GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
...@@ -206,18 +218,20 @@ WebRTCSession::acceptAnswer(const std::string &sdp) ...@@ -206,18 +218,20 @@ WebRTCSession::acceptAnswer(const std::string &sdp)
} }
void void
WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate>& candidates) WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &candidates)
{ {
if (isActive()) { if (state_ >= State::INITIATED) {
for (const auto& c : candidates) for (const auto &c : candidates)
g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str()); g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
} }
if (state_ < State::CONNECTED)
emit stateChanged(State::CONNECTING);
} }
bool bool
WebRTCSession::toggleMuteAudioSrc(bool &isMuted) WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
{ {
if (!isActive()) if (state_ < State::INITIATED)
return false; return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel"); GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
...@@ -241,20 +255,7 @@ WebRTCSession::end() ...@@ -241,20 +255,7 @@ WebRTCSession::end()
pipe_ = nullptr; pipe_ = nullptr;
} }
webrtc_ = nullptr; webrtc_ = nullptr;
emit pipelineChanged(false); emit stateChanged(State::DISCONNECTED);
}
void
WebRTCSession::addTurnServers()
{
if (!webrtc_)
return;
for (const auto &uri : turnServers_) {
nhlog::ui()->info("WebRTC: Setting TURN server: {}", uri);
gboolean udata;
g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
}
} }
namespace { namespace {
...@@ -373,8 +374,10 @@ gboolean ...@@ -373,8 +374,10 @@ gboolean
onICEGatheringCompletion(gpointer timerid) onICEGatheringCompletion(gpointer timerid)
{ {
*(guint*)(timerid) = 0; *(guint*)(timerid) = 0;
if (gisoffer) if (gisoffer) {
emit WebRTCSession::instance().offerCreated(glocalsdp, gcandidates); emit WebRTCSession::instance().offerCreated(glocalsdp, gcandidates);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
}
else else
emit WebRTCSession::instance().answerCreated(glocalsdp, gcandidates); emit WebRTCSession::instance().answerCreated(glocalsdp, gcandidates);
...@@ -445,6 +448,9 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe ...@@ -445,6 +448,9 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe
if (queuepad) { if (queuepad) {
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad))) if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: Unable to link new pad"); nhlog::ui()->error("WebRTC: Unable to link new pad");
else {
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTED);
}
gst_object_unref(queuepad); gst_object_unref(queuepad);
} }
} }
......
...@@ -14,6 +14,15 @@ class WebRTCSession : public QObject ...@@ -14,6 +14,15 @@ class WebRTCSession : public QObject
Q_OBJECT Q_OBJECT
public: public:
enum class State {
DISCONNECTED,
INITIATING,
INITIATED,
OFFERSENT,
CONNECTING,
CONNECTED
};
static WebRTCSession& instance() static WebRTCSession& instance()
{ {
static WebRTCSession instance; static WebRTCSession instance;
...@@ -27,7 +36,7 @@ public: ...@@ -27,7 +36,7 @@ public:
bool acceptAnswer(const std::string &sdp); bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate>&); void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
bool isActive() { return pipe_ != nullptr; } State state() const {return state_;}
bool toggleMuteAudioSrc(bool &isMuted); bool toggleMuteAudioSrc(bool &isMuted);
void end(); void end();
...@@ -37,12 +46,16 @@ public: ...@@ -37,12 +46,16 @@ public:
signals: signals:
void offerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&); void offerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
void answerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&); void answerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
void pipelineChanged(bool started); void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt
private slots:
void setState(State state) {state_ = state;}
private: private:
WebRTCSession() : QObject() {} WebRTCSession();
bool initialised_ = false; bool initialised_ = false;
State state_ = State::DISCONNECTED;
GstElement *pipe_ = nullptr; GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr; GstElement *webrtc_ = nullptr;
std::string stunServer_; std::string stunServer_;
...@@ -50,7 +63,6 @@ private: ...@@ -50,7 +63,6 @@ private:
bool startPipeline(int opusPayloadType); bool startPipeline(int opusPayloadType);
bool createPipeline(int opusPayloadType); bool createPipeline(int opusPayloadType);
void addTurnServers();
public: public:
WebRTCSession(WebRTCSession const&) = delete; WebRTCSession(WebRTCSession const&) = delete;
......
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QString>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "Config.h" #include "Config.h"
#include "Utils.h"
#include "dialogs/AcceptCall.h" #include "dialogs/AcceptCall.h"
#include "ui/Avatar.h"
namespace dialogs { namespace dialogs {
AcceptCall::AcceptCall(const QString &caller, const QString &displayName, QWidget *parent) AcceptCall::AcceptCall(
: QWidget(parent) const QString &caller,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl,
QWidget *parent) : QWidget(parent)
{ {
setAutoFillBackground(true); setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal); setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true); setAttribute(Qt::WA_DeleteOnClose, true);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING); layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN); layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout(); QFont f;
buttonLayout->setSpacing(15); f.setPointSizeF(f.pointSizeF());
buttonLayout->setMargin(0);
QFont labelFont;
labelFont.setWeight(QFont::Medium);
QLabel *displayNameLabel = nullptr;
if (!displayName.isEmpty() && displayName != caller) {
displayNameLabel = new QLabel(displayName, this);
labelFont.setPointSizeF(f.pointSizeF() * 2);
displayNameLabel ->setFont(labelFont);
displayNameLabel ->setAlignment(Qt::AlignCenter);
}
QLabel *callerLabel = new QLabel(caller, this);
labelFont.setPointSizeF(f.pointSizeF() * 1.2);
callerLabel->setFont(labelFont);
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);
if (!avatarUrl.isEmpty())
avatar->setImage(avatarUrl);
else
avatar->setLetter(utils::firstChar(roomName));
const int iconSize = 24;
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(20);
acceptBtn_ = new QPushButton(tr("Accept"), this); acceptBtn_ = new QPushButton(tr("Accept"), this);
acceptBtn_->setDefault(true); acceptBtn_->setDefault(true);
rejectBtn_ = new QPushButton(tr("Reject"), this); acceptBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png"));
acceptBtn_->setIconSize(QSize(iconSize, iconSize));
buttonLayout->addStretch(1); rejectBtn_ = new QPushButton(tr("Reject"), this);
rejectBtn_->setIcon(QIcon(":/icons/icons/ui/end-call.png"));
rejectBtn_->setIconSize(QSize(iconSize, iconSize));
buttonLayout->addWidget(acceptBtn_); buttonLayout->addWidget(acceptBtn_);
buttonLayout->addWidget(rejectBtn_); buttonLayout->addWidget(rejectBtn_);
QLabel *label; if (displayNameLabel)
if (!displayName.isEmpty() && displayName != caller) layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
label = new QLabel("Accept call from " + displayName + " (" + caller + ")?", this); layout->addWidget(callerLabel, 0, Qt::AlignCenter);
else layout->addWidget(voiceCallLabel, 0, Qt::AlignCenter);
label = new QLabel("Accept call from " + caller + "?", this); layout->addWidget(avatar, 0, Qt::AlignCenter);
layout->addWidget(label);
layout->addLayout(buttonLayout); layout->addLayout(buttonLayout);
connect(acceptBtn_, &QPushButton::clicked, this, [this]() { connect(acceptBtn_, &QPushButton::clicked, this, [this]() {
......
#pragma once #pragma once
#include <QString>
#include <QWidget> #include <QWidget>
class QPushButton; class QPushButton;
class QString;
namespace dialogs { namespace dialogs {
...@@ -12,7 +12,12 @@ class AcceptCall : public QWidget ...@@ -12,7 +12,12 @@ class AcceptCall : public QWidget
Q_OBJECT Q_OBJECT
public: public:
AcceptCall(const QString &caller, const QString &displayName, QWidget *parent = nullptr); AcceptCall(
const QString &caller,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl,
QWidget *parent = nullptr);
signals: signals:
void accept(); void accept();
......
...@@ -4,12 +4,18 @@ ...@@ -4,12 +4,18 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include "Config.h" #include "Config.h"
#include "Utils.h"
#include "dialogs/PlaceCall.h" #include "dialogs/PlaceCall.h"
#include "ui/Avatar.h"
namespace dialogs { namespace dialogs {
PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget *parent) PlaceCall::PlaceCall(
: QWidget(parent) const QString &callee,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl,
QWidget *parent) : QWidget(parent)
{ {
setAutoFillBackground(true); setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
...@@ -20,25 +26,31 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget ...@@ -20,25 +26,31 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget
layout->setSpacing(conf::modals::WIDGET_SPACING); layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN); layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout(); auto buttonLayout = new QHBoxLayout(this);
buttonLayout->setSpacing(15); buttonLayout->setSpacing(15);
buttonLayout->setMargin(0); buttonLayout->setMargin(0);
QFont f;
f.setPointSizeF(f.pointSizeF());
auto avatar = new Avatar(this, QFontMetrics(f).height() * 3);
if (!avatarUrl.isEmpty())
avatar->setImage(avatarUrl);
else
avatar->setLetter(utils::firstChar(roomName));
voiceBtn_ = new QPushButton(tr("Voice Call"), this); voiceBtn_ = new QPushButton(tr("Voice Call"), this);
voiceBtn_->setDefault(true); voiceBtn_->setDefault(true);
videoBtn_ = new QPushButton(tr("Video Call"), this); //videoBtn_ = new QPushButton(tr("Video Call"), this);
cancelBtn_ = new QPushButton(tr("Cancel"), this); cancelBtn_ = new QPushButton(tr("Cancel"), this);
buttonLayout->addStretch(1); buttonLayout->addStretch(1);
buttonLayout->addWidget(avatar);
buttonLayout->addWidget(voiceBtn_); buttonLayout->addWidget(voiceBtn_);
buttonLayout->addWidget(videoBtn_); //buttonLayout->addWidget(videoBtn_);
buttonLayout->addWidget(cancelBtn_); buttonLayout->addWidget(cancelBtn_);
QLabel *label; QString name = displayName.isEmpty() ? callee : displayName;
if (!displayName.isEmpty() && displayName != callee) QLabel *label = new QLabel("Place a call to " + name + "?", this);
label = new QLabel("Place a call to " + displayName + " (" + callee + ")?", this);
else
label = new QLabel("Place a call to " + callee + "?", this);
layout->addWidget(label); layout->addWidget(label);
layout->addLayout(buttonLayout); layout->addLayout(buttonLayout);
...@@ -47,10 +59,10 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget ...@@ -47,10 +59,10 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget
emit voice(); emit voice();
emit close(); emit close();
}); });
connect(videoBtn_, &QPushButton::clicked, this, [this]() { /*connect(videoBtn_, &QPushButton::clicked, this, [this]() {
emit video(); emit video();
emit close(); emit close();
}); });*/
connect(cancelBtn_, &QPushButton::clicked, this, [this]() { connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel(); emit cancel();
emit close(); emit close();
......
...@@ -12,16 +12,21 @@ class PlaceCall : public QWidget ...@@ -12,16 +12,21 @@ class PlaceCall : public QWidget
Q_OBJECT Q_OBJECT
public: public:
PlaceCall(const QString &callee, const QString &displayName, QWidget *parent = nullptr); PlaceCall(
const QString &callee,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl,
QWidget *parent = nullptr);
signals: signals:
void voice(); void voice();
void video(); // void video();
void cancel(); void cancel();
private: private:
QPushButton *voiceBtn_; QPushButton *voiceBtn_;
QPushButton *videoBtn_; // QPushButton *videoBtn_;
QPushButton *cancelBtn_; QPushButton *cancelBtn_;
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment