diff --git a/frontend/src/components/ChannelPage.tsx b/frontend/src/components/ChannelPage.tsx index 25aaf63..6eb976c 100644 --- a/frontend/src/components/ChannelPage.tsx +++ b/frontend/src/components/ChannelPage.tsx @@ -68,12 +68,20 @@ export default function ChannelPage({ const qc = useQueryClient(); const confirm = useConfirm(); const [tab, setTab] = useState<"videos" | "about">("videos"); + // The banner URL is valid, but googleusercontent intermittently throttles it when the channel + // page fires the banner + avatar + ~16 thumbnails in one burst — the request is dropped and the + // browser then NEGATIVE-CACHES the miss, so the banner stays broken even though a retry would + // succeed. So on error we retry a few times with a cache-buster (forcing a fresh request), and + // only fall back to the blank bar if it keeps failing (a genuinely dead URL — rare). + const MAX_BANNER_RETRIES = 3; + const [bannerAttempt, setBannerAttempt] = useState(0); const detail = useQuery({ queryKey: ["channel", channelId], queryFn: () => api.channelDetail(channelId), }); const ch = detail.data; + useEffect(() => setBannerAttempt(0), [ch?.banner_url]); // fresh channel/banner → try again // Background auto-ingest ("explore") of an un-subscribed channel's uploads. const [nextToken, setNextToken] = useState(undefined); @@ -197,7 +205,7 @@ export default function ChannelPage({
{/* Banner + back — OPTION C: inset, rounded card (not full-bleed) */}
- {ch?.banner_url ? ( + {ch?.banner_url && bannerAttempt <= MAX_BANNER_RETRIES ? ( // Match YouTube's banner: the stored bannerExternalUrl is the full 16:9 template at a // low default size, so request a crisp wide version (=w1707) and object-cover the // desktop "safe area" — the centre 2560×423 (~6:1) band. @@ -206,9 +214,17 @@ export default function ChannelPage({ style={{ aspectRatio: "2560 / 423", maxHeight: "150px" }} > per attempt; the ?r= cache-buster on a retry bypasses the + // browser's negative cache (googleusercontent accepts an extra query param). + key={bannerAttempt} + src={`${ch.banner_url}=w1707${bannerAttempt ? `?r=${bannerAttempt}` : ""}`} alt="" className="w-full h-full object-cover object-center" + onError={() => { + if (bannerAttempt <= MAX_BANNER_RETRIES) { + window.setTimeout(() => setBannerAttempt((a) => a + 1), 500 * (bannerAttempt + 1)); + } + }} />
) : (