diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp
index 8685ac5111811fffa578b4f565776dc5ef6c0fff..cfe7714daabfdfd596fbcb675c702f01a5064cd8 100644
--- a/components/codecs/include/ftl/codecs/packet.hpp
+++ b/components/codecs/include/ftl/codecs/packet.hpp
@@ -46,6 +46,7 @@ struct Packet {
 };
 
 static constexpr unsigned int kStreamCap_Static = 0x01;
+static constexpr unsigned int kStreamCap_Recorded = 0x02;
 
 /** V4 packets have no stream flags field */
 struct StreamPacketV4 {
diff --git a/components/streams/src/filestream.cpp b/components/streams/src/filestream.cpp
index 3c06ece80784019af43524c6491a286aa844ce99..90cf549ab31bfe989df6c9b3653527a0ec31b8ad 100644
--- a/components/streams/src/filestream.cpp
+++ b/components/streams/src/filestream.cpp
@@ -266,7 +266,7 @@ bool File::tick(int64_t ts) {
 		// Adjust timestamp
 		// FIXME: A potential bug where multiple times are merged into one?
 		std::get<0>(data).timestamp = (((std::get<0>(data).timestamp) - first_ts_) / interval_) * interval_ + timestart_;
-		std::get<0>(data).hint_capability = (is_video_) ? 0 : ftl::codecs::kStreamCap_Static;
+		std::get<0>(data).hint_capability = ((is_video_) ? 0 : ftl::codecs::kStreamCap_Static) | ftl::codecs::kStreamCap_Recorded;
 
 		// Maintain availability of channels.
 		available(0) += std::get<0>(data).channel;
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index 29f0088190f39631016c9f2bbf55f905407de3bf..ced6f24054382f25ab0fe0c943a4adee9bccd627 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -2,6 +2,7 @@
 #include <ftl/codecs/depth_convert_cuda.hpp>
 #include <ftl/profiler.hpp>
 #include <ftl/audio/software_decoder.hpp>
+#include <ftl/rgbd/capabilities.hpp>
 
 #include <opencv2/cudaimgproc.hpp>
 #include <opencv2/highgui.hpp>
@@ -23,7 +24,7 @@ using ftl::stream::parsePose;
 using ftl::stream::parseConfig;
 using ftl::stream::injectCalibration;
 using ftl::stream::injectPose;
-using ftl::codecs::definition_t;
+using ftl::rgbd::Capability;
 
 Receiver::Receiver(nlohmann::json &config, ftl::data::Pool *p) : ftl::Configurable(config), stream_(nullptr), pool_(p) {
 	timestamp_ = 0;
@@ -163,7 +164,23 @@ void Receiver::_processData(const StreamPacket &spkt, const Packet &pkt) {
 	auto &build = builder(spkt.streamID);
 	auto fs = build.get(spkt.timestamp, spkt.frame_number);
 	auto &f = (spkt.frame_number == 255) ? **fs : fs->frames[spkt.frame_number];
-	f.informChange(spkt.channel, build.changeType(), pkt);
+
+	// Remove LIVE capability if stream hints it is recorded
+	if (spkt.channel == Channel::Capabilities && (spkt.hint_capability & ftl::codecs::kStreamCap_Recorded)) {
+		std::any data;
+		ftl::data::decode_type<std::unordered_set<Capability>>(data, pkt.data);
+
+		auto &cap = *std::any_cast<std::unordered_set<Capability>>(&data);
+		if (cap.count(Capability::LIVE)) {
+			cap.erase(Capability::LIVE);
+		}
+
+		f.informChange(spkt.channel, build.changeType(), data);
+	} else {
+		f.informChange(spkt.channel, build.changeType(), pkt);
+	}
+
+	// TODO: Adjust metadata also for recorded streams
 
 	if (spkt.flags & ftl::codecs::kFlagCompleted) {
 		//UNIQUE_LOCK(vidstate.mutex, lk);