diff --git a/applications/gui2/src/views/camera3d.cpp b/applications/gui2/src/views/camera3d.cpp
index e4432a5031fb31f87a6fce0aed99e0f4d31383ca..384c182b3ef6c73b93428c050b8c2befc07412fd 100644
--- a/applications/gui2/src/views/camera3d.cpp
+++ b/applications/gui2/src/views/camera3d.cpp
@@ -33,6 +33,8 @@ CameraView3D::CameraView3D(ftl::gui2::Screen *parent, ftl::gui2::Camera *ctrl) :
 	delta_ = 0.0;
 	lerp_speed_ = 0.999f;
 
+	pose_up_to_date_.test_and_set();
+
 	setZoom(false);
 	setPan(false);
 }
@@ -42,16 +44,19 @@ bool CameraView3D::keyboardEvent(int key, int scancode, int action, int modifier
 		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
 		float scalar = (key == 263) ? -mag : mag;
 		neye_ += rotmat_*Eigen::Vector4d(scalar, 0.0, 0.0, 1.0);
+		pose_up_to_date_.clear();
 	}
 	else if (key == 264 || key == 265) {
 		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
 		float scalar = (key == 264) ? -mag : mag;
 		neye_ += rotmat_*Eigen::Vector4d(0.0, 0.0, scalar, 1.0);
+		pose_up_to_date_.clear();
 	}
 	else if (key == 266 || key == 267) {
 		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
 		float scalar = (key == 266) ? -mag : mag;
 		neye_ += rotmat_*Eigen::Vector4d(0.0, scalar, 0.0, 1.0);
+		pose_up_to_date_.clear();
 	}
 	else if (key >= '0' && key <= '5' && modifiers == 2) {  // Ctrl+NUMBER
 	}
@@ -71,6 +76,7 @@ bool CameraView3D::mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vecto
 
 	rx_ += rel[0];
 	ry_ += rel[1];
+	pose_up_to_date_.clear();
 
 	//LOG(INFO) << "New pose: \n" << getUpdatedPose();
 	//ctrl_->sendPose(getUpdatedPose());
@@ -112,6 +118,8 @@ Eigen::Matrix4d CameraView3D::getUpdatedPose() {
 
 void CameraView3D::draw(NVGcontext* ctx) {
 	// poll from ctrl_ or send on event instead?
-	ctrl_->sendPose(getUpdatedPose());
+	if (!pose_up_to_date_.test_and_set()) {
+		ctrl_->sendPose(getUpdatedPose());
+	}
 	CameraView::draw(ctx);
 }
diff --git a/applications/gui2/src/views/camera3d.hpp b/applications/gui2/src/views/camera3d.hpp
index 9bd762ee7872320d245a5f21710ca7385183d2af..47132b8c356e236b90cbe9d8ebe39e473c7b0a71 100644
--- a/applications/gui2/src/views/camera3d.hpp
+++ b/applications/gui2/src/views/camera3d.hpp
@@ -39,6 +39,8 @@ protected:
 
 	double lerp_speed_;
 
+	std::atomic_flag pose_up_to_date_;
+
 public:
 	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
 };
diff --git a/components/streams/include/ftl/streams/feed.hpp b/components/streams/include/ftl/streams/feed.hpp
index e01a20ba0de52bf8c1a7da9a570a30c24eb7c4c0..9054831c20e25fd5653f0dcd76b1eb8602a7b596 100644
--- a/components/streams/include/ftl/streams/feed.hpp
+++ b/components/streams/include/ftl/streams/feed.hpp
@@ -89,6 +89,7 @@ private:
 	std::unique_ptr<ftl::stream::Sender> recorder_;
 	std::unique_ptr<ftl::stream::Broadcast> record_stream_;
 	ftl::Handle handle_record_;
+	ftl::Handle record_recv_handle_;
 	Filter *record_filter_;
 
 	//ftl::Handler<const ftl::data::FrameSetPtr&> frameset_cb_;
diff --git a/components/streams/include/ftl/streams/receiver.hpp b/components/streams/include/ftl/streams/receiver.hpp
index c54a377e639be64e1d727dd0f48193f8ed276de4..bb78bb3dbbcde50c25c689c8cda5ab3b51124ace 100644
--- a/components/streams/include/ftl/streams/receiver.hpp
+++ b/components/streams/include/ftl/streams/receiver.hpp
@@ -41,6 +41,8 @@ class Receiver : public ftl::Configurable, public ftl::data::Generator {
 
 	void registerBuilder(const std::shared_ptr<ftl::streams::BaseBuilder> &b);
 
+	void processPackets(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt);
+
 	private:
 	ftl::stream::Stream *stream_;
 	ftl::data::Pool *pool_;
diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp
index cd168145965193a63abda763ef2a4ae7b917b492..0042bed839e5fb702d8c7a60c0f0d9e2835475f7 100644
--- a/components/streams/src/feed.cpp
+++ b/components/streams/src/feed.cpp
@@ -95,7 +95,9 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 		"recent_files",
 		"known_hosts",
 		"auto_host_connect",
-		"auto_host_sources"
+		"auto_host_sources",
+		"uri",
+		"recorder"
 	});
 
 	pool_ = std::make_unique<ftl::data::Pool>(3,5);
@@ -118,6 +120,11 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 		(ftl::create<ftl::stream::Broadcast>(this, "record_stream"));
 	recorder_->setStream(record_stream_.get());
 
+	record_recv_handle_ = record_stream_->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		receiver_->processPackets(spkt, pkt);
+		return true;
+	});
+
 	record_filter_ = nullptr;
 
 	//interceptor_->setStream(stream_.get());
@@ -645,7 +652,7 @@ uint32_t Feed::add(const std::string &path) {
 			auto *creator = new ftl::streams::ManualSourceBuilder(pool_.get(), fsid, source);
 			if (uri.getBaseURI() == "device::openvr") creator->setFrameRate(1000);
 			else creator->setFrameRate(30);
-			
+
 			std::shared_ptr<ftl::streams::BaseBuilder> creatorptr(creator);
 			lk.lock();
 			receiver_->registerBuilder(creatorptr);
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index b1bd910450b26094b271af903859857689634a5c..5b6f6b5756bd821c93f37e1c4c034cc73004810d 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -393,64 +393,68 @@ void Receiver::_processVideo(const StreamPacket &spkt, const Packet &pkt) {
 	}
 }
 
-void Receiver::setStream(ftl::stream::Stream *s) {
-	handle_.cancel();
-	stream_ = s;
+void Receiver::processPackets(const StreamPacket &spkt, const Packet &pkt) {
+	const unsigned int channum = (unsigned int)spkt.channel;
 
-	handle_ = s->onPacket([this](const StreamPacket &spkt, const Packet &pkt) {
-		const unsigned int channum = (unsigned int)spkt.channel;
-
-		// No data packet means channel is only available.
-		if (pkt.data.size() == 0) {
-			if (spkt.streamID < 255 && !(spkt.flags & ftl::codecs::kFlagRequest)) {
-				// Get the frameset
-				auto fs = builder(spkt.streamID).get(spkt.timestamp, spkt.frame_number+pkt.frame_count-1);
-				const auto *cs = stream_;
-				const auto sel = stream_->selected(spkt.frameSetID()) & cs->available(spkt.frameSetID());
-
-				for (auto &frame : fs->frames) {
-					//LOG(INFO) << "MARK " << frame.source() << " " << (int)spkt.channel;
-					frame.markAvailable(spkt.channel);
-
-					if (spkt.flags & ftl::codecs::kFlagCompleted) {
-						//UNIQUE_LOCK(vidstate.mutex, lk);  // FIXME: Should have a lock here...
-						timestamp_ = spkt.timestamp;
-						fs->completed(frame.source());
-					}
-
-					//if (frame.availableAll(sel)) {
-						//LOG(INFO) << "FRAME COMPLETED " << frame.source();
-					//	fs->completed(frame.source());
-					//}
+	// No data packet means channel is only available.
+	if (pkt.data.size() == 0) {
+		if (spkt.streamID < 255 && !(spkt.flags & ftl::codecs::kFlagRequest)) {
+			// Get the frameset
+			auto fs = builder(spkt.streamID).get(spkt.timestamp, spkt.frame_number+pkt.frame_count-1);
+			const auto *cs = stream_;
+			const auto sel = stream_->selected(spkt.frameSetID()) & cs->available(spkt.frameSetID());
+
+			for (auto &frame : fs->frames) {
+				//LOG(INFO) << "MARK " << frame.source() << " " << (int)spkt.channel;
+				frame.markAvailable(spkt.channel);
+
+				if (spkt.flags & ftl::codecs::kFlagCompleted) {
+					//UNIQUE_LOCK(vidstate.mutex, lk);  // FIXME: Should have a lock here...
+					timestamp_ = spkt.timestamp;
+					fs->completed(frame.source());
 				}
+
+				//if (frame.availableAll(sel)) {
+					//LOG(INFO) << "FRAME COMPLETED " << frame.source();
+				//	fs->completed(frame.source());
+				//}
 			}
-			return true;
 		}
+		return;
+	}
 
-		//LOG(INFO) << "PACKET: " << spkt.timestamp << ", " << (int)spkt.channel << ", " << (int)pkt.codec << ", " << (int)pkt.definition;
+	//LOG(INFO) << "PACKET: " << spkt.timestamp << ", " << (int)spkt.channel << ", " << (int)pkt.codec << ", " << (int)pkt.definition;
 
-		// TODO: Allow for multiple framesets
-		//if (spkt.frameSetID() > 0) LOG(INFO) << "Frameset " << spkt.frameSetID() << " received: " << (int)spkt.channel;
-		if (spkt.frameSetID() >= ftl::stream::kMaxStreams) return true;
+	// TODO: Allow for multiple framesets
+	//if (spkt.frameSetID() > 0) LOG(INFO) << "Frameset " << spkt.frameSetID() << " received: " << (int)spkt.channel;
+	if (spkt.frameSetID() >= ftl::stream::kMaxStreams) return;
 
-		// Frameset level data channels
-		if (spkt.frameNumber() == 255 && pkt.data.size() > 0) {
-			_processData(spkt,pkt);
-			return true;
-		}
+	// Frameset level data channels
+	if (spkt.frameNumber() == 255 && pkt.data.size() > 0) {
+		_processData(spkt,pkt);
+		return;
+	}
 
-		// Too many frames, so ignore.
-		//if (spkt.frameNumber() >= value("max_frames",32)) return;
-		if (spkt.frameNumber() >= 32 || ((1 << spkt.frameNumber()) & frame_mask_) == 0) return true;
+	// Too many frames, so ignore.
+	//if (spkt.frameNumber() >= value("max_frames",32)) return;
+	if (spkt.frameNumber() >= 32 || ((1 << spkt.frameNumber()) & frame_mask_) == 0) return;
 
 
-		if (channum >= 64) {
-			_processData(spkt,pkt);
-		} else if (channum >= 32 && channum < 64) {
-			_processAudio(spkt,pkt);
-		} else {
-			_processVideo(spkt,pkt);
-		}
+	if (channum >= 64) {
+		_processData(spkt,pkt);
+	} else if (channum >= 32 && channum < 64) {
+		_processAudio(spkt,pkt);
+	} else {
+		_processVideo(spkt,pkt);
+	}
+}
+
+void Receiver::setStream(ftl::stream::Stream *s) {
+	handle_.cancel();
+	stream_ = s;
+
+	handle_ = s->onPacket([this](const StreamPacket &spkt, const Packet &pkt) {
+		processPackets(spkt, pkt);
 		return true;
 	});
 }
diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp
index b6b807080180cc046a4900c2309f7a47fd891624..0eb8b64aaf09a257812d18f5e4a0fe4c37521b14 100644
--- a/components/streams/src/sender.cpp
+++ b/components/streams/src/sender.cpp
@@ -204,7 +204,7 @@ void Sender::post(ftl::data::FrameSet &fs, ftl::codecs::Channel c, bool noencode
 				StreamPacket spkt;
 				spkt.version = 5;
 				spkt.timestamp = fs.timestamp();
-				spkt.streamID = 0; //fs.id;
+				spkt.streamID = fs.frameset(); //fs.id;
 				spkt.frame_number = i;
 				spkt.channel = c;
 				spkt.flags = (last_flush) ? ftl::codecs::kFlagCompleted : 0;
@@ -297,7 +297,7 @@ void Sender::post(ftl::data::Frame &frame, ftl::codecs::Channel c) {
 				StreamPacket spkt;
 				spkt.version = 5;
 				spkt.timestamp = frame.timestamp();
-				spkt.streamID = 0; //fs.id;
+				spkt.streamID = frame.frameset(); //fs.id;
 				spkt.frame_number = frame.source();
 				spkt.channel = c;
 
@@ -393,7 +393,7 @@ void Sender::_encodeVideoChannel(ftl::data::FrameSet &fs, Channel c, bool reset,
 		StreamPacket spkt;
 		spkt.version = 5;
 		spkt.timestamp = fs.timestamp();
-		spkt.streamID = 0; // FIXME: fs.id;
+		spkt.streamID = fs.frameset();
 		spkt.frame_number = offset;
 		spkt.channel = c;
 		spkt.flags = (last_flush) ? ftl::codecs::kFlagCompleted : 0;
diff --git a/components/streams/src/stream.cpp b/components/streams/src/stream.cpp
index 9e66f165b110f357727cc21958b8352432bd6749..d01a1c2f7e63ab8305e5bb037640ec110bda9c1b 100644
--- a/components/streams/src/stream.cpp
+++ b/components/streams/src/stream.cpp
@@ -217,7 +217,7 @@ void Broadcast::add(Stream *s) {
 
 	streams_.push_back(s);
 	handles_.push_back(std::move(s->onPacket([this,s](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
-		LOG(INFO) << "BCAST Request: " << (int)spkt.streamID << " " << (int)spkt.channel << " " << spkt.timestamp;
+		//LOG(INFO) << "BCAST Request: " << (int)spkt.streamID << " " << (int)spkt.channel << " " << spkt.timestamp;
 		SHARED_LOCK(mutex_, lk);
 		if (spkt.frameSetID() < 255) available(spkt.frameSetID()) += spkt.channel;
 		cb_.trigger(spkt, pkt);