A modern, feature-rich internet radio player built with Next.js 16 and React 19. Stream 40,000+ stations with real-time lyrics, audio visualizers, a 5-band equalizer, podcast support, and audiobooks β all in your browser.
Features β’ Getting Started β’ Architecture β’ Contributing β’ Roadmap
- 40,000+ stations from Radio Browser API β browse by genre, country, trending, or local
- ICY metadata extraction β real-time "Now Playing" track info from stream headers
- Station queue β queue up stations and move between them seamlessly
- Stall recovery β automatic retry logic when streams buffer or drop
- Media Session API β OS-level controls (lock screen, headphone buttons, media keys)
- Wake Lock β prevents screen from sleeping during playback
- Sleep timer β auto-stop playback after a configurable duration
- Album artwork β automatic lookup via iTunes Search API with strict Jaro-distance matching and graceful fallback
- Audio visualizers β ferrofluid, spiral, and circular renderers using Web Audio FFT analysis
- Audio-reactive background β dynamic background that pulses with the music (works with or without audio effects enabled β uses a synthesized ambient pulse as fallback)
- Theater mode β immersive full-screen playback with parallax album art background
- Concert ticker β animated scrolling banner showing upcoming Bandsintown shows in theater mode
- Liquid Glass UI β Aerolab-inspired liquid glass buttons with SVG distortion filter, specular lighting, and frosted tint layers on play controls and language selector
- Glassmorphism UI β macOS-inspired dark theme with frosted glass surfaces
- 5-band parametric equalizer β Low (60Hz), Lo-Mid (230Hz), Mid (910Hz), Hi-Mid (3.6kHz), High (14kHz)
- 8 built-in presets β Flat, Bass Boost, Treble, V-Shape, Vocal, Rock, Electronic, Acoustic
- Custom presets β save and name your own EQ configurations
- Persistent EQ preset β selected preset name persists across sessions
- Seamless effects toggle β enable/disable audio enhancements without stream interruption (always-proxy architecture)
- Volume control with mute toggle
- Synced lyrics (LRC format) with auto-scrolling and highlighted current line
- Plain text lyrics fallback when synced version unavailable
- Realtime STT lyrics sync β on-device speech recognition aligns lyrics in real time when timestamps are unavailable
- Local caching for instant re-display on revisit
- Powered by LrcLib API
- Multi-language UI β locale-aware routing via
[countryCode]segments - Language selector β switch UI language in-app
- Country-based defaults β auto-detects preferred stations and language from locale
- Favorites β star stations for quick access
- Favorite songs β bookmark tracks you love
- Recent stations β last 15 played stations
- Play history β up to 100 entries with artist, track, and artwork
- Artist detail modal β view artist biography, upcoming Bandsintown concerts, and lyrics from history/favorites
- Social share β share current station or track via Web Share API (or clipboard fallback) from cards, detail modals, theater mode, and the now-playing bar
- Uniform card layout β all cards share the same height with station name anchored at the bottom
- Group by artist/album β horizontal scrollable thumbnail row for grouped tracks
- Adapts from desktop (sidebar + main) to tablet and mobile layouts
- Touch-friendly controls throughout
- Mobile lyrics reel β swipeable lyrics experience on small screens
- Single-row genre/country chips β horizontally scrollable with a dropdown to access all genres and countries
- Desktop stats modal β floating centered dialog instead of bottom sheet on desktop viewports
- Dev API console β in-browser scrollable log of all ICY, iTunes, Lyrics, and Bandsintown API requests (development mode only, visible in theater mode)
- Node.js 18.17 or later
- npm, yarn, pnpm, or bun
# Clone the repository
git clone https://github.com/CMolG/pulse-radio.git
cd pulse-radio
# Install dependencies
npm install
# Start the development server
npm run devOpen http://localhost:3000 to start listening.
npm run build
npm startcp .env.example .env.local| Variable | Required | Description |
|---|---|---|
CRON_SECRET |
Yes (prod) | Secures /api/cron/sync endpoint. Generate with openssl rand -hex 32 |
BANDSINTOWN_APP_ID |
No | Bandsintown API key for concert data. Falls back to demo key |
NODE_ENV |
Auto | Set by Next.js (development / production / test) |
src/
βββ app/ # Next.js App Router
β βββ [countryCode]/ # Locale-aware routing
β β βββ layout.tsx
β β βββ page.tsx
β β βββ head.tsx
β β βββ not-found.tsx
β βββ api/
β β βββ proxy-stream/ # CORS proxy for audio streams
β β βββ icy-meta/ # ICY metadata extraction endpoint
β β βββ itunes/ # Album artwork lookup proxy
β β βββ artist-info/ # Artist biography/info proxy
β β βββ concerts/ # Bandsintown concert data proxy
β β βββ lyrics/ # Lyrics fetching endpoint
β β βββ health/ # Health check endpoint
β β βββ analytics/ # Analytics endpoint
β β βββ cron/ # Scheduled sync jobs
β β βββ station-health/ # Station reliability scoring
β βββ layout.tsx # Root layout (fonts, metadata)
β βββ page.tsx # Home page β <Radio />
β βββ sitemap.ts # Dynamic sitemap generation
β βββ ServiceWorkerRegistrar.tsx
β βββ globals.css # Tailwind + theme variables
β
βββ components/radio/ # Main radio player
β βββ RadioShell.tsx # Top-level container & layout
β βββ components/ # UI subcomponents
β β βββ StationCard.tsx # Station list item
β β βββ NowPlayingBar.tsx # Bottom playback bar
β β βββ NowPlayingHero.tsx # Main "now playing" display
β β βββ BrowseView.tsx # Genre/country browser
β β βββ EqPanel.tsx # Equalizer interface
β β βββ LyricsPanel.tsx # Lyrics display
β β βββ MobileLyricsReel.tsx # Mobile lyrics experience
β β βββ LanguageSelector.tsx # UI language switcher
β β βββ KeyboardShortcutsHelp.tsx
β β βββ Sidebar.tsx # Navigation sidebar
β β βββ TheaterView.tsx # Full-screen theater mode
β βββ hooks/ # Custom React hooks
β β βββ useRadio.ts # Core playback engine
β β βββ useEqualizer.ts # Web Audio API EQ chain
β β βββ useStationMeta.ts # ICY metadata polling
β β βββ useLyrics.ts # Lyrics fetching & caching
β β βββ useRealtimeLyricsSync.ts # STT-based realtime lyrics sync
β β βββ useFavorites.ts # Favorite stations (localStorage)
β β βββ useFavoriteSongs.ts # Favorite songs (localStorage)
β β βββ useRecent.ts # Recent stations
β β βββ useHistory.ts # Play history tracker
β β βββ useMediaSession.ts # Browser media session
β β βββ useStationQueue.ts # Station queue management
β β βββ useSleepTimer.ts # Auto-stop sleep timer
β β βββ useWakeLock.ts # Screen wake lock
β β βββ usePlaybackPosition.ts
β β βββ useArtistInfo.ts # Artist biography lookup
β β βββ useAudioReactiveBackground.ts
β β βββ useParallaxBg.ts
β βββ services/ # External API clients
β β βββ radioApi.ts # Radio Browser API
β β βββ lyricsApi.ts # LrcLib API
β β βββ lyricsAligner.ts # Incremental lyrics alignment
β β βββ realtimeSpeechRecognition.ts # STT engine wrapper
β β βββ realtimeLyricsTypes.ts
β β βββ archiveApi.ts # Internet Archive API
β β βββ librivoxApi.ts # LibriVox API
β β βββ podcastApi.ts # Podcast RSS parsing
β βββ types.ts # TypeScript type definitions
β βββ constants.ts # Genres, EQ presets, storage keys
β βββ lrcParser.ts # LRC lyrics format parser
β βββ lyricsUtils.ts # Lyrics utility functions
β
βββ context/
β βββ LocaleContext.tsx # i18n locale provider
β
βββ lib/ # Shared utilities
βββ audio-visualizer/ # Canvas-based visualizations
β βββ useAudioAnalyser.ts # FFT frequency analysis hook
β βββ useAlbumArt.ts # iTunes artwork with Jaro-distance matching
β βββ FerrofluidRenderer.tsx
β βββ SpiralRenderer.tsx
β βββ CircularRenderer.tsx
βββ i18n/ # Internationalization
β βββ locales.ts # Supported locales
β βββ messages.ts # Translation strings
β βββ countries.ts # Country metadata
β βββ countryChips.ts # Country filter chips
β βββ countryDefaults.ts # Per-country defaults
β βββ languageMap.ts # Language β locale mapping
β βββ localeStorage.ts # Locale persistence
βββ playbackStore.ts # Zustand global state
βββ storageUtils.ts # localStorage helpers
| Layer | Technology |
|---|---|
| Framework | Next.js 16 with React 19 & React Compiler |
| Language | TypeScript 5 (strict mode) |
| Styling | Tailwind CSS 4 |
| State | Zustand 5 |
| Animation | Motion (Framer Motion) 12 |
| Audio | Web Audio API (biquad filters, FFT analysis) |
| Speech | Web Speech API (SpeechRecognition) |
| Icons | Lucide React |
| API | Purpose |
|---|---|
| Radio Browser | Station discovery (40K+ stations) |
| LrcLib | Synced & plain text lyrics |
| iTunes Search | Album artwork lookup |
| Bandsintown | Upcoming concert/tour dates |
We welcome contributions of all kinds! See CONTRIBUTING.md for the full guide covering setup, testing, code style, and architecture.
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes
- Test locally:
npm run build && npx playwright test --project=mobile-chrome - Lint:
npm run lint - Commit with a descriptive message:
git commit -m "feat: add sleep timer" - Push to your fork:
git push origin feat/my-feature - Open a Pull Request
We use Conventional Commits:
feat: New feature
fix: Bug fix
docs: Documentation only
style: Formatting, no code change
refactor: Code change that neither fixes a bug nor adds a feature
perf: Performance improvement
test: Adding or updating tests
chore: Build process or tooling changes
Look for issues labeled good first issue β these are beginner-friendly tasks that are a great starting point.
- The CORS proxy (
/api/proxy-stream) is required because browsers block cross-origin audio streams for Web Audio API analysis - ICY metadata is extracted server-side since browsers can't read ICY headers directly
- The equalizer uses a chain of
BiquadFilterNodes in a Web Audio graph - Lyrics are cached in
localStorageto avoid redundant API calls - The realtime STT engine resets its restart counter on every successful recognition result;
MAX_RESTARTSmeans consecutive failures, not total restarts
- Sleep timer
- Keyboard shortcuts
- i18n / localization
- Realtime STT lyrics sync
- Station queue
- Bandsintown concert integration (theater mode ticker + artist detail modal)
- Audio-reactive background (with analyser fallback)
- Persistent EQ preset selection
- Always-proxy audio pipeline (no stream interruptions)
- Liquid Glass UI (Aerolab-style buttons)
- Social share (Web Share API + clipboard fallback)
- Dev API console (development mode)
- Podcast support (search & RSS playback)
- Audiobook support (LibriVox + Internet Archive)
- Station search with fuzzy matching
- Chromecast / AirPlay support
- Shared playlists
This project is licensed under the Apache License 2.0.
Built with β€οΈ and way too many radio stations