channel groups

This commit is contained in:
Simon
2026-06-18 11:19:15 +00:00
parent 08c96d5903
commit 1b6fa5f924
4 changed files with 170 additions and 18 deletions

View File

@@ -137,12 +137,91 @@ App.videos = App.videos || {};
});
};
// Each channel in a group sends back a different number of videos per
// page, so a small per-channel count keeps any one channel from
// dominating a single interleaved batch.
const GROUP_CHANNEL_PAGE_SIZE = 4;
// Fetches one page from every channel in a group and zips the results
// together round-robin so the feed alternates between sources instead of
// running through one channel's videos before moving to the next.
App.videos.loadGroupVideos = async function(session) {
const group = session.channel;
const searchInput = document.getElementById('search-input');
const query = searchInput ? searchInput.value : "";
if (!state.groupCursors || state.groupCursors.groupId !== group.id || state.groupCursors.query !== query) {
state.groupCursors = {
groupId: group.id,
query: query,
channels: group.channelIds.map((id) => ({ id, page: 1, hasNextPage: true }))
};
}
const active = state.groupCursors.channels.filter((cursor) => cursor.hasNextPage);
if (active.length === 0) {
state.hasNextPage = false;
App.videos.updateLoadMoreState();
return;
}
try {
state.isLoading = true;
App.videos.updateLoadMoreState();
state.currentLoadController = new AbortController();
const results = await Promise.all(active.map(async (cursor) => {
const response = await fetch('/api/videos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
channel: cursor.id,
query: query || "",
page: cursor.page,
perPage: GROUP_CHANNEL_PAGE_SIZE,
server: session.server
}),
signal: state.currentLoadController.signal
});
const data = await response.json();
const items = data && Array.isArray(data.items) ? data.items : [];
cursor.page++;
cursor.hasNextPage = items.length > 0 && (data && data.pageInfo ? data.pageInfo.hasNextPage !== false : true);
return items;
}));
const interleaved = [];
const maxLen = results.reduce((max, items) => Math.max(max, items.length), 0);
for (let i = 0; i < maxLen; i++) {
results.forEach((items) => {
if (items[i]) interleaved.push(items[i]);
});
}
App.videos.renderVideos({ items: interleaved });
state.hasNextPage = state.groupCursors.channels.some((cursor) => cursor.hasNextPage);
App.videos.ensureViewportFilled();
} catch (err) {
if (err.name !== 'AbortError') {
console.error("Failed to load group videos:", err);
}
} finally {
state.isLoading = false;
state.currentLoadController = null;
App.videos.updateLoadMoreState();
}
};
// Fetches the next page of videos and renders them into the grid.
App.videos.loadVideos = async function() {
const session = App.storage.getSession();
if (!session) return;
if (!session || !session.channel) return;
if (state.isLoading || !state.hasNextPage) return;
if (session.channel.isGroup) {
return App.videos.loadGroupVideos(session);
}
const searchInput = document.getElementById('search-input');
const query = searchInput ? searchInput.value : "";
@@ -338,6 +417,7 @@ App.videos = App.videos || {};
state.hasNextPage = true;
state.renderedVideoIds.clear();
state.loadedVideos = [];
state.groupCursors = null;
const grid = document.getElementById('video-grid');
if (grid) grid.innerHTML = "";
if (App.feed && typeof App.feed.reset === 'function') {
@@ -357,6 +437,7 @@ App.videos = App.videos || {};
state.hasNextPage = true;
state.renderedVideoIds.clear();
state.loadedVideos = [];
state.groupCursors = null;
const grid = document.getElementById('video-grid');
if (grid) grid.innerHTML = "";
if (App.feed && typeof App.feed.reset === 'function') {