diff --git a/resources/icons/ui/end-call.png b/resources/icons/ui/end-call.png
new file mode 100644
index 0000000..6cbb983
Binary files /dev/null and b/resources/icons/ui/end-call.png differ
diff --git a/resources/icons/ui/microphone-mute.png b/resources/icons/ui/microphone-mute.png
new file mode 100644
index 0000000..0042fbe
Binary files /dev/null and b/resources/icons/ui/microphone-mute.png differ
diff --git a/resources/icons/ui/microphone-unmute.png b/resources/icons/ui/microphone-unmute.png
new file mode 100644
index 0000000..27999c7
Binary files /dev/null and b/resources/icons/ui/microphone-unmute.png differ
diff --git a/resources/icons/ui/place-call.png b/resources/icons/ui/place-call.png
new file mode 100644
index 0000000..a820cf3
Binary files /dev/null and b/resources/icons/ui/place-call.png differ
diff --git a/resources/res.qrc b/resources/res.qrc
index 3fd3fc9..b245f48 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -70,6 +70,11 @@
icons/ui/mail-reply.png
+ icons/ui/place-call.png
+ icons/ui/end-call.png
+ icons/ui/microphone-mute.png
+ icons/ui/microphone-unmute.png
+
icons/emoji-categories/people.png
icons/emoji-categories/people@2x.png
icons/emoji-categories/nature.png
diff --git a/src/ActiveCallBar.cpp b/src/ActiveCallBar.cpp
index a5ef754..5703c1e 100644
--- a/src/ActiveCallBar.cpp
+++ b/src/ActiveCallBar.cpp
@@ -1,10 +1,17 @@
+#include
+
+#include
#include
#include
#include
#include
+#include
#include "ActiveCallBar.h"
+#include "ChatPage.h"
+#include "Utils.h"
#include "WebRTCSession.h"
+#include "ui/Avatar.h"
#include "ui/FlatButton.h"
ActiveCallBar::ActiveCallBar(QWidget *parent)
@@ -12,7 +19,7 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
{
setAutoFillBackground(true);
auto p = palette();
- p.setColor(backgroundRole(), Qt::green);
+ p.setColor(backgroundRole(), QColorConstants::Svg::limegreen);
setPalette(p);
QFont f;
@@ -24,51 +31,126 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
setFixedHeight(contentHeight + widgetMargin);
- topLayout_ = new QHBoxLayout(this);
- topLayout_->setSpacing(widgetMargin);
- topLayout_->setContentsMargins(
+ layout_ = new QHBoxLayout(this);
+ layout_->setSpacing(widgetMargin);
+ layout_->setContentsMargins(
2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
- topLayout_->setSizeConstraint(QLayout::SetMinimumSize);
QFont labelFont;
- labelFont.setPointSizeF(labelFont.pointSizeF() * 1.2);
+ labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1);
labelFont.setWeight(QFont::Medium);
+ avatar_ = new Avatar(this, QFontMetrics(f).height() * 2.5);
+
callPartyLabel_ = new QLabel(this);
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);
- QIcon muteIcon;
- 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"));
+ setMuteIcon(false);
muteBtn_->setFixedSize(buttonSize_, buttonSize_);
muteBtn_->setCornerRadius(buttonSize_ / 2);
- connect(muteBtn_, &FlatButton::clicked, this, [this]() {
- if (WebRTCSession::instance().toggleMuteAudioSrc(muted_)) {
- QIcon icon;
- 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);
- }
+ connect(muteBtn_, &FlatButton::clicked, this, [this](){
+ if (WebRTCSession::instance().toggleMuteAudioSrc(muted_))
+ setMuteIcon(muted_);
});
- topLayout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft);
- topLayout_->addWidget(muteBtn_, 0, Qt::AlignRight);
+ layout_->addWidget(avatar_, 0, Qt::AlignLeft);
+ 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
-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("Active Call: " + displayName + " (" + userid + ")");
+ callPartyLabel_->setText(
+ (displayName.isEmpty() ? userid : displayName) + " -");
+
+ if (!avatarUrl.isEmpty())
+ avatar_->setImage(avatarUrl);
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;
+ }
}
diff --git a/src/ActiveCallBar.h b/src/ActiveCallBar.h
index dd01e2a..8440d7f 100644
--- a/src/ActiveCallBar.h
+++ b/src/ActiveCallBar.h
@@ -2,9 +2,12 @@
#include
+#include "WebRTCSession.h"
+
class QHBoxLayout;
class QLabel;
-class QString;
+class QTimer;
+class Avatar;
class FlatButton;
class ActiveCallBar : public QWidget
@@ -15,12 +18,24 @@ public:
ActiveCallBar(QWidget *parent = nullptr);
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:
- QHBoxLayout *topLayout_ = nullptr;
+ QHBoxLayout *layout_ = nullptr;
+ Avatar *avatar_ = nullptr;
QLabel *callPartyLabel_ = nullptr;
+ QLabel *stateLabel_ = nullptr;
+ QLabel *durationLabel_ = nullptr;
FlatButton *muteBtn_ = nullptr;
- int buttonSize_ = 32;
+ int buttonSize_ = 22;
bool muted_ = false;
+ qint64 callStartTime_ = 0;
+ QTimer *timer_ = nullptr;
+
+ void setMuteIcon(bool muted);
};
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index 92af3b2..b5c59e0 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -68,9 +68,9 @@ CallManager::CallManager(QSharedPointer userSettings)
turnServerTimer_.setInterval(res.ttl * 1000 * 0.9);
});
- connect(&session_, &WebRTCSession::pipelineChanged, this,
- [this](bool started) {
- if (!started)
+ connect(&session_, &WebRTCSession::stateChanged, this,
+ [this](WebRTCSession::State state) {
+ if (state == WebRTCSession::State::DISCONNECTED)
playRingtone("qrc:/media/media/callend.ogg", false);
});
@@ -87,9 +87,9 @@ CallManager::sendInvite(const QString &roomid)
if (onActiveCall())
return;
- std::vector members(cache::getMembers(roomid.toStdString()));
- if (members.size() != 2) {
- emit ChatPage::instance()->showNotification("Voice/Video calls are limited to 1:1 rooms");
+ auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
+ if (roomInfo.member_count != 2) {
+ emit ChatPage::instance()->showNotification("Voice calls are limited to 1:1 rooms.");
return;
}
@@ -105,11 +105,13 @@ CallManager::sendInvite(const QString &roomid)
// TODO Add invite timeout
generateCallID();
+ std::vector members(cache::getMembers(roomid.toStdString()));
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);
if (!session_.createOffer()) {
- emit ChatPage::instance()->showNotification("Problem setting up call");
+ emit ChatPage::instance()->showNotification("Problem setting up call.");
endCall();
}
}
@@ -127,7 +129,7 @@ CallManager::hangUp()
bool
CallManager::onActiveCall()
{
- return session_.isActive();
+ return session_.state() != WebRTCSession::State::DISCONNECTED;
}
void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
@@ -156,8 +158,8 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent)
if (callInviteEvent.content.call_id.empty())
return;
- std::vector members(cache::getMembers(callInviteEvent.room_id));
- if (onActiveCall() || members.size() != 2) {
+ auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
+ if (onActiveCall() || roomInfo.member_count != 2) {
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
CallHangUp{callInviteEvent.content.call_id, 0, CallHangUp::Reason::InviteTimeOut});
return;
@@ -168,10 +170,18 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent)
callid_ = callInviteEvent.content.call_id;
remoteICECandidates_.clear();
- const RoomMember &caller = members.front().user_id == utils::localUser() ? members.back() : members.front();
- emit newCallParty(caller.user_id, caller.display_name);
-
- auto dialog = new dialogs::AcceptCall(caller.user_id, caller.display_name, MainWindow::instance());
+ std::vector members(cache::getMembers(callInviteEvent.room_id));
+ const RoomMember &caller =
+ members.front().user_id == utils::localUser() ? members.back() : members.front();
+ 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,
[this, callInviteEvent](){
MainWindow::instance()->hideOverlay();
@@ -198,7 +208,7 @@ CallManager::answerInvite(const CallInvite &invite)
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
if (!session_.acceptOffer(invite.sdp)) {
- emit ChatPage::instance()->showNotification("Problem setting up call");
+ emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp();
return;
}
@@ -232,6 +242,7 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent)
if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() &&
callid_ == callAnswerEvent.content.call_id) {
+ emit ChatPage::instance()->showNotification("Call answered on another device.");
stopRingtone();
MainWindow::instance()->hideOverlay();
return;
@@ -240,7 +251,7 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent)
if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) {
stopRingtone();
if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
- emit ChatPage::instance()->showNotification("Problem setting up call");
+ emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp();
}
}
diff --git a/src/CallManager.h b/src/CallManager.h
index 8a93241..df83a87 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -36,7 +36,11 @@ signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer&);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp&);
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:
void retrieveTurnServer();
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 15b7c54..5b8ea47 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -138,13 +138,13 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
connect(
&callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
connect(&WebRTCSession::instance(),
- &WebRTCSession::pipelineChanged,
+ &WebRTCSession::stateChanged,
this,
- [this](bool callStarted) {
- if (callStarted)
- activeCallBar_->show();
- else
+ [this](WebRTCSession::State state) {
+ if (state == WebRTCSession::State::DISCONNECTED)
activeCallBar_->hide();
+ else
+ activeCallBar_->show();
});
// Splitter
@@ -469,22 +469,28 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
if (callManager_.onActiveCall()) {
callManager_.hangUp();
} else {
- if (cache::singleRoomInfo(current_room_.toStdString()).member_count != 2) {
- showNotification("Voice/Video calls are limited to 1:1 rooms");
+ if (auto roomInfo =
+ cache::singleRoomInfo(current_room_.toStdString());
+ roomInfo.member_count != 2) {
+ showNotification("Voice calls are limited to 1:1 rooms.");
} else {
std::vector members(
cache::getMembers(current_room_.toStdString()));
const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back()
: members.front();
- auto dialog =
- new dialogs::PlaceCall(callee.user_id, callee.display_name, MainWindow::instance());
+ auto dialog = new dialogs::PlaceCall(
+ callee.user_id,
+ callee.display_name,
+ QString::fromStdString(roomInfo.name),
+ QString::fromStdString(roomInfo.avatar_url),
+ MainWindow::instance());
connect(dialog, &dialogs::PlaceCall::voice, this, [this]() {
callManager_.sendInvite(current_room_);
});
- connect(dialog, &dialogs::PlaceCall::video, this, [this]() {
- showNotification("Video calls not yet implemented");
- });
+ /*connect(dialog, &dialogs::PlaceCall::video, this, [this]() {
+ showNotification("Video calls not yet implemented.");
+ });*/
utils::centerWidget(dialog, MainWindow::instance());
dialog->show();
}
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 2be0b40..d49fc74 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -31,7 +31,6 @@
#include "Logging.h"
#include "TextInputWidget.h"
#include "Utils.h"
-#include "WebRTCSession.h"
#include "ui/FlatButton.h"
#include "ui/LoadingIndicator.h"
@@ -455,9 +454,9 @@ TextInputWidget::TextInputWidget(QWidget *parent)
topLayout_->setContentsMargins(13, 1, 13, 0);
callBtn_ = new FlatButton(this);
- changeCallButtonState(false);
+ changeCallButtonState(WebRTCSession::State::DISCONNECTED);
connect(&WebRTCSession::instance(),
- &WebRTCSession::pipelineChanged,
+ &WebRTCSession::stateChanged,
this,
&TextInputWidget::changeCallButtonState);
@@ -664,17 +663,16 @@ TextInputWidget::paintEvent(QPaintEvent *)
}
void
-TextInputWidget::changeCallButtonState(bool callStarted)
+TextInputWidget::changeCallButtonState(WebRTCSession::State state)
{
- // TODO Telephone and HangUp icons - co-opt the ones below for now
QIcon icon;
- if (callStarted) {
- callBtn_->setToolTip(tr("Hang up"));
- icon.addFile(":/icons/icons/ui/remove-symbol.png");
- } else {
+ if (state == WebRTCSession::State::DISCONNECTED) {
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_->setIconSize(QSize(ButtonHeight, ButtonHeight));
+ callBtn_->setIconSize(QSize(ButtonHeight * 1.1, ButtonHeight * 1.1));
}
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index ae58f4e..27dff57 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -26,6 +26,7 @@
#include
#include
+#include "WebRTCSession.h"
#include "dialogs/PreviewUploadOverlay.h"
#include "emoji/PickButton.h"
#include "popups/SuggestionsPopup.h"
@@ -149,7 +150,7 @@ public slots:
void openFileSelection();
void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); }
- void changeCallButtonState(bool callStarted);
+ void changeCallButtonState(WebRTCSession::State);
private slots:
void addSelectedEmoji(const QString &emoji);
diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp
index 4ef7a81..5baed72 100644
--- a/src/WebRTCSession.cpp
+++ b/src/WebRTCSession.cpp
@@ -11,6 +11,8 @@ extern "C" {
#include "gst/webrtc/webrtc.h"
}
+Q_DECLARE_METATYPE(WebRTCSession::State)
+
namespace {
bool gisoffer;
std::string glocalsdp;
@@ -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);
}
+WebRTCSession::WebRTCSession() : QObject()
+{
+ qRegisterMetaType();
+ connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
+}
+
bool
WebRTCSession::init(std::string *errorMessage)
{
@@ -54,14 +62,14 @@ WebRTCSession::init(std::string *errorMessage)
nhlog::ui()->info("Initialised " + gstVersion);
// GStreamer Plugins:
- // Base: audioconvert, audioresample, opus, playback, videoconvert, volume
+ // Base: audioconvert, audioresample, opus, playback, volume
// Good: autodetect, rtpmanager, vpx
// Bad: dtls, srtp, webrtc
// libnice [GLib]: nice
initialised_ = true;
std::string strError = gstVersion + ": Missing plugins: ";
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();
for (guint i = 0; i < g_strv_length((gchar**)needed); i++) {
GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
@@ -91,17 +99,19 @@ WebRTCSession::createOffer()
}
bool
-WebRTCSession::acceptOffer(const std::string& sdp)
+WebRTCSession::acceptOffer(const std::string &sdp)
{
nhlog::ui()->debug("Received offer:\n{}", sdp);
+ if (state_ != State::DISCONNECTED)
+ return false;
+
gisoffer = false;
glocalsdp.clear();
gcandidates.clear();
int opusPayloadType = getPayloadType(sdp, "opus");
- if (opusPayloadType == -1) {
+ if (opusPayloadType == -1)
return false;
- }
GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
if (!offer)
@@ -120,9 +130,11 @@ WebRTCSession::acceptOffer(const std::string& sdp)
bool
WebRTCSession::startPipeline(int opusPayloadType)
{
- if (isActive())
+ if (state_ != State::DISCONNECTED)
return false;
+ emit stateChanged(State::INITIATING);
+
if (!createPipeline(opusPayloadType))
return false;
@@ -132,7 +144,12 @@ WebRTCSession::startPipeline(int opusPayloadType)
nhlog::ui()->info("WebRTC: Setting STUN server: {}", stunServer_);
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
if (gisoffer)
@@ -152,16 +169,14 @@ WebRTCSession::startPipeline(int opusPayloadType)
GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
nhlog::ui()->error("WebRTC: unable to start pipeline");
- gst_object_unref(pipe_);
- pipe_ = nullptr;
- webrtc_ = nullptr;
+ end();
return false;
}
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
gst_bus_add_watch(bus, newBusMessage, this);
gst_object_unref(bus);
- emit pipelineChanged(true);
+ emit stateChanged(State::INITIATED);
return true;
}
@@ -180,10 +195,7 @@ WebRTCSession::createPipeline(int opusPayloadType)
if (error) {
nhlog::ui()->error("WebRTC: Failed to parse pipeline: {}", error->message);
g_error_free(error);
- if (pipe_) {
- gst_object_unref(pipe_);
- pipe_ = nullptr;
- }
+ end();
return false;
}
return true;
@@ -193,7 +205,7 @@ bool
WebRTCSession::acceptAnswer(const std::string &sdp)
{
nhlog::ui()->debug("WebRTC: Received sdp:\n{}", sdp);
- if (!isActive())
+ if (state_ != State::OFFERSENT)
return false;
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
@@ -206,18 +218,20 @@ WebRTCSession::acceptAnswer(const std::string &sdp)
}
void
-WebRTCSession::acceptICECandidates(const std::vector& candidates)
+WebRTCSession::acceptICECandidates(const std::vector &candidates)
{
- if (isActive()) {
- for (const auto& c : candidates)
+ if (state_ >= State::INITIATED) {
+ for (const auto &c : candidates)
g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
}
+ if (state_ < State::CONNECTED)
+ emit stateChanged(State::CONNECTING);
}
bool
WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
{
- if (!isActive())
+ if (state_ < State::INITIATED)
return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
@@ -241,20 +255,7 @@ WebRTCSession::end()
pipe_ = nullptr;
}
webrtc_ = nullptr;
- emit pipelineChanged(false);
-}
-
-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));
- }
+ emit stateChanged(State::DISCONNECTED);
}
namespace {
@@ -373,8 +374,10 @@ gboolean
onICEGatheringCompletion(gpointer timerid)
{
*(guint*)(timerid) = 0;
- if (gisoffer)
+ if (gisoffer) {
emit WebRTCSession::instance().offerCreated(glocalsdp, gcandidates);
+ emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
+ }
else
emit WebRTCSession::instance().answerCreated(glocalsdp, gcandidates);
@@ -445,6 +448,9 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe
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);
}
}
diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h
index fffefb2..42db204 100644
--- a/src/WebRTCSession.h
+++ b/src/WebRTCSession.h
@@ -14,6 +14,15 @@ class WebRTCSession : public QObject
Q_OBJECT
public:
+ enum class State {
+ DISCONNECTED,
+ INITIATING,
+ INITIATED,
+ OFFERSENT,
+ CONNECTING,
+ CONNECTED
+ };
+
static WebRTCSession& instance()
{
static WebRTCSession instance;
@@ -27,7 +36,7 @@ public:
bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector&);
- bool isActive() { return pipe_ != nullptr; }
+ State state() const {return state_;}
bool toggleMuteAudioSrc(bool &isMuted);
void end();
@@ -37,12 +46,16 @@ public:
signals:
void offerCreated(const std::string &sdp, const std::vector&);
void answerCreated(const std::string &sdp, const std::vector&);
- void pipelineChanged(bool started);
+ void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt
+
+private slots:
+ void setState(State state) {state_ = state;}
private:
- WebRTCSession() : QObject() {}
+ WebRTCSession();
bool initialised_ = false;
+ State state_ = State::DISCONNECTED;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
std::string stunServer_;
@@ -50,7 +63,6 @@ private:
bool startPipeline(int opusPayloadType);
bool createPipeline(int opusPayloadType);
- void addTurnServers();
public:
WebRTCSession(WebRTCSession const&) = delete;
diff --git a/src/dialogs/AcceptCall.cpp b/src/dialogs/AcceptCall.cpp
index f04a613..6b5e2e6 100644
--- a/src/dialogs/AcceptCall.cpp
+++ b/src/dialogs/AcceptCall.cpp
@@ -1,43 +1,83 @@
#include
#include
+#include
#include
#include "Config.h"
+#include "Utils.h"
#include "dialogs/AcceptCall.h"
+#include "ui/Avatar.h"
namespace dialogs {
-AcceptCall::AcceptCall(const QString &caller, const QString &displayName, QWidget *parent)
- : QWidget(parent)
+AcceptCall::AcceptCall(
+ const QString &caller,
+ const QString &displayName,
+ const QString &roomName,
+ const QString &avatarUrl,
+ QWidget *parent) : QWidget(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
+ setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(15);
- buttonLayout->setMargin(0);
+ QFont f;
+ f.setPointSizeF(f.pointSizeF());
+
+ 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_->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(rejectBtn_);
- QLabel *label;
- if (!displayName.isEmpty() && displayName != caller)
- label = new QLabel("Accept call from " + displayName + " (" + caller + ")?", this);
- else
- label = new QLabel("Accept call from " + caller + "?", this);
-
- layout->addWidget(label);
+ if (displayNameLabel)
+ layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
+ layout->addWidget(callerLabel, 0, Qt::AlignCenter);
+ layout->addWidget(voiceCallLabel, 0, Qt::AlignCenter);
+ layout->addWidget(avatar, 0, Qt::AlignCenter);
layout->addLayout(buttonLayout);
connect(acceptBtn_, &QPushButton::clicked, this, [this]() {
diff --git a/src/dialogs/AcceptCall.h b/src/dialogs/AcceptCall.h
index a410d6b..8e3ed3b 100644
--- a/src/dialogs/AcceptCall.h
+++ b/src/dialogs/AcceptCall.h
@@ -1,9 +1,9 @@
#pragma once
-#include
#include
class QPushButton;
+class QString;
namespace dialogs {
@@ -12,7 +12,12 @@ class AcceptCall : public QWidget
Q_OBJECT
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:
void accept();
diff --git a/src/dialogs/PlaceCall.cpp b/src/dialogs/PlaceCall.cpp
index 8b37ff6..c5c78f9 100644
--- a/src/dialogs/PlaceCall.cpp
+++ b/src/dialogs/PlaceCall.cpp
@@ -4,12 +4,18 @@
#include
#include "Config.h"
+#include "Utils.h"
#include "dialogs/PlaceCall.h"
+#include "ui/Avatar.h"
namespace dialogs {
-PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget *parent)
- : QWidget(parent)
+PlaceCall::PlaceCall(
+ const QString &callee,
+ const QString &displayName,
+ const QString &roomName,
+ const QString &avatarUrl,
+ QWidget *parent) : QWidget(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
@@ -20,25 +26,31 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
- auto buttonLayout = new QHBoxLayout();
+ auto buttonLayout = new QHBoxLayout(this);
buttonLayout->setSpacing(15);
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_->setDefault(true);
- videoBtn_ = new QPushButton(tr("Video Call"), this);
+ //videoBtn_ = new QPushButton(tr("Video Call"), this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
buttonLayout->addStretch(1);
+ buttonLayout->addWidget(avatar);
buttonLayout->addWidget(voiceBtn_);
- buttonLayout->addWidget(videoBtn_);
+ //buttonLayout->addWidget(videoBtn_);
buttonLayout->addWidget(cancelBtn_);
- QLabel *label;
- if (!displayName.isEmpty() && displayName != callee)
- label = new QLabel("Place a call to " + displayName + " (" + callee + ")?", this);
- else
- label = new QLabel("Place a call to " + callee + "?", this);
+ QString name = displayName.isEmpty() ? callee : displayName;
+ QLabel *label = new QLabel("Place a call to " + name + "?", this);
layout->addWidget(label);
layout->addLayout(buttonLayout);
@@ -47,10 +59,10 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget
emit voice();
emit close();
});
- connect(videoBtn_, &QPushButton::clicked, this, [this]() {
+ /*connect(videoBtn_, &QPushButton::clicked, this, [this]() {
emit video();
emit close();
- });
+ });*/
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
diff --git a/src/dialogs/PlaceCall.h b/src/dialogs/PlaceCall.h
index b4de142..1c157b7 100644
--- a/src/dialogs/PlaceCall.h
+++ b/src/dialogs/PlaceCall.h
@@ -12,16 +12,21 @@ class PlaceCall : public QWidget
Q_OBJECT
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:
void voice();
- void video();
+// void video();
void cancel();
private:
QPushButton *voiceBtn_;
- QPushButton *videoBtn_;
+// QPushButton *videoBtn_;
QPushButton *cancelBtn_;
};