diff --git a/src/call.cpp b/src/call.cpp
index c036d56c7c32e5377f120a1dcbdec4557df4c627..87dfa762b790278f19826a5c72bbeefd3b07dcc5 100644
--- a/src/call.cpp
+++ b/src/call.cpp
@@ -676,28 +676,9 @@ Call::setConferenceInfo(const std::string& msg)
             // confID_ empty -> participant set confInfo with the received one
             confInfo_ = std::move(newInfo);
             // Inform client that layout has changed
-            jami::emitSignal<DRing::CallSignal::OnConferenceInfosUpdated>(
-                id_, confInfo_.toVectorMapStringString());
-        } else {
-            // confID_ not empty -> host merge confInfo with the received confInfo
-            for (auto& newI : newInfo) {
-                bool isNewParticipant = true;
-                for (auto& oldI : confInfo_) {
-                    if (newI.uri == oldI.uri) {
-                        oldI = newI;
-                        isNewParticipant = false;
-                        break;
-                    }
-                }
-                if (isNewParticipant) {
-                    // ParticipantInfo not present in confInfo -> the sender of newInfo ...
-                    // is currently hosting another conference. Add the unknown participant ...
-                    // to the confInfo
-                    confInfo_.emplace_back(newI);
-                }
-            }
-            if (auto conf = Manager::instance().getConferenceFromID(confID_))
-                conf->updateConferenceInfo(confInfo_);
+            jami::emitSignal<DRing::CallSignal::OnConferenceInfosUpdated>(id_, confInfo_.toVectorMapStringString());
+        } else if (auto conf = Manager::instance().getConferenceFromID(confID_)) {
+            conf->mergeConfInfo(newInfo, getPeerNumber());
         }
     }
 }
diff --git a/src/conference.cpp b/src/conference.cpp
index 3228946a48066b303cc4ddf8474fdf28da19dc5c..5651bc8440f6671d6bb53975d538f93d554cf57a 100644
--- a/src/conference.cpp
+++ b/src/conference.cpp
@@ -40,6 +40,9 @@
 #include "logger.h"
 #include "dring/media_const.h"
 #include "audio/ringbufferpool.h"
+#include "sip/sipcall.h"
+
+#include <opendht/thread_pool.h>
 
 using namespace std::literals;
 
@@ -275,6 +278,16 @@ ConfInfo::toVectorMapStringString() const
     return infos;
 }
 
+std::string
+ConfInfo::toString() const
+{
+    Json::Value jsonArray = {};
+    for (const auto& info : *this) {
+        jsonArray.append(info.toJson());
+    }
+    return Json::writeString(Json::StreamWriterBuilder{}, jsonArray);
+}
+
 void
 Conference::sendConferenceInfos()
 {
@@ -288,25 +301,19 @@ Conference::sendConferenceInfos()
             if (!account)
                 continue;
 
-            ConfInfo confInfo = getConfInfoHostUri(account->getUsername() + "@ring.dht");
-            Json::Value jsonArray = {};
-            for (const auto& info : confInfo) {
-                jsonArray.append(info.toJson());
-            }
-
-            runOnMainThread(
-                [call,
-                 confInfoStr = Json::writeString(Json::StreamWriterBuilder {}, jsonArray),
-                 from = account->getFromUri()] {
-                    call->sendTextMessage({{"application/confInfo+json", confInfoStr}}, from);
-                });
+            dht::ThreadPool::io().run([
+                call,
+                confInfo = getConfInfoHostUri(account->getUsername()+ "@ring.dht",
+                                                call->getPeerNumber()),
+                from = account->getFromUri()
+            ] {
+                call->sendTextMessage({{"application/confInfo+json", confInfo.toString()}}, from);
+            });
         }
     }
 
     // Inform client that layout has changed
-    jami::emitSignal<DRing::CallSignal::OnConferenceInfosUpdated>(id_,
-                                                                  confInfo_
-                                                                      .toVectorMapStringString());
+    jami::emitSignal<DRing::CallSignal::OnConferenceInfosUpdated>(id_, getConfInfoHostUri("", "").toVectorMapStringString());
 }
 
 void
@@ -721,15 +728,34 @@ Conference::updateMuted()
 }
 
 ConfInfo
-Conference::getConfInfoHostUri(std::string_view uri)
+Conference::getConfInfoHostUri(std::string_view localHostURI, std::string_view destURI)
 {
     ConfInfo newInfo = confInfo_;
-    for (auto& info : newInfo) {
-        if (info.uri.empty()) {
-            info.uri = uri;
-            break;
+
+    for (auto it = newInfo.begin(); it != newInfo.end();) {
+        bool isRemoteHost = remoteHosts_.find(it->uri) != remoteHosts_.end();
+        if (it->uri.empty() and not destURI.empty()) {
+            // fill the empty uri with the local host URI, let void for local client
+            it->uri = localHostURI;
+        }
+        if (isRemoteHost) {
+            // Don't send back the ParticipantInfo for remote Host
+            // For other than remote Host, the new info is in remoteHosts_
+            it = newInfo.erase(it);
+        } else {
+            ++it;
         }
     }
+    // Add remote Host info
+    for (const auto& [hostUri, confInfo] : remoteHosts_) {
+        // Add remote info for remote host destination
+        // Example: ConfA, ConfB & ConfC
+        // ConfA send ConfA and ConfB for ConfC
+        // ConfA send ConfA and ConfC for ConfB
+        // ...
+        if (destURI != hostUri)
+            newInfo.insert(newInfo.end(), confInfo.begin(), confInfo.end());
+    }
     return newInfo;
 }
 
@@ -817,4 +843,100 @@ Conference::muteLocalHost(bool is_muted, const std::string& mediaType)
     }
 }
 
+void
+Conference::resizeRemoteParticipant(const std::string& peerURI, ParticipantInfo& remoteCell)
+{
+    int remoteFrameHeight {0};
+    int remoteFrameWidth {0};
+    ParticipantInfo localCell;
+
+    // get the size of the remote frame
+    for (const auto& item : participants_) {
+        auto sipCall = std::dynamic_pointer_cast<SIPCall>(
+            Manager::instance().callFactory.getCall(item, Call::LinkType::SIP));
+        if (sipCall && sipCall->getPeerNumber().find(peerURI) != std::string::npos) {
+            remoteFrameHeight = sipCall->getVideoRtp().getVideoReceive()->getHeight();
+            remoteFrameWidth = sipCall->getVideoRtp().getVideoReceive()->getWidth();
+            break;
+        }
+    }
+
+    if (remoteFrameHeight == 0 or remoteFrameWidth == 0) {
+        JAMI_WARN("Remote frame size not found.");
+        return;
+    }
+
+    // get the size of the local frame
+    for (const auto& p : confInfo_) {
+        if (p.uri == peerURI) {
+            localCell = p;
+            break;
+        }
+    }
+
+    const float zoomX = (float) remoteFrameWidth / localCell.w;
+    const float zoomY = (float) remoteFrameHeight / localCell.h;
+
+    remoteCell.x = remoteCell.x / zoomX + localCell.x;
+    remoteCell.y = remoteCell.y / zoomY + localCell.y;
+    remoteCell.w = remoteCell.w / zoomX;
+    remoteCell.h = remoteCell.h / zoomY;
+
+}
+
+std::string
+Conference::confInfo2str(const ConfInfo& confInfo)
+{
+    Json::Value jsonArray = {};
+    for (const auto& info : confInfo) {
+        jsonArray.append(info.toJson());
+    }
+    return Json::writeString(Json::StreamWriterBuilder{}, jsonArray);
+}
+
+void
+Conference::mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI)
+{
+    if (newInfo.empty()) {
+        JAMI_DBG("confInfo empty, remove remoteHost");
+        remoteHosts_.erase(peerURI);
+        return;
+    }
+
+    for (auto& partInfo : newInfo) {
+        resizeRemoteParticipant(peerURI, partInfo);
+    }
+
+    bool updateNeeded = false;
+    auto it = remoteHosts_.find(peerURI);
+    if (it != remoteHosts_.end()) {
+        // Compare confInfo before update
+        if (it->second != newInfo) {
+            it->second = newInfo;
+            updateNeeded = true;
+        } else
+            JAMI_WARN("No change in confInfo, don't update");
+    } else {
+        remoteHosts_.emplace(peerURI, newInfo);
+        updateNeeded = true;
+    }
+    // Send confInfo only if needed to avoid loops
+    if (updateNeeded) {
+        std::lock_guard<std::mutex> lk(confInfoMutex_);
+        sendConferenceInfos();
+    }
+}
+
+std::string_view
+Conference::findHostforRemoteParticipant(std::string_view uri)
+{
+    for (const auto& host : remoteHosts_) {
+        for (const auto& p : host.second) {
+            if (uri == string_remove_suffix(p.uri, '@'))
+                return host.first;
+        }
+    }
+    return "";
+}
+
 } // namespace jami
diff --git a/src/conference.h b/src/conference.h
index 61aaefcede3d5558ee897de170d3471fd3dd0d5d..76dc6ec0ea086a8ee100e088ab9920c51a7e18b6 100644
--- a/src/conference.h
+++ b/src/conference.h
@@ -29,6 +29,7 @@
 #include <memory>
 #include <vector>
 #include <string_view>
+#include <map>
 
 #include "audio/audio_input.h"
 
@@ -106,13 +107,55 @@ struct ParticipantInfo
                 {"audioModeratorMuted", audioModeratorMuted ? "true" : "false"},
                 {"isModerator", isModerator ? "true" : "false"}};
     }
+
+    friend bool operator==(const ParticipantInfo& p1, const ParticipantInfo& p2)
+    {
+        return
+            p1.uri == p2.uri
+            and p1.device == p2.device
+            and p1.active == p2.active
+            and p1.x == p2.x
+            and p1.y == p2.y
+            and p1.w == p2.w
+            and p1.h == p2.h
+            and p1.videoMuted == p2.videoMuted
+            and p1.audioLocalMuted == p2.audioLocalMuted
+            and p1.audioModeratorMuted == p2.audioModeratorMuted
+            and p1.isModerator == p2.isModerator;
+    }
+
+    friend bool operator!=(const ParticipantInfo& p1, const ParticipantInfo& p2)
+    {
+        return !(p1 == p2);
+    }
 };
 
 struct ConfInfo : public std::vector<ParticipantInfo>
 {
-    std::vector<std::map<std::string, std::string>> toVectorMapStringString() const;
     int h {0};
     int w {0};
+
+    friend bool operator==(const ConfInfo& c1, const ConfInfo& c2)
+    {
+        for (auto& p1 : c1) {
+            auto it = std::find_if(c2.begin(), c2.end(),
+                [p1] (const ParticipantInfo& p2) { return p1 == p2; });
+            if (it != c2.end())
+                continue;
+            else
+                return false;
+        }
+        return true;
+    }
+
+    friend bool operator!=(const ConfInfo& c1, const ConfInfo& c2)
+    {
+        return !(c1 == c2);
+    }
+
+    std::vector<std::map<std::string, std::string>> toVectorMapStringString() const;
+    std::string toString() const;
+
 };
 
 using ParticipantSet = std::set<std::string>;
@@ -250,6 +293,9 @@ public:
     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 resizeRemoteParticipant(const std::string& peerURI, ParticipantInfo& remoteCell);
+    void mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI);
 
 private:
     std::weak_ptr<Conference> weak()
@@ -289,12 +335,16 @@ private:
 
     bool isMuted(std::string_view uri) const;
 
-    ConfInfo getConfInfoHostUri(std::string_view uri);
+    ConfInfo getConfInfoHostUri(std::string_view localHostURI, std::string_view destURI);
     bool isHost(std::string_view uri) const;
     bool audioMuted_ {false};
     bool videoMuted_ {false};
 
     bool localModAdded_ {false};
+
+    std::map<std::string, ConfInfo> remoteHosts_;
+    std::string confInfo2str(const ConfInfo& confInfo);
+    std::string_view findHostforRemoteParticipant(std::string_view uri);
 };
 
 } // namespace jami