# RadioPlayer

RadioPlayer is a Vite + React web app for browsing, playing, and casting radio stations.
It uses a managed catalog from `public/stations.json`, exposes a same-origin managed endpoint at `/api/managed-stations.json`, supports user stations with local persistence, and includes scripts to refresh station data from Radio.si.

## Features

- Station library with search, category tabs, favorites, recents, sorting, and pagination.
- Country filtering plus a country picker to choose which Radio Browser countries are synced and shown.
- Audio playback with previous/next controls, volume/mute, and coverflow quick picks.
- Google Cast and AirPlay output support.
- User station management with local import/export/reset tooling.
- Managed catalog fallback chain: remote endpoint -> cached remote -> bundled `stations.json`.
- Production service worker with app shell caching, station sync cache, and periodic refresh registration.
- PWA install prompt and offline-friendly launch behavior.

## Requirements

- Node.js 18+
- npm
- For production managed endpoint: PHP-enabled web server with rewrite support (Apache + `.htaccess` in this repo)

## Getting Started

Install dependencies:

```bash
npm install
```

Run development server:

```bash
npm run dev
```

Build for production:

```bash
npm run build
```

Preview production build locally:

```bash
npm run preview
```

Deploy built assets with the included script:

```bash
bash sync.sh
```

## Managed Catalog Endpoint

Runtime managed endpoint path:

```text
/api/managed-stations.json
```

In production this is served by:

- `public/.htaccess` rewrite rule
- `public/api/managed-stations.php`

Response shape:

```json
{
  "schemaVersion": 1,
  "updatedAt": "2026-04-29T07:39:58.374Z",
  "stations": []
}
```

In dev/preview, Vite middleware in `vite.config.js` serves the same envelope from `stations.json`.

If the endpoint is unavailable, frontend and service worker fall back to bundled `public/stations.json`.

## Station Data Refresh

Refresh managed stations from Radio.si:

```bash
npm run update:stations
```

Refresh and rebuild in one command:

```bash
npm run update:stations:build
```

Custom source and output:

```bash
node scripts/update-stations.mjs <source-url> <output-path>
```

## Cron Automation (Server)

Helper script:

```bash
bash scripts/cron-refresh-stations.sh
```

Update-only helper script:

```bash
bash scripts/cron-update-stations.sh
```

The script:

- acquires a lock file to prevent overlapping runs,
- runs station refresh and build,
- optionally runs `DEPLOY_CMD`,
- writes logs to `/tmp/radioplayer-refresh-stations.log` by default,
- rotates logs automatically when they exceed 1 MB, keeping 5 archives by default.

The update-only script:

- acquires its own lock file,
- runs only `npm run update:stations`,
- optionally runs `POST_UPDATE_CMD`,
- writes logs to `/tmp/radioplayer-update-stations.log`,
- uses the same log rotation behavior.

Example crontab (every 6 hours):

```cron
0 */6 * * * REPO_DIR=/opt/www/virtual/RadioPlayer DEPLOY_CMD='rsync -av --delete /opt/www/virtual/RadioPlayer/dist/ /opt/www/virtual/RadioPlayer/' /opt/www/virtual/RadioPlayer/scripts/cron-refresh-stations.sh
```

Example update-only crontab (every 2 hours):

```cron
0 */2 * * * REPO_DIR=/opt/www/virtual/RadioPlayer /opt/www/virtual/RadioPlayer/scripts/cron-update-stations.sh
```

## Project Structure

```text
index.html
privacy.html
package.json
public/
  .htaccess
  api/
    managed-stations.php
  data/
    radio-stations.json
    radio-stations-sync.json
  manifest.json
  stations.json
  sw.js
scripts/
  cron-refresh-stations.sh
  cron-update-stations.sh
  import-radio-stations.ts
  update-stations.mjs
src/
  App.jsx
  main.jsx
  player.js
  styles.css
  radio/
  storage/
```

## Notes

- Service worker is only active in production builds. In dev, SW registrations and caches are cleared automatically.
- `src/main.jsx` registers background sync / periodic sync where supported.
- `src/player.js` also refreshes managed catalog on app focus after a timeout (fallback for browsers without periodic sync).
- `sync.sh` deploys `dist/` only.
- If you edit stations manually, rerun `npm run update:stations` to resync from upstream when needed.
