Merge pull request #289 from trilene/master

Port ActiveCallBar to Qml
This commit is contained in:
DeepBlueV7.X 2020-09-25 19:29:21 +02:00 committed by GitHub
commit 5cce5b9999
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 235 additions and 278 deletions

View file

@ -279,7 +279,6 @@ set(SRC_FILES
src/ui/Theme.cpp src/ui/Theme.cpp
src/ui/ThemeManager.cpp src/ui/ThemeManager.cpp
src/ActiveCallBar.cpp
src/AvatarProvider.cpp src/AvatarProvider.cpp
src/BlurhashProvider.cpp src/BlurhashProvider.cpp
src/Cache.cpp src/Cache.cpp
@ -491,7 +490,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/notifications/Manager.h src/notifications/Manager.h
src/ActiveCallBar.h
src/AvatarProvider.h src/AvatarProvider.h
src/BlurhashProvider.h src/BlurhashProvider.h
src/Cache_p.h src/Cache_p.h

View file

@ -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
}
}
}

View file

@ -14,7 +14,7 @@ Rectangle {
Label { Label {
anchors.fill: parent anchors.fill: parent
text: timelineManager.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0))) text: timelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
textFormat: Text.RichText textFormat: Text.RichText
font.pixelSize: avatar.height/2 font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View file

@ -3,6 +3,8 @@ import QtQuick.Controls 2.3
AbstractButton { AbstractButton {
property string image: undefined property string image: undefined
property color highlightColor: colors.highlight
property color buttonTextColor: colors.buttonText
width: 16 width: 16
height: 16 height: 16
id: button id: button
@ -11,7 +13,7 @@ AbstractButton {
id: buttonImg id: buttonImg
// Workaround, can't get icon.source working for now... // Workaround, can't get icon.source working for now...
anchors.fill: parent anchors.fill: parent
source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText) source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
} }
MouseArea MouseArea

View file

@ -497,6 +497,11 @@ Page {
} }
} }
} }
ActiveCallBar {
Layout.fillWidth: true
z: 3
}
} }
} }
} }

View file

@ -121,6 +121,7 @@
<file>qtquickcontrols2.conf</file> <file>qtquickcontrols2.conf</file>
<file>qml/TimelineView.qml</file> <file>qml/TimelineView.qml</file>
<file>qml/ActiveCallBar.qml</file>
<file>qml/Avatar.qml</file> <file>qml/Avatar.qml</file>
<file>qml/ImageButton.qml</file> <file>qml/ImageButton.qml</file>
<file>qml/MatrixText.qml</file> <file>qml/MatrixText.qml</file>

View file

@ -1,160 +0,0 @@
#include <cstdio>
#include <QDateTime>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QString>
#include <QTimer>
#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;
}
}

View file

@ -1,40 +0,0 @@
#pragma once
#include <QWidget>
#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);
};

View file

