MoqtUniStream expects that MoqtTrackPublisher will not deliver empty object fragments. Fix MoqtRelayTrackPublisher (the only child class that can deliver fragments) to not do that, thus avoiding the QUICHE_BUG OutgoingSubgroupStream_empty_payload.

PiperOrigin-RevId: 955873210
diff --git a/quiche/quic/moqt/moqt_publisher.h b/quiche/quic/moqt/moqt_publisher.h
index f978df2..059bf9f 100644
--- a/quiche/quic/moqt/moqt_publisher.h
+++ b/quiche/quic/moqt/moqt_publisher.h
@@ -83,7 +83,8 @@
   // whenever they are sent.  Once an object is not available via the cache, it
   // can no longer be sent; this ensures that objects are not buffered forever.
   //
-  // This method returns nullopt if the object is not currently available.
+  // This method returns nullopt if the object is not available, or a fragment
+  // at |offset| > 0 is not available.
   // If |subgroup| is nullopt, the object is a datagram.
   virtual std::optional<PublishedObject> GetCachedObject(
       uint64_t group, std::optional<uint64_t> subgroup, uint64_t min_object,
diff --git a/quiche/quic/moqt/moqt_relay_track_publisher.cc b/quiche/quic/moqt/moqt_relay_track_publisher.cc
index 797febe..07dc72d 100644
--- a/quiche/quic/moqt/moqt_relay_track_publisher.cc
+++ b/quiche/quic/moqt/moqt_relay_track_publisher.cc
@@ -331,6 +331,10 @@
     // No object after the last one received.
     return std::nullopt;
   }
+  if (offset > 0 && object_it->second.payload_received() <= offset) {
+    // No new data.
+    return std::nullopt;
+  }
   return object_it->second.ToPublishedObject(offset);
 }
 
diff --git a/quiche/quic/moqt/moqt_relay_track_publisher_test.cc b/quiche/quic/moqt/moqt_relay_track_publisher_test.cc
index e12227d..b2ebd8c 100644
--- a/quiche/quic/moqt/moqt_relay_track_publisher_test.cc
+++ b/quiche/quic/moqt/moqt_relay_track_publisher_test.cc
@@ -575,6 +575,27 @@
   EXPECT_EQ(payload, "0123456789");
 }
 
+// Repro for b/539633547.
+TEST_F(MoqtRelayTrackPublisherTest,
+       GetCachedObjectWithOffsetReturnsNulloptAtEnd) {
+  SubscribeAndOk();
+  Location location = kLargestLocation.Next();
+  uint64_t subgroup = 0;
+  PublishedObjectMetadata metadata = {location, subgroup,
+                                      "",       MoqtObjectStatus::kNormal,
+                                      128,      location.object == 0,
+                                      1000};
+  EXPECT_CALL(listener_,
+              OnNewObjectAvailable(location, Optional(subgroup), 128));
+  publisher_.OnObjectFragment(kTrackName, metadata, std::string(900, 'a'), 0);
+  std::optional<PublishedObject> object =
+      publisher_.GetCachedObject(location.group, subgroup, location.object, 0);
+  ASSERT_TRUE(object.has_value());
+  object = publisher_.GetCachedObject(location.group, subgroup, location.object,
+                                      900);
+  EXPECT_FALSE(object.has_value());
+}
+
 }  // namespace
 
 }  // namespace moqt::test