Skip to content

IFM Radio Listener — Desktop Consumer App

Simple, radio-like listening experience for end users. Open, tune, listen — no accounts, no config.


Overview

PropertyValue
Binaryifm-radio-listener
Packagepackages/ifm-radio-listener/
Coreifm-core via Tauri/N-API
UIReact + TypeScript (WebView)
PlatformsWindows, macOS, Linux (x64 + arm64)
Installcurl -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 hotkeysCtrl+Shift+Space play/pause, Ctrl+Shift+Right next 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

ActionShortcut
Play/PauseCtrl+Shift+Space / Media Play/Pause
Next favoriteCtrl+Shift+Right / Media Next
Previous favoriteCtrl+Shift+Left / Media Previous
MuteCtrl+Shift+M
Volume upCtrl+Shift+Up
Volume downCtrl+Shift+Down
Open/close windowCtrl+Shift+I
Search/tuneCtrl+K / Cmd+K
Favorite currentCtrl+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 help

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=abc123

Development

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.ts

Release Artifacts

PlatformFormatSize (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

  • AVAudioSession category .playback with .mixWithOthers
  • MPNowPlayingInfoCenter for lock screen metadata
  • MPRemoteCommandCenter for media key handling
  • App Nap disabled via ProcessInfo.performExpiringActivity

Windows

  • AudioCategory::BackgroundCapableMedia
  • SystemMediaTransportControls for lock screen + media keys
  • ExtendedExecutionSession with Reason::Unspecified for background

Linux

  • MPRIS via dbus for media controls
  • systemd-inhibit or org.freedesktop.ScreenSaver.Inhibit for sleep prevention
  • PulseAudio/PipeWire media.role=music stream 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/*"] }
  }
}

Released under the MIT License.