diff --git a/CMakeLists.txt b/CMakeLists.txt index fe686ddf..f5628e2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,7 +279,6 @@ set(SRC_FILES src/ui/Theme.cpp src/ui/ThemeManager.cpp - src/ActiveCallBar.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp src/Cache.cpp @@ -491,7 +490,6 @@ qt5_wrap_cpp(MOC_HEADERS src/notifications/Manager.h - src/ActiveCallBar.h src/AvatarProvider.h src/BlurhashProvider.h src/Cache_p.h diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml new file mode 100644 index 00000000..8d837c29 --- /dev/null +++ b/resources/qml/ActiveCallBar.qml @@ -0,0 +1,111 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 + +import im.nheko 1.0 + +Rectangle { + id: activeCallBar + visible: timelineManager.callState != WebRTCState.DISCONNECTED + color: "#2ECC71" + implicitHeight: rowLayout.height + 8 + + RowLayout { + id: rowLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 8 + + Avatar { + width: avatarSize + height: avatarSize + + url: timelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") + displayName: timelineManager.callPartyName + } + + Label { + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: " " + timelineManager.callPartyName + " " + } + + Image { + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + source: "qrc:/icons/icons/ui/place-call.png" + } + + Label { + id: callStateLabel + font.pointSize: fontMetrics.font.pointSize * 1.1 + } + + Connections { + target: timelineManager + function onCallStateChanged(state) { + switch (state) { + case WebRTCState.INITIATING: + callStateLabel.text = qsTr("Initiating...") + break; + case WebRTCState.OFFERSENT: + callStateLabel.text = qsTr("Calling...") + break; + case WebRTCState.CONNECTING: + callStateLabel.text = qsTr("Connecting...") + break; + case WebRTCState.CONNECTED: + callStateLabel.text = "00:00" + var d = new Date() + callTimer.startTime = Math.floor(d.getTime() / 1000) + break; + case WebRTCState.DISCONNECTED: + callStateLabel.text = "" + } + } + } + + Timer { + id: callTimer + property int startTime + interval: 1000 + running: timelineManager.callState == WebRTCState.CONNECTED + repeat: true + onTriggered: { + var d = new Date() + let seconds = Math.floor(d.getTime() / 1000 - startTime) + let s = Math.floor(seconds % 60) + let m = Math.floor(seconds / 60) % 60 + let h = Math.floor(seconds / 3600) + callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) + } + + function pad(n) { + return (n < 10) ? ("0" + n) : n + } + } + + Item { + Layout.fillWidth: true + } + + ImageButton { + width: 24 + height: 24 + buttonTextColor: "#000000" + image: timelineManager.isMicMuted ? + ":/icons/icons/ui/microphone-unmute.png" : + ":/icons/icons/ui/microphone-mute.png" + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: timelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") + + onClicked: timelineManager.toggleMicMute() + } + + Item { + implicitWidth: 16 + } + } +} diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index a3943806..c030c843 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -14,7 +14,7 @@ Rectangle { Label { anchors.fill: parent - text: timelineManager.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0))) + text: timelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") textFormat: Text.RichText font.pixelSize: avatar.height/2 verticalAlignment: Text.AlignVCenter diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index dd67d597..54399ae7 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -3,6 +3,8 @@ import QtQuick.Controls 2.3 AbstractButton { property string image: undefined + property color highlightColor: colors.highlight + property color buttonTextColor: colors.buttonText width: 16 height: 16 id: button @@ -11,7 +13,7 @@ AbstractButton { id: buttonImg // Workaround, can't get icon.source working for now... anchors.fill: parent - source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText) + source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor) } MouseArea diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index de818cd9..07c5e1a4 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -497,6 +497,11 @@ Page { } } } + + ActiveCallBar { + Layout.fillWidth: true + z: 3 + } } } } diff --git a/resources/res.qrc b/resources/res.qrc index b245f48f..63a40431 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -121,6 +121,7 @@ qtquickcontrols2.conf qml/TimelineView.qml + qml/ActiveCallBar.qml qml/Avatar.qml qml/ImageButton.qml qml/MatrixText.qml diff --git a/src/ActiveCallBar.cpp b/src/ActiveCallBar.cpp deleted file mode 100644 index c0d2c13a..00000000 --- a/src/ActiveCallBar.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#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) - : QWidget(parent) -{ - setAutoFillBackground(true); - auto p = palette(); - p.setColor(backgroundRole(), QColor(46, 204, 113)); - setPalette(p); - - QFont f; - f.setPointSizeF(f.pointSizeF()); - - const int fontHeight = QFontMetrics(f).height(); - const int widgetMargin = fontHeight / 3; - const int contentHeight = fontHeight * 3; - - setFixedHeight(contentHeight + widgetMargin); - - layout_ = new QHBoxLayout(this); - layout_->setSpacing(widgetMargin); - layout_->setContentsMargins(2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin); - - QFont labelFont; - 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); - - stateLabel_ = new QLabel(this); - stateLabel_->setFont(labelFont); - - durationLabel_ = new QLabel(this); - durationLabel_->setFont(labelFont); - durationLabel_->hide(); - - muteBtn_ = new FlatButton(this); - setMuteIcon(false); - muteBtn_->setFixedSize(buttonSize_, buttonSize_); - muteBtn_->setCornerRadius(buttonSize_ / 2); - connect(muteBtn_, &FlatButton::clicked, this, [this]() { - if (WebRTCSession::instance().toggleMuteAudioSrc(muted_)) - setMuteIcon(muted_); - }); - - 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, - const QString &roomName, - const QString &avatarUrl) -{ - callPartyLabel_->setText(" " + (displayName.isEmpty() ? userid : displayName) + " "); - - if (!avatarUrl.isEmpty()) - avatar_->setImage(avatarUrl); - else - avatar_->setLetter(utils::firstChar(roomName)); -} - -void -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_->setPixmap( - QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(buttonSize_, buttonSize_))); - durationLabel_->setText("00:00"); - durationLabel_->show(); - break; - case WebRTCSession::State::ICEFAILED: - case WebRTCSession::State::DISCONNECTED: - hide(); - 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 deleted file mode 100644 index 1e940227..00000000 --- a/src/ActiveCallBar.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -#include "WebRTCSession.h" - -class QHBoxLayout; -class QLabel; -class QTimer; -class Avatar; -class FlatButton; - -class ActiveCallBar : public QWidget -{ - Q_OBJECT - -public: - ActiveCallBar(QWidget *parent = nullptr); - -public slots: - void update(WebRTCSession::State); - void setCallParty(const QString &userid, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl); - -private: - QHBoxLayout *layout_ = nullptr; - Avatar *avatar_ = nullptr; - QLabel *callPartyLabel_ = nullptr; - QLabel *stateLabel_ = nullptr; - QLabel *durationLabel_ = nullptr; - FlatButton *muteBtn_ = nullptr; - 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 7a8d2ca7..b1d1a75a 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -52,7 +52,7 @@ CallManager::CallManager(QSharedPointer userSettings) emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_}); emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); QTimer::singleShot(timeoutms_, this, [this]() { - if (session_.state() == WebRTCSession::State::OFFERSENT) { + if (session_.state() == webrtc::State::OFFERSENT) { hangUp(CallHangUp::Reason::InviteTimeOut); emit ChatPage::instance()->showNotification( "The remote side failed to pick up."); @@ -99,13 +99,13 @@ CallManager::CallManager(QSharedPointer userSettings) turnServerTimer_.setInterval(ttl * 1000 * 0.9); }); - connect(&session_, &WebRTCSession::stateChanged, this, [this](WebRTCSession::State state) { + connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) { switch (state) { - case WebRTCSession::State::DISCONNECTED: + case webrtc::State::DISCONNECTED: playRingtone("qrc:/media/media/callend.ogg", false); clear(); break; - case WebRTCSession::State::ICEFAILED: { + case webrtc::State::ICEFAILED: { QString error("Call connection failed."); if (turnURIs_.empty()) error += " Your homeserver has no configured TURN server."; @@ -155,10 +155,9 @@ CallManager::sendInvite(const QString &roomid) 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, - QString::fromStdString(roomInfo.name), - QString::fromStdString(roomInfo.avatar_url)); + callPartyName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name; + callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); + emit newCallParty(); playRingtone("qrc:/media/media/ringback.ogg", true); if (!session_.createOffer()) { emit ChatPage::instance()->showNotification("Problem setting up call."); @@ -193,9 +192,9 @@ CallManager::hangUp(CallHangUp::Reason reason) } bool -CallManager::onActiveCall() +CallManager::onActiveCall() const { - return session_.state() != WebRTCSession::State::DISCONNECTED; + return session_.state() != webrtc::State::DISCONNECTED; } void @@ -259,11 +258,9 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent) 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)); - + callPartyName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name; + callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); + emit newCallParty(); auto dialog = new dialogs::AcceptCall(caller.user_id, caller.display_name, QString::fromStdString(roomInfo.name), @@ -376,6 +373,8 @@ void CallManager::clear() { roomid_.clear(); + callPartyName_.clear(); + callPartyAvatarUrl_.clear(); callid_.clear(); remoteICECandidates_.clear(); } diff --git a/src/CallManager.h b/src/CallManager.h index 3a406438..640230a4 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -29,7 +29,9 @@ public: void sendInvite(const QString &roomid); void hangUp( mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); - bool onActiveCall(); + bool onActiveCall() const; + QString callPartyName() const { return callPartyName_; } + QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } void refreshTurnServer(); public slots: @@ -40,11 +42,8 @@ signals: void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); + void newCallParty(); void turnServerRetrieved(const mtx::responses::TurnServer &); - void newCallParty(const QString &userid, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl); private slots: void retrieveTurnServer(); @@ -52,6 +51,8 @@ private slots: private: WebRTCSession &session_; QString roomid_; + QString callPartyName_; + QString callPartyAvatarUrl_; std::string callid_; const uint32_t timeoutms_ = 120000; std::vector remoteICECandidates_; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 6008846a..59ae7ef2 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -22,7 +22,6 @@ #include #include -#include "ActiveCallBar.h" #include "AvatarProvider.h" #include "Cache.h" #include "Cache_p.h" @@ -40,7 +39,6 @@ #include "UserInfoWidget.h" #include "UserSettingsPage.h" #include "Utils.h" -#include "WebRTCSession.h" #include "ui/OverlayModal.h" #include "ui/Theme.h" @@ -129,12 +127,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) contentLayout_->addWidget(view_manager_->getWidget()); - activeCallBar_ = new ActiveCallBar(this); - contentLayout_->addWidget(activeCallBar_); - activeCallBar_->hide(); - connect( - &callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty); - // Splitter splitter->addWidget(sideBar_); splitter->addWidget(content_); diff --git a/src/ChatPage.h b/src/ChatPage.h index a139b5fd..6b3916a2 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -41,7 +41,6 @@ #include "notifications/Manager.h" #include "popups/UserMentions.h" -class ActiveCallBar; class OverlayModal; class QuickSwitcher; class RoomList; @@ -235,7 +234,6 @@ private: SideBarActions *sidebarActions_; TextInputWidget *text_input_; - ActiveCallBar *activeCallBar_; QTimer connectivityTimer_; std::atomic_bool isConnected_; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 29abed86..330ba2a3 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -288,7 +288,7 @@ MainWindow::showChatPage() void MainWindow::closeEvent(QCloseEvent *event) { - if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) { + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") != QMessageBox::Yes) { event->ignore(); @@ -440,7 +440,7 @@ MainWindow::openLogoutDialog() { auto dialog = new dialogs::Logout(this); connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { - if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) { + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { if (QMessageBox::question( this, "nheko", "A call is in progress. Log out?") != QMessageBox::Yes) { diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 4a25c4cf..f0d23e21 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -560,7 +560,7 @@ TextInputWidget::TextInputWidget(QWidget *parent) #ifdef GSTREAMER_AVAILABLE callBtn_ = new FlatButton(this); - changeCallButtonState(WebRTCSession::State::DISCONNECTED); + changeCallButtonState(webrtc::State::DISCONNECTED); connect(&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, @@ -776,11 +776,10 @@ TextInputWidget::paintEvent(QPaintEvent *) } void -TextInputWidget::changeCallButtonState(WebRTCSession::State state) +TextInputWidget::changeCallButtonState(webrtc::State state) { QIcon icon; - if (state == WebRTCSession::State::ICEFAILED || - state == WebRTCSession::State::DISCONNECTED) { + if (state == webrtc::State::ICEFAILED || state == webrtc::State::DISCONNECTED) { callBtn_->setToolTip(tr("Place a call")); icon.addFile(":/icons/icons/ui/place-call.png"); } else { diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index 3aa05c39..f4ca980e 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -164,7 +164,7 @@ public slots: void openFileSelection(); void hideUploadSpinner(); void focusLineEdit() { input_->setFocus(); } - void changeCallButtonState(WebRTCSession::State); + void changeCallButtonState(webrtc::State); private slots: void addSelectedEmoji(const QString &emoji); diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index d8497833..9c8c032d 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -1,3 +1,4 @@ +#include #include #include "Logging.h" @@ -14,12 +15,17 @@ extern "C" } #endif -Q_DECLARE_METATYPE(WebRTCSession::State) +Q_DECLARE_METATYPE(webrtc::State) + +using webrtc::State; WebRTCSession::WebRTCSession() : QObject() { - qRegisterMetaType(); + qRegisterMetaType(); + qmlRegisterUncreatableMetaObject( + webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum"); + connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); init(); } @@ -246,12 +252,10 @@ iceGatheringStateChanged(GstElement *webrtc, nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete"); if (isoffering_) { emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged( - WebRTCSession::State::OFFERSENT); + emit WebRTCSession::instance().stateChanged(State::OFFERSENT); } else { emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged( - WebRTCSession::State::ANSWERSENT); + emit WebRTCSession::instance().stateChanged(State::ANSWERSENT); } } } @@ -264,10 +268,10 @@ onICEGatheringCompletion(gpointer timerid) *(guint *)(timerid) = 0; if (isoffering_) { emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT); + emit WebRTCSession::instance().stateChanged(State::OFFERSENT); } else { emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT); + emit WebRTCSession::instance().stateChanged(State::ANSWERSENT); } return FALSE; } @@ -285,7 +289,7 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate}); return; #else - if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) { + if (WebRTCSession::instance().state() >= State::OFFERSENT) { emit WebRTCSession::instance().newICECandidate( {"audio", (uint16_t)mlineIndex, candidate}); return; @@ -314,11 +318,11 @@ iceConnectionStateChanged(GstElement *webrtc, switch (newState) { case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING: nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking"); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING); + emit WebRTCSession::instance().stateChanged(State::CONNECTING); break; case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED: nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed"); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED); + emit WebRTCSession::instance().stateChanged(State::ICEFAILED); break; default: break; @@ -355,8 +359,7 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe 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); + emit WebRTCSession::instance().stateChanged(State::CONNECTED); } gst_object_unref(queuepad); } @@ -633,21 +636,30 @@ WebRTCSession::createPipeline(int opusPayloadType) } bool -WebRTCSession::toggleMuteAudioSrc(bool &isMuted) +WebRTCSession::isMicMuted() const { if (state_ < State::INITIATED) return false; GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel"); - if (!srclevel) + gboolean muted; + g_object_get(srclevel, "mute", &muted, nullptr); + gst_object_unref(srclevel); + return muted; +} + +bool +WebRTCSession::toggleMicMute() +{ + if (state_ < State::INITIATED) return false; + GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel"); gboolean muted; g_object_get(srclevel, "mute", &muted, nullptr); g_object_set(srclevel, "mute", !muted, nullptr); gst_object_unref(srclevel); - isMuted = !muted; - return true; + return !muted; } void @@ -778,7 +790,13 @@ WebRTCSession::createPipeline(int) } bool -WebRTCSession::toggleMuteAudioSrc(bool &) +WebRTCSession::isMicMuted() const +{ + return false; +} + +bool +WebRTCSession::toggleMicMute() { return false; } diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h index 653ec2cf..83cabf5c 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h @@ -9,23 +9,30 @@ typedef struct _GstElement GstElement; +namespace webrtc { +Q_NAMESPACE + +enum class State +{ + DISCONNECTED, + ICEFAILED, + INITIATING, + INITIATED, + OFFERSENT, + ANSWERSENT, + CONNECTING, + CONNECTED + +}; +Q_ENUM_NS(State) + +} + class WebRTCSession : public QObject { Q_OBJECT public: - enum class State - { - DISCONNECTED, - ICEFAILED, - INITIATING, - INITIATED, - OFFERSENT, - ANSWERSENT, - CONNECTING, - CONNECTED - }; - static WebRTCSession &instance() { static WebRTCSession instance; @@ -33,14 +40,15 @@ public: } bool init(std::string *errorMessage = nullptr); - State state() const { return state_; } + webrtc::State state() const { return state_; } bool createOffer(); bool acceptOffer(const std::string &sdp); bool acceptAnswer(const std::string &sdp); void acceptICECandidates(const std::vector &); - bool toggleMuteAudioSrc(bool &isMuted); + bool isMicMuted() const; + bool toggleMicMute(); void end(); void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; } @@ -55,16 +63,16 @@ signals: void answerCreated(const std::string &sdp, const std::vector &); void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &); - void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt + void stateChanged(webrtc::State); private slots: - void setState(State state) { state_ = state; } + void setState(webrtc::State state) { state_ = state; } private: WebRTCSession(); bool initialised_ = false; - State state_ = State::DISCONNECTED; + webrtc::State state_ = webrtc::State::DISCONNECTED; GstElement *pipe_ = nullptr; GstElement *webrtc_ = nullptr; unsigned int busWatchId_ = 0; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index abb807b3..2b453e56 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -7,7 +7,6 @@ #include #include "BlurhashProvider.h" -#include "CallManager.h" #include "ChatPage.h" #include "ColorImageProvider.h" #include "DelegateChooser.h" @@ -141,6 +140,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin isInitialSync_ = true; emit initialSyncChanged(true); }); + connect(&WebRTCSession::instance(), + &WebRTCSession::stateChanged, + this, + &TimelineViewManager::callStateChanged); + connect( + callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged); } void @@ -212,6 +217,13 @@ TimelineViewManager::escapeEmoji(QString str) const return utils::replaceEmoji(str); } +void +TimelineViewManager::toggleMicMute() +{ + WebRTCSession::instance().toggleMicMute(); + emit micMuteChanged(); +} + void TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 1a98f64d..9ff9adac 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -10,15 +10,16 @@ #include #include "Cache.h" +#include "CallManager.h" #include "Logging.h" #include "TimelineModel.h" #include "Utils.h" +#include "WebRTCSession.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" class MxcImageProvider; class BlurhashProvider; -class CallManager; class ColorImageProvider; class UserSettings; class ChatPage; @@ -33,6 +34,10 @@ class TimelineViewManager : public QObject bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) Q_PROPERTY( bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged) + Q_PROPERTY(webrtc::State callState READ callState NOTIFY callStateChanged) + Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged) + Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged) + Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) public: TimelineViewManager(QSharedPointer userSettings, @@ -48,6 +53,11 @@ public: Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; } Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isNarrowView() const { return isNarrowView_; } + webrtc::State callState() const { return WebRTCSession::instance().state(); } + QString callPartyName() const { return callManager_->callPartyName(); } + QString callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); } + bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); } + Q_INVOKABLE void toggleMicMute(); Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; @@ -72,6 +82,9 @@ signals: void inviteUsers(QStringList users); void showRoomList(); void narrowViewChanged(); + void callStateChanged(webrtc::State); + void callPartyChanged(); + void micMuteChanged(); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids);