diff --git a/applications/gui2/src/modules/camera.cpp b/applications/gui2/src/modules/camera.cpp
index 36e0e37793c1a66dc356e121166820af3ad7706c..aec6695fdaa9aaf429681437e165a7f191fa8193 100644
--- a/applications/gui2/src/modules/camera.cpp
+++ b/applications/gui2/src/modules/camera.cpp
@@ -30,13 +30,16 @@ void Camera::update(double delta) {
 	if (nframes_ < 0) { return; }
 	if (nframes_ > update_fps_freq_) {
 		float n = nframes_;
+		float l = latency_ / n;
 		nframes_ = 0;
+		latency_ = 0;
 		auto t =  glfwGetTime();
 		float diff = t - last_;
 		last_ =  t;
 
 		auto *mod = screen->getModule<ftl::gui2::Statistics>();
 		mod->getJSON(StatisticsPanel::PERFORMANCE_INFO)["FPS"] = n/diff;
+		mod->getJSON(StatisticsPanel::PERFORMANCE_INFO)["Latency"] = l;
 		if (live_) mod->getJSON(StatisticsPanel::MEDIA_STATUS)["LIVE"] = nlohmann::json{{"icon", ENTYPO_ICON_VIDEO_CAMERA},{"value", true},{"colour","#0000FF"},{"size",28}};
 
 		auto ptr = std::atomic_load(&latest_);
@@ -285,6 +288,7 @@ void Camera::activate(ftl::data::FrameID id) {
 
 			screen->redraw();
 			nframes_++;
+			latency_ += ftl::timer::get_time() - fs->localTimestamp;
 			return true;
 		}
 	);
diff --git a/applications/gui2/src/modules/camera.hpp b/applications/gui2/src/modules/camera.hpp
index 7405c8c5091dda95b43fdfd44dd6d594b4ff8193..d4da36f3b391b484711960a332ffb06f0bcd4549 100644
--- a/applications/gui2/src/modules/camera.hpp
+++ b/applications/gui2/src/modules/camera.hpp
@@ -77,6 +77,7 @@ private:
 	bool vr_=false;
 	float last_=0.0f;
 	std::atomic_int16_t nframes_=0;
+	std::atomic_int64_t latency_=0;
 	int update_fps_freq_=30; // fps counter update frequency (frames)
 
 	ftl::data::FrameSetPtr current_fs_;
diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp
index 02d0af71f4d46ebf7a2a6a196b46d3ffdf51d98a..040f8a1dae03d2f0f87bc34d54c022ab41845bf3 100644
--- a/components/codecs/include/ftl/codecs/packet.hpp
+++ b/components/codecs/include/ftl/codecs/packet.hpp
@@ -59,9 +59,8 @@ struct StreamPacketV4 {
 
 	inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; }
 	inline size_t frameSetID() const { return (version >= 4) ? streamID : 0; }
-	inline int64_t localTimestamp() const { return timestamp + originClockDelta; }
 
-	int64_t originClockDelta;  		// Not message packet / saved
+	int64_t localTimestamp;  		// Not message packet / saved
 	unsigned int hint_capability;	// Is this a video stream, for example
 	size_t hint_source_total;		// Number of tracks per frame to expect
 
@@ -86,9 +85,8 @@ struct StreamPacket {
 
 	inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; }
 	inline size_t frameSetID() const { return (version >= 4) ? streamID : 0; }
-	inline int64_t localTimestamp() const { return timestamp + originClockDelta; }
 
-	int64_t originClockDelta;  		// Not message packet / saved
+	int64_t localTimestamp;  		// Not message packet / saved
 	unsigned int hint_capability;	// Is this a video stream, for example
 	size_t hint_source_total;		// Number of tracks per frame to expect
 	int retry_count = 0;			// Decode retry count
diff --git a/components/streams/src/builder.cpp b/components/streams/src/builder.cpp
index 486823cadbaa3192663d758acc96771dd43ebcdb..23249568542140e034a377b1f7eb6d73aa83f74d 100644
--- a/components/streams/src/builder.cpp
+++ b/components/streams/src/builder.cpp
@@ -88,6 +88,7 @@ std::shared_ptr<ftl::data::FrameSet> LocalBuilder::getNextFrameSet(int64_t ts) {
 	// Must lock to ensure no updates can happen here
 	UNIQUE_LOCK(fs->smtx, lk2);
 	fs->changeTimestamp(ts);
+	fs->localTimestamp = ts;
 	fs->store();
 	//for (auto &f : fs->frames) {
 	//	f.store();
@@ -421,6 +422,7 @@ std::shared_ptr<ftl::data::FrameSet> ForeignBuilder::_addFrameset(int64_t timest
 
 	newf->count = 0;
 	newf->mask = 0;
+	newf->localTimestamp = timestamp;
 	newf->clearFlags();
 
 	// Insertion sort by timestamp
diff --git a/components/streams/src/filestream.cpp b/components/streams/src/filestream.cpp
index ad94a6f090d85a66a080ebd7ba88af8477a6b1d0..61db4ce84b78f1f12e84f638c4f6eca1cb1e11ea 100644
--- a/components/streams/src/filestream.cpp
+++ b/components/streams/src/filestream.cpp
@@ -206,6 +206,7 @@ void File::_patchPackets(ftl::codecs::StreamPacket &spkt, ftl::codecs::Packet &p
 	}
 
 	spkt.version = 5;
+	spkt.localTimestamp = spkt.timestamp;
 
 	// Fix for flags corruption
 	if (pkt.data.size() == 0) {
diff --git a/components/streams/src/netstream.cpp b/components/streams/src/netstream.cpp
index 2643b99b307d4806936782ed0e64a8fe415a58e6..f904d66382634a9505c8fdb37e6fd7c21dfe0c11 100644
--- a/components/streams/src/netstream.cpp
+++ b/components/streams/src/netstream.cpp
@@ -169,7 +169,7 @@ bool Net::begin() {
 		StreamPacket spkt = spkt_raw;
 		// FIXME: see #335
 		//spkt.timestamp -= clock_adjust_;
-		spkt.originClockDelta = clock_adjust_;
+		spkt.localTimestamp = now - ttimeoff;
 		spkt.hint_capability = 0;
 		spkt.hint_source_total = 0;
 		//LOG(INFO) << "LATENCY: " << ftl::timer::get_time() - spkt.localTimestamp() << " : " << spkt.timestamp << " - " << clock_adjust_;
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index e1f286c9ed6809f74bc54cbb9c537bbb01562bde..8b59bf6ed1676486d4aa0435e1e3b4fdcd4e7f37 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -209,6 +209,7 @@ void Receiver::_processData(const StreamPacket &spkt, const Packet &pkt) {
 		//UNIQUE_LOCK(vidstate.mutex, lk);
 		timestamp_ = spkt.timestamp;
 		fs->completed(spkt.frame_number);
+		fs->localTimestamp = spkt.localTimestamp;
 	}
 
 	/*const auto *cs = stream_;
@@ -258,6 +259,7 @@ void Receiver::_processAudio(const StreamPacket &spkt, const Packet &pkt) {
 		//UNIQUE_LOCK(vidstate.mutex, lk);
 		timestamp_ = spkt.timestamp;
 		fs->completed(spkt.frame_number);
+		fs->localTimestamp = spkt.localTimestamp;
 	}
 
 	// Generate settings from packet data
@@ -435,6 +437,7 @@ void Receiver::_processVideo(const StreamPacket &spkt, const Packet &pkt) {
 			UNIQUE_LOCK(vidstate.mutex, lk);
 			timestamp_ = spkt.timestamp;
 			fs->completed(spkt.frame_number+i);
+			fs->localTimestamp = spkt.localTimestamp;
 		}
 	}
 }
@@ -458,6 +461,7 @@ void Receiver::processPackets(const StreamPacket &spkt, const Packet &pkt) {
 					//UNIQUE_LOCK(vidstate.mutex, lk);  // FIXME: Should have a lock here...
 					timestamp_ = spkt.timestamp;
 					fs->completed(frame.source());
+					fs->localTimestamp = spkt.localTimestamp;
 				}
 
 				//if (frame.availableAll(sel)) {
diff --git a/components/structures/include/ftl/data/new_frameset.hpp b/components/structures/include/ftl/data/new_frameset.hpp
index 096a02c2f616f404b97d7cbabc82796d5b14141c..ad75c990ba0d6e6822774e257d560d1e6eea28ab 100644
--- a/components/structures/include/ftl/data/new_frameset.hpp
+++ b/components/structures/include/ftl/data/new_frameset.hpp
@@ -38,7 +38,7 @@ class FrameSet : public ftl::data::Frame {
 
 	//int id=0;
 	//int64_t timestamp;				// Millisecond timestamp of all frames
-	int64_t originClockDelta;
+	int64_t localTimestamp;
 	std::vector<Frame> frames;
 	std::atomic<int> count;				// Number of valid frames
 	std::atomic<unsigned int> mask;		// Mask of all sources that contributed
@@ -47,8 +47,6 @@ class FrameSet : public ftl::data::Frame {
 
 	//Eigen::Matrix4d pose;  // Set to identity by default.
 
-	inline int64_t localTimestamp() const { return timestamp() + originClockDelta; }
-
 	inline void set(FSFlag f) { flags_ |= (1 << static_cast<int>(f)); }
 	inline void clear(FSFlag f) { flags_ &= ~(1 << static_cast<int>(f)); }
 	inline bool test(FSFlag f) const { return flags_ & (1 << static_cast<int>(f)); }