This document describes the architecture of MT, a desktop music player built with Tauri and native Rust.
MT uses a modern pure-Rust architecture:
| Component | Technology |
|---|---|
| Frontend | Tauri WebView (Alpine.js + Basecoat) |
| Backend | Native Rust (91 Tauri commands) |
| Audio | Rust audio engine (symphonia + rodio) |
| Database | SQLite via rusqlite |
| Media Keys | souvlaki (Now Playing/MPRIS/SMTC) + tauri-plugin-global-shortcut (keyboard F7/F8/F9) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Tauri Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Frontend (WebView) β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β β
β β β Alpine.js β β Basecoat β β Player Controls β β β
β β β Stores β β Components β β Library Browser β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β Tauri invoke / events β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Tauri Core (Rust) β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β β
β β β Window β β Menus β β Media Keys β β β
β β β Lifecycle β β & Tray β β (tauri-plugin-global- β β β
β β β β β β β shortcut) β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Rust Audio Engine β β β
β β β βββββββββββββ βββββββββββββ βββββββββββββββββββββββββ β β β
β β β β symphonia β β rodio β β Audio State β β β β
β β β β (decode) β β (output) β β (position, volume) β β β β
β β β βββββββββββββ βββββββββββββ βββββββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Data Layer (Rust) β β β
β β β βββββββββββββ βββββββββββββ βββββββββββββββββββββββββ β β β
β β β β SQLite β β Library β β Metadata β β β β
β β β β (rusqlite)β β Scanner β β (lofty-rs) β β β β
β β β βββββββββββββ βββββββββββββ βββββββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Rust backend handles all functionality:
| Component | Responsibility |
|---|---|
| Window Lifecycle | Create, resize, minimize, close, fullscreen |
| Native Menus | Application menu, context menus |
| Media Keys | OS media integration via souvlaki (Now Playing/MPRIS/SMTC) + tauri-plugin-global-shortcut for keyboard media keys (F7/F8/F9). macOS keyboard sends NX_KEYTYPE_REWIND/FAST (not PREVIOUS/NEXT), so both code families are registered. |
| Audio Engine | Decode and play audio with sub-millisecond latency |
| Database | SQLite operations via rusqlite |
| Library Scanner | Directory traversal, metadata extraction |
| Last.fm | Scrobbling, authentication, loved tracks |
Native audio playback:
| Library | Purpose |
|---|---|
| symphonia | Audio decoding (FLAC, MP3, M4A/AAC, OGG/Vorbis, WAV) |
| rodio | Audio output abstraction (wraps cpal) |
| cpal | Cross-platform audio I/O |
Audio State:
- Current position (milliseconds)
- Duration
- Volume (0.0 - 1.0)
- Playing/paused state
- Loop mode (none, track, queue)
All data operations in native Rust:
| Component | Responsibility |
|---|---|
| rusqlite | SQLite database (mt.db in Tauri app data dir) |
| lofty-rs | Audio metadata extraction |
| Library Scanner | Recursive directory traversal |
| Watched Folders | File system monitoring |
Modern web UI:
| Technology | Purpose |
|---|---|
| Alpine.js | Reactive state management, minimal footprint |
| Basecoat | Tailwind-based component library |
| Vite | Fast development builds, HMR |
UI Components:
- Library browser (virtual scrolling)
- Artists browser (split-pane: artist list + album/track detail)
- Albums browser (grid layout with album artwork)
- Queue view (drag-and-drop reordering)
- Player controls (play/pause, seek, volume)
- Search bar (instant filtering)
- Sidebar navigation (library sections, playlists)
- Settings panel
- Metadata editor
Mixin Architecture:
Large Alpine.js components use a mixin factory pattern to keep files under 500 LOC. Each mixin is a function that returns a plain object spread into the component:
| Mixin | File | Responsibility |
|---|---|---|
typeToJumpMixin |
js/mixins/type-to-jump.js |
Keyboard-driven artist navigation |
columnGeometryMixin |
js/mixins/column-geometry.js |
Column widths, resize, auto-fit |
columnReorderMixin |
js/mixins/column-reorder.js |
Column drag-and-drop reordering |
columnSettingsMixin |
js/mixins/column-settings.js |
Column visibility, persistence, header context menu |
playlistDragMixin |
js/mixins/playlist-drag.js |
Playlist track reorder via drag |
contextMenuActionsMixin |
js/mixins/context-menu-actions.js |
Context menu, playback, track management |
virtualScrollMixin |
js/mixins/virtual-scroll.js |
Scroll tracking, scroll-to-track |
Mixins use regular function syntax so this binds to the Alpine component at runtime. Getters must stay in the main component since ...spread loses getter descriptors. Shared pure utilities live in js/utils/ and constants in js/constants.js.
View Caching:
The library store implements persistent caching to eliminate loading spinners when switching between views:
| Feature | Implementation |
|---|---|
| Per-section cache | Each library section (Music, Liked, Recent, Added, Top 25) and playlist cached separately |
| Persistent storage | Cache stored in Tauri settings (library:sectionCache) - survives app restarts |
| Background refresh | Cached data shown instantly, fresh data fetched silently in background |
| Cache invalidation | Cleared on library changes, playlist modifications, or file system events |
Cache keys: all, liked, recent, added, top25, playlist-{id}
Tauri Invoke (Commands):
// Frontend calls Rust function
const status = await invoke('audio_get_status');
await invoke('audio_load', { path: '/path/to/song.mp3' });
await invoke('audio_seek', { positionMs: 30000 });Tauri Events (Push):
// Rust pushes updates to frontend
listen('playback-progress', (event) => {
playerStore.position = event.payload.position_ms;
});
listen('track-ended', () => {
playerStore.playNext();
});User clicks "Add Music" button
β
βΌ
βββββββββββββββββββ
β Frontend β invoke('scan_directory', { path: '/Music' })
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Tauri Core β Scans directory, extracts metadata via lofty-rs
β (Rust) β Emits scan-progress events
ββββββββββ¬βββββββββ
β emit("scan-progress", { count: 150 })
βΌ
βββββββββββββββββββ
β Frontend β Updates progress bar
βββββββββββββββββββ
| Feature | Implementation |
|---|---|
| Window chrome | Native titlebar with traffic lights |
| Media keys | souvlaki (Now Playing widget) + tauri-plugin-global-shortcut (MediaRewind/MediaFastForward for F7/F9, MediaTrackPrevious/MediaTrackNext for AirPods/Bluetooth) |
| Menu bar | Native application menu |
| File associations | Info.plist configuration |
| Feature | Implementation |
|---|---|
| Window chrome | Client-side decorations (CSD) |
| Media keys | D-Bus MPRIS integration |
| Audio output | PulseAudio/PipeWire via cpal |
| Feature | Implementation |
|---|---|
| Window chrome | Native Win32 frame |
| Media keys | System Media Transport Controls (SMTC) |
| Audio output | WASAPI via cpal |
The project uses a Cargo workspace with a single crate:
mt/
βββ Cargo.toml # Workspace root (members: mt-tauri)
βββ crates/
β βββ mt-tauri/ # Tauri app (all backend logic)
β βββ src/
β β βββ main.rs # Entry point
β β βββ audio/ # Audio engine module
β β βββ commands/ # Tauri command handlers
β β βββ db/ # Database operations
β β βββ lastfm/ # Last.fm integration
β β βββ scanner/ # Library scanning
β β βββ watcher/ # File system monitoring
β βββ Cargo.toml
β βββ tauri.conf.json
βββ app/frontend/ # Frontend source
β βββ index.html
β βββ js/
β β βββ main.js # Alpine.js initialization
β β βββ stores/ # Alpine.js stores
β βββ components/ # UI components
β βββ styles/
β βββ main.css # Tailwind + Basecoat
βββ package.json # Frontend dependencies
| Metric | macOS | Linux | Notes |
|---|---|---|---|
| Cold start | < 500ms | < 1s | |
| Track switch | < 50ms | < 50ms | |
| Memory (idle) | ~115 MB | ~480 MB | WebKitGTK multi-process overhead on Linux |
| CPU (playing) | < 5% | < 5% | |
| Binary size | ~30 MB | ~30 MB |
See builds.md for memory optimization details.
The optional tauri-plugin-mcp-bridge enables AI agents (Claude, Cursor, Windsurf) to interact with the running app via the Model Context Protocol:
task tauri:dev:mcp # Run with MCP bridge enabledFeatures: Screenshots, DOM snapshots, IPC monitoring, UI automation, console logs. See hypothesi/mcp-server-tauri.
[dependencies]
tauri = { version = "2", features = ["shell-open"] }
symphonia = { version = "0.5", features = ["mp3", "flac", "aac", "ogg"] }
rodio = { version = "0.21", features = ["flac", "mp3", "mp4", "vorbis", "wav"] }
rusqlite = { version = "0.38", features = ["bundled"] }
lofty = "0.22"
souvlaki = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["json"] }
[dependencies.tauri-plugin-global-shortcut]
version = "2"{
"dependencies": {
"alpinejs": "^3.14",
"basecoat-css": "^0.3.10-beta.2"
},
"devDependencies": {
"vite": "^6",
"tailwindcss": "^4.0",
"@tailwindcss/vite": "^4.0"
}
}