diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp
index e7f16077a17a69728627b05ac2cc8df99a6d069e..58ad403a86280a5efb3c75dbd33df35f42cccafd 100644
--- a/src/media/audio/audio_rtp_session.cpp
+++ b/src/media/audio/audio_rtp_session.cpp
@@ -196,23 +196,25 @@ AudioRtpSession::setMuted(bool isMuted)
 void
 AudioRtpSession::initRecorder(std::shared_ptr<MediaRecorder>& rec)
 {
-    if (receiveThread_) {
-        receiveThread_->attach(rec.get());
-        rec->addStream(receiveThread_->getInfo());
-    }
-    if (auto input = ring::getAudioInput(callID_)) {
-        input->attach(rec.get());
-        rec->addStream(input->getInfo());
-    }
+    if (receiveThread_)
+        receiveThread_->attach(rec->addStream(receiveThread_->getInfo()));
+    if (auto input = ring::getAudioInput(callID_))
+        input->attach(rec->addStream(input->getInfo()));
 }
 
 void
 AudioRtpSession::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
 {
-    if (receiveThread_)
-        receiveThread_->detach(rec.get());
-    if (auto input = ring::getAudioInput(callID_))
-        input->detach(rec.get());
+    if (receiveThread_) {
+        if (auto ob = rec->getStream(receiveThread_->getInfo().name)) {
+            receiveThread_->detach(ob);
+        }
+    }
+    if (auto input = ring::getAudioInput(callID_)) {
+        if (auto ob = rec->getStream(input->getInfo().name)) {
+            input->detach(ob);
+        }
+    }
 }
 
 } // namespace ring
diff --git a/src/media/localrecorder.cpp b/src/media/localrecorder.cpp
index 8eaed6c993829aa3ab989c544cda7e7a9b589f0b..4a995e9c16e470e841babafd6d841fd85d2af259 100644
--- a/src/media/localrecorder.cpp
+++ b/src/media/localrecorder.cpp
@@ -73,16 +73,14 @@ LocalRecorder::startRecording()
 
     audioInput_ = ring::getAudioInput(path_);
     audioInput_->setFormat(AudioFormat::STEREO());
-    recorder_->addStream(audioInput_->getInfo());
-    audioInput_->attach(recorder_.get());
+    audioInput_->attach(recorder_->addStream(audioInput_->getInfo()));
 
 #ifdef RING_VIDEO
     // video recording
     if (!isAudioOnly_) {
         videoInput_ = std::static_pointer_cast<video::VideoInput>(ring::getVideoCamera());
         if (videoInput_) {
-            recorder_->addStream(videoInput_->getInfo());
-            videoInput_->attach(recorder_.get());
+            videoInput_->attach(recorder_->addStream(videoInput_->getInfo()));
         } else {
             RING_ERR() << "Unable to record video (no video input)";
             return false;
@@ -98,9 +96,11 @@ LocalRecorder::stopRecording()
 {
     Recordable::stopRecording();
     Manager::instance().getRingBufferPool().unBindHalfDuplexOut(path_, RingBufferPool::DEFAULT_ID);
-    audioInput_->detach(recorder_.get());
+    if (auto ob = recorder_->getStream(audioInput_->getInfo().name))
+        audioInput_->detach(ob);
     if (videoInput_)
-        videoInput_->detach(recorder_.get());
+        if (auto ob = recorder_->getStream(videoInput_->getInfo().name))
+            videoInput_->detach(ob);
     audioInput_.reset();
     videoInput_.reset();
 }
diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp
index baf35522ece8469c2dd8df3b3ea1b66be3833bba..1b8005fdd9c47f3394fc21dff97916ec85959497 100644
--- a/src/media/media_recorder.cpp
+++ b/src/media/media_recorder.cpp
@@ -19,9 +19,6 @@
  */
 
 #include "libav_deps.h" // MUST BE INCLUDED FIRST
-#include "audio/audio_input.h"
-#include "audio/audio_receive_thread.h"
-#include "audio/audio_sender.h"
 #include "client/ring_signal.h"
 #include "fileutils.h"
 #include "logger.h"
@@ -29,8 +26,6 @@
 #include "media_recorder.h"
 #include "system_codec_container.h"
 #include "thread_pool.h"
-#include "video/video_input.h"
-#include "video/video_receive_thread.h"
 
 #include <algorithm>
 #include <iomanip>
@@ -119,45 +114,50 @@ MediaRecorder::stopRecording()
     }
 }
 
-int
+Observer<std::shared_ptr<MediaFrame>>*
 MediaRecorder::addStream(const MediaStream& ms)
 {
     if (audioOnly_ && ms.isVideo) {
         RING_ERR() << "Trying to add video stream to audio only recording";
-        return -1;
+        return nullptr;
     }
 
-    if (streams_.insert(std::make_pair(ms.name, ms)).second) {
+    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) {
         RING_DBG() << "Recorder input #" << streams_.size() << ": " << ms;
         if (ms.isVideo)
             hasVideo_ = true;
         else
             hasAudio_ = true;
-        return 0;
+        return p.first->second.get();
     } else {
-        RING_ERR() << "Could not add stream '" << ms.name << "' to record";
-        return -1;
+        RING_WARN() << "Recorder already has '" << ms.name << "' as input";
+        return p.first->second.get();
     }
 }
 
+Observer<std::shared_ptr<MediaFrame>>*
+MediaRecorder::getStream(const std::string& name) const
+{
+    const auto it = streams_.find(name);
+    if (it != streams_.cend())
+        return it->second.get();
+    return nullptr;
+}
+
 void
-MediaRecorder::update(Observable<std::shared_ptr<MediaFrame>>* ob, const std::shared_ptr<MediaFrame>& m)
+MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame>& frame)
 {
     if (!isRecording_)
         return;
-    std::string name;
-    if (dynamic_cast<AudioReceiveThread*>(ob))
-        name = "a:remote";
-    else if (dynamic_cast<AudioInput*>(ob))
-        name = "a:local";
-    else if (dynamic_cast<video::VideoReceiveThread*>(ob))
-        name = "v:remote";
-    else if (dynamic_cast<video::VideoInput*>(ob))
-        name = "v:local";
+
     // copy frame to not mess with the original frame's pts (does not actually copy frame data)
     MediaFrame clone;
-    clone.copyFrom(*m);
-    clone.pointer()->pts -= streams_[name].firstTimestamp;
+    clone.copyFrom(*frame);
+    clone.pointer()->pts -= streams_[name]->info.firstTimestamp;
     if (clone.pointer()->width > 0 && clone.pointer()->height > 0)
         videoFilter_->feedInput(clone.pointer(), name);
     else
@@ -262,19 +262,19 @@ MediaRecorder::setupVideoOutput()
 {
     MediaStream encoderStream, peer, local;
     auto it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair){
-        return pair.second.isVideo && pair.second.name.find("remote") != std::string::npos;
+        return pair.second->info.isVideo && pair.second->info.name.find("remote") != std::string::npos;
     });
 
     if (it != streams_.end()) {
-        peer = it->second;
+        peer = it->second->info;
     }
 
     it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair){
-        return pair.second.isVideo && pair.second.name.find("local") != std::string::npos;
+        return pair.second->info.isVideo && pair.second->info.name.find("local") != std::string::npos;
     });
 
     if (it != streams_.end()) {
-        local = it->second;
+        local = it->second->info;
     }
 
     // vp8 supports only yuv420p
@@ -348,19 +348,19 @@ MediaRecorder::setupAudioOutput()
 {
     MediaStream encoderStream, peer, local;
     auto it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair){
-        return !pair.second.isVideo && pair.second.name.find("remote") != std::string::npos;
+        return !pair.second->info.isVideo && pair.second->info.name.find("remote") != std::string::npos;
     });
 
     if (it != streams_.end()) {
-        peer = it->second;
+        peer = it->second->info;
     }
 
     it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair){
-        return !pair.second.isVideo && pair.second.name.find("local") != std::string::npos;
+        return !pair.second->info.isVideo && pair.second->info.name.find("local") != std::string::npos;
     });
 
     if (it != streams_.end()) {
-        local = it->second;
+        local = it->second->info;
     }
 
     // resample to common audio format, so any player can play the file
diff --git a/src/media/media_recorder.h b/src/media/media_recorder.h
index 0bcb632f0ab1ea7918f570761ec62a785f12de80..b169a8279344b6090d9dff23d2b492d7946014e5 100644
--- a/src/media/media_recorder.h
+++ b/src/media/media_recorder.h
@@ -27,9 +27,6 @@
 #include "media_stream.h"
 #include "noncopyable.h"
 #include "observer.h"
