import { Injectable, DestroyRef, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { interval, startWith, Subscription, switchMap } from 'rxjs'; import { GameApiService } from './game-api.service'; import { GameStreamEvent } from '../models/game.models'; @Injectable({ providedIn: 'root' }) export class GameStreamService { private readonly gameApi = inject(GameApiService); private readonly destroyRef = inject(DestroyRef); private streamSubscription: Subscription | null = null; private pollSubscription: Subscription | null = null; private lastGameStateHash: string | null = null; startStreaming( gameId: string, onEvent: (event: GameStreamEvent) => void, onStreamError: () => void ): void { this.streamSubscription = this.gameApi .streamGame(gameId) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: (event) => { this.lastGameStateHash = JSON.stringify(event); onEvent(event); }, error: () => { onStreamError(); this.startPolling(gameId, onEvent); }, complete: () => { onStreamError(); this.startPolling(gameId, onEvent); } }); } startPolling(gameId: string, onEvent: (event: GameStreamEvent) => void): void { if (this.pollSubscription) { return; } this.pollSubscription = interval(5000) .pipe( startWith(0), switchMap(() => this.gameApi.getGame(gameId)), takeUntilDestroyed(this.destroyRef) ) .subscribe({ next: (game) => { // Only emit if game state changed to avoid unnecessary updates const stateHash = JSON.stringify(game.state); if (this.lastGameStateHash !== stateHash) { this.lastGameStateHash = stateHash; const event: GameStreamEvent = { type: 'gameFull', game }; onEvent(event); } } }); } cleanup(): void { this.streamSubscription?.unsubscribe(); this.pollSubscription?.unsubscribe(); this.streamSubscription = null; this.pollSubscription = null; this.lastGameStateHash = null; } }