diff --git a/src/Cache.cpp b/src/Cache.cpp
index 863f0683..a83b73f7 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -184,8 +184,18 @@ Cache::isHiddenEvent(lmdb::txn &txn,
hiddenEvents.hidden_event_types = std::vector{
EventType::Reaction,
EventType::CallCandidates,
+ EventType::CallNegotiate,
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, "")) {
auto h = std::get<
@@ -1661,11 +1671,18 @@ isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallAnswer> &)
{
return true;
}
+
auto
isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &)
{
return true;
}
+
+// auto
+// isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallReject> &)
+// {
+// return true;
+// }
}
void
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 3736ec6b..756ef425 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -363,6 +363,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
connectCallMessage<mtx::events::voip::CallCandidates>();
connectCallMessage<mtx::events::voip::CallAnswer>();
connectCallMessage<mtx::events::voip::CallHangUp>();
+ connectCallMessage<mtx::events::voip::CallSelectAnswer>();
+ connectCallMessage<mtx::events::voip::CallReject>();
+ connectCallMessage<mtx::events::voip::CallNegotiate>();
}
void
diff --git a/src/PowerlevelsEditModels.cpp b/src/PowerlevelsEditModels.cpp
index 8cc2dcc0..2c2d4a7f 100644
--- a/src/PowerlevelsEditModels.cpp
+++ b/src/PowerlevelsEditModels.cpp
@@ -222,6 +222,8 @@ PowerlevelsTypeListModel::data(const QModelIndex &index, int role) const
return tr("Answer a call");
else if (type.type == "m.call.hangup")
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")
return tr("Change the room emotes");
return QString::fromStdString(type.type);
diff --git a/src/Utils.cpp b/src/Utils.cpp
index cedf537a..b92c6cce 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -219,6 +219,7 @@ utils::getMessageDescription(const TimelineEvent &event,
using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>;
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>;
if (std::holds_alternative<Audio>(event)) {
@@ -241,6 +242,8 @@ utils::getMessageDescription(const TimelineEvent &event,
return createDescriptionInfo<CallAnswer>(event, localUser, displayName);
} else if (std::holds_alternative<CallHangUp>(event)) {
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)) {
return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName);
} else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) {
diff --git a/src/Utils.h b/src/Utils.h
index 8f8b9cad..e4c3ccb3 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -110,6 +110,7 @@ messageDescription(const QString &username = QString(),
using CallInvite = mtx::events::RoomEvent<mtx::events::voip::CallInvite>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::voip::CallAnswer>;
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>;
if (std::is_same<T, Audio>::value) {
@@ -185,6 +186,12 @@ messageDescription(const QString &username = QString(),
else
return QCoreApplication::translate("message-description sent:", "%1 ended a call")
.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 {
return QCoreApplication::translate("utils", "Unknown Message Type");
}
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 0e726bde..6aa81d8b 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -138,6 +138,20 @@ struct RoomEventType
{
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::LocationMessage; }
};
@@ -258,6 +272,15 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
/// m.call.candidates
case qml_mtx_events::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
case qml_mtx_events::CanonicalAlias:
return mtx::events::EventType::RoomCanonicalAlias;
@@ -922,16 +945,22 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
}
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::CallAnswer>>(e) ||
+ std::holds_alternative<RoomEvent<voip::CallSelectAnswer>>(e) ||
+ std::holds_alternative<RoomEvent<voip::CallReject>>(e) ||
std::holds_alternative<RoomEvent<voip::CallHangUp>>(e))
std::visit(
[this](auto &event) {
event.room_id = room_id_.toStdString();
- if constexpr (std::is_same_v<std::decay_t<decltype(event)>,
- RoomEvent<voip::CallAnswer>> ||
- std::is_same_v<std::decay_t<decltype(event)>,
- RoomEvent<voip::CallHangUp>>)
+ if constexpr (
+ std::is_same_v<std::decay_t<decltype(event)>, RoomEvent<voip::CallAnswer>> ||
+ std::is_same_v<std::decay_t<decltype(event)>, RoomEvent<voip::CallInvite>> ||
+ 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);
else {
if (event.sender != http::client()->user_id().to_string())
@@ -1007,6 +1036,17 @@ isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &)
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
auto
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);
}
+ 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)
{
sendRoomEvent<mtx::events::msg::KeyVerificationRequest,
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index a4904f4f..2a04c9c9 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -58,6 +58,12 @@ enum EventType
CallHangUp,
/// m.call.candidates
CallCandidates,
+ /// m.call.select_answer
+ CallSelectAnswer,
+ /// m.call.reject
+ CallReject,
+ /// m.call.negotiate
+ CallNegotiate,
/// m.room.canonical_alias
CanonicalAlias,
/// m.room.create
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index a75a79d1..9c46d201 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -378,6 +378,28 @@ TimelineViewManager::queueCallMessage(const QString &roomid,
}
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
TimelineViewManager::focusMessageInput()
{
emit focusInput();
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index c0895b2c..c305fe66 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -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::CallAnswer &);
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();
diff --git a/src/ui/HiddenEvents.cpp b/src/ui/HiddenEvents.cpp
index 1ebcda3e..4686dfef 100644
--- a/src/ui/HiddenEvents.cpp
+++ b/src/ui/HiddenEvents.cpp
@@ -17,9 +17,20 @@ HiddenEvents::load()
hiddenEvents.hidden_event_types = std::vector{
EventType::Reaction,
EventType::CallCandidates,
+ EventType::CallNegotiate,
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 =
cache::client()->getAccountData(mtx::events::EventType::NhekoHiddenEvents, "")) {
auto h = std::get<
diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp
index 14f3adf8..5e711499 100644
--- a/src/voip/CallManager.cpp
+++ b/src/voip/CallManager.cpp
@@ -24,6 +24,10 @@
#include "mtx/responses/turn_server.hpp"
+/*
+ * Select Answer when one instance of the client supports v0
+ */
+
#ifdef XCB_AVAILABLE
#include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h>
@@ -67,10 +71,15 @@ CallManager::CallManager(QObject *parent)
this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
- emit newMessage(
- roomid_,
- CallInvite{callid_, partyid_, SDO{sdp, SDO::Type::Offer}, "0", timeoutms_, invitee_});
- emit newMessage(roomid_, CallCandidates{callid_, partyid_, candidates, "0"});
+ emit newMessage(roomid_,
+ CallInvite{callid_,
+ partyid_,
+ SDO{sdp, SDO::Type::Offer},
+ callPartyVersion_,
+ timeoutms_,
+ invitee_});
+ emit newMessage(roomid_,
+ CallCandidates{callid_, partyid_, candidates, callPartyVersion_});
std::string callid(callid_);
QTimer::singleShot(timeoutms_, this, [this, callid]() {
if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) {
@@ -87,8 +96,10 @@ CallManager::CallManager(QObject *parent)
this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_);
- emit newMessage(roomid_, CallAnswer{callid_, partyid_, "0", SDO{sdp, SDO::Type::Answer}});
- emit newMessage(roomid_, CallCandidates{callid_, partyid_, candidates, "0"});
+ emit newMessage(
+ roomid_, CallAnswer{callid_, partyid_, callPartyVersion_, SDO{sdp, SDO::Type::Answer}});
+ emit newMessage(roomid_,
+ CallCandidates{callid_, partyid_, candidates, callPartyVersion_});
});
connect(&session_,
@@ -96,7 +107,8 @@ CallManager::CallManager(QObject *parent)
this,
[this](const CallCandidates::Candidate &candidate) {
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);
@@ -170,23 +182,14 @@ CallManager::CallManager(QObject *parent)
void
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;
- 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());
- if (roomInfo.member_count != 2) {
- emit ChatPage::instance()->showNotification(
- QStringLiteral("Calls are limited to 1:1 rooms."));
- return;
- }
std::string errorMessage;
if (!session_.havePlugins(false, &errorMessage) ||
@@ -198,17 +201,60 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
callType_ = callType;
roomid_ = roomid;
- session_.setTurnServers(turnURIs_);
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 =
callType_ == CallType::VOICE ? "voice" : (callType_ == CallType::VIDEO ? "video" : "screen");
+
nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType);
- std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
- const RoomMember &callee =
- 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;
+ callParty_ = callee->user_id;
+ callPartyDisplayName_ = callee->display_name.isEmpty() ? callee->user_id : callee->display_name;
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
+ invitee_ = callParty_.toStdString();
emit newInviteState();
playRingtone(QUrl(QStringLiteral("qrc:/media/media/ringback.ogg")), true);
if (!session_.createOffer(callType,
@@ -249,7 +295,7 @@ CallManager::hangUp(CallHangUp::Reason reason)
if (!callid_.empty()) {
nhlog::ui()->debug(
"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();
}
}
@@ -259,7 +305,9 @@ CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
{
#ifdef GSTREAMER_AVAILABLE
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;
#else
(void)event;
@@ -289,41 +337,121 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend();
-
- nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}",
+ nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from ({},{}) ",
callInviteEvent.content.call_id,
(isVideo ? "video" : "voice"),
- callInviteEvent.sender);
+ callInviteEvent.sender,
+ callInviteEvent.content.party_id);
if (callInviteEvent.content.call_id.empty())
return;
- auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
- if (isOnCall() || roomInfo.member_count != 2) {
- emit newMessage(
- QString::fromStdString(callInviteEvent.room_id),
- CallHangUp{
- callInviteEvent.content.call_id, partyid_, "0", CallHangUp::Reason::InviteTimeOut});
- return;
+ if (callInviteEvent.sender == utils::localUser().toStdString()) {
+ if (callInviteEvent.content.party_id == partyid_)
+ return;
+ else {
+ if (callInviteEvent.content.invitee != utils::localUser().toStdString()) {
+ isOnCallOnOtherDevice_ = callInviteEvent.content.call_id;
+ 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();
- 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")
? QUrl(QStringLiteral("qrc:/media/media/ring.ogg"))
: QUrl::fromLocalFile(ringtone),
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;
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;
@@ -333,6 +461,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
void
CallManager::acceptInvite()
{
+ // if call was accepted/rejected elsewhere and m.call.select_answer is received
+ // before acceptInvite
if (!haveCallInvite_)
return;
@@ -341,7 +471,7 @@ CallManager::acceptInvite()
if (!session_.havePlugins(false, &errorMessage) ||
(callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) {
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
- hangUp();
+ hangUp(CallHangUp::Reason::UserMediaFailed);
return;
}
@@ -358,14 +488,30 @@ CallManager::acceptInvite()
}
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
CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent)
{
- if (callCandidatesEvent.sender == utils::localUser().toStdString())
+ if (callCandidatesEvent.sender == utils::localUser().toStdString() &&
+ callCandidatesEvent.content.party_id == partyid_)
return;
-
- nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}",
+ nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from ({}, {})",
callCandidatesEvent.content.call_id,
- callCandidatesEvent.sender);
+ callCandidatesEvent.sender,
+ callCandidatesEvent.content.party_id);
if (callid_ == callCandidatesEvent.content.call_id) {
if (isOnCall())
@@ -382,20 +528,31 @@ CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent)
void
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.sender);
+ callAnswerEvent.sender,
+ callAnswerEvent.content.party_id);
+ if (answerSelected_)
+ return;
if (callAnswerEvent.sender == utils::localUser().toStdString() &&
callid_ == callAnswerEvent.content.call_id) {
+ if (partyid_ == callAnswerEvent.content.party_id)
+ return;
+
if (!isOnCall()) {
emit ChatPage::instance()->showNotification(
QStringLiteral("Call answered on another device."));
stopRingtone();
haveCallInvite_ = false;
+ if (callPartyVersion_ != "1") {
+ isOnCallOnOtherDevice_ = callid_;
+ emit newCallDeviceState();
+ }
emit newInviteState();
}
- return;
+ if (callParty_ != utils::localUser())
+ return;
}
if (isOnCall() && callid_ == callAnswerEvent.content.call_id) {
@@ -405,21 +562,142 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
hangUp();
}
}
+ emit newMessage(
+ roomid_,
+ CallSelectAnswer{callid_, partyid_, callPartyVersion_, callAnswerEvent.content.party_id});
+ selectedpartyid_ = callAnswerEvent.content.party_id;
+ answerSelected_ = true;
}
void
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,
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();
}
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
CallManager::toggleMicMute()
{
session_.toggleMicMute();
@@ -467,7 +745,7 @@ CallManager::generateCallID()
}
void
-CallManager::clear()
+CallManager::clear(bool endAllCalls)
{
roomid_.clear();
callParty_.clear();
@@ -476,17 +754,23 @@ CallManager::clear()
callid_.clear();
callType_ = CallType::VOICE;
haveCallInvite_ = false;
+ answerSelected_ = false;
+ if (endAllCalls) {
+ isOnCallOnOtherDevice_ = "";
+ rejectCallPartyIDs_.clear();
+ emit newCallDeviceState();
+ }
emit newInviteState();
inviteSDP_.clear();
remoteICECandidates_.clear();
}
void
-CallManager::endCall()
+CallManager::endCall(bool endAllCalls)
{
stopRingtone();
session_.end();
- clear();
+ clear(endAllCalls);
}
void
diff --git a/src/voip/CallManager.h b/src/voip/CallManager.h
index 8f1615f8..3011444f 100644
--- a/src/voip/CallManager.h
+++ b/src/voip/CallManager.h
@@ -18,6 +18,7 @@
#include "WebRTCSession.h"
#include "mtx/events/collections.hpp"
#include "mtx/events/voip.hpp"
+#include <mtxclient/utils.hpp>
namespace mtx::responses {
struct TurnServer;
@@ -30,6 +31,7 @@ class CallManager final : public QObject
Q_OBJECT
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
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::State callState READ callState NOTIFY newCallState)
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
@@ -46,7 +48,9 @@ public:
CallManager(QObject *);
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::State callState() const { return session_.state(); }
QString callParty() const { return callParty_; }
@@ -67,8 +71,9 @@ public slots:
void toggleMicMute();
void toggleLocalPiP() { session_.toggleLocalPiP(); }
void acceptInvite();
- void
- hangUp(mtx::events::voip::CallHangUp::Reason = mtx::events::voip::CallHangUp::Reason::User);
+ void hangUp(
+ mtx::events::voip::CallHangUp::Reason = mtx::events::voip::CallHangUp::Reason::UserHangUp);
+ void rejectInvite();
QStringList windowList();
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::CallAnswer &);
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 newCallState();
+ void newCallDeviceState();
void micMuteChanged();
void devicesChanged();
void turnServerRetrieved(const mtx::responses::TurnServer &);
@@ -92,18 +101,23 @@ private:
QString callParty_;
QString callPartyDisplayName_;
QString callPartyAvatarUrl_;
+ std::string callPartyVersion_ = "1";
std::string callid_;
- std::string partyid_ = "";
- std::string invitee_ = "";
- const uint32_t timeoutms_ = 120000;
- webrtc::CallType callType_ = webrtc::CallType::VOICE;
- bool haveCallInvite_ = false;
+ std::string partyid_ = mtx::client::utils::random_token(8, false);
+ std::string selectedpartyid_ = "";
+ std::string invitee_ = "";
+ const uint32_t timeoutms_ = 120000;
+ webrtc::CallType callType_ = webrtc::CallType::VOICE;
+ bool haveCallInvite_ = false;
+ bool answerSelected_ = false;
+ std::string isOnCallOnOtherDevice_ = "";
std::string inviteSDP_;
std::vector<mtx::events::voip::CallCandidates::Candidate> remoteICECandidates_;
std::vector<std::string> turnURIs_;
QTimer turnServerTimer_;
QMediaPlayer player_;
std::vector<std::pair<QString, uint32_t>> windows_;
+ std::vector<std::string> rejectCallPartyIDs_;
template<typename T>
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::CallAnswer> &);
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 generateCallID();
QStringList devices(bool isVideo) const;
- void clear();
- void endCall();
+ void clear(bool endAllCalls = true);
+ void endCall(bool endAllCalls = true);
void playRingtone(const QUrl &ringtone, bool repeat);
void stopRingtone();
};
diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp
index 49a5cca0..b3e6bf26 100644
--- a/src/voip/WebRTCSession.cpp
+++ b/src/voip/WebRTCSession.cpp
@@ -700,6 +700,15 @@ WebRTCSession::acceptOffer(const std::string &sdp)
}
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
WebRTCSession::acceptAnswer(const std::string &sdp)
{
nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
@@ -1139,6 +1148,12 @@ WebRTCSession::createOffer(webrtc::CallType, uint32_t)
// clang-format on
bool
+WebRTCSession::acceptNegotiation(const std::string &)
+{
+ return false;
+}
+
+bool
WebRTCSession::acceptOffer(const std::string &)
{
return false;
diff --git a/src/voip/WebRTCSession.h b/src/voip/WebRTCSession.h
index a0ee9720..081611e5 100644
--- a/src/voip/WebRTCSession.h
+++ b/src/voip/WebRTCSession.h
@@ -64,6 +64,7 @@ public:
bool createOffer(webrtc::CallType, uint32_t shareWindowId);
bool acceptOffer(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> &);
bool isMicMuted() const;
|