diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6fef36dcccf4c8c116ff792b8d3f34be76a085cf..2dd71d125ea42afd9366902370e3141276696668 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.16)
 
 project(jami
-    VERSION 13.10.0
+    VERSION 13.11.0
     LANGUAGES C CXX)
 set(PACKAGE_NAME "Jami Daemon")
 set (CMAKE_CXX_STANDARD 17)
diff --git a/configure.ac b/configure.ac
index 236fc4042923c87af5446ab2ff3ed78469d76c99..796d72c93b09796f90628d6bff4ae23562cdba19 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.10.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[13.11.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 e61b933f1231c70c9ba7e21833494c383a26122e..73c16b77456bf43885c5c9ea7da960964a61dcca 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('jami-daemon', ['c', 'cpp'],
-        version: '13.10.0',
+        version: '13.11.0',
         license: 'GPL3+',
         default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
         meson_version:'>= 0.56'
diff --git a/src/account.cpp b/src/account.cpp
index ded7046ca290eeddc60a575c5f715f30cf3b7c6d..de8ca79bdee169baaf0dd7f6b214b9763ddea84e 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -443,7 +443,7 @@ Account::meetMinimumRequiredVersion(const std::vector<unsigned>& version,
     for (size_t i = 0; i < minRequiredVersion.size(); i++) {
         if (i == version.size() or version[i] < minRequiredVersion[i])
             return false;
-        if (version[i] > minRequiredVersion[i])
+        if (version[i] >= minRequiredVersion[i])
             return true;
     }
     return true;
diff --git a/src/call.h b/src/call.h
index 67df2f886cea2fd3a977d74a878ec5c1e8b95671..565552d2d9e273887fcae3aa860f5467a80f16c5 100644
--- a/src/call.h
+++ b/src/call.h
@@ -371,6 +371,8 @@ public:
 
     virtual std::vector<MediaAttribute> getMediaAttributeList() const = 0;
 
+    virtual std::map<std::string, bool> getAudioStreams() const = 0;
+
 #ifdef ENABLE_VIDEO
     virtual void createSinks(ConfInfo& infos) = 0;
 #endif
diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp
index d7a56c5670854e7677ee430396e95343644b3d09..aca34eeb9f8ddaabeda8980661dce0e774a78965 100644
--- a/src/client/videomanager.cpp
+++ b/src/client/videomanager.cpp
@@ -690,9 +690,9 @@ getVideoDeviceMonitor()
 }
 
 std::shared_ptr<video::VideoInput>
-getVideoInput(const std::string& id, video::VideoInputMode inputMode, const std::string& sink)
+getVideoInput(const std::string& resource, video::VideoInputMode inputMode, const std::string& sink)
 {
-    auto sinkId = sink.empty() ? id : sink;
+    auto sinkId = sink.empty() ? resource : sink;
     auto& vmgr = Manager::instance().getVideoManager();
     std::lock_guard<std::mutex> lk(vmgr.videoMutex);
     auto it = vmgr.videoInputs.find(sinkId);
@@ -702,7 +702,7 @@ getVideoInput(const std::string& id, video::VideoInputMode inputMode, const std:
         }
     }
 
-    auto input = std::make_shared<video::VideoInput>(inputMode, id, sinkId);
+    auto input = std::make_shared<video::VideoInput>(inputMode, resource, sinkId);
     vmgr.videoInputs[sinkId] = input;
     return input;
 }
@@ -715,7 +715,7 @@ VideoManager::setDeviceOrientation(const std::string& deviceId, int angle)
 #endif
 
 std::shared_ptr<AudioInput>
-getAudioInput(const std::string& id)
+getAudioInput(const std::string& device)
 {
     auto& vmgr = Manager::instance().getVideoManager();
     std::lock_guard<std::mutex> lk(vmgr.audioMutex);
@@ -728,15 +728,15 @@ getAudioInput(const std::string& id)
             ++it;
     }
 
-    auto it = vmgr.audioInputs.find(id);
+    auto it = vmgr.audioInputs.find(device);
     if (it != vmgr.audioInputs.end()) {
         if (auto input = it->second.lock()) {
             return input;
         }
     }
 
-    auto input = std::make_shared<AudioInput>(id);
-    vmgr.audioInputs[id] = input;
+    auto input = std::make_shared<AudioInput>(device);
+    vmgr.audioInputs[device] = input;
     return input;
 }
 
diff --git a/src/client/videomanager.h b/src/client/videomanager.h
index 81b5a72652f889d33834f6abd94facd2e61d89a2..eb03fea9ec5ee8196f69fca554f517ad7b1d229f 100644
--- a/src/client/videomanager.h
+++ b/src/client/videomanager.h
@@ -71,11 +71,11 @@ public:
 #ifdef ENABLE_VIDEO
 video::VideoDeviceMonitor& getVideoDeviceMonitor();
 std::shared_ptr<video::VideoInput> getVideoInput(
-    const std::string& id,
+    const std::string& resource,
     video::VideoInputMode inputMode = video::VideoInputMode::Undefined,
     const std::string& sink = "");
 #endif
-std::shared_ptr<AudioInput> getAudioInput(const std::string& id);
+std::shared_ptr<AudioInput> getAudioInput(const std::string& device);
 std::string createMediaPlayer(const std::string& path);
 std::shared_ptr<MediaPlayer> getMediaPlayer(const std::string& id);
 bool pausePlayer(const std::string& id, bool pause);
diff --git a/src/conference.cpp b/src/conference.cpp
index 551b7b35b2b4df6adfb6b00da112a0eaf81064e6..40243964bb645999d799458fb016d9084a2225ad 100644
--- a/src/conference.cpp
+++ b/src/conference.cpp
@@ -329,6 +329,8 @@ Conference::~Conference()
 #endif // ENABLE_PLUGIN
     if (shutdownCb_)
         shutdownCb_(getDuration().count());
+    // do not propagate sharing from conf host to calls
+    closeMediaPlayer(mediaPlayerId_);
     jami_tracepoint(conference_end, id_.c_str());
 }
 
@@ -486,6 +488,7 @@ Conference::isMediaSourceMuted(MediaType type) const
         return true;
     }
 
+    // if one is muted, then consider that all are
     for (const auto& source : hostSources_) {
         if (source.muted_ && source.type_ == type)
             return true;
@@ -576,14 +579,41 @@ bool
 Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
 {
     if (getState() != State::ACTIVE_ATTACHED) {
-        JAMI_ERR("[conf %s] Request media change can be performed only in attached mode",
-                 getConfId().c_str());
+        JAMI_ERROR("[conf {}] Request media change can be performed only in attached mode",
+                   getConfId());
         return false;
     }
 
     JAMI_DEBUG("[conf {:s}] Request media change", getConfId());
 
-    auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, false);
+    auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, false);bool hasFileSharing {false};
+
+    for (const auto& media : mediaAttrList) {
+        if (!media.enabled_ || media.sourceUri_.empty())
+            continue;
+
+        // Supported MRL schemes
+        static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
+
+        const auto pos = media.sourceUri_.find(sep);
+        if (pos == std::string::npos)
+            continue;
+
+        const auto prefix = media.sourceUri_.substr(0, pos);
+        if ((pos + sep.size()) >= media.sourceUri_.size())
+            continue;
+
+        if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
+            hasFileSharing = true;
+            mediaPlayerId_ = media.sourceUri_;
+            createMediaPlayer(mediaPlayerId_);
+        }
+    }
+
+    if (!hasFileSharing) {
+        closeMediaPlayer(mediaPlayerId_);
+        mediaPlayerId_ = "";
+    }
 
     for (auto const& mediaAttr : mediaAttrList) {
         JAMI_DEBUG("[conf {:s}] New requested media: {:s}", getConfId(), mediaAttr.toString(true));
@@ -593,7 +623,9 @@ Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
     for (auto const& mediaAttr : mediaAttrList) {
         // Find media
         auto oldIdx = std::find_if(hostSources_.begin(), hostSources_.end(), [&](auto oldAttr) {
-            return oldAttr.sourceUri_ == mediaAttr.sourceUri_ && oldAttr.type_ == mediaAttr.type_;
+            return oldAttr.sourceUri_ == mediaAttr.sourceUri_
+                   && oldAttr.type_ == mediaAttr.type_
+                   && oldAttr.label_ == mediaAttr.label_;
         });
         // If video, add to newVideoInputs
         // NOTE: For now, only supports video
@@ -617,6 +649,8 @@ Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
         videoMixer_->switchInputs(newVideoInputs);
 #endif
     hostSources_ = mediaAttrList; // New medias
+    if (!isMuted("host"sv) && !isMediaSourceMuted(MediaType::MEDIA_AUDIO))
+        bindHost();
 
     // It's host medias, so no need to negotiate anything, but inform the client.
     reportMediaNegotiationStatus();
@@ -911,20 +945,7 @@ Conference::attachLocalParticipant()
         setState(State::ACTIVE_ATTACHED);
         setLocalHostDefaultMediaSource();
 
-        auto& rbPool = Manager::instance().getRingBufferPool();
-        for (const auto& participant : getParticipantList()) {
-            if (auto call = Manager::instance().getCallFromCallID(participant)) {
-                if (isMuted(call->getCallId()))
-                    rbPool.bindHalfDuplexOut(participant, RingBufferPool::DEFAULT_ID);
-                else
-                    rbPool.bindCallID(participant, RingBufferPool::DEFAULT_ID);
-                rbPool.flush(participant);
-            }
-
-            // Reset ringbuffer's readpointers
-            rbPool.flush(participant);
-        }
-        rbPool.flush(RingBufferPool::DEFAULT_ID);
+        bindHost();
 
 #ifdef ENABLE_VIDEO
         if (videoMixer_) {
@@ -948,12 +969,8 @@ void
 Conference::detachLocalParticipant()
 {
     JAMI_INFO("Detach local participant from conference %s", id_.c_str());
-
     if (getState() == State::ACTIVE_ATTACHED) {
-        foreachCall([&](auto call) {
-            Manager::instance().getRingBufferPool().unBindCallID(call->getCallId(),
-                                                                 RingBufferPool::DEFAULT_ID);
-        });
+        unbindHost();
 
 #ifdef ENABLE_VIDEO
         if (videoMixer_)
@@ -974,31 +991,39 @@ Conference::detachLocalParticipant()
 void
 Conference::bindParticipant(const std::string& participant_id)
 {
-    JAMI_INFO("Bind participant %s to conference %s", participant_id.c_str(), id_.c_str());
+    JAMI_LOG("Bind participant {} to conference {}", participant_id, id_);
 
     auto& rbPool = Manager::instance().getRingBufferPool();
 
-    for (const auto& item : getParticipantList()) {
-        if (participant_id != item) {
-            // Do not attach muted participants
-            if (auto call = Manager::instance().getCallFromCallID(item)) {
-                if (isMuted(call->getCallId()))
-                    rbPool.bindHalfDuplexOut(item, participant_id);
+    // Bind each of the new participant's audio streams to each of the other participants audio streams
+    if (auto participantCall = getCall(participant_id)) {
+        auto participantStreams = participantCall->getAudioStreams();
+        for (auto stream : participantStreams) {
+            for (const auto& other : getParticipantList()) {
+                auto otherCall = other != participant_id ? getCall(other) : nullptr;
+                if (otherCall) {
+                    auto otherStreams = otherCall->getAudioStreams();
+                    for (auto otherStream : otherStreams) {
+                        if (isMuted(other))
+                            rbPool.bindHalfDuplexOut(otherStream.first, stream.first);
+                        else
+                            rbPool.bindRingbuffers(stream.first, otherStream.first);
+
+                        rbPool.flush(otherStream.first);
+                    }
+                }
+            }
+
+            // Bind local participant to other participants only if the
+            // local is attached to the conference.
+            if (getState() == State::ACTIVE_ATTACHED) {
+                if (isMediaSourceMuted(MediaType::MEDIA_AUDIO))
+                    rbPool.bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, stream.first);
                 else
-                    rbPool.bindCallID(participant_id, item);
+                    rbPool.bindRingbuffers(stream.first, RingBufferPool::DEFAULT_ID);
+                rbPool.flush(RingBufferPool::DEFAULT_ID);
             }
         }
-        rbPool.flush(item);
-    }
-
-    // Bind local participant to other participants only if the
-    // local is attached to the conference.
-    if (getState() == State::ACTIVE_ATTACHED) {
-        if (isMediaSourceMuted(MediaType::MEDIA_AUDIO))
-            rbPool.bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, participant_id);
-        else
-            rbPool.bindCallID(participant_id, RingBufferPool::DEFAULT_ID);
-        rbPool.flush(RingBufferPool::DEFAULT_ID);
     }
 }
 
@@ -1006,7 +1031,13 @@ 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);
+    if (auto call = getCall(participant_id)) {
+        auto medias = call->getAudioStreams();
+        auto& rbPool = Manager::instance().getRingBufferPool();
+        for (const auto& [id, muted] : medias) {
+            rbPool.unBindAllHalfDuplexOut(id);
+        }
+    }
 }
 
 void
@@ -1017,20 +1048,57 @@ Conference::bindHost()
     auto& rbPool = Manager::instance().getRingBufferPool();
 
     for (const auto& item : getParticipantList()) {
-        if (auto call = Manager::instance().getCallFromCallID(item)) {
-            if (isMuted(call->getCallId()))
-                continue;
-            rbPool.bindCallID(item, RingBufferPool::DEFAULT_ID);
-            rbPool.flush(RingBufferPool::DEFAULT_ID);
+        if (auto call = getCall(item)) {
+            auto medias = call->getAudioStreams();
+            for (const auto& [id, muted] : medias) {
+                for (const auto& source : hostSources_) {
+                    if (source.type_ == MediaType::MEDIA_AUDIO) {
+                        if (source.label_ == sip_utils::DEFAULT_AUDIO_STREAMID) {
+                            if (muted)
+                                rbPool.bindHalfDuplexOut(id, RingBufferPool::DEFAULT_ID);
+                            else
+                                rbPool.bindRingbuffers(id, RingBufferPool::DEFAULT_ID);
+                        } else {
+                            auto buffer = source.sourceUri_;
+                            static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
+                            const auto pos = source.sourceUri_.find(sep);
+                            if (pos != std::string::npos)
+                                buffer = source.sourceUri_.substr(pos + sep.size());
+
+                            if (muted)
+                                rbPool.bindHalfDuplexOut(id, buffer);
+                            else
+                                rbPool.bindRingbuffers(id, buffer);
+                        }
+                    }
+                }
+                rbPool.flush(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);
+    for (const auto& source : hostSources_) {
+        if (source.type_ == MediaType::MEDIA_AUDIO) {
+            if (source.label_ == sip_utils::DEFAULT_AUDIO_STREAMID) {
+                Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(RingBufferPool::DEFAULT_ID);
+            } else {
+                auto buffer = source.sourceUri_;
+                static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
+                const auto pos = source.sourceUri_.find(sep);
+                if (pos != std::string::npos)
+                    buffer = source.sourceUri_.substr(pos + sep.size());
+
+                Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(buffer);
+            }
+        }
+    }
 }
 
 ParticipantSet
@@ -1463,8 +1531,7 @@ Conference::muteParticipant(const std::string& participant_id, const bool& state
         }
     }
 
-    // NOTE: For now we only have one audio per call, and no way to only
-    // mute one stream
+    // NOTE: For now we have no way to mute only one stream
     if (isHost(participant_id))
         muteHost(state);
     else if (auto call = getCallFromPeerID(participant_id))
diff --git a/src/conference.h b/src/conference.h
index ad1c4298f668b9bbce06dbeb94ed1bac344c7986..20191a6628f16824414c9e5a04bd9cac9a984943 100644
--- a/src/conference.h
+++ b/src/conference.h
@@ -441,6 +441,7 @@ private:
     State confState_ {State::ACTIVE_ATTACHED};
     mutable std::mutex participantsMtx_ {};
     ParticipantSet participants_;
+    std::string mediaPlayerId_ {};
 
     mutable std::mutex confInfoMutex_ {};
     ConfInfo confInfo_ {};
diff --git a/src/manager.cpp b/src/manager.cpp
index 7bfa0fdf9ec0ce2a53f7efa8cee11ed2ecdb0c8b..88796552789005d2b410fa0717ff95c1a4d76142 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -539,8 +539,15 @@ Manager::ManagerPimpl::processRemainingParticipants(Conference& conf)
 
     if (n > 1) {
         // Reset ringbuffer's readpointers
-        for (const auto& p : participants)
-            base_.getRingBufferPool().flush(p);
+        for (const auto& p : participants) {
+            if (auto call = base_.getCallFromCallID(p)) {
+                auto medias = call->getAudioStreams();
+                for (const auto& media : medias) {
+                    JAMI_DEBUG("[call:{}] Remove local audio {}", p, media.first);
+                    base_.getRingBufferPool().flush(media.first);
+                }
+            }
+        }
 
         base_.getRingBufferPool().flush(RingBufferPool::DEFAULT_ID);
     } else if (n == 1) {
@@ -683,7 +690,11 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf)
 
     JAMI_DEBUG("[call:{}] bind to conference {} (callState={})", callId, confId, state);
 
-    base_.getRingBufferPool().unBindAll(callId);
+    auto medias = call.getAudioStreams();
+    for (const auto& media : medias) {
+        JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first);
+        base_.getRingBufferPool().unBindAll(media.first);
+    }
 
     conf.addParticipant(callId);
 
@@ -1680,10 +1691,10 @@ void
 Manager::addAudio(Call& call)
 {
     const auto& callId = call.getCallId();
-    JAMI_INFO("Add audio to call %s", callId.c_str());
+    JAMI_LOG("Add audio to call {}", callId);
 
     if (call.isConferenceParticipant()) {
-        JAMI_DBG("[conf:%s] Attach local audio", callId.c_str());
+        JAMI_DEBUG("[conf:{}] Attach local audio", callId);
 
         // bind to conference participant
         /*auto iter = pimpl_->conferenceMap_.find(callId);
@@ -1691,16 +1702,21 @@ Manager::addAudio(Call& call)
             iter->second->bindParticipant(callId);
         }*/
     } else {
-        JAMI_DBG("[call:%s] Attach audio", callId.c_str());
+        JAMI_DEBUG("[call:{}] Attach audio", callId);
 
         // bind to main
-        getRingBufferPool().bindCallID(callId, RingBufferPool::DEFAULT_ID);
+        auto medias = call.getAudioStreams();
+        for (const auto& media : medias) {
+            JAMI_DEBUG("[call:{}] Attach audio", media.first);
+            getRingBufferPool().bindRingbuffers(media.first,
+                                            RingBufferPool::DEFAULT_ID);
+        }
         auto oldGuard = std::move(call.audioGuard);
         call.audioGuard = startAudioStream(AudioDeviceType::PLAYBACK);
 
         std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
         if (!pimpl_->audiodriver_) {
-            JAMI_ERR("Audio driver not initialized");
+            JAMI_ERROR("Audio driver not initialized");
             return;
         }
         pimpl_->audiodriver_->flushUrgent();
@@ -1712,9 +1728,11 @@ void
 Manager::removeAudio(Call& call)
 {
     const auto& callId = call.getCallId();
-    JAMI_DBG("[call:%s] Remove local audio", callId.c_str());
-    getRingBufferPool().unBindAll(callId);
-    call.audioGuard.reset();
+    auto medias = call.getAudioStreams();
+    for (const auto& media : medias) {
+        JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first);
+        getRingBufferPool().unBindAll(media.first);
+    }
 }
 
 ScheduledExecutor&
diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp
index db669c56a08601cb2cc4ce80887b58cce7e80984..cadfc4ef65a293062ec0938ba7acdb033f1ece25 100644
--- a/src/media/audio/audio_input.cpp
+++ b/src/media/audio/audio_input.cpp
@@ -47,11 +47,11 @@ AudioInput::AudioInput(const std::string& id)
                                      [this](std::shared_ptr<AudioFrame>&& f) {
                                          frameResized(std::move(f));
                                      }))
-    , fileId_(id + "_file")
     , deviceGuard_()
     , loop_([] { return true; }, [this] { process(); }, [] {})
 {
-    JAMI_DBG() << "Creating audio input with id: " << id;
+    JAMI_DEBUG("Creating audio input with id: {}", id_);
+    ringBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(id_);
 }
 
 AudioInput::AudioInput(const std::string& id, const std::string& resource)
@@ -65,7 +65,10 @@ AudioInput::~AudioInput()
     if (playingFile_) {
         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     }
+    ringBuf_.reset();
     loop_.join();
+
+    Manager::instance().getRingBufferPool().flush(id_);
 }
 
 void
@@ -106,11 +109,11 @@ AudioInput::readFromDevice()
     {
         std::lock_guard<std::mutex> lk(resourceMutex_);
         if (decodingFile_)
-            while (fileBuf_->isEmpty())
+            while (ringBuf_ && ringBuf_->isEmpty())
                 readFromFile();
         if (playingFile_) {
-            readFromQueue();
-            return;
+            while (ringBuf_ && ringBuf_->isEmpty())
+                readFromQueue();
         }
     }
 
@@ -197,24 +200,28 @@ AudioInput::configureFilePlayback(const std::string& path,
                                   int index)
 {
     decoder_.reset();
-    Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
-    fileBuf_.reset();
     devOpts_ = {};
     devOpts_.input = path;
     devOpts_.name = path;
     auto decoder
         = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) {
-              if (muteState_) {
+              if (muteState_)
                   libav_utils::fillWithSilence(frame->pointer());
-                  return;
-              }
-              fileBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
+              if (ringBuf_)
+                  ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
           });
     decoder->emulateRate();
+    decoder->setInterruptCallback(
+        [](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); }, this);
+
+    // have file audio mixed into the local buffer so it gets played
+    Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
+    deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
 
-    fileBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(id_);
     playingFile_ = true;
     decoder_ = std::move(decoder);
+    resource_ = path;
+    loop_.start();
 }
 
 void
@@ -255,11 +262,9 @@ AudioInput::initFile(const std::string& path)
         JAMI_WARN() << "Cannot decode audio from file, switching back to default device";
         return initDevice("");
     }
-    fileBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(fileId_);
-    // have file audio mixed into the call buffer so it gets sent to the peer
-    Manager::instance().getRingBufferPool().bindHalfDuplexOut(id_, fileId_);
+
     // have file audio mixed into the local buffer so it gets played
-    Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, fileId_);
+    Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     decodingFile_ = true;
     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     return true;
@@ -271,40 +276,38 @@ AudioInput::switchInput(const std::string& resource)
     // Always switch inputs, even if it's the same resource, so audio will be in sync with video
     std::unique_lock<std::mutex> lk(resourceMutex_);
 
-    JAMI_DBG() << "Switching audio source to match '" << resource << "'";
+    JAMI_DEBUG("Switching audio source from {} to {}", resource_, resource);
 
     auto oldGuard = std::move(deviceGuard_);
 
     decoder_.reset();
     if (decodingFile_) {
         decodingFile_ = false;
-        Manager::instance().getRingBufferPool().unBindHalfDuplexOut(id_, fileId_);
         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID,
-                                                                    fileId_);
+                                                                    id_);
     }
-    fileBuf_.reset();
 
     playingDevice_ = false;
-    currentResource_ = resource;
+    resource_ = resource;
     devOptsFound_ = false;
 
     std::promise<DeviceParams> p;
     foundDevOpts_.swap(p);
 
-    if (resource.empty()) {
+    if (resource_.empty()) {
         if (initDevice(""))
             foundDevOpts(devOpts_);
     } else {
         static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
-        const auto pos = resource.find(sep);
+        const auto pos = resource_.find(sep);
         if (pos == std::string::npos)
             return {};
 
-        const auto prefix = resource.substr(0, pos);
-        if ((pos + sep.size()) >= resource.size())
+        const auto prefix = resource_.substr(0, pos);
+        if ((pos + sep.size()) >= resource_.size())
             return {};
 
-        const auto suffix = resource.substr(pos + sep.size());
+        const auto suffix = resource_.substr(pos + sep.size());
         bool ready = false;
         if (prefix == libjami::Media::VideoProtocolPrefix::FILE)
             ready = initFile(suffix);
@@ -358,7 +361,8 @@ AudioInput::createDecoder()
     }
 
     auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) {
-        fileBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
+        if (ringBuf_)
+            ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
     });
 
     // NOTE don't emulate rate, file is read as frames are needed
diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h
index d17d3bcbddc8a3a686c3ca39dae65b4f3b0b7114..22a58615f30bac0e240e9484d12fa110c3328471 100644
--- a/src/media/audio/audio_input.h
+++ b/src/media/audio/audio_input.h
@@ -73,6 +73,8 @@ public:
 
     void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb);
 
+    std::string getId() const { return id_; };
+
 private:
     void readFromDevice();
     void readFromFile();
@@ -83,6 +85,7 @@ private:
     void frameResized(std::shared_ptr<AudioFrame>&& ptr);
 
     std::string id_;
+    std::shared_ptr<RingBuffer> ringBuf_;
     bool muteState_ {false};
     uint64_t sent_samples = 0;
     mutable std::mutex fmtMutex_ {};
@@ -94,10 +97,7 @@ private:
     std::unique_ptr<AudioFrameResizer> resizer_;
     std::unique_ptr<MediaDecoder> decoder_;
 
-    std::string fileId_;
-    std::shared_ptr<RingBuffer> fileBuf_;
-
-    std::string currentResource_;
+    std::string resource_;
     std::mutex resourceMutex_ {};
     DeviceParams devOpts_;
     std::promise<DeviceParams> foundDevOpts_;
diff --git a/src/media/audio/audio_receive_thread.cpp b/src/media/audio/audio_receive_thread.cpp
index b5524c2e52efaa19e87d7bca51691ac549bff8c7..2703555bd326a708793d9cab7731c989e6a5dec8 100644
--- a/src/media/audio/audio_receive_thread.cpp
+++ b/src/media/audio/audio_receive_thread.cpp
@@ -33,11 +33,11 @@
 
 namespace jami {
 
-AudioReceiveThread::AudioReceiveThread(const std::string& id,
+AudioReceiveThread::AudioReceiveThread(const std::string& streamId,
                                        const AudioFormat& format,
                                        const std::string& sdp,
                                        const uint16_t mtu)
-    : id_(id)
+    : streamId_(streamId)
     , format_(format)
     , stream_(sdp)
     , sdpContext_(new MediaIOHandle(sdp.size(), false, &readFunction, 0, 0, this))
@@ -90,7 +90,8 @@ AudioReceiveThread::setup()
         return false;
     }
 
-    ringbuffer_ = Manager::instance().getRingBufferPool().getRingBuffer(id_);
+    ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(streamId_);
+    Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, streamId_);
 
     if (onSuccessfulSetup_)
         onSuccessfulSetup_(MEDIA_AUDIO, 1);
diff --git a/src/media/audio/audio_receive_thread.h b/src/media/audio/audio_receive_thread.h
index ce5688ba0879754dea2b979f3671892eafa84d57..24253ccb6f5653bb6f7e4b39ed896b8429fa1ab4 100644
--- a/src/media/audio/audio_receive_thread.h
+++ b/src/media/audio/audio_receive_thread.h
@@ -42,7 +42,7 @@ class RingBuffer;
 class AudioReceiveThread : public Observable<std::shared_ptr<MediaFrame>>
 {
 public:
-    AudioReceiveThread(const std::string& id,
+    AudioReceiveThread(const std::string& streamId,
                        const AudioFormat& format,
                        const std::string& sdp,
                        const uint16_t mtu);
@@ -72,7 +72,7 @@ private:
     /*-----------------------------------------------------------------*/
     /* These variables should be used in thread (i.e. process()) only! */
     /*-----------------------------------------------------------------*/
-    const std::string id_;
+    const std::string streamId_;
     const AudioFormat& format_;
 
     DeviceParams args_;
diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp
index 2cc92af6719856ab214e1aff568313eca1e317c8..e646527a55eb8366595237bf2ef252add5e14d37 100644
--- a/src/media/audio/audio_rtp_session.cpp
+++ b/src/media/audio/audio_rtp_session.cpp
@@ -35,6 +35,7 @@
 #include "media_decoder.h"
 #include "media_io_handle.h"
 #include "media_device.h"
+#include "media_const.h"
 
 #include "audio/audio_input.h"
 #include "audio/ringbufferpool.h"
@@ -56,29 +57,29 @@ AudioRtpSession::AudioRtpSession(const std::string& callId,
 
 {
     recorder_ = rec;
-    JAMI_DBG("Created Audio RTP session: %p - call Id %s", this, callId_.c_str());
+    JAMI_DEBUG("Created Audio RTP session: {} - stream id {}", fmt::ptr(this), streamId_);
 
     // don't move this into the initializer list or Cthulus will emerge
-    ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(callId_);
+    ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(streamId_);
 }
 
 AudioRtpSession::~AudioRtpSession()
 {
     deinitRecorder();
     stop();
-    JAMI_DBG("Destroyed Audio RTP session: %p - call Id %s", this, callId_.c_str());
+    JAMI_DEBUG("Destroyed Audio RTP session: {} - stream id {}", fmt::ptr(this), streamId_);
 }
 
 void
 AudioRtpSession::startSender()
 {
     std::lock_guard<std::recursive_mutex> lock(mutex_);
-    JAMI_DBG("Start audio RTP sender: input [%s] - muted [%s]",
-             input_.c_str(),
+    JAMI_DEBUG("Start audio RTP sender: input [{}] - muted [{}]",
+             input_,
              muteState_ ? "YES" : "NO");
 
     if (not send_.enabled or send_.onHold) {
-        JAMI_WARN("Audio sending disabled");
+        JAMI_WARNING("Audio sending disabled");
         if (sender_) {
             if (socketPair_)
                 socketPair_->interrupt();
@@ -90,12 +91,24 @@ AudioRtpSession::startSender()
     }
 
     if (sender_)
-        JAMI_WARN("Restarting audio sender");
+        JAMI_WARNING("Restarting audio sender");
     if (audioInput_)
         audioInput_->detach(sender_.get());
 
+    bool fileAudio = !input_.empty() && input_.find("file://") != std::string::npos;
+    auto audioInputId = streamId_;
+    if (fileAudio) {
+        auto suffix = input_;
+        static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
+        const auto pos = input_.find(sep);
+        if (pos != std::string::npos) {
+            suffix = input_.substr(pos + sep.size());
+        }
+        audioInputId = suffix;
+    }
+
     // sender sets up input correctly, we just keep a reference in case startSender is called
-    audioInput_ = jami::getAudioInput(callId_);
+    audioInput_ = jami::getAudioInput(audioInputId);
     audioInput_->setRecorderCallback(
             [w=weak_from_this()](const MediaStream& ms) {
                 Manager::instance().ioContext()->post([w=std::move(w), ms]() {
@@ -105,19 +118,23 @@ AudioRtpSession::startSender()
             });
     audioInput_->setMuted(muteState_);
     audioInput_->setSuccessfulSetupCb(onSuccessfulSetup_);
-    auto newParams = audioInput_->switchInput(input_);
-    try {
-        if (newParams.valid()
-            && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) {
-            localAudioParams_ = newParams.get();
-        } else {
-            JAMI_ERR() << "No valid new audio parameters";
+    if (!fileAudio) {
+        auto newParams = audioInput_->switchInput(input_);
+        try {
+            if (newParams.valid()
+                && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) {
+                localAudioParams_ = newParams.get();
+            } else {
+                JAMI_ERROR("No valid new audio parameters");
+                return;
+            }
+        } catch (const std::exception& e) {
+            JAMI_ERROR("Exception while retrieving audio parameters: {}", e.what());
             return;
         }
-    } catch (const std::exception& e) {
-        JAMI_ERR() << "Exception while retrieving audio parameters: " << e.what();
-        return;
     }
+    if (streamId_ != audioInput_->getId())
+        Manager::instance().getRingBufferPool().bindHalfDuplexOut(streamId_, audioInput_->getId());
 
     send_.fecEnabled = true;
 
@@ -130,19 +147,17 @@ AudioRtpSession::startSender()
         socketPair_->stopSendOp(false);
         sender_.reset(new AudioSender(getRemoteRtpUri(), send_, *socketPair_, initSeqVal_, mtu_));
     } catch (const MediaEncoderException& e) {
-        JAMI_ERR("%s", e.what());
+        JAMI_ERROR("{}", e.what());
         send_.enabled = false;
     }
 
-    if (voiceCallback_) {
+    if (voiceCallback_)
         sender_->setVoiceCallback(voiceCallback_);
-    }
 
     // NOTE do after sender/encoder are ready
     auto codec = std::static_pointer_cast<SystemAudioCodecInfo>(send_.codec);
     audioInput_->setFormat(codec->audioformat);
-    if (audioInput_)
-        audioInput_->attach(sender_.get());
+    audioInput_->attach(sender_.get());
 
     if (not rtcpCheckerThread_.isRunning())
         rtcpCheckerThread_.start();
@@ -167,16 +182,16 @@ AudioRtpSession::startReceiver()
         socketPair_->setReadBlockingMode(true);
 
     if (not receive_.enabled or receive_.onHold) {
-        JAMI_WARN("Audio receiving disabled");
+        JAMI_WARNING("Audio receiving disabled");
         receiveThread_.reset();
         return;
     }
 
     if (receiveThread_)
-        JAMI_WARN("Restarting audio receiver");
+        JAMI_WARNING("Restarting audio receiver");
 
     auto accountAudioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(receive_.codec);
-    receiveThread_.reset(new AudioReceiveThread(callId_,
+    receiveThread_.reset(new AudioReceiveThread(streamId_,
                                                 accountAudioCodec->audioformat,
                                                 receive_.receiving_sdp,
                                                 mtu_));
@@ -225,7 +240,7 @@ AudioRtpSession::start(std::unique_ptr<dhtnet::IceSocket> rtp_sock, std::unique_
                                     send_.crypto.getSrtpKeyInfo().c_str());
         }
     } catch (const std::runtime_error& e) {
-        JAMI_ERR("Socket creation failed: %s", e.what());
+        JAMI_ERROR("Socket creation failed: {}", e.what());
         return;
     }
 
@@ -238,7 +253,7 @@ AudioRtpSession::stop()
 {
     std::lock_guard<std::recursive_mutex> lock(mutex_);
 
-    JAMI_DBG("[%p] Stopping receiver", this);
+    JAMI_DEBUG("[{}] Stopping receiver", fmt::ptr(this));
 
     if (not receiveThread_)
         return;
@@ -276,6 +291,7 @@ AudioRtpSession::setMuted(bool muted, Direction dir)
             } else {
                 if (shared->receiveThread_) {
                     auto ms = shared->receiveThread_->getInfo();
+                    ms.name = shared->streamId_ + ":remote";
                     if (muted) {
                         if (auto ob = shared->recorder_->getStream(ms.name)) {
                             shared->receiveThread_->detach(ob);
@@ -354,9 +370,9 @@ AudioRtpSession::setNewPacketLoss(unsigned int newPL)
             auto ret = sender_->setPacketLoss(newPL);
             packetLoss_ = newPL;
             if (ret == -1)
-                JAMI_ERR("Fail to access the encoder");
+                JAMI_ERROR("Fail to access the encoder");
         } else {
-            JAMI_ERR("Fail to access the sender");
+            JAMI_ERROR("Fail to access the sender");
         }
     }
 }
@@ -387,7 +403,9 @@ AudioRtpSession::attachRemoteRecorder(const MediaStream& ms)
     std::lock_guard<std::recursive_mutex> lock(mutex_);
     if (!recorder_ || !receiveThread_)
         return;
-    if (auto ob = recorder_->addStream(ms)) {
+    MediaStream remoteMS = ms;
+    remoteMS.name = streamId_ + ":remote";
+    if (auto ob = recorder_->addStream(remoteMS)) {
         receiveThread_->attach(ob);
     }
 }
@@ -398,7 +416,9 @@ AudioRtpSession::attachLocalRecorder(const MediaStream& ms)
     std::lock_guard<std::recursive_mutex> lock(mutex_);
     if (!recorder_ || !audioInput_)
         return;
-    if (auto ob = recorder_->addStream(ms)) {
+    MediaStream localMS = ms;
+    localMS.name = streamId_ + ":local";
+    if (auto ob = recorder_->addStream(localMS)) {
         audioInput_->attach(ob);
     }
 }
@@ -433,6 +453,7 @@ AudioRtpSession::deinitRecorder()
         return;
     if (receiveThread_) {
         auto ms = receiveThread_->getInfo();
+        ms.name = streamId_ + ":remote";
         if (auto ob = recorder_->getStream(ms.name)) {
             receiveThread_->detach(ob);
             recorder_->removeStream(ms);
@@ -440,6 +461,7 @@ AudioRtpSession::deinitRecorder()
     }
     if (audioInput_) {
         auto ms = audioInput_->getInfo();
+        ms.name = streamId_ + ":local";
         if (auto ob = recorder_->getStream(ms.name)) {
             audioInput_->detach(ob);
             recorder_->removeStream(ms);
diff --git a/src/media/audio/ringbuffer.cpp b/src/media/audio/ringbuffer.cpp
index 6e683f5f67441c8c8b2302db22f0d9baef3a1676..7393bc44c52f16c2c123d6dcf57c1aa3ffba409d 100644
--- a/src/media/audio/ringbuffer.cpp
+++ b/src/media/audio/ringbuffer.cpp
@@ -52,18 +52,18 @@ RingBuffer::RingBuffer(const std::string& rbuf_id, size_t /*size*/, AudioFormat
         putToBuffer(std::move(frame));
     })
 {
-    JAMI_INFO("Create new RingBuffer %s", id.c_str());
+    JAMI_LOG("Create new RingBuffer {}", id);
 }
 
 RingBuffer::~RingBuffer()
 {
-    JAMI_INFO("Destroy RingBuffer %s", id.c_str());
+    JAMI_LOG("Destroy RingBuffer {}", id);
 }
 
 void
-RingBuffer::flush(const std::string& call_id)
+RingBuffer::flush(const std::string& ringbufferId)
 {
-    storeReadOffset(endPos_, call_id);
+    storeReadOffset(endPos_, ringbufferId);
 }
 
 void
@@ -84,12 +84,12 @@ RingBuffer::putLength() const
 }
 
 size_t
-RingBuffer::getLength(const std::string& call_id) const
+RingBuffer::getLength(const std::string& ringbufferId) const
 {
     const size_t buffer_size = buffer_.size();
     if (buffer_size == 0)
         return 0;
-    return (endPos_ + buffer_size - getReadOffset(call_id)) % buffer_size;
+    return (endPos_ + buffer_size - getReadOffset(ringbufferId)) % buffer_size;
 }
 
 void
@@ -99,9 +99,9 @@ RingBuffer::debug()
 }
 
 size_t
-RingBuffer::getReadOffset(const std::string& call_id) const
+RingBuffer::getReadOffset(const std::string& ringbufferId) const
 {
-    auto iter = readoffsets_.find(call_id);
+    auto iter = readoffsets_.find(ringbufferId);
     return (iter != readoffsets_.end()) ? iter->second.offset : 0;
 }
 
@@ -117,37 +117,37 @@ RingBuffer::getSmallestReadOffset() const
 }
 
 void
-RingBuffer::storeReadOffset(size_t offset, const std::string& call_id)
+RingBuffer::storeReadOffset(size_t offset, const std::string& ringbufferId)
 {
-    ReadOffsetMap::iterator iter = readoffsets_.find(call_id);
+    ReadOffsetMap::iterator iter = readoffsets_.find(ringbufferId);
 
     if (iter != readoffsets_.end())
         iter->second.offset = offset;
     else
-        JAMI_ERR("RingBuffer::storeReadOffset() failed: unknown call '%s'", call_id.c_str());
+        JAMI_ERROR("RingBuffer::storeReadOffset() failed: unknown ringbuffer '{}'", ringbufferId);
 }
 
 void
-RingBuffer::createReadOffset(const std::string& call_id)
+RingBuffer::createReadOffset(const std::string& ringbufferId)
 {
     std::lock_guard<std::mutex> l(lock_);
-    if (!hasThisReadOffset(call_id))
-        readoffsets_.emplace(call_id, ReadOffset {endPos_, {}});
+    if (!hasThisReadOffset(ringbufferId))
+        readoffsets_.emplace(ringbufferId, ReadOffset {endPos_, {}});
 }
 
 void
-RingBuffer::removeReadOffset(const std::string& call_id)
+RingBuffer::removeReadOffset(const std::string& ringbufferId)
 {
     std::lock_guard<std::mutex> l(lock_);
-    auto iter = readoffsets_.find(call_id);
+    auto iter = readoffsets_.find(ringbufferId);
     if (iter != readoffsets_.end())
         readoffsets_.erase(iter);
 }
 
 bool
-RingBuffer::hasThisReadOffset(const std::string& call_id) const
+RingBuffer::hasThisReadOffset(const std::string& ringbufferId) const
 {
-    return readoffsets_.find(call_id) != readoffsets_.end();
+    return readoffsets_.find(ringbufferId) != readoffsets_.end();
 }
 
 bool
@@ -211,18 +211,18 @@ RingBuffer::putToBuffer(std::shared_ptr<AudioFrame>&& data)
 //
 
 size_t
-RingBuffer::availableForGet(const std::string& call_id) const
+RingBuffer::availableForGet(const std::string& ringbufferId) const
 {
     // Used space
-    return getLength(call_id);
+    return getLength(ringbufferId);
 }
 
 std::shared_ptr<AudioFrame>
-RingBuffer::get(const std::string& call_id)
+RingBuffer::get(const std::string& ringbufferId)
 {
     std::lock_guard<std::mutex> l(lock_);
 
-    auto offset = readoffsets_.find(call_id);
+    auto offset = readoffsets_.find(ringbufferId);
     if (offset == readoffsets_.end())
         return {};
 
@@ -241,20 +241,20 @@ RingBuffer::get(const std::string& call_id)
 }
 
 size_t
-RingBuffer::waitForDataAvailable(const std::string& call_id, const time_point& deadline) const
+RingBuffer::waitForDataAvailable(const std::string& ringbufferId, const time_point& deadline) const
 {
     std::unique_lock<std::mutex> l(lock_);
 
     if (buffer_.empty())
         return 0;
-    if (readoffsets_.find(call_id) == readoffsets_.end())
+    if (readoffsets_.find(ringbufferId) == readoffsets_.end())
         return 0;
 
     size_t getl = 0;
     auto check = [=, &getl] {
         // Re-find read_ptr: it may be destroyed during the wait
         const size_t buffer_size = buffer_.size();
-        const auto read_ptr = readoffsets_.find(call_id);
+        const auto read_ptr = readoffsets_.find(ringbufferId);
         if (buffer_size == 0 || read_ptr == readoffsets_.end())
             return true;
         getl = (endPos_ + buffer_size - read_ptr->second.offset) % buffer_size;
@@ -272,7 +272,7 @@ RingBuffer::waitForDataAvailable(const std::string& call_id, const time_point& d
 }
 
 size_t
-RingBuffer::discard(size_t toDiscard, const std::string& call_id)
+RingBuffer::discard(size_t toDiscard, const std::string& ringbufferId)
 {
     std::lock_guard<std::mutex> l(lock_);
 
@@ -280,7 +280,7 @@ RingBuffer::discard(size_t toDiscard, const std::string& call_id)
     if (buffer_size == 0)
         return 0;
 
-    auto offset = readoffsets_.find(call_id);
+    auto offset = readoffsets_.find(ringbufferId);
     if (offset == readoffsets_.end())
         return 0;
 
diff --git a/src/media/audio/ringbuffer.h b/src/media/audio/ringbuffer.h
index 582e4c8bfcc561289b26dc8c935851c839382543..dd965a724cd4ab0d12e98bdf227a3ef39d4278c4 100644
--- a/src/media/audio/ringbuffer.h
+++ b/src/media/audio/ringbuffer.h
@@ -64,7 +64,7 @@ public:
     /**
      * Reset the counters to 0 for this read offset
      */
-    void flush(const std::string& call_id);
+    void flush(const std::string& ringbufferId);
 
     void flushAll();
 
@@ -80,21 +80,20 @@ public:
     /**
      * Add a new readoffset for this ringbuffer
      */
-    void createReadOffset(const std::string& call_id);
+    void createReadOffset(const std::string& ringbufferId);
 
-    void createReadOffset(const std::string& call_id, FrameCallback cb);
+    void createReadOffset(const std::string& ringbufferId, FrameCallback cb);
 
     /**
      * Remove a readoffset for this ringbuffer
      */
-    void removeReadOffset(const std::string& call_id);
+    void removeReadOffset(const std::string& ringbufferId);
 
     size_t readOffsetCount() const { return readoffsets_.size(); }
 
     /**
      * Write data in the ring buffer
-     * @param buffer Data to copied
-     * @param toCopy Number of bytes to copy
+     * @param AudioFrame
      */
     void put(std::shared_ptr<AudioFrame>&& data);
 
@@ -102,22 +101,21 @@ public:
      * To get how much samples are available in the buffer to read in
      * @return int The available (multichannel) samples number
      */
-    size_t availableForGet(const std::string& call_id) const;
+    size_t availableForGet(const std::string& ringbufferId) const;
 
     /**
      * Get data in the ring buffer
-     * @param buffer Data to copied
-     * @param toCopy Number of bytes to copy
-     * @return size_t Number of bytes copied
+     * @param ringbufferId
+     * @return AudioFRame
      */
-    std::shared_ptr<AudioFrame> get(const std::string& call_id);
+    std::shared_ptr<AudioFrame> get(const std::string& ringbufferId);
 
     /**
      * Discard data from the buffer
      * @param toDiscard Number of samples to discard
      * @return size_t Number of samples discarded
      */
-    size_t discard(size_t toDiscard, const std::string& call_id);
+    size_t discard(size_t toDiscard, const std::string& ringbufferId);
 
     /**
      * Total length of the ring buffer which is available for "putting"
@@ -125,7 +123,7 @@ public:
      */
     size_t putLength() const;
 
-    size_t getLength(const std::string& call_id) const;
+    size_t getLength(const std::string& ringbufferId) const;
 
     inline bool isFull() const { return putLength() == buffer_.size(); }
 
@@ -136,13 +134,13 @@ public:
     /**
      * Blocks until min_data_length samples of data is available, or until deadline has passed.
      *
-     * @param call_id The read offset for which data should be available.
+     * @param ringbufferId The read offset for which data should be available.
      * @param min_data_length Minimum number of samples that should be available for the call to return
-     * @param deadline The call is guaranteed to end after this time point. If no deadline is provided,
+     * @param deadline The ringbufferId is guaranteed to end after this time point. If no deadline is provided,
      * the call blocks indefinitely.
-     * @return available data for call_id after the call returned (same as calling getLength(call_id) ).
+     * @return available data for ringbufferId after the call returned (same as calling getLength(ringbufferId) ).
      */
-    size_t waitForDataAvailable(const std::string& call_id,
+    size_t waitForDataAvailable(const std::string& ringbufferId,
                                 const time_point& deadline = time_point::max()) const;
 
     /**
@@ -174,17 +172,17 @@ private:
     /**
      * Get read offset coresponding to this call
      */
-    size_t getReadOffset(const std::string& call_id) const;
+    size_t getReadOffset(const std::string& ringbufferId) const;
 
     /**
      * Move readoffset forward by offset
      */
-    void storeReadOffset(size_t offset, const std::string& call_id);
+    void storeReadOffset(size_t offset, const std::string& ringbufferId);
 
     /**
      * Test if readoffset coresponding to this call is still active
      */
-    bool hasThisReadOffset(const std::string& call_id) const;
+    bool hasThisReadOffset(const std::string& ringbufferId) const;
 
     /**
      * Discard data from all read offsets to make place for new data.
diff --git a/src/media/audio/ringbufferpool.cpp b/src/media/audio/ringbufferpool.cpp
index 674822f6880c485f3c67fcca1ea77b9c20d7228f..1cce8518057f7af38f69e997791cbd93d4aadb24 100644
--- a/src/media/audio/ringbufferpool.cpp
+++ b/src/media/audio/ringbufferpool.cpp
@@ -47,7 +47,7 @@ RingBufferPool::~RingBufferPool()
     for (const auto& item : ringBufferMap_) {
         const auto& weak = item.second;
         if (not weak.expired())
-            JAMI_WARN("Leaking RingBuffer '%s'", item.first.c_str());
+            JAMI_WARNING("Leaking RingBuffer '{}'", item.first);
     }
 }
 
@@ -110,7 +110,7 @@ RingBufferPool::createRingBuffer(const std::string& id)
 
     auto rbuf = getRingBuffer(id);
     if (rbuf) {
-        JAMI_DBG("Ringbuffer already exists for id '%s'", id.c_str());
+        JAMI_DEBUG("Ringbuffer already exists for id '{}'", id);
         return rbuf;
     }
 
@@ -120,183 +120,183 @@ RingBufferPool::createRingBuffer(const std::string& id)
 }
 
 const RingBufferPool::ReadBindings*
-RingBufferPool::getReadBindings(const std::string& call_id) const
+RingBufferPool::getReadBindings(const std::string& ringbufferId) const
 {
-    const auto& iter = readBindingsMap_.find(call_id);
+    const auto& iter = readBindingsMap_.find(ringbufferId);
     return iter != readBindingsMap_.cend() ? &iter->second : nullptr;
 }
 
 RingBufferPool::ReadBindings*
-RingBufferPool::getReadBindings(const std::string& call_id)
+RingBufferPool::getReadBindings(const std::string& ringbufferId)
 {
-    const auto& iter = readBindingsMap_.find(call_id);
+    const auto& iter = readBindingsMap_.find(ringbufferId);
     return iter != readBindingsMap_.cend() ? &iter->second : nullptr;
 }
 
 void
-RingBufferPool::removeReadBindings(const std::string& call_id)
+RingBufferPool::removeReadBindings(const std::string& ringbufferId)
 {
-    if (not readBindingsMap_.erase(call_id))
-        JAMI_ERR("CallID set %s does not exist!", call_id.c_str());
+    if (not readBindingsMap_.erase(ringbufferId))
+        JAMI_ERROR("Ringbuffer {} does not exist!", ringbufferId);
 }
 
 /**
- * Make given call ID a reader of given ring buffer
+ * Make given ringbuffer a reader of given ring buffer
  */
 void
 RingBufferPool::addReaderToRingBuffer(const std::shared_ptr<RingBuffer>& rbuf,
-                                      const std::string& call_id)
+                                      const std::string& ringbufferId)
 {
-    if (call_id != DEFAULT_ID and rbuf->getId() == call_id)
-        JAMI_WARN("RingBuffer has a readoffset on itself");
+    if (ringbufferId != DEFAULT_ID and rbuf->getId() == ringbufferId)
+        JAMI_WARNING("RingBuffer has a readoffset on itself");
 
-    rbuf->createReadOffset(call_id);
-    readBindingsMap_[call_id].insert(rbuf); // bindings list created if not existing
-    JAMI_DBG("Bind rbuf '%s' to callid '%s'", rbuf->getId().c_str(), call_id.c_str());
+    rbuf->createReadOffset(ringbufferId);
+    readBindingsMap_[ringbufferId].insert(rbuf); // bindings list created if not existing
+    JAMI_DEBUG("Bind rbuf '{}' to ringbuffer '{}'", rbuf->getId(), ringbufferId);
 }
 
 void
 RingBufferPool::removeReaderFromRingBuffer(const std::shared_ptr<RingBuffer>& rbuf,
-                                           const std::string& call_id)
+                                           const std::string& ringbufferId)
 {
-    if (auto bindings = getReadBindings(call_id)) {
+    if (auto bindings = getReadBindings(ringbufferId)) {
         bindings->erase(rbuf);
         if (bindings->empty())
-            removeReadBindings(call_id);
+            removeReadBindings(ringbufferId);
     }
 
-    rbuf->removeReadOffset(call_id);
+    rbuf->removeReadOffset(ringbufferId);
 }
 
 void
-RingBufferPool::bindCallID(const std::string& call_id1, const std::string& call_id2)
+RingBufferPool::bindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2)
 {
-    JAMI_INFO("Bind call %s to call %s", call_id1.c_str(), call_id2.c_str());
+    JAMI_LOG("Bind ringbuffer {} to ringbuffer {}", ringbufferId1, ringbufferId2);
 
-    const auto& rb_call1 = getRingBuffer(call_id1);
-    if (not rb_call1) {
-        JAMI_ERR("No ringbuffer associated with call '%s'", call_id1.c_str());
+    const auto& rb1 = getRingBuffer(ringbufferId1);
+    if (not rb1) {
+        JAMI_ERROR("No ringbuffer associated with id '{}'", ringbufferId1);
         return;
     }
 
-    const auto& rb_call2 = getRingBuffer(call_id2);
-    if (not rb_call2) {
-        JAMI_ERR("No ringbuffer associated to call '%s'", call_id2.c_str());
+    const auto& rb2 = getRingBuffer(ringbufferId2);
+    if (not rb2) {
+        JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId2);
         return;
     }
 
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    addReaderToRingBuffer(rb_call1, call_id2);
-    addReaderToRingBuffer(rb_call2, call_id1);
+    addReaderToRingBuffer(rb1, ringbufferId2);
+    addReaderToRingBuffer(rb2, ringbufferId1);
 }
 
 void
-RingBufferPool::bindHalfDuplexOut(const std::string& process_id, const std::string& call_id)
+RingBufferPool::bindHalfDuplexOut(const std::string& processId, const std::string& ringbufferId)
 {
-    /* This method is used only for active calls, if this call does not exist,
+    /* This method is used only for active ringbuffers, if this ringbuffer does not exist,
      * do nothing */
-    if (const auto& rb = getRingBuffer(call_id)) {
+    if (const auto& rb = getRingBuffer(ringbufferId)) {
         std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-        addReaderToRingBuffer(rb, process_id);
+        addReaderToRingBuffer(rb, processId);
     }
 }
 
 void
-RingBufferPool::unBindCallID(const std::string& call_id1, const std::string& call_id2)
+RingBufferPool::unbindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2)
 {
-    JAMI_INFO("Unbind calls %s and %s", call_id1.c_str(), call_id2.c_str());
+    JAMI_LOG("Unbind ringbuffers {} and {}", ringbufferId1, ringbufferId2);
 
-    const auto& rb_call1 = getRingBuffer(call_id1);
-    if (not rb_call1) {
-        JAMI_ERR("No ringbuffer associated to call '%s'", call_id1.c_str());
+    const auto& rb1 = getRingBuffer(ringbufferId1);
+    if (not rb1) {
+        JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId1);
         return;
     }
 
-    const auto& rb_call2 = getRingBuffer(call_id2);
-    if (not rb_call2) {
-        JAMI_ERR("No ringbuffer associated to call '%s'", call_id2.c_str());
+    const auto& rb2 = getRingBuffer(ringbufferId2);
+    if (not rb2) {
+        JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId2);
         return;
     }
 
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    removeReaderFromRingBuffer(rb_call1, call_id2);
-    removeReaderFromRingBuffer(rb_call2, call_id1);
+    removeReaderFromRingBuffer(rb1, ringbufferId2);
+    removeReaderFromRingBuffer(rb2, ringbufferId1);
 }
 
 void
-RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, const std::string& call_id)
+RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, const std::string& ringbufferId)
 {
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    if (const auto& rb = getRingBuffer(call_id))
+    if (const auto& rb = getRingBuffer(ringbufferId))
         removeReaderFromRingBuffer(rb, process_id);
 }
 
 void
-RingBufferPool::unBindAllHalfDuplexOut(const std::string& call_id)
+RingBufferPool::unBindAllHalfDuplexOut(const std::string& ringbufferId)
 {
-    const auto& rb_call = getRingBuffer(call_id);
-    if (not rb_call) {
-        JAMI_ERR("No ringbuffer associated to call '%s'", call_id.c_str());
+    const auto& rb = getRingBuffer(ringbufferId);
+    if (not rb) {
+        JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId);
         return;
     }
 
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    auto bindings = getReadBindings(call_id);
+    auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return;
 
     const auto bindings_copy = *bindings; // temporary copy
     for (const auto& rbuf : bindings_copy) {
-        removeReaderFromRingBuffer(rb_call, rbuf->getId());
+        removeReaderFromRingBuffer(rb, rbuf->getId());
     }
 }
 
 void
-RingBufferPool::unBindAll(const std::string& call_id)
+RingBufferPool::unBindAll(const std::string& ringbufferId)
 {
-    JAMI_INFO("Unbind call %s from all bound calls", call_id.c_str());
+    JAMI_LOG("Unbind ringbuffer {} from all bound ringbuffers", ringbufferId);
 
-    const auto& rb_call = getRingBuffer(call_id);
-    if (not rb_call) {
-        JAMI_ERR("No ringbuffer associated to call '%s'", call_id.c_str());
+    const auto& rb = getRingBuffer(ringbufferId);
+    if (not rb) {
+        JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId);
         return;
     }
 
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    auto bindings = getReadBindings(call_id);
+    auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return;
 
     const auto bindings_copy = *bindings; // temporary copy
     for (const auto& rbuf : bindings_copy) {
-        removeReaderFromRingBuffer(rbuf, call_id);
-        removeReaderFromRingBuffer(rb_call, rbuf->getId());
+        removeReaderFromRingBuffer(rbuf, ringbufferId);
+        removeReaderFromRingBuffer(rb, rbuf->getId());
     }
 }
 
 std::shared_ptr<AudioFrame>
-RingBufferPool::getData(const std::string& call_id)
+RingBufferPool::getData(const std::string& ringbufferId)
 {
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    const auto bindings = getReadBindings(call_id);
+    const auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return {};
 
     // No mixing
     if (bindings->size() == 1)
-        return (*bindings->cbegin())->get(call_id);
+        return (*bindings->cbegin())->get(ringbufferId);
 
     auto mixBuffer = std::make_shared<AudioFrame>(internalAudioFormat_);
     auto mixed = false;
     for (const auto& rbuf : *bindings) {
-        if (auto b = rbuf->get(call_id)) {
+        if (auto b = rbuf->get(ringbufferId)) {
             mixed = true;
             mixBuffer->mix(*b);
 
@@ -309,7 +309,7 @@ RingBufferPool::getData(const std::string& call_id)
 }
 
 bool
-RingBufferPool::waitForDataAvailable(const std::string& call_id,
+RingBufferPool::waitForDataAvailable(const std::string& ringbufferId,
                                      const std::chrono::microseconds& max_wait) const
 {
     std::unique_lock<std::recursive_mutex> lk(stateLock_);
@@ -317,14 +317,14 @@ RingBufferPool::waitForDataAvailable(const std::string& call_id,
     // convert to absolute time
     const auto deadline = std::chrono::high_resolution_clock::now() + max_wait;
 
-    auto bindings = getReadBindings(call_id);
+    auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return 0;
 
     const auto bindings_copy = *bindings; // temporary copy
     for (const auto& rbuf : bindings_copy) {
         lk.unlock();
-        if (rbuf->waitForDataAvailable(call_id, deadline) == 0)
+        if (rbuf->waitForDataAvailable(ringbufferId, deadline) == 0)
             return false;
         lk.lock();
     }
@@ -332,30 +332,30 @@ RingBufferPool::waitForDataAvailable(const std::string& call_id,
 }
 
 std::shared_ptr<AudioFrame>
-RingBufferPool::getAvailableData(const std::string& call_id)
+RingBufferPool::getAvailableData(const std::string& ringbufferId)
 {
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    auto bindings = getReadBindings(call_id);
+    auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return 0;
 
     // No mixing
     if (bindings->size() == 1) {
-        return (*bindings->cbegin())->get(call_id);
+        return (*bindings->cbegin())->get(ringbufferId);
     }
 
     size_t availableFrames = 0;
 
     for (const auto& rbuf : *bindings)
-        availableFrames = std::min(availableFrames, rbuf->availableForGet(call_id));
+        availableFrames = std::min(availableFrames, rbuf->availableForGet(ringbufferId));
 
     if (availableFrames == 0)
         return {};
 
     auto buf = std::make_shared<AudioFrame>(internalAudioFormat_);
     for (const auto& rbuf : *bindings) {
-        if (auto b = rbuf->get(call_id)) {
+        if (auto b = rbuf->get(ringbufferId)) {
             buf->mix(*b);
 
             // voice is true if any of mixed frames has voice
@@ -367,23 +367,23 @@ RingBufferPool::getAvailableData(const std::string& call_id)
 }
 
 size_t
-RingBufferPool::availableForGet(const std::string& call_id) const
+RingBufferPool::availableForGet(const std::string& ringbufferId) const
 {
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    const auto bindings = getReadBindings(call_id);
+    const auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return 0;
 
     // No mixing
     if (bindings->size() == 1) {
-        return (*bindings->begin())->availableForGet(call_id);
+        return (*bindings->begin())->availableForGet(ringbufferId);
     }
 
     size_t availableSamples = std::numeric_limits<size_t>::max();
 
     for (const auto& rbuf : *bindings) {
-        const size_t nbSamples = rbuf->availableForGet(call_id);
+        const size_t nbSamples = rbuf->availableForGet(ringbufferId);
         if (nbSamples != 0)
             availableSamples = std::min(availableSamples, nbSamples);
     }
@@ -392,31 +392,31 @@ RingBufferPool::availableForGet(const std::string& call_id) const
 }
 
 size_t
-RingBufferPool::discard(size_t toDiscard, const std::string& call_id)
+RingBufferPool::discard(size_t toDiscard, const std::string& ringbufferId)
 {
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    const auto bindings = getReadBindings(call_id);
+    const auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return 0;
 
     for (const auto& rbuf : *bindings)
-        rbuf->discard(toDiscard, call_id);
+        rbuf->discard(toDiscard, ringbufferId);
 
     return toDiscard;
 }
 
 void
-RingBufferPool::flush(const std::string& call_id)
+RingBufferPool::flush(const std::string& ringbufferId)
 {
     std::lock_guard<std::recursive_mutex> lk(stateLock_);
 
-    const auto bindings = getReadBindings(call_id);
+    const auto bindings = getReadBindings(ringbufferId);
     if (not bindings)
         return;
 
     for (const auto& rbuf : *bindings)
-        rbuf->flush(call_id);
+        rbuf->flush(ringbufferId);
 }
 
 void
diff --git a/src/media/audio/ringbufferpool.h b/src/media/audio/ringbufferpool.h
index 76c64554ff468c335a3411f9a7036c16e3d75929..9b6f6255cb93ccfd3fdf2dcba42e893d2985b253 100644
--- a/src/media/audio/ringbufferpool.h
+++ b/src/media/audio/ringbufferpool.h
@@ -52,44 +52,43 @@ public:
     void setInternalAudioFormat(AudioFormat format);
 
     /**
-     * Bind together two audio streams so that a client will be able
-     * to put and get data specifying its callid only.
+     * Bind together two audio streams
      */
-    void bindCallID(const std::string& call_id1, const std::string& call_id2);
+    void bindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2);
 
     /**
-     * Add a new call_id to unidirectional outgoing stream
-     * \param call_id New call id to be added for this stream
-     * \param process_id Process that require this stream
+     * Add a new ringbufferId to unidirectional outgoing stream
+     * \param ringbufferId New ringbufferId to be added for this stream
+     * \param processId Process that require this stream
      */
-    void bindHalfDuplexOut(const std::string& process_id, const std::string& call_id);
+    void bindHalfDuplexOut(const std::string& processId, const std::string& ringbufferId);
 
     /**
-     * Unbind two calls
+     * Unbind two ringbuffers
      */
-    void unBindCallID(const std::string& call_id1, const std::string& call_id2);
+    void unbindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2);
 
     /**
      * Unbind a unidirectional stream
      */
-    void unBindHalfDuplexOut(const std::string& process_id, const std::string& call_id);
+    void unBindHalfDuplexOut(const std::string& process_id, const std::string& ringbufferId);
 
-    void unBindAllHalfDuplexOut(const std::string& call_id);
+    void unBindAllHalfDuplexOut(const std::string& ringbufferId);
 
-    void unBindAll(const std::string& call_id);
+    void unBindAll(const std::string& ringbufferId);
 
-    bool waitForDataAvailable(const std::string& call_id,
+    bool waitForDataAvailable(const std::string& ringbufferId,
                               const std::chrono::microseconds& max_wait) const;
 
-    std::shared_ptr<AudioFrame> getData(const std::string& call_id);
+    std::shared_ptr<AudioFrame> getData(const std::string& ringbufferId);
 
-    std::shared_ptr<AudioFrame> getAvailableData(const std::string& call_id);
+    std::shared_ptr<AudioFrame> getAvailableData(const std::string& ringbufferId);
 
-    size_t availableForGet(const std::string& call_id) const;
+    size_t availableForGet(const std::string& ringbufferId) const;
 
-    size_t discard(size_t toDiscard, const std::string& call_id);
+    size_t discard(size_t toDiscard, const std::string& ringbufferId);
 
-    void flush(const std::string& call_id);
+    void flush(const std::string& ringbufferId);
 
     void flushAllBuffers();
 
@@ -123,15 +122,15 @@ private:
     using ReadBindings
         = std::set<std::shared_ptr<RingBuffer>, std::owner_less<std::shared_ptr<RingBuffer>>>;
 
-    const ReadBindings* getReadBindings(const std::string& call_id) const;
-    ReadBindings* getReadBindings(const std::string& call_id);
+    const ReadBindings* getReadBindings(const std::string& ringbufferId) const;
+    ReadBindings* getReadBindings(const std::string& ringbufferId);
 
-    void removeReadBindings(const std::string& call_id);
+    void removeReadBindings(const std::string& ringbufferId);
 
-    void addReaderToRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, const std::string& call_id);
+    void addReaderToRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, const std::string& ringbufferId);
 
     void removeReaderFromRingBuffer(const std::shared_ptr<RingBuffer>& rbuf,
-                                    const std::string& call_id);
+                                    const std::string& ringbufferId);
 
     // A cache of created RingBuffers listed by IDs.
     std::map<std::string, std::weak_ptr<RingBuffer>> ringBufferMap_ {};
diff --git a/src/media/media_player.cpp b/src/media/media_player.cpp
index fd0c971886939cf3c2d82d3bd0fec3a4b139a624..271f334a71ad9ee91d3affc0a06c93d633ebf71f 100644
--- a/src/media/media_player.cpp
+++ b/src/media/media_player.cpp
@@ -42,11 +42,6 @@ MediaPlayer::MediaPlayer(const std::string& resource)
 
     path_ = suffix;
 
-    if (access(suffix.c_str(), R_OK) != 0) {
-        JAMI_ERR() << "File '" << path_ << "' not available";
-        return;
-    }
-
     audioInput_ = jami::getAudioInput(path_);
     audioInput_->setPaused(paused_);
 #ifdef ENABLE_VIDEO
diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp
index 69a10ba967f3903ccb618a46520f4c478b4fc44a..3273211a2c089eda30f1c74e0f6c5f3913573a4a 100644
--- a/src/media/media_recorder.cpp
+++ b/src/media/media_recorder.cpp
@@ -609,60 +609,25 @@ MediaRecorder::buildVideoFilter(const std::vector<MediaStream>& peers,
 void
 MediaRecorder::setupAudioOutput()
 {
-    MediaStream encoderStream, peer, local, mixer;
-    auto it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
-        return !pair.second->info.isVideo
-               && pair.second->info.name.find("remote") != std::string::npos;
-    });
-    if (it != streams_.end())
-        peer = it->second->info;
-
-    it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
-        return !pair.second->info.isVideo
-               && pair.second->info.name.find("local") != std::string::npos;
-    });
-    if (it != streams_.end())
-        local = it->second->info;
-
-    it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
-        return !pair.second->info.isVideo
-               && pair.second->info.name.find("mixer") != std::string::npos;
-    });
-    if (it != streams_.end())
-        local = it->second->info;
+    MediaStream encoderStream;
 
     // resample to common audio format, so any player can play the file
     audioFilter_.reset(new MediaFilter);
     int ret = -1;
-    int streams = peer.isValid() + local.isValid() + mixer.isValid();
-    switch (streams) {
-    case 0: {
+
+    if (streams_.empty()) {
         JAMI_WARN() << "Trying to record a audio stream but none is valid";
         return;
     }
-    case 1: {
-        MediaStream inputStream;
-        if (peer.isValid())
-            inputStream = peer;
-        else if (local.isValid())
-            inputStream = local;
-        else if (mixer.isValid())
-            inputStream = mixer;
-        else {
-            JAMI_ERR("Trying to record a stream but none is valid");
-            break;
-        }
-        ret = audioFilter_->initialize(buildAudioFilter({}, inputStream), {inputStream});
-        break;
-    }
-    case 2: // mix both audio streams
-        ret = audioFilter_->initialize(buildAudioFilter({peer}, local), {peer, local});
-        break;
-    default:
-        JAMI_ERR() << "Recording more than 2 audio streams is not supported";
-        break;
+
+    std::vector<MediaStream> peers {};
+    for (const auto& media : streams_) {
+        if (!media.second->info.isVideo && media.second->info.isValid())
+            peers.emplace_back(media.second->info);
     }
 
+    ret = audioFilter_->initialize(buildAudioFilter(peers), peers);
+
     if (ret < 0) {
         JAMI_ERR() << "Failed to initialize audio filter";
         return;
@@ -679,9 +644,9 @@ MediaRecorder::setupAudioOutput()
     }
 
     outputAudioFilter_.reset(new MediaFilter);
-    ret = outputAudioFilter_
-            ->initialize("[input]aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo",
-                                       {secondaryFilter});
+    ret = outputAudioFilter_->initialize(
+        "[input]aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo",
+        {secondaryFilter});
 
     if (ret < 0) {
         JAMI_ERR() << "Failed to initialize output audio filter";
@@ -691,24 +656,14 @@ MediaRecorder::setupAudioOutput()
 }
 
 std::string
-MediaRecorder::buildAudioFilter(const std::vector<MediaStream>& peers,
-                                const MediaStream& local) const
+MediaRecorder::buildAudioFilter(const std::vector<MediaStream>& peers) const
 {
     std::string baseFilter = "aresample=osr=48000:ochl=stereo:osf=s16";
     std::ostringstream a;
 
-    switch (peers.size()) {
-    case 0:
-        a << "[" << local.name << "] " << baseFilter;
-        break;
-    default:
-        a << "[" << local.name << "] ";
-        for (const auto& ms : peers)
-            a << "[" << ms.name << "] ";
-        a << " amix=inputs=" << peers.size() + (local.isValid() ? 1 : 0) << ", " << baseFilter;
-        break;
-    }
-
+    for (const auto& ms : peers)
+        a << "[" << ms.name << "] ";
+    a << " amix=inputs=" << peers.size() << ", " << baseFilter;
     return a.str();
 }
 
diff --git a/src/media/media_recorder.h b/src/media/media_recorder.h
index ef1773756170a2efbf524199796fd3a907e0366a..59813787930aae0533fc98051f7361773c4dfe10 100644
--- a/src/media/media_recorder.h
+++ b/src/media/media_recorder.h
@@ -140,8 +140,7 @@ private:
                                  const MediaStream& local) const;
     void setupAudioOutput();
     std::mutex mutexStreamSetup_;
-    std::string buildAudioFilter(const std::vector<MediaStream>& peers,
-                                 const MediaStream& local) const;
+    std::string buildAudioFilter(const std::vector<MediaStream>& peers) const;
 
     std::mutex mutexFrameBuff_;
     std::mutex mutexFilterVideo_;
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 05658b9143c4f425f1abd25415cb5774e8fec923..cbe08582ff2194159794095b75a8249a1844d808 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -105,6 +105,9 @@ static const std::vector<unsigned> NEW_CONFPROTOCOL_VERSION
 static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR = "11.0.2"sv;
 static const std::vector<unsigned> REUSE_ICE_IN_REINVITE_REQUIRED_VERSION
     = split_string_to_unsigned(REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR, '.');
+static constexpr auto MULTIAUDIO_REQUIRED_VERSION_STR = "13.11.0"sv;
+static const std::vector<unsigned> MULTIAUDIO_REQUIRED_VERSION
+    = split_string_to_unsigned(MULTIAUDIO_REQUIRED_VERSION_STR, '.');
 
 SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account,
                  const std::string& callId,
@@ -1547,7 +1550,6 @@ SIPCall::setVideoOrientation(int streamIdx, int rotation)
 void
 SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from)
 {
-    std::lock_guard<std::recursive_mutex> lk {callMutex_};
     // TODO: for now we ignore the "from" (the previous implementation for sending this info was
     //      buggy and verbose), another way to send the original message sender will be implemented
     //      in the future
@@ -1712,18 +1714,16 @@ SIPCall::setPeerUaVersion(std::string_view ua)
     }
 
     if (peerUserAgent_.empty()) {
-        JAMI_DBG("[call:%s] Set peer's User-Agent to [%.*s]",
-                 getCallId().c_str(),
-                 (int) ua.size(),
-                 ua.data());
+        JAMI_DEBUG("[call:{}] Set peer's User-Agent to [{}]",
+                   getCallId(),
+                   ua);
     } else if (not peerUserAgent_.empty()) {
         // Unlikely, but should be handled since we dont have control over the peer.
         // Even if it's unexpected, we still try to parse the UA version.
-        JAMI_WARN("[call:%s] Peer's User-Agent unexpectedly changed from [%s] to [%.*s]",
-                  getCallId().c_str(),
-                  peerUserAgent_.c_str(),
-                  (int) ua.size(),
-                  ua.data());
+        JAMI_WARNING("[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]",
+                     getCallId(),
+                     peerUserAgent_,
+                     ua);
     }
 
     peerUserAgent_ = ua;
@@ -1755,13 +1755,13 @@ SIPCall::setPeerUaVersion(std::string_view ua)
     }
 
     if (version.empty()) {
-        JAMI_DBG("[call:%s] Could not parse peer's version", getCallId().c_str());
+        JAMI_DEBUG("[call:{}] Could not parse peer's version", getCallId());
         return;
     }
 
     auto peerVersion = split_string_to_unsigned(version, '.');
     if (peerVersion.size() > 4u) {
-        JAMI_WARN("[call:%s] Could not parse peer's version", getCallId().c_str());
+        JAMI_WARNING("[call:{}] Could not parse peer's version", getCallId());
         return;
     }
 
@@ -1769,35 +1769,40 @@ SIPCall::setPeerUaVersion(std::string_view ua)
     peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerVersion,
                                                                   MULTISTREAM_REQUIRED_VERSION);
     if (not peerSupportMultiStream_) {
-        JAMI_DBG(
-            "Peer's version [%.*s] does not support multi-stream. Min required version: [%.*s]",
-            (int) version.size(),
-            version.data(),
-            (int) MULTISTREAM_REQUIRED_VERSION_STR.size(),
-            MULTISTREAM_REQUIRED_VERSION_STR.data());
+        JAMI_DEBUG(
+            "Peer's version [{}] does not support multi-stream. Min required version: [{}]",
+            version,
+            MULTISTREAM_REQUIRED_VERSION_STR);
     }
+
+    // Check if peer's version is at least 13.11.0 to enable multi-audio-stream.
+    peerSupportMultiAudioStream_ = Account::meetMinimumRequiredVersion(peerVersion,
+                                                                       MULTIAUDIO_REQUIRED_VERSION);
+    if (not peerSupportMultiAudioStream_) {
+        JAMI_DEBUG(
+            "Peer's version [{}] does not support multi-audio-stream. Min required version: [{}]",
+            version,
+            MULTIAUDIO_REQUIRED_VERSION_STR);
+    }
+
     // Check if peer's version is at least 13.3.0 to enable multi-ICE.
     peerSupportMultiIce_ = Account::meetMinimumRequiredVersion(peerVersion,
                                                                MULTIICE_REQUIRED_VERSION);
     if (not peerSupportMultiIce_) {
-        JAMI_DBG("Peer's version [%.*s] does not support more than 2 ICE medias. Min required "
-                 "version: [%.*s]",
-                 (int) version.size(),
-                 version.data(),
-                 (int) MULTIICE_REQUIRED_VERSION_STR.size(),
-                 MULTIICE_REQUIRED_VERSION_STR.data());
+        JAMI_DEBUG("Peer's version [{}] does not support more than 2 ICE medias. Min required "
+                   "version: [{}]",
+                   version,
+                   MULTIICE_REQUIRED_VERSION_STR);
     }
 
     // Check if peer's version supports re-invite without ICE renegotiation.
     peerSupportReuseIceInReinv_
         = Account::meetMinimumRequiredVersion(peerVersion, REUSE_ICE_IN_REINVITE_REQUIRED_VERSION);
     if (not peerSupportReuseIceInReinv_) {
-        JAMI_DBG("Peer's version [%.*s] does not support re-invite without ICE renegotiation. Min "
-                 "required version: [%.*s]",
-                 (int) version.size(),
-                 version.data(),
-                 (int) REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR.size(),
-                 REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR.data());
+        JAMI_DEBUG("Peer's version [%.*s] does not support re-invite without ICE renegotiation. Min "
+                   "required version: [%.*s]",
+                   version,
+                   REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR);
     }
 }
 
@@ -2538,7 +2543,7 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
     // Disable video if disabled in the account.
     auto account = getSIPAccount();
     if (not account) {
-        JAMI_ERR("[call:%s] No account detected", getCallId().c_str());
+        JAMI_ERROR("[call:{}] No account detected", getCallId());
         return false;
     }
     if (not account->isVideoEnabled()) {
@@ -2546,9 +2551,9 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
             if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
                 // This an API misuse. The new medialist should not contain video
                 // if it was disabled in the account settings.
-                JAMI_ERR("[call:%s] New media has video, but it's disabled in the account. "
-                         "Ignoring the change request!",
-                         getCallId().c_str());
+                JAMI_ERROR("[call:{}] New media has video, but it's disabled in the account. "
+                           "Ignoring the change request!",
+                           getCallId());
                 return false;
             }
         }
@@ -2558,18 +2563,32 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
     // media list is different from the current media list, the media
     // change request will be ignored.
     if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
-        JAMI_WARN("[call:%s] Peer does not support multi-stream. Media change request ignored",
-                  getCallId().c_str());
+        JAMI_WARNING("[call:{}] Peer does not support multi-stream. Media change request ignored",
+                     getCallId());
         return false;
     }
 
+    // If the peer does not support multi-audio-stream and the new
+    // media list has more than one audio. Ignore the one that comes from a file.
+    if (not peerSupportMultiAudioStream_ and rtpStreams_.size() != mediaAttrList.size() and hasFileSharing) {
+        JAMI_WARNING("[call:{}] Peer does not support multi-audio-stream. New Audio will be ignored",
+                     getCallId());
+        for (auto it = mediaAttrList.begin(); it != mediaAttrList.end();) {
+            if (it->type_ == MediaType::MEDIA_AUDIO and !it->sourceUri_.empty() and mediaPlayerId_ == it->sourceUri_) {
+                it = mediaAttrList.erase(it);
+                continue;
+            }
+            ++it;
+        }
+    }
+
     // If peer doesn't support multiple ice, keep only the last audio/video
     // This keep the old behaviour (if sharing both camera + sharing a file, will keep the shared file)
     if (!peerSupportMultiIce_) {
         if (mediaList.size() > 2)
-            JAMI_WARN("[call:%s] Peer does not support more than 2 ICE medias. Media change "
-                      "request modified",
-                      getCallId().c_str());
+            JAMI_WARNING("[call:{}] Peer does not support more than 2 ICE medias. Media change "
+                         "request modified",
+                         getCallId());
         MediaAttribute audioAttr;
         MediaAttribute videoAttr;
         auto hasVideo = false, hasAudio = false;
@@ -2592,14 +2611,14 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
         if (hasVideo)
             mediaAttrList.emplace_back(videoAttr);
     }
-    JAMI_DBG("[call:%s] Requesting media change. List of new media:", getCallId().c_str());
+    JAMI_DEBUG("[call:{}] Requesting media change. List of new media:", getCallId());
 
     unsigned idx = 0;
     for (auto const& newMediaAttr : mediaAttrList) {
-        JAMI_DBG("[call:%s] Media @%u: %s",
-                 getCallId().c_str(),
-                 idx++,
-                 newMediaAttr.toString(true).c_str());
+        JAMI_DEBUG("[call:{}] Media @{:d}: {}",
+                   getCallId(),
+                   idx++,
+                   newMediaAttr.toString(true));
     }
 
     auto needReinvite = isReinviteRequired(mediaAttrList);
@@ -2609,12 +2628,12 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
         return false;
 
     if (needReinvite) {
-        JAMI_DBG("[call:%s] Media change requires a new negotiation (re-invite)",
-                 getCallId().c_str());
+        JAMI_DEBUG("[call:{}] Media change requires a new negotiation (re-invite)",
+                   getCallId());
         requestReinvite(mediaAttrList, needNewIce);
     } else {
-        JAMI_DBG("[call:%s] Media change DOES NOT require a new negotiation (re-invite)",
-                 getCallId().c_str());
+        JAMI_DEBUG("[call:{}] Media change DOES NOT require a new negotiation (re-invite)",
+                   getCallId());
         reportMediaNegotiationStatus();
     }
 
@@ -2638,6 +2657,20 @@ SIPCall::getMediaAttributeList() const
     return mediaList;
 }
 
+std::map<std::string, bool>
+SIPCall::getAudioStreams() const
+{
+    std::map<std::string, bool> audioMedias {};
+    auto medias = getMediaAttributeList();
+    for (const auto& media : medias) {
+        if (media.type_ == MEDIA_AUDIO) {
+            auto label = fmt::format("{}_{}", getCallId(), media.label_);
+            audioMedias.emplace(label, media.muted_);
+        }
+    }
+    return audioMedias;
+}
+
 void
 SIPCall::onMediaNegotiationComplete()
 {
@@ -3125,6 +3158,7 @@ SIPCall::enterConference(std::shared_ptr<Conference> conference)
         for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
             std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference);
 #endif
+    conference->bindParticipant(getCallId());
 
 #ifdef ENABLE_PLUGIN
     clearCallAVStreams();
@@ -3138,9 +3172,14 @@ SIPCall::exitConference()
     JAMI_DBG("[call:%s] Leaving conference", getCallId().c_str());
 
     auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
-    if (hasAudio && !isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)) {
+    if (hasAudio) {
         auto& rbPool = Manager::instance().getRingBufferPool();
-        rbPool.bindCallID(getCallId(), RingBufferPool::DEFAULT_ID);
+        auto medias = getAudioStreams();
+        for (const auto& media : medias) {
+            if (!media.second) {
+                rbPool.bindRingbuffers(media.first, RingBufferPool::DEFAULT_ID);
+            }
+        }
         rbPool.flush(RingBufferPool::DEFAULT_ID);
     }
 #ifdef ENABLE_VIDEO
@@ -3479,6 +3518,7 @@ SIPCall::merge(Call& call)
     localVideoPort_ = subcall.localVideoPort_;
     peerUserAgent_ = subcall.peerUserAgent_;
     peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
+    peerSupportMultiAudioStream_ = subcall.peerSupportMultiAudioStream_;
     peerSupportMultiIce_ = subcall.peerSupportMultiIce_;
     peerAllowedMethods_ = subcall.peerAllowedMethods_;
     peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_;
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 8dee71e1b64ac6b83118e9324bb0b8044194a00c..1dc3bb64b255fe0c00766385e4a10864563e36e0 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -142,6 +142,7 @@ public:
     void removeCall() override;
     void muteMedia(const std::string& mediaType, bool isMuted) override;
     std::vector<MediaAttribute> getMediaAttributeList() const override;
+    std::map<std::string, bool> getAudioStreams() const override;
     void restartMediaSender() override;
     std::shared_ptr<SystemCodecInfo> getAudioCodec() const override;
     std::shared_ptr<SystemCodecInfo> getVideoCodec() const override;
@@ -457,6 +458,8 @@ private:
     std::string peerUserAgent_ {};
     // Flag to indicate if the peer's Daemon version supports multi-stream.
     bool peerSupportMultiStream_ {false};
+    // Flag to indicate if the peer's Daemon version supports multi-stream.
+    bool peerSupportMultiAudioStream_ {false};
     // Flag to indicate if the peer's Daemon version can negotiate more than 2 ICE medias
     bool peerSupportMultiIce_ {false};
 
diff --git a/test/.gitignore b/test/.gitignore
index fdeec9068bc9a8c6cc20248b9b1a028dfb13e40f..57b9705b07a70b7da593ebafa0c864cf3c4b30f8 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -9,4 +9,5 @@ ut_*
 
 .dirstamp
 jami-sample.yml
+jami-sample.bak
 jami-sample.yml.bak
diff --git a/test/unitTest/call/recorder.cpp b/test/unitTest/call/recorder.cpp
index 7d914950436497883d7c85ad9598695a29e131db..b0f84af9338c08c0b8304d6cee9801f2e5ef9661 100644
--- a/test/unitTest/call/recorder.cpp
+++ b/test/unitTest/call/recorder.cpp
@@ -131,9 +131,10 @@ RecorderTest::setUp()
 void
 RecorderTest::tearDown()
 {
+    player.reset();
+    jami::closeMediaPlayer(playerId);
     libjami::setIsAlwaysRecording(false);
     dhtnet::fileutils::removeAll(recordDir);
-    player.reset();
 
     wait_for_removal_of({aliceId, bobId});
 }