diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index febb490e8aa03050f467ddcc37512cf3d3385f64..4c9acb4ddae1f01c3dac32c84488910c8615d729 100644
--- a/bin/dbus/cx.ring.Ring.CallManager.xml
+++ b/bin/dbus/cx.ring.Ring.CallManager.xml
@@ -258,6 +258,13 @@
             <arg type="b" name="state" direction="in"/>
         </method>
 
+        <method name="muteParticipant" tp:name-for-bindings="muteParticipant">
+            <tp:added version="9.7.0"/>
+            <arg type="s" name="confId" direction="in"/>
+            <arg type="s" name="peerId" direction="in"/>
+            <arg type="b" name="state" 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 9ccbc714622d1cc0dc9ca95ab091a58fae4e2772..2fbe410b7d124f998a00df462158068beaf0b5fa 100644
--- a/bin/dbus/dbuscallmanager.cpp
+++ b/bin/dbus/dbuscallmanager.cpp
@@ -303,3 +303,10 @@ DBusCallManager::setModerator(const std::string& confId, const std::string& peer
 {
     DRing::setModerator(confId, peerId, state);
 }
+
+void
+DBusCallManager::muteParticipant(const std::string& confId, const std::string& peerId, const bool& state)
+{
+    DRing::muteParticipant(confId, peerId, state);
+}
+
diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h
index ecbe7f64e2fc4dc2004253276dd0c2770cff57b0..5d27a1a294d806afdb6801162f76c074457e9253 100644
--- a/bin/dbus/dbuscallmanager.h
+++ b/bin/dbus/dbuscallmanager.h
@@ -102,6 +102,7 @@ class DRING_PUBLIC DBusCallManager :
         void startSmartInfo(const uint32_t& refreshTimeMs);
         void stopSmartInfo();
         void setModerator(const std::string& confId, const std::string& peerId, const bool& state);
+        void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state);
 };
 
 #endif // __RING_CALLMANAGER_H__
diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i
index d0167c43c5320d7039a43b6776067e133cf93567..4a3de61d7d84f0d67f232c888389768d26d11434 100644
--- a/bin/jni/callmanager.i
+++ b/bin/jni/callmanager.i
@@ -93,6 +93,7 @@ std::string getConferenceId(const std::string& callID);
 std::map<std::string, std::string> getConferenceDetails(const std::string& callID);
 std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId);
 void setModerator(const std::string& confId, const std::string& peerId, const bool& state);
+void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state);
 
 /* File Playback methods */
 bool startRecordedFilePlayback(const std::string& filepath);
diff --git a/bin/nodejs/callmanager.i b/bin/nodejs/callmanager.i
index 4b5fdc40fcddf292dccfd22e06fe6e002370a0ae..12f10cad92c2ef908fb8853b7f64ab3553079c7f 100644
--- a/bin/nodejs/callmanager.i
+++ b/bin/nodejs/callmanager.i
@@ -91,6 +91,7 @@ std::string getConferenceId(const std::string& callID);
 std::map<std::string, std::string> getConferenceDetails(const std::string& callID);
 std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId);
 void setModerator(const std::string& confId, const std::string& peerId, const bool& state);
+void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state);
 
 /* File Playback methods */
 bool startRecordedFilePlayback(const std::string& filepath);
diff --git a/configure.ac b/configure.ac
index c59ca2726487eb598ba963eaaa81826087497af4..a1b6815223c345e72041d8e5a7710d76e033f48c 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.6.0],[ring@gnu.org],[jami])
+AC_INIT([Jami Daemon],[9.7.0],[ring@gnu.org],[jami])
 
 AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2020]])
 AC_REVISION([$Revision$])
diff --git a/src/call.h b/src/call.h
index 247783b18bbf91b7c021c2c6af407b24c7e851a0..2116c90ec0c8308cc1b75594b5a00f290ed9c08b 100644
--- a/src/call.h
+++ b/src/call.h
@@ -364,6 +364,9 @@ protected:
     /** Protect every attribute that can be changed by two threads */
     mutable std::recursive_mutex callMutex_ {};
 
+    mutable std::mutex confInfoMutex_ {};
+    mutable ConfInfo confInfo_ {};
+
 private:
     bool validStateTransition(CallState newState);
 
@@ -409,8 +412,6 @@ private:
     ///< MultiDevice: message received by subcall to merged yet
     MsgList pendingInMessages_;
 
-    mutable std::mutex confInfoMutex_ {};
-    mutable ConfInfo confInfo_ {};
 };
 
 // Helpers
diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp
index bd7b63bc7dc4ce95e103ed4f42cae6302fdfd19d..77d0f6bfe4f646f9cba6a327f6d41573be056fd1 100644
--- a/src/client/callmanager.cpp
+++ b/src/client/callmanager.cpp
@@ -351,4 +351,11 @@ setModerator(const std::string& confId,
     jami::Manager::instance().setModerator(confId, peerId, state);
 }
 
+void
+muteParticipant(const std::string& confId,
+             const std::string& peerId,
+             const bool& state)
+{
+    jami::Manager::instance().muteParticipant(confId, peerId, state);
+}
 } // namespace DRing
diff --git a/src/conference.cpp b/src/conference.cpp
index 46fb35a332137438000b0ce3b59784ac2d06dca2..982624117151e57693bc5ba0e0fddb3cbbd3e079 100644
--- a/src/conference.cpp
+++ b/src/conference.cpp
@@ -25,8 +25,8 @@
 #include "conference.h"
 #include "manager.h"
 #include "audio/audiolayer.h"
-#include "audio/ringbufferpool.h"
 #include "jamidht/jamiaccount.h"
+#include "string_utils.h"
 
 #ifdef ENABLE_VIDEO
 #include "sip/sipcall.h"
@@ -38,6 +38,10 @@
 #include "call_factory.h"
 
 #include "logger.h"
+#include "dring/media_const.h"
+#include "audio/ringbufferpool.h"
+
+using namespace std::literals;
 
 namespace jami {
 
@@ -91,11 +95,12 @@ Conference::Conference()
                                  and not videoMixer->getActiveParticipant()); // by default, local
                                                                               // is shown as active
                 subCalls.erase(it->second);
-                auto partURI = uri;
-                auto separator = partURI.find('@');
-                if (separator != std::string::npos)
-                    partURI = partURI.substr(0, separator);
+                std::string_view partURI = uri;
+                partURI = string_remove_suffix(partURI, '@');
                 auto isModerator = shared->isModerator(partURI);
+                if (uri.empty())
+                    partURI = "host";
+                auto isMuted = shared->isMuted(partURI);
                 newInfo.emplace_back(ParticipantInfo {std::move(uri),
                                                       "",
                                                       active,
@@ -104,7 +109,7 @@ Conference::Conference()
                                                       info.w,
                                                       info.h,
                                                       !info.hasVideo,
-                                                      false,
+                                                      isMuted,
                                                       isModerator});
             }
             lk.unlock();
@@ -262,13 +267,11 @@ Conference::sendConferenceInfos()
 
             ConfInfo confInfo = std::move(getConfInfoHostUri(account->getUsername()+ "@ring.dht"));
             Json::Value jsonArray = {};
-            std::vector<std::map<std::string, std::string>> toSend = {};
             {
                 std::lock_guard<std::mutex> lk2(confInfoMutex_);
                 for (const auto& info : confInfo) {
                     jsonArray.append(info.toJson());
                 }
-                toSend = confInfo.toVectorMapStringString();
             }
 
             Json::StreamWriterBuilder builder = {};
@@ -386,6 +389,40 @@ Conference::bindParticipant(const std::string& participant_id)
     }
 }
 
+void
+Conference::unbindParticipant(const std::string& participant_id)
+{
+    JAMI_INFO("Unbind participant %s from conference %s", participant_id.c_str(), id_.c_str());
+    Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(participant_id);
+}
+
+void
+Conference::bindHost()
+{
+    JAMI_INFO("Bind host to conference %s", id_.c_str());
+
+    auto& rbPool = Manager::instance().getRingBufferPool();
+
+    for (const auto& item : participants_) {
+        if (auto call = Manager::instance().getCallFromCallID(item)) {
+            std::string_view uri = call->getPeerNumber();
+            uri = string_remove_suffix(uri, '@');
+            if (isMuted(uri))
+                continue;
+            rbPool.bindCallID(item, RingBufferPool::DEFAULT_ID);
+            rbPool.flush(RingBufferPool::DEFAULT_ID);
+        }
+    }
+}
+
+
+void
+Conference::unbindHost()
+{
+    JAMI_INFO("Unbind host from conference %s", id_.c_str());
+    Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(RingBufferPool::DEFAULT_ID);
+}
+
 const ParticipantSet&
 Conference::getParticipantList() const
 {
@@ -498,12 +535,10 @@ 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);
+        std::string_view uri = call->getPeerNumber();
+        uri = string_remove_suffix(uri, '@');
         if (!isModerator(uri)) {
-            JAMI_WARN("Received conference order from a non master (%s)", uri.c_str());
+            JAMI_WARN("Received conference order from a non master (%s)", uri.data());
             return;
         }
 
@@ -512,7 +547,7 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder)
         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());
+            JAMI_WARN("Couldn't parse conference order from %s", uri.data());
             return;
         }
         if (root.isMember("layout")) {
@@ -521,18 +556,21 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder)
         if (root.isMember("activeParticipant")) {
             setActiveParticipant(root["activeParticipant"].asString());
         }
+        if (root.isMember("muteParticipant")  and root.isMember("muteState")) {
+            muteParticipant(root["muteParticipant"].asString(), root["muteState"].asString() == "true");
+        }
     }
 }
 
 bool
-Conference::isModerator(const std::string& uri) const
+Conference::isModerator(std::string_view uri) const
 {
     return std::find_if(moderators_.begin(),
                         moderators_.end(),
-                        [&uri](const std::string& moderator) {
-                            return moderator.find(uri) != std::string::npos;
+                        [&uri](std::string_view moderator) {
+                            return moderator.find(uri) != std::string_view::npos;
                         })
-           != moderators_.end();
+           != moderators_.end() or isHost(uri);
 }
 
 void
@@ -540,17 +578,15 @@ Conference::setModerator(const std::string& uri, const bool& state)
 {
     for (const auto& p : participants_) {
         if (auto call = Manager::instance().callFactory.getCall<SIPCall>(p)) {
-            auto partURI = call->getPeerNumber();
-            auto separator = partURI.find('@');
-            if (separator != std::string::npos)
-                partURI = partURI.substr(0, separator);
+            std::string_view partURI = call->getPeerNumber();
+            partURI = string_remove_suffix(partURI, '@');
             if (partURI == uri) {
                 if (state and not isModerator(uri)) {
-                    JAMI_DBG("Add %s as moderator", partURI.c_str());
+                    JAMI_DBG("Add %s as moderator", partURI.data());
                     moderators_.emplace(uri);
                     updateModerators();
                 } else if (not state and isModerator(uri)) {
-                    JAMI_DBG("Remove %s as moderator", partURI.c_str());
+                    JAMI_DBG("Remove %s as moderator", partURI.data());
                     moderators_.erase(uri);
                     updateModerators();
                 }
@@ -568,17 +604,101 @@ Conference::updateModerators()
         std::lock_guard<std::mutex> lk2(confInfoMutex_);
         for (auto& info : confInfo_) {
             auto uri = info.uri;
-            auto separator = uri.find('@');
-            if (separator != std::string::npos)
-                uri = uri.substr(0, separator);
+            uri = string_remove_suffix(uri, '@');
             info.isModerator = isModerator(uri);
         }
     }
     sendConferenceInfos();
 }
 
+bool
+Conference::isMuted(std::string_view uri) const
+{
+    return std::find_if(participantsMuted_.begin(),
+                        participantsMuted_.end(),
+                        [&uri](std::string_view pMuted) {
+                            return pMuted.find(uri) != std::string_view::npos;
+                        })
+           != participantsMuted_.end();
+}
+
+void
+Conference::muteParticipant(const std::string& uri, const bool& state, const std::string& mediaType)
+{
+    // Mute host
+    if (isHost(uri)) {
+        if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_AUDIO) == 0) {
+            if (state and not isMuted("host")) {
+                JAMI_DBG("Mute host");
+                participantsMuted_.emplace("host");
+                unbindHost();
+                updateMuted();
+            } else if (not state and isMuted("host")) {
+                JAMI_DBG("Unmute host");
+                participantsMuted_.erase("host");
+                bindHost();
+                updateMuted();
+            }
+            emitSignal<DRing::CallSignal::AudioMuted>(id_, state);
+            return;
+        } else if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_VIDEO) == 0) {
+#ifdef ENABLE_VIDEO
+            if (state) {
+                if (auto mixer = getVideoMixer()) {
+                    mixer->stopInput();
+                }
+            } else {
+                if (auto mixer = getVideoMixer()) {
+                    mixer->switchInput(mediaInput_);
+                }
+            }
+            return;
+#endif
+        }
+    }
+
+    // Mute participant
+    for (const auto& p : participants_) {
+        std::string_view peerURI = string_remove_suffix(uri, '@');
+        if (auto call = Manager::instance().callFactory.getCall<SIPCall>(p)) {
+            std::string_view partURI = call->getPeerNumber();
+            partURI = string_remove_suffix(partURI, '@');
+            if (partURI == peerURI) {
+                if (state and not isMuted(partURI)) {
+                    JAMI_DBG("Mute participant %s", partURI.data());
+                    participantsMuted_.emplace(std::string(partURI));
+                    unbindParticipant(p);
+                    updateMuted();
+                } else if (not state and isMuted(partURI)) {
+                    JAMI_DBG("Unmute participant %s", partURI.data());
+                    participantsMuted_.erase(std::string(partURI));
+                    bindParticipant(p);
+                    updateMuted();
+                }
+                return;
+            }
+        }
+    }
+}
+
+void
+Conference::updateMuted()
+{
+    {
+        std::lock_guard<std::mutex> lk2(confInfoMutex_);
+        for (auto& info : confInfo_) {
+            auto uri = string_remove_suffix(info.uri, '@');
+            if (uri.empty())
+                uri = "host";
+            info.audioMuted = isMuted(uri);
+        }
+    }
+    sendConferenceInfos();
+}
+
+
 ConfInfo
-Conference::getConfInfoHostUri(const std::string& uri)
+Conference::getConfInfoHostUri(std::string_view uri)
 {
     ConfInfo newInfo = confInfo_;
     for (auto& info : newInfo) {
@@ -590,6 +710,28 @@ Conference::getConfInfoHostUri(const std::string& uri)
     return newInfo;
 }
 
+bool
+Conference::isHost(std::string_view uri) const
+{
+    if (uri.empty())
+        return true;
+
+    // Check if the URI is a local URI (AccountID) for at least one of the subcall
+    // (a local URI can be in the call with another device)
+    for (const auto& p : participants_) {
+        if (auto call = Manager::instance().callFactory.getCall<SIPCall>(p)) {
+            auto w = call->getAccount();
+            auto account = w.lock();
+            if (!account)
+                continue;
+            if (account->getUsername() == uri) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 void
 Conference::updateConferenceInfo(ConfInfo confInfo)
 {
diff --git a/src/conference.h b/src/conference.h
index de66bf937f6050f6dd8075428a714e575415f46a..0f7cb02b513bffbc392958e17a8a9b33918d6795 100644
--- a/src/conference.h
+++ b/src/conference.h
@@ -28,6 +28,7 @@
 #include <string>
 #include <memory>
 #include <vector>
+#include <string_view>
 
 #include "audio/audio_input.h"
 
@@ -182,6 +183,21 @@ public:
      */
     void bindParticipant(const std::string& participant_id);
 
+    /**
+     * Bind host to the conference
+     */
+    void bindHost();
+
+    /**
+     * unbind a participant from the conference
+     */
+    void unbindParticipant(const std::string& participant_id);
+
+    /**
+     * unbind host from conference
+     */
+    void unbindHost();
+
     /**
      * Get the participant list for this conference
      */
@@ -206,7 +222,6 @@ public:
     void detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame);
 
     void onConfOrder(const std::string& callId, const std::string& order);
-    void setModerator(const std::string& uri, const bool& state);
 
 #ifdef ENABLE_VIDEO
     std::shared_ptr<video::VideoMixer> getVideoMixer();
@@ -221,13 +236,17 @@ public:
 
     void updateConferenceInfo(ConfInfo confInfo);
 
+    void setModerator(const std::string& uri, const bool& state);
+    void muteParticipant(const std::string& uri, const bool& state, const std::string& mediaType = "MEDIA_TYPE_AUDIO");
+
 private:
     std::weak_ptr<Conference> weak()
     {
         return std::static_pointer_cast<Conference>(shared_from_this());
     }
 
-    bool isModerator(const std::string& uri) const;
+    bool isModerator(std::string_view uri) const;
+    void updateModerators();
 
     std::string id_;
     State confState_ {State::ACTIVE_ATTACHED};
@@ -248,13 +267,16 @@ private:
 
     std::shared_ptr<jami::AudioInput> audioMixer_;
     std::set<std::string> moderators_ {};
+    std::set<std::string> participantsMuted_ {};
 
     void initRecorder(std::shared_ptr<MediaRecorder>& rec);
     void deinitRecorder(std::shared_ptr<MediaRecorder>& rec);
 
-    void updateModerators();
+    bool isMuted(std::string_view uri) const;
+    void updateMuted();
 
-    ConfInfo getConfInfoHostUri(const std::string& uri);
+    ConfInfo getConfInfoHostUri(std::string_view uri);
+    bool isHost(std::string_view uri) const;
 };
 
 } // namespace jami
diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h
index 0fb9873aa116006abe73eb91845eaadce0917447..c52d49e9b7153bdb3d6289ec20688363ad18221d 100644
--- a/src/dring/callmanager_interface.h
+++ b/src/dring/callmanager_interface.h
@@ -78,6 +78,7 @@ DRING_PUBLIC std::map<std::string, std::string> getConferenceDetails(const std::
 DRING_PUBLIC std::vector<std::map<std::string, std::string>> getConferenceInfos(
     const std::string& confId);
 DRING_PUBLIC void setModerator(const std::string& confId, const std::string& peerId, const bool& state);
+DRING_PUBLIC void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state);
 
 /* Statistic related methods */
 DRING_PUBLIC void startSmartInfo(uint32_t refreshTimeMs);
diff --git a/src/manager.cpp b/src/manager.cpp
index b92a8b592b2b389953b8242ee223eae54a0012d2..b9baec1fcf0a6f2dedb1f6049d8a19b462ad0b33 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -3231,4 +3231,23 @@ Manager::setModerator(const std::string& confId, const std::string& peerId, cons
         JAMI_WARN("Fail to change moderator %s, conference %s not found", peerId.c_str(), confId.c_str());
 }
 
+void
+Manager::muteParticipant(const std::string& confId, const std::string& participant, const bool& state)
+{
+    if (auto conf = getConferenceFromID(confId)) {
+        conf->muteParticipant(participant, state);
+    } else if (auto call = getCallFromCallID(confId)) {
+        std::map<std::string, std::string> messages;
+        Json::Value root;
+        root["muteParticipant"] = participant;
+        root["muteState"] = state ? TRUE_STR : FALSE_STR;
+        Json::StreamWriterBuilder wbuilder;
+        wbuilder["commentStyle"] = "None";
+        wbuilder["indentation"] = "";
+        auto output = Json::writeString(wbuilder, root);
+        messages["application/confOrder+json"] = output;
+        call->sendTextMessage(messages, call->getPeerDisplayName());
+    }
+}
+
 } // namespace jami
diff --git a/src/manager.h b/src/manager.h
index 759f78fc2af6bc47aad3b5a86533492b60571646..7e83fd81977f6992a8b4057c247a46d6c36fcf2a 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -935,6 +935,7 @@ public:
 #endif
 
     void setModerator(const std::string& confId, const std::string& peerId, const bool& state);
+    void muteParticipant(const std::string& confId, const std::string& peerId, const bool& state);
 
 private:
     Manager();
diff --git a/src/media/audio/ringbufferpool.cpp b/src/media/audio/ringbufferpool.cpp
index 92429a7deaf4752446a3a834ef3895e0dece362b..4b5f094a2947142c0324a452d2834fc4cd744929 100644
--- a/src/media/audio/ringbufferpool.cpp
+++ b/src/media/audio/ringbufferpool.cpp
@@ -236,6 +236,27 @@ RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, const std::st
         removeReaderFromRingBuffer(rb, process_id);
 }
 
+void
+RingBufferPool::unBindAllHalfDuplexOut(const std::string& call_id)
+{
+    const auto& rb_call = getRingBuffer(call_id);
+    if (not rb_call) {
+        JAMI_ERR("No ringbuffer associated to call '%s'", call_id.c_str());
+        return;
+    }
+
+    std::lock_guard<std::recursive_mutex> lk(stateLock_);
+
+    auto bindings = getReadBindings(call_id);
+    if (not bindings)
+        return;
+
+    const auto bindings_copy = *bindings; // temporary copy
+    for (const auto& rbuf : bindings_copy) {
+        removeReaderFromRingBuffer(rb_call, rbuf->getId());
+    }
+}
+
 void
 RingBufferPool::unBindAll(const std::string& call_id)
 {
diff --git a/src/media/audio/ringbufferpool.h b/src/media/audio/ringbufferpool.h
index db0643a7202cf17a04c033c4189ba0c99d8afd66..c28a267dd1602210b23a98c7848d33287de21239 100644
--- a/src/media/audio/ringbufferpool.h
+++ b/src/media/audio/ringbufferpool.h
@@ -73,6 +73,8 @@ public:
      */
     void unBindHalfDuplexOut(const std::string& process_id, const std::string& call_id);
 
+    void unBindAllHalfDuplexOut(const std::string& call_id);
+
     void unBindAll(const std::string& call_id);
 
     bool waitForDataAvailable(const std::string& call_id,
diff --git a/src/string_utils.cpp b/src/string_utils.cpp
index 567102b8666666ebb6839898c46f92a64cc07932..6dad08fea90cecdea7b3bed273c9e9047567f94e 100644
--- a/src/string_utils.cpp
+++ b/src/string_utils.cpp
@@ -29,6 +29,7 @@
 #include <stdexcept>
 #include <ios>
 #include <charconv>
+#include <string_view>
 #ifdef _WIN32
 #include <windows.h>
 #include <oleauto.h>
@@ -136,4 +137,13 @@ string_replace(std::string& str, const std::string& from, const std::string& to)
     }
 }
 
+std::string_view
+string_remove_suffix(std::string_view str, char separator)
+{
+    auto it = str.find(separator);
+    if (it != std::string_view::npos)
+        str = str.substr(0, it);
+    return str;
+}
+
 } // namespace jami
diff --git a/src/string_utils.h b/src/string_utils.h
index 8f0bb07d8738d1e5d77b5b2f334ef8b9479c4f03..c172323cc87f68d90adf4b2a9c2342e8849e0e26 100644
--- a/src/string_utils.h
+++ b/src/string_utils.h
@@ -141,4 +141,6 @@ std::vector<unsigned> split_string_to_unsigned(const std::string& s, char sep);
 
 void string_replace(std::string& str, const std::string& from, const std::string& to);
 
+std::string_view string_remove_suffix(std::string_view str, char separator);
+
 } // namespace jami