@ -52,7 +52,7 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_}); emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
QTimer::singleShot(timeoutms_, this, [this]() { QTimer::singleShot(timeoutms_, this, [this]() {
if (session_.state() == WebRTCSession::State::OFFERSENT) { if (session_.state() == webrtc::State::OFFERSENT) {
hangUp(CallHangUp::Reason::InviteTimeOut); hangUp(CallHangUp::Reason::InviteTimeOut);
emit ChatPage::instance()->showNotification( emit ChatPage::instance()->showNotification(
"The remote side failed to pick up."); "The remote side failed to pick up.");
@ -99,13 +99,13 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
turnServerTimer_.setInterval(ttl * 1000 * 0.9); 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) { switch (state) {
case WebRTCSession::State::DISCONNECTED: case webrtc::State::DISCONNECTED:
playRingtone("qrc:/media/media/callend.ogg", false); playRingtone("qrc:/media/media/callend.ogg", false);
clear(); clear();
break; break;
case WebRTCSession::State::ICEFAILED: { case webrtc::State::ICEFAILED: {
QString error("Call connection failed."); QString error("Call connection failed.");
if (turnURIs_.empty()) if (turnURIs_.empty())
error += " Your homeserver has no configured TURN server."; error += " Your homeserver has no configured TURN server.";
@ -155,10 +155,9 @@ CallManager::sendInvite(const QString &roomid)
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString())); std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee = const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() : members.front(); members.front().user_id == utils::localUser() ? members.back() : members.front();
emit newCallParty(callee.user_id, callPartyName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
callee.display_name, callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
QString::fromStdString(roomInfo.name), emit newCallParty();
QString::fromStdString(roomInfo.avatar_url));
playRingtone("qrc:/media/media/ringback.ogg", true); playRingtone("qrc:/media/media/ringback.ogg", true);
if (!session_.createOffer()) { if (!session_.createOffer()) {
emit ChatPage::instance()->showNotification("Problem setting up call."); emit ChatPage::instance()->showNotification("Problem setting up call.");
@ -193,9 +192,9 @@ CallManager::hangUp(CallHangUp::Reason reason)
} }
bool bool
CallManager::onActiveCall() CallManager::onActiveCall() const
{ {
return session_.state() != WebRTCSession::State::DISCONNECTED; return session_.state() != webrtc::State::DISCONNECTED;
} }
void void
@ -259,11 +258,9 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id)); std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller = const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front(); members.front().user_id == utils::localUser() ? members.back() : members.front();
emit newCallParty(caller.user_id, callPartyName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
caller.display_name, callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
QString::fromStdString(roomInfo.name), emit newCallParty();
QString::fromStdString(roomInfo.avatar_url));
auto dialog = new dialogs::AcceptCall(caller.user_id, auto dialog = new dialogs::AcceptCall(caller.user_id,
caller.display_name, caller.display_name,
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.name),
@ -376,6 +373,8 @@ void
CallManager::clear() CallManager::clear()
{ {
roomid_.clear(); roomid_.clear();
callPartyName_.clear();
callPartyAvatarUrl_.clear();
callid_.clear(); callid_.clear();
remoteICECandidates_.clear(); remoteICECandidates_.clear();
} }

View file

@ -29,7 +29,9 @@ public:
void sendInvite(const QString &roomid); void sendInvite(const QString &roomid);
void hangUp( void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); 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(); void refreshTurnServer();
public slots: 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::CallCandidates &);
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
void newCallParty();
void turnServerRetrieved(const mtx::responses::TurnServer &); void turnServerRetrieved(const mtx::responses::TurnServer &);
void newCallParty(const QString &userid,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl);
private slots: private slots:
void retrieveTurnServer(); void retrieveTurnServer();
@ -52,6 +51,8 @@ private slots:
private: private:
WebRTCSession &session_; WebRTCSession &session_;
QString roomid_; QString roomid_;
QString callPartyName_;
QString callPartyAvatarUrl_;
std::string callid_; std::string callid_;
const uint32_t timeoutms_ = 120000; const uint32_t timeoutms_ = 120000;
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_; std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;

View file

@ -22,7 +22,6 @@
#include <QShortcut> #include <QShortcut>
#include <QtConcurrent> #include <QtConcurrent>
#include "ActiveCallBar.h"
#include "AvatarProvider.h" #include "AvatarProvider.h"
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h" #include "Cache_p.h"
@ -40,7 +39,6 @@
#include "UserInfoWidget.h" #include "UserInfoWidget.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "Utils.h" #include "Utils.h"
#include "WebRTCSession.h"
#include "ui/OverlayModal.h" #include "ui/OverlayModal.h"
#include "ui/Theme.h" #include "ui/Theme.h"
@ -129,12 +127,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
contentLayout_->addWidget(view_manager_->getWidget()); contentLayout_->addWidget(view_manager_->getWidget());
activeCallBar_ = new ActiveCallBar(this);
contentLayout_->addWidget(activeCallBar_);
activeCallBar_->hide();
connect(
&callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
// Splitter // Splitter
splitter->addWidget(sideBar_); splitter->addWidget(sideBar_);
splitter->addWidget(content_); splitter->addWidget(content_);

View file

@ -41,7 +41,6 @@
#include "notifications/Manager.h" #include "notifications/Manager.h"
#include "popups/UserMentions.h" #include "popups/UserMentions.h"
class ActiveCallBar;
class OverlayModal; class OverlayModal;
class QuickSwitcher; class QuickSwitcher;
class RoomList; class RoomList;
@ -235,7 +234,6 @@ private:
SideBarActions *sidebarActions_; SideBarActions *sidebarActions_;
TextInputWidget *text_input_; TextInputWidget *text_input_;
ActiveCallBar *activeCallBar_;
QTimer connectivityTimer_; QTimer connectivityTimer_;
std::atomic_bool isConnected_; std::atomic_bool isConnected_;

View file

@ -288,7 +288,7 @@ MainWindow::showChatPage()
void void
MainWindow::closeEvent(QCloseEvent *event) 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?") != if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
QMessageBox::Yes) { QMessageBox::Yes) {
event->ignore(); event->ignore();
@ -440,7 +440,7 @@ MainWindow::openLogoutDialog()
{ {
auto dialog = new dialogs::Logout(this); auto dialog = new dialogs::Logout(this);
connect(dialog, &dialogs::Logout::loggingOut, this, [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( if (QMessageBox::question(
this, "nheko", "A call is in progress. Log out?") != this, "nheko", "A call is in progress. Log out?") !=
QMessageBox::Yes) { QMessageBox::Yes) {

View file

@ -560,7 +560,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
callBtn_ = new FlatButton(this); callBtn_ = new FlatButton(this);
changeCallButtonState(WebRTCSession::State::DISCONNECTED); changeCallButtonState(webrtc::State::DISCONNECTED);
connect(&WebRTCSession::instance(), connect(&WebRTCSession::instance(),
&WebRTCSession::stateChanged, &WebRTCSession::stateChanged,
this, this,
@ -776,11 +776,10 @@ TextInputWidget::paintEvent(QPaintEvent *)
} }
void void
TextInputWidget::changeCallButtonState(WebRTCSession::State state) TextInputWidget::changeCallButtonState(webrtc::State state)
{ {
QIcon icon; QIcon icon;
if (state == WebRTCSession::State::ICEFAILED || if (state == webrtc::State::ICEFAILED || state == webrtc::State::DISCONNECTED) {
state == WebRTCSession::State::DISCONNECTED) {
callBtn_->setToolTip(tr("Place a call")); callBtn_->setToolTip(tr("Place a call"));
icon.addFile(":/icons/icons/ui/place-call.png"); icon.addFile(":/icons/icons/ui/place-call.png");
} else { } else {

View file

@ -164,7 +164,7 @@ public slots:
void openFileSelection(); void openFileSelection();
void hideUploadSpinner(); void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); } void focusLineEdit() { input_->setFocus(); }
void changeCallButtonState(WebRTCSession::State); void changeCallButtonState(webrtc::State);
private slots: private slots:
void addSelectedEmoji(const QString &emoji); void addSelectedEmoji(const QString &emoji);

View file

@ -1,3 +1,4 @@
#include <QQmlEngine>
#include <cctype> #include <cctype>
#include "Logging.h" #include "Logging.h"
@ -14,12 +15,17 @@ extern "C"
} }
#endif #endif
Q_DECLARE_METATYPE(WebRTCSession::State) Q_DECLARE_METATYPE(webrtc::State)
using webrtc::State;
WebRTCSession::WebRTCSession() WebRTCSession::WebRTCSession()
: QObject() : QObject()
{ {
qRegisterMetaType<WebRTCSession::State>(); qRegisterMetaType<webrtc::State>();
qmlRegisterUncreatableMetaObject(
webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum");
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
init(); init();
} }
@ -246,12 +252,10 @@ iceGatheringStateChanged(GstElement *webrtc,
nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete"); nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
if (isoffering_) { if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged( emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
WebRTCSession::State::OFFERSENT);
} else { } else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged( emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
WebRTCSession::State::ANSWERSENT);
} }
} }
} }
@ -264,10 +268,10 @@ onICEGatheringCompletion(gpointer timerid)
*(guint *)(timerid) = 0; *(guint *)(timerid) = 0;
if (isoffering_) { if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT); emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
} else { } else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT); emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
} }
return FALSE; return FALSE;
} }
@ -285,7 +289,7 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate}); localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate});
return; return;
#else #else
if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) { if (WebRTCSession::instance().state() >= State::OFFERSENT) {
emit WebRTCSession::instance().newICECandidate( emit WebRTCSession::instance().newICECandidate(
{"audio", (uint16_t)mlineIndex, candidate}); {"audio", (uint16_t)mlineIndex, candidate});
return; return;
@ -314,11 +318,11 @@ iceConnectionStateChanged(GstElement *webrtc,
switch (newState) { switch (newState) {
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING: case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking"); nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING); emit WebRTCSession::instance().stateChanged(State::CONNECTING);
break; break;
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED: case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed"); nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED); emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
break; break;
default: default:
break; 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))) if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: unable to link new pad"); nhlog::ui()->error("WebRTC: unable to link new pad");
else { else {
emit WebRTCSession::instance().stateChanged( emit WebRTCSession::instance().stateChanged(State::CONNECTED);
WebRTCSession::State::CONNECTED);
} }
gst_object_unref(queuepad); gst_object_unref(queuepad);
} }
@ -633,21 +636,30 @@ WebRTCSession::createPipeline(int opusPayloadType)
} }
bool bool
WebRTCSession::toggleMuteAudioSrc(bool &isMuted) WebRTCSession::isMicMuted() const
{ {
if (state_ < State::INITIATED) if (state_ < State::INITIATED)
return false; return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel"); GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
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; return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
gboolean muted; gboolean muted;
g_object_get(srclevel, "mute", &muted, nullptr); g_object_get(srclevel, "mute", &muted, nullptr);
g_object_set(srclevel, "mute", !muted, nullptr); g_object_set(srclevel, "mute", !muted, nullptr);
gst_object_unref(srclevel); gst_object_unref(srclevel);
isMuted = !muted; return !muted;
return true;
} }
void void
@ -778,7 +790,13 @@ WebRTCSession::createPipeline(int)
} }
bool bool
WebRTCSession::toggleMuteAudioSrc(bool &) WebRTCSession::isMicMuted() const
{
return false;
}
bool
WebRTCSession::toggleMicMute()
{ {
return false; return false;
} }

View file

@ -9,11 +9,9 @@
typedef struct _GstElement GstElement; typedef struct _GstElement GstElement;
class WebRTCSession : public QObject namespace webrtc {
{ Q_NAMESPACE
Q_OBJECT
public:
enum class State enum class State
{ {
DISCONNECTED, DISCONNECTED,
@ -24,8 +22,17 @@ public:
ANSWERSENT, ANSWERSENT,
CONNECTING, CONNECTING,
CONNECTED CONNECTED
};
};
Q_ENUM_NS(State)
}
class WebRTCSession : public QObject
{
Q_OBJECT
public:
static WebRTCSession &instance() static WebRTCSession &instance()
{ {
static WebRTCSession instance; static WebRTCSession instance;
@ -33,14 +40,15 @@ public:
} }
bool init(std::string *errorMessage = nullptr); bool init(std::string *errorMessage = nullptr);
State state() const { return state_; } webrtc::State state() const { return state_; }
bool createOffer(); bool createOffer();
bool acceptOffer(const std::string &sdp); bool acceptOffer(const std::string &sdp);
bool acceptAnswer(const std::string &sdp); bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &); void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
bool toggleMuteAudioSrc(bool &isMuted); bool isMicMuted() const;
bool toggleMicMute();
void end(); void end();
void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; } void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; }
@ -55,16 +63,16 @@ signals:
void answerCreated(const std::string &sdp, void answerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &); const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &); void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt void stateChanged(webrtc::State);
private slots: private slots:
void setState(State state) { state_ = state; } void setState(webrtc::State state) { state_ = state; }
private: private:
WebRTCSession(); WebRTCSession();
bool initialised_ = false; bool initialised_ = false;
State state_ = State::DISCONNECTED; webrtc::State state_ = webrtc::State::DISCONNECTED;
GstElement *pipe_ = nullptr; GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr; GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0; unsigned int busWatchId_ = 0;

View file

@ -7,7 +7,6 @@
#include <QString> #include <QString>
#include "BlurhashProvider.h" #include "BlurhashProvider.h"
#include "CallManager.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "ColorImageProvider.h" #include "ColorImageProvider.h"
#include "DelegateChooser.h" #include "DelegateChooser.h"
@ -141,6 +140,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
isInitialSync_ = true; isInitialSync_ = true;
emit initialSyncChanged(true); emit initialSyncChanged(true);
}); });
connect(&WebRTCSession::instance(),
&WebRTCSession::stateChanged,
this,
&TimelineViewManager::callStateChanged);
connect(
callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged);
} }
void void
@ -212,6 +217,13 @@ TimelineViewManager::escapeEmoji(QString str) const
return utils::replaceEmoji(str); return utils::replaceEmoji(str);
} }
void
TimelineViewManager::toggleMicMute()
{
WebRTCSession::instance().toggleMicMute();
emit micMuteChanged();
}
void void
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
{ {

View file

@ -10,15 +10,16 @@
#include <mtx/responses.hpp> #include <mtx/responses.hpp>
#include "Cache.h" #include "Cache.h"
#include "CallManager.h"
#include "Logging.h" #include "Logging.h"
#include "TimelineModel.h" #include "TimelineModel.h"
#include "Utils.h" #include "Utils.h"
#include "WebRTCSession.h"
#include "emoji/EmojiModel.h" #include "emoji/EmojiModel.h"
#include "emoji/Provider.h" #include "emoji/Provider.h"
class MxcImageProvider; class MxcImageProvider;
class BlurhashProvider; class BlurhashProvider;
class CallManager;
class ColorImageProvider; class ColorImageProvider;
class UserSettings; class UserSettings;
class ChatPage; class ChatPage;
@ -33,6 +34,10 @@ class TimelineViewManager : public QObject
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
Q_PROPERTY( Q_PROPERTY(
bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged) 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: public:
TimelineViewManager(QSharedPointer<UserSettings> userSettings, TimelineViewManager(QSharedPointer<UserSettings> userSettings,
@ -48,6 +53,11 @@ public:
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; } Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isNarrowView() const { return isNarrowView_; } 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 void openImageOverlay(QString mxcUrl, QString eventId) const;
Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QColor userColor(QString id, QColor background);
Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString escapeEmoji(QString str) const;
@ -72,6 +82,9 @@ signals:
void inviteUsers(QStringList users); void inviteUsers(QStringList users);
void showRoomList(); void showRoomList();
void narrowViewChanged(); void narrowViewChanged();
void callStateChanged(webrtc::State);
void callPartyChanged();
void micMuteChanged();
public slots: public slots:
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);