mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Support video calls
This commit is contained in:
parent
3499abd99a
commit
d1f3a3ef40
18 changed files with 1171 additions and 447 deletions
BIN
resources/icons/ui/video-call.png
Normal file
BIN
resources/icons/ui/video-call.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 353 B |
|
@ -10,6 +10,12 @@ Rectangle {
|
||||||
color: "#2ECC71"
|
color: "#2ECC71"
|
||||||
implicitHeight: rowLayout.height + 8
|
implicitHeight: rowLayout.height + 8
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: if (TimelineManager.onVideoCall)
|
||||||
|
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: rowLayout
|
id: rowLayout
|
||||||
|
|
||||||
|
@ -33,7 +39,8 @@ Rectangle {
|
||||||
Image {
|
Image {
|
||||||
Layout.preferredWidth: 24
|
Layout.preferredWidth: 24
|
||||||
Layout.preferredHeight: 24
|
Layout.preferredHeight: 24
|
||||||
source: "qrc:/icons/icons/ui/place-call.png"
|
source: TimelineManager.onVideoCall ?
|
||||||
|
"qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -58,9 +65,12 @@ Rectangle {
|
||||||
callStateLabel.text = "00:00";
|
callStateLabel.text = "00:00";
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
callTimer.startTime = Math.floor(d.getTime() / 1000);
|
callTimer.startTime = Math.floor(d.getTime() / 1000);
|
||||||
|
if (TimelineManager.onVideoCall)
|
||||||
|
stackLayout.currentIndex = 1;
|
||||||
break;
|
break;
|
||||||
case WebRTCState.DISCONNECTED:
|
case WebRTCState.DISCONNECTED:
|
||||||
callStateLabel.text = "";
|
callStateLabel.text = "";
|
||||||
|
stackLayout.currentIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import "./emoji"
|
||||||
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
|
||||||
import QtQuick.Layouts 1.2
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
import im.nheko.EmojiModel 1.0
|
import im.nheko.EmojiModel 1.0
|
||||||
|
@ -282,144 +282,157 @@ Page {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
StackLayout {
|
||||||
id: chat
|
id: stackLayout
|
||||||
|
currentIndex: 0
|
||||||
|
|
||||||
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
|
Connections {
|
||||||
|
target: TimelineManager
|
||||||
visible: TimelineManager.timeline != null
|
function onActiveTimelineChanged() {
|
||||||
cacheBuffer: 400
|
stackLayout.currentIndex = 0;
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
model: TimelineManager.timeline
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
pixelAligned: true
|
|
||||||
spacing: 4
|
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
|
||||||
onCountChanged: {
|
|
||||||
if (atYEnd)
|
|
||||||
model.currentIndex = 0;
|
|
||||||
|
|
||||||
} // Mark last event as read, since we are at the bottom
|
|
||||||
|
|
||||||
ScrollHelper {
|
|
||||||
flickable: parent
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: StandardKey.MoveToPreviousPage
|
|
||||||
onActivated: {
|
|
||||||
chat.contentY = chat.contentY - chat.height / 2;
|
|
||||||
chat.returnToBounds();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
ListView {
|
||||||
sequence: StandardKey.MoveToNextPage
|
id: chat
|
||||||
onActivated: {
|
|
||||||
chat.contentY = chat.contentY + chat.height / 2;
|
|
||||||
chat.returnToBounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
|
||||||
sequence: StandardKey.Cancel
|
|
||||||
onActivated: chat.model.reply = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
visible: TimelineManager.timeline != null
|
||||||
sequence: "Alt+Up"
|
cacheBuffer: 400
|
||||||
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
|
Layout.fillWidth: true
|
||||||
}
|
Layout.fillHeight: true
|
||||||
|
model: TimelineManager.timeline
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
pixelAligned: true
|
||||||
|
spacing: 4
|
||||||
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
onCountChanged: {
|
||||||
|
if (atYEnd)
|
||||||
|
model.currentIndex = 0;
|
||||||
|
|
||||||
Shortcut {
|
} // Mark last event as read, since we are at the bottom
|
||||||
sequence: "Alt+Down"
|
|
||||||
onActivated: {
|
|
||||||
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
|
|
||||||
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
ScrollHelper {
|
||||||
id: userProfileComponent
|
flickable: parent
|
||||||
|
anchors.fill: parent
|
||||||
UserProfile {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Shortcut {
|
||||||
|
sequence: StandardKey.MoveToPreviousPage
|
||||||
|
onActivated: {
|
||||||
|
chat.contentY = chat.contentY - chat.height / 2;
|
||||||
|
chat.returnToBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
Shortcut {
|
||||||
property: "section"
|
sequence: StandardKey.MoveToNextPage
|
||||||
}
|
onActivated: {
|
||||||
|
chat.contentY = chat.contentY + chat.height / 2;
|
||||||
|
chat.returnToBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Shortcut {
|
||||||
id: sectionHeader
|
sequence: StandardKey.Cancel
|
||||||
|
onActivated: chat.model.reply = undefined
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Shortcut {
|
||||||
property var modelData
|
sequence: "Alt+Up"
|
||||||
property string section
|
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
|
||||||
property string nextSection
|
}
|
||||||
|
|
||||||
topPadding: 4
|
Shortcut {
|
||||||
bottomPadding: 4
|
sequence: "Alt+Down"
|
||||||
spacing: 8
|
onActivated: {
|
||||||
visible: !!modelData
|
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
|
||||||
width: parent.width
|
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
|
||||||
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Component {
|
||||||
id: dateBubble
|
id: userProfileComponent
|
||||||
|
|
||||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
|
||||||
visible: section.includes(" ")
|
|
||||||
text: chat.model.formatDateSeparator(modelData.timestamp)
|
|
||||||
color: colors.text
|
|
||||||
height: fontMetrics.height * 1.4
|
|
||||||
width: contentWidth * 1.2
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: parent.height / 2
|
|
||||||
color: colors.base
|
|
||||||
}
|
|
||||||
|
|
||||||
|
UserProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
}
|
||||||
height: userName.height
|
|
||||||
|
section {
|
||||||
|
property: "section"
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: sectionHeader
|
||||||
|
|
||||||
|
Column {
|
||||||
|
property var modelData
|
||||||
|
property string section
|
||||||
|
property string nextSection
|
||||||
|
|
||||||
|
topPadding: 4
|
||||||
|
bottomPadding: 4
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
visible: !!modelData
|
||||||
Avatar {
|
width: parent.width
|
||||||
width: avatarSize
|
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
||||||
height: avatarSize
|
|
||||||
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
|
|
||||||
displayName: modelData.userName
|
|
||||||
userid: modelData.userId
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: chat.model.openUserProfile(modelData.userId)
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
propagateComposedEvents: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: userName
|
id: dateBubble
|
||||||
|
|
||||||
text: TimelineManager.escapeEmoji(modelData.userName)
|
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||||
color: TimelineManager.userColor(modelData.userId, colors.window)
|
visible: section.includes(" ")
|
||||||
textFormat: Text.RichText
|
text: chat.model.formatDateSeparator(modelData.timestamp)
|
||||||
|
color: colors.text
|
||||||
|
height: fontMetrics.height * 1.4
|
||||||
|
width: contentWidth * 1.2
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: parent.height / 2
|
||||||
|
color: colors.base
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
height: userName.height
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
width: avatarSize
|
||||||
|
height: avatarSize
|
||||||
|
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
|
||||||
|
displayName: modelData.userName
|
||||||
|
userid: modelData.userId
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: chat.model.openUserProfile(modelData.userId)
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
propagateComposedEvents: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: userName
|
||||||
|
|
||||||
|
text: TimelineManager.escapeEmoji(modelData.userName)
|
||||||
|
color: TimelineManager.userColor(modelData.userId, colors.window)
|
||||||
|
textFormat: Text.RichText
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
onClicked: chat.model.openUserProfile(modelData.userId)
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
propagateComposedEvents: true
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
onClicked: chat.model.openUserProfile(modelData.userId)
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
propagateComposedEvents: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -428,62 +441,67 @@ Page {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
id: scrollbar
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
id: scrollbar
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: wrapper
|
|
||||||
|
|
||||||
// This would normally be previousSection, but our model's order is inverted.
|
|
||||||
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
|
|
||||||
property Item section
|
|
||||||
|
|
||||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
|
||||||
width: chat.delegateMaxWidth
|
|
||||||
height: section ? section.height + timelinerow.height : timelinerow.height
|
|
||||||
onSectionBoundaryChanged: {
|
|
||||||
if (sectionBoundary) {
|
|
||||||
var properties = {
|
|
||||||
"modelData": model.dump,
|
|
||||||
"section": ListView.section,
|
|
||||||
"nextSection": ListView.nextSection
|
|
||||||
};
|
|
||||||
section = sectionHeader.createObject(wrapper, properties);
|
|
||||||
} else {
|
|
||||||
section.destroy();
|
|
||||||
section = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineRow {
|
delegate: Item {
|
||||||
id: timelinerow
|
id: wrapper
|
||||||
|
|
||||||
y: section ? section.y + section.height : 0
|
// This would normally be previousSection, but our model's order is inverted.
|
||||||
}
|
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
|
||||||
|
property Item section
|
||||||
Connections {
|
|
||||||
function onMovementEnded() {
|
|
||||||
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
|
||||||
chat.model.currentIndex = index;
|
|
||||||
|
|
||||||
|
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||||
|
width: chat.delegateMaxWidth
|
||||||
|
height: section ? section.height + timelinerow.height : timelinerow.height
|
||||||
|
onSectionBoundaryChanged: {
|
||||||
|
if (sectionBoundary) {
|
||||||
|
var properties = {
|
||||||
|
"modelData": model.dump,
|
||||||
|
"section": ListView.section,
|
||||||
|
"nextSection": ListView.nextSection
|
||||||
|
};
|
||||||
|
section = sectionHeader.createObject(wrapper, properties);
|
||||||
|
} else {
|
||||||
|
section.destroy();
|
||||||
|
section = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: chat
|
TimelineRow {
|
||||||
|
id: timelinerow
|
||||||
|
|
||||||
|
y: section ? section.y + section.height : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onMovementEnded() {
|
||||||
|
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
||||||
|
chat.model.currentIndex = index;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
target: chat
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: BusyIndicator {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
running: chat.model && chat.model.paginationInProgress
|
||||||
|
height: 50
|
||||||
|
width: 50
|
||||||
|
z: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: BusyIndicator {
|
Loader {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
id: videoCallLoader
|
||||||
running: chat.model && chat.model.paginationInProgress
|
source: TimelineManager.onVideoCall ? "VideoCall.qml" : ""
|
||||||
height: 50
|
onLoaded: TimelineManager.setVideoCallItem()
|
||||||
width: 50
|
|
||||||
z: 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
7
resources/qml/VideoCall.qml
Normal file
7
resources/qml/VideoCall.qml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import QtQuick 2.9
|
||||||
|
|
||||||
|
import org.freedesktop.gstreamer.GLVideoItem 1.0
|
||||||
|
|
||||||
|
GstGLVideoItem {
|
||||||
|
objectName: "videoCallItem"
|
||||||
|
}
|
|
@ -74,6 +74,7 @@
|
||||||
<file>icons/ui/end-call.png</file>
|
<file>icons/ui/end-call.png</file>
|
||||||
<file>icons/ui/microphone-mute.png</file>
|
<file>icons/ui/microphone-mute.png</file>
|
||||||
<file>icons/ui/microphone-unmute.png</file>
|
<file>icons/ui/microphone-unmute.png</file>
|
||||||
|
<file>icons/ui/video-call.png</file>
|
||||||
|
|
||||||
<file>icons/emoji-categories/people.png</file>
|
<file>icons/emoji-categories/people.png</file>
|
||||||
<file>icons/emoji-categories/people@2x.png</file>
|
<file>icons/emoji-categories/people@2x.png</file>
|
||||||
|
@ -130,6 +131,7 @@
|
||||||
<file>qml/Reactions.qml</file>
|
<file>qml/Reactions.qml</file>
|
||||||
<file>qml/ScrollHelper.qml</file>
|
<file>qml/ScrollHelper.qml</file>
|
||||||
<file>qml/TimelineRow.qml</file>
|
<file>qml/TimelineRow.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>
|
||||||
|
|
|
@ -25,9 +25,6 @@ Q_DECLARE_METATYPE(mtx::responses::TurnServer)
|
||||||
using namespace mtx::events;
|
using namespace mtx::events;
|
||||||
using namespace mtx::events::msg;
|
using namespace mtx::events::msg;
|
||||||
|
|
||||||
// https://github.com/vector-im/riot-web/issues/10173
|
|
||||||
#define STUN_SERVER "stun://turn.matrix.org:3478"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
getTurnURIs(const mtx::responses::TurnServer &turnServer);
|
getTurnURIs(const mtx::responses::TurnServer &turnServer);
|
||||||
|
@ -43,6 +40,8 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
|
||||||
qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
|
qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
|
||||||
qRegisterMetaType<mtx::responses::TurnServer>();
|
qRegisterMetaType<mtx::responses::TurnServer>();
|
||||||
|
|
||||||
|
session_.setSettings(userSettings);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
&session_,
|
&session_,
|
||||||
&WebRTCSession::offerCreated,
|
&WebRTCSession::offerCreated,
|
||||||
|
@ -128,30 +127,29 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CallManager::sendInvite(const QString &roomid)
|
CallManager::sendInvite(const QString &roomid, bool isVideo)
|
||||||
{
|
{
|
||||||
if (onActiveCall())
|
if (onActiveCall())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
|
auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
|
||||||
if (roomInfo.member_count != 2) {
|
if (roomInfo.member_count != 2) {
|
||||||
emit ChatPage::instance()->showNotification(
|
emit ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms.");
|
||||||
"Voice calls are limited to 1:1 rooms.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
if (!session_.init(&errorMessage)) {
|
if (!session_.havePlugins(false, &errorMessage) ||
|
||||||
|
(isVideo && !session_.havePlugins(true, &errorMessage))) {
|
||||||
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
roomid_ = roomid;
|
roomid_ = roomid;
|
||||||
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
|
|
||||||
session_.setTurnServers(turnURIs_);
|
session_.setTurnServers(turnURIs_);
|
||||||
|
|
||||||
generateCallID();
|
generateCallID();
|
||||||
nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_);
|
nhlog::ui()->debug(
|
||||||
|
"WebRTC: call id: {} - creating {} invite", callid_, isVideo ? "video" : "voice");
|
||||||
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();
|
||||||
|
@ -159,10 +157,12 @@ CallManager::sendInvite(const QString &roomid)
|
||||||
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
|
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
|
||||||
emit newCallParty();
|
emit newCallParty();
|
||||||
playRingtone("qrc:/media/media/ringback.ogg", true);
|
playRingtone("qrc:/media/media/ringback.ogg", true);
|
||||||
if (!session_.createOffer()) {
|
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 {
|
||||||
|
@ -242,7 +242,7 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
|
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
|
||||||
if (onActiveCall() || roomInfo.member_count != 2 || isVideo) {
|
if (onActiveCall() || roomInfo.member_count != 2) {
|
||||||
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
|
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
|
||||||
CallHangUp{callInviteEvent.content.call_id,
|
CallHangUp{callInviteEvent.content.call_id,
|
||||||
0,
|
0,
|
||||||
|
@ -266,10 +266,11 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
|
||||||
QString::fromStdString(roomInfo.name),
|
QString::fromStdString(roomInfo.name),
|
||||||
QString::fromStdString(roomInfo.avatar_url),
|
QString::fromStdString(roomInfo.avatar_url),
|
||||||
settings_,
|
settings_,
|
||||||
|
isVideo,
|
||||||
MainWindow::instance());
|
MainWindow::instance());
|
||||||
connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent]() {
|
connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent, isVideo]() {
|
||||||
MainWindow::instance()->hideOverlay();
|
MainWindow::instance()->hideOverlay();
|
||||||
answerInvite(callInviteEvent.content);
|
answerInvite(callInviteEvent.content, isVideo);
|
||||||
});
|
});
|
||||||
connect(dialog, &dialogs::AcceptCall::reject, this, [this]() {
|
connect(dialog, &dialogs::AcceptCall::reject, this, [this]() {
|
||||||
MainWindow::instance()->hideOverlay();
|
MainWindow::instance()->hideOverlay();
|
||||||
|
@ -279,19 +280,18 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CallManager::answerInvite(const CallInvite &invite)
|
CallManager::answerInvite(const CallInvite &invite, bool isVideo)
|
||||||
{
|
{
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
if (!session_.init(&errorMessage)) {
|
if (!session_.havePlugins(false, &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_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
|
|
||||||
session_.setTurnServers(turnURIs_);
|
session_.setTurnServers(turnURIs_);
|
||||||
|
|
||||||
if (!session_.acceptOffer(invite.sdp)) {
|
if (!session_.acceptOffer(invite.sdp)) {
|
||||||
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
||||||
hangUp();
|
hangUp();
|
||||||
|
@ -299,6 +299,8 @@ CallManager::answerInvite(const CallInvite &invite)
|
||||||
}
|
}
|
||||||
session_.acceptICECandidates(remoteICECandidates_);
|
session_.acceptICECandidates(remoteICECandidates_);
|
||||||
remoteICECandidates_.clear();
|
remoteICECandidates_.clear();
|
||||||
|
if (isVideo)
|
||||||
|
emit newVideoCallState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -384,7 +386,10 @@ CallManager::endCall()
|
||||||
{
|
{
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
clear();
|
clear();
|
||||||
|
bool isVideo = session_.isVideo();
|
||||||
session_.end();
|
session_.end();
|
||||||
|
if (isVideo)
|
||||||
|
emit newVideoCallState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -26,7 +26,7 @@ class CallManager : public QObject
|
||||||
public:
|
public:
|
||||||
CallManager(QSharedPointer<UserSettings>);
|
CallManager(QSharedPointer<UserSettings>);
|
||||||
|
|
||||||
void sendInvite(const QString &roomid);
|
void sendInvite(const QString &roomid, bool isVideo);
|
||||||
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() const;
|
bool onActiveCall() const;
|
||||||
|
@ -43,6 +43,7 @@ signals:
|
||||||
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 newCallParty();
|
||||||
|
void newVideoCallState();
|
||||||
void turnServerRetrieved(const mtx::responses::TurnServer &);
|
void turnServerRetrieved(const mtx::responses::TurnServer &);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -67,7 +68,7 @@ private:
|
||||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
|
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
|
||||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
|
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
|
||||||
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 &);
|
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
|
||||||
void generateCallID();
|
void generateCallID();
|
||||||
void clear();
|
void clear();
|
||||||
void endCall();
|
void endCall();
|
||||||
|
|
|
@ -437,7 +437,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
} else {
|
} else {
|
||||||
if (auto roomInfo = cache::singleRoomInfo(current_room_.toStdString());
|
if (auto roomInfo = cache::singleRoomInfo(current_room_.toStdString());
|
||||||
roomInfo.member_count != 2) {
|
roomInfo.member_count != 2) {
|
||||||
showNotification("Voice calls are limited to 1:1 rooms.");
|
showNotification("Calls are limited to 1:1 rooms.");
|
||||||
} else {
|
} else {
|
||||||
std::vector<RoomMember> members(
|
std::vector<RoomMember> members(
|
||||||
cache::getMembers(current_room_.toStdString()));
|
cache::getMembers(current_room_.toStdString()));
|
||||||
|
@ -452,7 +452,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
userSettings_,
|
userSettings_,
|
||||||
MainWindow::instance());
|
MainWindow::instance());
|
||||||
connect(dialog, &dialogs::PlaceCall::voice, this, [this]() {
|
connect(dialog, &dialogs::PlaceCall::voice, this, [this]() {
|
||||||
callManager_.sendInvite(current_room_);
|
callManager_.sendInvite(current_room_, false);
|
||||||
|
});
|
||||||
|
connect(dialog, &dialogs::PlaceCall::video, this, [this]() {
|
||||||
|
callManager_.sendInvite(current_room_, true);
|
||||||
});
|
});
|
||||||
utils::centerWidget(dialog, MainWindow::instance());
|
utils::centerWidget(dialog, MainWindow::instance());
|
||||||
dialog->show();
|
dialog->show();
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "Olm.h"
|
#include "Olm.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "WebRTCSession.h"
|
||||||
#include "ui/FlatButton.h"
|
#include "ui/FlatButton.h"
|
||||||
#include "ui/ToggleButton.h"
|
#include "ui/ToggleButton.h"
|
||||||
|
|
||||||
|
@ -77,8 +78,11 @@ UserSettings::load()
|
||||||
presence_ =
|
presence_ =
|
||||||
settings.value("user/presence", QVariant::fromValue(Presence::AutomaticPresence))
|
settings.value("user/presence", QVariant::fromValue(Presence::AutomaticPresence))
|
||||||
.value<Presence>();
|
.value<Presence>();
|
||||||
useStunServer_ = settings.value("user/use_stun_server", false).toBool();
|
microphone_ = settings.value("user/microphone", QString()).toString();
|
||||||
defaultAudioSource_ = settings.value("user/default_audio_source", QString()).toString();
|
camera_ = settings.value("user/camera", QString()).toString();
|
||||||
|
cameraResolution_ = settings.value("user/camera_resolution", QString()).toString();
|
||||||
|
cameraFrameRate_ = settings.value("user/camera_frame_rate", QString()).toString();
|
||||||
|
useStunServer_ = settings.value("user/use_stun_server", false).toBool();
|
||||||
|
|
||||||
applyTheme();
|
applyTheme();
|
||||||
}
|
}
|
||||||
|
@ -292,12 +296,42 @@ UserSettings::setUseStunServer(bool useStunServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UserSettings::setDefaultAudioSource(const QString &defaultAudioSource)
|
UserSettings::setMicrophone(QString microphone)
|
||||||
{
|
{
|
||||||
if (defaultAudioSource == defaultAudioSource_)
|
if (microphone == microphone_)
|
||||||
return;
|
return;
|
||||||
defaultAudioSource_ = defaultAudioSource;
|
microphone_ = microphone;
|
||||||
emit defaultAudioSourceChanged(defaultAudioSource);
|
emit microphoneChanged(microphone);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettings::setCamera(QString camera)
|
||||||
|
{
|
||||||
|
if (camera == camera_)
|
||||||
|
return;
|
||||||
|
camera_ = camera;
|
||||||
|
emit cameraChanged(camera);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettings::setCameraResolution(QString resolution)
|
||||||
|
{
|
||||||
|
if (resolution == cameraResolution_)
|
||||||
|
return;
|
||||||
|
cameraResolution_ = resolution;
|
||||||
|
emit cameraResolutionChanged(resolution);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettings::setCameraFrameRate(QString frameRate)
|
||||||
|
{
|
||||||
|
if (frameRate == cameraFrameRate_)
|
||||||
|
return;
|
||||||
|
cameraFrameRate_ = frameRate;
|
||||||
|
emit cameraFrameRateChanged(frameRate);
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,8 +420,11 @@ UserSettings::save()
|
||||||
settings.setValue("font_family", font_);
|
settings.setValue("font_family", font_);
|
||||||
settings.setValue("emoji_font_family", emojiFont_);
|
settings.setValue("emoji_font_family", emojiFont_);
|
||||||
settings.setValue("presence", QVariant::fromValue(presence_));
|
settings.setValue("presence", QVariant::fromValue(presence_));
|
||||||
|
settings.setValue("microphone", microphone_);
|
||||||
|
settings.setValue("camera", camera_);
|
||||||
|
settings.setValue("camera_resolution", cameraResolution_);
|
||||||
|
settings.setValue("camera_frame_rate", cameraFrameRate_);
|
||||||
settings.setValue("use_stun_server", useStunServer_);
|
settings.setValue("use_stun_server", useStunServer_);
|
||||||
settings.setValue("default_audio_source", defaultAudioSource_);
|
|
||||||
|
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
|
@ -458,6 +495,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
fontSizeCombo_ = new QComboBox{this};
|
fontSizeCombo_ = new QComboBox{this};
|
||||||
fontSelectionCombo_ = new QComboBox{this};
|
fontSelectionCombo_ = new QComboBox{this};
|
||||||
emojiFontSelectionCombo_ = new QComboBox{this};
|
emojiFontSelectionCombo_ = new QComboBox{this};
|
||||||
|
microphoneCombo_ = new QComboBox{this};
|
||||||
|
cameraCombo_ = new QComboBox{this};
|
||||||
|
cameraResolutionCombo_ = new QComboBox{this};
|
||||||
|
cameraFrameRateCombo_ = new QComboBox{this};
|
||||||
timelineMaxWidthSpin_ = new QSpinBox{this};
|
timelineMaxWidthSpin_ = new QSpinBox{this};
|
||||||
|
|
||||||
if (!settings_->tray())
|
if (!settings_->tray())
|
||||||
|
@ -645,6 +686,14 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
|
|
||||||
formLayout_->addRow(callsLabel);
|
formLayout_->addRow(callsLabel);
|
||||||
formLayout_->addRow(new HorizontalLine{this});
|
formLayout_->addRow(new HorizontalLine{this});
|
||||||
|
boxWrap(tr("Microphone"), microphoneCombo_);
|
||||||
|
boxWrap(tr("Camera"), cameraCombo_);
|
||||||
|
boxWrap(tr("Camera resolution"), cameraResolutionCombo_);
|
||||||
|
boxWrap(tr("Camera frame rate"), cameraFrameRateCombo_);
|
||||||
|
microphoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||||
|
cameraCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||||
|
cameraResolutionCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||||
|
cameraFrameRateCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||||
boxWrap(tr("Allow fallback call assist server"),
|
boxWrap(tr("Allow fallback call assist server"),
|
||||||
useStunServer_,
|
useStunServer_,
|
||||||
tr("Will use turn.matrix.org as assist when your home server does not offer one."));
|
tr("Will use turn.matrix.org as assist when your home server does not offer one."));
|
||||||
|
@ -698,6 +747,38 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
connect(emojiFontSelectionCombo_,
|
connect(emojiFontSelectionCombo_,
|
||||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||||
[this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
|
[this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
|
||||||
|
|
||||||
|
connect(microphoneCombo_,
|
||||||
|
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||||
|
[this](const QString µphone) { settings_->setMicrophone(microphone); });
|
||||||
|
|
||||||
|
connect(cameraCombo_,
|
||||||
|
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||||
|
[this](const QString &camera) {
|
||||||
|
settings_->setCamera(camera);
|
||||||
|
std::vector<std::string> resolutions =
|
||||||
|
WebRTCSession::instance().getResolutions(camera.toStdString());
|
||||||
|
cameraResolutionCombo_->clear();
|
||||||
|
for (const auto &resolution : resolutions)
|
||||||
|
cameraResolutionCombo_->addItem(QString::fromStdString(resolution));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(cameraResolutionCombo_,
|
||||||
|
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||||
|
[this](const QString &resolution) {
|
||||||
|
settings_->setCameraResolution(resolution);
|
||||||
|
std::vector<std::string> frameRates =
|
||||||
|
WebRTCSession::instance().getFrameRates(settings_->camera().toStdString(),
|
||||||
|
resolution.toStdString());
|
||||||
|
cameraFrameRateCombo_->clear();
|
||||||
|
for (const auto &frameRate : frameRates)
|
||||||
|
cameraFrameRateCombo_->addItem(QString::fromStdString(frameRate));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(cameraFrameRateCombo_,
|
||||||
|
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||||
|
[this](const QString &frameRate) { settings_->setCameraFrameRate(frameRate); });
|
||||||
|
|
||||||
connect(trayToggle_, &Toggle::toggled, this, [this](bool disabled) {
|
connect(trayToggle_, &Toggle::toggled, this, [this](bool disabled) {
|
||||||
settings_->setTray(!disabled);
|
settings_->setTray(!disabled);
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
|
@ -807,6 +888,26 @@ UserSettingsPage::showEvent(QShowEvent *)
|
||||||
enlargeEmojiOnlyMessages_->setState(!settings_->enlargeEmojiOnlyMessages());
|
enlargeEmojiOnlyMessages_->setState(!settings_->enlargeEmojiOnlyMessages());
|
||||||
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
|
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
|
||||||
timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth());
|
timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth());
|
||||||
|
|
||||||
|
WebRTCSession::instance().refreshDevices();
|
||||||
|
auto mics =
|
||||||
|
WebRTCSession::instance().getDeviceNames(false, settings_->microphone().toStdString());
|
||||||
|
microphoneCombo_->clear();
|
||||||
|
for (const auto &m : mics)
|
||||||
|
microphoneCombo_->addItem(QString::fromStdString(m));
|
||||||
|
|
||||||
|
auto cameraResolution = settings_->cameraResolution();
|
||||||
|
auto cameraFrameRate = settings_->cameraFrameRate();
|
||||||
|
|
||||||
|
auto cameras =
|
||||||
|
WebRTCSession::instance().getDeviceNames(true, settings_->camera().toStdString());
|
||||||
|
cameraCombo_->clear();
|
||||||
|
for (const auto &c : cameras)
|
||||||
|
cameraCombo_->addItem(QString::fromStdString(c));
|
||||||
|
|
||||||
|
utils::restoreCombobox(cameraResolutionCombo_, cameraResolution);
|
||||||
|
utils::restoreCombobox(cameraFrameRateCombo_, cameraFrameRate);
|
||||||
|
|
||||||
useStunServer_->setState(!settings_->useStunServer());
|
useStunServer_->setState(!settings_->useStunServer());
|
||||||
|
|
||||||
deviceFingerprintValue_->setText(
|
deviceFingerprintValue_->setText(
|
||||||
|
|
|
@ -73,8 +73,12 @@ class UserSettings : public QObject
|
||||||
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
|
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
|
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
|
||||||
Q_PROPERTY(QString defaultAudioSource READ defaultAudioSource WRITE setDefaultAudioSource
|
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
|
||||||
NOTIFY defaultAudioSourceChanged)
|
Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
|
||||||
|
Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY
|
||||||
|
cameraResolutionChanged)
|
||||||
|
Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY
|
||||||
|
cameraFrameRateChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UserSettings();
|
UserSettings();
|
||||||
|
@ -111,8 +115,11 @@ public:
|
||||||
void setAvatarCircles(bool state);
|
void setAvatarCircles(bool state);
|
||||||
void setDecryptSidebar(bool state);
|
void setDecryptSidebar(bool state);
|
||||||
void setPresence(Presence state);
|
void setPresence(Presence state);
|
||||||
|
void setMicrophone(QString microphone);
|
||||||
|
void setCamera(QString camera);
|
||||||
|
void setCameraResolution(QString resolution);
|
||||||
|
void setCameraFrameRate(QString frameRate);
|
||||||
void setUseStunServer(bool state);
|
void setUseStunServer(bool state);
|
||||||
void setDefaultAudioSource(const QString &deviceName);
|
|
||||||
|
|
||||||
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
|
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
|
||||||
bool messageHoverHighlight() const { return messageHoverHighlight_; }
|
bool messageHoverHighlight() const { return messageHoverHighlight_; }
|
||||||
|
@ -138,8 +145,11 @@ public:
|
||||||
QString font() const { return font_; }
|
QString font() const { return font_; }
|
||||||
QString emojiFont() const { return emojiFont_; }
|
QString emojiFont() const { return emojiFont_; }
|
||||||
Presence presence() const { return presence_; }
|
Presence presence() const { return presence_; }
|
||||||
|
QString microphone() const { return microphone_; }
|
||||||
|
QString camera() const { return camera_; }
|
||||||
|
QString cameraResolution() const { return cameraResolution_; }
|
||||||
|
QString cameraFrameRate() const { return cameraFrameRate_; }
|
||||||
bool useStunServer() const { return useStunServer_; }
|
bool useStunServer() const { return useStunServer_; }
|
||||||
QString defaultAudioSource() const { return defaultAudioSource_; }
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void groupViewStateChanged(bool state);
|
void groupViewStateChanged(bool state);
|
||||||
|
@ -162,8 +172,11 @@ signals:
|
||||||
void fontChanged(QString state);
|
void fontChanged(QString state);
|
||||||
void emojiFontChanged(QString state);
|
void emojiFontChanged(QString state);
|
||||||
void presenceChanged(Presence state);
|
void presenceChanged(Presence state);
|
||||||
|
void microphoneChanged(QString microphone);
|
||||||
|
void cameraChanged(QString camera);
|
||||||
|
void cameraResolutionChanged(QString resolution);
|
||||||
|
void cameraFrameRateChanged(QString frameRate);
|
||||||
void useStunServerChanged(bool state);
|
void useStunServerChanged(bool state);
|
||||||
void defaultAudioSourceChanged(const QString &deviceName);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
|
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
|
||||||
|
@ -191,8 +204,11 @@ private:
|
||||||
QString font_;
|
QString font_;
|
||||||
QString emojiFont_;
|
QString emojiFont_;
|
||||||
Presence presence_;
|
Presence presence_;
|
||||||
|
QString microphone_;
|
||||||
|
QString camera_;
|
||||||
|
QString cameraResolution_;
|
||||||
|
QString cameraFrameRate_;
|
||||||
bool useStunServer_;
|
bool useStunServer_;
|
||||||
QString defaultAudioSource_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HorizontalLine : public QFrame
|
class HorizontalLine : public QFrame
|
||||||
|
@ -256,6 +272,10 @@ private:
|
||||||
QComboBox *fontSizeCombo_;
|
QComboBox *fontSizeCombo_;
|
||||||
QComboBox *fontSelectionCombo_;
|
QComboBox *fontSelectionCombo_;
|
||||||
QComboBox *emojiFontSelectionCombo_;
|
QComboBox *emojiFontSelectionCombo_;
|
||||||
|
QComboBox *microphoneCombo_;
|
||||||
|
QComboBox *cameraCombo_;
|
||||||
|
QComboBox *cameraResolutionCombo_;
|
||||||
|
QComboBox *cameraFrameRateCombo_;
|
||||||
|
|
||||||
QSpinBox *timelineMaxWidthSpin_;
|
QSpinBox *timelineMaxWidthSpin_;
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,10 +4,13 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include "mtx/events/voip.hpp"
|
#include "mtx/events/voip.hpp"
|
||||||
|
|
||||||
typedef struct _GstElement GstElement;
|
typedef struct _GstElement GstElement;
|
||||||
|
class QQuickItem;
|
||||||
|
class UserSettings;
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
Q_NAMESPACE
|
Q_NAMESPACE
|
||||||
|
@ -39,10 +42,13 @@ public:
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool init(std::string *errorMessage = nullptr);
|
bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
|
||||||
webrtc::State state() const { return state_; }
|
webrtc::State state() const { return state_; }
|
||||||
|
bool isVideo() const { return isVideo_; }
|
||||||
|
bool isOffering() const { return isOffering_; }
|
||||||
|
bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
|
||||||
|
|
||||||
bool createOffer();
|
bool createOffer(bool isVideo);
|
||||||
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> &);
|
||||||
|
@ -51,11 +57,18 @@ public:
|
||||||
bool toggleMicMute();
|
bool toggleMicMute();
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; }
|
void setSettings(QSharedPointer<UserSettings> settings) { settings_ = settings; }
|
||||||
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
|
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
|
||||||
|
|
||||||
std::vector<std::string> getAudioSourceNames(const std::string &defaultDevice);
|
void refreshDevices();
|
||||||
void setAudioSource(int audioDeviceIndex) { audioSourceIndex_ = audioDeviceIndex; }
|
std::vector<std::string> getDeviceNames(bool isVideo,
|
||||||
|
const std::string &defaultDevice) const;
|
||||||
|
std::vector<std::string> getResolutions(const std::string &cameraName) const;
|
||||||
|
std::vector<std::string> getFrameRates(const std::string &cameraName,
|
||||||
|
const std::string &resolution) const;
|
||||||
|
|
||||||
|
void setVideoItem(QQuickItem *item) { videoItem_ = item; }
|
||||||
|
QQuickItem *getVideoItem() const { return videoItem_; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void offerCreated(const std::string &sdp,
|
void offerCreated(const std::string &sdp,
|
||||||
|
@ -71,18 +84,24 @@ private slots:
|
||||||
private:
|
private:
|
||||||
WebRTCSession();
|
WebRTCSession();
|
||||||
|
|
||||||
bool initialised_ = false;
|
bool initialised_ = false;
|
||||||
webrtc::State state_ = webrtc::State::DISCONNECTED;
|
bool haveVoicePlugins_ = false;
|
||||||
GstElement *pipe_ = nullptr;
|
bool haveVideoPlugins_ = false;
|
||||||
GstElement *webrtc_ = nullptr;
|
webrtc::State state_ = webrtc::State::DISCONNECTED;
|
||||||
unsigned int busWatchId_ = 0;
|
bool isVideo_ = false;
|
||||||
std::string stunServer_;
|
bool isOffering_ = false;
|
||||||
|
bool isRemoteVideoRecvOnly_ = false;
|
||||||
|
QQuickItem *videoItem_ = nullptr;
|
||||||
|
GstElement *pipe_ = nullptr;
|
||||||
|
GstElement *webrtc_ = nullptr;
|
||||||
|
unsigned int busWatchId_ = 0;
|
||||||
|
QSharedPointer<UserSettings> settings_;
|
||||||
std::vector<std::string> turnServers_;
|
std::vector<std::string> turnServers_;
|
||||||
int audioSourceIndex_ = -1;
|
|
||||||
|
|
||||||
bool startPipeline(int opusPayloadType);
|
bool init(std::string *errorMessage = nullptr);
|
||||||
bool createPipeline(int opusPayloadType);
|
bool startPipeline(int opusPayloadType, int vp8PayloadType);
|
||||||
void refreshDevices();
|
bool createPipeline(int opusPayloadType, int vp8PayloadType);
|
||||||
|
bool addVideoPipeline(int vp8PayloadType);
|
||||||
void startDeviceMonitor();
|
void startDeviceMonitor();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -19,23 +19,32 @@ AcceptCall::AcceptCall(const QString &caller,
|
||||||
const QString &roomName,
|
const QString &roomName,
|
||||||
const QString &avatarUrl,
|
const QString &avatarUrl,
|
||||||
QSharedPointer<UserSettings> settings,
|
QSharedPointer<UserSettings> settings,
|
||||||
|
bool isVideo,
|
||||||
QWidget *parent)
|
QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
if (!WebRTCSession::instance().init(&errorMessage)) {
|
WebRTCSession *session = &WebRTCSession::instance();
|
||||||
|
if (!session->havePlugins(false, &errorMessage)) {
|
||||||
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
||||||
emit close();
|
emit close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
audioDevices_ = WebRTCSession::instance().getAudioSourceNames(
|
if (isVideo && !session->havePlugins(true, &errorMessage)) {
|
||||||
settings->defaultAudioSource().toStdString());
|
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
||||||
if (audioDevices_.empty()) {
|
|
||||||
emit ChatPage::instance()->showNotification(
|
|
||||||
"Incoming call: No audio sources found.");
|
|
||||||
emit close();
|
emit close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
session->refreshDevices();
|
||||||
|
microphones_ = session->getDeviceNames(false, settings->microphone().toStdString());
|
||||||
|
if (microphones_.empty()) {
|
||||||
|
emit ChatPage::instance()->showNotification(
|
||||||
|
tr("Incoming call: No microphone found."));
|
||||||
|
emit close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isVideo)
|
||||||
|
cameras_ = session->getDeviceNames(true, settings->camera().toStdString());
|
||||||
|
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||||
|
@ -77,9 +86,10 @@ AcceptCall::AcceptCall(const QString &caller,
|
||||||
const int iconSize = 22;
|
const int iconSize = 22;
|
||||||
QLabel *callTypeIndicator = new QLabel(this);
|
QLabel *callTypeIndicator = new QLabel(this);
|
||||||
callTypeIndicator->setPixmap(
|
callTypeIndicator->setPixmap(
|
||||||
QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(iconSize * 2, iconSize * 2)));
|
QIcon(isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png")
|
||||||
|
.pixmap(QSize(iconSize * 2, iconSize * 2)));
|
||||||
|
|
||||||
QLabel *callTypeLabel = new QLabel("Voice Call", this);
|
QLabel *callTypeLabel = new QLabel(isVideo ? tr("Video Call") : tr("Voice Call"), this);
|
||||||
labelFont.setPointSizeF(f.pointSizeF() * 1.1);
|
labelFont.setPointSizeF(f.pointSizeF() * 1.1);
|
||||||
callTypeLabel->setFont(labelFont);
|
callTypeLabel->setFont(labelFont);
|
||||||
callTypeLabel->setAlignment(Qt::AlignCenter);
|
callTypeLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
@ -88,7 +98,8 @@ AcceptCall::AcceptCall(const QString &caller,
|
||||||
buttonLayout->setSpacing(18);
|
buttonLayout->setSpacing(18);
|
||||||
acceptBtn_ = new QPushButton(tr("Accept"), this);
|
acceptBtn_ = new QPushButton(tr("Accept"), this);
|
||||||
acceptBtn_->setDefault(true);
|
acceptBtn_->setDefault(true);
|
||||||
acceptBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png"));
|
acceptBtn_->setIcon(
|
||||||
|
QIcon(isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"));
|
||||||
acceptBtn_->setIconSize(QSize(iconSize, iconSize));
|
acceptBtn_->setIconSize(QSize(iconSize, iconSize));
|
||||||
|
|
||||||
rejectBtn_ = new QPushButton(tr("Reject"), this);
|
rejectBtn_ = new QPushButton(tr("Reject"), this);
|
||||||
|
@ -97,18 +108,17 @@ AcceptCall::AcceptCall(const QString &caller,
|
||||||
buttonLayout->addWidget(acceptBtn_);
|
buttonLayout->addWidget(acceptBtn_);
|
||||||
buttonLayout->addWidget(rejectBtn_);
|
buttonLayout->addWidget(rejectBtn_);
|
||||||
|
|
||||||
auto deviceLayout = new QHBoxLayout;
|
microphoneCombo_ = new QComboBox(this);
|
||||||
auto audioLabel = new QLabel(this);
|
for (const auto &m : microphones_)
|
||||||
audioLabel->setPixmap(
|
microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"),
|
||||||
QIcon(":/icons/icons/ui/microphone-unmute.png").pixmap(QSize(iconSize, iconSize)));
|
QString::fromStdString(m));
|
||||||
|
|
||||||
auto deviceList = new QComboBox(this);
|
if (!cameras_.empty()) {
|
||||||
for (const auto &d : audioDevices_)
|
cameraCombo_ = new QComboBox(this);
|
||||||
deviceList->addItem(QString::fromStdString(d));
|
for (const auto &c : cameras_)
|
||||||
|
cameraCombo_->addItem(QIcon(":/icons/icons/ui/video-call.png"),
|
||||||
deviceLayout->addStretch();
|
QString::fromStdString(c));
|
||||||
deviceLayout->addWidget(audioLabel);
|
}
|
||||||
deviceLayout->addWidget(deviceList);
|
|
||||||
|
|
||||||
if (displayNameLabel)
|
if (displayNameLabel)
|
||||||
layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
|
layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
|
||||||
|
@ -117,12 +127,17 @@ AcceptCall::AcceptCall(const QString &caller,
|
||||||
layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter);
|
layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter);
|
||||||
layout->addWidget(callTypeLabel, 0, Qt::AlignCenter);
|
layout->addWidget(callTypeLabel, 0, Qt::AlignCenter);
|
||||||
layout->addLayout(buttonLayout);
|
layout->addLayout(buttonLayout);
|
||||||
layout->addLayout(deviceLayout);
|
layout->addWidget(microphoneCombo_);
|
||||||
|
if (cameraCombo_)
|
||||||
|
layout->addWidget(cameraCombo_);
|
||||||
|
|
||||||
connect(acceptBtn_, &QPushButton::clicked, this, [this, deviceList, settings]() {
|
connect(acceptBtn_, &QPushButton::clicked, this, [this, settings, session]() {
|
||||||
WebRTCSession::instance().setAudioSource(deviceList->currentIndex());
|
settings->setMicrophone(
|
||||||
settings->setDefaultAudioSource(
|
QString::fromStdString(microphones_[microphoneCombo_->currentIndex()]));
|
||||||
QString::fromStdString(audioDevices_[deviceList->currentIndex()]));
|
if (cameraCombo_) {
|
||||||
|
settings->setCamera(
|
||||||
|
QString::fromStdString(cameras_[cameraCombo_->currentIndex()]));
|
||||||
|
}
|
||||||
emit accept();
|
emit accept();
|
||||||
emit close();
|
emit close();
|
||||||
});
|
});
|
||||||
|
@ -131,4 +146,5 @@ AcceptCall::AcceptCall(const QString &caller,
|
||||||
emit close();
|
emit close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QString;
|
class QString;
|
||||||
class UserSettings;
|
class UserSettings;
|
||||||
|
@ -22,6 +23,7 @@ public:
|
||||||
const QString &roomName,
|
const QString &roomName,
|
||||||
const QString &avatarUrl,
|
const QString &avatarUrl,
|
||||||
QSharedPointer<UserSettings> settings,
|
QSharedPointer<UserSettings> settings,
|
||||||
|
bool isVideo,
|
||||||
QWidget *parent = nullptr);
|
QWidget *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -29,8 +31,12 @@ signals:
|
||||||
void reject();
|
void reject();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPushButton *acceptBtn_;
|
QPushButton *acceptBtn_ = nullptr;
|
||||||
QPushButton *rejectBtn_;
|
QPushButton *rejectBtn_ = nullptr;
|
||||||
std::vector<std::string> audioDevices_;
|
QComboBox *microphoneCombo_ = nullptr;
|
||||||
|
QComboBox *cameraCombo_ = nullptr;
|
||||||
|
std::vector<std::string> microphones_;
|
||||||
|
std::vector<std::string> cameras_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,18 +23,20 @@ PlaceCall::PlaceCall(const QString &callee,
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
if (!WebRTCSession::instance().init(&errorMessage)) {
|
WebRTCSession *session = &WebRTCSession::instance();
|
||||||
|
if (!session->havePlugins(false, &errorMessage)) {
|
||||||
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
|
||||||
emit close();
|
emit close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
audioDevices_ = WebRTCSession::instance().getAudioSourceNames(
|
session->refreshDevices();
|
||||||
settings->defaultAudioSource().toStdString());
|
microphones_ = session->getDeviceNames(false, settings->microphone().toStdString());
|
||||||
if (audioDevices_.empty()) {
|
if (microphones_.empty()) {
|
||||||
emit ChatPage::instance()->showNotification("No audio sources found.");
|
emit ChatPage::instance()->showNotification(tr("No microphone found."));
|
||||||
emit close();
|
emit close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
cameras_ = session->getDeviceNames(true, settings->camera().toStdString());
|
||||||
|
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||||
|
@ -56,48 +58,74 @@ PlaceCall::PlaceCall(const QString &callee,
|
||||||
avatar->setImage(avatarUrl);
|
avatar->setImage(avatarUrl);
|
||||||
else
|
else
|
||||||
avatar->setLetter(utils::firstChar(roomName));
|
avatar->setLetter(utils::firstChar(roomName));
|
||||||
const int iconSize = 18;
|
|
||||||
voiceBtn_ = new QPushButton(tr("Voice"), this);
|
voiceBtn_ = new QPushButton(tr("Voice"), this);
|
||||||
voiceBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png"));
|
voiceBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png"));
|
||||||
voiceBtn_->setIconSize(QSize(iconSize, iconSize));
|
voiceBtn_->setIconSize(QSize(iconSize_, iconSize_));
|
||||||
voiceBtn_->setDefault(true);
|
voiceBtn_->setDefault(true);
|
||||||
|
|
||||||
|
if (!cameras_.empty()) {
|
||||||
|
videoBtn_ = new QPushButton(tr("Video"), this);
|
||||||
|
videoBtn_->setIcon(QIcon(":/icons/icons/ui/video-call.png"));
|
||||||
|
videoBtn_->setIconSize(QSize(iconSize_, iconSize_));
|
||||||
|
}
|
||||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||||
|
|
||||||
buttonLayout->addWidget(avatar);
|
buttonLayout->addWidget(avatar);
|
||||||
buttonLayout->addStretch();
|
buttonLayout->addStretch();
|
||||||
buttonLayout->addWidget(voiceBtn_);
|
buttonLayout->addWidget(voiceBtn_);
|
||||||
|
if (videoBtn_)
|
||||||
|
buttonLayout->addWidget(videoBtn_);
|
||||||
buttonLayout->addWidget(cancelBtn_);
|
buttonLayout->addWidget(cancelBtn_);
|
||||||
|
|
||||||
QString name = displayName.isEmpty() ? callee : displayName;
|
QString name = displayName.isEmpty() ? callee : displayName;
|
||||||
QLabel *label = new QLabel("Place a call to " + name + "?", this);
|
QLabel *label = new QLabel(tr("Place a call to ") + name + "?", this);
|
||||||
|
|
||||||
auto deviceLayout = new QHBoxLayout;
|
microphoneCombo_ = new QComboBox(this);
|
||||||
auto audioLabel = new QLabel(this);
|
for (const auto &m : microphones_)
|
||||||
audioLabel->setPixmap(QIcon(":/icons/icons/ui/microphone-unmute.png")
|
microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"),
|
||||||
.pixmap(QSize(iconSize * 1.2, iconSize * 1.2)));
|
QString::fromStdString(m));
|
||||||
|
|
||||||
auto deviceList = new QComboBox(this);
|
if (videoBtn_) {
|
||||||
for (const auto &d : audioDevices_)
|
cameraCombo_ = new QComboBox(this);
|
||||||
deviceList->addItem(QString::fromStdString(d));
|
for (const auto &c : cameras_)
|
||||||
|
cameraCombo_->addItem(QIcon(":/icons/icons/ui/video-call.png"),
|
||||||
deviceLayout->addStretch();
|
QString::fromStdString(c));
|
||||||
deviceLayout->addWidget(audioLabel);
|
}
|
||||||
deviceLayout->addWidget(deviceList);
|
|
||||||
|
|
||||||
layout->addWidget(label);
|
layout->addWidget(label);
|
||||||
layout->addLayout(buttonLayout);
|
layout->addLayout(buttonLayout);
|
||||||
layout->addLayout(deviceLayout);
|
layout->addStretch();
|
||||||
|
layout->addWidget(microphoneCombo_);
|
||||||
|
if (videoBtn_)
|
||||||
|
layout->addWidget(cameraCombo_);
|
||||||
|
|
||||||
connect(voiceBtn_, &QPushButton::clicked, this, [this, deviceList, settings]() {
|
connect(voiceBtn_, &QPushButton::clicked, this, [this, settings, session]() {
|
||||||
WebRTCSession::instance().setAudioSource(deviceList->currentIndex());
|
settings->setMicrophone(
|
||||||
settings->setDefaultAudioSource(
|
QString::fromStdString(microphones_[microphoneCombo_->currentIndex()]));
|
||||||
QString::fromStdString(audioDevices_[deviceList->currentIndex()]));
|
|
||||||
emit voice();
|
emit voice();
|
||||||
emit close();
|
emit close();
|
||||||
});
|
});
|
||||||
|
if (videoBtn_)
|
||||||
|
connect(videoBtn_, &QPushButton::clicked, this, [this, settings, session]() {
|
||||||
|
std::string error;
|
||||||
|
if (!session->havePlugins(true, &error)) {
|
||||||
|
emit ChatPage::instance()->showNotification(
|
||||||
|
QString::fromStdString(error));
|
||||||
|
emit close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings->setMicrophone(
|
||||||
|
QString::fromStdString(microphones_[microphoneCombo_->currentIndex()]));
|
||||||
|
settings->setCamera(
|
||||||
|
QString::fromStdString(cameras_[cameraCombo_->currentIndex()]));
|
||||||
|
emit video();
|
||||||
|
emit close();
|
||||||
|
});
|
||||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||||
emit cancel();
|
emit cancel();
|
||||||
emit close();
|
emit close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QString;
|
class QString;
|
||||||
class UserSettings;
|
class UserSettings;
|
||||||
|
@ -26,11 +27,18 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void voice();
|
void voice();
|
||||||
|
void video();
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPushButton *voiceBtn_;
|
const int iconSize_ = 18;
|
||||||
QPushButton *cancelBtn_;
|
QPushButton *voiceBtn_ = nullptr;
|
||||||
std::vector<std::string> audioDevices_;
|
QPushButton *videoBtn_ = nullptr;
|
||||||
|
QPushButton *cancelBtn_ = nullptr;
|
||||||
|
QComboBox *microphoneCombo_ = nullptr;
|
||||||
|
QComboBox *cameraCombo_ = nullptr;
|
||||||
|
std::vector<std::string> microphones_;
|
||||||
|
std::vector<std::string> cameras_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,6 +242,17 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
|
||||||
&TimelineViewManager::callStateChanged);
|
&TimelineViewManager::callStateChanged);
|
||||||
connect(
|
connect(
|
||||||
callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged);
|
callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged);
|
||||||
|
connect(callManager_,
|
||||||
|
&CallManager::newVideoCallState,
|
||||||
|
this,
|
||||||
|
&TimelineViewManager::videoCallChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineViewManager::setVideoCallItem()
|
||||||
|
{
|
||||||
|
WebRTCSession::instance().setVideoItem(
|
||||||
|
view->rootObject()->findChild<QQuickItem *>("videoCallItem"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -36,6 +36,7 @@ class TimelineViewManager : public QObject
|
||||||
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(webrtc::State callState READ callState NOTIFY callStateChanged)
|
||||||
|
Q_PROPERTY(bool onVideoCall READ onVideoCall NOTIFY videoCallChanged)
|
||||||
Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged)
|
Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged)
|
||||||
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged)
|
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged)
|
||||||
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
|
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
|
||||||
|
@ -55,6 +56,8 @@ public:
|
||||||
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(); }
|
webrtc::State callState() const { return WebRTCSession::instance().state(); }
|
||||||
|
bool onVideoCall() const { return WebRTCSession::instance().isVideo(); }
|
||||||
|
Q_INVOKABLE void setVideoCallItem();
|
||||||
QString callPartyName() const { return callManager_->callPartyName(); }
|
QString callPartyName() const { return callManager_->callPartyName(); }
|
||||||
QString callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); }
|
QString callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); }
|
||||||
bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); }
|
bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); }
|
||||||
|
@ -89,6 +92,7 @@ signals:
|
||||||
void showRoomList();
|
void showRoomList();
|
||||||
void narrowViewChanged();
|
void narrowViewChanged();
|
||||||
void callStateChanged(webrtc::State);
|
void callStateChanged(webrtc::State);
|
||||||
|
void videoCallChanged();
|
||||||
void callPartyChanged();
|
void callPartyChanged();
|
||||||
void micMuteChanged();
|
void micMuteChanged();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue