VoIP v1 implementation (#1161)

* Initial commit for VoIP v1 implementation

* Added draft of event handlers for voip methods

* Added event handlers for VoIP events, added rejectCall, added version tracking for call version for V0 and V1 compatibility

* Added call events to the general message pipeline. Modified Call Reject mechanism

* Added message delegates for new events. Modified hidden events. Updated handle events.

* Updated implementation to keep track of calls on other devices

* Fixed linting

* Fixed code warnings

* Fixed minor bugs

* fixed ci

* Added acceptNegotiation method definition when missing gstreamer

* Fixed warnings

* Fixed linting
This commit is contained in:
Rohit Sutradhar 2022-10-14 19:19:05 +05:30 committed by GitHub
parent 8a4bb32b4a
commit ac48c33286
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 583 additions and 79 deletions

View file

@ -46,14 +46,14 @@ Rectangle {
ImageButton { ImageButton {
visible: CallManager.callsSupported && showAllButtons visible: CallManager.callsSupported && showAllButtons
opacity: CallManager.haveCallInvite ? 0.3 : 1 opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
hoverEnabled: true hoverEnabled: true
width: 22 width: 22
height: 22 height: 22
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call"))
Layout.margins: 8 Layout.margins: 8
onClicked: { onClicked: {
if (room) { if (room) {
@ -61,7 +61,11 @@ Rectangle {
return ; return ;
} else if (CallManager.isOnCall) { } else if (CallManager.isOnCall) {
CallManager.hangUp(); CallManager.hangUp();
} else { }
else if(CallManager.isOnCallOnOtherDevice) {
return;
}
else {
var dialog = placeCallDialog.createObject(timelineRoot); var dialog = placeCallDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);

View file

@ -378,6 +378,34 @@ Item {
} }
DelegateChoice {
roleValue: MtxEvent.CallReject
NoticeMessage {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
keepFullText: d.keepFullText
isStateEvent: d.isStateEvent
formatted: qsTr("%1 rejected the call.").arg(d.userName)
}
}
DelegateChoice {
roleValue: MtxEvent.CallSelectAnswer
NoticeMessage {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
keepFullText: d.keepFullText
isStateEvent: d.isStateEvent
formatted: qsTr("%1 select answer").arg(d.userName)
// formatted: qsTr("Call answered elsewhere")
}
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallHangUp roleValue: MtxEvent.CallHangUp
@ -406,6 +434,20 @@ Item {
} }
DelegateChoice {
roleValue: MtxEvent.CallNegotiate
NoticeMessage {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
keepFullText: d.keepFullText
isStateEvent: d.isStateEvent
formatted: qsTr("%1 is negotiating the call...").arg(d.userName)
}
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.PowerLevels roleValue: MtxEvent.PowerLevels

View file

@ -7,6 +7,7 @@ import Qt.labs.platform 1.1 as P
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import im.nheko 1.0 import im.nheko 1.0
import "../voip"
P.MessageDialog { P.MessageDialog {
id: leaveRoomRoot id: leaveRoomRoot
@ -18,5 +19,14 @@ P.MessageDialog {
text: qsTr("Are you sure you want to leave?") text: qsTr("Are you sure you want to leave?")
modality: Qt.ApplicationModal modality: Qt.ApplicationModal
buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
onAccepted: Rooms.leave(roomId, reason) onAccepted: {
if (CallManager.haveCallInvite) {
callManager.rejectInvite();
} else if (CallManager.isOnCall) {
CallManager.hangUp();
}
Rooms.leave(roomId, reason)
}
} }

View file

@ -153,7 +153,7 @@ Popup {
implicitWidth: buttonLayout.buttonSize implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize
onClicked: { onClicked: {
CallManager.hangUp(); CallManager.rejectInvite();
close(); close();
} }

View file

@ -129,7 +129,7 @@ Rectangle {
text: qsTr("Decline") text: qsTr("Decline")
palette: Nheko.colors palette: Nheko.colors
onClicked: { onClicked: {
CallManager.hangUp(); CallManager.rejectInvite();
} }
} }

View file

@ -184,8 +184,18 @@ Cache::isHiddenEvent(lmdb::txn &txn,
hiddenEvents.hidden_event_types = std::vector{ hiddenEvents.hidden_event_types = std::vector{
EventType::Reaction, EventType::Reaction,
EventType::CallCandidates, EventType::CallCandidates,
EventType::CallNegotiate,
EventType::Unsupported, EventType::Unsupported,
}; };
// check if selected answer is from to local user
/*
* localUser accepts/rejects the call and it is selected by caller - No message
* Another User accepts/rejects the call and it is selected by caller - "Call answered/rejected
* elsewhere"
*/
bool callLocalUser_ = true;
if (callLocalUser_)
hiddenEvents.hidden_event_types->push_back(EventType::CallSelectAnswer);
if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) { if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) {
auto h = std::get< auto h = std::get<
@ -1661,11 +1671,18 @@ isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallAnswer> &)
{ {
return true; return true;
} }
auto auto
isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &) isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &)
{ {
return true; return true;
} }
// auto
// isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallReject> &)
// {
// return true;
// }
} }
void void

View file

@ -363,6 +363,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
connectCallMessage<mtx::events::voip::CallCandidates>(); connectCallMessage<mtx::events::voip::CallCandidates>();
connectCallMessage<mtx::events::voip::CallAnswer>(); connectCallMessage<mtx::events::voip::CallAnswer>();
connectCallMessage<mtx::events::voip::CallHangUp>(); connectCallMessage<mtx::events::voip::CallHangUp>();
connectCallMessage<mtx::events::voip::CallSelectAnswer>();
connectCallMessage<mtx::events::voip::CallReject>();
connectCallMessage<mtx::events::voip::CallNegotiate>();
} }
void void

View file

@ -222,6 +222,8 @@ PowerlevelsTypeListModel::data(const QModelIndex &index, int role) const
return tr("Answer a call"); return tr("Answer a call");
else if (type.type == "m.call.hangup") else if (type.type == "m.call.hangup")
return tr("Hang up a call"); return tr("Hang up a call");
else if (type.type == "m.call.reject")
return tr("Reject a call");
else if (type.type == "im.ponies.room_emotes") else if (type.type == "im.ponies.room_emotes")
return tr("Change the room emotes"); return tr("Change the room emotes");
return QString::fromStdString(type.type); return QString::fromStdString(type.type);

View file

@ -219,6 +219,7 @@ utils::getMessageDescription(const TimelineEvent &event,
using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>;
using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>;
using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
if (std::holds_alternative<Audio>(event)) { if (std::holds_alternative<Audio>(event)) {
@ -241,6 +242,8 @@ utils::getMessageDescription(const TimelineEvent &event,
return createDescriptionInfo<CallAnswer>(event, localUser, displayName); return createDescriptionInfo<CallAnswer>(event, localUser, displayName);
} else if (std::holds_alternative<CallHangUp>(event)) { } else if (std::holds_alternative<CallHangUp>(event)) {
return createDescriptionInfo<CallHangUp>(event, localUser, displayName); return createDescriptionInfo<CallHangUp>(event, localUser, displayName);
} else if (std::holds_alternative<CallReject>(event)) {
return createDescriptionInfo<CallReject>(event, localUser, displayName);
} else if (std::holds_alternative<mtx::events::Sticker>(event)) { } else if (std::holds_alternative<mtx::events::Sticker>(event)) {
return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName); return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName);
} else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) { } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) {

View file

@ -110,6 +110,7 @@ messageDescription(const QString &username = QString(),
using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>; using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>; using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>;
using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>; using CallHangUp = mtx::events::RoomEvent<mtx::events::voip::CallHangUp>;
using CallReject = mtx::events::RoomEvent<mtx::events::voip::CallReject>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
if (std::is_same<T, Audio>::value) { if (std::is_same<T, Audio>::value) {
@ -185,6 +186,12 @@ messageDescription(const QString &username = QString(),
else else
return QCoreApplication::translate("message-description sent:", "%1 ended a call") return QCoreApplication::translate("message-description sent:", "%1 ended a call")
.arg(username); .arg(username);
} else if (std::is_same<T, CallReject>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You rejected a call");
else
return QCoreApplication::translate("message-description sent:", "%1 rejected a call")
.arg(username);
} else { } else {
return QCoreApplication::translate("utils", "Unknown Message Type"); return QCoreApplication::translate("utils", "Unknown Message Type");
} }

View file

@ -138,6 +138,20 @@ struct RoomEventType
{ {
return qml_mtx_events::EventType::CallCandidates; return qml_mtx_events::EventType::CallCandidates;
} }
qml_mtx_events::EventType
operator()(const mtx::events::Event<mtx::events::voip::CallSelectAnswer> &)
{
return qml_mtx_events::EventType::CallSelectAnswer;
}
qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::voip::CallReject> &)
{
return qml_mtx_events::EventType::CallReject;
}
qml_mtx_events::EventType
operator()(const mtx::events::Event<mtx::events::voip::CallNegotiate> &)
{
return qml_mtx_events::EventType::CallNegotiate;
}
// ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return // ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return
// ::EventType::LocationMessage; } // ::EventType::LocationMessage; }
}; };
@ -258,6 +272,15 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
/// m.call.candidates /// m.call.candidates
case qml_mtx_events::CallCandidates: case qml_mtx_events::CallCandidates:
return mtx::events::EventType::CallCandidates; return mtx::events::EventType::CallCandidates;
/// m.call.select_answer
case qml_mtx_events::CallSelectAnswer:
return mtx::events::EventType::CallSelectAnswer;
/// m.call.reject
case qml_mtx_events::CallReject:
return mtx::events::EventType::CallReject;
/// m.call.negotiate
case qml_mtx_events::CallNegotiate:
return mtx::events::EventType::CallNegotiate;
/// m.room.canonical_alias /// m.room.canonical_alias
case qml_mtx_events::CanonicalAlias: case qml_mtx_events::CanonicalAlias:
return mtx::events::EventType::RoomCanonicalAlias; return mtx::events::EventType::RoomCanonicalAlias;
@ -922,16 +945,22 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
} }
if (std::holds_alternative<RoomEvent<voip::CallCandidates>>(e) || if (std::holds_alternative<RoomEvent<voip::CallCandidates>>(e) ||
std::holds_alternative<RoomEvent<voip::CallNegotiate>>(e) ||
std::holds_alternative<RoomEvent<voip::CallInvite>>(e) || std::holds_alternative<RoomEvent<voip::CallInvite>>(e) ||
std::holds_alternative<RoomEvent<voip::CallAnswer>>(e) || std::holds_alternative<RoomEvent<voip::CallAnswer>>(e) ||
std::holds_alternative<RoomEvent<voip::CallSelectAnswer>>(e) ||
std::holds_alternative<RoomEvent<voip::CallReject>>(e) ||
std::holds_alternative<RoomEvent<voip::CallHangUp>>(e)) std::holds_alternative<RoomEvent<voip::CallHangUp>>(e))
std::visit( std::visit(
[this](auto &event) { [this](auto &event) {
event.room_id = room_id_.toStdString(); event.room_id = room_id_.toStdString();
if constexpr (std::is_same_v<std::decay_t<decltype(event)>, if constexpr (
RoomEvent<voip::CallAnswer>> || std::is_same_v<std::decay_t<decltype(event)>, RoomEvent<voip::CallAnswer>> ||
std::is_same_v<std::decay_t<decltype(event)>, std::is_same_v<std::decay_t<decltype(event)>, RoomEvent<voip::CallInvite>> ||
RoomEvent<voip::CallHangUp>>) std::is_same_v<std::decay_t<decltype(event)>,
RoomEvent<voip::CallSelectAnswer>> ||
std::is_same_v<std::decay_t<decltype(event)>, RoomEvent<voip::CallReject>> ||
std::is_same_v<std::decay_t<decltype(event)>, RoomEvent<voip::CallHangUp>>)
emit newCallEvent(event); emit newCallEvent(event);
else { else {
if (event.sender != http::client()->user_id().to_string()) if (event.sender != http::client()->user_id().to_string())
@ -1007,6 +1036,17 @@ isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &)
return true; return true;
} }
auto
isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallReject> &)
{
return true;
}
auto
isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallSelectAnswer> &)
{
return true;
}
// Workaround. We also want to see a room at the top, if we just joined it // Workaround. We also want to see a room at the top, if we just joined it
auto auto
isYourJoin(const mtx::events::StateEvent<mtx::events::state::Member> &e) isYourJoin(const mtx::events::StateEvent<mtx::events::state::Member> &e)
@ -1503,6 +1543,23 @@ struct SendMessageVisitor
sendRoomEvent<mtx::events::voip::CallHangUp, mtx::events::EventType::CallHangUp>(event); sendRoomEvent<mtx::events::voip::CallHangUp, mtx::events::EventType::CallHangUp>(event);
} }
void operator()(const mtx::events::RoomEvent<mtx::events::voip::CallSelectAnswer> &event)
{
sendRoomEvent<mtx::events::voip::CallSelectAnswer,
mtx::events::EventType::CallSelectAnswer>(event);
}
void operator()(const mtx::events::RoomEvent<mtx::events::voip::CallReject> &event)
{
sendRoomEvent<mtx::events::voip::CallReject, mtx::events::EventType::CallReject>(event);
}
void operator()(const mtx::events::RoomEvent<mtx::events::voip::CallNegotiate> &event)
{
sendRoomEvent<mtx::events::voip::CallNegotiate, mtx::events::EventType::CallNegotiate>(
event);
}
void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg)
{ {
sendRoomEvent<mtx::events::msg::KeyVerificationRequest, sendRoomEvent<mtx::events::msg::KeyVerificationRequest,

View file

@ -58,6 +58,12 @@ enum EventType
CallHangUp, CallHangUp,
/// m.call.candidates /// m.call.candidates
CallCandidates, CallCandidates,
/// m.call.select_answer
CallSelectAnswer,
/// m.call.reject
CallReject,
/// m.call.negotiate
CallNegotiate,
/// m.room.canonical_alias /// m.room.canonical_alias
CanonicalAlias, CanonicalAlias,
/// m.room.create /// m.room.create

View file

@ -377,6 +377,28 @@ TimelineViewManager::queueCallMessage(const QString &roomid,
room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp); room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp);
} }
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::voip::CallSelectAnswer &callSelectAnswer)
{
if (auto room = rooms_->getRoomById(roomid))
room->sendMessageEvent(callSelectAnswer, mtx::events::EventType::CallSelectAnswer);
}
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::voip::CallReject &callReject)
{
if (auto room = rooms_->getRoomById(roomid))
room->sendMessageEvent(callReject, mtx::events::EventType::CallReject);
}
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::voip::CallNegotiate &callNegotiate)
{
if (auto room = rooms_->getRoomById(roomid))
room->sendMessageEvent(callNegotiate, mtx::events::EventType::CallNegotiate);
}
void void
TimelineViewManager::focusMessageInput() TimelineViewManager::focusMessageInput()
{ {

View file

@ -109,6 +109,9 @@ public slots:
void queueCallMessage(const QString &roomid, const mtx::events::voip::CallCandidates &); void queueCallMessage(const QString &roomid, const mtx::events::voip::CallCandidates &);
void queueCallMessage(const QString &roomid, const mtx::events::voip::CallAnswer &); void queueCallMessage(const QString &roomid, const mtx::events::voip::CallAnswer &);
void queueCallMessage(const QString &roomid, const mtx::events::voip::CallHangUp &); void queueCallMessage(const QString &roomid, const mtx::events::voip::CallHangUp &);
void queueCallMessage(const QString &roomid, const mtx::events::voip::CallSelectAnswer &);
void queueCallMessage(const QString &roomid, const mtx::events::voip::CallReject &);
void queueCallMessage(const QString &roomid, const mtx::events::voip::CallNegotiate &);
void setVideoCallItem(); void setVideoCallItem();

View file

@ -17,9 +17,20 @@ HiddenEvents::load()
hiddenEvents.hidden_event_types = std::vector{ hiddenEvents.hidden_event_types = std::vector{
EventType::Reaction, EventType::Reaction,
EventType::CallCandidates, EventType::CallCandidates,
EventType::CallNegotiate,
EventType::Unsupported, EventType::Unsupported,
}; };
// check if selected answer is from to local user
/*
* localUser accepts/rejects the call and it is selected by caller - No message
* Another User accepts/rejects the call and it is selected by caller - "Call answered/rejected
* elsewhere"
*/
bool callLocalUser_ = true;
if (callLocalUser_)
hiddenEvents.hidden_event_types->push_back(EventType::CallSelectAnswer);
if (auto temp = if (auto temp =
cache::client()->getAccountData(mtx::events::EventType::NhekoHiddenEvents, "")) { cache::client()->getAccountData(mtx::events::EventType::NhekoHiddenEvents, "")) {
auto h = std::get< auto h = std::get<

View file

@ -24,6 +24,10 @@
#include "mtx/responses/turn_server.hpp" #include "mtx/responses/turn_server.hpp"
/*
* Select Answer when one instance of the client supports v0
*/
#ifdef XCB_AVAILABLE #ifdef XCB_AVAILABLE
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h> #include <xcb/xcb_ewmh.h>
@ -67,10 +71,15 @@ CallManager::CallManager(QObject *parent)
this, this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) { [this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_); nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
emit newMessage( emit newMessage(roomid_,
roomid_, CallInvite{callid_,
CallInvite{callid_, partyid_, SDO{sdp, SDO::Type::Offer}, "0", timeoutms_, invitee_}); partyid_,
emit newMessage(roomid_, CallCandidates{callid_, partyid_, candidates, "0"}); SDO{sdp, SDO::Type::Offer},
callPartyVersion_,
timeoutms_,
invitee_});
emit newMessage(roomid_,
CallCandidates{callid_, partyid_, candidates, callPartyVersion_});
std::string callid(callid_); std::string callid(callid_);
QTimer::singleShot(timeoutms_, this, [this, callid]() { QTimer::singleShot(timeoutms_, this, [this, callid]() {
if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) { if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) {
@ -87,8 +96,10 @@ CallManager::CallManager(QObject *parent)
this, this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) { [this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_); nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_);
emit newMessage(roomid_, CallAnswer{callid_, partyid_, "0", SDO{sdp, SDO::Type::Answer}}); emit newMessage(
emit newMessage(roomid_, CallCandidates{callid_, partyid_, candidates, "0"}); roomid_, CallAnswer{callid_, partyid_, callPartyVersion_, SDO{sdp, SDO::Type::Answer}});
emit newMessage(roomid_,
CallCandidates{callid_, partyid_, candidates, callPartyVersion_});
}); });
connect(&session_, connect(&session_,
@ -96,7 +107,8 @@ CallManager::CallManager(QObject *parent)
this, this,
[this](const CallCandidates::Candidate &candidate) { [this](const CallCandidates::Candidate &candidate) {
nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_); nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_);
emit newMessage(roomid_, CallCandidates{callid_, partyid_, {candidate}, "0"}); emit newMessage(roomid_,
CallCandidates{callid_, partyid_, {candidate}, callPartyVersion_});
}); });
connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer); connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer);
@ -170,23 +182,14 @@ CallManager::CallManager(QObject *parent)
void void
CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int windowIndex) CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int windowIndex)
{ {
if (isOnCall()) if (isOnCall() || isOnCallOnOtherDevice()) {
if (isOnCallOnOtherDevice_ != "")
emit ChatPage::instance()->showNotification(
QStringLiteral("User is already in a call"));
return; return;
if (callType == CallType::SCREEN) {
if (!screenShareSupported())
return;
if (windows_.empty() || windowIndex >= windows_.size()) {
nhlog::ui()->error("WebRTC: window index out of range");
return;
}
} }
auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
if (roomInfo.member_count != 2) {
emit ChatPage::instance()->showNotification(
QStringLiteral("Calls are limited to 1:1 rooms."));
return;
}
std::string errorMessage; std::string errorMessage;
if (!session_.havePlugins(false, &errorMessage) || if (!session_.havePlugins(false, &errorMessage) ||
@ -198,17 +201,60 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
callType_ = callType; callType_ = callType;
roomid_ = roomid; roomid_ = roomid;
session_.setTurnServers(turnURIs_);
generateCallID(); generateCallID();
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember *callee;
if (roomInfo.member_count == 1)
callee = &members.front();
else if (roomInfo.member_count == 2)
callee = members.front().user_id == utils::localUser() ? &members.back() : &members.front();
else {
emit ChatPage::instance()->showNotification(
QStringLiteral("Calls are limited to rooms with less than two members"));
return;
}
if (callType == CallType::SCREEN) {
if (!screenShareSupported())
return;
if (windows_.empty() || windowIndex >= windows_.size()) {
nhlog::ui()->error("WebRTC: window index out of range");
return;
}
}
if (haveCallInvite_) {
nhlog::ui()->debug(
"WebRTC: Discarding outbound call for inbound call. localUser is polite party");
if (callParty_ == callee->user_id) {
if (callType == callType_)
acceptInvite();
else {
emit ChatPage::instance()->showNotification(
QStringLiteral("Can't place call. Call types do not match"));
emit newMessage(
roomid_,
CallHangUp{callid_, partyid_, callPartyVersion_, CallHangUp::Reason::UserBusy});
}
} else {
emit ChatPage::instance()->showNotification(
QStringLiteral("Already on a call with a different user"));
emit newMessage(
roomid_,
CallHangUp{callid_, partyid_, callPartyVersion_, CallHangUp::Reason::UserBusy});
}
return;
}
session_.setTurnServers(turnURIs_);
std::string strCallType = std::string strCallType =
callType_ == CallType::VOICE ? "voice" : (callType_ == CallType::VIDEO ? "video" : "screen"); callType_ == CallType::VOICE ? "voice" : (callType_ == CallType::VIDEO ? "video" : "screen");
nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType); nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType);
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString())); callParty_ = callee->user_id;
const RoomMember &callee = callPartyDisplayName_ = callee->display_name.isEmpty() ? callee->user_id : callee->display_name;
members.front().user_id == utils::localUser() ? members.back() : members.front();
callParty_ = callee.user_id;
callPartyDisplayName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
invitee_ = callParty_.toStdString();
emit newInviteState(); emit newInviteState();
playRingtone(QUrl(QStringLiteral("qrc:/media/media/ringback.ogg")), true); playRingtone(QUrl(QStringLiteral("qrc:/media/media/ringback.ogg")), true);
if (!session_.createOffer(callType, if (!session_.createOffer(callType,
@ -249,7 +295,7 @@ CallManager::hangUp(CallHangUp::Reason reason)
if (!callid_.empty()) { if (!callid_.empty()) {
nhlog::ui()->debug( nhlog::ui()->debug(
"WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason)); "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason));
emit newMessage(roomid_, CallHangUp{callid_, partyid_, "0", reason}); emit newMessage(roomid_, CallHangUp{callid_, partyid_, callPartyVersion_, reason});
endCall(); endCall();
} }
} }
@ -259,7 +305,9 @@ CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
{ {
#ifdef GSTREAMER_AVAILABLE #ifdef GSTREAMER_AVAILABLE
if (handleEvent<CallInvite>(event) || handleEvent<CallCandidates>(event) || if (handleEvent<CallInvite>(event) || handleEvent<CallCandidates>(event) ||
handleEvent<CallAnswer>(event) || handleEvent<CallHangUp>(event)) handleEvent<CallNegotiate>(event) || handleEvent<CallSelectAnswer>(event) ||
handleEvent<CallAnswer>(event) || handleEvent<CallReject>(event) ||
handleEvent<CallHangUp>(event))
return; return;
#else #else
(void)event; (void)event;
@ -289,41 +337,121 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
[](unsigned char c1, unsigned char c2) { [](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2); return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend(); }) != sdp.cend();
nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from ({},{}) ",
nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}",
callInviteEvent.content.call_id, callInviteEvent.content.call_id,
(isVideo ? "video" : "voice"), (isVideo ? "video" : "voice"),
callInviteEvent.sender); callInviteEvent.sender,
callInviteEvent.content.party_id);
if (callInviteEvent.content.call_id.empty()) if (callInviteEvent.content.call_id.empty())
return; return;
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id); if (callInviteEvent.sender == utils::localUser().toStdString()) {
if (isOnCall() || roomInfo.member_count != 2) { if (callInviteEvent.content.party_id == partyid_)
emit newMessage( return;
QString::fromStdString(callInviteEvent.room_id), else {
CallHangUp{ if (callInviteEvent.content.invitee != utils::localUser().toStdString()) {
callInviteEvent.content.call_id, partyid_, "0", CallHangUp::Reason::InviteTimeOut}); isOnCallOnOtherDevice_ = callInviteEvent.content.call_id;
return; emit newCallDeviceState();
nhlog::ui()->debug("WebRTC: User is on call on other device.");
return;
}
}
} }
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
callPartyVersion_ = callInviteEvent.content.version;
const QString &ringtone = UserSettings::instance()->ringtone(); const QString &ringtone = UserSettings::instance()->ringtone();
if (ringtone != QLatin1String("Mute")) bool sharesRoom = true;
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller =
*std::find_if(members.begin(), members.end(), [&](RoomMember member) {
return member.user_id.toStdString() == callInviteEvent.sender;
});
if (isOnCall() || isOnCallOnOtherDevice()) {
if (isOnCallOnOtherDevice_ != "")
return;
if (callParty_.toStdString() == callInviteEvent.sender) {
if (session_.state() == webrtc::State::OFFERSENT) {
if (callid_ > callInviteEvent.content.call_id) {
endCall();
callParty_ = caller.user_id;
callPartyDisplayName_ =
caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
roomid_ = QString::fromStdString(callInviteEvent.room_id);
callid_ = callInviteEvent.content.call_id;
remoteICECandidates_.clear();
haveCallInvite_ = true;
callType_ = isVideo ? CallType::VIDEO : CallType::VOICE;
inviteSDP_ = callInviteEvent.content.offer.sdp;
emit newInviteState();
acceptInvite();
}
return;
} else if (session_.state() < webrtc::State::OFFERSENT)
endCall();
else
return;
} else
return;
}
if (callPartyVersion_ == "0") {
if (roomInfo.member_count != 2) {
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
CallHangUp{callInviteEvent.content.call_id,
partyid_,
callPartyVersion_,
CallHangUp::Reason::InviteTimeOut});
return;
}
} else {
if (caller.user_id == utils::localUser() &&
callInviteEvent.content.party_id == partyid_) // remote echo
return;
if (roomInfo.member_count == 2 || // general call in room with two members
(roomInfo.member_count == 1 &&
partyid_ !=
callInviteEvent.content.party_id) || // self call, ring if not the same party_id
callInviteEvent.content.invitee == "" || // empty, meant for everyone
callInviteEvent.content.invitee ==
utils::localUser().toStdString()) // meant specifically for local user
{
if (roomInfo.member_count > 2) {
// check if shares room
sharesRoom = checkSharesRoom(QString::fromStdString(callInviteEvent.room_id),
callInviteEvent.content.invitee);
}
} else {
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
CallHangUp{callInviteEvent.content.call_id,
partyid_,
callPartyVersion_,
CallHangUp::Reason::InviteTimeOut});
return;
}
}
// ring if not mute or does not have direct message room
if (ringtone != QLatin1String("Mute") && sharesRoom)
playRingtone(ringtone == QLatin1String("Default") playRingtone(ringtone == QLatin1String("Default")
? QUrl(QStringLiteral("qrc:/media/media/ring.ogg")) ? QUrl(QStringLiteral("qrc:/media/media/ring.ogg"))
: QUrl::fromLocalFile(ringtone), : QUrl::fromLocalFile(ringtone),
true); true);
roomid_ = QString::fromStdString(callInviteEvent.room_id);
callid_ = callInviteEvent.content.call_id;
remoteICECandidates_.clear();
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front();
callParty_ = caller.user_id; callParty_ = caller.user_id;
callPartyDisplayName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name; callPartyDisplayName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
roomid_ = QString::fromStdString(callInviteEvent.room_id);
callid_ = callInviteEvent.content.call_id;
remoteICECandidates_.clear();
haveCallInvite_ = true; haveCallInvite_ = true;
callType_ = isVideo ? CallType::VIDEO : CallType::VOICE; callType_ = isVideo ? CallType::VIDEO : CallType::VOICE;
inviteSDP_ = callInviteEvent.content.offer.sdp; inviteSDP_ = callInviteEvent.content.offer.sdp;
@ -333,6 +461,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
void void
CallManager::acceptInvite() CallManager::acceptInvite()
{ {
// if call was accepted/rejected elsewhere and m.call.select_answer is received
// before acceptInvite
if (!haveCallInvite_) if (!haveCallInvite_)
return; return;
@ -341,7 +471,7 @@ CallManager::acceptInvite()
if (!session_.havePlugins(false, &errorMessage) || if (!session_.havePlugins(false, &errorMessage) ||
(callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) { (callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) {
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
hangUp(); hangUp(CallHangUp::Reason::UserMediaFailed);
return; return;
} }
@ -357,15 +487,31 @@ CallManager::acceptInvite()
emit newInviteState(); emit newInviteState();
} }
void
CallManager::rejectInvite()
{
if (callPartyVersion_ == "0") {
hangUp();
// send m.call.reject after sending hangup as mentioned in MSC2746
emit newMessage(roomid_, CallReject{callid_, partyid_, callPartyVersion_});
}
if (!callid_.empty()) {
nhlog::ui()->debug("WebRTC: call id: {} - rejecting call", callid_);
emit newMessage(roomid_, CallReject{callid_, partyid_, callPartyVersion_});
endCall(false);
}
}
void void
CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent) CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent)
{ {
if (callCandidatesEvent.sender == utils::localUser().toStdString()) if (callCandidatesEvent.sender == utils::localUser().toStdString() &&
callCandidatesEvent.content.party_id == partyid_)
return; return;
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from ({}, {})",
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}",
callCandidatesEvent.content.call_id, callCandidatesEvent.content.call_id,
callCandidatesEvent.sender); callCandidatesEvent.sender,
callCandidatesEvent.content.party_id);
if (callid_ == callCandidatesEvent.content.call_id) { if (callid_ == callCandidatesEvent.content.call_id) {
if (isOnCall()) if (isOnCall())
@ -382,20 +528,31 @@ CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent)
void void
CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent) CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
{ {
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}", nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from ({}, {})",
callAnswerEvent.content.call_id, callAnswerEvent.content.call_id,
callAnswerEvent.sender); callAnswerEvent.sender,
callAnswerEvent.content.party_id);
if (answerSelected_)
return;
if (callAnswerEvent.sender == utils::localUser().toStdString() && if (callAnswerEvent.sender == utils::localUser().toStdString() &&
callid_ == callAnswerEvent.content.call_id) { callid_ == callAnswerEvent.content.call_id) {
if (partyid_ == callAnswerEvent.content.party_id)
return;
if (!isOnCall()) { if (!isOnCall()) {
emit ChatPage::instance()->showNotification( emit ChatPage::instance()->showNotification(
QStringLiteral("Call answered on another device.")); QStringLiteral("Call answered on another device."));
stopRingtone(); stopRingtone();
haveCallInvite_ = false; haveCallInvite_ = false;
if (callPartyVersion_ != "1") {
isOnCallOnOtherDevice_ = callid_;
emit newCallDeviceState();
}
emit newInviteState(); emit newInviteState();
} }
return; if (callParty_ != utils::localUser())
return;
} }
if (isOnCall() && callid_ == callAnswerEvent.content.call_id) { if (isOnCall() && callid_ == callAnswerEvent.content.call_id) {
@ -405,20 +562,141 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
hangUp(); hangUp();
} }
} }
emit newMessage(
roomid_,
CallSelectAnswer{callid_, partyid_, callPartyVersion_, callAnswerEvent.content.party_id});
selectedpartyid_ = callAnswerEvent.content.party_id;
answerSelected_ = true;
} }
void void
CallManager::handleEvent(const RoomEvent<CallHangUp> &callHangUpEvent) CallManager::handleEvent(const RoomEvent<CallHangUp> &callHangUpEvent)
{ {
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}", nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from ({}, {})",
callHangUpEvent.content.call_id, callHangUpEvent.content.call_id,
callHangUpReasonString(callHangUpEvent.content.reason), callHangUpReasonString(callHangUpEvent.content.reason),
callHangUpEvent.sender); callHangUpEvent.sender,
callHangUpEvent.content.party_id);
if (callid_ == callHangUpEvent.content.call_id) if (callid_ == callHangUpEvent.content.call_id ||
isOnCallOnOtherDevice_ == callHangUpEvent.content.call_id)
endCall(); endCall();
} }
void
CallManager::handleEvent(const RoomEvent<CallSelectAnswer> &callSelectAnswerEvent)
{
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallSelectAnswer from ({}, {})",
callSelectAnswerEvent.content.call_id,
callSelectAnswerEvent.sender,
callSelectAnswerEvent.content.party_id);
if (callSelectAnswerEvent.sender == utils::localUser().toStdString()) {
if (callSelectAnswerEvent.content.party_id != partyid_) {
if (std::find(rejectCallPartyIDs_.begin(),
rejectCallPartyIDs_.begin(),
callSelectAnswerEvent.content.selected_party_id) !=
rejectCallPartyIDs_.end())
endCall();
else {
if (callSelectAnswerEvent.content.selected_party_id == partyid_)
return;
nhlog::ui()->debug("WebRTC: call id: {} - user is on call with this user!",
callSelectAnswerEvent.content.call_id);
isOnCallOnOtherDevice_ = callSelectAnswerEvent.content.call_id;
emit newCallDeviceState();
}
}
return;
} else if (callid_ == callSelectAnswerEvent.content.call_id) {
if (callSelectAnswerEvent.content.selected_party_id != partyid_) {
bool endAllCalls = false;
if (std::find(rejectCallPartyIDs_.begin(),
rejectCallPartyIDs_.begin(),
callSelectAnswerEvent.content.selected_party_id) !=
rejectCallPartyIDs_.end())
endAllCalls = true;
else {
isOnCallOnOtherDevice_ = callid_;
emit newCallDeviceState();
}
endCall(endAllCalls);
} else if (session_.state() == webrtc::State::DISCONNECTED)
endCall();
}
}
void
CallManager::handleEvent(const RoomEvent<CallReject> &callRejectEvent)
{
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallReject from ({}, {})",
callRejectEvent.content.call_id,
callRejectEvent.sender,
callRejectEvent.content.party_id);
if (answerSelected_)
return;
rejectCallPartyIDs_.push_back(callRejectEvent.content.party_id);
// check remote echo
if (callRejectEvent.sender == utils::localUser().toStdString()) {
if (callRejectEvent.content.party_id != partyid_ && callParty_ != utils::localUser())
emit ChatPage::instance()->showNotification(
QStringLiteral("Call rejected on another device."));
endCall();
return;
}
if (callRejectEvent.content.call_id == callid_) {
if (session_.state() == webrtc::State::OFFERSENT) {
// only accept reject if webrtc is in OFFERSENT state, else call has been accepted
emit newMessage(
roomid_,
CallSelectAnswer{
callid_, partyid_, callPartyVersion_, callRejectEvent.content.party_id});
endCall();
}
}
}
void
CallManager::handleEvent(const RoomEvent<CallNegotiate> &callNegotiateEvent)
{
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallNegotiate from ({}, {})",
callNegotiateEvent.content.call_id,
callNegotiateEvent.sender,
callNegotiateEvent.content.party_id);
std::string negotiationSDP_ = callNegotiateEvent.content.description.sdp;
if (!session_.acceptNegotiation(negotiationSDP_)) {
emit ChatPage::instance()->showNotification(QStringLiteral("Problem accepting new SDP"));
hangUp();
return;
}
session_.acceptICECandidates(remoteICECandidates_);
remoteICECandidates_.clear();
}
bool
CallManager::checkSharesRoom(QString roomid, std::string invitee) const
{
/*
IMPLEMENTATION REQUIRED
Check if room is shared to determine whether to ring or not.
Called from handle callInvite event
*/
if (roomid.toStdString() != "") {
if (invitee == "") {
// check all members
return true;
} else {
return true;
// check if invitee shares a direct room with local user
}
return true;
}
return true;
}
void void
CallManager::toggleMicMute() CallManager::toggleMicMute()
{ {
@ -467,7 +745,7 @@ CallManager::generateCallID()
} }
void void
CallManager::clear() CallManager::clear(bool endAllCalls)
{ {
roomid_.clear(); roomid_.clear();
callParty_.clear(); callParty_.clear();
@ -476,17 +754,23 @@ CallManager::clear()
callid_.clear(); callid_.clear();
callType_ = CallType::VOICE; callType_ = CallType::VOICE;
haveCallInvite_ = false; haveCallInvite_ = false;
answerSelected_ = false;
if (endAllCalls) {
isOnCallOnOtherDevice_ = "";
rejectCallPartyIDs_.clear();
emit newCallDeviceState();
}
emit newInviteState(); emit newInviteState();
inviteSDP_.clear(); inviteSDP_.clear();
remoteICECandidates_.clear(); remoteICECandidates_.clear();
} }
void void
CallManager::endCall() CallManager::endCall(bool endAllCalls)
{ {
stopRingtone(); stopRingtone();
session_.end(); session_.end();
clear(); clear(endAllCalls);
} }
void void

View file

@ -18,6 +18,7 @@
#include "WebRTCSession.h" #include "WebRTCSession.h"
#include "mtx/events/collections.hpp" #include "mtx/events/collections.hpp"
#include "mtx/events/voip.hpp" #include "mtx/events/voip.hpp"
#include <mtxclient/utils.hpp>
namespace mtx::responses { namespace mtx::responses {
struct TurnServer; struct TurnServer;
@ -30,6 +31,7 @@ class CallManager final : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState) Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
Q_PROPERTY(bool isOnCallOnOtherDevice READ isOnCallOnOtherDevice NOTIFY newCallDeviceState)
Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState) Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState) Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
@ -46,7 +48,9 @@ public:
CallManager(QObject *); CallManager(QObject *);
bool haveCallInvite() const { return haveCallInvite_; } bool haveCallInvite() const { return haveCallInvite_; }
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } bool isOnCall() const { return (session_.state() != webrtc::State::DISCONNECTED); }
bool isOnCallOnOtherDevice() const { return (isOnCallOnOtherDevice_ != ""); }
bool checkSharesRoom(QString roomid_, std::string invitee) const;
webrtc::CallType callType() const { return callType_; } webrtc::CallType callType() const { return callType_; }
webrtc::State callState() const { return session_.state(); } webrtc::State callState() const { return session_.state(); }
QString callParty() const { return callParty_; } QString callParty() const { return callParty_; }
@ -67,8 +71,9 @@ public slots:
void toggleMicMute(); void toggleMicMute();
void toggleLocalPiP() { session_.toggleLocalPiP(); } void toggleLocalPiP() { session_.toggleLocalPiP(); }
void acceptInvite(); void acceptInvite();
void void hangUp(
hangUp(mtx::events::voip::CallHangUp::Reason = mtx::events::voip::CallHangUp::Reason::User); mtx::events::voip::CallHangUp::Reason = mtx::events::voip::CallHangUp::Reason::UserHangUp);
void rejectInvite();
QStringList windowList(); QStringList windowList();
void previewWindow(unsigned int windowIndex) const; void previewWindow(unsigned int windowIndex) const;
@ -77,8 +82,12 @@ signals:
void newMessage(const QString &roomid, const mtx::events::voip::CallCandidates &); void newMessage(const QString &roomid, const mtx::events::voip::CallCandidates &);
void newMessage(const QString &roomid, const mtx::events::voip::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::voip::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::voip::CallHangUp &); void newMessage(const QString &roomid, const mtx::events::voip::CallHangUp &);
void newMessage(const QString &roomid, const mtx::events::voip::CallSelectAnswer &);
void newMessage(const QString &roomid, const mtx::events::voip::CallReject &);
void newMessage(const QString &roomid, const mtx::events::voip::CallNegotiate &);
void newInviteState(); void newInviteState();
void newCallState(); void newCallState();
void newCallDeviceState();
void micMuteChanged(); void micMuteChanged();
void devicesChanged(); void devicesChanged();
void turnServerRetrieved(const mtx::responses::TurnServer &); void turnServerRetrieved(const mtx::responses::TurnServer &);
@ -92,18 +101,23 @@ private:
QString callParty_; QString callParty_;
QString callPartyDisplayName_; QString callPartyDisplayName_;
QString callPartyAvatarUrl_; QString callPartyAvatarUrl_;
std::string callPartyVersion_ = "1";
std::string callid_; std::string callid_;
std::string partyid_ = ""; std::string partyid_ = mtx::client::utils::random_token(8, false);
std::string invitee_ = ""; std::string selectedpartyid_ = "";
const uint32_t timeoutms_ = 120000; std::string invitee_ = "";
webrtc::CallType callType_ = webrtc::CallType::VOICE; const uint32_t timeoutms_ = 120000;
bool haveCallInvite_ = false; webrtc::CallType callType_ = webrtc::CallType::VOICE;
bool haveCallInvite_ = false;
bool answerSelected_ = false;
std::string isOnCallOnOtherDevice_ = "";
std::string inviteSDP_; std::string inviteSDP_;
std::vector<mtx::events::voip::CallCandidates::Candidate> remoteICECandidates_; std::vector<mtx::events::voip::CallCandidates::Candidate> remoteICECandidates_;
std::vector<std::string> turnURIs_; std::vector<std::string> turnURIs_;
QTimer turnServerTimer_; QTimer turnServerTimer_;
QMediaPlayer player_; QMediaPlayer player_;
std::vector<std::pair<QString, uint32_t>> windows_; std::vector<std::pair<QString, uint32_t>> windows_;
std::vector<std::string> rejectCallPartyIDs_;
template<typename T> template<typename T>
bool handleEvent(const mtx::events::collections::TimelineEvents &event); bool handleEvent(const mtx::events::collections::TimelineEvents &event);
@ -111,11 +125,14 @@ private:
void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallCandidates> &); void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallCandidates> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallAnswer> &); void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallAnswer> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &); void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallSelectAnswer> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallReject> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::voip::CallNegotiate> &);
void answerInvite(const mtx::events::voip::CallInvite &, bool isVideo); void answerInvite(const mtx::events::voip::CallInvite &, bool isVideo);
void generateCallID(); void generateCallID();
QStringList devices(bool isVideo) const; QStringList devices(bool isVideo) const;
void clear(); void clear(bool endAllCalls = true);
void endCall(); void endCall(bool endAllCalls = true);
void playRingtone(const QUrl &ringtone, bool repeat); void playRingtone(const QUrl &ringtone, bool repeat);
void stopRingtone(); void stopRingtone();
}; };

View file

@ -699,6 +699,15 @@ WebRTCSession::acceptOffer(const std::string &sdp)
return true; return true;
} }
bool
WebRTCSession::acceptNegotiation(const std::string &sdp)
{
nhlog::ui()->debug("WebRTC: received negotiation offer:\n{}", sdp);
if (state_ == State::DISCONNECTED)
return false;
return false;
}
bool bool
WebRTCSession::acceptAnswer(const std::string &sdp) WebRTCSession::acceptAnswer(const std::string &sdp)
{ {
@ -1138,6 +1147,12 @@ WebRTCSession::createOffer(webrtc::CallType, uint32_t)
} }
// clang-format on // clang-format on
bool
WebRTCSession::acceptNegotiation(const std::string &)
{
return false;
}
bool bool
WebRTCSession::acceptOffer(const std::string &) WebRTCSession::acceptOffer(const std::string &)
{ {

View file

@ -64,6 +64,7 @@ public:
bool createOffer(webrtc::CallType, uint32_t shareWindowId); bool createOffer(webrtc::CallType, uint32_t shareWindowId);
bool acceptOffer(const std::string &sdp); bool acceptOffer(const std::string &sdp);
bool acceptAnswer(const std::string &sdp); bool acceptAnswer(const std::string &sdp);
bool acceptNegotiation(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::voip::CallCandidates::Candidate> &); void acceptICECandidates(const std::vector<mtx::events::voip::CallCandidates::Candidate> &);
bool isMicMuted() const; bool isMicMuted() const;