diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml index f86788d0378693893be728628aa6f485adcd5da3..ee796b871afc999d14050b4e9e6045afbe64d122 100644 --- a/bin/dbus/cx.ring.Ring.CallManager.xml +++ b/bin/dbus/cx.ring.Ring.CallManager.xml @@ -228,6 +228,30 @@ <arg type="as" name="participants" direction="in"/> </method> + <method name="setConferenceLayout" tp:name-for-bindings="setConferenceLayout"> + <tp:added version="9.4.0"/> + <tp:docstring> + <p>Change displayed layout for a conference. We currently support 3 layouts: + 0. (default) = grid view + 1. One big with small previews + 2. One participant</p> + </tp:docstring> + <arg type="s" name="confId" direction="in"/> + <arg type="u" name="layout" direction="in"/> + </method> + + <method name="setActiveParticipant" tp:name-for-bindings="setActiveParticipant"> + <tp:added version="9.4.0"/> + <tp:docstring> + <p>Depending the layout of the conference, someone can be shown bigger than the others. + This methods is here to set which call is shown. Note if the active participant leave while + shown, the layout will change to the default one (grid view).</p> + </tp:docstring> + <arg type="s" name="confId" direction="in"/> + <arg type="s" name="callId" direction="in"/> + </method> + + <method name="isConferenceParticipant" tp:name-for-bindings="isConferenceParticipant"> <arg type="s" name="callID" direction="in"/> <arg type="b" name="isParticipant" direction="out"/> diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp index 544503e9ef01028d99fbb215aefcfdf4c59ebdb2..68b1f12ad8255225b22fe3199c12de684c98215d 100644 --- a/bin/dbus/dbuscallmanager.cpp +++ b/bin/dbus/dbuscallmanager.cpp @@ -117,6 +117,19 @@ DBusCallManager::createConfFromParticipantList(const std::vector< std::string >& DRing::createConfFromParticipantList(participants); } +void +DBusCallManager::setConferenceLayout(const std::string& confId, const uint32_t& layout) +{ + DRing::setConferenceLayout(confId, layout); +} + +void +DBusCallManager::setActiveParticipant(const std::string& confId, const std::string& callId) +{ + DRing::setActiveParticipant(confId, callId); +} + + auto DBusCallManager::isConferenceParticipant(const std::string& call_id) -> decltype(DRing::isConferenceParticipant(call_id)) { diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h index da96cfae7e048d3391dc855e58ebf0c949f51a60..1a3d0b03adbabacfd740a239960ce8af31465351 100644 --- a/bin/dbus/dbuscallmanager.h +++ b/bin/dbus/dbuscallmanager.h @@ -71,6 +71,8 @@ class DRING_PUBLIC DBusCallManager : void removeConference(const std::string& conference_id); bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); void createConfFromParticipantList(const std::vector< std::string >& participants); + void setConferenceLayout(const std::string& confId, const uint32_t& layout); + void setActiveParticipant(const std::string& confId, const std::string& callId); bool isConferenceParticipant(const std::string& call_id); bool addParticipant(const std::string& callID, const std::string& confID); bool addMainParticipant(const std::string& confID); diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i index 5525d589931aef8718d6de2b9c5f658f13967681..a7008f54f399e953015298fd82422b73f2a62319 100644 --- a/bin/jni/callmanager.i +++ b/bin/jni/callmanager.i @@ -74,6 +74,8 @@ std::vector<std::string> getCallList(); void removeConference(const std::string& conference_id); bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); void createConfFromParticipantList(const std::vector<std::string>& participants); +void setConferenceLayout(const std::string& confId, int layout); +void setActiveParticipant(const std::string& confId, const std::string& callId); bool isConferenceParticipant(const std::string& call_id); bool addParticipant(const std::string& callID, const std::string& confID); bool addMainParticipant(const std::string& confID); diff --git a/bin/nodejs/callmanager.i b/bin/nodejs/callmanager.i index 0fc71d7ff3e37175433cea6b2359db498b5cd8e5..6f584daf4cff158bd4240d8542d78ad0d1203de8 100644 --- a/bin/nodejs/callmanager.i +++ b/bin/nodejs/callmanager.i @@ -73,6 +73,8 @@ std::vector<std::string> getCallList(); void removeConference(const std::string& conference_id); bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); void createConfFromParticipantList(const std::vector<std::string>& participants); +void setConferenceLayout(const std::string& confId, int layout); +void setActiveParticipant(const std::string& confId, const std::string& callId); bool isConferenceParticipant(const std::string& call_id); bool addParticipant(const std::string& callID, const std::string& confID); bool addMainParticipant(const std::string& confID); diff --git a/configure.ac b/configure.ac index afae2ecc9ed1891d6fbad1102ee2798058103641..79ad5e1d3d98e5903157f14f6620123831c25cf6 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac for automake 1.9 and autoconf 2.59 dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([Jami Daemon],[9.3.0],[ring@gnu.org],[jami]) +AC_INIT([Jami Daemon],[9.4.0],[ring@gnu.org],[jami]) AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2019]]) AC_REVISION([$Revision$]) diff --git a/meson.build b/meson.build index 8d20804801356906822b68f31dafa846fc9edd5e..b114f8088297cae2e36b7b10fbf7cfa1099fd223 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '9.2.0', + version: '9.4.0', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.54' diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp index 2a2cb9f324acbc71220f2397eadd0024d1414414..cd6e591e2656748264d1b041f060f4f0e2267df4 100644 --- a/src/client/callmanager.cpp +++ b/src/client/callmanager.cpp @@ -135,6 +135,18 @@ createConfFromParticipantList(const std::vector<std::string>& participants) jami::Manager::instance().createConfFromParticipantList(participants); } +void +setConferenceLayout(const std::string& confId, uint32_t layout) +{ + jami::Manager::instance().setConferenceLayout(confId, layout); +} + +void +setActiveParticipant(const std::string& confId, const std::string& callId) +{ + jami::Manager::instance().setActiveParticipant(confId, callId); +} + bool isConferenceParticipant(const std::string& callID) { diff --git a/src/conference.cpp b/src/conference.cpp index 9b7cb694ef981cda12bb580c114b50cd780097e8..fc0f78c62b0c64baeabd06c4c402a4e1c16ca162 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -62,12 +62,14 @@ Conference::getState() const return confState_; } -void Conference::setState(State state) +void +Conference::setState(State state) { confState_ = state; } -void Conference::add(const std::string &participant_id) +void +Conference::add(const std::string &participant_id) { if (participants_.insert(participant_id).second) { #ifdef ENABLE_VIDEO @@ -79,7 +81,23 @@ void Conference::add(const std::string &participant_id) } } -void Conference::remove(const std::string &participant_id) +void +Conference::setActiveParticipant(const std::string &participant_id) +{ + for (const auto &item : participants_) { + if (participant_id == item) { + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) { + videoMixer_->setActiveParticipant(call->getVideoRtp().getVideoReceive().get()); + return; + } + } + } + // Set local by default + videoMixer_->setActiveParticipant(nullptr); +} + +void +Conference::remove(const std::string &participant_id) { if (participants_.erase(participant_id)) { #ifdef ENABLE_VIDEO @@ -128,7 +146,8 @@ Conference::detach() } } -void Conference::bindParticipant(const std::string &participant_id) +void +Conference::bindParticipant(const std::string &participant_id) { auto &rbPool = Manager::instance().getRingBufferPool(); diff --git a/src/conference.h b/src/conference.h index 219be7f23ad49aa181a946a155a75d957b688d9f..d165d47a267d54645ffb234c1f09fdf28f272936 100644 --- a/src/conference.h +++ b/src/conference.h @@ -134,6 +134,8 @@ public: void switchInput(const std::string& input); + void setActiveParticipant(const std::string &participant_id); + #ifdef ENABLE_VIDEO std::shared_ptr<video::VideoMixer> getVideoMixer(); std::string getVideoInput() const { return mediaInput_; } diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h index b323e7a9d4ddd05393edfd0ef528fa4a3beb6210..2c68d049a2269d412b04ba2a05881b3dc6276472 100644 --- a/src/dring/callmanager_interface.h +++ b/src/dring/callmanager_interface.h @@ -57,6 +57,8 @@ DRING_PUBLIC std::vector<std::string> getCallList(); DRING_PUBLIC void removeConference(const std::string& conference_id); DRING_PUBLIC bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); DRING_PUBLIC void createConfFromParticipantList(const std::vector<std::string>& participants); +DRING_PUBLIC void setConferenceLayout(const std::string& confId, uint32_t layout); +DRING_PUBLIC void setActiveParticipant(const std::string& confId, const std::string& callId); DRING_PUBLIC bool isConferenceParticipant(const std::string& call_id); DRING_PUBLIC bool addParticipant(const std::string& callID, const std::string& confID); DRING_PUBLIC bool addMainParticipant(const std::string& confID); diff --git a/src/manager.cpp b/src/manager.cpp index 760d6ba0416b5855a909dfbc428800950c028ef9..5af6beff3fa41b045afeecf9f33e8deeea6f05ad 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -83,6 +83,7 @@ using random_device = dht::crypto::random_device; #include "libav_utils.h" #include "video/sinkclient.h" +#include "media/video/video_mixer.h" #include "audio/tonecontrol.h" #include "data_transfer.h" @@ -1450,6 +1451,36 @@ Manager::createConfFromParticipantList(const std::vector< std::string > &partici } } +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; + } + } +} + +void +Manager::setActiveParticipant(const std::string& confId, const std::string& callId) +{ + if (auto conf = getConferenceFromID(confId)) { + conf->setActiveParticipant(callId); + } +} + bool Manager::detachLocalParticipant(const std::string& conf_id) { diff --git a/src/manager.h b/src/manager.h index 26b8f8065c4956a473099bef0fd7a0986272564a..247d647206bb51396447ce9e09d19b880632accd 100644 --- a/src/manager.h +++ b/src/manager.h @@ -293,6 +293,20 @@ class DRING_TESTABLE Manager { */ void createConfFromParticipantList(const std::vector< std::string > &); + /** + * Change the conference layout + * @param confId + * @param layout 0 = matrix, 1 = one big, others in small, 2 = one in big + */ + void setConferenceLayout(const std::string& confId, int layout); + + /** + * Change the active participant (used in layout != matrix) + * @param confId + * @param callId If callId not found, the local video will be shown + */ + void setActiveParticipant(const std::string& confId, const std::string& callId); + /** * Detach a participant from a conference, put the call on hold, do not hangup it * @param call id diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp index 3c3a13b759ea80c1db6ef721d6209784a56c8c08..316a72bda6b23af845f518d8e87b6d13119dc63b 100644 --- a/src/media/video/video_mixer.cpp +++ b/src/media/video/video_mixer.cpp @@ -121,6 +121,12 @@ VideoMixer::stopInput() } } +void +VideoMixer::setActiveParticipant(Observable<std::shared_ptr<MediaFrame>>* ob) +{ + activeSource_ = ob; +} + void VideoMixer::attached(Observable<std::shared_ptr<MediaFrame>>* ob) { @@ -138,6 +144,11 @@ VideoMixer::detached(Observable<std::shared_ptr<MediaFrame>>* ob) for (const auto& x : sources_) { if (x->source == ob) { + // Handle the case where the current shown source leave the conference + if (activeSource_ == ob) { + currentLayout_ = Layout::GRID; + activeSource_ = nullptr; + } sources_.remove(x); break; } @@ -187,20 +198,42 @@ VideoMixer::process() auto lock(rwMutex_.read()); int i = 0; + bool activeFound = false; for (const auto& x : sources_) { /* thread stop pending? */ if (!loop_.isRunning()) return; - // make rendered frame temporarily unavailable for update() - // to avoid concurrent access. - std::unique_ptr<VideoFrame> input; - x->atomic_swap_render(input); + if (currentLayout_ != Layout::ONE_BIG + or activeSource_ == x->source + or (not activeSource_ and not activeFound) /* By default ONE_BIG will show the first source */) { + + // make rendered frame temporarily unavailable for update() + // to avoid concurrent access. + std::unique_ptr<VideoFrame> input; + x->atomic_swap_render(input); + + auto wantedIndex = i; + if (currentLayout_ == Layout::ONE_BIG) { + wantedIndex = 0; + activeFound = true; + } else if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) { + if (!activeSource_ && i == 0) { + activeFound = true; + } if (activeSource_ == x->source) { + wantedIndex = 0; + activeFound = true; + } else if (not activeFound) { + wantedIndex += 1; + } + } + + if (input) + render_frame(output, *input, x, wantedIndex); + + x->atomic_swap_render(input); + } - if (input) - render_frame(output, *input, x, i); - - x->atomic_swap_render(input); ++i; } } @@ -221,12 +254,29 @@ VideoMixer::render_frame(VideoFrame& output, const VideoFrame& input, std::shared_ptr<VideoFrame> frame = input; #endif - const int n = sources_.size(); - const int zoom = ceil(sqrt(n)); + const int n = currentLayout_ == Layout::ONE_BIG? 1 : sources_.size(); + const int zoom = currentLayout_ == Layout::ONE_BIG_WITH_SMALL? std::max(6,n) : ceil(sqrt(n)); int cell_width = width_ / zoom; int cell_height = height_ / zoom; + if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL && index == 0) { + // In ONE_BIG_WITH_SMALL, the first line at the top is the previews + // The rest is the active source + cell_width = width_; + cell_height = height_ - cell_height; + } int xoff = (index % zoom) * cell_width; int yoff = (index / zoom) * cell_height; + if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) { + if (index == 0) { + xoff = 0; + yoff = height_ / zoom; // First line height + } else { + xoff = (index-1) * cell_width; + // Show sources in center + xoff += (width_ - (n - 1) * cell_width) / 2; + yoff = 0; + } + } AVFrameSideData* sideData = av_frame_get_side_data(frame->pointer(), AV_FRAME_DATA_DISPLAYMATRIX); int angle = 0; diff --git a/src/media/video/video_mixer.h b/src/media/video/video_mixer.h index 4bfa7688d48bf1a586437814ec1823f1b16bc018..e75d6f82f52a74b0a849083a957b1b82344f6c70 100644 --- a/src/media/video/video_mixer.h +++ b/src/media/video/video_mixer.h @@ -35,6 +35,13 @@ namespace jami { namespace video { class SinkClient; + +enum class Layout { + GRID, + ONE_BIG_WITH_SMALL, + ONE_BIG +}; + class VideoMixer: public VideoGenerator, public VideoFramePassiveReader @@ -57,6 +64,12 @@ public: void switchInput(const std::string& input); void stopInput(); + void setActiveParticipant(Observable<std::shared_ptr<MediaFrame>>* ob); + + void setVideoLayout(Layout newLayout) { + currentLayout_ = newLayout; + } + private: NON_COPYABLE(VideoMixer); @@ -74,7 +87,6 @@ private: int width_ = 0; int height_ = 0; AVPixelFormat format_ = AV_PIX_FMT_YUV422P; - std::list<std::unique_ptr<VideoMixerSource>> sources_; rw_mutex rwMutex_; std::shared_ptr<SinkClient> sink_; @@ -84,6 +96,10 @@ private: VideoScaler scaler_; ThreadLoop loop_; // as to be last member + + Layout currentLayout_ {Layout::GRID}; + Observable<std::shared_ptr<MediaFrame>>* activeSource_ {nullptr}; + std::list<std::unique_ptr<VideoMixerSource>> sources_; }; }} // namespace jami::video diff --git a/src/sip/sipvoiplink.h b/src/sip/sipvoiplink.h index 269ad7d77538cbbc53290b2aeb0efac58ee0cfd9..eaf878267c3d9902c3bb5f1215841291ee86f4ad 100644 --- a/src/sip/sipvoiplink.h +++ b/src/sip/sipvoiplink.h @@ -56,8 +56,6 @@ class SIPAccountBase; class SIPVoIPLink; class SipTransportBroker; -typedef std::map<std::string, std::shared_ptr<SIPCall> > SipCallMap; - /** * @file sipvoiplink.h * @brief Specific VoIPLink for SIP (SIP core for incoming and outgoing events).