Make call invites less intrusive
This commit is contained in:
parent
ac410f46f2
commit
7124024977
@ -245,7 +245,6 @@ configure_file(cmake/nheko.h config/nheko.h)
|
|||||||
#
|
#
|
||||||
set(SRC_FILES
|
set(SRC_FILES
|
||||||
# Dialogs
|
# Dialogs
|
||||||
src/dialogs/AcceptCall.cpp
|
|
||||||
src/dialogs/CreateRoom.cpp
|
src/dialogs/CreateRoom.cpp
|
||||||
src/dialogs/FallbackAuth.cpp
|
src/dialogs/FallbackAuth.cpp
|
||||||
src/dialogs/ImageOverlay.cpp
|
src/dialogs/ImageOverlay.cpp
|
||||||
@ -464,7 +463,6 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG
|
|||||||
|
|
||||||
qt5_wrap_cpp(MOC_HEADERS
|
qt5_wrap_cpp(MOC_HEADERS
|
||||||
# Dialogs
|
# Dialogs
|
||||||
src/dialogs/AcceptCall.h
|
|
||||||
src/dialogs/CreateRoom.h
|
src/dialogs/CreateRoom.h
|
||||||
src/dialogs/FallbackAuth.h
|
src/dialogs/FallbackAuth.h
|
||||||
src/dialogs/ImageOverlay.h
|
src/dialogs/ImageOverlay.h
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "./delegates"
|
import "./delegates"
|
||||||
import "./device-verification"
|
import "./device-verification"
|
||||||
import "./emoji"
|
import "./emoji"
|
||||||
|
import "./voip"
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
@ -210,7 +211,7 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
source: CallManager.isOnVideoCall ? "VideoCall.qml" : ""
|
source: CallManager.isOnCall && CallManager.isVideo ? "voip/VideoCall.qml" : ""
|
||||||
onLoaded: TimelineManager.setVideoCallItem()
|
onLoaded: TimelineManager.setVideoCallItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +224,12 @@ Page {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CallInviteBar {
|
||||||
|
id: callInviteBar
|
||||||
|
Layout.fillWidth: true
|
||||||
|
z: 3
|
||||||
|
}
|
||||||
|
|
||||||
ActiveCallBar {
|
ActiveCallBar {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
z: 3
|
z: 3
|
||||||
|
@ -2,18 +2,18 @@ import QtQuick 2.9
|
|||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Layouts 1.2
|
import QtQuick.Layouts 1.2
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
import "../"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: activeCallBar
|
|
||||||
|
|
||||||
visible: CallManager.isOnCall
|
visible: CallManager.isOnCall
|
||||||
color: "#2ECC71"
|
color: callInviteBar.color
|
||||||
implicitHeight: visible ? rowLayout.height + 8 : 0
|
implicitHeight: visible ? rowLayout.height + 8 : 0
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (CallManager.isOnVideoCall)
|
if (CallManager.isVideo)
|
||||||
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
|
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -31,18 +31,18 @@ Rectangle {
|
|||||||
width: avatarSize
|
width: avatarSize
|
||||||
height: avatarSize
|
height: avatarSize
|
||||||
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
displayName: CallManager.callPartyName
|
displayName: CallManager.callParty
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||||
text: " " + CallManager.callPartyName + " "
|
text: " " + CallManager.callParty + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
Layout.preferredWidth: 24
|
Layout.preferredWidth: 24
|
||||||
Layout.preferredHeight: 24
|
Layout.preferredHeight: 24
|
||||||
source: CallManager.isOnVideoCall ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@ -99,7 +99,7 @@ Rectangle {
|
|||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stackLayout
|
target: stackLayout
|
||||||
currentIndex: CallManager.isOnVideoCall ? 1 : 0
|
currentIndex: CallManager.isVideo ? 1 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -148,7 +148,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
visible: CallManager.isOnVideoCall
|
visible: CallManager.isVideo
|
||||||
width: 24
|
width: 24
|
||||||
height: 24
|
height: 24
|
||||||
buttonTextColor: "#000000"
|
buttonTextColor: "#000000"
|
95
resources/qml/voip/CallInviteBar.qml
Normal file
95
resources/qml/voip/CallInviteBar.qml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Dialogs 1.3
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import im.nheko 1.0
|
||||||
|
import "../"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
|
||||||
|
visible: CallManager.haveCallInvite
|
||||||
|
color: "#2ECC71"
|
||||||
|
implicitHeight: visible ? rowLayout.height + 8 : 0
|
||||||
|
|
||||||
|
MessageDialog {
|
||||||
|
id: warningDialog
|
||||||
|
icon: StandardIcon.Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
width: avatarSize
|
||||||
|
height: avatarSize
|
||||||
|
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
|
displayName: CallManager.callParty
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||||
|
text: " " + CallManager.callParty + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.preferredWidth: 24
|
||||||
|
Layout.preferredHeight: 24
|
||||||
|
source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||||
|
text: CallManager.isVideo ? "Video Call" : "Voice Call"
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||||
|
palette: colors
|
||||||
|
text: qsTr("Accept")
|
||||||
|
onClicked: {
|
||||||
|
if (CallManager.mics.length == 0) {
|
||||||
|
warningDialog.text = "No microphone found.";
|
||||||
|
warningDialog.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!CallManager.mics.includes(Settings.microphone)) {
|
||||||
|
warningDialog.text = "Unknown microphone: " + Settings.microphone;
|
||||||
|
warningDialog.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
|
||||||
|
warningDialog.text = "Unknown camera: " + Settings.camera;
|
||||||
|
warningDialog.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CallManager.acceptInvite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
implicitWidth: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
icon.source: "qrc:/icons/icons/ui/end-call.png"
|
||||||
|
palette: colors
|
||||||
|
text: qsTr("Decline")
|
||||||
|
onClicked: {
|
||||||
|
CallManager.hangUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
implicitWidth: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -123,7 +123,6 @@
|
|||||||
<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/Completer.qml</file>
|
<file>qml/Completer.qml</file>
|
||||||
<file>qml/EncryptionIndicator.qml</file>
|
<file>qml/EncryptionIndicator.qml</file>
|
||||||
@ -139,7 +138,6 @@
|
|||||||
<file>qml/TimelineRow.qml</file>
|
<file>qml/TimelineRow.qml</file>
|
||||||
<file>qml/TopBar.qml</file>
|
<file>qml/TopBar.qml</file>
|
||||||
<file>qml/TypingIndicator.qml</file>
|
<file>qml/TypingIndicator.qml</file>
|
||||||
<file>qml/VideoCall.qml</file>
|
|
||||||
<file>qml/emoji/EmojiButton.qml</file>
|
<file>qml/emoji/EmojiButton.qml</file>
|
||||||
<file>qml/emoji/EmojiPicker.qml</file>
|
<file>qml/emoji/EmojiPicker.qml</file>
|
||||||
<file>qml/UserProfile.qml</file>
|
<file>qml/UserProfile.qml</file>
|
||||||
@ -159,6 +157,9 @@
|
|||||||
<file>qml/device-verification/NewVerificationRequest.qml</file>
|
<file>qml/device-verification/NewVerificationRequest.qml</file>
|
||||||
<file>qml/device-verification/Failed.qml</file>
|
<file>qml/device-verification/Failed.qml</file>
|
||||||
<file>qml/device-verification/Success.qml</file>
|
<file>qml/device-verification/Success.qml</file>
|
||||||
|
<file>qml/voip/ActiveCallBar.qml</file>
|
||||||
|
<file>qml/voip/CallInviteBar.qml</file>
|
||||||
|
<file>qml/voip/VideoCall.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/media">
|
<qresource prefix="/media">
|
||||||
<file>media/ring.ogg</file>
|
<file>media/ring.ogg</file>
|
||||||
|
@ -10,10 +10,9 @@
|
|||||||
#include "CallManager.h"
|
#include "CallManager.h"
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "MainWindow.h"
|
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
|
#include "UserSettingsPage.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "dialogs/AcceptCall.h"
|
|
||||||
|
|
||||||
#include "mtx/responses/turn_server.hpp"
|
#include "mtx/responses/turn_server.hpp"
|
||||||
|
|
||||||
@ -114,6 +113,22 @@ CallManager::CallManager(QObject *parent)
|
|||||||
emit newCallState();
|
emit newCallState();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(&session_, &WebRTCSession::devicesChanged, this, [this]() {
|
||||||
|
if (ChatPage::instance()->userSettings()->microphone().isEmpty()) {
|
||||||
|
auto mics = session_.getDeviceNames(false, std::string());
|
||||||
|
if (!mics.empty())
|
||||||
|
ChatPage::instance()->userSettings()->setMicrophone(
|
||||||
|
QString::fromStdString(mics.front()));
|
||||||
|
}
|
||||||
|
if (ChatPage::instance()->userSettings()->camera().isEmpty()) {
|
||||||
|
auto cameras = session_.getDeviceNames(true, std::string());
|
||||||
|
if (!cameras.empty())
|
||||||
|
ChatPage::instance()->userSettings()->setCamera(
|
||||||
|
QString::fromStdString(cameras.front()));
|
||||||
|
}
|
||||||
|
emit devicesChanged();
|
||||||
|
});
|
||||||
|
|
||||||
connect(&player_,
|
connect(&player_,
|
||||||
&QMediaPlayer::mediaStatusChanged,
|
&QMediaPlayer::mediaStatusChanged,
|
||||||
this,
|
this,
|
||||||
@ -160,7 +175,8 @@ CallManager::sendInvite(const QString &roomid, bool isVideo)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
roomid_ = roomid;
|
isVideo_ = isVideo;
|
||||||
|
roomid_ = roomid;
|
||||||
session_.setTurnServers(turnURIs_);
|
session_.setTurnServers(turnURIs_);
|
||||||
generateCallID();
|
generateCallID();
|
||||||
nhlog::ui()->debug(
|
nhlog::ui()->debug(
|
||||||
@ -168,16 +184,14 @@ CallManager::sendInvite(const QString &roomid, bool isVideo)
|
|||||||
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();
|
||||||
callPartyName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
|
callParty_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
|
||||||
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
|
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
|
||||||
emit newCallParty();
|
emit newInviteState();
|
||||||
playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
|
playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
|
||||||
if (!session_.createOffer(isVideo)) {
|
if (!session_.createOffer(isVideo)) {
|
||||||
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
||||||
endCall();
|
endCall();
|
||||||
}
|
}
|
||||||
if (isVideo)
|
|
||||||
emit newVideoCallState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -271,48 +285,41 @@ 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();
|
||||||
callPartyName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
|
callParty_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
|
||||||
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
|
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
|
||||||
emit newCallParty();
|
|
||||||
auto dialog = new dialogs::AcceptCall(caller.user_id,
|
haveCallInvite_ = true;
|
||||||
caller.display_name,
|
isVideo_ = isVideo;
|
||||||
QString::fromStdString(roomInfo.name),
|
inviteSDP_ = callInviteEvent.content.sdp;
|
||||||
QString::fromStdString(roomInfo.avatar_url),
|
session_.refreshDevices();
|
||||||
isVideo,
|
emit newInviteState();
|
||||||
MainWindow::instance());
|
|
||||||
connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent, isVideo]() {
|
|
||||||
MainWindow::instance()->hideOverlay();
|
|
||||||
answerInvite(callInviteEvent.content, isVideo);
|
|
||||||
});
|
|
||||||
connect(dialog, &dialogs::AcceptCall::reject, this, [this]() {
|
|
||||||
MainWindow::instance()->hideOverlay();
|
|
||||||
hangUp();
|
|
||||||
});
|
|
||||||
MainWindow::instance()->showSolidOverlayModal(dialog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CallManager::answerInvite(const CallInvite &invite, bool isVideo)
|
CallManager::acceptInvite()
|
||||||
{
|
{
|
||||||
|
if (!haveCallInvite_)
|
||||||
|
return;
|
||||||
|
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
if (!session_.havePlugins(false, &errorMessage) ||
|
if (!session_.havePlugins(false, &errorMessage) ||
|
||||||
(isVideo && !session_.havePlugins(true, &errorMessage))) {
|
(isVideo_ && !session_.havePlugins(true, &errorMessage))) {
|
||||||
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
||||||
hangUp();
|
hangUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session_.setTurnServers(turnURIs_);
|
session_.setTurnServers(turnURIs_);
|
||||||
if (!session_.acceptOffer(invite.sdp)) {
|
if (!session_.acceptOffer(inviteSDP_)) {
|
||||||
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
||||||
hangUp();
|
hangUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
session_.acceptICECandidates(remoteICECandidates_);
|
session_.acceptICECandidates(remoteICECandidates_);
|
||||||
remoteICECandidates_.clear();
|
remoteICECandidates_.clear();
|
||||||
if (isVideo)
|
haveCallInvite_ = false;
|
||||||
emit newVideoCallState();
|
emit newInviteState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -348,7 +355,8 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
|
|||||||
callid_ == callAnswerEvent.content.call_id) {
|
callid_ == callAnswerEvent.content.call_id) {
|
||||||
emit ChatPage::instance()->showNotification("Call answered on another device.");
|
emit ChatPage::instance()->showNotification("Call answered on another device.");
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
MainWindow::instance()->hideOverlay();
|
haveCallInvite_ = false;
|
||||||
|
emit newInviteState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,10 +377,8 @@ CallManager::handleEvent(const RoomEvent<CallHangUp> &callHangUpEvent)
|
|||||||
callHangUpReasonString(callHangUpEvent.content.reason),
|
callHangUpReasonString(callHangUpEvent.content.reason),
|
||||||
callHangUpEvent.sender);
|
callHangUpEvent.sender);
|
||||||
|
|
||||||
if (callid_ == callHangUpEvent.content.call_id) {
|
if (callid_ == callHangUpEvent.content.call_id)
|
||||||
MainWindow::instance()->hideOverlay();
|
|
||||||
endCall();
|
endCall();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -392,6 +398,23 @@ CallManager::callsSupported() const
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList
|
||||||
|
CallManager::devices(bool isVideo) const
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
const QString &defaultDevice = isVideo ? ChatPage::instance()->userSettings()->camera()
|
||||||
|
: ChatPage::instance()->userSettings()->microphone();
|
||||||
|
std::vector<std::string> devices =
|
||||||
|
session_.getDeviceNames(isVideo, defaultDevice.toStdString());
|
||||||
|
ret.reserve(devices.size());
|
||||||
|
std::transform(devices.cbegin(),
|
||||||
|
devices.cend(),
|
||||||
|
std::back_inserter(ret),
|
||||||
|
[](const auto &d) { return QString::fromStdString(d); });
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CallManager::generateCallID()
|
CallManager::generateCallID()
|
||||||
{
|
{
|
||||||
@ -404,9 +427,13 @@ void
|
|||||||
CallManager::clear()
|
CallManager::clear()
|
||||||
{
|
{
|
||||||
roomid_.clear();
|
roomid_.clear();
|
||||||
callPartyName_.clear();
|
callParty_.clear();
|
||||||
callPartyAvatarUrl_.clear();
|
callPartyAvatarUrl_.clear();
|
||||||
callid_.clear();
|
callid_.clear();
|
||||||
|
isVideo_ = false;
|
||||||
|
haveCallInvite_ = false;
|
||||||
|
emit newInviteState();
|
||||||
|
inviteSDP_.clear();
|
||||||
remoteICECandidates_.clear();
|
remoteICECandidates_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,11 +441,8 @@ void
|
|||||||
CallManager::endCall()
|
CallManager::endCall()
|
||||||
{
|
{
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
clear();
|
|
||||||
bool isVideo = session_.isVideo();
|
|
||||||
session_.end();
|
session_.end();
|
||||||
if (isVideo)
|
clear();
|
||||||
emit newVideoCallState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -16,48 +16,56 @@ namespace mtx::responses {
|
|||||||
struct TurnServer;
|
struct TurnServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QStringList;
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
|
||||||
class CallManager : public QObject
|
class CallManager : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
|
||||||
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
|
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
|
||||||
Q_PROPERTY(bool isOnVideoCall READ isOnVideoCall NOTIFY newVideoCallState)
|
Q_PROPERTY(bool isVideo READ isVideo NOTIFY newInviteState)
|
||||||
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
|
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
|
||||||
Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY newCallParty)
|
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
|
||||||
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newCallParty)
|
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
|
||||||
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
|
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
|
||||||
Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
|
Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
|
||||||
|
Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
|
||||||
|
Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CallManager(QObject *);
|
CallManager(QObject *);
|
||||||
|
|
||||||
void sendInvite(const QString &roomid, bool isVideo);
|
void sendInvite(const QString &roomid, bool isVideo);
|
||||||
void hangUp(
|
bool haveCallInvite() const { return haveCallInvite_; }
|
||||||
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
|
|
||||||
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
|
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
|
||||||
bool isOnVideoCall() const { return session_.isVideo(); }
|
bool isVideo() const { return isVideo_; }
|
||||||
webrtc::State callState() const { return session_.state(); }
|
webrtc::State callState() const { return session_.state(); }
|
||||||
QString callPartyName() const { return callPartyName_; }
|
QString callParty() const { return callParty_; }
|
||||||
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
|
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
|
||||||
bool isMicMuted() const { return session_.isMicMuted(); }
|
bool isMicMuted() const { return session_.isMicMuted(); }
|
||||||
bool callsSupported() const;
|
bool callsSupported() const;
|
||||||
|
QStringList mics() const { return devices(false); }
|
||||||
|
QStringList cameras() const { return devices(true); }
|
||||||
void refreshTurnServer();
|
void refreshTurnServer();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void syncEvent(const mtx::events::collections::TimelineEvents &event);
|
void syncEvent(const mtx::events::collections::TimelineEvents &event);
|
||||||
void toggleMicMute();
|
void toggleMicMute();
|
||||||
void toggleCameraView() { session_.toggleCameraView(); }
|
void toggleCameraView() { session_.toggleCameraView(); }
|
||||||
|
void acceptInvite();
|
||||||
|
void hangUp(
|
||||||
|
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
|
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
|
||||||
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 newInviteState();
|
||||||
void newCallState();
|
void newCallState();
|
||||||
void newVideoCallState();
|
|
||||||
void newCallParty();
|
|
||||||
void micMuteChanged();
|
void micMuteChanged();
|
||||||
|
void devicesChanged();
|
||||||
void turnServerRetrieved(const mtx::responses::TurnServer &);
|
void turnServerRetrieved(const mtx::responses::TurnServer &);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -66,10 +74,13 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
WebRTCSession &session_;
|
WebRTCSession &session_;
|
||||||
QString roomid_;
|
QString roomid_;
|
||||||
QString callPartyName_;
|
QString callParty_;
|
||||||
QString callPartyAvatarUrl_;
|
QString callPartyAvatarUrl_;
|
||||||
std::string callid_;
|
std::string callid_;
|
||||||
const uint32_t timeoutms_ = 120000;
|
const uint32_t timeoutms_ = 120000;
|
||||||
|
bool isVideo_ = false;
|
||||||
|
bool haveCallInvite_ = false;
|
||||||
|
std::string inviteSDP_;
|
||||||
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
|
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
|
||||||
std::vector<std::string> turnURIs_;
|
std::vector<std::string> turnURIs_;
|
||||||
QTimer turnServerTimer_;
|
QTimer turnServerTimer_;
|
||||||
@ -83,6 +94,7 @@ private:
|
|||||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
|
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
|
||||||
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
|
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
|
||||||
void generateCallID();
|
void generateCallID();
|
||||||
|
QStringList devices(bool isVideo) const;
|
||||||
void clear();
|
void clear();
|
||||||
void endCall();
|
void endCall();
|
||||||
void playRingtone(const QUrl &ringtone, bool repeat);
|
void playRingtone(const QUrl &ringtone, bool repeat);
|
||||||
|
@ -242,12 +242,14 @@ newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data)
|
|||||||
GstDevice *device;
|
GstDevice *device;
|
||||||
gst_message_parse_device_added(msg, &device);
|
gst_message_parse_device_added(msg, &device);
|
||||||
addDevice(device);
|
addDevice(device);
|
||||||
|
emit WebRTCSession::instance().devicesChanged();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||||
GstDevice *device;
|
GstDevice *device;
|
||||||
gst_message_parse_device_removed(msg, &device);
|
gst_message_parse_device_removed(msg, &device);
|
||||||
removeDevice(device, false);
|
removeDevice(device, false);
|
||||||
|
emit WebRTCSession::instance().devicesChanged();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GST_MESSAGE_DEVICE_CHANGED: {
|
case GST_MESSAGE_DEVICE_CHANGED: {
|
||||||
|
@ -75,6 +75,7 @@ signals:
|
|||||||
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(webrtc::State);
|
void stateChanged(webrtc::State);
|
||||||
|
void devicesChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void setState(webrtc::State state) { state_ = state; }
|
void setState(webrtc::State state) { state_ = state; }
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
#include <QComboBox>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QString>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "ChatPage.h"
|
|
||||||
#include "Config.h"
|
|
||||||
#include "UserSettingsPage.h"
|
|
||||||
#include "Utils.h"
|
|
||||||
#include "WebRTCSession.h"
|
|
||||||
#include "dialogs/AcceptCall.h"
|
|
||||||
#include "ui/Avatar.h"
|
|
||||||
|
|
||||||
namespace dialogs {
|
|
||||||
|
|
||||||
AcceptCall::AcceptCall(const QString &caller,
|
|
||||||
const QString &displayName,
|
|
||||||
const QString &roomName,
|
|
||||||
const QString &avatarUrl,
|
|
||||||
bool isVideo,
|
|
||||||
QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
{
|
|
||||||
std::string errorMessage;
|
|
||||||
WebRTCSession *session = &WebRTCSession::instance();
|
|
||||||
if (!session->havePlugins(false, &errorMessage)) {
|
|
||||||
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
|
||||||
emit close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isVideo && !session->havePlugins(true, &errorMessage)) {
|
|
||||||
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
|
||||||
emit close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
session->refreshDevices();
|
|
||||||
microphones_ = session->getDeviceNames(
|
|
||||||
false, ChatPage::instance()->userSettings()->microphone().toStdString());
|
|
||||||
if (microphones_.empty()) {
|
|
||||||
emit ChatPage::instance()->showNotification(
|
|
||||||
tr("Incoming call: No microphone found."));
|
|
||||||
emit close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isVideo)
|
|
||||||
cameras_ = session->getDeviceNames(
|
|
||||||
true, ChatPage::instance()->userSettings()->camera().toStdString());
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
auto avatar = new Avatar(this, QFontMetrics(f).height() * 6);
|
|
||||||
if (!avatarUrl.isEmpty())
|
|
||||||
avatar->setImage(avatarUrl);
|
|
||||||
else
|
|
||||||
avatar->setLetter(utils::firstChar(roomName));
|
|
||||||
|
|
||||||
const int iconSize = 22;
|
|
||||||
QLabel *callTypeIndicator = new QLabel(this);
|
|
||||||
callTypeIndicator->setPixmap(
|
|
||||||
QIcon(isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png")
|
|
||||||
.pixmap(QSize(iconSize * 2, iconSize * 2)));
|
|
||||||
|
|
||||||
QLabel *callTypeLabel = new QLabel(isVideo ? tr("Video Call") : tr("Voice Call"), this);
|
|
||||||
labelFont.setPointSizeF(f.pointSizeF() * 1.1);
|
|
||||||
callTypeLabel->setFont(labelFont);
|
|
||||||
callTypeLabel->setAlignment(Qt::AlignCenter);
|
|
||||||
|
|
||||||
auto buttonLayout = new QHBoxLayout;
|
|
||||||
buttonLayout->setSpacing(18);
|
|
||||||
acceptBtn_ = new QPushButton(tr("Accept"), this);
|
|
||||||
acceptBtn_->setDefault(true);
|
|
||||||
acceptBtn_->setIcon(
|
|
||||||
QIcon(isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"));
|
|
||||||
acceptBtn_->setIconSize(QSize(iconSize, iconSize));
|
|
||||||
|
|
||||||
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_);
|
|
||||||
|
|
||||||
microphoneCombo_ = new QComboBox(this);
|
|
||||||
for (const auto &m : microphones_)
|
|
||||||
microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"),
|
|
||||||
QString::fromStdString(m));
|
|
||||||
|
|
||||||
if (!cameras_.empty()) {
|
|
||||||
cameraCombo_ = new QComboBox(this);
|
|
||||||
for (const auto &c : cameras_)
|
|
||||||
cameraCombo_->addItem(QIcon(":/icons/icons/ui/video-call.png"),
|
|
||||||
QString::fromStdString(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayNameLabel)
|
|
||||||
layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
|
|
||||||
layout->addWidget(callerLabel, 0, Qt::AlignCenter);
|
|
||||||
layout->addWidget(avatar, 0, Qt::AlignCenter);
|
|
||||||
layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter);
|
|
||||||
layout->addWidget(callTypeLabel, 0, Qt::AlignCenter);
|
|
||||||
layout->addLayout(buttonLayout);
|
|
||||||
layout->addWidget(microphoneCombo_);
|
|
||||||
if (cameraCombo_)
|
|
||||||
layout->addWidget(cameraCombo_);
|
|
||||||
|
|
||||||
connect(acceptBtn_, &QPushButton::clicked, this, [this]() {
|
|
||||||
ChatPage::instance()->userSettings()->setMicrophone(
|
|
||||||
QString::fromStdString(microphones_[microphoneCombo_->currentIndex()]));
|
|
||||||
if (cameraCombo_) {
|
|
||||||
ChatPage::instance()->userSettings()->setCamera(
|
|
||||||
QString::fromStdString(cameras_[cameraCombo_->currentIndex()]));
|
|
||||||
}
|
|
||||||
emit accept();
|
|
||||||
emit close();
|
|
||||||
});
|
|
||||||
connect(rejectBtn_, &QPushButton::clicked, this, [this]() {
|
|
||||||
emit reject();
|
|
||||||
emit close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
class QComboBox;
|
|
||||||
class QPushButton;
|
|
||||||
class QString;
|
|
||||||
|
|
||||||
namespace dialogs {
|
|
||||||
|
|
||||||
class AcceptCall : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
AcceptCall(const QString &caller,
|
|
||||||
const QString &displayName,
|
|
||||||
const QString &roomName,
|
|
||||||
const QString &avatarUrl,
|
|
||||||
bool isVideo,
|
|
||||||
QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void accept();
|
|
||||||
void reject();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QPushButton *acceptBtn_ = nullptr;
|
|
||||||
QPushButton *rejectBtn_ = nullptr;
|
|
||||||
QComboBox *microphoneCombo_ = nullptr;
|
|
||||||
QComboBox *cameraCombo_ = nullptr;
|
|
||||||
std::vector<std::string> microphones_;
|
|
||||||
std::vector<std::string> cameras_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -597,7 +597,9 @@ void
|
|||||||
InputBar::callButton()
|
InputBar::callButton()
|
||||||
{
|
{
|
||||||
auto callManager_ = ChatPage::instance()->callManager();
|
auto callManager_ = ChatPage::instance()->callManager();
|
||||||
if (callManager_->isOnCall()) {
|
if (callManager_->haveCallInvite()) {
|
||||||
|
return;
|
||||||
|
} else if (callManager_->isOnCall()) {
|
||||||
callManager_->hangUp();
|
callManager_->hangUp();
|
||||||
} else {
|
} else {
|
||||||
auto current_room_ = room->roomId();
|
auto current_room_ = room->roomId();
|
||||||
|
Loading…
Reference in New Issue
Block a user