diff --git a/src/call.cpp b/src/call.cpp
index 3ae91d5d48da69b22512a86209a18ffb606a871f..2d3d37e6ebc55f6d4158a4973687daf8615d8068 100644
--- a/src/call.cpp
+++ b/src/call.cpp
@@ -86,8 +86,6 @@ Call::Call(const std::shared_ptr<Account>& account,
     , type_(type)
     , account_(account)
 {
-    updateDetails(details);
-
     addStateListener([this](Call::CallState call_state,
                             Call::ConnectionState cnx_state,
                             UNUSED int code) {
@@ -367,14 +365,6 @@ Call::toggleRecording()
     return startRecording;
 }
 
-void
-Call::updateDetails(const std::map<std::string, std::string>& details)
-{
-    const auto& iter = details.find(libjami::Call::Details::AUDIO_ONLY);
-    if (iter != std::end(details))
-        isAudioOnly_ = iter->second == TRUE_STR;
-}
-
 std::map<std::string, std::string>
 Call::getDetails() const
 {
diff --git a/src/call.h b/src/call.h
index d2e370c563b8273684a31ae2fa507d92fc057b40..2861fef2b4a239de129865b173b7176118a33463 100644
--- a/src/call.h
+++ b/src/call.h
@@ -426,15 +426,6 @@ public:
 
     virtual void restartMediaSender() = 0;
 
-    /**
-     * Update call details after creation.
-     * @param details to update
-     *
-     * \note No warranty to update any details, only some details can be modified.
-     *       See the implementation for more ... details :-).
-     */
-    void updateDetails(const std::map<std::string, std::string>& details);
-
     // Media status methods
     virtual bool hasVideo() const = 0;
     virtual bool isCaptureDeviceMuted(const MediaType& mediaType) const = 0;
diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp
index 44ecc48184059a5b73928318e57c8886651631c1..db669c56a08601cb2cc4ce80887b58cce7e80984 100644
--- a/src/media/audio/audio_input.cpp
+++ b/src/media/audio/audio_input.cpp
@@ -137,6 +137,10 @@ AudioInput::readFromDevice()
         audioFrame = resampler_->resample(std::move(audioFrame), format_);
     resizer_->enqueue(std::move(audioFrame));
 
+    if (recorderCallback_ && settingMS_.exchange(false)) {
+        recorderCallback_(MediaStream("a:local", format_, sent_samples));
+    }
+
     jami_tracepoint(audio_input_read_from_device_end, id_.c_str());
 }
 
@@ -329,6 +333,21 @@ AudioInput::foundDevOpts(const DeviceParams& params)
     }
 }
 
+void
+AudioInput::setRecorderCallback(
+    const std::function<void(const MediaStream& ms)>&
+        cb)
+{
+    settingMS_.exchange(true);
+    recorderCallback_ = cb;
+    if (decoder_)
+        decoder_->setContextCallback([this]() {
+            if (recorderCallback_)
+                recorderCallback_(getInfo());
+        });
+}
+
+
 bool
 AudioInput::createDecoder()
 {
@@ -366,6 +385,10 @@ AudioInput::createDecoder()
 
     decoder_ = std::move(decoder);
     foundDevOpts(devOpts_);
+    decoder_->setContextCallback([this]() {
+        if (recorderCallback_)
+            recorderCallback_(getInfo());
+    });
     return true;
 }
 
diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h
index cb1ee732131f992a30c08b85e6284645b3a49cb8..f2485d8fea90274dfc0e6802c22708b4ad16bb01 100644
--- a/src/media/audio/audio_input.h
+++ b/src/media/audio/audio_input.h
@@ -71,6 +71,8 @@ public:
         onSuccessfulSetup_ = cb;
     }
 
+    void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb);
+
 private:
     void readFromDevice();
     void readFromFile();
@@ -114,6 +116,8 @@ private:
     std::chrono::time_point<std::chrono::high_resolution_clock> wakeUp_;
 
     std::function<void(MediaType, bool)> onSuccessfulSetup_;
+    std::function<void(const MediaStream& ms)> recorderCallback_;
+    std::atomic_bool settingMS_ {true};
 };
 
 } // namespace jami
diff --git a/src/media/audio/audio_receive_thread.cpp b/src/media/audio/audio_receive_thread.cpp
index 3237b10d3adb68dfa2a59ff84fa0bf6dc5aa988f..2f8010e3c3f03842d86a2438a8e43a4875b9cff3 100644
--- a/src/media/audio/audio_receive_thread.cpp
+++ b/src/media/audio/audio_receive_thread.cpp
@@ -60,6 +60,10 @@ AudioReceiveThread::setup()
         notify(frame);
         ringbuffer_->put(std::static_pointer_cast<AudioFrame>(frame));
     }));
+    audioDecoder_->setContextCallback([this]() {
+        if (recorderCallback_)
+            recorderCallback_(getInfo());
+    });
     audioDecoder_->setInterruptCallback(interruptCb, this);
 
     // custom_io so the SDP demuxer will not open any UDP connections
@@ -131,6 +135,18 @@ AudioReceiveThread::addIOContext(SocketPair& socketPair)
     demuxContext_.reset(socketPair.createIOContext(mtu_));
 }
 
+void
+AudioReceiveThread::setRecorderCallback(
+    const std::function<void(const MediaStream& ms)>& cb)
+{
+    recorderCallback_ = cb;
+    if (audioDecoder_)
+        audioDecoder_->setContextCallback([this]() {
+            if (recorderCallback_)
+                recorderCallback_(getInfo());
+        });
+}
+
 MediaStream
 AudioReceiveThread::getInfo() const
 {
diff --git a/src/media/audio/audio_receive_thread.h b/src/media/audio/audio_receive_thread.h
index 9f177aad3fbfa87b70e0cf067a666bacd6cc7bcd..1fbe53fa770913c386b8ee010e97b5e2353289b7 100644
--- a/src/media/audio/audio_receive_thread.h
+++ b/src/media/audio/audio_receive_thread.h
@@ -59,6 +59,8 @@ public:
         onSuccessfulSetup_ = cb;
     }
 
+    void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb);
+
 private:
     NON_COPYABLE(AudioReceiveThread);
 
@@ -90,6 +92,7 @@ private:
     void cleanup();
 
     std::function<void(MediaType, bool)> onSuccessfulSetup_;
+    std::function<void(const MediaStream& ms)> recorderCallback_;
 };
 
 } // namespace jami
diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp
index 975c096d32bf0c5993fc966778dd8780a67341af..769fc065dc894ecc98483246f884d882036c78ba 100644
--- a/src/media/audio/audio_rtp_session.cpp
+++ b/src/media/audio/audio_rtp_session.cpp
@@ -47,11 +47,14 @@
 
 namespace jami {
 
-AudioRtpSession::AudioRtpSession(const std::string& callId, const std::string& streamId)
+AudioRtpSession::AudioRtpSession(const std::string& callId,
+                                 const std::string& streamId,
+                                 const std::shared_ptr<MediaRecorder>& rec)
     : RtpSession(callId, streamId, MediaType::MEDIA_AUDIO)
     , rtcpCheckerThread_([] { return true; }, [this] { processRtcpChecker(); }, [] {})
 
 {
+    recorder_ = rec;
     JAMI_DBG("Created Audio RTP session: %p - call Id %s", this, callId_.c_str());
 
     // don't move this into the initializer list or Cthulus will emerge
@@ -60,6 +63,7 @@ AudioRtpSession::AudioRtpSession(const std::string& callId, const std::string& s
 
 AudioRtpSession::~AudioRtpSession()
 {
+    deinitRecorder();
     stop();
     JAMI_DBG("Destroyed Audio RTP session: %p - call Id %s", this, callId_.c_str());
 }
@@ -91,6 +95,7 @@ AudioRtpSession::startSender()
 
     // sender sets up input correctly, we just keep a reference in case startSender is called
     audioInput_ = jami::getAudioInput(callId_);
+    audioInput_->setRecorderCallback([this](const MediaStream& ms) { attachLocalRecorder(ms); });
     audioInput_->setMuted(muteState_);
     audioInput_->setSuccessfulSetupCb(onSuccessfulSetup_);
     auto newParams = audioInput_->switchInput(input_);
@@ -168,6 +173,8 @@ AudioRtpSession::startReceiver()
                                                 accountAudioCodec->audioformat,
                                                 receive_.receiving_sdp,
                                                 mtu_));
+
+    receiveThread_->setRecorderCallback([this](const MediaStream& ms) { attachRemoteRecorder(ms); });
     receiveThread_->addIOContext(*socketPair_);
     receiveThread_->setSuccessfulSetupCb(onSuccessfulSetup_);
     receiveThread_->startReceiver();
@@ -244,12 +251,28 @@ AudioRtpSession::stop()
 }
 
 void
-AudioRtpSession::setMuted(bool muted, Direction)
+AudioRtpSession::setMuted(bool muted, Direction dir)
 {
     std::lock_guard<std::recursive_mutex> lock(mutex_);
-    muteState_ = muted;
-    if (audioInput_)
-        audioInput_->setMuted(muted);
+    if (dir == Direction::SEND) {
+        muteState_ = muted;
+        if (audioInput_)
+            audioInput_->setMuted(muted);
+    } else {
+        if (receiveThread_) {
+            auto ms = receiveThread_->getInfo();
+            if (muted) {
+                if (auto ob = recorder_->getStream(ms.name)) {
+                    receiveThread_->detach(ob);
+                    recorder_->removeStream(ms);
+                }
+            } else {
+                if (auto ob = recorder_->addStream(ms)) {
+                    receiveThread_->attach(ob);
+                }
+            }
+        }
+    }
 }
 
 void
@@ -342,25 +365,55 @@ AudioRtpSession::processRtcpChecker()
 }
 
 void
-AudioRtpSession::initRecorder(std::shared_ptr<MediaRecorder>& rec)
+AudioRtpSession::attachRemoteRecorder(const MediaStream& ms)
 {
+    if (!recorder_ || !receiveThread_)
+        return;
+    if (auto ob = recorder_->addStream(ms)) {
+        receiveThread_->attach(ob);
+    }
+}
+
+void
+AudioRtpSession::attachLocalRecorder(const MediaStream& ms)
+{
+    if (!recorder_ || !audioInput_)
+        return;
+    if (auto ob = recorder_->addStream(ms)) {
+        audioInput_->attach(ob);
+    }
+}
+
+void
+AudioRtpSession::initRecorder()
+{
+    if (!recorder_)
+        return;
     if (receiveThread_)
-        receiveThread_->attach(rec->addStream(receiveThread_->getInfo()));
-    if (auto input = jami::getAudioInput(callId_))
-        input->attach(rec->addStream(input->getInfo()));
+        receiveThread_->setRecorderCallback(
+            [this](const MediaStream& ms) { attachRemoteRecorder(ms); });
+    if (audioInput_)
+        audioInput_->setRecorderCallback(
+            [this](const MediaStream& ms) { attachLocalRecorder(ms); });
 }
 
 void
-AudioRtpSession::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
+AudioRtpSession::deinitRecorder()
 {
+    if (!recorder_)
+        return;
     if (receiveThread_) {
-        if (auto ob = rec->getStream(receiveThread_->getInfo().name)) {
+        auto ms = receiveThread_->getInfo();
+        if (auto ob = recorder_->getStream(ms.name)) {
             receiveThread_->detach(ob);
+            recorder_->removeStream(ms);
         }
     }
-    if (auto input = jami::getAudioInput(callId_)) {
-        if (auto ob = rec->getStream(input->getInfo().name)) {
-            input->detach(ob);
+    if (audioInput_) {
+        auto ms = audioInput_->getInfo();
+        if (auto ob = recorder_->getStream(ms.name)) {
+            audioInput_->detach(ob);
+            recorder_->removeStream(ms);
         }
     }
 }
diff --git a/src/media/audio/audio_rtp_session.h b/src/media/audio/audio_rtp_session.h
index 2246f09dbd874e3b9d609fbf28a9fe38973b6341..ba84857ab5a04feabde468078beface9651b22e2 100644
--- a/src/media/audio/audio_rtp_session.h
+++ b/src/media/audio/audio_rtp_session.h
@@ -24,6 +24,7 @@
 #include "audiobuffer.h"
 #include "media_device.h"
 #include "rtp_session.h"
+#include "media_stream.h"
 
 #include "threadloop.h"
 
@@ -50,7 +51,9 @@ struct RTCPInfo
 class AudioRtpSession : public RtpSession
 {
 public:
-    AudioRtpSession(const std::string& callId, const std::string& streamId);
+    AudioRtpSession(const std::string& callId,
+                    const std::string& streamId,
+                    const std::shared_ptr<MediaRecorder>& rec);
     virtual ~AudioRtpSession();
 
     void start(std::unique_ptr<IceSocket> rtp_sock, std::unique_ptr<IceSocket> rtcp_sock) override;
@@ -58,8 +61,8 @@ public:
     void stop() override;
     void setMuted(bool muted, Direction dir = Direction::SEND) override;
 
-    void initRecorder(std::shared_ptr<MediaRecorder>& rec) override;
-    void deinitRecorder(std::shared_ptr<MediaRecorder>& rec) override;
+    void initRecorder() override;
+    void deinitRecorder() override;
 
     std::shared_ptr<AudioInput>& getAudioLocal() { return audioInput_; }
     std::unique_ptr<AudioReceiveThread>& getAudioReceive() { return receiveThread_; }
@@ -91,6 +94,9 @@ private:
     std::chrono::seconds rtcp_checking_interval {4};
 
     std::function<void(bool)> voiceCallback_;
+
+    void attachRemoteRecorder(const MediaStream& ms);
+    void attachLocalRecorder(const MediaStream& ms);
 };
 
 } // namespace jami
diff --git a/src/media/audio/pulseaudio/pulselayer.cpp b/src/media/audio/pulseaudio/pulselayer.cpp
index 644bd2e49ed5697af2a628f769176dc64f6bff8a..a958cd7c57b998f27873f1ed0ac1accf2771b0bf 100644
--- a/src/media/audio/pulseaudio/pulselayer.cpp
+++ b/src/media/audio/pulseaudio/pulselayer.cpp
@@ -482,6 +482,9 @@ PulseLayer::stopStream(AudioDeviceType type)
     stream->stop();
     stream.reset();
 
+    if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::ALL)
+        playbackChanged(false);
+
     std::lock_guard<std::mutex> lk(mutex_);
     if (not playback_ and not ringtone_ and not record_) {
         pendingStreams = 0;
diff --git a/src/media/media_attribute.cpp b/src/media/media_attribute.cpp
index d42d3c6faea71481658a22abf3853a8df1cb399a..2292d234d92bb41cfdb3f3d8926e1183fbf4e9eb 100644
--- a/src/media/media_attribute.cpp
+++ b/src/media/media_attribute.cpp
@@ -200,4 +200,10 @@ MediaAttribute::toString(bool full) const
 
     return descr.str();
 }
+
+bool
+MediaAttribute::hasValidVideo()
+{
+    return type_ == MediaType::MEDIA_VIDEO && enabled_&& !muted_ && !onHold_;
+}
 } // namespace jami
diff --git a/src/media/media_attribute.h b/src/media/media_attribute.h
index 06908a89ef44d00962515242c84ff8e53b52b96d..5053321fea4e137d86aa077efbd709ffeac30eaa 100644
--- a/src/media/media_attribute.h
+++ b/src/media/media_attribute.h
@@ -102,5 +102,7 @@ public:
     // For instance, muting the audio can be done by disabling the
     // audio input (capture) of the encoding session, resulting in
     // sending RTP packets without actual audio (silence).
+
+    bool hasValidVideo();
 };
 } // namespace jami
diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp
index 52625cf15f02766b5bd16a15c881bc65a08ed8af..f86f257905cf40ef404fa29de3f6897e3684694f 100644
--- a/src/media/media_decoder.cpp
+++ b/src/media/media_decoder.cpp
@@ -696,6 +696,11 @@ MediaDecoder::decode(AVPacket& packet)
 
         if (callback_)
             callback_(std::move(f));
+
+        if (contextCallback_ && firstDecode_.load()) {
+            firstDecode_.exchange(false);
+            contextCallback_();
+        }
         return DecodeStatus::FrameFinished;
     }
     return DecodeStatus::Success;
diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h
index f9194c0a94b852c69067215a7322d57d31bef490..8384ff89d0ace10c8ef2da07d1d2672792a04e13 100644
--- a/src/media/media_decoder.h
+++ b/src/media/media_decoder.h
@@ -214,6 +214,12 @@ public:
 
     void setFEC(bool enable) { fecEnabled_ = enable; }
 
+    void setContextCallback(const std::function<void()>& cb)
+    {
+        firstDecode_.exchange(true);
+        contextCallback_ = cb;
+    }
+
 private:
     NON_COPYABLE(MediaDecoder);
 
@@ -253,6 +259,9 @@ private:
 
     bool fecEnabled_ {false};
 
+    std::function<void()> contextCallback_;
+    std::atomic_bool firstDecode_ {true};
+
 protected:
     AVDictionary* options_ = nullptr;
 };
diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp
index b767e2f123e7ba0f357d82a1c304c86c14adf696..0e8a3547c06eaf2600ce91311b13324d4549f6f2 100644
--- a/src/media/media_encoder.cpp
+++ b/src/media/media_encoder.cpp
@@ -312,7 +312,7 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr
                 JAMI_WARN("Using hardware encoding for %s with %s ",
                           avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)),
                           it.getName().c_str());
-                encoders_.push_back(encoderCtx);
+                encoders_.emplace_back(encoderCtx);
                 break;
             }
         }
@@ -326,7 +326,7 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr
                                static_cast<AVCodecID>(systemCodecInfo.avcodecId),
                                videoOpts_.bitrate);
         readConfig(encoderCtx);
-        encoders_.push_back(encoderCtx);
+        encoders_.emplace_back(encoderCtx);
         if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
             throw MediaEncoderException("Could not open encoder");
     }
@@ -495,6 +495,8 @@ MediaEncoder::encode(AVFrame* frame, int streamIdx)
         }
     }
     int ret = 0;
+    if (streamIdx >= encoders_.size())
+        return -1;
     AVCodecContext* encoderCtx = encoders_[streamIdx];
     AVPacket pkt;
     av_init_packet(&pkt);
diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp
index 58fec39906b70fc87d55891d51370285f828ecb1..2fc71ddfd30d9bf5678900be44f631024bccce36 100644
--- a/src/media/media_recorder.cpp
+++ b/src/media/media_recorder.cpp
@@ -69,7 +69,12 @@ struct MediaRecorder::StreamObserver : public Observer<std::shared_ptr<MediaFram
         : info(ms)
         , cb_(func) {};
 
-    ~StreamObserver() {};
+    ~StreamObserver()
+    {
+        for (auto& obs : observablesFrames_) {
+            obs->detach(this);
+        }
+    };
 
     void update(Observable<std::shared_ptr<MediaFrame>>* /*ob*/,
                 const std::shared_ptr<MediaFrame>& m) override
@@ -117,15 +122,32 @@ struct MediaRecorder::StreamObserver : public Observer<std::shared_ptr<MediaFram
 #endif
     }
 
+    void attached(Observable<std::shared_ptr<MediaFrame>>* obs) override
+    {
+        observablesFrames_.insert(obs);
+    }
+
+    void detached(Observable<std::shared_ptr<MediaFrame>>* obs) override
+    {
+        auto it = observablesFrames_.find(obs);
+        if (it != observablesFrames_.end())
+            observablesFrames_.erase(it);
+    }
+
 private:
     std::function<void(const std::shared_ptr<MediaFrame>&)> cb_;
     std::unique_ptr<MediaFilter> videoRotationFilter_ {};
     int rotation_ = 0;
+    std::set<Observable<std::shared_ptr<MediaFrame>>*> observablesFrames_;
 };
 
 MediaRecorder::MediaRecorder() {}
 
-MediaRecorder::~MediaRecorder() {}
+MediaRecorder::~MediaRecorder()
+{
+    flush();
+    reset();
+}
 
 bool
 MediaRecorder::isRecording() const
@@ -168,6 +190,7 @@ MediaRecorder::startRecording()
     startTime_ = *std::localtime(&t);
     startTimeStamp_ = av_gettime();
 
+    std::lock_guard<std::mutex> lk(encoderMtx_);
     encoder_.reset(new MediaEncoder);
 
     JAMI_DBG() << "Start recording '" << getPath() << "'";
