Files
RadioPlayerWeb/.vscode/agents/radio-player-stations-importer_v1.md

8.9 KiB

RadioPlayer Stations Importer v1

Goal

Build a production-ready radio station importer for our web RadioPlayer.

The importer must use the public Radio Browser API as the source for radio stations and generate a clean local station dataset that the RadioPlayer can use.

We need stations for these countries:

  • Austria
  • Croatia
  • Serbia
  • Montenegro
  • Bosnia & Herzegovina
  • Germany
  • United Kingdom
  • Italy
  • France
  • Spain
  • USA
  • Canada
  • Australia
  • Luxembourg
  • Netherlands
  • Sweden
  • Switzerland
  • Hungary
  • Czechia
  • Poland

The importer must also include station logos when available.


Important Context

Radio Browser station objects can contain:

  • stationuuid
  • name
  • url
  • url_resolved
  • homepage
  • favicon
  • country
  • countrycode
  • language
  • tags
  • codec
  • bitrate
  • votes
  • clickcount

For our app:

  • url_resolved should be preferred over url
  • favicon should be used as the station logo
  • broken/missing logos must not break the UI
  • HTTP streams should be avoided for browser compatibility
  • HTTPS streams should be preferred
  • broken streams should be filtered out where possible

Countries

Create a shared country list:

export const radioCountries = [
  { name: "Austria", code: "AT" },
  { name: "Croatia", code: "HR" },
  { name: "Serbia", code: "RS" },
  { name: "Montenegro", code: "ME" },
  { name: "Bosnia & Herzegovina", code: "BA" },
  { name: "Germany", code: "DE" },
  { name: "United Kingdom", code: "GB" },
  { name: "Italy", code: "IT" },
  { name: "France", code: "FR" },
  { name: "Spain", code: "ES" },
  { name: "USA", code: "US" },
  { name: "Canada", code: "CA" },
  { name: "Australia", code: "AU" },
  { name: "Luxembourg", code: "LU" },
  { name: "Netherlands", code: "NL" },
  { name: "Sweden", code: "SE" },
  { name: "Switzerland", code: "CH" },
  { name: "Hungary", code: "HU" },
  { name: "Czechia", code: "CZ" },
  { name: "Poland", code: "PL" },
] as const;

Required Output Format

Normalize every imported station to this structure:

export type RadioStation = {
  id: string;
  name: string;
  country: string;
  countryCode: string;
  language: string | null;
  tags: string[];
  codec: string | null;
  bitrate: number | null;
  streamUrl: string;
  homepage: string | null;
  logoUrl: string | null;
  votes: number;
  clickcount: number;
  source: "radio-browser";
  sourceStationUuid: string;
};

Rules:

  • id must use stationuuid
  • sourceStationUuid must also store stationuuid
  • streamUrl must use url_resolved || url
  • logoUrl must use favicon || null
  • tags must be converted from comma-separated string to string array
  • empty strings must become null
  • invalid stations must be skipped
  • duplicate stations must be removed by stationuuid
  • duplicate stream URLs should also be avoided where possible

API Endpoint

Use this endpoint pattern:

https://de1.api.radio-browser.info/json/stations/search

Use these query params:

countrycode={COUNTRY_CODE}
hidebroken=true
is_https=true
order=clickcount
reverse=true
limit=100

Example:

https://de1.api.radio-browser.info/json/stations/search?countrycode=DE&hidebroken=true&is_https=true&order=clickcount&reverse=true&limit=100

Import Requirements

Create an importer script that:

  1. Loops through all configured countries.
  2. Fetches up to 100 stations per country.
  3. Filters invalid stations.
  4. Normalizes station data.
  5. Deduplicates stations.
  6. Sorts stations by country, then clickcount descending.
  7. Saves the final dataset as JSON.
  8. Does not fail the whole import if one country fails.
  9. Logs a summary after import.

Expected output file:

public/data/radio-stations.json

If the project uses a different structure, place it in the closest appropriate public/static data directory and document the chosen path.


Create or adapt these files depending on the current project structure:

src/radio/radioCountries.ts
src/radio/radioTypes.ts
src/radio/radioStationNormalizer.ts
scripts/import-radio-stations.ts
public/data/radio-stations.json

If this is a Vite/React project, this structure is preferred.

If the project already has a different convention, follow the existing convention.


Normalizer Requirements

Create a normalizer function:

export function normalizeRadioBrowserStation(
  station: RadioBrowserStation,
  countryName: string
): RadioStation | null

The function must:

  • return null if station has no stationuuid
  • return null if station has no valid name
  • return null if station has no valid stream URL
  • trim all string values
  • convert empty values to null
  • parse tags safely
  • limit tags to maximum 12 tags per station
  • prefer url_resolved
  • map favicon to logoUrl
  • preserve vote/click metadata

Logo Handling

Station logos come from Radio Browser field:

favicon

Use it as:

logoUrl: station.favicon || null

Frontend must support fallback logo behavior:

<img
  src={station.logoUrl || "/images/radio-placeholder.svg"}
  alt={station.name}
  loading="lazy"
  onError={(event) => {
    event.currentTarget.src = "/images/radio-placeholder.svg";
  }}
/>

Create a fallback placeholder if one does not exist:

public/images/radio-placeholder.svg

The placeholder should be simple and lightweight.


Frontend Integration

Update the RadioPlayer so it can load stations from:

/data/radio-stations.json

Add or update a loader function:

export async function loadRadioStations(): Promise<RadioStation[]> {
  const response = await fetch("/data/radio-stations.json");

  if (!response.ok) {
    throw new Error(`Failed to load radio stations: ${response.status}`);
  }

  return response.json();
}

The UI should support:

  • country filter
  • station search by name
  • station logo
  • station name
  • country
  • tags
  • bitrate/codec where available
  • graceful empty state
  • graceful loading state
  • graceful error state

Player Requirements

When user clicks a station:

  • use streamUrl
  • display station name
  • display logo fallback if logo fails
  • show country
  • show codec/bitrate if available
  • do not crash if playback fails
  • display a user-friendly playback error

Add a script command to package.json:

{
  "scripts": {
    "radio:import": "tsx scripts/import-radio-stations.ts"
  }
}

If tsx is not installed and the project uses TypeScript scripts, add it as a dev dependency.

If the project does not use tsx, use the existing project script runner.


Error Handling

The importer must handle:

  • network errors
  • invalid JSON
  • empty responses
  • country-specific failures
  • missing favicon
  • missing homepage
  • missing language
  • invalid station URLs
  • duplicated stations
  • duplicated stream URLs

Do not stop the whole import because one country fails.

Log errors like:

[radio-import] Failed to import Germany (DE): {error message}

At the end, log:

[radio-import] Imported {total} stations from {successfulCountries}/{totalCountries} countries.
[radio-import] Failed countries: DE, FR
[radio-import] Output: public/data/radio-stations.json

Data Quality Rules

Skip stations when:

  • stationuuid is missing
  • name is missing
  • stream URL is missing
  • stream URL is not HTTPS
  • codec is obviously unsupported by browsers

Preferred codecs:

  • MP3
  • AAC
  • OGG

Do not hard fail on unknown codec, but keep codec in the dataset.


Browser Compatibility

Since this is a web RadioPlayer:

  • prefer HTTPS streams
  • avoid HTTP streams
  • keep fallback image local
  • do not assume every logo loads
  • do not assume every stream can play in every browser
  • do not autoplay without user interaction

Acceptance Criteria

The task is complete when:

  • country list exists
  • importer script exists
  • normalized station type exists
  • radio-stations.json is generated
  • logos are included through logoUrl
  • UI can load the local JSON file
  • UI shows logo fallback on broken/missing logos
  • player can play a selected station
  • import failure for one country does not fail the whole script
  • npm run radio:import or equivalent command works
  • TypeScript build passes
  • lint passes if configured
  • existing app behavior is not broken

Testing Checklist

Manually verify:

  • Import script runs successfully
  • JSON file is generated
  • Germany has stations
  • Austria has stations
  • Croatia has stations
  • USA has stations
  • United Kingdom has stations
  • stations have streamUrl
  • many stations have logoUrl
  • missing logos show fallback
  • clicking a station starts playback
  • broken streams show a friendly error
  • country filtering works
  • search works
  • no console crash happens when logo or stream fails

Notes

Do not manually hardcode hundreds of station stream URLs.

Use Radio Browser as the source of truth for imported stations.

Keep a curated featured station list separate later if needed.