Merge pull request #311 from trilene/webrtc-video

Support video calls
pull/312/head
DeepBlueV7.X 4 years ago committed by GitHub
commit b64e6e9cd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. BIN
      resources/icons/ui/video-call.png
  2. 12
      resources/qml/ActiveCallBar.qml
  3. 25
      resources/qml/TimelineView.qml
  4. 7
      resources/qml/VideoCall.qml
  5. 2
      resources/res.qrc
  6. 44
      src/CallManager.cpp
  7. 10
      src/CallManager.h
  8. 9
      src/ChatPage.cpp
  9. 115
      src/UserSettingsPage.cpp
  10. 32
      src/UserSettingsPage.h
  11. 821
      src/WebRTCSession.cpp
  12. 45
      src/WebRTCSession.h
  13. 70
      src/dialogs/AcceptCall.cpp
  14. 15
      src/dialogs/AcceptCall.h
  15. 78
      src/dialogs/PlaceCall.cpp
  16. 14
      src/dialogs/PlaceCall.h
  17. 11
      src/timeline/TimelineViewManager.cpp
  18. 4
      src/timeline/TimelineViewManager.h

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

@ -10,6 +10,12 @@ Rectangle {
color: "#2ECC71"
implicitHeight: visible ? rowLayout.height + 8 : 0
MouseArea {
anchors.fill: parent
onClicked: if (TimelineManager.onVideoCall)
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
}
RowLayout {
id: rowLayout
@ -33,7 +39,8 @@ Rectangle {
Image {
Layout.preferredWidth: 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 {
@ -58,9 +65,12 @@ Rectangle {
callStateLabel.text = "00:00";
var d = new Date();
callTimer.startTime = Math.floor(d.getTime() / 1000);
if (TimelineManager.onVideoCall)
stackLayout.currentIndex = 1;
break;
case WebRTCState.DISCONNECTED:
callStateLabel.text = "";
stackLayout.currentIndex = 0;
}
}

@ -4,7 +4,7 @@ import "./emoji"
import QtGraphicalEffects 1.0
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
@ -190,9 +190,26 @@ Page {
anchors.fill: parent
spacing: 0
MessageView {
Layout.fillWidth: true
Layout.fillHeight: true
StackLayout {
id: stackLayout
currentIndex: 0
Connections {
target: TimelineManager
function onActiveTimelineChanged() {
stackLayout.currentIndex = 0;
}
}
MessageView {
Layout.fillWidth: true
Layout.fillHeight: true
}
Loader {
source: TimelineManager.onVideoCall ? "VideoCall.qml" : ""
onLoaded: TimelineManager.setVideoCallItem()
}
}
TypingIndicator {

@ -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/microphone-mute.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@2x.png</file>
@ -135,6 +136,7 @@
<file>qml/Reactions.qml</file>
<file>qml/ScrollHelper.qml</file>
<file>qml/TimelineRow.qml</file>
<file>qml/VideoCall.qml</file>
<file>qml/emoji/EmojiButton.qml</file>
<file>qml/emoji/EmojiPicker.qml</file>
<file>qml/UserProfile.qml</file>

@ -12,7 +12,6 @@
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "UserSettingsPage.h"
#include "Utils.h"
#include "WebRTCSession.h"
#include "dialogs/AcceptCall.h"
@ -26,19 +25,15 @@ Q_DECLARE_METATYPE(mtx::responses::TurnServer)
using namespace mtx::events;
using namespace mtx::events::msg;
// https://github.com/vector-im/riot-web/issues/10173
#define STUN_SERVER "stun://turn.matrix.org:3478"
namespace {
std::vector<std::string>
getTurnURIs(const mtx::responses::TurnServer &turnServer);
}
CallManager::CallManager(QSharedPointer<UserSettings> userSettings, QObject *parent)
CallManager::CallManager(QObject *parent)
: QObject(parent)
, session_(WebRTCSession::instance())
, turnServerTimer_(this)
, settings_(userSettings)
{
qRegisterMetaType<std::vector<mtx::events::msg::CallCandidates::Candidate>>();
qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
@ -129,30 +124,29 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings, QObject *par
}
void
CallManager::sendInvite(const QString &roomid)
CallManager::sendInvite(const QString &roomid, bool isVideo)
{
if (onActiveCall())
return;
auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
if (roomInfo.member_count != 2) {
emit ChatPage::instance()->showNotification(
"Voice calls are limited to 1:1 rooms.");
emit ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms.");
return;
}
std::string errorMessage;
if (!session_.init(&errorMessage)) {
if (!session_.havePlugins(false, &errorMessage) ||
(isVideo && !session_.havePlugins(true, &errorMessage))) {
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
return;
}
roomid_ = roomid;
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
session_.setTurnServers(turnURIs_);
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()));
const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() : members.front();
@ -160,10 +154,12 @@ CallManager::sendInvite(const QString &roomid)
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
emit newCallParty();
playRingtone("qrc:/media/media/ringback.ogg", true);
if (!session_.createOffer()) {
if (!session_.createOffer(isVideo)) {
emit ChatPage::instance()->showNotification("Problem setting up call.");
endCall();
}
if (isVideo)
emit newVideoCallState();
}
namespace {
@ -243,7 +239,7 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
return;
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),
CallHangUp{callInviteEvent.content.call_id,
0,
@ -266,11 +262,11 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
caller.display_name,
QString::fromStdString(roomInfo.name),
QString::fromStdString(roomInfo.avatar_url),
settings_,
isVideo,
MainWindow::instance());
connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent]() {
connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent, isVideo]() {
MainWindow::instance()->hideOverlay();
answerInvite(callInviteEvent.content);
answerInvite(callInviteEvent.content, isVideo);
});
connect(dialog, &dialogs::AcceptCall::reject, this, [this]() {
MainWindow::instance()->hideOverlay();
@ -280,19 +276,18 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
}
void
CallManager::answerInvite(const CallInvite &invite)
CallManager::answerInvite(const CallInvite &invite, bool isVideo)
{
stopRingtone();
std::string errorMessage;
if (!session_.init(&errorMessage)) {
if (!session_.havePlugins(false, &errorMessage) ||
(isVideo && !session_.havePlugins(true, &errorMessage))) {
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
hangUp();
return;
}
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
session_.setTurnServers(turnURIs_);
if (!session_.acceptOffer(invite.sdp)) {
emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp();
@ -300,6 +295,8 @@ CallManager::answerInvite(const CallInvite &invite)
}
session_.acceptICECandidates(remoteICECandidates_);
remoteICECandidates_.clear();
if (isVideo)
emit newVideoCallState();
}
void
@ -385,7 +382,10 @@ CallManager::endCall()
{
stopRingtone();
clear();
bool isVideo = session_.isVideo();
session_.end();
if (isVideo)
emit newVideoCallState();
}
void

@ -5,7 +5,6 @@
#include <QMediaPlayer>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include <QTimer>
@ -16,7 +15,6 @@ namespace mtx::responses {
struct TurnServer;
}
class UserSettings;
class WebRTCSession;
class CallManager : public QObject
@ -24,9 +22,9 @@ class CallManager : public QObject
Q_OBJECT
public:
CallManager(QSharedPointer<UserSettings>, QObject *);
CallManager(QObject *);
void sendInvite(const QString &roomid);
void sendInvite(const QString &roomid, bool isVideo);
void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
bool onActiveCall() const;
@ -43,6 +41,7 @@ signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
void newCallParty();
void newVideoCallState();
void turnServerRetrieved(const mtx::responses::TurnServer &);
private slots:
@ -58,7 +57,6 @@ private:
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
std::vector<std::string> turnURIs_;
QTimer turnServerTimer_;
QSharedPointer<UserSettings> settings_;
QMediaPlayer player_;
template<typename T>
@ -67,7 +65,7 @@ private:
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::CallHangUp> &);
void answerInvite(const mtx::events::msg::CallInvite &);
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
void generateCallID();
void clear();
void endCall();

@ -72,7 +72,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
, isConnected_(true)
, userSettings_{userSettings}
, notificationsManager(this)
, callManager_(new CallManager(userSettings, this))
, callManager_(new CallManager(this))
{
setObjectName("chatPage");
@ -442,7 +442,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
} else {
if (auto roomInfo = cache::singleRoomInfo(current_room_.toStdString());
roomInfo.member_count != 2) {
showNotification("Voice calls are limited to 1:1 rooms.");
showNotification("Calls are limited to 1:1 rooms.");
} else {
std::vector<RoomMember> members(
cache::getMembers(current_room_.toStdString()));
@ -457,7 +457,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
userSettings_,
MainWindow::instance());
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());
dialog->show();

@ -44,6 +44,7 @@
#include "Olm.h"
#include "UserSettingsPage.h"
#include "Utils.h"
#include "WebRTCSession.h"
#include "ui/FlatButton.h"
#include "ui/ToggleButton.h"
@ -82,8 +83,11 @@ UserSettings::load()
presence_ =
settings.value("user/presence", QVariant::fromValue(Presence::AutomaticPresence))
.value<Presence>();
useStunServer_ = settings.value("user/use_stun_server", false).toBool();
defaultAudioSource_ = settings.value("user/default_audio_source", QString()).toString();
microphone_ = settings.value("user/microphone", 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();
}
@ -318,12 +322,42 @@ UserSettings::setShareKeysWithTrustedUsers(bool shareKeys)
}
void
UserSettings::setDefaultAudioSource(const QString &defaultAudioSource)
UserSettings::setMicrophone(QString microphone)
{
if (defaultAudioSource == defaultAudioSource_)
if (microphone == microphone_)
return;
defaultAudioSource_ = defaultAudioSource;
emit defaultAudioSourceChanged(defaultAudioSource);
microphone_ = microphone;
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();
}
@ -416,8 +450,11 @@ UserSettings::save()
settings.setValue("font_family", font_);
settings.setValue("emoji_font_family", emojiFont_);
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("default_audio_source", defaultAudioSource_);
settings.endGroup();
@ -493,6 +530,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
fontSizeCombo_ = new QComboBox{this};
fontSelectionCombo_ = new QFontComboBox{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};
if (!settings_->tray())
@ -679,6 +720,14 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
formLayout_->addRow(callsLabel);
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"),
useStunServer_,
tr("Will use turn.matrix.org as assist when your home server does not offer one."));
@ -736,6 +785,38 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
connect(emojiFontSelectionCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
connect(microphoneCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &microphone) { 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) {
settings_->setTray(!disabled);
if (disabled) {
@ -855,6 +936,26 @@ UserSettingsPage::showEvent(QShowEvent *)
enlargeEmojiOnlyMessages_->setState(!settings_->enlargeEmojiOnlyMessages());
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
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());
deviceFingerprintValue_->setText(

@ -73,10 +73,14 @@ class UserSettings : public QObject
Q_PROPERTY(
QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
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)
Q_PROPERTY(
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
Q_PROPERTY(QString defaultAudioSource READ defaultAudioSource WRITE setDefaultAudioSource
NOTIFY defaultAudioSourceChanged)
Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
@ -116,8 +120,11 @@ public:
void setAvatarCircles(bool state);
void setDecryptSidebar(bool 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 setDefaultAudioSource(const QString &deviceName);
void setShareKeysWithTrustedUsers(bool state);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
@ -145,8 +152,11 @@ public:
QString font() const { return font_; }
QString emojiFont() const { return emojiFont_; }
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_; }
QString defaultAudioSource() const { return defaultAudioSource_; }
bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
signals:
@ -171,8 +181,11 @@ signals:
void fontChanged(QString state);
void emojiFontChanged(QString 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 defaultAudioSourceChanged(const QString &deviceName);
void shareKeysWithTrustedUsersChanged(bool state);
private:
@ -203,8 +216,11 @@ private:
QString font_;
QString emojiFont_;
Presence presence_;
QString microphone_;
QString camera_;
QString cameraResolution_;
QString cameraFrameRate_;
bool useStunServer_;
QString defaultAudioSource_;
};
class HorizontalLine : public QFrame
@ -270,6 +286,10 @@ private:
QComboBox *fontSizeCombo_;
QFontComboBox *fontSelectionCombo_;
QComboBox *emojiFontSelectionCombo_;
QComboBox *microphoneCombo_;
QComboBox *cameraCombo_;
QComboBox *cameraResolutionCombo_;
QComboBox *cameraFrameRateCombo_;
QSpinBox *timelineMaxWidthSpin_;

File diff suppressed because it is too large Load Diff

@ -8,6 +8,7 @@
#include "mtx/events/voip.hpp"
typedef struct _GstElement GstElement;
class QQuickItem;
namespace webrtc {
Q_NAMESPACE
@ -39,10 +40,13 @@ public:
return instance;
}
bool init(std::string *errorMessage = nullptr);
bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
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 acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
@ -51,11 +55,17 @@ public:
bool toggleMicMute();
void end();
void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; }
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
std::vector<std::string> getAudioSourceNames(const std::string &defaultDevice);
void setAudioSource(int audioDeviceIndex) { audioSourceIndex_ = audioDeviceIndex; }
void refreshDevices();
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:
void offerCreated(const std::string &sdp,
@ -71,18 +81,23 @@ private slots:
private:
WebRTCSession();
bool initialised_ = false;
webrtc::State state_ = webrtc::State::DISCONNECTED;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0;
std::string stunServer_;
bool initialised_ = false;
bool haveVoicePlugins_ = false;
bool haveVideoPlugins_ = false;
webrtc::State state_ = webrtc::State::DISCONNECTED;
bool isVideo_ = false;
bool isOffering_ = false;
bool isRemoteVideoRecvOnly_ = false;
QQuickItem *videoItem_ = nullptr;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0;
std::vector<std::string> turnServers_;
int audioSourceIndex_ = -1;
bool startPipeline(int opusPayloadType);
bool createPipeline(int opusPayloadType);
void refreshDevices();
bool init(std::string *errorMessage = nullptr);
bool startPipeline(int opusPayloadType, int vp8PayloadType);
bool createPipeline(int opusPayloadType, int vp8PayloadType);
bool addVideoPipeline(int vp8PayloadType);
void startDeviceMonitor();
public:

@ -18,24 +18,35 @@ AcceptCall::AcceptCall(const QString &caller,
const QString &displayName,
const QString &roomName,
const QString &avatarUrl,
QSharedPointer<UserSettings> settings,
bool isVideo,
QWidget *parent)
: QWidget(parent)
{
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 close();
return;
}
audioDevices_ = WebRTCSession::instance().getAudioSourceNames(
settings->defaultAudioSource().toStdString());
if (audioDevices_.empty()) {
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(
"Incoming call: No audio sources found.");
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);
@ -77,9 +88,10 @@ AcceptCall::AcceptCall(const QString &caller,
const int iconSize = 22;
QLabel *callTypeIndicator = new QLabel(this);
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);
callTypeLabel->setFont(labelFont);
callTypeLabel->setAlignment(Qt::AlignCenter);
@ -88,7 +100,8 @@ AcceptCall::AcceptCall(const QString &caller,
buttonLayout->setSpacing(18);
acceptBtn_ = new QPushButton(tr("Accept"), this);
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));
rejectBtn_ = new QPushButton(tr("Reject"), this);
@ -97,18 +110,17 @@ AcceptCall::AcceptCall(const QString &caller,
buttonLayout->addWidget(acceptBtn_);
buttonLayout->addWidget(rejectBtn_);
auto deviceLayout = new QHBoxLayout;
auto audioLabel = new QLabel(this);
audioLabel->setPixmap(
QIcon(":/icons/icons/ui/microphone-unmute.png").pixmap(QSize(iconSize, iconSize)));
microphoneCombo_ = new QComboBox(this);
for (const auto &m : microphones_)
microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"),
QString::fromStdString(m));
auto deviceList = new QComboBox(this);
for (const auto &d : audioDevices_)
deviceList->addItem(QString::fromStdString(d));
deviceLayout->addStretch();
deviceLayout->addWidget(audioLabel);
deviceLayout->addWidget(deviceList);
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);
@ -117,12 +129,17 @@ AcceptCall::AcceptCall(const QString &caller,
layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter);
layout->addWidget(callTypeLabel, 0, Qt::AlignCenter);
layout->addLayout(buttonLayout);
layout->addLayout(deviceLayout);
connect(acceptBtn_, &QPushButton::clicked, this, [this, deviceList, settings]() {
WebRTCSession::instance().setAudioSource(deviceList->currentIndex());
settings->setDefaultAudioSource(
QString::fromStdString(audioDevices_[deviceList->currentIndex()]));
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();
});
@ -131,4 +148,5 @@ AcceptCall::AcceptCall(const QString &caller,
emit close();
});
}
}

@ -3,12 +3,11 @@
#include <string>
#include <vector>
#include <QSharedPointer>
#include <QWidget>
class QComboBox;
class QPushButton;
class QString;
class UserSettings;
namespace dialogs {
@ -21,7 +20,7 @@ public:
const QString &displayName,
const QString &roomName,
const QString &avatarUrl,
QSharedPointer<UserSettings> settings,
bool isVideo,
QWidget *parent = nullptr);
signals:
@ -29,8 +28,12 @@ signals:
void reject();
private:
QPushButton *acceptBtn_;
QPushButton *rejectBtn_;
std::vector<std::string> audioDevices_;
QPushButton *acceptBtn_ = nullptr;
QPushButton *rejectBtn_ = nullptr;
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)
{
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 close();
return;
}
audioDevices_ = WebRTCSession::instance().getAudioSourceNames(
settings->defaultAudioSource().toStdString());
if (audioDevices_.empty()) {
emit ChatPage::instance()->showNotification("No audio sources found.");
session->refreshDevices();
microphones_ = session->getDeviceNames(false, settings->microphone().toStdString());
if (microphones_.empty()) {
emit ChatPage::instance()->showNotification(tr("No microphone found."));
emit close();
return;
}
cameras_ = session->getDeviceNames(true, settings->camera().toStdString());
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
@ -56,48 +58,74 @@ PlaceCall::PlaceCall(const QString &callee,
avatar->setImage(avatarUrl);
else
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_->setIconSize(QSize(iconSize, iconSize));
voiceBtn_->setIconSize(QSize(iconSize_, iconSize_));
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);
buttonLayout->addWidget(avatar);
buttonLayout->addStretch();
buttonLayout->addWidget(voiceBtn_);
if (videoBtn_)
buttonLayout->addWidget(videoBtn_);
buttonLayout->addWidget(cancelBtn_);
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;
auto audioLabel = new QLabel(this);
audioLabel->setPixmap(QIcon(":/icons/icons/ui/microphone-unmute.png")
.pixmap(QSize(iconSize * 1.2, iconSize * 1.2)));
microphoneCombo_ = new QComboBox(this);
for (const auto &m : microphones_)
microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"),
QString::fromStdString(m));
auto deviceList = new QComboBox(this);
for (const auto &d : audioDevices_)
deviceList->addItem(QString::fromStdString(d));
deviceLayout->addStretch();
deviceLayout->addWidget(audioLabel);
deviceLayout->addWidget(deviceList);
if (videoBtn_) {
cameraCombo_ = new QComboBox(this);
for (const auto &c : cameras_)
cameraCombo_->addItem(QIcon(":/icons/icons/ui/video-call.png"),
QString::fromStdString(c));
}
layout->addWidget(label);
layout->addLayout(buttonLayout);
layout->addLayout(deviceLayout);
layout->addStretch();
layout->addWidget(microphoneCombo_);
if (videoBtn_)
layout->addWidget(cameraCombo_);
connect(voiceBtn_, &QPushButton::clicked, this, [this, deviceList, settings]() {
WebRTCSession::instance().setAudioSource(deviceList->currentIndex());
settings->setDefaultAudioSource(
QString::fromStdString(audioDevices_[deviceList->currentIndex()]));
connect(voiceBtn_, &QPushButton::clicked, this, [this, settings]() {
settings->setMicrophone(
QString::fromStdString(microphones_[microphoneCombo_->currentIndex()]));
emit voice();
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]() {
emit cancel();
emit close();
});
}
}

@ -6,6 +6,7 @@
#include <QSharedPointer>
#include <QWidget>
class QComboBox;
class QPushButton;
class QString;
class UserSettings;
@ -26,11 +27,18 @@ public:
signals:
void voice();
void video();
void cancel();
private:
QPushButton *voiceBtn_;
QPushButton *cancelBtn_;
std::vector<std::string> audioDevices_;
const int iconSize_ = 18;
QPushButton *voiceBtn_ = nullptr;
QPushButton *videoBtn_ = nullptr;
QPushButton *cancelBtn_ = nullptr;
QComboBox *microphoneCombo_ = nullptr;
QComboBox *cameraCombo_ = nullptr;
std::vector<std::string> microphones_;
std::vector<std::string> cameras_;
};
}

@ -240,6 +240,17 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
&TimelineViewManager::callStateChanged);
connect(
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

@ -37,6 +37,7 @@ class TimelineViewManager : public QObject
Q_PROPERTY(
bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged)
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 callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged)
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
@ -54,6 +55,8 @@ public:
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isNarrowView() const { return isNarrowView_; }
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 callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); }
bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); }
@ -88,6 +91,7 @@ signals:
void showRoomList();
void narrowViewChanged();
void callStateChanged(webrtc::State);
void videoCallChanged();
void callPartyChanged();
void micMuteChanged();

Loading…
Cancel
Save