@@ -175,6 +198,7 @@ MediaRecorder::startRecording()
         isRecording_ = true;
         // start thread after isRecording_ is set to true
         dht::ThreadPool::computation().run([rec = shared_from_this()] {
+            std::lock_guard<std::mutex> lk(rec->encoderMtx_);
             while (rec->isRecording()) {
                 std::shared_ptr<MediaFrame> frame;
                 // get frame from queue
@@ -191,7 +215,7 @@ MediaRecorder::startRecording()
                 }
                 try {
                     // encode frame
-                    if (frame && frame->pointer()) {
+                    if (rec->encoder_ && frame && frame->pointer()) {
 #ifdef ENABLE_VIDEO
                         bool isVideo = (frame->pointer()->width > 0 && frame->pointer()->height > 0);
                         rec->encoder_->encode(frame->pointer(),
@@ -208,6 +232,7 @@ MediaRecorder::startRecording()
             rec->reset(); // allows recorder to be reused in same call
         });
     }
+    interrupted_ = false;
     return 0;
 }
 
@@ -226,32 +251,53 @@ MediaRecorder::stopRecording()
 Observer<std::shared_ptr<MediaFrame>>*
 MediaRecorder::addStream(const MediaStream& ms)
 {
+    std::lock_guard<std::mutex> lk(mutexStreamSetup_);
     if (audioOnly_ && ms.isVideo) {
         JAMI_ERR() << "Trying to add video stream to audio only recording";
         return nullptr;
     }
-    if (ms.isVideo && ms.format < 0) {
-        JAMI_ERR() << "Trying to add invalid video stream to recording";
+    if (ms.format < 0 || ms.name.empty()) {
+        JAMI_ERR() << "Trying to add invalid stream to recording";
         return nullptr;
     }
 
-    auto ptr = std::make_unique<StreamObserver>(ms,
-                                                [this,
-                                                 ms](const std::shared_ptr<MediaFrame>& frame) {
-                                                    onFrame(ms.name, frame);
-                                                });
-    auto p = streams_.insert(std::make_pair(ms.name, std::move(ptr)));
-    if (p.second) {
-        JAMI_DBG() << "Recorder input #" << streams_.size() << ": " << ms;
+    auto it = streams_.find(ms.name);
+    if (it == streams_.end()) {
+        auto streamPtr = std::make_unique<StreamObserver>(ms,
+                                                          [this,
+                                                           ms](const std::shared_ptr<MediaFrame>& frame) {
+                                                              onFrame(ms.name, frame);
+                                                          });
+        it = streams_.insert(std::make_pair(ms.name, std::move(streamPtr))).first;
+        JAMI_LOG("[Recorder: {:p}] Recorder input #{}: {:s}", fmt::ptr(this), streams_.size(), ms.name);
+    } else {
+        JAMI_LOG("[Recorder: {:p}] Recorder already has '{:s}' as input", fmt::ptr(this), ms.name);
+    }
+
+    if (ms.isVideo)
+        setupVideoOutput();
+    else
+        setupAudioOutput();
+    return it->second.get();
+}
+
+void
+MediaRecorder::removeStream(const MediaStream& ms)
+{
+    std::lock_guard<std::mutex> lk(mutexStreamSetup_);
+
+    auto it = streams_.find(ms.name);
+    if (it == streams_.end()) {
+        JAMI_LOG("[Recorder: {:p}] Recorder no stream to remove", fmt::ptr(this));
+    } else {
+        JAMI_LOG("[Recorder: {:p}] Recorder removing '{:s}'", fmt::ptr(this), ms.name);
+        streams_.erase(it);
         if (ms.isVideo)
-            hasVideo_ = true;
+            setupVideoOutput();
         else
-            hasAudio_ = true;
-        return p.first->second.get();
-    } else {
-        JAMI_WARN() << "Recorder already has '" << ms.name << "' as input";
-        return p.first->second.get();
+            setupAudioOutput();
     }
+    return;
 }
 
 Observer<std::shared_ptr<MediaFrame>>*
@@ -266,9 +312,11 @@ MediaRecorder::getStream(const std::string& name) const
 void
 MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame>& frame)
 {
-    if (not isRecording_)
+    if (not isRecording_ || interrupted_)
         return;
 
+    std::lock_guard<std::mutex> lk(mutexStreamSetup_);
+
     // copy frame to not mess with the original frame's pts (does not actually copy frame data)
     std::unique_ptr<MediaFrame> clone;
     const auto& ms = streams_[name]->info;
@@ -303,15 +351,23 @@ MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame
                                                                      | AV_ROUND_PASS_MINMAX));
     std::unique_ptr<MediaFrame> filteredFrame;
 #ifdef ENABLE_VIDEO
-    if (ms.isVideo) {
+    if (ms.isVideo && videoFilter_ && outputVideoFilter_) {
         std::lock_guard<std::mutex> lk(mutexFilterVideo_);
         videoFilter_->feedInput(clone->pointer(), name);
-        filteredFrame = videoFilter_->readOutput();
-    } else {
+        auto videoFilterOutput = videoFilter_->readOutput();
+        if (videoFilterOutput) {
+            outputVideoFilter_->feedInput(videoFilterOutput->pointer(), "input");
+            filteredFrame = outputVideoFilter_->readOutput();
+        }
+    } else if (audioFilter_ && outputAudioFilter_) {
 #endif // ENABLE_VIDEO
         std::lock_guard<std::mutex> lk(mutexFilterAudio_);
         audioFilter_->feedInput(clone->pointer(), name);
-        filteredFrame = audioFilter_->readOutput();
+        auto audioFilterOutput = audioFilter_->readOutput();
+        if (audioFilterOutput) {
+            outputAudioFilter_->feedInput(audioFilterOutput->pointer(), "input");
+            filteredFrame = outputAudioFilter_->readOutput();
+        }
 #ifdef ENABLE_VIDEO
     }
 #endif // ENABLE_VIDEO
@@ -348,32 +404,17 @@ MediaRecorder::initRecord()
 #ifdef RING_ACCEL
     encoder_->enableAccel(false); // TODO recorder has problems with hardware encoding
 #endif
-
-    videoFilter_.reset();
-    if (hasVideo_) {
-        const MediaStream& videoStream = setupVideoOutput();
-        if (videoStream.format < 0) {
-            JAMI_ERR() << "Could not retrieve video recorder stream properties";
-            return -1;
-        }
-        MediaDescription args;
-        args.mode = RateMode::CQ;
-        encoder_->setOptions(videoStream);
-        encoder_->setOptions(args);
-    }
 #endif // ENABLE_VIDEO
 
-    audioFilter_.reset();
-    if (hasAudio_) {
-        const MediaStream& audioStream = setupAudioOutput();
-        if (audioStream.format < 0) {
-            JAMI_ERR() << "Could not retrieve audio recorder stream properties";
-            return -1;
-        }
+    {
+        MediaStream audioStream;
+        audioStream.name = "audioOutput";
+        audioStream.format = 1;
+        audioStream.timeBase = rational<int>(1, 48000);
+        audioStream.sampleRate = 48000;
+        audioStream.nbChannels = 2;
         encoder_->setOptions(audioStream);
-    }
 
-    if (hasAudio_) {
         auto audioCodec = std::static_pointer_cast<jami::SystemAudioCodecInfo>(
             getSystemCodecContainer()->searchCodecByName("opus", jami::MEDIA_AUDIO));
         audioIdx_ = encoder_->addStream(*audioCodec.get());
@@ -384,7 +425,23 @@ MediaRecorder::initRecord()
     }
 
 #ifdef ENABLE_VIDEO
-    if (hasVideo_) {
+    if (!audioOnly_) {
+        MediaStream videoStream;
+
+        videoStream.name = "videoOutput";
+        videoStream.format = 0;
+        videoStream.isVideo = true;
+        videoStream.timeBase = rational<int>(0, 1);
+        videoStream.width = 1280;
+        videoStream.height = 720;
+        videoStream.frameRate = rational<int>(30, 1);
+        videoStream.bitrate = Manager::instance().videoPreferences.getRecordQuality();
+
+        MediaDescription args;
+        args.mode = RateMode::CQ;
+        encoder_->setOptions(videoStream);
+        encoder_->setOptions(args);
+
         auto videoCodec = std::static_pointer_cast<jami::SystemVideoCodecInfo>(
             getSystemCodecContainer()->searchCodecByName("VP8", jami::MEDIA_VIDEO));
         videoIdx_ = encoder_->addStream(*videoCodec.get());
@@ -401,7 +458,7 @@ MediaRecorder::initRecord()
     return 0;
 }
 
-MediaStream
+void
 MediaRecorder::setupVideoOutput()
 {
     MediaStream encoderStream, peer, local, mixer;
@@ -432,8 +489,8 @@ MediaRecorder::setupVideoOutput()
     int streams = peer.isValid() + local.isValid() + mixer.isValid();
     switch (streams) {
     case 0: {
-        JAMI_ERR("Trying to record a stream but none is valid");
-        break;
+        JAMI_WARN() << "Trying to record a video stream but none is valid";
+        return;
     }
     case 1: {
         MediaStream inputStream;
@@ -460,16 +517,38 @@ MediaRecorder::setupVideoOutput()
     }
 
 #ifdef ENABLE_VIDEO
-    if (ret >= 0) {
-        encoderStream = videoFilter_->getOutputParams();
-        encoderStream.bitrate = Manager::instance().videoPreferences.getRecordQuality();
-        JAMI_DBG() << "Recorder output: " << encoderStream;
-    } else {
+    if (ret < 0) {
         JAMI_ERR() << "Failed to initialize video filter";
     }
+
+    // setup output filter
+    if (!videoFilter_)
+        return;
+    MediaStream secondaryFilter = videoFilter_->getOutputParams();
+    secondaryFilter.name = "input";
+    if (outputVideoFilter_) {
+        outputVideoFilter_->flush();
+        outputVideoFilter_.reset();
+    }
+
+    outputVideoFilter_.reset(new MediaFilter);
+
+    float scaledHeight = 1280 * (float)secondaryFilter.height / (float)secondaryFilter.width;
+    std::string scaleFilter = "scale=1280:-2";
+    if (scaledHeight > 720)
+        scaleFilter += ",scale=-2:720";
+
+    ret = outputVideoFilter_->initialize(
+        "[input]" + scaleFilter + ",pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=pix_fmts=yuv420p,fps=30",
+        {secondaryFilter});
+
+    if (ret < 0) {
+        JAMI_ERR() << "Failed to initialize output video filter";
+    }
+
 #endif
 
-    return encoderStream;
+    return;
 }
 
 std::string
@@ -509,7 +588,7 @@ MediaRecorder::buildVideoFilter(const std::vector<MediaStream>& peers,
     return v.str();
 }
 
-MediaStream
+void
 MediaRecorder::setupAudioOutput()
 {
     MediaStream encoderStream, peer, local, mixer;
@@ -539,6 +618,10 @@ MediaRecorder::setupAudioOutput()
     int ret = -1;
     int streams = peer.isValid() + local.isValid() + mixer.isValid();
     switch (streams) {
+    case 0: {
+        JAMI_WARN() << "Trying to record a audio stream but none is valid";
+        return;
+    }
     case 1: {
         MediaStream inputStream;
         if (peer.isValid())
@@ -562,14 +645,31 @@ MediaRecorder::setupAudioOutput()
         break;
     }
 
-    if (ret >= 0) {
-        encoderStream = audioFilter_->getOutputParams();
-        JAMI_DBG() << "Recorder output: " << encoderStream;
-    } else {
+    if (ret < 0) {
         JAMI_ERR() << "Failed to initialize audio filter";
+        return;
+    }
+
+    // setup output filter
+    if (!audioFilter_)
+        return;
+    MediaStream secondaryFilter = audioFilter_->getOutputParams();
+    secondaryFilter.name = "input";
+    if (outputAudioFilter_) {
+        outputAudioFilter_->flush();
+        outputAudioFilter_.reset();
+    }
+
+    outputAudioFilter_.reset(new MediaFilter);
+    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";
     }
 
-    return encoderStream;
+    return;
 }
 
 std::string
@@ -600,13 +700,16 @@ MediaRecorder::flush()
     if (videoFilter_) {
         std::lock_guard<std::mutex> lk(mutexFilterVideo_);
         videoFilter_->flush();
+        outputVideoFilter_->flush();
     }
 
     if (audioFilter_) {
         std::lock_guard<std::mutex> lk(mutexFilterAudio_);
         audioFilter_->flush();
+        outputAudioFilter_->flush();
     }
-    encoder_->flush();
+    if (encoder_)
+        encoder_->flush();
 }
 
 void
@@ -616,11 +719,11 @@ MediaRecorder::reset()
         std::lock_guard<std::mutex> lk(mutexFrameBuff_);
         frameBuff_.clear();
     }
-    streams_.clear();
     videoIdx_ = audioIdx_ = -1;
-    audioOnly_ = false;
     videoFilter_.reset();
     audioFilter_.reset();
+    outputAudioFilter_.reset();
+    outputVideoFilter_.reset();
     encoder_.reset();
 }
 
diff --git a/src/media/media_recorder.h b/src/media/media_recorder.h
index b8021b74ff89e1fc9ecdac792ffe481b428b190d..ef1773756170a2efbf524199796fd3a907e0366a 100644
--- a/src/media/media_recorder.h
+++ b/src/media/media_recorder.h
@@ -96,6 +96,13 @@ public:
      */
     Observer<std::shared_ptr<MediaFrame>>* addStream(const MediaStream& ms);
 
+    /**
+     * @brief Removes a stream from the recorder.
+     *
+     * Caller must then detach this from the media source.
+     */
+    void removeStream(const MediaStream& ms);
+
     /**
      * @brief Gets the stream observer.
      *
@@ -128,10 +135,11 @@ private:
     void reset();
 
     int initRecord();
-    MediaStream setupVideoOutput();
+    void setupVideoOutput();
     std::string buildVideoFilter(const std::vector<MediaStream>& peers,
                                  const MediaStream& local) const;
-    MediaStream setupAudioOutput();
+    void setupAudioOutput();
+    std::mutex mutexStreamSetup_;
     std::string buildAudioFilter(const std::vector<MediaStream>& peers,
                                  const MediaStream& local) const;
 
@@ -148,15 +156,18 @@ private:
     std::string description_;
 
     std::unique_ptr<MediaEncoder> encoder_;
+    std::mutex encoderMtx_;
+    std::unique_ptr<MediaFilter> outputVideoFilter_;
+    std::unique_ptr<MediaFilter> outputAudioFilter_;
+
     std::unique_ptr<MediaFilter> videoFilter_;
     std::unique_ptr<MediaFilter> audioFilter_;
 
-    bool hasAudio_ {false};
-    bool hasVideo_ {false};
     int videoIdx_ = -1;
     int audioIdx_ = -1;
     bool isRecording_ = false;
     bool audioOnly_ = false;
+    int lastVideoPts_ = 0;
 
     std::condition_variable cv_;
     std::atomic_bool interrupted_ {false};
diff --git a/src/media/peerrecorder.h b/src/media/peerrecorder.h
index 3e8e09e52dc3b11700675b736861787d2508d232..39cb3a16a57e3550d0ddd430b4a643791f7d42fe 100644
--- a/src/media/peerrecorder.h
+++ b/src/media/peerrecorder.h
@@ -37,7 +37,7 @@ public:
 
     virtual bool isPeerRecording() const { return peerRecording_; }
 
-    virtual void peerMuted(bool muted) = 0;
+    virtual void peerMuted(bool muted, int streamIdx) = 0;
 
     virtual bool isPeerMuted() const { return peerMuted_; }
 
diff --git a/src/media/recordable.cpp b/src/media/recordable.cpp
index 3784a70e86df1d1a1959cad45909ef402eea008c..6124fb3c95dda2bc6f089ed45b43d95bc34fb685 100644
--- a/src/media/recordable.cpp
+++ b/src/media/recordable.cpp
@@ -114,8 +114,6 @@ Recordable::stopRecording()
 
     recorder_->stopRecording();
     recording_ = false;
-    // new recorder since this one may still be recording
-    recorder_ = std::make_shared<MediaRecorder>();
 }
 
 bool
diff --git a/src/media/rtp_session.h b/src/media/rtp_session.h
index b86f51cee751184786d4e8e145b85b3234899a2d..4c6cfbbfb62956a2db1ec02b0c68c7733efba19f 100644
--- a/src/media/rtp_session.h
+++ b/src/media/rtp_session.h
@@ -71,8 +71,8 @@ public:
         onSuccessfulSetup_ = cb;
     }
 
-    virtual void initRecorder(std::shared_ptr<MediaRecorder>& rec) = 0;
-    virtual void deinitRecorder(std::shared_ptr<MediaRecorder>& rec) = 0;
+    virtual void initRecorder() = 0;
+    virtual void deinitRecorder() = 0;
     std::shared_ptr<AccountCodecInfo> getCodec() const { return send_.codec; }
     const IpAddr& getSendAddr() const { return send_.addr; };
     const IpAddr& getRecvAddr() const { return receive_.addr; };
@@ -89,7 +89,7 @@ protected:
     MediaDescription send_;
     MediaDescription receive_;
     uint16_t mtu_;
-
+    std::shared_ptr<MediaRecorder> recorder_;
     std::function<void(MediaType, bool)> onSuccessfulSetup_;
 
     std::string getRemoteRtpUri() const { return "rtp://" + send_.addr.toString(true); }
diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp
index 9d6bce3a9dfe2bcb160e32697dec231ee800bafd..8b224a5d60587ecb10e219c460d03109358b4aed 100644
--- a/src/media/video/video_input.cpp
+++ b/src/media/video/video_input.cpp
@@ -261,6 +261,18 @@ VideoInput::configureFilePlayback(const std::string&,
     sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
 }
 
+void
+VideoInput::setRecorderCallback(
+    const std::function<void(const MediaStream& ms)>& cb)
+{
+    recorderCallback_ = cb;
+    if (decoder_)
+        decoder_->setContextCallback([this]() {
+            if (recorderCallback_)
+                recorderCallback_(getInfo());
+        });
+}
+
 void
 VideoInput::createDecoder()
 {
@@ -346,10 +358,16 @@ VideoInput::createDecoder()
         onSuccessfulSetup_(MEDIA_VIDEO, 0);
 
     decoder_ = std::move(decoder);
+
     foundDecOpts(decOpts_);
 
     /* Signal the client about readable sink */
     sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
+
+    decoder_->setContextCallback([this]() {
+        if (recorderCallback_)
+            recorderCallback_(getInfo());
+    });
 }
 
 void
diff --git a/src/media/video/video_input.h b/src/media/video/video_input.h
index 2b84786bee283c687919d3b80ac8028230f70661..d56d186d8dccc304f7c2f02e3855c1d1fb1609ed 100644
--- a/src/media/video/video_input.h
+++ b/src/media/video/video_input.h
@@ -53,7 +53,7 @@ class SinkClient;
 
 enum class VideoInputMode { ManagedByClient, ManagedByDaemon, Undefined };
 
-class VideoInput : public VideoGenerator, public std::enable_shared_from_this<VideoInput>
+class VideoInput : public VideoGenerator
 {
 public:
     VideoInput(VideoInputMode inputMode = VideoInputMode::Undefined,
@@ -87,6 +87,8 @@ public:
     void setupSink();
     void stopSink();
 
+    void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb);
+
 #if VIDEO_CLIENT_INPUT
     /*
      * these functions are used to pass buffer from/to the daemon
@@ -169,6 +171,7 @@ private:
     std::atomic_bool paused_ {true};
 
     std::function<void(MediaType, bool)> onSuccessfulSetup_;
+    std::function<void(const MediaStream& ms)> recorderCallback_;
 };
 
 } // namespace video
diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp
index 0138814d47ab2ba02825f22303b94f2723819b23..24341615b633cf062e418bf7204695b8678a7050 100644
--- a/src/media/video/video_receive_thread.cpp
+++ b/src/media/video/video_receive_thread.cpp
@@ -103,6 +103,10 @@ VideoReceiveThread::setup()
                                             displayMatrix.release());
         publishFrame(std::static_pointer_cast<VideoFrame>(frame));
     }));
+    videoDecoder_->setContextCallback([this]() {
+        if (recorderCallback_)
+            recorderCallback_(getInfo());
+    });
     videoDecoder_->setResolutionChangedCallback([this](int width, int height) {
         dstWidth_ = width;
         dstHeight_ = height;
@@ -188,6 +192,18 @@ VideoReceiveThread::addIOContext(SocketPair& socketPair)
     demuxContext_.reset(socketPair.createIOContext(mtu_));
 }
 
+void
+VideoReceiveThread::setRecorderCallback(
+    const std::function<void(const MediaStream& ms)>& cb)
+{
+    recorderCallback_ = cb;
+    if (videoDecoder_)
+        videoDecoder_->setContextCallback([this]() {
+            if (recorderCallback_)
+                recorderCallback_(getInfo());
+        });
+}
+
 void
 VideoReceiveThread::decodeFrame()
 {
diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h
index 375670f3bf47083851d54235f004d5d983dcf533..ca5d47fbb73a139050c9a173ed53f1fcd5079bbd 100644
--- a/src/media/video/video_receive_thread.h
+++ b/src/media/video/video_receive_thread.h
@@ -86,6 +86,9 @@ public:
         onSuccessfulSetup_ = cb;
     }
 
+    void setRecorderCallback(
+        const std::function<void(const MediaStream& ms)>& cb);
+
 private:
     NON_COPYABLE(VideoReceiveThread);
 
@@ -123,6 +126,7 @@ private:
 
     std::function<void(void)> keyFrameRequestCallback_;
     std::function<void(MediaType, bool)> onSuccessfulSetup_;
+    std::function<void(const MediaStream& ms)> recorderCallback_;
 };
 
 } // namespace video
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index bc5bd25e789858b9a29aba1dfb6f2181f2873a4a..0a620ce49124c1baf7abbc2c2c5cd2b0df77c386 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -61,12 +61,14 @@ constexpr auto DELAY_AFTER_REMB_DEC = std::chrono::milliseconds(500);
 
 VideoRtpSession::VideoRtpSession(const string& callId,
                                  const string& streamId,
-                                 const DeviceParams& localVideoParams)
+                                 const DeviceParams& localVideoParams,
+                                 const std::shared_ptr<MediaRecorder>& rec)
     : RtpSession(callId, streamId, MediaType::MEDIA_VIDEO)
     , localVideoParams_(localVideoParams)
     , videoBitrateInfo_ {}
     , rtcpCheckerThread_([] { return true; }, [this] { processRtcpChecker(); }, [] {})
 {
+    recorder_ = rec;
     setupVideoBitrateInfo(); // reset bitrate
     cc = std::make_unique<CongestionControl>();
     JAMI_DBG("[%p] Video RTP session created for call %s", this, callId_.c_str());
@@ -74,6 +76,7 @@ VideoRtpSession::VideoRtpSession(const string& callId,
 
 VideoRtpSession::~VideoRtpSession()
 {
+    deinitRecorder();
     stop();
     JAMI_DBG("[%p] Video RTP session destroyed", this);
 }
@@ -128,6 +131,10 @@ VideoRtpSession::startSender()
             auto input = getVideoInput(input_);
             videoLocal_ = input;
             if (input) {
+                videoLocal_->setRecorderCallback(
+                    [this](const MediaStream& ms) {
+                        attachLocalRecorder(ms);
+                    });
                 auto newParams = input->getParams();
                 try {
                     if (newParams.valid()
@@ -272,6 +279,8 @@ VideoRtpSession::startReceiver()
             if (activeStream)
                 videoMixer_->setActiveStream(streamId_);
         }
+        receiveThread_->setRecorderCallback(
+            [this](const MediaStream& ms) { attachRemoteRecorder(ms); });
 
     } else {
         JAMI_DBG("[%p] Video receiver disabled", this);
@@ -316,6 +325,12 @@ VideoRtpSession::stopReceiver()
     if (socketPair_)
         socketPair_->setReadBlockingMode(false);
 
+    auto ms = receiveThread_->getInfo();
+    if (auto ob = recorder_->getStream(ms.name)) {
+        receiveThread_->detach(ob);
+        recorder_->removeStream(ms);
+    }
+
     receiveThread_->stopLoop();
     receiveThread_->stopSink();
 }
@@ -417,6 +432,13 @@ VideoRtpSession::setMuted(bool mute, Direction dir)
         }
 
         if ((send_.onHold = mute)) {
+            if (videoLocal_) {
+                auto ms = videoLocal_->getInfo();
+                if (auto ob = recorder_->getStream(ms.name)) {
+                    videoLocal_->detach(ob);
+                    recorder_->removeStream(ms);
+                }
+            }
             stopSender();
         } else {
             restartSender();
@@ -431,6 +453,13 @@ VideoRtpSession::setMuted(bool mute, Direction dir)
     }
 
     if ((receive_.onHold = mute)) {
+        if (receiveThread_) {
+            auto ms = receiveThread_->getInfo();
+            if (auto ob = recorder_->getStream(ms.name)) {
+                receiveThread_->detach(ob);
+                recorder_->removeStream(ms);
+            }
+        }
         stopReceiver();
     } else {
         startReceiver();
@@ -738,35 +767,57 @@ VideoRtpSession::processRtcpChecker()
 }
 
 void
-VideoRtpSession::initRecorder(std::shared_ptr<MediaRecorder>& rec)
+VideoRtpSession::attachRemoteRecorder(const MediaStream& ms)
 {
+    if (!recorder_ || !receiveThread_)
+        return;
+    if (auto ob = recorder_->addStream(ms)) {
+        receiveThread_->attach(ob);
+    }
+}
+
+void
+VideoRtpSession::attachLocalRecorder(const MediaStream& ms)
+{
+    if (!recorder_ || !videoLocal_ || !Manager::instance().videoPreferences.getRecordPreview())
+        return;
+    if (auto ob = recorder_->addStream(ms)) {
+        videoLocal_->attach(ob);
+    }
+}
+
+void
+VideoRtpSession::initRecorder()
+{
+	if (!recorder_)
+		return;
     if (receiveThread_) {
-        if (auto ob = rec->addStream(receiveThread_->getInfo())) {
-            receiveThread_->attach(ob);
-        }
+        receiveThread_->setRecorderCallback(
+            [this](const MediaStream& ms) { attachRemoteRecorder(ms); });
     }
-    if (Manager::instance().videoPreferences.getRecordPreview()) {
-        if (auto input = std::static_pointer_cast<VideoInput>(videoLocal_)) {
-            if (auto ob = rec->addStream(input->getInfo())) {
-                input->attach(ob);
-            }
-        }
+    if (videoLocal_ && !send_.onHold) {
+        videoLocal_->setRecorderCallback(
+            [this](const MediaStream& ms) { attachLocalRecorder(ms); });
     }
 }
 
 void
-VideoRtpSession::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
+VideoRtpSession::deinitRecorder()
 {
-    if (!rec)
-        return;
+	if (!recorder_)
+		return;
     if (receiveThread_) {
-        if (auto ob = rec->getStream(receiveThread_->getInfo().name)) {
+        auto ms = receiveThread_->getInfo();
+        if (auto ob = recorder_->getStream(ms.name)) {
             receiveThread_->detach(ob);
+            recorder_->removeStream(ms);
         }
     }
-    if (auto input = std::static_pointer_cast<VideoInput>(videoLocal_)) {
-        if (auto ob = rec->getStream(input->getInfo().name)) {
-            input->detach(ob);
+    if (videoLocal_) {
+        auto ms = videoLocal_->getInfo();
+        if (auto ob = recorder_->getStream(ms.name)) {
+            videoLocal_->detach(ob);
+            recorder_->removeStream(ms);
         }
     }
 }
diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h
index 7a3b099de9c7d22e5b6163c8cc3ae6aacde84e24..fca36bc91217a6b0b3d8a1e471a7ea7c6b99160e 100644
--- a/src/media/video/video_rtp_session.h
+++ b/src/media/video/video_rtp_session.h
@@ -70,7 +70,10 @@ class VideoRtpSession : public RtpSession
 public:
     using BaseType = RtpSession;
 
-    VideoRtpSession(const std::string& callId, const std::string& streamId, const DeviceParams& localVideoParams);
+    VideoRtpSession(const std::string& callId,
+                    const std::string& streamId,
+                    const DeviceParams& localVideoParams,
+                    const std::shared_ptr<MediaRecorder>& rec);
     ~VideoRtpSession();
 
     void setRequestKeyFrameCallback(std::function<void(void)> cb);
@@ -97,8 +100,8 @@ public:
     void exitConference();
 
     void setChangeOrientationCallback(std::function<void(int)> cb);
-    void initRecorder(std::shared_ptr<MediaRecorder>& rec) override;
-    void deinitRecorder(std::shared_ptr<MediaRecorder>& rec) override;
+    void initRecorder() override;
+    void deinitRecorder() override;
 
     const VideoBitrateInfo& getVideoBitrateInfo();
 
@@ -175,6 +178,9 @@ private:
     std::function<void(void)> cbKeyFrameRequest_;
 
     std::atomic<int> rotation_ {0};
+
+    void attachRemoteRecorder(const MediaStream& ms);
+    void attachLocalRecorder(const MediaStream& ms);
 };
 
 } // namespace video
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 2d22667b51c66b53d18ea4c1e84a8d205bbbc14e..2b5355995e8626327253c9036e1e74af764cd714 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -185,13 +185,14 @@ SIPCall::createRtpSession(RtpStream& stream)
     // To get audio_0 ; video_0
     auto streamId = sip_utils::streamId(id_, stream.mediaAttribute_->label_);
     if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
-        stream.rtpSession_ = std::make_shared<AudioRtpSession>(id_, streamId);
+        stream.rtpSession_ = std::make_shared<AudioRtpSession>(id_, streamId, recorder_);
     }
 #ifdef ENABLE_VIDEO
     else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
         stream.rtpSession_ = std::make_shared<video::VideoRtpSession>(id_,
                                                                       streamId,
-                                                                      getVideoSettings());
+                                                                      getVideoSettings(),
+                                                                      recorder_);
         std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation_);
     }
 #endif
@@ -236,7 +237,7 @@ SIPCall::configureRtpSession(const std::shared_ptr<RtpSession>& rtpSession,
 
     rtpSession->setSuccessfulSetupCb([w = weak()](MediaType type, bool isRemote) {
         if (auto thisPtr = w.lock())
-            thisPtr->rtpSetupSuccess(type, isRemote);
+            thisPtr->rtpSetupSuccess();
     });
 
     if (localMedia.type == MediaType::MEDIA_AUDIO) {
@@ -1472,7 +1473,6 @@ SIPCall::switchInput(const std::string& source)
     }
     if (isRec) {
         readyToRecord_ = false;
-        resetMediaReady();
         pendingRecord_ = true;
     }
 }
@@ -2017,7 +2017,11 @@ SIPCall::hasVideo() const
 {
 #ifdef ENABLE_VIDEO
     std::function<bool(const RtpStream& stream)> videoCheck = [](auto const& stream) {
-        return stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO;
+        bool validVideo = stream.mediaAttribute_
+                          && stream.mediaAttribute_->hasValidVideo();
+        bool validRemoteVideo = stream.remoteMediaAttribute_
+                                && stream.remoteMediaAttribute_->hasValidVideo();
+        return validVideo || validRemoteVideo;
     };
 
     const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), videoCheck);
@@ -2153,7 +2157,6 @@ SIPCall::startAllMedia()
 
     // reset
     readyToRecord_ = false;
-    resetMediaReady();
 
     for (auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) {
         if (not iter->mediaAttribute_) {
@@ -2219,9 +2222,6 @@ void
 SIPCall::stopAllMedia()
 {
     JAMI_DBG("[call:%s] Stopping all media", getCallId().c_str());
-    deinitRecorder();
-    if (Call::isRecording())
-        stopRecording(); // if call stops, finish recording
 
 #ifdef ENABLE_VIDEO
     {
@@ -2270,11 +2270,11 @@ SIPCall::updateRemoteMedia()
         auto const& remoteMedia = rtpStream.remoteMediaAttribute_ = std::make_shared<MediaAttribute>(
             remoteMediaList[idx]);
         if (remoteMedia->type_ == MediaType::MEDIA_VIDEO) {
+            rtpStream.rtpSession_->setMuted(remoteMedia->muted_, RtpSession::Direction::RECV);
             JAMI_DEBUG("[call:{:s}] Remote media @ {:d}: {:s}",
                        getCallId(),
                        idx,
                        remoteMedia->toString());
-            rtpStream.rtpSession_->setMuted(remoteMedia->muted_, RtpSession::Direction::RECV);
             // Request a key-frame if we are un-muting the video
             if (not remoteMedia->muted_)
                 requestKeyframe(findRtpStreamIndex(remoteMedia->label_));
@@ -2650,6 +2650,19 @@ SIPCall::reportMediaNegotiationStatus()
         callId,
         libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS,
         currentMediaList());
+    auto previousState = isAudioOnly_;
+    auto newState = !hasVideo();
+
+    if (previousState != newState && Call::isRecording()) {
+        deinitRecorder();
+        toggleRecording();
+        pendingRecord_ = true;
+    }
+    isAudioOnly_ = newState;
+
+    if (pendingRecord_ && readyToRecord_) {
+        toggleRecording();
+    }
 }
 
 void
@@ -3259,10 +3272,9 @@ SIPCall::toggleRecording()
                                  peerUri_);
         recorder_->setMetadata(title, ""); // use default description
         for (const auto& rtpSession : getRtpSessionList())
-            rtpSession->initRecorder(recorder_);
+            rtpSession->initRecorder();
     } else {
         updateRecState(false);
-        deinitRecorder();
     }
     pendingRecord_ = false;
     auto state = Call::toggleRecording();
@@ -3275,7 +3287,7 @@ void
 SIPCall::deinitRecorder()
 {
     for (const auto& rtpSession : getRtpSessionList())
-        rtpSession->deinitRecorder(recorder_);
+        rtpSession->deinitRecorder();
 }
 
 void
@@ -3541,25 +3553,21 @@ SIPCall::newIceSocket(unsigned compId)
 }
 
 void
-SIPCall::rtpSetupSuccess(MediaType type, bool isRemote)
+SIPCall::rtpSetupSuccess()
 {
     std::lock_guard<std::mutex> lk {setupSuccessMutex_};
-    if (type == MEDIA_AUDIO) {
-        if (isRemote)
-            mediaReady_.at("a:remote") = true;
-        else
-            mediaReady_.at("a:local") = true;
-    } else {
-        if (isRemote)
-            mediaReady_.at("v:remote") = true;
-        else
-            mediaReady_.at("v:local") = true;
-    }
 
-    isAudioOnly_ = !hasVideo();
-#ifdef ENABLE_VIDEO
     readyToRecord_ = true; // We're ready to record whenever a stream is ready
-#endif
+
+    auto previousState = isAudioOnly_;
+    auto newState = !hasVideo();
+
+    if (previousState != newState && Call::isRecording()) {
+        deinitRecorder();
+        toggleRecording();
+        pendingRecord_ = true;
+    }
+    isAudioOnly_ = newState;
 
     if (pendingRecord_ && readyToRecord_)
         toggleRecording();
@@ -3583,13 +3591,23 @@ SIPCall::peerRecording(bool state)
 }
 
 void
-SIPCall::peerMuted(bool muted)
+SIPCall::peerMuted(bool muted, int streamIdx)
 {
     if (muted) {
         JAMI_WARN("Peer muted");
     } else {
         JAMI_WARN("Peer un-muted");
     }
+
+    if (streamIdx == -1) {
+        for (const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
+            audioRtp->setMuted(muted, RtpSession::Direction::RECV);
+    } else if (streamIdx > -1 && streamIdx < static_cast<int>(rtpStreams_.size())) {
+        auto& stream = rtpStreams_[streamIdx];
+        if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO)
+            stream.rtpSession_->setMuted(muted, RtpSession::Direction::RECV);
+    }
+
     peerMuted_ = muted;
     if (auto conf = conf_.lock())
         conf->updateMuted();
@@ -3608,11 +3626,4 @@ SIPCall::peerVoice(bool voice)
     }
 }
 
-void
-SIPCall::resetMediaReady()
-{
-    for (auto& m : mediaReady_)
-        m.second = false;
-}
-
 } // namespace jami
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 663b7c7b7935c5abca39ee0bb4094b04212054f0..97c92feda6622f6db96cb0cfa48eff7e01c84628 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -170,7 +170,7 @@ public:
 
     // Override PeerRecorder
     void peerRecording(bool state) override;
-    void peerMuted(bool state) override;
+    void peerMuted(bool state, int streamIdx) override;
     void peerVoice(bool state) override;
     // end override PeerRecorder
 
@@ -347,7 +347,7 @@ private:
 
     void deinitRecorder();
 
-    void rtpSetupSuccess(MediaType type, bool isRemote);
+    void rtpSetupSuccess();
 
     void setupVoiceCallback(const std::shared_ptr<RtpSession>& rtpSession);
 
@@ -518,12 +518,6 @@ private:
 
     std::atomic_bool waitForIceInit_ {false};
 
-    std::map<const std::string, bool> mediaReady_ {{"a:local", false},
-                                                   {"a:remote", false},
-                                                   {"v:local", false},
-                                                   {"v:remote", false}};
-
-    void resetMediaReady();
     void detachAudioFromConference();
 
     std::mutex setupSuccessMutex_;
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index ea0d3ef1aea0ef777b1f437aef0f8cf4a117f639..588a859d54873a2954162cd0275db36fcf14a5b1 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -1239,7 +1239,7 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body)
             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
                 try {
                     bool state = std::stoi(matched_pattern[1]);
-                    call.peerMuted(state);
+                    call.peerMuted(state, streamIdx);
                 } catch (const std::exception& e) {
                     JAMI_WARN("Error parsing state remote mute: %s", e.what());
                 }
diff --git a/test/unitTest/call/recorder.cpp b/test/unitTest/call/recorder.cpp
index a45efc7a02c7c6e88bcb3ed7d2cc5dd2f158a310..a101c68f85551b1a154c9cdb3de0b28a7766384f 100644
--- a/test/unitTest/call/recorder.cpp
+++ b/test/unitTest/call/recorder.cpp
@@ -31,6 +31,7 @@
 #include "jamidht/jamiaccount.h"
 #include "manager.h"
 #include "media_const.h"
+#include "client/videomanager.h"
 
 #include "common.h"
 
@@ -47,6 +48,7 @@ struct CallData
     std::string mediaStatus {};
     std::string device {};
     std::string hostState {};
+    bool changeRequested = false;
 
     void reset()
     {
@@ -83,6 +85,8 @@ public:
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
 
+    std::string videoPath = std::filesystem::absolute("media/test_video_file.mp4").string();
+
 private:
     void registerSignalHandlers();
     void testRecordCall();
@@ -146,6 +150,14 @@ RecorderTest::registerSignalHandlers()
             }
             cv.notify_one();
         }));
+    confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaChangeRequested>(
+        [=](const std::string& accountId,
+            const std::string& callId,
+            const std::vector<std::map<std::string, std::string>>&) {
+            if (accountId == bobId && bobCall.callId == callId) {
+                bobCall.changeRequested = true;
+            }
+        }));
     confHandlers.insert(
         libjami::exportable_callback<libjami::CallSignal::StateChange>([=](const std::string& accountId,
                                                                        const std::string& callId,
@@ -189,38 +201,84 @@ RecorderTest::testRecordCall()
     std::vector<std::map<std::string, std::string>> mediaList;
     std::map<std::string, std::string> mediaAttributeA
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
-           {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
-           {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
-           {libjami::Media::MediaAttributeKey::SOURCE, ""}};
+        {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
+        {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+        {libjami::Media::MediaAttributeKey::LABEL, "audio_0"},
+        {libjami::Media::MediaAttributeKey::SOURCE, ""}};
     std::map<std::string, std::string> mediaAttributeV
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
-           {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
-           {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
-           {libjami::Media::MediaAttributeKey::SOURCE, ""}};
+        {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
+        {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+        {libjami::Media::MediaAttributeKey::LABEL, "video_0"},
+        {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}};
     mediaList.emplace_back(mediaAttributeA);
-    mediaList.emplace_back(mediaAttributeV);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
-    Manager::instance().answerCall(bobId, bobCall.callId);
+    libjami::acceptWithMedia(bobId, bobCall.callId, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
         return bobCall.mediaStatus
                == libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS;
     }));
-    // give time to start camera
+
     std::this_thread::sleep_for(5s);
 
     // Start recorder
     recordedFile.clear();
     CPPUNIT_ASSERT(!libjami::getIsRecording(aliceId, callId));
     libjami::toggleRecording(aliceId, callId);
-
-    // Stop recorder after a few seconds
     std::this_thread::sleep_for(5s);
     CPPUNIT_ASSERT(libjami::getIsRecording(aliceId, callId));
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return recordedFile.empty(); }));
+
+    // add local video
+    {
+        auto newMediaList = mediaList;
+        newMediaList.emplace_back(mediaAttributeV);
+
+        // Request Media Change
+        libjami::requestMediaChange(aliceId, callId, newMediaList);
+
+        CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return bobCall.changeRequested; }));
+
+        // Answer the change request
+        bobCall.mediaStatus = "";
+        libjami::answerMediaChangeRequest(bobId, bobCall.callId, newMediaList);
+        bobCall.changeRequested = false;
+        CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
+            return bobCall.mediaStatus
+                == libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS;
+        }));
+
+        CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !recordedFile.empty() && recordedFile.find(".ogg") != std::string::npos; }));
+        recordedFile = "";
+        // give time to start camera
+        std::this_thread::sleep_for(10s);
+
+        CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return recordedFile.empty(); }));
+    }
+
+    // mute local video
+    {
+        mediaAttributeV[libjami::Media::MediaAttributeKey::MUTED] = TRUE_STR;
+        auto newMediaList = mediaList;
+        newMediaList.emplace_back(mediaAttributeV);
+
+        // Mute Bob video
+        libjami::requestMediaChange(aliceId, callId, newMediaList);
+        std::this_thread::sleep_for(5s);
+        libjami::requestMediaChange(bobId, bobCall.callId, newMediaList);
+
+        CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !recordedFile.empty() && recordedFile.find(".webm") != std::string::npos; }));
+        recordedFile = "";
+        std::this_thread::sleep_for(10s);
+    }
+
+    // Stop recorder after a few seconds
     libjami::toggleRecording(aliceId, callId);
     CPPUNIT_ASSERT(!libjami::getIsRecording(aliceId, callId));
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !recordedFile.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !recordedFile.empty() && recordedFile.find(".ogg") != std::string::npos; }));
 
     Manager::instance().hangupCall(aliceId, callId);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER"; }));
@@ -237,16 +295,18 @@ RecorderTest::testRecordAudioOnlyCall()
     auto bobUri = bobAccount->getUsername();
 
     JAMI_INFO("Start call between Alice and Bob");
+    // Audio only call
     std::vector<std::map<std::string, std::string>> mediaList;
     std::map<std::string, std::string> mediaAttribute
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
            {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
            {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+           {libjami::Media::MediaAttributeKey::LABEL, "audio_0"},
            {libjami::Media::MediaAttributeKey::SOURCE, ""}};
     mediaList.emplace_back(mediaAttribute);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
-    Manager::instance().answerCall(bobId, bobCall.callId);
+    libjami::acceptWithMedia(bobId, bobCall.callId, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
         return bobCall.mediaStatus
                == libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS;
@@ -255,10 +315,10 @@ RecorderTest::testRecordAudioOnlyCall()
     // Start recorder
     recordedFile.clear();
     libjami::toggleRecording(aliceId, callId);
-
-    // Stop recorder
     std::this_thread::sleep_for(5s);
     CPPUNIT_ASSERT(libjami::getIsRecording(aliceId, callId));
+
+    // Toggle recording
     libjami::toggleRecording(aliceId, callId);
     CPPUNIT_ASSERT(!libjami::getIsRecording(aliceId, callId));
 
@@ -290,11 +350,13 @@ RecorderTest::testRecordCallOnePersonRdv()
     recordedFile.clear();
 
     JAMI_INFO("Start call between Alice and Bob");
+    // Audio only call
     std::vector<std::map<std::string, std::string>> mediaList;
     std::map<std::string, std::string> mediaAttributeA
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
            {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
            {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+           {libjami::Media::MediaAttributeKey::LABEL, "audio_0"},
            {libjami::Media::MediaAttributeKey::SOURCE, ""}};
     mediaList.emplace_back(mediaAttributeA);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
@@ -306,18 +368,19 @@ RecorderTest::testRecordCallOnePersonRdv()
 
     CPPUNIT_ASSERT(!libjami::getIsRecording(aliceId, callId));
     libjami::toggleRecording(aliceId, callId);
-
-    // Stop recorder after a few seconds
     std::this_thread::sleep_for(5s);
+
     CPPUNIT_ASSERT(libjami::getIsRecording(aliceId, callId));
+
+    // Stop recorder
     libjami::toggleRecording(aliceId, callId);
     CPPUNIT_ASSERT(!libjami::getIsRecording(aliceId, callId));
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !recordedFile.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !recordedFile.empty() && recordedFile.find(".ogg") != std::string::npos; }));
 
     Manager::instance().hangupCall(aliceId, callId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && !recordedFile.empty(); }));
+        cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER"; }));
     JAMI_INFO("End testRecordCallOnePersonRdv");
 }
 
@@ -334,36 +397,39 @@ RecorderTest::testStopCallWhileRecording()
     std::vector<std::map<std::string, std::string>> mediaList;
     std::map<std::string, std::string> mediaAttributeA
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
-           {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
-           {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
-           {libjami::Media::MediaAttributeKey::SOURCE, ""}};
+        {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
+        {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+        {libjami::Media::MediaAttributeKey::LABEL, "audio_0"},
+        {libjami::Media::MediaAttributeKey::SOURCE, ""}};
     std::map<std::string, std::string> mediaAttributeV
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
-           {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
-           {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
-           {libjami::Media::MediaAttributeKey::SOURCE, ""}};
+        {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
+        {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+        {libjami::Media::MediaAttributeKey::LABEL, "video_0"},
+        {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}};
     mediaList.emplace_back(mediaAttributeA);
     mediaList.emplace_back(mediaAttributeV);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
-    Manager::instance().answerCall(bobId, bobCall.callId);
+    libjami::acceptWithMedia(bobId, bobCall.callId, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
         return bobCall.mediaStatus
                == libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS;
     }));
+
     // give time to start camera
     std::this_thread::sleep_for(5s);
 
     // Start recorder
     recordedFile.clear();
     libjami::toggleRecording(aliceId, callId);
+    std::this_thread::sleep_for(10s);
+    CPPUNIT_ASSERT(libjami::getIsRecording(aliceId, callId));
 
     // Hangup call
-    std::this_thread::sleep_for(5s);
-    CPPUNIT_ASSERT(libjami::getIsRecording(aliceId, callId));
     Manager::instance().hangupCall(aliceId, callId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && !recordedFile.empty(); }));
+        cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && !recordedFile.empty() && recordedFile.find(".webm") != std::string::npos; }));
     JAMI_INFO("End testStopCallWhileRecording");
 }
 
@@ -383,13 +449,21 @@ RecorderTest::testDaemonPreference()
     std::vector<std::map<std::string, std::string>> mediaList;
     std::map<std::string, std::string> mediaAttributeA
         = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
-           {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
-           {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
-           {libjami::Media::MediaAttributeKey::SOURCE, ""}};
+        {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
+        {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+        {libjami::Media::MediaAttributeKey::LABEL, "audio_0"},
+        {libjami::Media::MediaAttributeKey::SOURCE, ""}};
+    std::map<std::string, std::string> mediaAttributeV
+        = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
+        {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
+        {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
+        {libjami::Media::MediaAttributeKey::LABEL, "video_0"},
+        {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}};
     mediaList.emplace_back(mediaAttributeA);
+    mediaList.emplace_back(mediaAttributeV);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
-    Manager::instance().answerCall(bobId, bobCall.callId);
+    libjami::acceptWithMedia(bobId, bobCall.callId, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
         return bobCall.mediaStatus
                == libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS;
@@ -398,10 +472,11 @@ RecorderTest::testDaemonPreference()
     // Let record some seconds
     std::this_thread::sleep_for(5s);
     CPPUNIT_ASSERT(libjami::getIsRecording(aliceId, callId));
+    std::this_thread::sleep_for(10s);
 
     Manager::instance().hangupCall(aliceId, callId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && !recordedFile.empty(); }));
+        cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && !recordedFile.empty() && recordedFile.find(".webm") != std::string::npos; }));
     JAMI_INFO("End testDaemonPreference");
 }