IFM Radio Listener — Desktop Consumer App
Simple, radio-like listening experience for end users. Open, tune, listen — no accounts, no config.
Overview
| Property | Value |
|---|---|
| Binary | ifm-radio-listener |
| Package | packages/ifm-radio-listener/ |
| Core | ifm-core via Tauri/N-API |
| UI | React + TypeScript (WebView) |
| Platforms | Windows, macOS, Linux (x64 + arm64) |
| Install | curl -fsSL https://ifm.sh/install-radio-listener.sh | bash |
Features
Radio-Like Experience
- Large frequency display — Big, clear frequency number (FM-style:
91.700) - Simple tuning — Click preset, type number, or scan knob
- Instant play — No buffering spinner; cached init segment = immediate audio
- Visual feedback — LIVE indicator, signal strength, hop info (peer/relay/origin)
Discovery
- Frequencies on Air — Live list from mDNS + DHT (no account, no login)
- Auto-refresh — Updates every 10 seconds; pull-to-refresh
- Categories — Music, Talk, News, Sports, Community, Custom
- Favorites — Star stations; appear at top, persist across launches
Protected Frequencies
- Enter access key — Simple dialog: "Enter key for 92.300"
- Key stored securely — OS keychain; never in plaintext config
- One-time entry — Remembered per frequency
Communication
- Chat — Real-time text chat with station and listeners
- Files — Receive files broadcast by station (auto-download to
~/Downloads/IFM/) - Reactions — Quick emoji reactions (👍 ❤️ 🔥 😂)
Background Playback (Critical)
- Continues on window close → minimizes to tray, audio plays
- Continues on screen lock — macOS/Windows/Linux: audio keeps playing
- Media keys — Play/pause, next/prev station work from keyboard/lock screen
- Now Playing — System media session (macOS Now Playing, Windows SMTC, Linux MPRIS)
- Notifications — "Station X is now live" (with permission)
System Integration
- System tray — Quick tune, volume, now playing
- Global hotkeys —
Ctrl+Shift+Spaceplay/pause,Ctrl+Shift+Rightnext favorite - Auto-start — Optional: launch at login, resume last station
- Sleep prevention — Prevents sleep while playing (configurable)
Architecture
User Flow
First Launch
Daily Use
Configuration
Stored in platform config dir:
- macOS:
~/Library/Application Support/ifm-radio-listener/ - Windows:
%APPDATA%\ifm-radio-listener\ - Linux:
~/.config/ifm-radio-listener/
toml
# config.toml
[identity]
private_key = "ed25519..." # Generated on first run
public_key = "ed25519..."
[playback]
volume = 0.8 # 0.0–1.0
auto_play = true # Resume last station on launch
prevent_sleep = true # Keep awake while playing
crossfade_ms = 0 # Future: crossfade between stations
[favorites]
# Auto-managed via UI
"91.700" = { name = "music.lofi", added = "2024-01-15" }
"92.300" = { name = "team.private", added = "2024-01-20", protected = true }
[protected_keys]
# Stored in OS keychain; only references here
"92.300" = { keychain_ref = "ifm-radio-listener/92.300" }
[discovery]
mdns_enabled = true
dht_enabled = true
refresh_interval_sec = 10
bootstrap_peers = [
"/ip4/bootstrap.ifm.sh/tcp/8790/p2p/12D3KooW..."
]
[ui]
theme = "system" # light/dark/system
minimize_to_tray = true
close_to_tray = true # X button → tray, not quit
show_notifications = true
notification_permission = "granted" # requested on first station
[hotkeys]
play_pause = "Ctrl+Shift+Space"
next_favorite = "Ctrl+Shift+Right"
prev_favorite = "Ctrl+Shift+Left"
toggle_window = "Ctrl+Shift+I"
mute = "Ctrl+Shift+M"Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Play/Pause | Ctrl+Shift+Space / Media Play/Pause |
| Next favorite | Ctrl+Shift+Right / Media Next |
| Previous favorite | Ctrl+Shift+Left / Media Previous |
| Mute | Ctrl+Shift+M |
| Volume up | Ctrl+Shift+Up |
| Volume down | Ctrl+Shift+Down |
| Open/close window | Ctrl+Shift+I |
| Search/tune | Ctrl+K / Cmd+K |
| Favorite current | Ctrl+Shift+F / Cmd+Shift+F |
CLI Flags
bash
ifm-radio-listener [OPTIONS]
Options:
--freq <FREQUENCY> Auto-tune to frequency on start
--headless No window, play only (for kiosk/automation)
--config <PATH> Custom config file
--log-level <LEVEL> trace/debug/info/warn/error
--version Print version
--help Show helpDeep Links
bash
# Tune directly from terminal/URL
ifm-radio-listener --freq 91.700
# URL scheme (registered on install)
ifm://tune/91.700
ifm://tune/92.300?key=abc123Development
bash
cd packages/ifm-radio-listener
# Install deps
bun install
# Dev mode (hot reload)
bun run tauri dev
# Build release
bun run tauri build
# Output: src-tauri/target/release/bundle/Project Structure
packages/ifm-radio-listener/
├── src/
│ ├── App.tsx # Main app, routing
│ ├── main.tsx # Entry
│ ├── components/
│ │ ├── Discover/ # Station list, search, categories
│ │ ├── Player/ # Big frequency display, controls
│ │ ├── Chat/ # Chat panel
│ │ ├── Files/ # Received files list
│ │ ├── Favorites/ # Starred stations, history
│ │ ├── ProtectedKey/ # Key entry dialog
│ │ └── Header/ # Brand, tray menu, search
│ ├── hooks/
│ │ useStation.ts # Station tuning, playback
│ │ useDiscovery.ts # mDNS + DHT station list
│ │ useFavorites.ts # Favorites persistence
│ │ useMediaSession.ts # System media integration
│ │ useTray.ts # System tray menu
│ │ useNotifications.ts # Push/local notifications
│ │ useHotkeys.ts # Global hotkey registration
│ │ useKeychain.ts # Protected key storage
│ ├── lib/
│ │ ├── ifm.ts # Tauri command wrappers
│ │ ├── audio.ts # AudioContext, Opus decoding
│ │ ├── keychain.ts # OS keychain abstraction
│ │ └── media.ts # Media session API
│ └── styles/
│ ├── globals.css
│ ├── variables.css
│ └── player.css # Big frequency display styles
├── src-tauri/
│ ├── Cargo.toml
│ ├── tauri.conf.json
│ ├── src/
│ │ ├── main.rs
│ │ ├── commands/
│ │ │ ├── node.rs # node_create, tune, scan, peers
│ │ │ ├── audio.rs # playback_start/stop, volume
│ │ │ ├── discovery.rs # mdns_start, dht_bootstrap
│ │ │ ├── chat.rs # send, subscribe
│ │ │ ├── files.rs # receive
│ │ │ ├── config.rs # favorites, keys
│ │ │ ├── tray.rs # tray_setup, menu
│ │ │ ├── media.rs # media_session_update
│ │ │ └── notifications.rs
│ │ └── lib.rs
│ └── icons/
├── package.json
├── tauri.conf.json
└── vite.config.tsRelease Artifacts
| Platform | Format | Size (est.) |
|---|---|---|
| macOS arm64 | .dmg | ~20 MB |
| macOS x64 | .dmg | ~22 MB |
| Windows x64 | .msi | ~25 MB |
| Windows arm64 | .msi | ~23 MB |
| Linux x64 | .AppImage | ~28 MB |
| Linux arm64 | .AppImage | ~26 MB |
| Linux x64 | .deb | ~8 MB |
| Linux x64 | .rpm | ~8 MB |
Background Audio Implementation Details
macOS
AVAudioSessioncategory.playbackwith.mixWithOthersMPNowPlayingInfoCenterfor lock screen metadataMPRemoteCommandCenterfor media key handling- App Nap disabled via
ProcessInfo.performExpiringActivity
Windows
AudioCategory::BackgroundCapableMediaSystemMediaTransportControlsfor lock screen + media keysExtendedExecutionSessionwithReason::Unspecifiedfor background
Linux
MPRISviadbusfor media controlssystemd-inhibitororg.freedesktop.ScreenSaver.Inhibitfor sleep prevention- PulseAudio/PipeWire
media.role=musicstream property
Tauri Config (tauri.conf.json)
json
{
"identifier": "sh.ifm.radio-listener",
"windows": [{
"label": "main",
"title": "IFM Radio Listener",
"width": 480,
"height": 720,
"resizable": false,
"hiddenTitle": true,
"decorations": false,
"transparent": true
}],
"systemTray": {
"iconPath": "icons/tray-icon.png",
"menu": {
"items": [
{ "id": "play_pause", "text": "Play/Pause", "accelerator": "MediaPlayPause" },
{ "id": "next", "text": "Next Station", "accelerator": "MediaNextTrack" },
{ "id": "prev", "text": "Previous Station", "accelerator": "MediaPreviousTrack" },
{ "type": "separator" },
{ "id": "favorites", "text": "Favorites", "submenu": [...] },
{ "type": "separator" },
{ "id": "show", "text": "Show Window", "accelerator": "CmdOrCtrl+Shift+I" },
{ "id": "quit", "text": "Quit" }
]
}
},
"macOSPrivateApi": true,
"allowlist": {
"globalShortcut": { "all": true },
"notification": { "all": true },
"clipboard": { "all": true },
"shell": { "all": false, "open": true },
"fs": { "readFile": true, "writeFile": true, "scope": ["$DOWNLOAD/*"] }
}
}