diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index e1a9130ba..6f724bd76 100644
--- a/bin/dbus/cx.ring.Ring.CallManager.xml
+++ b/bin/dbus/cx.ring.Ring.CallManager.xml
@@ -334,6 +334,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -350,6 +360,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -365,11 +385,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp
index 2501a9391..a5b55ebca 100644
--- a/bin/dbus/dbuscallmanager.cpp
+++ b/bin/dbus/dbuscallmanager.cpp
@@ -183,6 +183,38 @@ DBusCallManager::setActiveParticipant(const std::string& accountId,
DRing::setActiveParticipant(accountId, confId, callId);
}
+void
+DBusCallManager::muteStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state)
+{
+ DRing::muteStream(accountId, confId, accountUri, deviceId, streamId, state);
+}
+
+void
+DBusCallManager::setActiveStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state)
+{
+ DRing::setActiveStream(accountId, confId, accountUri, deviceId, streamId, state);
+}
+
+void
+DBusCallManager::raiseHand(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const bool& state)
+{
+ DRing::raiseHand(accountId, confId, accountUri, deviceId, state);
+}
+
auto
DBusCallManager::isConferenceParticipant(const std::string& accountId, const std::string& call_id)
-> decltype(DRing::isConferenceParticipant(accountId, call_id))
@@ -388,9 +420,10 @@ DBusCallManager::muteParticipant(const std::string& accountId,
void
DBusCallManager::hangupParticipant(const std::string& accountId,
const std::string& confId,
- const std::string& peerId)
+ const std::string& peerId,
+ const std::string& deviceId)
{
- DRing::hangupParticipant(accountId, confId, peerId);
+ DRing::hangupParticipant(accountId, confId, peerId, deviceId);
}
void
diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h
index 76aaaa405..572d6251a 100644
--- a/bin/dbus/dbuscallmanager.h
+++ b/bin/dbus/dbuscallmanager.h
@@ -144,13 +144,32 @@ public:
const std::string& confId,
const std::string& peerId,
const bool& state);
+ void muteStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+ void setActiveStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+ void raiseHand(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const bool& state);
+ void hangupParticipant(const std::string& accountId,
+ const std::string& confId,
+ const std::string& peerId,
+ const std::string& deviceId);
+ // DEPRECATED
void muteParticipant(const std::string& accountId,
const std::string& confId,
const std::string& peerId,
const bool& state);
- void hangupParticipant(const std::string& accountId,
- const std::string& confId,
- const std::string& peerId);
void raiseParticipantHand(const std::string& accountId,
const std::string& confId,
const std::string& peerId,
diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i
index 6de910416..8ff6ff528 100644
--- a/bin/jni/callmanager.i
+++ b/bin/jni/callmanager.i
@@ -102,8 +102,29 @@ std::string getConferenceId(const std::string& accountId, const std::string& cal
std::map getConferenceDetails(const std::string& accountId, const std::string& callId);
std::vector> getConferenceInfos(const std::string& accountId, const std::string& confId);
void setModerator(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state);
+void muteStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+void setActiveStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+void hangupParticipant(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId);
+void raiseHand(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const bool& state);
+// DEPRECATED
void muteParticipant(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state);
-void hangupParticipant(const std::string& accountId, const std::string& confId, const std::string& peerId);
void raiseParticipantHand(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state);
/* File Playback methods */
diff --git a/bin/nodejs/callmanager.i b/bin/nodejs/callmanager.i
index 6de910416..8ff6ff528 100644
--- a/bin/nodejs/callmanager.i
+++ b/bin/nodejs/callmanager.i
@@ -102,8 +102,29 @@ std::string getConferenceId(const std::string& accountId, const std::string& cal
std::map getConferenceDetails(const std::string& accountId, const std::string& callId);
std::vector> getConferenceInfos(const std::string& accountId, const std::string& confId);
void setModerator(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state);
+void muteStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+void setActiveStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+void hangupParticipant(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId);
+void raiseHand(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const bool& state);
+// DEPRECATED
void muteParticipant(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state);
-void hangupParticipant(const std::string& accountId, const std::string& confId, const std::string& peerId);
void raiseParticipantHand(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state);
/* File Playback methods */
diff --git a/configure.ac b/configure.ac
index f44f6ac81..6be137048 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
-AC_INIT([Jami Daemon],[13.0.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[13.1.0],[jami@gnu.org],[jami])
dnl Clear the implicit flags that default to '-g -O2', otherwise they
dnl take precedence over the values we set via the
diff --git a/meson.build b/meson.build
index de2282be5..f62198119 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project('jami-daemon', ['c', 'cpp'],
- version: '13.0.0',
+ version: '13.1.0',
license: 'GPL3+',
default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
meson_version:'>= 0.56'
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e5957f90b..78cf16a5f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -22,6 +22,8 @@ list (APPEND Source_Files
"${CMAKE_CURRENT_SOURCE_DIR}/compiler_intrinsics.h"
"${CMAKE_CURRENT_SOURCE_DIR}/conference.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/conference.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/conference_protocol.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/conference_protocol.h"
"${CMAKE_CURRENT_SOURCE_DIR}/data_transfer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/data_transfer.h"
"${CMAKE_CURRENT_SOURCE_DIR}/enumclass_utils.h"
diff --git a/src/Makefile.am b/src/Makefile.am
index 64195d163..09ab4f5b7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -75,6 +75,7 @@ endif
libring_la_SOURCES = \
buildinfo.cpp \
conference.cpp \
+ conference_protocol.cpp \
account_factory.cpp \
call_factory.cpp \
preferences.cpp \
@@ -93,6 +94,7 @@ libring_la_SOURCES = \
ice_transport.h \
threadloop.h \
conference.h \
+ conference_protocol.h \
account_factory.h \
call_factory.h \
preferences.h \
diff --git a/src/call.cpp b/src/call.cpp
index f1df75990..4fba51b2f 100644
--- a/src/call.cpp
+++ b/src/call.cpp
@@ -655,6 +655,10 @@ Call::setConferenceInfo(const std::string& msg)
newInfo.emplace_back(pInfo);
}
}
+ if (json.isMember("v")) {
+ newInfo.v = json["v"].asInt();
+ peerConfProtocol_ = newInfo.v;
+ }
if (json.isMember("w"))
newInfo.w = json["w"].asInt();
if (json.isMember("h"))
diff --git a/src/call.h b/src/call.h
index 55caa1430..7b7f05000 100644
--- a/src/call.h
+++ b/src/call.h
@@ -436,6 +436,8 @@ public:
virtual void monitor() const = 0;
+ int conferenceProtocolVersion() const { return peerConfProtocol_; }
+
protected:
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
@@ -516,6 +518,9 @@ protected:
///< MultiDevice: message received by subcall to merged yet
MsgList pendingInMessages_;
+
+ /// Supported conference protocol version
+ int peerConfProtocol_ {0};
};
// Helpers
diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp
index ed257f67f..1f9771c6a 100644
--- a/src/client/callmanager.cpp
+++ b/src/client/callmanager.cpp
@@ -27,12 +27,14 @@
#include "client/ring_signal.h"
#include "sip/sipvoiplink.h"
+#include "sip/sipcall.h"
#include "audio/audiolayer.h"
#include "media/media_attribute.h"
#include "string_utils.h"
#include "logger.h"
#include "manager.h"
+#include "jamidht/jamiaccount.h"
#include "smartools.h"
@@ -211,22 +213,6 @@ setConferenceLayout(const std::string& accountId, const std::string& confId, uin
}
}
-void
-setActiveParticipant(const std::string& accountId,
- const std::string& confId,
- const std::string& participant)
-{
- if (const auto account = jami::Manager::instance().getAccount(accountId)) {
- if (auto conf = account->getConference(confId)) {
- conf->setActiveParticipant(participant);
- } else if (auto call = account->getCall(confId)) {
- Json::Value root;
- root["activeParticipant"] = participant;
- call->sendConfOrder(root);
- }
- }
-}
-
bool
isConferenceParticipant(const std::string& accountId, const std::string& callId)
{
@@ -270,7 +256,7 @@ detachLocalParticipant()
}
bool
-detachParticipant(const std::string& accountId, const std::string& callId)
+detachParticipant(const std::string&, const std::string& callId)
{
return jami::Manager::instance().detachParticipant(callId);
}
@@ -514,6 +500,7 @@ muteParticipant(const std::string& accountId,
const std::string& peerId,
const bool& state)
{
+ JAMI_ERR() << "muteParticipant is deprecated, please use muteStream";
if (const auto account = jami::Manager::instance().getAccount(accountId)) {
if (auto conf = account->getConference(confId)) {
conf->muteParticipant(peerId, state);
@@ -527,27 +514,139 @@ muteParticipant(const std::string& accountId,
}
void
-hangupParticipant(const std::string& accountId,
- const std::string& confId,
- const std::string& participant)
+muteStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state)
{
if (const auto account = jami::Manager::instance().getAccount(accountId)) {
if (auto conf = account->getConference(confId)) {
- conf->hangupParticipant(participant);
+ conf->muteStream(accountUri, deviceId, streamId, state);
+ } else if (auto call = account->getCall(confId)) {
+ if (call->conferenceProtocolVersion() == 1) {
+ Json::Value sinkVal;
+ sinkVal["muteAudio"] = state;
+ Json::Value mediasObj;
+ mediasObj[streamId] = sinkVal;
+ Json::Value deviceVal;
+ deviceVal["medias"] = mediasObj;
+ Json::Value deviceObj;
+ deviceObj[deviceId] = deviceVal;
+ Json::Value accountVal;
+ deviceVal["devices"] = deviceObj;
+ Json::Value root;
+ root[accountUri] = deviceVal;
+ root["version"] = 1;
+ call->sendConfOrder(root);
+ } else if (call->conferenceProtocolVersion() == 0) {
+ Json::Value root;
+ root["muteParticipant"] = accountUri;
+ root["muteState"] = state ? jami::TRUE_STR : jami::FALSE_STR;
+ call->sendConfOrder(root);
+ }
+ }
+ }
+}
+
+void
+setActiveParticipant(const std::string& accountId,
+ const std::string& confId,
+ const std::string& participant)
+{
+ JAMI_ERR() << "setActiveParticipant is deprecated, please use setActiveStream";
+ if (const auto account = jami::Manager::instance().getAccount(accountId)) {
+ if (auto conf = account->getConference(confId)) {
+ conf->setActiveParticipant(participant);
} else if (auto call = account->getCall(confId)) {
Json::Value root;
- root["hangupParticipant"] = participant;
+ root["activeParticipant"] = participant;
call->sendConfOrder(root);
}
}
}
+void
+setActiveStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state)
+{
+ if (const auto account = jami::Manager::instance().getAccount(accountId)) {
+ if (auto conf = account->getConference(confId)) {
+ // TODO, as for now the videoMixer doesn't have the streamId
+ if (deviceId == account->currentDeviceId() && accountUri == account->getUsername()) {
+ conf->setActiveStream("", state);
+ } else if (auto call = std::static_pointer_cast(
+ conf->getCallFromPeerID(accountUri))) {
+ conf->setActiveStream(call->getCallId(), state);
+ }
+ // conf->setActiveStream(streamId, state);
+ } else if (auto call = account->getCall(confId)) {
+ if (call->conferenceProtocolVersion() == 1) {
+ Json::Value sinkVal;
+ sinkVal["active"] = state;
+ Json::Value mediasObj;
+ mediasObj[streamId] = sinkVal;
+ Json::Value deviceVal;
+ deviceVal["medias"] = mediasObj;
+ Json::Value deviceObj;
+ deviceObj[deviceId] = deviceVal;
+ Json::Value accountVal;
+ deviceVal["devices"] = deviceObj;
+ Json::Value root;
+ root[accountUri] = deviceVal;
+ root["version"] = 1;
+ call->sendConfOrder(root);
+ } else if (call->conferenceProtocolVersion() == 0) {
+ Json::Value root;
+ root["activeParticipant"] = accountUri;
+ call->sendConfOrder(root);
+ }
+ }
+ }
+}
+
+void
+hangupParticipant(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId)
+{
+ if (const auto account = jami::Manager::instance().getAccount(accountId)) {
+ if (auto conf = account->getConference(confId)) {
+ conf->hangupParticipant(accountUri, deviceId);
+ } else if (auto call = std::static_pointer_cast(account->getCall(confId))) {
+ if (call->conferenceProtocolVersion() == 1) {
+ Json::Value deviceVal;
+ deviceVal["hangup"] = jami::TRUE_STR;
+ Json::Value deviceObj;
+ deviceObj[deviceId] = deviceVal;
+ Json::Value accountVal;
+ deviceVal["devices"] = deviceObj;
+ Json::Value root;
+ root[accountUri] = deviceVal;
+ root["version"] = 1;
+ call->sendConfOrder(root);
+ } else if (call->conferenceProtocolVersion() == 0) {
+ Json::Value root;
+ root["hangupParticipant"] = accountUri;
+ call->sendConfOrder(root);
+ }
+ }
+ }
+}
+
void
raiseParticipantHand(const std::string& accountId,
const std::string& confId,
const std::string& peerId,
const bool& state)
{
+ JAMI_ERR() << "raiseParticipantHand is deprecated, please use raiseHand";
if (const auto account = jami::Manager::instance().getAccount(accountId)) {
if (auto conf = account->getConference(confId)) {
conf->setHandRaised(peerId, state);
@@ -560,4 +659,39 @@ raiseParticipantHand(const std::string& accountId,
}
}
+void
+raiseHand(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const bool& state)
+{
+ if (const auto account = jami::Manager::instance().getAccount(accountId)) {
+ if (auto conf = account->getConference(confId)) {
+ conf->setHandRaised(accountUri, state);
+ } else if (auto call = std::static_pointer_cast(account->getCall(confId))) {
+ if (call->conferenceProtocolVersion() == 1) {
+ Json::Value deviceVal;
+ deviceVal["raiseHand"] = state;
+ Json::Value deviceObj;
+ std::string device = deviceId.empty() ? std::string(account->currentDeviceId())
+ : deviceId;
+ deviceObj[device] = deviceVal;
+ Json::Value accountVal;
+ deviceVal["devices"] = deviceObj;
+ Json::Value root;
+ std::string uri = accountUri.empty() ? account->getUsername() : accountUri;
+ root[uri] = deviceVal;
+ root["version"] = 1;
+ call->sendConfOrder(root);
+ } else if (call->conferenceProtocolVersion() == 0) {
+ Json::Value root;
+ root["handRaised"] = account->getUsername();
+ root["handState"] = state ? jami::TRUE_STR : jami::FALSE_STR;
+ call->sendConfOrder(root);
+ }
+ }
+ }
+}
+
} // namespace DRing
diff --git a/src/conference.cpp b/src/conference.cpp
index 9ea5af27e..f4f69e1c8 100644
--- a/src/conference.cpp
+++ b/src/conference.cpp
@@ -101,6 +101,12 @@ Conference::Conference(const std::shared_ptr& account)
if (!acc)
return;
ConfInfo newInfo;
+ {
+ std::lock_guard lock(shared->confInfoMutex_);
+ newInfo.w = shared->confInfo_.w;
+ newInfo.h = shared->confInfo_.h;
+ newInfo.layout = shared->confInfo_.layout;
+ }
auto hostAdded = false;
// Handle participants showing their video
std::unique_lock lk(shared->videoToCallMtx_);
@@ -116,37 +122,42 @@ Conference::Conference(const std::shared_ptr& account)
if (auto* transport = call->getTransport())
deviceId = transport->deviceId();
}
- if (auto videoMixer = shared->videoMixer_)
- active = videoMixer->verifyActive(info.id);
std::string_view peerId = string_remove_suffix(uri, '@');
auto isModerator = shared->isModerator(peerId);
auto isHandRaised = shared->isHandRaised(peerId);
- auto isModeratorMuted = shared->isMuted(peerId);
+ auto isModeratorMuted = shared->isMuted(info.id);
auto sinkId = shared->getConfId() + peerId;
+ if (auto videoMixer = shared->videoMixer_)
+ active = videoMixer->verifyActive(info.id); // TODO streamId
newInfo.emplace_back(ParticipantInfo {std::move(uri),
- deviceId,
- std::move(sinkId),
- active,
- info.x,
- info.y,
- info.w,
- info.h,
- !info.hasVideo,
- isLocalMuted,
- isModeratorMuted,
- isModerator,
- isHandRaised});
+ deviceId,
+ std::move(sinkId),
+ active,
+ info.x,
+ info.y,
+ info.w,
+ info.h,
+ !info.hasVideo,
+ isLocalMuted,
+ isModeratorMuted,
+ isModerator,
+ isHandRaised});
} else {
auto it = shared->videoToCall_.find(info.source);
if (it == shared->videoToCall_.end())
it = shared->videoToCall_.emplace_hint(it, info.source, std::string());
// If not local
+ auto isModeratorMuted = false;
if (!it->second.empty()) {
// Retrieve calls participants
// TODO: this is a first version, we assume that the peer is not
// a master of a conference and there is only one remote
// In the future, we should retrieve confInfo from the call
// To merge layouts informations
+ // TODO sinkId
+ auto isModeratorMuted = shared->isMuted(it->second);
+ if (auto videoMixer = shared->videoMixer_)
+ active = videoMixer->verifyActive(it->second);
if (auto call = std::dynamic_pointer_cast(getCall(it->second))) {
uri = call->getPeerNumber();
isLocalMuted = call->isPeerMuted();
@@ -154,9 +165,8 @@ Conference::Conference(const std::shared_ptr& account)
deviceId = transport->deviceId();
}
}
- if (auto videoMixer = shared->videoMixer_)
- active = videoMixer->verifyActive(info.source);
std::string_view peerId = string_remove_suffix(uri, '@');
+ // TODO (another patch): use deviceId instead of peerId as specified in protocol
auto isModerator = shared->isModerator(peerId);
if (uri.empty() && !hostAdded) {
hostAdded = true;
@@ -164,22 +174,23 @@ Conference::Conference(const std::shared_ptr& account)
deviceId = acc->currentDeviceId();
isLocalMuted = shared->isMediaSourceMuted(MediaType::MEDIA_AUDIO);
}
- auto isHandRaised = shared->isHandRaised(peerId);
- auto isModeratorMuted = shared->isMuted(peerId);
+ auto isHandRaised = shared->isHandRaised(deviceId);
auto sinkId = shared->getConfId() + peerId;
+ if (auto videoMixer = shared->videoMixer_)
+ active |= videoMixer->verifyActive(info.source);
newInfo.emplace_back(ParticipantInfo {std::move(uri),
- deviceId,
- std::move(sinkId),
- active,
- info.x,
- info.y,
- info.w,
- info.h,
- !info.hasVideo,
- isLocalMuted,
- isModeratorMuted,
- isModerator,
- isHandRaised});
+ deviceId,
+ std::move(sinkId),
+ active,
+ info.x,
+ info.y,
+ info.w,
+ info.h,
+ !info.hasVideo,
+ isLocalMuted,
+ isModeratorMuted,
+ isModerator,
+ isHandRaised});
}
}
if (auto videoMixer = shared->videoMixer_) {
@@ -199,6 +210,38 @@ Conference::Conference(const std::shared_ptr& account)
});
});
#endif
+
+ parser_.onVersion([&](uint32_t) {}); // TODO
+ parser_.onCheckAuthorization([&](std::string_view peerId) { return isModerator(peerId); });
+ parser_.onHangupParticipant([&](const auto& accountUri, const auto& deviceId) {
+ hangupParticipant(accountUri, deviceId);
+ });
+ parser_.onRaiseHand(
+ [&](const auto& deviceId, bool state) { setHandRaised(deviceId, state); });
+ parser_.onSetActiveStream([&](const auto& accountUri, const auto& deviceId, bool state) {
+ // TODO replace per streamId
+ if (auto call = getCallWith(accountUri, deviceId))
+ setHandRaised(call->getCallId(), state);
+ });
+ parser_.onMuteStreamAudio
+ (
+ [&](const auto& accountUri, const auto& deviceId, const auto& streamId, bool state) {
+ muteStream(accountUri, deviceId, streamId, state);
+ });
+ parser_.onSetLayout([&](int layout) { setLayout(layout); });
+
+ // Version 0, deprecated
+ parser_.onKickParticipant(
+ [&](const auto& participantId) { hangupParticipant(participantId); });
+ parser_.onSetActiveParticipant(
+ [&](const auto& participantId) { setActiveParticipant(participantId); });
+ parser_.onMuteParticipant(
+ [&](const auto& participantId, bool state) { muteParticipant(participantId, state); });
+ parser_.onRaiseHandUri([&](const auto& uri, bool state) {
+ if (auto call = std::dynamic_pointer_cast(getCallFromPeerID(uri)))
+ if (auto* transport = call->getTransport())
+ setHandRaised(std::string(transport->deviceId()), state);
+ });
}
Conference::~Conference()
@@ -336,7 +379,11 @@ Conference::createConfAVStreams()
// Preview
if (auto& videoPreview = videoMixer_->getVideoLocal()) {
auto previewSubject = std::make_shared(pluginVideoMap_);
- StreamData previewStreamData {getConfId(), false, StreamType::video, getConfId(), accountId};
+ StreamData previewStreamData {getConfId(),
+ false,
+ StreamType::video,
+ getConfId(),
+ accountId};
createConfAVStream(previewStreamData, *videoPreview, previewSubject);
}
}
@@ -623,7 +670,7 @@ Conference::addParticipant(const std::string& participant_id)
// Check if participant was muted before conference
if (auto call = getCall(participant_id)) {
if (call->isPeerMuted()) {
- participantsMuted_.emplace(string_remove_suffix(call->getPeerNumber(), '@'));
+ participantsMuted_.emplace(call->getCallId());
}
// NOTE:
@@ -662,8 +709,9 @@ Conference::addParticipant(const std::string& participant_id)
// call, it must be listed in the audioonlylist.
auto mediaList = call->getMediaAttributeList();
if (not MediaAttribute::hasMediaType(mediaList, MediaType::MEDIA_VIDEO)) {
- if (videoMixer_)
+ if (videoMixer_) {
videoMixer_->addAudioOnlySource(call->getCallId());
+ }
}
call->enterConference(shared_from_this());
// Continue the recording for the conference if one participant was recording
@@ -691,14 +739,14 @@ Conference::setActiveParticipant(const std::string& participant_id)
if (!videoMixer_)
return;
if (isHost(participant_id)) {
- videoMixer_->setActiveHost();
+ videoMixer_->addActiveHost();
return;
}
if (auto call = getCallFromPeerID(participant_id)) {
if (auto videoRecv = call->getReceiveVideoFrameActiveWriter())
- videoMixer_->setActiveParticipant(videoRecv.get());
+ videoMixer_->setActiveStream(videoRecv.get());
else
- videoMixer_->setActiveParticipant(call->getCallId());
+ videoMixer_->setActiveStream(call->getCallId());
return;
}
@@ -709,7 +757,32 @@ Conference::setActiveParticipant(const std::string& participant_id)
return;
}
// Unset active participant by default
- videoMixer_->resetActiveParticipant();
+ videoMixer_->resetActiveStream();
+#endif
+}
+
+void
+Conference::setActiveStream(const std::string& streamId, bool state)
+{
+ // TODO BUG: for now activeStream is the callId, and should be the sink!
+#ifdef ENABLE_VIDEO
+ if (!videoMixer_)
+ return;
+ if (state) {
+ // TODO remove
+ if (streamId.empty()) {
+ videoMixer_->addActiveHost();
+ } else if (auto call = getCall(streamId)) {
+ if (auto videoRecv = call->getReceiveVideoFrameActiveWriter())
+ videoMixer_->setActiveStream(videoRecv.get());
+ else
+ videoMixer_->setActiveStream(call->getCallId());
+ return;
+ }
+ // TODO videoMixer_->setActiveStream(sinkId);
+ } else {
+ videoMixer_->resetActiveStream();
+ }
#endif
}
@@ -717,21 +790,17 @@ void
Conference::setLayout(int layout)
{
#ifdef ENABLE_VIDEO
- switch (layout) {
- case 0:
- videoMixer_->setVideoLayout(video::Layout::GRID);
- // The layout shouldn't have an active participant
- videoMixer_->resetActiveParticipant();
- break;
- case 1:
- videoMixer_->setVideoLayout(video::Layout::ONE_BIG_WITH_SMALL);
- break;
- case 2:
- videoMixer_->setVideoLayout(video::Layout::ONE_BIG);
- break;
- default:
- break;
+ if (layout < 0 || layout > 2) {
+ JAMI_ERR("Unknown layout %u", layout);
+ return;
}
+ if (!videoMixer_)
+ return;
+ {
+ std::lock_guard lk(confInfoMutex_);
+ confInfo_.layout = layout;
+ }
+ videoMixer_->setVideoLayout(static_cast(layout));
#endif
}
@@ -754,6 +823,8 @@ ConfInfo::toString() const
}
val["w"] = w;
val["h"] = h;
+ val["v"] = v;
+ val["layout"] = layout;
return Json::writeString(Json::StreamWriterBuilder {}, val);
}
@@ -833,11 +904,15 @@ Conference::removeParticipant(const std::string& participant_id)
return;
}
if (auto call = getCall(participant_id)) {
- if (videoMixer_->verifyActive(call->getCallId()))
- videoMixer_->resetActiveParticipant();
- participantsMuted_.erase(std::string(string_remove_suffix(call->getPeerNumber(), '@')));
- handsRaised_.erase(std::string(string_remove_suffix(call->getPeerNumber(), '@')));
+ auto peerId = std::string(string_remove_suffix(call->getPeerNumber(), '@'));
+ participantsMuted_.erase(call->getCallId());
+ handsRaised_.erase(peerId);
#ifdef ENABLE_VIDEO
+ auto sinkId = getConfId() + peerId;
+ // Remove if active
+ // TODO if (videoMixer_->verifyActive(sinkId))
+ if (videoMixer_->verifyActive(participant_id))
+ videoMixer_->resetActiveStream();
call->exitConference();
if (call->isPeerRecording())
call->peerRecording(false);
@@ -857,7 +932,7 @@ Conference::attachLocalParticipant()
auto& rbPool = Manager::instance().getRingBufferPool();
for (const auto& participant : getParticipantList()) {
if (auto call = Manager::instance().getCallFromCallID(participant)) {
- if (isMuted(string_remove_suffix(call->getPeerNumber(), '@')))
+ if (isMuted(call->getCallId()))
rbPool.bindHalfDuplexOut(participant, RingBufferPool::DEFAULT_ID);
else
rbPool.bindCallID(participant, RingBufferPool::DEFAULT_ID);
@@ -927,7 +1002,7 @@ Conference::bindParticipant(const std::string& participant_id)
if (participant_id != item) {
// Do not attach muted participants
if (auto call = Manager::instance().getCallFromCallID(item)) {
- if (isMuted(string_remove_suffix(call->getPeerNumber(), '@')))
+ if (isMuted(call->getCallId()))
rbPool.bindHalfDuplexOut(item, participant_id);
else
rbPool.bindCallID(participant_id, item);
@@ -963,7 +1038,7 @@ Conference::bindHost()
for (const auto& item : getParticipantList()) {
if (auto call = Manager::instance().getCallFromCallID(item)) {
- if (isMuted(string_remove_suffix(call->getPeerNumber(), '@')))
+ if (isMuted(call->getCallId()))
continue;
rbPool.bindCallID(item, RingBufferPool::DEFAULT_ID);
rbPool.flush(RingBufferPool::DEFAULT_ID);
@@ -1026,7 +1101,11 @@ Conference::switchInput(const std::string& input)
// Preview
if (auto& videoPreview = mixer->getVideoLocal()) {
auto previewSubject = std::make_shared(pluginVideoMap_);
- StreamData previewStreamData {getConfId(), false, StreamType::video, getConfId(), getAccountId()};
+ StreamData previewStreamData {getConfId(),
+ false,
+ StreamType::video,
+ getConfId(),
+ getAccountId()};
createConfAVStream(previewStreamData, *videoPreview, previewSubject, true);
}
#endif
@@ -1113,7 +1192,7 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder)
{
// Check if the peer is a master
if (auto call = Manager::instance().getCallFromCallID(callId)) {
- auto peerID = string_remove_suffix(call->getPeerNumber(), '@');
+ auto peerId = string_remove_suffix(call->getPeerNumber(), '@');
std::string err;
Json::Value root;
@@ -1121,41 +1200,13 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder)
auto reader = std::unique_ptr(rbuilder.newCharReader());
if (!reader->parse(confOrder.c_str(), confOrder.c_str() + confOrder.size(), &root, &err)) {
JAMI_WARN("Couldn't parse conference order from %.*s",
- (int) peerID.size(),
- peerID.data());
+ (int) peerId.size(),
+ peerId.data());
return;
}
- if (root.isMember("handRaised")) {
- auto state = root["handState"].asString() == "true";
- if (peerID == root["handRaised"].asString()) {
- // In this case, the user want to change their state
- setHandRaised(root["handRaised"].asString(), state);
- } else if (!state && isModerator(peerID)) {
- // In this case a moderator can lower the hand
- setHandRaised(root["handRaised"].asString(), state);
- }
- }
-
- if (!isModerator(peerID)) {
- JAMI_WARN("Received conference order from a non master (%.*s)",
- (int) peerID.size(),
- peerID.data());
- return;
- }
- if (isVideoEnabled() and root.isMember("layout")) {
- setLayout(root["layout"].asUInt());
- }
- if (root.isMember("activeParticipant")) {
- setActiveParticipant(root["activeParticipant"].asString());
- }
- if (root.isMember("muteParticipant") and root.isMember("muteState")) {
- muteParticipant(root["muteParticipant"].asString(),
- root["muteState"].asString() == "true");
- }
- if (root.isMember("hangupParticipant")) {
- hangupParticipant(root["hangupParticipant"].asString());
- }
+ parser_.initData(std::move(root), peerId);
+ parser_.parse();
}
}
@@ -1267,9 +1318,63 @@ Conference::foreachCall(const std::function& ca
}
bool
-Conference::isMuted(std::string_view uri) const
+Conference::isMuted(std::string_view callId) const
{
- return participantsMuted_.find(uri) != participantsMuted_.end();
+ return participantsMuted_.find(callId) != participantsMuted_.end();
+}
+
+void
+Conference::muteStream(const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string&,
+ const bool& state)
+{
+ if (auto acc = std::dynamic_pointer_cast(account_.lock())) {
+ if (accountUri == acc->getUsername() && deviceId == acc->currentDeviceId()) {
+ muteHost(state);
+ } else if (auto call = getCallWith(accountUri, deviceId)) {
+ muteCall(call->getCallId(), state);
+ } else {
+ JAMI_WARN("No call with %s - %s", accountUri.c_str(), deviceId.c_str());
+ }
+ }
+}
+
+void
+Conference::muteHost(bool state)
+{
+ auto isHostMuted = isMuted("host"sv);
+ if (state and not isHostMuted) {
+ participantsMuted_.emplace("host"sv);
+ if (not isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
+ JAMI_DBG("Mute host");
+ unbindHost();
+ }
+ } else if (not state and isHostMuted) {
+ participantsMuted_.erase("host");
+ if (not isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
+ JAMI_DBG("Unmute host");
+ bindHost();
+ }
+ }
+ updateMuted();
+}
+
+void
+Conference::muteCall(const std::string& callId, bool state)
+{
+ auto isPartMuted = isMuted(callId);
+ if (state and not isPartMuted) {
+ JAMI_DBG("Mute participant %.*s", (int) callId.size(), callId.data());
+ participantsMuted_.emplace(callId);
+ unbindParticipant(callId);
+ updateMuted();
+ } else if (not state and isPartMuted) {
+ JAMI_DBG("Unmute participant %.*s", (int) callId.size(), callId.data());
+ participantsMuted_.erase(callId);
+ bindParticipant(callId);
+ updateMuted();
+ }
}
void
@@ -1295,42 +1400,12 @@ Conference::muteParticipant(const std::string& participant_id, const bool& state
}
}
- // Moderator mute host
- if (isHost(participant_id)) {
- auto isHostMuted = isMuted("host"sv);
- if (state and not isHostMuted) {
- participantsMuted_.emplace("host"sv);
- if (not isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
- JAMI_DBG("Mute host");
- unbindHost();
- }
- } else if (not state and isHostMuted) {
- participantsMuted_.erase("host");
- if (not isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
- JAMI_DBG("Unmute host");
- bindHost();
- }
- }
- updateMuted();
- return;
- }
-
- // Mute participant
- if (auto call = getCallFromPeerID(participant_id)) {
- auto isPartMuted = isMuted(participant_id);
- if (state and not isPartMuted) {
- JAMI_DBG("Mute participant %.*s", (int) participant_id.size(), participant_id.data());
- participantsMuted_.emplace(std::string(participant_id));
- unbindParticipant(call->getCallId());
- updateMuted();
- } else if (not state and isPartMuted) {
- JAMI_DBG("Unmute participant %.*s", (int) participant_id.size(), participant_id.data());
- participantsMuted_.erase(std::string(participant_id));
- bindParticipant(call->getCallId());
- updateMuted();
- }
- return;
- }
+ // NOTE: For now we only have one audio per call, and no way to only
+ // mute one stream
+ if (isHost(participant_id))
+ muteHost(state);
+ else if (auto call = getCallFromPeerID(participant_id))
+ muteCall(call->getCallId(), state);
}
void
@@ -1338,15 +1413,13 @@ Conference::updateMuted()
{
std::lock_guard lk(confInfoMutex_);
for (auto& info : confInfo_) {
- auto peerID = string_remove_suffix(info.uri, '@');
- if (peerID.empty()) {
- peerID = "host"sv;
- info.audioModeratorMuted = isMuted(peerID);
+ if (info.uri.empty()) {
+ info.audioModeratorMuted = isMuted("host"sv);
info.audioLocalMuted = isMediaSourceMuted(MediaType::MEDIA_AUDIO);
- } else {
- info.audioModeratorMuted = isMuted(peerID);
- if (auto call = getCallFromPeerID(peerID))
- info.audioLocalMuted = call->isPeerMuted();
+ } else if (auto call = getCallWith(std::string(string_remove_suffix(info.uri, '@')),
+ info.device)) {
+ info.audioModeratorMuted = isMuted(call->getCallId());
+ info.audioLocalMuted = call->isPeerMuted();
}
}
sendConferenceInfos();
@@ -1412,36 +1485,34 @@ Conference::updateConferenceInfo(ConfInfo confInfo)
}
void
-Conference::hangupParticipant(const std::string& participant_id)
+Conference::hangupParticipant(const std::string& accountUri, const std::string& deviceId)
{
- if (isHost(participant_id)) {
- Manager::instance().detachLocalParticipant(shared_from_this());
- return;
- }
-
- if (auto call = getCallFromPeerID(participant_id)) {
- if (auto account = call->getAccount().lock()) {
- Manager::instance().hangupCall(account->getAccountID(), call->getCallId());
- }
- return;
- }
-
- // Transfert remote participant hangup
- auto remoteHost = findHostforRemoteParticipant(participant_id);
- if (remoteHost.empty()) {
- JAMI_WARN("Can't hangup %s, peer not found", participant_id.c_str());
- return;
- }
- if (auto call = getCallFromPeerID(string_remove_suffix(remoteHost, '@'))) {
- auto w = call->getAccount();
- auto account = w.lock();
- if (!account)
+ if (auto acc = std::dynamic_pointer_cast(account_.lock())) {
+ if (deviceId.empty()) {
+ // If deviceId is empty, hangup all calls with device
+ while (auto call = getCallFromPeerID(accountUri)) {
+ Manager::instance().hangupCall(acc->getAccountID(), call->getCallId());
+ }
return;
-
- Json::Value root;
- root["hangupParticipant"] = participant_id;
- call->sendConfOrder(root);
- return;
+ } else {
+ if (accountUri == acc->getUsername() && deviceId == acc->currentDeviceId()) {
+ Manager::instance().detachLocalParticipant(shared_from_this());
+ return;
+ } else if (auto call = getCallWith(accountUri, deviceId)) {
+ Manager::instance().hangupCall(acc->getAccountID(), call->getCallId());
+ return;
+ }
+ }
+ // Else, it may be a remote host
+ auto remoteHost = findHostforRemoteParticipant(accountUri, deviceId);
+ if (remoteHost.empty()) {
+ JAMI_WARN("Can't hangup %s, peer not found", accountUri.c_str());
+ return;
+ }
+ if (auto call = getCallFromPeerID(string_remove_suffix(remoteHost, '@'))) {
+ // Forward to the remote host.
+ DRing::hangupParticipant(acc->getAccountID(), call->getCallId(), accountUri, deviceId);
+ }
}
}
@@ -1580,11 +1651,11 @@ Conference::mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI)
}
std::string_view
-Conference::findHostforRemoteParticipant(std::string_view uri)
+Conference::findHostforRemoteParticipant(std::string_view uri, std::string_view deviceId)
{
for (const auto& host : remoteHosts_) {
for (const auto& p : host.second) {
- if (uri == string_remove_suffix(p.uri, '@'))
+ if (uri == string_remove_suffix(p.uri, '@') && (deviceId == "" || deviceId == p.device))
return host.first;
}
}
@@ -1603,4 +1674,19 @@ Conference::getCallFromPeerID(std::string_view peerID)
return nullptr;
}
+std::shared_ptr
+Conference::getCallWith(const std::string& accountUri, const std::string& deviceId)
+{
+ for (const auto& p : getParticipantList()) {
+ if (auto call = std::dynamic_pointer_cast(getCall(p))) {
+ auto* transport = call->getTransport();
+ if (accountUri == string_remove_suffix(call->getPeerNumber(), '@') && transport
+ && deviceId == transport->deviceId()) {
+ return call;
+ }
+ }
+ }
+ return {};
+}
+
} // namespace jami
diff --git a/src/conference.h b/src/conference.h
index 86d75aeed..4e28ca25d 100644
--- a/src/conference.h
+++ b/src/conference.h
@@ -33,6 +33,7 @@
#include
#include "audio/audio_input.h"
+#include "conference_protocol.h"
#include "media_attribute.h"
#include
@@ -147,6 +148,8 @@ struct ConfInfo : public std::vector
{
int h {0};
int w {0};
+ int v {1}; // Supported conference protocol version
+ int layout {0};
friend bool operator==(const ConfInfo& c1, const ConfInfo& c2)
{
@@ -323,6 +326,7 @@ public:
void switchSecondaryInput(const std::string& input);
void setActiveParticipant(const std::string& participant_id);
+ void setActiveStream(const std::string& streamId, bool state);
void setLayout(int layout);
void onConfOrder(const std::string& callId, const std::string& order);
@@ -345,14 +349,31 @@ public:
void updateConferenceInfo(ConfInfo confInfo);
void setModerator(const std::string& uri, const bool& state);
+ void hangupParticipant(const std::string& accountUri, const std::string& deviceId = "");
void setHandRaised(const std::string& uri, const bool& state);
+
void muteParticipant(const std::string& uri, const bool& state);
- void hangupParticipant(const std::string& participant_id);
- void updateMuted();
void muteLocalHost(bool is_muted, const std::string& mediaType);
bool isRemoteParticipant(const std::string& uri);
void mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI);
+ /**
+ * The client shows one tile per stream (video/audio related to a media)
+ * @note for now, in conferences we can only mute the audio of a call
+ * @todo add a track (audio OR video) parameter to know what we want to mute
+ * @param accountUri Account of the stream
+ * @param deviceId Device of the stream
+ * @param streamId Stream to mute
+ * @param state True to mute, false to unmute
+ */
+ void muteStream(const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+ void updateMuted();
+
+ std::shared_ptr getCallFromPeerID(std::string_view peerID);
+
private:
std::weak_ptr weak()
{
@@ -364,6 +385,8 @@ private:
bool isHandRaised(std::string_view uri) const;
void updateModerators();
void updateHandsRaised();
+ void muteHost(bool state);
+ void muteCall(const std::string& callId, bool state);
void foreachCall(const std::function& call)>& cb);
@@ -424,8 +447,10 @@ private:
#ifdef ENABLE_VIDEO
void resizeRemoteParticipants(ConfInfo& confInfo, std::string_view peerURI);
#endif
- std::string_view findHostforRemoteParticipant(std::string_view uri);
- std::shared_ptr getCallFromPeerID(std::string_view peerID);
+ std::string_view findHostforRemoteParticipant(std::string_view uri,
+ std::string_view deviceId = "");
+
+ std::shared_ptr getCallWith(const std::string& accountUri, const std::string& deviceId);
std::mutex sinksMtx_ {};
@@ -466,6 +491,8 @@ private:
std::mutex avStreamsMtx_ {};
std::map> confAVStreams;
#endif // ENABLE_PLUGIN
+
+ ConfProtocolParser parser_;
};
} // namespace jami
diff --git a/src/conference_protocol.cpp b/src/conference_protocol.cpp
new file mode 100644
index 000000000..e7652487e
--- /dev/null
+++ b/src/conference_protocol.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * Author: Sébastien Blin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "conference_protocol.h"
+
+#include "string_utils.h"
+
+namespace jami {
+
+namespace ProtocolKeys {
+
+constexpr static const char* PROTOVERSION = "version";
+constexpr static const char* LAYOUT = "layout";
+// V0
+constexpr static const char* HANDRAISED = "handRaised";
+constexpr static const char* HANDSTATE = "handState";
+constexpr static const char* ACTIVEPART = "activeParticipant";
+constexpr static const char* MUTEPART = "muteParticipant";
+constexpr static const char* MUTESTATE = "muteState";
+constexpr static const char* HANGUPPART = "hangupParticipant";
+// V1
+constexpr static const char* DEVICES = "devices";
+constexpr static const char* MEDIAS = "medias";
+constexpr static const char* RAISEHAND = "raiseHand";
+constexpr static const char* HANGUP = "hangup";
+constexpr static const char* ACTIVE = "active";
+constexpr static const char* MUTEAUDIO = "muteAudio";
+// Future
+constexpr static const char* MUTEVIDEO = "muteVideo";
+
+} // namespace ProtocolKeys
+
+void
+ConfProtocolParser::parse()
+{
+ if (data_.isMember(ProtocolKeys::PROTOVERSION)) {
+ uint32_t version = data_[ProtocolKeys::PROTOVERSION].asUInt();
+ if (version_) version_(version);
+ if (version == 1) {
+ parseV1();
+ } else {
+ JAMI_WARN() << "Unsupported protocol version " << version;
+ }
+ } else {
+ parseV0();
+ }
+}
+
+void
+ConfProtocolParser::parseV0()
+{
+ if (!checkAuthorization_ || !raiseHandUri_ || !setLayout_ || !setActiveParticipant_
+ || !muteParticipant_ || !kickParticipant_) {
+ JAMI_ERR() << "Missing methods for ConfProtocolParser";
+ return;
+ }
+ // Check if all lambdas set
+ auto isPeerModerator = checkAuthorization_(peerId_);
+ if (data_.isMember(ProtocolKeys::HANDRAISED)) {
+ auto state = data_[ProtocolKeys::HANDSTATE].asString() == TRUE_STR;
+ std::string deviceId;
+ auto uri = data_[ProtocolKeys::HANDRAISED].asString();
+ if (peerId_ == uri) {
+ // In this case, the user want to change their state
+ raiseHandUri_(deviceId, state);
+ } else if (!state && isPeerModerator) {
+ // In this case a moderator can lower the hand
+ raiseHandUri_(deviceId, state);
+ }
+ }
+ if (!isPeerModerator) {
+ JAMI_WARN("Received conference order from a non master (%.*s)",
+ (int) peerId_.size(),
+ peerId_.data());
+ return;
+ }
+ if (data_.isMember(ProtocolKeys::LAYOUT)) {
+ setLayout_(data_[ProtocolKeys::LAYOUT].asInt());
+ }
+ if (data_.isMember(ProtocolKeys::ACTIVEPART)) {
+ setActiveParticipant_(data_[ProtocolKeys::ACTIVEPART].asString());
+ }
+ if (data_.isMember(ProtocolKeys::MUTEPART) && data_.isMember(ProtocolKeys::MUTESTATE)) {
+ muteParticipant_(data_[ProtocolKeys::MUTEPART].asString(),
+ data_[ProtocolKeys::MUTESTATE].asString() == TRUE_STR);
+ }
+ if (data_.isMember(ProtocolKeys::HANGUPPART)) {
+ kickParticipant_(data_[ProtocolKeys::HANGUPPART].asString());
+ }
+}
+
+void
+ConfProtocolParser::parseV1()
+{
+ if (!checkAuthorization_ || !setLayout_ || !raiseHand_ || !hangupParticipant_ || !muteStreamAudio_
+ || !setActiveStream_) {
+ JAMI_ERR() << "Missing methods for ConfProtocolParser";
+ return;
+ }
+
+ auto isPeerModerator = checkAuthorization_(peerId_);
+ for (Json::Value::const_iterator itr = data_.begin(); itr != data_.end(); itr++) {
+ auto key = itr.key();
+ if (isPeerModerator && key == ProtocolKeys::LAYOUT) {
+ // Note: can be removed soon
+ setLayout_(itr->asInt());
+ } else {
+ auto accValue = *itr;
+ if (accValue.isMember(ProtocolKeys::DEVICES)) {
+ auto accountUri = key.asString();
+ for (Json::Value::const_iterator itrd = accValue[ProtocolKeys::DEVICES].begin();
+ itrd != accValue[ProtocolKeys::DEVICES].end();
+ itrd++) {
+ auto deviceId = itrd.key().asString();
+ auto deviceValue = *itrd;
+ if (deviceValue.isMember(ProtocolKeys::RAISEHAND)) {
+ auto newState = deviceValue[ProtocolKeys::RAISEHAND].asBool();
+ if (peerId_ == accountUri || (!newState && isPeerModerator))
+ raiseHand_(deviceId, newState);
+ }
+ if (isPeerModerator && deviceValue.isMember(ProtocolKeys::HANGUP)) {
+ hangupParticipant_(accountUri, deviceId);
+ }
+ if (deviceValue.isMember(ProtocolKeys::MEDIAS)) {
+ for (Json::Value::const_iterator itrm = accValue[ProtocolKeys::MEDIAS].begin();
+ itrm != accValue[ProtocolKeys::MEDIAS].end();
+ itrm++) {
+ auto streamId = itrm.key().asString();
+ auto mediaVal = *itrm;
+ if (isPeerModerator) {
+ if (mediaVal.isMember(ProtocolKeys::MUTEVIDEO) && !muteStreamVideo_) {
+ // Note: For now, it's not implemented so not set
+ muteStreamVideo_(accountUri,
+ deviceId,
+ streamId,
+ mediaVal[ProtocolKeys::MUTEVIDEO].asBool());
+ }
+ if (mediaVal.isMember(ProtocolKeys::MUTEAUDIO)) {
+ muteStreamAudio_(accountUri,
+ deviceId,
+ streamId,
+ mediaVal[ProtocolKeys::MUTEAUDIO].asBool());
+ }
+ if (mediaVal.isMember(ProtocolKeys::ACTIVE)) {
+ // TODO streamId
+ setActiveStream_(accountUri,
+ deviceId,
+ mediaVal[ProtocolKeys::ACTIVE].asBool());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace jami
diff --git a/src/conference_protocol.h b/src/conference_protocol.h
new file mode 100644
index 000000000..cee480826
--- /dev/null
+++ b/src/conference_protocol.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * Author: Sébastien Blin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+#include
+
+#include "logger.h"
+
+namespace jami {
+
+/**
+ * Used to parse confOrder objects
+ * @note the user of this class must initialize the different lambdas.
+ */
+class ConfProtocolParser {
+
+public:
+ ConfProtocolParser() {};
+
+ void onVersion(std::function&& cb)
+ {
+ version_ = std::move(cb);
+ }
+ /**
+ * Ask the caller to check if a peer is authorized (moderator of the conference)
+ */
+ void onCheckAuthorization(std::function&& cb) {
+ checkAuthorization_ = std::move(cb);
+ }
+
+ void onHangupParticipant(std::function&& cb)
+ {
+ hangupParticipant_ = std::move(cb);
+ }
+ void onRaiseHand(std::function&& cb)
+ {
+ raiseHand_ = std::move(cb);
+ }
+ /**
+ * @todo, replace the 2 strings per streamId
+ */
+ void onSetActiveStream(std::function&& cb)
+ {
+ setActiveStream_ = std::move(cb);
+ }
+ void onMuteStreamAudio(
+ std::function&& cb)
+ {
+ muteStreamAudio_ = std::move(cb);
+ }
+ void onMuteStreamVideo(
+ std::function&& cb)
+ {
+ muteStreamVideo_ = std::move(cb);
+ }
+ void onSetLayout(std::function&& cb)
+ {
+ setLayout_ = std::move(cb);
+ }
+
+ // Version 0, deprecated
+ void onKickParticipant(std::function&& cb)
+ {
+ kickParticipant_ = std::move(cb);
+ }
+ void onSetActiveParticipant(std::function&& cb)
+ {
+ setActiveParticipant_ = std::move(cb);
+ }
+ void onMuteParticipant(std::function&& cb)
+ {
+ muteParticipant_ = std::move(cb);
+ }
+ void onRaiseHandUri(std::function&& cb)
+ {
+ raiseHandUri_ = std::move(cb);
+ }
+
+ /**
+ * Inject in the parser the data to parse
+ */
+ void initData(Json::Value&& d, std::string_view peerId)
+ {
+ data_ = std::move(d);
+ peerId_ = peerId;
+ }
+
+ /**
+ * Parse the datas, this will call the methods injected if necessary
+ */
+ void parse();
+
+private:
+ void parseV0();
+ void parseV1();
+
+ std::string_view peerId_;
+ Json::Value data_;
+
+ std::function version_;
+
+ std::function checkAuthorization_;
+ std::function hangupParticipant_;
+ std::function raiseHand_;
+ std::function setActiveStream_;
+ std::function
+ muteStreamAudio_;
+ std::function
+ muteStreamVideo_;
+ std::function setLayout_;
+
+ std::function raiseHandUri_;
+ std::function kickParticipant_;
+ std::function setActiveParticipant_;
+ std::function muteParticipant_;
+};
+
+} // namespace jami
diff --git a/src/jami/callmanager_interface.h b/src/jami/callmanager_interface.h
index 222c4ce39..053e44161 100644
--- a/src/jami/callmanager_interface.h
+++ b/src/jami/callmanager_interface.h
@@ -96,9 +96,6 @@ DRING_PUBLIC void createConfFromParticipantList(const std::string& accountId,
DRING_PUBLIC void setConferenceLayout(const std::string& accountId,
const std::string& confId,
uint32_t layout);
-DRING_PUBLIC void setActiveParticipant(const std::string& accountId,
- const std::string& confId,
- const std::string& callId);
DRING_PUBLIC bool isConferenceParticipant(const std::string& accountId, const std::string& callId);
DRING_PUBLIC bool addParticipant(const std::string& accountId,
const std::string& callId,
@@ -124,19 +121,44 @@ DRING_PUBLIC std::vector> getConferenceInfos(
const std::string& accountId, const std::string& confId);
DRING_PUBLIC void setModerator(const std::string& accountId,
const std::string& confId,
- const std::string& peerId,
+ const std::string& accountUri,
const bool& state);
+/// DEPRECATED USE muteStream
DRING_PUBLIC void muteParticipant(const std::string& accountId,
const std::string& confId,
- const std::string& peerId,
+ const std::string& accountUri,
+ const bool& state);
+// Note: muting Audio not supported yet
+DRING_PUBLIC void muteStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
+ const bool& state);
+/// DEPRECATED, USE setActiveStream
+DRING_PUBLIC void setActiveParticipant(const std::string& accountId,
+ const std::string& confId,
+ const std::string& callId);
+DRING_PUBLIC void setActiveStream(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const std::string& streamId,
const bool& state);
DRING_PUBLIC void hangupParticipant(const std::string& accountId,
const std::string& confId,
- const std::string& participant);
+ const std::string& accountUri,
+ const std::string& deviceId);
+/// DEPRECATED, use raiseHand
DRING_PUBLIC void raiseParticipantHand(const std::string& accountId,
const std::string& confId,
const std::string& peerId,
const bool& state);
+DRING_PUBLIC void raiseHand(const std::string& accountId,
+ const std::string& confId,
+ const std::string& accountUri,
+ const std::string& deviceId,
+ const bool& state);
/* Statistic related methods */
DRING_PUBLIC void startSmartInfo(uint32_t refreshTimeMs);
diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp
index f45971c89..d0f2515d9 100644
--- a/src/media/video/video_mixer.cpp
+++ b/src/media/video/video_mixer.cpp
@@ -178,32 +178,34 @@ VideoMixer::stopInput()
}
void
-VideoMixer::setActiveHost()
+VideoMixer::addActiveHost()
{
- activeAudioOnly_ = "";
+ activeStream_ = "";
activeSource_ = videoLocalSecondary_ ? videoLocalSecondary_.get() : videoLocal_.get();
updateLayout();
}
void
-VideoMixer::setActiveParticipant(Observable>* ob)
+VideoMixer::setActiveStream(Observable>* ob)
{
- activeAudioOnly_ = "";
+ activeStream_ = "";
activeSource_ = ob;
updateLayout();
}
void
-VideoMixer::setActiveParticipant(const std::string& id)
+VideoMixer::setActiveStream(const std::string& id)
{
- activeAudioOnly_ = id;
activeSource_ = nullptr;
+ activeStream_ = id;
updateLayout();
}
void
VideoMixer::updateLayout()
{
+ if (activeStream_ == "" && activeSource_ == nullptr)
+ currentLayout_ = Layout::GRID;
layoutUpdated_ += 1;
}
@@ -229,9 +231,8 @@ VideoMixer::detached(Observable>* ob)
for (const auto& x : sources_) {
if (x->source == ob) {
// Handle the case where the current shown source leave the conference
- if (activeSource_ == ob) {
- resetActiveParticipant();
- }
+ if (verifyActive(ob))
+ resetActiveStream();
JAMI_DBG("Remove source [%p]", x.get());
sources_.remove(x);
JAMI_DBG("Total sources: %lu", sources_.size());
@@ -303,10 +304,12 @@ VideoMixer::process()
sourcesInfo.reserve(sources_.size() + audioOnlySources_.size());
// add all audioonlysources
for (auto& id : audioOnlySources_) {
- if (currentLayout_ != Layout::ONE_BIG or activeAudioOnly_ == id) {
+ JAMI_ERR() << "@@@ " << id;
+ auto active = verifyActive(id);
+ if (currentLayout_ != Layout::ONE_BIG or active) {
sourcesInfo.emplace_back(SourceInfo {{}, 0, 0, 10, 10, false, id});
}
- if (currentLayout_ == Layout::ONE_BIG and activeAudioOnly_ == id)
+ if (currentLayout_ == Layout::ONE_BIG and active)
successfullyRendered = true;
}
// add video sources
@@ -315,7 +318,8 @@ VideoMixer::process()
if (!loop_.isRunning())
return;
- if (currentLayout_ != Layout::ONE_BIG or activeSource_ == x->source) {
+ auto activeSource = verifyActive(x->source);
+ if (currentLayout_ != Layout::ONE_BIG or activeSource) {
// make rendered frame temporarily unavailable for update()
// to avoid concurrent access.
std::shared_ptr input = x->getRenderFrame();
@@ -326,7 +330,7 @@ VideoMixer::process()
wantedIndex = 0;
activeFound = true;
} else if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) {
- if (activeSource_ == x->source) {
+ if (activeSource) {
wantedIndex = 0;
activeFound = true;
} else if (not activeFound) {
diff --git a/src/media/video/video_mixer.h b/src/media/video/video_mixer.h
index d271c9fd4..bbd982ddd 100644
--- a/src/media/video/video_mixer.h
+++ b/src/media/video/video_mixer.h
@@ -73,31 +73,37 @@ public:
void switchSecondaryInput(const std::string& input);
void stopInput();
- void setActiveParticipant(Observable>* ob);
- void setActiveParticipant(const std::string& id);
- void resetActiveParticipant() {
- activeAudioOnly_ = "";
- activeSource_ = nullptr;
+ void setActiveStream(Observable>* ob);
+ void setActiveStream(const std::string& id);
+ void resetActiveStream()
+ {
+ activeStream_ = {};
+ activeSource_ = {};
updateLayout();
}
- void setActiveHost();
+ void addActiveHost();
+ // TODO group, we can only use a set of string to identify actives
bool verifyActive(const std::string& id)
{
- return id == activeAudioOnly_;
+ return activeStream_ == id;
}
bool verifyActive(Observable>* ob)
{
- return ob == activeSource_;
+ return activeSource_ == ob;
}
void setVideoLayout(Layout newLayout)
{
currentLayout_ = newLayout;
+ if (currentLayout_ == Layout::GRID)
+ resetActiveStream();
layoutUpdated_ += 1;
}
+ Layout getVideoLayout() const { return currentLayout_; }
+
void setOnSourcesUpdated(OnSourcesUpdatedCb&& cb) { onSourcesUpdated_ = std::move(cb); }
MediaStream getStream(const std::string& name) const;
@@ -118,8 +124,8 @@ public:
void removeAudioOnlySource(const std::string& id)
{
std::lock_guard lk(audioOnlySourcesMtx_);
- audioOnlySources_.erase(id);
- updateLayout();
+ if (audioOnlySources_.erase(id))
+ updateLayout();
}
private:
@@ -161,7 +167,7 @@ private:
std::mutex audioOnlySourcesMtx_;
std::set audioOnlySources_;
- std::string activeAudioOnly_{};
+ std::string activeStream_ {};
std::atomic_int layoutUpdated_ {0};
OnSourcesUpdatedCb onSourcesUpdated_ {};
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index ffb719427..df09ddbec 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -260,7 +260,7 @@ VideoRtpSession::startReceiver()
|| videoMixer_->verifyActive(callID_);
videoMixer_->removeAudioOnlySource(callID_);
if (activeParticipant)
- videoMixer_->setActiveParticipant(receiveThread_.get());
+ videoMixer_->setActiveStream(receiveThread_.get());
}
} else {
@@ -271,7 +271,7 @@ VideoRtpSession::startReceiver()
videoMixer_->addAudioOnlySource(callID_);
receiveThread_->detach(videoMixer_.get());
if (activeParticipant)
- videoMixer_->setActiveParticipant(callID_);
+ videoMixer_->setActiveStream(callID_);
}
}
if (socketPair_)
@@ -293,7 +293,7 @@ VideoRtpSession::stopReceiver()
videoMixer_->addAudioOnlySource(callID_);
receiveThread_->detach(videoMixer_.get());
if (activeParticipant)
- videoMixer_->setActiveParticipant(callID_);
+ videoMixer_->setActiveStream(callID_);
}
// We need to disable the read operation, otherwise the
@@ -549,7 +549,7 @@ VideoRtpSession::exitConference()
conference_->detachVideo(receiveThread_.get());
receiveThread_->startSink();
if (activetParticipant)
- videoMixer_->setActiveParticipant(callID_);
+ videoMixer_->setActiveStream(callID_);
}
videoMixer_.reset();
diff --git a/src/meson.build b/src/meson.build
index 7a53e38dd..9abdb1906 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -106,6 +106,7 @@ libjami_sources = files(
'call.cpp',
'call_factory.cpp',
'conference.cpp',
+ 'conference_protocol.cpp',
'data_transfer.cpp',
'fileutils.cpp',
'ftp_server.cpp',
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index ba52bd4bf..aeb7ded93 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -88,6 +88,9 @@ static constexpr int ICE_COMP_COUNT_PER_STREAM {2};
static constexpr auto MULTISTREAM_REQUIRED_VERSION_STR = "10.0.2"sv;
static const std::vector MULTISTREAM_REQUIRED_VERSION
= split_string_to_unsigned(MULTISTREAM_REQUIRED_VERSION_STR, '.');
+static constexpr auto NEW_CONFPROTOCOL_VERSION_STR = "13.1.0"sv;
+static const std::vector NEW_CONFPROTOCOL_VERSION
+ = split_string_to_unsigned(NEW_CONFPROTOCOL_VERSION_STR, '.');
static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR = "11.0.2"sv;
static const std::vector REUSE_ICE_IN_REINVITE_REQUIRED_VERSION
= split_string_to_unsigned(REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR, '.');
diff --git a/test/unitTest/call/conference.cpp b/test/unitTest/call/conference.cpp
index 447a90b27..196370f83 100644
--- a/test/unitTest/call/conference.cpp
+++ b/test/unitTest/call/conference.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C)2021 Savoir-faire Linux Inc.
+ * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
* Author: Sébastien Blin
*
* This program is free software; you can redistribute it and/or modify
@@ -34,6 +34,7 @@
#include "video/sinkclient.h"
using namespace DRing::Account;
+using namespace std::literals::chrono_literals;
namespace jami {
namespace test {
@@ -43,6 +44,7 @@ struct CallData
std::string callId {};
std::string state {};
std::string device {};
+ std::string streamId {};
std::string hostState {};
std::atomic_bool moderatorMuted {false};
std::atomic_bool raisedHand {false};
@@ -53,6 +55,7 @@ struct CallData
callId = "";
state = "";
device = "";
+ streamId = "";
hostState = "";
moderatorMuted = false;
active = false;
@@ -78,6 +81,7 @@ public:
private:
void testGetConference();
void testModeratorMuteUpdateParticipantsInfos();
+ void testUnauthorizedMute();
void testAudioVideoMutedStates();
void testCreateParticipantsSinks();
void testMuteStatusAfterRemove();
@@ -86,10 +90,13 @@ private:
void testPeerLeaveConference();
void testJoinCallFromOtherAccount();
void testDevices();
+ void testUnauthorizedSetActive();
+ void testHangup();
CPPUNIT_TEST_SUITE(ConferenceTest);
CPPUNIT_TEST(testGetConference);
CPPUNIT_TEST(testModeratorMuteUpdateParticipantsInfos);
+ CPPUNIT_TEST(testUnauthorizedMute);
CPPUNIT_TEST(testAudioVideoMutedStates);
CPPUNIT_TEST(testCreateParticipantsSinks);
CPPUNIT_TEST(testMuteStatusAfterRemove);
@@ -98,6 +105,8 @@ private:
CPPUNIT_TEST(testPeerLeaveConference);
CPPUNIT_TEST(testJoinCallFromOtherAccount);
CPPUNIT_TEST(testDevices);
+ CPPUNIT_TEST(testUnauthorizedSetActive);
+ CPPUNIT_TEST(testHangup);
CPPUNIT_TEST_SUITE_END();
// Common parts
@@ -220,16 +229,19 @@ ConferenceTest::registerSignalHandlers()
bobCall.moderatorMuted = infos.at("audioModeratorMuted") == "true";
bobCall.raisedHand = infos.at("handRaised") == "true";
bobCall.device = infos.at("device");
+ bobCall.streamId = infos.at("sinkId");
} else if (infos.at("uri").find(carlaUri) != std::string::npos) {
carlaCall.active = infos.at("active") == "true";
carlaCall.moderatorMuted = infos.at("audioModeratorMuted") == "true";
carlaCall.raisedHand = infos.at("handRaised") == "true";
carlaCall.device = infos.at("device");
+ carlaCall.streamId = infos.at("sinkId");
} else if (infos.at("uri").find(daviUri) != std::string::npos) {
daviCall.active = infos.at("active") == "true";
daviCall.moderatorMuted = infos.at("audioModeratorMuted") == "true";
daviCall.raisedHand = infos.at("handRaised") == "true";
daviCall.device = infos.at("device");
+ daviCall.streamId = infos.at("sinkId");
}
}
cv.notify_one();
@@ -249,27 +261,25 @@ ConferenceTest::startConference()
JAMI_INFO("Start call between Alice and Bob");
auto call1 = DRing::placeCallWithMedia(aliceId, bobUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !bobCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
Manager::instance().answerCall(bobId, bobCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return bobCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.hostState == "CURRENT"; }));
JAMI_INFO("Start call between Alice and Carla");
auto call2 = DRing::placeCallWithMedia(aliceId, carlaUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !carlaCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !carlaCall.callId.empty(); }));
Manager::instance().answerCall(carlaId, carlaCall.callId);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] {
- return carlaCall.hostState == "CURRENT";
- }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return carlaCall.hostState == "CURRENT"; }));
JAMI_INFO("Start conference");
confChanged = false;
Manager::instance().joinParticipant(aliceId, call1, aliceId, call2);
// ConfChanged is the signal emitted when the 2 calls will be added to the conference
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !confId.empty() && confChanged; }));
+ // Also, wait that participants appears in conf info to get all good informations
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
+ return !confId.empty() && confChanged && !carlaCall.device.empty()
+ && !bobCall.device.empty();
+ }));
}
void
@@ -277,7 +287,7 @@ ConferenceTest::hangupConference()
{
JAMI_INFO("Stop conference");
Manager::instance().hangupConference(aliceId, confId);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] {
+ CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] {
return bobCall.state == "OVER" && carlaCall.state == "OVER" && confId.empty();
}));
}
@@ -312,13 +322,30 @@ ConferenceTest::testModeratorMuteUpdateParticipantsInfos()
startConference();
JAMI_INFO("Play with mute from the moderator");
- DRing::muteParticipant(aliceId, confId, bobUri, true);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return bobCall.moderatorMuted.load(); }));
+ DRing::muteStream(aliceId, confId, bobUri, bobCall.device, bobCall.streamId, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.moderatorMuted.load(); }));
- DRing::muteParticipant(aliceId, confId, bobUri, false);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !bobCall.moderatorMuted.load(); }));
+ DRing::muteStream(aliceId, confId, bobUri, bobCall.device, bobCall.streamId, false);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.moderatorMuted.load(); }));
+
+ hangupConference();
+
+ DRing::unregisterSignalHandlers();
+}
+
+void
+ConferenceTest::testUnauthorizedMute()
+{
+ registerSignalHandlers();
+
+ auto bobAccount = Manager::instance().getAccount(bobId);
+ auto bobUri = bobAccount->getUsername();
+
+ startConference();
+
+ JAMI_INFO("Play with mute from unauthorized");
+ DRing::muteStream(carlaId, confId, bobUri, bobCall.device, bobCall.streamId, true);
+ CPPUNIT_ASSERT(!cv.wait_for(lk, 15s, [&] { return bobCall.moderatorMuted.load(); }));
hangupConference();
@@ -338,23 +365,18 @@ ConferenceTest::testAudioVideoMutedStates()
JAMI_INFO("Start call between Alice and Bob");
auto call1Id = DRing::placeCallWithMedia(aliceId, bobUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !bobCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
Manager::instance().answerCall(bobId, bobCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return bobCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.hostState == "CURRENT"; }));
auto call1 = aliceAccount->getCall(call1Id);
call1->muteMedia(DRing::Media::MediaAttributeValue::AUDIO, true);
call1->muteMedia(DRing::Media::MediaAttributeValue::VIDEO, true);
JAMI_INFO("Start call between Alice and Carla");
auto call2Id = DRing::placeCallWithMedia(aliceId, carlaUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !carlaCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !carlaCall.callId.empty(); }));
Manager::instance().answerCall(carlaId, carlaCall.callId);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] {
- return carlaCall.hostState == "CURRENT";
- }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return carlaCall.hostState == "CURRENT"; }));
auto call2 = aliceAccount->getCall(call2Id);
call2->muteMedia(DRing::Media::MediaAttributeValue::AUDIO, true);
@@ -362,13 +384,13 @@ ConferenceTest::testAudioVideoMutedStates()
JAMI_INFO("Start conference");
Manager::instance().joinParticipant(aliceId, call1Id, aliceId, call2Id);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] { return !confId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !confId.empty(); }));
auto conf = aliceAccount->getConference(confId);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] {
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
return conf->isMediaSourceMuted(jami::MediaType::MEDIA_AUDIO);
}));
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] {
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
return conf->isMediaSourceMuted(jami::MediaType::MEDIA_VIDEO);
}));
@@ -391,7 +413,7 @@ ConferenceTest::testCreateParticipantsSinks()
auto infos = DRing::getConferenceInfos(aliceId, confId);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] {
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
bool sinksStatus = true;
for (auto& info : infos) {
if (info["uri"] == bobUri) {
@@ -423,42 +445,35 @@ ConferenceTest::testMuteStatusAfterRemove()
JAMI_INFO("Start call between Alice and Davi");
auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
- DRing::muteParticipant(aliceId, confId, daviUri, true);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return daviCall.moderatorMuted.load(); }));
+ DRing::muteStream(aliceId, confId, daviUri, daviCall.device, daviCall.streamId, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return daviCall.moderatorMuted.load(); }));
Manager::instance().hangupCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
daviCall.reset();
auto call2 = DRing::placeCallWithMedia(aliceId, daviUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !daviCall.moderatorMuted.load(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !daviCall.moderatorMuted.load(); }));
Manager::instance().hangupCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
hangupConference();
DRing::unregisterSignalHandlers();
}
-
void
ConferenceTest::testActiveStatusAfterRemove()
{
@@ -480,37 +495,35 @@ ConferenceTest::testActiveStatusAfterRemove()
auto call1 = DRing::placeCallWithMedia(aliceId,
daviUri,
MediaAttribute::mediaAttributesToMediaMaps(
- {defaultAudio}));
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ {defaultAudio}));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
- DRing::setActiveParticipant(aliceId, confId, daviUri);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return daviCall.active.load(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
+
+ DRing::setActiveStream(aliceId, confId, daviUri, daviCall.device, daviCall.streamId, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return daviCall.active.load(); }));
Manager::instance().hangupCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
daviCall.reset();
- auto call2 = DRing::placeCallWithMedia(aliceId, daviUri, MediaAttribute::mediaAttributesToMediaMaps({defaultAudio}));
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ auto call2 = DRing::placeCallWithMedia(aliceId,
+ daviUri,
+ MediaAttribute::mediaAttributesToMediaMaps(
+ {defaultAudio}));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !daviCall.active.load(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !daviCall.active.load(); }));
Manager::instance().hangupCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
hangupConference();
DRing::unregisterSignalHandlers();
@@ -532,68 +545,56 @@ ConferenceTest::testHandsUp()
startConference();
JAMI_INFO("Play with raise hand");
- DRing::raiseParticipantHand(bobId, bobCall.callId, bobUri, true);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return bobCall.raisedHand.load(); }));
+ DRing::raiseHand(bobId, bobCall.callId, bobUri, bobCall.device, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.raisedHand.load(); }));
- DRing::raiseParticipantHand(bobId, bobCall.callId, bobUri, false);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !bobCall.raisedHand.load(); }));
+ DRing::raiseHand(bobId, bobCall.callId, bobUri, bobCall.device, false);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.raisedHand.load(); }));
JAMI_INFO("Start call between Alice and Davi");
auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
// Remove davi from moderators
DRing::setModerator(aliceId, confId, daviUri, false);
// Test to raise hand
- DRing::raiseParticipantHand(daviId, daviCall.callId, daviUri, true);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return daviCall.raisedHand.load(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
+ DRing::raiseHand(daviId, daviCall.callId, daviUri, daviCall.device, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return daviCall.raisedHand.load(); }));
// Test to raise hand for another one (should fail)
- DRing::raiseParticipantHand(bobId, bobCall.callId, carlaUri, true);
- CPPUNIT_ASSERT(
- !cv.wait_for(lk, std::chrono::seconds(5), [&] { return carlaCall.raisedHand.load(); }));
+ DRing::raiseHand(bobId, bobCall.callId, carlaUri, carlaCall.device, true);
+ CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&] { return carlaCall.raisedHand.load(); }));
// However, a moderator should be able to lower the hand (but not a non moderator)
- DRing::raiseParticipantHand(carlaId, carlaCall.callId, carlaUri, true);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return carlaCall.raisedHand.load(); }));
+ DRing::raiseHand(carlaId, carlaCall.callId, carlaUri, carlaCall.device, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return carlaCall.raisedHand.load(); }));
- DRing::raiseParticipantHand(daviId, carlaCall.callId, carlaUri, false);
- CPPUNIT_ASSERT(
- !cv.wait_for(lk, std::chrono::seconds(5), [&] { return !carlaCall.raisedHand.load(); }));
+ DRing::raiseHand(daviId, carlaCall.callId, carlaUri, carlaCall.device, false);
+ CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&] { return !carlaCall.raisedHand.load(); }));
- DRing::raiseParticipantHand(bobId, bobCall.callId, carlaUri, false);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !carlaCall.raisedHand.load(); }));
+ DRing::raiseHand(bobId, bobCall.callId, carlaUri, carlaCall.device, false);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !carlaCall.raisedHand.load(); }));
Manager::instance().hangupCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
daviCall.reset();
auto call2 = DRing::placeCallWithMedia(aliceId, daviUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !daviCall.raisedHand.load(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !daviCall.raisedHand.load(); }));
Manager::instance().hangupCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
hangupConference();
DRing::unregisterSignalHandlers();
@@ -610,9 +611,7 @@ ConferenceTest::testPeerLeaveConference()
startConference();
Manager::instance().hangupCall(bobId, bobCall.callId);
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] {
- return bobCall.state == "OVER" && confId.empty();
- }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && confId.empty(); }));
DRing::unregisterSignalHandlers();
}
@@ -631,21 +630,17 @@ ConferenceTest::testJoinCallFromOtherAccount()
startConference();
JAMI_INFO("Play with raise hand");
- DRing::raiseParticipantHand(aliceId, confId, bobUri, true);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return bobCall.raisedHand.load(); }));
+ DRing::raiseHand(aliceId, confId, bobUri, bobCall.device, true);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.raisedHand.load(); }));
- DRing::raiseParticipantHand(aliceId, confId, bobUri, false);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(5), [&] { return !bobCall.raisedHand.load(); }));
+ DRing::raiseHand(aliceId, confId, bobUri, bobCall.device, false);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.raisedHand.load(); }));
JAMI_INFO("Start call between Alice and Davi");
auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
Manager::instance().answerCall(daviId, daviCall.callId);
- CPPUNIT_ASSERT(
- cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
CPPUNIT_ASSERT(Manager::instance().addParticipant(daviId, daviCall.callId, aliceId, confId));
hangupConference();
@@ -664,7 +659,7 @@ ConferenceTest::testDevices()
startConference();
- CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] {
+ CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
return bobCall.device == bobDevice && carlaDevice == carlaCall.device;
}));
@@ -673,6 +668,57 @@ ConferenceTest::testDevices()
DRing::unregisterSignalHandlers();
}
+void
+ConferenceTest::testUnauthorizedSetActive()
+{
+ registerSignalHandlers();
+
+ auto bobAccount = Manager::instance().getAccount(bobId);
+ auto bobUri = bobAccount->getUsername();
+ auto carlaAccount = Manager::instance().getAccount(carlaId);
+ auto carlaUri = carlaAccount->getUsername();
+
+ startConference();
+
+ DRing::setActiveStream(carlaId, confId, bobUri, bobCall.device, bobCall.streamId, true);
+ CPPUNIT_ASSERT(!cv.wait_for(lk, 15s, [&] { return bobCall.active.load(); }));
+
+ hangupConference();
+
+ DRing::unregisterSignalHandlers();
+}
+
+void
+ConferenceTest::testHangup()
+{
+ registerSignalHandlers();
+
+ auto bobAccount = Manager::instance().getAccount(bobId);
+ auto bobUri = bobAccount->getUsername();
+ auto carlaAccount = Manager::instance().getAccount(carlaId);
+ auto daviAccount = Manager::instance().getAccount(daviId);
+ auto daviUri = daviAccount->getUsername();
+
+ startConference();
+
+ JAMI_INFO("Start call between Alice and Davi");
+ auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
+ Manager::instance().answerCall(daviId, daviCall.callId);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
+ Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
+
+ DRing::hangupParticipant(carlaId, confId, daviUri, daviCall.device); // Unauthorized
+ CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&] { return daviCall.state == "OVER"; }));
+ DRing::hangupParticipant(aliceId, confId, daviUri, daviCall.device);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return daviCall.state == "OVER"; }));
+
+ hangupConference();
+
+ DRing::unregisterSignalHandlers();
+}
+
} // namespace test
} // namespace jami