Commit 073d68f9 authored by Sébastien Blin's avatar Sébastien Blin

conference: a conference can now be controlled from authorized accounts

Implement application+confOrder to allow a remote peer to control the
conference's layout. The authorized peer can send a json likes:
{
    "layout": 1,
    "activeParticipant: "uri"
}
For now authorized peers are local accounts. This also change the conference
layout by sending "isMaster" for each participants to allow the peer to know
if we are the master when we are not the host.

Change-Id: Iba8a55453421e3f173b43e773d1b43502efec747
Gitlab: #241
parent 06a885c0
......@@ -419,6 +419,13 @@ Call::onTextMessage(std::map<std::string, std::string>&& messages)
return;
}
it = messages.find("application/confOrder+json");
if (it != messages.end()) {
if (auto conf = Manager::instance().getConferenceFromID(confID_))
conf->onConfOrder(getCallId(), it->second);
return;
}
{
std::lock_guard<std::recursive_mutex> lk {callMutex_};
if (parent_) {
......
......@@ -19,12 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <regex>
#include <sstream>
#include "conference.h"
#include "manager.h"
#include "audio/audiolayer.h"
#include "audio/ringbufferpool.h"
#include "jamidht/jamiaccount.h"
#ifdef ENABLE_VIDEO
#include "sip/sipcall.h"
......@@ -47,6 +49,17 @@ Conference::Conference()
{
JAMI_INFO("Create new conference %s", id_.c_str());
// TODO: For now, add all accounts on the same device as
// conference master. In the future, this should be
// retrieven with another way
auto accounts = jami::Manager::instance().getAllAccounts<JamiAccount>();
moderators_.reserve(accounts.size());
for (const auto& account : accounts) {
if (!account)
continue;
moderators_.emplace_back(account->getUsername());
}
#ifdef ENABLE_VIDEO
getVideoMixer()->setOnSourcesUpdated([this](const std::vector<video::SourceInfo>&& infos) {
runOnMainThread([w = weak(), infos = std::move(infos)] {
......@@ -79,8 +92,15 @@ Conference::Conference()
and not videoMixer->getActiveParticipant()); // by default, local
// is shown as active
subCalls.erase(it->second);
newInfo.emplace_back(ParticipantInfo {
std::move(uri), active, info.x, info.y, info.w, info.h, !info.hasVideo, false});
newInfo.emplace_back(ParticipantInfo {std::move(uri),
active,
info.x,
info.y,
info.w,
info.h,
!info.hasVideo,
false,
shared->isModerator(uri)});
}
lk.unlock();
// Handle participants not present in the video mixer
......@@ -88,7 +108,15 @@ Conference::Conference()
std::string uri = "";
if (auto call = Manager::instance().callFactory.getCall<SIPCall>(subCall))
uri = call->getPeerNumber();
ParticipantInfo {std::move(uri), false, 0, 0, 0, 0, true, false};
ParticipantInfo {std::move(uri),
false,
0,
0,
0,
0,
true,
false,
shared->isModerator(uri)};
}
{
......@@ -119,7 +147,8 @@ Conference::~Conference()
JAMI_DBG("Stop recording for conf %s", getConfID().c_str());
this->toggleRecording();
if (not call->isRecording()) {
JAMI_DBG("Conference was recorded, start recording for conf %s", call->getCallId().c_str());
JAMI_DBG("Conference was recorded, start recording for conf %s",
call->getCallId().c_str());
call->toggleRecording();
}
}
......@@ -155,12 +184,12 @@ Conference::add(const std::string& participant_id)
JAMI_DBG("Stop recording for call %s", call->getCallId().c_str());
call->toggleRecording();
if (not this->isRecording()) {
JAMI_DBG("One participant was recording, start recording for conference %s", getConfID().c_str());
JAMI_DBG("One participant was recording, start recording for conference %s",
getConfID().c_str());
this->toggleRecording();
}
}
}
else
} else
JAMI_ERR("no call associate to participant %s", participant_id.c_str());
#endif // ENABLE_VIDEO
}
......@@ -172,8 +201,9 @@ Conference::setActiveParticipant(const std::string& participant_id)
if (!videoMixer_)
return;
for (const auto& item : participants_) {
if (participant_id == item) {
if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) {
if (auto call = Manager::instance().callFactory.getCall<SIPCall>(item)) {
if (participant_id == item
|| call->getPeerNumber().find(participant_id) != std::string::npos) {
videoMixer_->setActiveParticipant(call->getVideoRtp().getVideoReceive().get());
return;
}
......@@ -183,6 +213,24 @@ Conference::setActiveParticipant(const std::string& participant_id)
videoMixer_->setActiveParticipant(nullptr);
}
void
Conference::setLayout(int layout)
{
switch (layout) {
case 0:
getVideoMixer()->setVideoLayout(video::Layout::GRID);
break;
case 1:
getVideoMixer()->setVideoLayout(video::Layout::ONE_BIG_WITH_SMALL);
break;
case 2:
getVideoMixer()->setVideoLayout(video::Layout::ONE_BIG);
break;
default:
break;
}
}
std::vector<std::map<std::string, std::string>>
ConfInfo::toVectorMapStringString() const
{
......@@ -434,4 +482,46 @@ Conference::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
ghostRingBuffer_.reset();
}
void
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 uri = call->getPeerNumber();
auto separator = uri.find('@');
if (separator != std::string::npos)
uri = uri.substr(0, separator - 1);
if (!isModerator(uri)) {
JAMI_WARN("Received conference order from a non master (%s)", uri.c_str());
return;
}
std::string err;
Json::Value root;
Json::CharReaderBuilder rbuilder;
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (!reader->parse(confOrder.c_str(), confOrder.c_str() + confOrder.size(), &root, &err)) {
JAMI_WARN("Couldn't parse conference order from %s", uri.c_str());
return;
}
if (root.isMember("layout")) {
setLayout(root["layout"].asUInt());
}
if (root.isMember("activeParticipant")) {
setActiveParticipant(root["activeParticipant"].asString());
}
}
}
bool
Conference::isModerator(const std::string& uri) const
{
return std::find_if(moderators_.begin(),
moderators_.end(),
[&uri](const std::string& master) {
return master.find(uri) != std::string::npos;
})
!= moderators_.end();
}
} // namespace jami
......@@ -31,7 +31,6 @@
#include "audio/audio_input.h"
#include <json/json.h>
#include "recordable.h"
......@@ -54,6 +53,7 @@ struct ParticipantInfo
int h {0};
bool videoMuted {false};
bool audioMuted {false};
bool isModerator {false};
void fromJson(const Json::Value& v)
{
......@@ -65,6 +65,7 @@ struct ParticipantInfo
h = v["h"].asInt();
videoMuted = v["videoMuted"].asBool();
audioMuted = v["audioMuted"].asBool();
isModerator = v["isModerator"].asBool();
}
Json::Value toJson() const
......@@ -78,6 +79,7 @@ struct ParticipantInfo
val["h"] = h;
val["videoMuted"] = videoMuted;
val["audioMuted"] = audioMuted;
val["isModerator"] = isModerator;
return val;
}
......@@ -90,7 +92,8 @@ struct ParticipantInfo
{"w", std::to_string(w)},
{"h", std::to_string(h)},
{"videoMuted", videoMuted ? "true" : "false"},
{"audioMuted", audioMuted ? "true" : "false"}};
{"audioMuted", audioMuted ? "true" : "false"},
{"isModerator", isModerator ? "true" : "false"}};
}
};
......@@ -193,10 +196,13 @@ public:
void switchInput(const std::string& input);
void setActiveParticipant(const std::string& participant_id);
void setLayout(int layout);
void attachVideo(Observable<std::shared_ptr<MediaFrame>>* frame, const std::string& callId);
void detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame);
void onConfOrder(const std::string& callId, const std::string& order);
#ifdef ENABLE_VIDEO
std::shared_ptr<video::VideoMixer> getVideoMixer();
std::string getVideoInput() const { return mediaInput_; }
......@@ -214,6 +220,8 @@ private:
return std::static_pointer_cast<Conference>(shared_from_this());
}
bool isModerator(const std::string& uri) const;
std::string id_;
State confState_ {State::ACTIVE_ATTACHED};
ParticipantSet participants_;
......@@ -232,6 +240,7 @@ private:
#endif
std::shared_ptr<jami::AudioInput> audioMixer_;
std::vector<std::string> moderators_ {};
void initRecorder(std::shared_ptr<MediaRecorder>& rec);
void deinitRecorder(std::shared_ptr<MediaRecorder>& rec);
......
......@@ -1469,28 +1469,35 @@ void
Manager::setConferenceLayout(const std::string& confId, int layout)
{
if (auto conf = getConferenceFromID(confId)) {
auto videoMixer = conf->getVideoMixer();
switch (layout) {
case 0:
videoMixer->setVideoLayout(video::Layout::GRID);
break;
case 1:
videoMixer->setVideoLayout(video::Layout::ONE_BIG_WITH_SMALL);
break;
case 2:
videoMixer->setVideoLayout(video::Layout::ONE_BIG);
break;
default:
break;
}
conf->setLayout(layout);
} else if (auto call = getCallFromCallID(confId)) {
std::map<std::string, std::string> messages;
Json::Value root;
root["layout"] = layout;
Json::StreamWriterBuilder wbuilder;
wbuilder["commentStyle"] = "None";
wbuilder["indentation"] = "";
auto output = Json::writeString(wbuilder, root);
messages["application/confOrder+json"] = output;
call->sendTextMessage(messages, call->getPeerDisplayName());
}
}
void
Manager::setActiveParticipant(const std::string& confId, const std::string& callId)
Manager::setActiveParticipant(const std::string& confId, const std::string& participant)
{
if (auto conf = getConferenceFromID(confId)) {
conf->setActiveParticipant(callId);
conf->setActiveParticipant(participant);
} else if (auto call = getCallFromCallID(confId)) {
std::map<std::string, std::string> messages;
Json::Value root;
root["activeParticipant"] = participant;
Json::StreamWriterBuilder wbuilder;
wbuilder["commentStyle"] = "None";
wbuilder["indentation"] = "";
auto output = Json::writeString(wbuilder, root);
messages["application/confOrder+json"] = output;
call->sendTextMessage(messages, call->getPeerDisplayName());
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment