perf: batch video updates in subscribeVideos to reduce UI thrashing

Updates `NostrClient.subscribeVideos` to buffer incoming events and invoke the `onVideo` callback once per flush cycle with an array of updates, instead of once per video. This significantly reduces redundant sorting, filtering, and persistence operations in `NostrService`.

Also optimizes `populateNip71MetadataForVideos` to run as a single batched request per flush cycle.

BREAKING CHANGE: `subscribeVideos` callback now receives `Video[]` instead of `Video`. All internal consumers have been updated.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-02 17:04:55 +00:00
1 parent 1ef2db012e
commit 1e53070717
3 files changed
+34 -19

No files matched your search

+21 -13
View File
@@ -7750,6 +7750,7 @@ export class NostrClient {
const toProcess = eventBuffer;
eventBuffer = [];
const updatedVideos = [];
for (const evt of toProcess) {
try {
@@ -7805,25 +7806,32 @@ export class NostrClient {
const prevActive = this.activeMap.get(activeKey);
if (!prevActive || video.created_at > prevActive.created_at) {
this.activeMap.set(activeKey, video);
onVideo(video); // Trigger the callback that re-renders
// Fetch NIP-71 metadata (categorization tags) in the background
this.populateNip71MetadataForVideos([video])
.then(() => {
this.applyRootCreatedAt(video);
})
.catch((error) => {
devLogger.warn(
"[nostr] Failed to hydrate NIP-71 metadata for live video:",
error
);
});
updatedVideos.push(video);
}
} catch (err) {
devLogger.error("[subscribeVideos] Error processing event:", err);
}
}
if (updatedVideos.length > 0) {
// Trigger the callback once per batch to avoid UI thrashing
onVideo(updatedVideos);
// Fetch NIP-71 metadata (categorization tags) in the background for the whole batch
this.populateNip71MetadataForVideos(updatedVideos)
.then(() => {
for (const video of updatedVideos) {
this.applyRootCreatedAt(video);
}
})
.catch((error) => {
devLogger.warn(
"[nostr] Failed to hydrate NIP-71 metadata for live video batch:",
error
);
});
}
// Persist processed events after each flush so reloads warm quickly.
this.saveLocalData("subscribeVideos:flush");
};
+8 -4
View File
@@ -196,8 +196,10 @@ await (async function testDeleteFlowPublishesDeletionFlag() {
},
};
client.subscribeVideos((videoUpdate) => {
seenUpdates.push(videoUpdate);
client.subscribeVideos((videos) => {
if (Array.isArray(videos)) {
seenUpdates.push(...videos);
}
});
const deleteEvent = {
@@ -300,8 +302,10 @@ await (async function testSubscribeVideosSkipsOlderThanTombstone() {
},
};
client.subscribeVideos((videoUpdate) => {
seenUpdates.push(videoUpdate);
client.subscribeVideos((videos) => {
if (Array.isArray(videos)) {
seenUpdates.push(...videos);
}
});
const pubkey = "PUBKEY789";
+5 -2
View File
@@ -148,8 +148,11 @@ describe("NostrClient", () => {
client.pool.sub.mock.mockImplementation(() => subMock);
let capturedVideo = null;
const sub = client.subscribeVideos((video) => {
capturedVideo = video;
const sub = client.subscribeVideos((videos) => {
// Callback receives an array of updates
if (Array.isArray(videos) && videos.length > 0) {
capturedVideo = videos[0];
}
});
assert.ok(sub);