fix(channel): recover a throttled channel banner instead of leaving it broken
The banner URL is valid, but googleusercontent intermittently drops the banner request when the channel page fires the banner + avatar + ~16 thumbnails at once; the browser then negative-caches the miss, so the banner stays broken with no auto-retry (even though the same URL loads fine on a fresh request). On error we now retry a few times with a ?r= cache-buster (a fresh URL that bypasses the negative cache), and only fall back to the blank bar if it keeps failing (a genuinely dead URL). Fixes the broken-banner on channel pages like BASSHUNTER.
This commit is contained in:
parent
eef0e870d4
commit
69bbdb15d2
1 changed files with 18 additions and 2 deletions
|
|
@ -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<string | null | undefined>(undefined);
|
||||
|
|
@ -197,7 +205,7 @@ export default function ChannelPage({
|
|||
<main className="flex-1 min-w-0 overflow-y-auto [scrollbar-gutter:stable]">
|
||||
{/* Banner + back — OPTION C: inset, rounded card (not full-bleed) */}
|
||||
<div className="relative px-4 sm:px-6 pt-3">
|
||||
{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" }}
|
||||
>
|
||||
<img
|
||||
src={`${ch.banner_url}=w1707`}
|
||||
// key forces a fresh <img> 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));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue