From 7a206441c86cd2aa84cbbbc6be803f03b2f355ab Mon Sep 17 00:00:00 2001 From: trilene Date: Fri, 10 Jul 2020 19:19:48 -0400 Subject: Support voice calls --- src/TextInputWidget.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src/TextInputWidget.cpp') diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 3e3915bb..2be0b404 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -31,6 +31,7 @@ #include "Logging.h" #include "TextInputWidget.h" #include "Utils.h" +#include "WebRTCSession.h" #include "ui/FlatButton.h" #include "ui/LoadingIndicator.h" @@ -453,6 +454,13 @@ TextInputWidget::TextInputWidget(QWidget *parent) topLayout_->setSpacing(0); topLayout_->setContentsMargins(13, 1, 13, 0); + callBtn_ = new FlatButton(this); + changeCallButtonState(false); + connect(&WebRTCSession::instance(), + &WebRTCSession::pipelineChanged, + this, + &TextInputWidget::changeCallButtonState); + QIcon send_file_icon; send_file_icon.addFile(":/icons/icons/ui/paper-clip-outline.png"); @@ -521,6 +529,7 @@ TextInputWidget::TextInputWidget(QWidget *parent) emojiBtn_->setIcon(emoji_icon); emojiBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight)); + topLayout_->addWidget(callBtn_); topLayout_->addWidget(sendFileBtn_); topLayout_->addWidget(input_); topLayout_->addWidget(emojiBtn_); @@ -528,6 +537,7 @@ TextInputWidget::TextInputWidget(QWidget *parent) setLayout(topLayout_); + connect(callBtn_, &FlatButton::clicked, this, &TextInputWidget::callButtonPress); connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit); connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage); @@ -652,3 +662,19 @@ TextInputWidget::paintEvent(QPaintEvent *) style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } + +void +TextInputWidget::changeCallButtonState(bool callStarted) +{ + // 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 { + callBtn_->setToolTip(tr("Place a call")); + icon.addFile(":/icons/icons/ui/speech-bubbles-comment-option.png"); + } + callBtn_->setIcon(icon); + callBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight)); +} -- cgit 1.5.1 From 88cfa3a8fa7554ab545f6779f2dda9709f72fbbb Mon Sep 17 00:00:00 2001 From: trilene Date: Wed, 22 Jul 2020 21:15:45 -0400 Subject: Polish voice call UI --- resources/icons/ui/end-call.png | Bin 0 -> 643 bytes resources/icons/ui/microphone-mute.png | Bin 0 -> 1153 bytes resources/icons/ui/microphone-unmute.png | Bin 0 -> 1093 bytes resources/icons/ui/place-call.png | Bin 0 -> 759 bytes resources/res.qrc | 5 ++ src/ActiveCallBar.cpp | 142 ++++++++++++++++++++++++------- src/ActiveCallBar.h | 23 ++++- src/CallManager.cpp | 45 ++++++---- src/CallManager.h | 6 +- src/ChatPage.cpp | 30 ++++--- src/TextInputWidget.cpp | 20 ++--- src/TextInputWidget.h | 3 +- src/WebRTCSession.cpp | 76 +++++++++-------- src/WebRTCSession.h | 20 ++++- src/dialogs/AcceptCall.cpp | 68 ++++++++++++--- src/dialogs/AcceptCall.h | 9 +- src/dialogs/PlaceCall.cpp | 36 +++++--- src/dialogs/PlaceCall.h | 11 ++- 18 files changed, 348 insertions(+), 146 deletions(-) create mode 100644 resources/icons/ui/end-call.png create mode 100644 resources/icons/ui/microphone-mute.png create mode 100644 resources/icons/ui/microphone-unmute.png create mode 100644 resources/icons/ui/place-call.png (limited to 'src/TextInputWidget.cpp') diff --git a/resources/icons/ui/end-call.png b/resources/icons/ui/end-call.png new file mode 100644 index 00000000..6cbb983e 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 00000000..0042fbe2 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 00000000..27999c70 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 00000000..a820cf3f Binary files /dev/null and b/resources/icons/ui/place-call.png differ diff --git a/resources/res.qrc b/resources/res.qrc index 3fd3fc96..b245f48f 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 a5ef754d..5703c1ed 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 dd01e2ad..8440d7f3 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 92af3b2f..b5c59e08 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 8a93241f..df83a87a 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 15b7c545..5b8ea475 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 2be0b404..d49fc746 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 ae58f4e3..27dff57f 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 4ef7a818..5baed72e 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 fffefb25..42db204d 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 f04a613a..6b5e2e60 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 a410d6b7..8e3ed3b2 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 8b37ff6a..c5c78f94 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 b4de1428..1c157b7b 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_; }; -- cgit 1.5.1 From 43ec0c062467c05a66eac7a3fb992bc093315c89 Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 26 Jul 2020 10:59:50 -0400 Subject: Handle ICE failure --- src/ActiveCallBar.cpp | 7 ++++ src/CallManager.cpp | 96 ++++++++++++++++++++++++++++++++----------------- src/CallManager.h | 8 +++-- src/ChatPage.cpp | 9 ----- src/TextInputWidget.cpp | 3 +- src/WebRTCSession.cpp | 65 ++++++++++++++++++++++----------- src/WebRTCSession.h | 4 ++- 7 files changed, 125 insertions(+), 67 deletions(-) (limited to 'src/TextInputWidget.cpp') diff --git a/src/ActiveCallBar.cpp b/src/ActiveCallBar.cpp index 564842da..e55b2e86 100644 --- a/src/ActiveCallBar.cpp +++ b/src/ActiveCallBar.cpp @@ -123,25 +123,32 @@ ActiveCallBar::update(WebRTCSession::State state) { switch (state) { case WebRTCSession::State::INITIATING: + show(); stateLabel_->setText("Initiating call..."); break; case WebRTCSession::State::INITIATED: + show(); stateLabel_->setText("Call initiated..."); break; case WebRTCSession::State::OFFERSENT: + show(); stateLabel_->setText("Calling..."); break; case WebRTCSession::State::CONNECTING: + show(); stateLabel_->setText("Connecting..."); break; case WebRTCSession::State::CONNECTED: + show(); callStartTime_ = QDateTime::currentSecsSinceEpoch(); timer_->start(1000); stateLabel_->setText("Voice call:"); durationLabel_->setText("00:00"); durationLabel_->show(); break; + case WebRTCSession::State::ICEFAILED: case WebRTCSession::State::DISCONNECTED: + hide(); timer_->stop(); callPartyLabel_->setText(QString()); stateLabel_->setText(QString()); diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 3caa812d..b57ef1bb 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -11,9 +11,10 @@ #include "MatrixClient.h" #include "UserSettingsPage.h" #include "WebRTCSession.h" - #include "dialogs/AcceptCall.h" +#include "mtx/responses/turn_server.hpp" + Q_DECLARE_METATYPE(std::vector) Q_DECLARE_METATYPE(mtx::events::msg::CallCandidates::Candidate) Q_DECLARE_METATYPE(mtx::responses::TurnServer) @@ -24,6 +25,11 @@ using namespace mtx::events::msg; // https://github.com/vector-im/riot-web/issues/10173 #define STUN_SERVER "stun://turn.matrix.org:3478" +namespace { +std::vector +getTurnURIs(const mtx::responses::TurnServer &turnServer); +} + CallManager::CallManager(QSharedPointer userSettings) : QObject(), session_(WebRTCSession::instance()), @@ -80,15 +86,23 @@ CallManager::CallManager(QSharedPointer userSettings) // Request new credentials close to expiry // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 - turnServer_ = res; + turnURIs_ = getTurnURIs(res); turnServerTimer_.setInterval(res.ttl * 1000 * 0.9); }); connect(&session_, &WebRTCSession::stateChanged, this, [this](WebRTCSession::State state) { - if (state == WebRTCSession::State::DISCONNECTED) + if (state == WebRTCSession::State::DISCONNECTED) { playRingtone("qrc:/media/media/callend.ogg", false); - }); + } + else if (state == WebRTCSession::State::ICEFAILED) { + QString error("Call connection failed."); + if (turnURIs_.empty()) + error += " Your homeserver has no configured TURN server."; + emit ChatPage::instance()->showNotification(error); + hangUp(CallHangUp::Reason::ICEFailed); + } + }); connect(&player_, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus status) { @@ -116,8 +130,8 @@ CallManager::sendInvite(const QString &roomid) } roomid_ = roomid; - setTurnServers(); session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : ""); + session_.setTurnServers(turnURIs_); generateCallID(); nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_); @@ -132,11 +146,26 @@ CallManager::sendInvite(const QString &roomid) } } +namespace { +std::string callHangUpReasonString(CallHangUp::Reason reason) +{ + switch (reason) { + case CallHangUp::Reason::ICEFailed: + return "ICE failed"; + case CallHangUp::Reason::InviteTimeOut: + return "Invite time out"; + default: + return "User"; + } +} +} + void CallManager::hangUp(CallHangUp::Reason reason) { if (!callid_.empty()) { - nhlog::ui()->debug("WebRTC: call id: {} - hanging up", callid_); + nhlog::ui()->debug("WebRTC: call id: {} - hanging up ({})", callid_, + callHangUpReasonString(reason)); emit newMessage(roomid_, CallHangUp{callid_, 0, reason}); endCall(); } @@ -221,8 +250,8 @@ CallManager::answerInvite(const CallInvite &invite) return; } - setTurnServers(); session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : ""); + session_.setTurnServers(turnURIs_); if (!session_.acceptOffer(invite.sdp)) { emit ChatPage::instance()->showNotification("Problem setting up call."); @@ -279,8 +308,9 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent) void CallManager::handleEvent(const RoomEvent &callHangUpEvent) { - nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp from {}", - callHangUpEvent.content.call_id, callHangUpEvent.sender); + nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}", + callHangUpEvent.content.call_id, callHangUpReasonString(callHangUpEvent.content.reason), + callHangUpEvent.sender); if (callid_ == callHangUpEvent.content.call_id) { MainWindow::instance()->hideOverlay(); @@ -320,12 +350,30 @@ CallManager::retrieveTurnServer() } void -CallManager::setTurnServers() +CallManager::playRingtone(const QString &ringtone, bool repeat) +{ + static QMediaPlaylist playlist; + playlist.clear(); + playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop : QMediaPlaylist::CurrentItemOnce); + playlist.addMedia(QUrl(ringtone)); + player_.setVolume(100); + player_.setPlaylist(&playlist); +} + +void +CallManager::stopRingtone() +{ + player_.setPlaylist(nullptr); +} + +namespace { +std::vector +getTurnURIs(const mtx::responses::TurnServer &turnServer) { // gstreamer expects: turn(s)://username:password@host:port?transport=udp(tcp) // where username and password are percent-encoded - std::vector uris; - for (const auto &uri : turnServer_.uris) { + std::vector ret; + for (const auto &uri : turnServer.uris) { if (auto c = uri.find(':'); c == std::string::npos) { nhlog::ui()->error("Invalid TURN server uri: {}", uri); continue; @@ -338,29 +386,13 @@ CallManager::setTurnServers() } QString encodedUri = QString::fromStdString(scheme) + "://" + - QUrl::toPercentEncoding(QString::fromStdString(turnServer_.username)) + ":" + - QUrl::toPercentEncoding(QString::fromStdString(turnServer_.password)) + "@" + + QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) + ":" + + QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) + "@" + QString::fromStdString(std::string(uri, ++c)); - uris.push_back(encodedUri.toStdString()); + ret.push_back(encodedUri.toStdString()); } } - if (!uris.empty()) - session_.setTurnServers(uris); + return ret; } - -void -CallManager::playRingtone(const QString &ringtone, bool repeat) -{ - static QMediaPlaylist playlist; - playlist.clear(); - playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop : QMediaPlaylist::CurrentItemOnce); - playlist.addMedia(QUrl(ringtone)); - player_.setVolume(100); - player_.setPlaylist(&playlist); } -void -CallManager::stopRingtone() -{ - player_.setPlaylist(nullptr); -} diff --git a/src/CallManager.h b/src/CallManager.h index 3debf2e8..6518fd13 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -11,7 +11,10 @@ #include "mtx/events/collections.hpp" #include "mtx/events/voip.hpp" -#include "mtx/responses/turn_server.hpp" + +namespace mtx::responses { +struct TurnServer; +} class UserSettings; class WebRTCSession; @@ -51,7 +54,7 @@ private: std::string callid_; const uint32_t timeoutms_ = 120000; std::vector remoteICECandidates_; - mtx::responses::TurnServer turnServer_; + std::vector turnURIs_; QTimer turnServerTimer_; QSharedPointer settings_; QMediaPlayer player_; @@ -65,7 +68,6 @@ private: void answerInvite(const mtx::events::msg::CallInvite&); void generateCallID(); void endCall(); - void setTurnServers(); void playRingtone(const QString &ringtone, bool repeat); void stopRingtone(); }; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 5b8ea475..b53a5761 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -137,15 +137,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) activeCallBar_->hide(); connect( &callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty); - connect(&WebRTCSession::instance(), - &WebRTCSession::stateChanged, - this, - [this](WebRTCSession::State state) { - if (state == WebRTCSession::State::DISCONNECTED) - activeCallBar_->hide(); - else - activeCallBar_->show(); - }); // Splitter splitter->addWidget(sideBar_); diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index d49fc746..9aadc101 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -666,7 +666,8 @@ void TextInputWidget::changeCallButtonState(WebRTCSession::State state) { QIcon icon; - if (state == WebRTCSession::State::DISCONNECTED) { + if (state == WebRTCSession::State::ICEFAILED || + state == WebRTCSession::State::DISCONNECTED) { callBtn_->setToolTip(tr("Place a call")); icon.addFile(":/icons/icons/ui/place-call.png"); } else { diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index ff9ec661..95a9041e 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -14,9 +14,9 @@ extern "C" { Q_DECLARE_METATYPE(WebRTCSession::State) namespace { -bool gisoffer; -std::string glocalsdp; -std::vector gcandidates; +bool isoffering_; +std::string localsdp_; +std::vector localcandidates_; gboolean newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data); GstWebRTCSessionDescription* parseSDP(const std::string &sdp, GstWebRTCSDPType type); @@ -24,6 +24,7 @@ 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); @@ -92,9 +93,9 @@ WebRTCSession::init(std::string *errorMessage) bool WebRTCSession::createOffer() { - gisoffer = true; - glocalsdp.clear(); - gcandidates.clear(); + isoffering_ = true; + localsdp_.clear(); + localcandidates_.clear(); return startPipeline(111); // a dynamic opus payload type } @@ -105,9 +106,9 @@ WebRTCSession::acceptOffer(const std::string &sdp) if (state_ != State::DISCONNECTED) return false; - gisoffer = false; - glocalsdp.clear(); - gcandidates.clear(); + isoffering_ = false; + localsdp_.clear(); + localcandidates_.clear(); int opusPayloadType = getPayloadType(sdp, "opus"); if (opusPayloadType == -1) @@ -152,14 +153,20 @@ WebRTCSession::startPipeline(int opusPayloadType) gboolean udata; g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata)); } + if (turnServers_.empty()) + nhlog::ui()->warn("WebRTC: no TURN server provided"); // generate the offer when the pipeline goes to PLAYING - if (gisoffer) + if (isoffering_) g_signal_connect(webrtc_, "on-negotiation-needed", G_CALLBACK(generateOffer), nullptr); // on-ice-candidate is emitted when a local ICE candidate has been gathered g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr); + // capture ICE failure + g_signal_connect(webrtc_, "notify::ice-connection-state", + G_CALLBACK(iceConnectionStateChanged), nullptr); + // incoming streams trigger pad-added gst_element_set_state(pipe_, GST_STATE_READY); g_signal_connect(webrtc_, "pad-added", G_CALLBACK(addDecodeBin), pipe_); @@ -229,8 +236,6 @@ WebRTCSession::acceptICECandidates(const std::vectordebug("WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate); g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str()); } - if (state_ == State::OFFERSENT) - emit stateChanged(State::CONNECTING); } } @@ -357,11 +362,11 @@ setLocalDescription(GstPromise *promise, gpointer webrtc) g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr); gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp); - glocalsdp = std::string(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", glocalsdp); + nhlog::ui()->debug("WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_); } void @@ -369,12 +374,12 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, guint mlineIndex, gchar * { nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate); - if (WebRTCSession::instance().state() == WebRTCSession::State::CONNECTED) { + if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) { emit WebRTCSession::instance().newICECandidate({"audio", (uint16_t)mlineIndex, candidate}); return; } - gcandidates.push_back({"audio", (uint16_t)mlineIndex, candidate}); + 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 @@ -390,18 +395,36 @@ gboolean onICEGatheringCompletion(gpointer timerid) { *(guint*)(timerid) = 0; - if (gisoffer) { - emit WebRTCSession::instance().offerCreated(glocalsdp, gcandidates); + if (isoffering_) { + emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT); } else { - emit WebRTCSession::instance().answerCreated(glocalsdp, gcandidates); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING); + 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) { diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h index f9882089..d79047a8 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h @@ -15,10 +15,12 @@ class WebRTCSession : public QObject public: enum class State { + ICEFAILED, DISCONNECTED, INITIATING, INITIATED, OFFERSENT, + ANSWERSENT, CONNECTING, CONNECTED }; @@ -30,13 +32,13 @@ public: } bool init(std::string *errorMessage = nullptr); + State state() const {return state_;} bool createOffer(); bool acceptOffer(const std::string &sdp); bool acceptAnswer(const std::string &sdp); void acceptICECandidates(const std::vector&); - State state() const {return state_;} bool toggleMuteAudioSrc(bool &isMuted); void end(); -- cgit 1.5.1 From 14a0aac74873c27c0454d206848f27b4eec123ae Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 9 Aug 2020 23:36:47 +0200 Subject: Add /clear-timeline command --- src/Cache.cpp | 118 +++++++++++++++++++++++++++++++++---- src/Cache_p.h | 3 + src/ChatPage.cpp | 5 ++ src/TextInputWidget.cpp | 24 ++++---- src/TextInputWidget.h | 1 + src/timeline/EventStore.cpp | 20 +++++++ src/timeline/EventStore.h | 1 + src/timeline/TimelineModel.h | 1 + src/timeline/TimelineViewManager.h | 6 ++ 9 files changed, 157 insertions(+), 22 deletions(-) (limited to 'src/TextInputWidget.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index 0c692d07..0d879584 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2304,6 +2304,11 @@ Cache::saveTimelineMessages(lmdb::txn &txn, lmdb::val event_id = event_id_val; + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + if (first && !res.prev_batch.empty()) + orderEntry["prev_batch"] = res.prev_batch; + lmdb::val txn_order; if (!txn_id.empty() && lmdb::dbi_get(txn, evToOrderDb, lmdb::val(txn_id), txn_order)) { @@ -2317,7 +2322,7 @@ Cache::saveTimelineMessages(lmdb::txn &txn, lmdb::dbi_del(txn, msg2orderDb, lmdb::val(txn_id)); } - lmdb::dbi_put(txn, orderDb, txn_order, event_id); + lmdb::dbi_put(txn, orderDb, txn_order, lmdb::val(orderEntry.dump())); lmdb::dbi_put(txn, evToOrderDb, event_id, txn_order); lmdb::dbi_del(txn, evToOrderDb, lmdb::val(txn_id)); @@ -2389,10 +2394,6 @@ Cache::saveTimelineMessages(lmdb::txn &txn, ++index; - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - if (first && !res.prev_batch.empty()) - orderEntry["prev_batch"] = res.prev_batch; first = false; nhlog::db()->debug("saving '{}'", orderEntry.dump()); @@ -2440,6 +2441,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message auto relationsDb = getRelationsDb(txn, room_id); auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); auto msg2orderDb = getMessageToOrderDb(txn, room_id); auto order2msgDb = getOrderToMessageDb(txn, room_id); @@ -2483,6 +2485,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message lmdb::dbi_put( txn, orderDb, lmdb::val(&index, sizeof(index)), lmdb::val(orderEntry.dump())); + lmdb::dbi_put(txn, evToOrderDb, event_id, lmdb::val(&index, sizeof(index))); // TODO(Nico): Allow blacklisting more event types in UI if (event["type"] != "m.reaction" && event["type"] != "m.dummy") { @@ -2516,6 +2519,94 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message return msgIndex; } +void +Cache::clearTimeline(const std::string &room_id) +{ + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); + + lmdb::val indexVal, val; + auto cursor = lmdb::cursor::open(txn, orderDb); + + bool start = true; + bool passed_pagination_token = false; + while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { + start = false; + json obj; + + try { + obj = json::parse(std::string_view(val.data(), val.size())); + } catch (std::exception &) { + // workaround bug in the initial db format, where we sometimes didn't store + // json... + obj = {{"event_id", std::string(val.data(), val.size())}}; + } + + if (passed_pagination_token) { + if (obj.count("event_id") != 0) { + lmdb::val event_id = obj["event_id"].get(); + lmdb::dbi_del(txn, evToOrderDb, event_id); + lmdb::dbi_del(txn, eventsDb, event_id); + + lmdb::dbi_del(txn, relationsDb, event_id); + + lmdb::val order{}; + bool exists = lmdb::dbi_get(txn, msg2orderDb, event_id, order); + if (exists) { + lmdb::dbi_del(txn, order2msgDb, order); + lmdb::dbi_del(txn, msg2orderDb, event_id); + } + } + lmdb::cursor_del(cursor); + } else { + if (obj.count("prev_batch") != 0) + passed_pagination_token = true; + } + } + + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + start = true; + while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { + start = false; + + lmdb::val eventId; + bool innerStart = true; + bool found = false; + while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) { + innerStart = false; + + json obj; + try { + obj = json::parse(std::string_view(eventId.data(), eventId.size())); + } catch (std::exception &) { + obj = {{"event_id", std::string(eventId.data(), eventId.size())}}; + } + + if (obj["event_id"] == std::string(val.data(), val.size())) { + found = true; + break; + } + } + + if (!found) + break; + } + + do { + lmdb::cursor_del(msgCursor); + } while (msgCursor.get(indexVal, val, MDB_PREV)); + + cursor.close(); + msgCursor.close(); + txn.commit(); +} + mtx::responses::Notifications Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id) { @@ -2654,11 +2745,13 @@ Cache::deleteOldMessages() auto room_ids = getRoomIds(txn); for (const auto &room_id : room_ids) { - auto orderDb = getEventOrderDb(txn, room_id); - auto o2m = getOrderToMessageDb(txn, room_id); - auto m2o = getMessageToOrderDb(txn, room_id); - auto eventsDb = getEventsDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, orderDb); + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto o2m = getOrderToMessageDb(txn, room_id); + auto m2o = getMessageToOrderDb(txn, room_id); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, orderDb); uint64_t first, last; if (cursor.get(indexVal, val, MDB_LAST)) { @@ -2678,14 +2771,17 @@ Cache::deleteOldMessages() bool start = true; while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) && - message_count-- < MAX_RESTORED_MESSAGES) { + message_count-- > MAX_RESTORED_MESSAGES) { start = false; auto obj = json::parse(std::string_view(val.data(), val.size())); if (obj.count("event_id") != 0) { lmdb::val event_id = obj["event_id"].get(); + lmdb::dbi_del(txn, evToOrderDb, event_id); lmdb::dbi_del(txn, eventsDb, event_id); + lmdb::dbi_del(txn, relationsDb, event_id); + lmdb::val order{}; bool exists = lmdb::dbi_get(txn, m2o, event_id, order); if (exists) { diff --git a/src/Cache_p.h b/src/Cache_p.h index 61d91b0c..d3ec6ee0 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -208,6 +208,9 @@ public: const std::string &room_id); void removePendingStatus(const std::string &room_id, const std::string &txn_id); + //! clear timeline keeping only the latest batch + void clearTimeline(const std::string &room_id); + //! Remove old unused data. void deleteOldMessages(); void deleteOldData() noexcept; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 518be31c..63d13fb9 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -155,6 +155,11 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) trySync(); }); + connect(text_input_, + &TextInputWidget::clearRoomTimeline, + view_manager_, + &TimelineViewManager::clearCurrentRoomTimeline); + connect( new QShortcut(QKeySequence("Ctrl+Down"), this), &QShortcut::activated, this, [this]() { if (isVisible()) diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 3e3915bb..91846230 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -566,27 +566,29 @@ void TextInputWidget::command(QString command, QString args) { if (command == "me") { - sendEmoteMessage(args); + emit sendEmoteMessage(args); } else if (command == "join") { - sendJoinRoomRequest(args); + emit sendJoinRoomRequest(args); } else if (command == "invite") { - sendInviteRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + emit sendInviteRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == "kick") { - sendKickRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + emit sendKickRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == "ban") { - sendBanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + emit sendBanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == "unban") { - sendUnbanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + emit sendUnbanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == "roomnick") { - changeRoomNick(args); + emit changeRoomNick(args); } else if (command == "shrug") { - sendTextMessage("¯\\_(ツ)_/¯"); + emit sendTextMessage("¯\\_(ツ)_/¯"); } else if (command == "fliptable") { - sendTextMessage("(╯°□°)╯︵ ┻━┻"); + emit sendTextMessage("(╯°□°)╯︵ ┻━┻"); } else if (command == "unfliptable") { - sendTextMessage(" ┯━┯╭( º _ º╭)"); + emit sendTextMessage(" ┯━┯╭( º _ º╭)"); } else if (command == "sovietflip") { - sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\"); + emit sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\"); + } else if (command == "clear-timeline") { + emit clearRoomTimeline(); } } diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index a0105eb0..cbb6ea95 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -156,6 +156,7 @@ private slots: signals: void sendTextMessage(const QString &msg); void sendEmoteMessage(QString msg); + void clearRoomTimeline(); void heightChanged(int height); void uploadMedia(const QSharedPointer data, diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index a983fe01..fca1d31d 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -175,6 +175,26 @@ EventStore::addPending(mtx::events::collections::TimelineEvents event) emit processPending(); } +void +EventStore::clearTimeline() +{ + emit beginResetModel(); + + cache::client()->clearTimeline(room_id_); + auto range = cache::client()->getTimelineRange(room_id_); + if (range) { + nhlog::db()->info("Range {} {}", range->last, range->first); + this->last = range->last; + this->first = range->first; + } else { + this->first = std::numeric_limits::max(); + this->last = std::numeric_limits::max(); + } + nhlog::ui()->info("Range {} {}", this->last, this->first); + + emit endResetModel(); +} + void EventStore::handleSync(const mtx::responses::Timeline &events) { diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index b5c17d10..d4353a18 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -101,6 +101,7 @@ signals: public slots: void addPending(mtx::events::collections::TimelineEvents event); + void clearTimeline(); private: mtx::events::collections::TimelineEvents *decryptEvent( diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index f8a84f17..0bcf42b7 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -242,6 +242,7 @@ public slots: } } void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; } + void clearTimeline() { events.clearTimeline(); } private slots: void addPendingMessage(mtx::events::collections::TimelineEvents event); diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 63106916..20dbc3bb 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -92,6 +92,12 @@ public slots: uint64_t dsize); void updateEncryptedDescriptions(); + void clearCurrentRoomTimeline() + { + if (timeline_) + timeline_->clearTimeline(); + } + private: #ifdef USE_QUICK_VIEW QQuickView *view; -- cgit 1.5.1 From f157602a52278519370c45cde4c9a0b12ede8d7e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 14 Aug 2020 01:03:27 +0200 Subject: Disable call support, when GStreamer is unavailable Integrating that in our CI is currently a bit hard, so disable it for now, if GStreamer isn't found. Just make sure to build against GStreamer for call support! --- CMakeLists.txt | 10 +++++--- src/CallManager.cpp | 4 +++ src/TextInputWidget.cpp | 6 +++++ src/WebRTCSession.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 5 deletions(-) (limited to 'src/TextInputWidget.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index e17f70f6..1be11fa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -427,8 +427,7 @@ else() endif() include(FindPkgConfig) -pkg_check_modules(GST_SDP REQUIRED IMPORTED_TARGET gstreamer-sdp-1.0>=1.14) -pkg_check_modules(GST_WEBRTC REQUIRED IMPORTED_TARGET gstreamer-webrtc-1.0>=1.14) +pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.14 gstreamer-webrtc-1.0>=1.14) # single instance functionality set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") @@ -595,8 +594,6 @@ target_link_libraries(nheko PRIVATE lmdbxx::lmdbxx liblmdb::lmdb tweeny - PkgConfig::GST_SDP - PkgConfig::GST_WEBRTC SingleApplication::SingleApplication) if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") @@ -606,6 +603,11 @@ target_precompile_headers(nheko ) endif() +if (TARGET PkgConfig::GSTREAMER) + target_link_libraries(nheko PRIVATE PkgConfig::GSTREAMER) + target_compile_definitions(nheko PRIVATE GSTREAMER_AVAILABLE) +endif() + if(MSVC) target_link_libraries(nheko PRIVATE ntdll) endif() diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 7ecabde0..32b82fdf 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -201,9 +201,13 @@ CallManager::onActiveCall() void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) { +#ifdef GSTREAMER_AVAILABLE if (handleEvent_(event) || handleEvent_(event) || handleEvent_(event) || handleEvent_(event)) return; +#else + (void)event; +#endif } template diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 9aadc101..a3392170 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -453,12 +453,14 @@ TextInputWidget::TextInputWidget(QWidget *parent) topLayout_->setSpacing(0); topLayout_->setContentsMargins(13, 1, 13, 0); +#ifdef GSTREAMER_AVAILABLE callBtn_ = new FlatButton(this); changeCallButtonState(WebRTCSession::State::DISCONNECTED); connect(&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &TextInputWidget::changeCallButtonState); +#endif QIcon send_file_icon; send_file_icon.addFile(":/icons/icons/ui/paper-clip-outline.png"); @@ -528,7 +530,9 @@ TextInputWidget::TextInputWidget(QWidget *parent) emojiBtn_->setIcon(emoji_icon); emojiBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight)); +#ifdef GSTREAMER_AVAILABLE topLayout_->addWidget(callBtn_); +#endif topLayout_->addWidget(sendFileBtn_); topLayout_->addWidget(input_); topLayout_->addWidget(emojiBtn_); @@ -536,7 +540,9 @@ TextInputWidget::TextInputWidget(QWidget *parent) setLayout(topLayout_); +#ifdef GSTREAMER_AVAILABLE connect(callBtn_, &FlatButton::clicked, this, &TextInputWidget::callButtonPress); +#endif connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit); connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage); diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index 5638c607..dd1dd61f 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -3,6 +3,7 @@ #include "Logging.h" #include "WebRTCSession.h" +#ifdef GSTREAMER_AVAILABLE extern "C" { #include "gst/gst.h" @@ -11,6 +12,7 @@ extern "C" #define GST_USE_UNSTABLE_API #include "gst/webrtc/webrtc.h" } +#endif Q_DECLARE_METATYPE(WebRTCSession::State) @@ -24,6 +26,7 @@ WebRTCSession::WebRTCSession() bool WebRTCSession::init(std::string *errorMessage) { +#ifdef GSTREAMER_AVAILABLE if (initialised_) return true; @@ -81,10 +84,14 @@ WebRTCSession::init(std::string *errorMessage) *errorMessage = strError; } return initialised_; +#else + (void)errorMessage; + return false; +#endif } +#ifdef GSTREAMER_AVAILABLE namespace { - bool isoffering_; std::string localsdp_; std::vector localcandidates_; @@ -631,3 +638,62 @@ WebRTCSession::getAudioSourceNames(const std::string &defaultDevice) } return ret; } +#else + +bool +WebRTCSession::createOffer() +{ + return false; +} + +bool +WebRTCSession::acceptOffer(const std::string &) +{ + return false; +} + +bool +WebRTCSession::acceptAnswer(const std::string &) +{ + return false; +} + +void +WebRTCSession::acceptICECandidates(const std::vector &) +{} + +bool +WebRTCSession::startPipeline(int) +{ + return false; +} + +bool +WebRTCSession::createPipeline(int) +{ + return false; +} + +bool +WebRTCSession::toggleMuteAudioSrc(bool &) +{ + return false; +} + +void +WebRTCSession::end() +{ +} + +void +WebRTCSession::refreshDevices() +{ +} + +std::vector +WebRTCSession::getAudioSourceNames(const std::string &) +{ + return {}; +} + +#endif -- cgit 1.5.1 From 1402732b5ffec7446edb5c06c9b7004a480b987b Mon Sep 17 00:00:00 2001 From: trilene Date: Mon, 17 Aug 2020 17:42:06 -0400 Subject: Stop SendFile and Call buttons swapping places on file upload --- src/TextInputWidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/TextInputWidget.cpp') diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index a3392170..633b12ba 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -633,7 +633,7 @@ TextInputWidget::showUploadSpinner() topLayout_->removeWidget(sendFileBtn_); sendFileBtn_->hide(); - topLayout_->insertWidget(0, spinner_); + topLayout_->insertWidget(1, spinner_); spinner_->start(); } @@ -641,7 +641,7 @@ void TextInputWidget::hideUploadSpinner() { topLayout_->removeWidget(spinner_); - topLayout_->insertWidget(0, sendFileBtn_); + topLayout_->insertWidget(1, sendFileBtn_); sendFileBtn_->show(); spinner_->stop(); } -- cgit 1.5.1