fix(twitch): token partagé + timeouts + fan-out réduit sur Home/Explore
CI / build-and-test (push) Successful in 14m10s
CI / build-and-test (push) Successful in 14m10s
This commit is contained in:
@@ -14,6 +14,7 @@ import { ChannelIdentityComponent } from '../../app/shared/components/channel-id
|
||||
import { InfiniteAnchorComponent } from '../shared/infinite-anchor/infinite-anchor.component';
|
||||
import { classifyVideo, isLiveVideo, isShortVideo } from '../../app/shared/utils/video-kind';
|
||||
import { map, of, switchMap, catchError, forkJoin, Observable } from 'rxjs';
|
||||
import { timeout } from 'rxjs/operators';
|
||||
import { PagedResult } from '../../services/youtube-api.service';
|
||||
|
||||
const TWITCH_THEME_KEYWORDS: Record<string, string[]> = {
|
||||
@@ -316,17 +317,27 @@ export class ProviderThemePageComponent implements OnDestroy {
|
||||
this.cdr.markForCheck();
|
||||
};
|
||||
|
||||
// Une section lente ne doit jamais bloquer les autres (forkJoin
|
||||
// échoue dès qu'un appel erreur : chaque section est bornée à 25 s
|
||||
// et retombe sur une page vide, le filet top-streams suit).
|
||||
const bounded = (call: Observable<PagedResult<Video>>): Observable<PagedResult<Video>> =>
|
||||
call.pipe(
|
||||
timeout(25000),
|
||||
catchError(() => of({ items: [], nextCursor: null } as PagedResult<Video>))
|
||||
);
|
||||
|
||||
const fetchAll = (gameId: string | null) => {
|
||||
const calls: Record<string, Observable<PagedResult<Video>>> = {};
|
||||
if (wantLive) {
|
||||
calls['live'] = gameId
|
||||
const liveCall = gameId
|
||||
? this.api.getTwitchGameStreamsPage(gameId, this.twitchCursors.live || undefined, 12).pipe(
|
||||
catchError(() => this.api.getTwitchTopStreamsPage(this.twitchCursors.live || undefined)))
|
||||
: this.api.getTwitchTopStreamsPage(this.twitchCursors.live || undefined);
|
||||
calls['live'] = bounded(liveCall);
|
||||
}
|
||||
if (wantVod) calls['vod'] = this.api.searchTwitchVodsPage(query, this.twitchCursors.vod || undefined);
|
||||
if (wantClip) calls['clip'] = this.api.searchTwitchClipsPage(query, this.twitchCursors.clip || undefined);
|
||||
if (wantChannel) calls['channel'] = this.api.searchTwitchAllChannelsPage(query, this.twitchCursors.channel || undefined, 20);
|
||||
if (wantVod) calls['vod'] = bounded(this.api.searchTwitchVodsPage(query, this.twitchCursors.vod || undefined));
|
||||
if (wantClip) calls['clip'] = bounded(this.api.searchTwitchClipsPage(query, this.twitchCursors.clip || undefined));
|
||||
if (wantChannel) calls['channel'] = bounded(this.api.searchTwitchAllChannelsPage(query, this.twitchCursors.channel || undefined, 20));
|
||||
if (Object.keys(calls).length === 0) {
|
||||
this.loading.set(false);
|
||||
this.loadingMore.set(false);
|
||||
@@ -359,8 +370,13 @@ export class ProviderThemePageComponent implements OnDestroy {
|
||||
};
|
||||
|
||||
if (firstPage) {
|
||||
// Première page : résoudre la catégorie Twitch pour des lives thématiques.
|
||||
this.api.searchTwitchCategory(query).subscribe({
|
||||
// Première page : résoudre la catégorie Twitch pour des lives
|
||||
// thématiques, SANS bloquer l'affichage (4 s max, la recherche de
|
||||
// catégorie séquentielle prenait plusieurs secondes à elle seule).
|
||||
this.api.searchTwitchCategory(query).pipe(
|
||||
timeout(4000),
|
||||
catchError(() => of(null))
|
||||
).subscribe({
|
||||
next: (game) => {
|
||||
if (snapshotVersion !== this.version) return;
|
||||
if (game) this.twitchGame.set(game);
|
||||
|
||||
@@ -219,15 +219,33 @@ export class ThemePageComponent {
|
||||
if (block.provider === 'twitch') {
|
||||
const raw = tokens.join(' ').trim();
|
||||
const q = (raw.split(/[^A-Za-z0-9à-ÿ]+/).filter(w => w.length >= 3)[0] || raw).trim();
|
||||
const show = (items: Video[]) => {
|
||||
block.items = (items || []).slice(0, max);
|
||||
block.loading = false;
|
||||
if (!block.items.length) console.info('[ThemePage] Empty block page', { provider: block.provider, theme: slug, query: q });
|
||||
this.blocks.update(a => [...a]);
|
||||
};
|
||||
const fail = () => { block.error = 'Erreur de chargement'; block.loading = false; this.blocks.update(a => [...a]); };
|
||||
const call = q ? this.api.searchTwitchThemePage(q, null) : this.api.getTwitchTopStreamsPage(null);
|
||||
call.subscribe({
|
||||
next: res => {
|
||||
block.items = (res.items || []).slice(0, max);
|
||||
block.loading = false;
|
||||
if (!block.items.length) console.info('[ThemePage] Empty block page', { provider: block.provider, theme: slug, query: q });
|
||||
this.blocks.update(a => [...a]);
|
||||
// Thème vide malgré les replis : dernière chance top streams live.
|
||||
if (!(res.items || []).length && q) {
|
||||
this.api.getTwitchTopStreamsPage(null).subscribe({
|
||||
next: top => show(top.items || []),
|
||||
error: () => fail(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
show(res.items || []);
|
||||
},
|
||||
error: () => { block.error = 'Erreur de chargement'; block.loading = false; this.blocks.update(a => [...a]); }
|
||||
error: () => {
|
||||
// Erreur (timeout 30 s…) : une tentative top streams avant l'erreur.
|
||||
this.api.getTwitchTopStreamsPage(null).subscribe({
|
||||
next: top => { if ((top.items || []).length) show(top.items || []); else fail(); },
|
||||
error: () => fail(),
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user