fix: parallel ICY polling with batch size 10 and 5s timeout

This commit is contained in:
nk
2026-06-02 20:15:15 +03:00
parent 6b2e02f6c0
commit d0874ae9db

View File

@@ -17,41 +17,58 @@ export class IcyNowPlayingService {
this.logger.log('Starting ICY now playing poll...'); this.logger.log('Starting ICY now playing poll...');
const stations = await this.prisma.station.findMany({ const stations = await this.prisma.station.findMany({
where: { recordStationId: null, isOnline: true }, where: { recordStationId: null, isOnline: true },
take: 50,
}); });
for (const station of stations) { let successCount = 0;
try {
const track = await this.parseIcyMetadata(station.streamUrl);
if (!track) continue;
const updated = await this.prisma.nowPlaying.upsert({ for (let i = 0; i < stations.length; i += 10) {
where: { stationId: station.id }, const batch = stations.slice(i, i + 10);
create: { const results = await Promise.allSettled(
stationId: station.id, batch.map(async (station) => {
const track = await this.parseIcyMetadata(station.streamUrl);
if (!track) return null;
const updated = await this.prisma.nowPlaying.upsert({
where: { stationId: station.id },
create: {
stationId: station.id,
song: track.song,
artist: track.artist,
coverUrl: null,
},
update: {
song: track.song,
artist: track.artist,
coverUrl: null,
},
});
this.gateway.broadcastNowPlaying(station.stationId.toString(), {
song: track.song, song: track.song,
artist: track.artist, artist: track.artist,
coverUrl: null, coverUrl: null,
}, updatedAt: updated.updatedAt,
update: { });
song: track.song, return track;
artist: track.artist, }),
coverUrl: null, );
},
});
this.gateway.broadcastNowPlaying(station.stationId.toString(), { for (let j = 0; j < results.length; j++) {
song: track.song, const result = results[j];
artist: track.artist, if (result.status === 'fulfilled' && result.value) {
coverUrl: null, successCount++;
updatedAt: updated.updatedAt, } else if (result.status === 'rejected') {
}); this.logger.warn(
} catch (error) { `ICY failed for ${batch[j].name}: ${result.reason?.message || result.reason}`,
this.logger.warn( );
`ICY failed for ${station.name}: ${error.message}`, }
);
} }
} }
this.logger.log(`ICY poll complete for ${stations.length} stations`);
this.logger.log(
`ICY poll complete: ${successCount}/${stations.length} stations updated`,
);
} }
private async parseIcyMetadata( private async parseIcyMetadata(
@@ -59,7 +76,7 @@ export class IcyNowPlayingService {
): Promise<{ artist: string; song: string } | null> { ): Promise<{ artist: string; song: string } | null> {
const response = await fetch(url, { const response = await fetch(url, {
headers: { 'Icy-MetaData': '1' }, headers: { 'Icy-MetaData': '1' },
signal: AbortSignal.timeout(10000), signal: AbortSignal.timeout(5000),
}); });
const metaintHeader = response.headers.get('icy-metaint'); const metaintHeader = response.headers.get('icy-metaint');