-#ifdef RING_VIDEO
-#include "video/video_base.h"
-#endif
 
 #include <map>
 #include <memory>
@@ -41,8 +38,7 @@
 
 namespace ring {
 
-class MediaRecorder : public Observer<std::shared_ptr<MediaFrame>>
-                    , public std::enable_shared_from_this<MediaRecorder>
+class MediaRecorder : public std::enable_shared_from_this<MediaRecorder>
 {
 public:
     MediaRecorder();
@@ -84,8 +80,14 @@ public:
     void setMetadata(const std::string& title, const std::string& desc);
 
     /**
+     * Adds a stream to the recorder. Caller must then attach this to the media source.
      */
-    int addStream(const MediaStream& ms);
+    Observer<std::shared_ptr<MediaFrame>>* addStream(const MediaStream& ms);
+
+    /**
+     * Gets the stream observer so the caller can detach it from the media source.
+     */
+    Observer<std::shared_ptr<MediaFrame>>* getStream(const std::string& name) const;
 
     /**
      * Starts the record. Streams must have been added using Observable::attach and
@@ -98,14 +100,29 @@ public:
      */
     void stopRecording();
 
-    /**
-     * Updates the recorder with an audio or video frame.
-     */
-    void update(Observable<std::shared_ptr<MediaFrame>>* ob, const std::shared_ptr<MediaFrame>& a) override;
-
 private:
     NON_COPYABLE(MediaRecorder);
 
+    struct StreamObserver : public Observer<std::shared_ptr<MediaFrame>> {
+        const MediaStream info;
+
+        StreamObserver(const MediaStream& ms, std::function<void(const std::shared_ptr<MediaFrame>&)> func)
+            : info(ms), cb_(func)
+        {};
+
+        ~StreamObserver() {};
+
+        void update(Observable<std::shared_ptr<MediaFrame>>* /*ob*/, const std::shared_ptr<MediaFrame>& m) override
+        {
+            cb_(m);
+        }
+
+    private:
+        std::function<void(const std::shared_ptr<MediaFrame>&)> cb_;
+    };
+
+    void onFrame(const std::string& name, const std::shared_ptr<MediaFrame>& frame);
+
     void flush();
     void reset();
 
@@ -117,7 +134,7 @@ private:
 
     std::mutex mutex_; // protect against concurrent file writes
 
-    std::map<std::string, const MediaStream> streams_;
+    std::map<std::string, std::unique_ptr<StreamObserver>> streams_;
 
     std::string path_;
     std::tm startTime_;
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index 9cf78560558a2f8e85c74edbd2a3f9723c81f863..332ceb71fbbfaf7123bfcb7fc22492fd3a86206d 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -568,22 +568,30 @@ void
 VideoRtpSession::initRecorder(std::shared_ptr<MediaRecorder>& rec)
 {
     if (receiveThread_) {
-        receiveThread_->attach(rec.get());
-        rec->addStream(receiveThread_->getInfo());
+        if (auto ob = rec->addStream(receiveThread_->getInfo())) {
+            receiveThread_->attach(ob);
+        }
     }
-    if (auto vidInput = std::static_pointer_cast<VideoInput>(videoLocal_)) {
-        vidInput->attach(rec.get());
-        rec->addStream(vidInput->getInfo());
+    if (auto input = std::static_pointer_cast<VideoInput>(videoLocal_)) {
+        if (auto ob = rec->addStream(input->getInfo())) {
+            input->attach(ob);
+        }
     }
 }
 
 void
 VideoRtpSession::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
 {
-    if (receiveThread_)
-        receiveThread_->detach(rec.get());
-    if (auto vidInput = std::static_pointer_cast<VideoInput>(videoLocal_))
-        vidInput->detach(rec.get());
+    if (receiveThread_) {
+        if (auto ob = rec->getStream(receiveThread_->getInfo().name)) {
+            receiveThread_->detach(ob);
+        }
+    }
+    if (auto input = std::static_pointer_cast<VideoInput>(videoLocal_)) {
+        if (auto ob = rec->getStream(input->getInfo().name)) {
+            input->detach(ob);
+        }
+    }
 }
 
 }} // namespace ring::video