# rockykln/refrain architecture

> Architecture diagram of the GitHub repository rockykln/refrain, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-10-20). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/rockykln/refrain
- Repository: https://github.com/rockykln/refrain

## Overview

Refrain is a Linux desktop companion that reads Apple Music playback metadata from MPRIS (including browser players) or Bluetooth AVRCP, then publishes track status to Discord. Its daemon coordinates playback polling, track timing, cover lookup, local history, and optional Last.fm scrobbling; Qt tray, status, history, welcome, and settings interfaces expose controls and configuration. The README documents these capabilities, while sampled daemon excerpts confirm source setup, cover lookup, and history updates. Other links are limited to explicit source-index references or README statements; unsampled implementation details are not inferred.

Main entry points: [`src/refrain/daemon.py`](https://github.com/rockykln/refrain/blob/main/src/refrain/daemon.py) (Playback daemon), [`src/refrain/app.py`](https://github.com/rockykln/refrain/blob/main/src/refrain/app.py) (Application startup), [`src/refrain/ui/settings_window.py`](https://github.com/rockykln/refrain/blob/main/src/refrain/ui/settings_window.py) (Settings window), [`src/refrain/sources/bluetooth.py`](https://github.com/rockykln/refrain/blob/main/src/refrain/sources/bluetooth.py) (Bluetooth source)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_playback["Playback and coordination"]
  node_application["Application startup<br/>[app.py]"]
  node_daemon["Playback daemon<br/>[daemon.py]"]
  node_track_model["Track model<br/>[base.py]"]
  node_mpris_source["MPRIS source<br/>[mpris.py]"]
  node_bluetooth_source["Bluetooth source<br/>[bluetooth.py]"]
  node_mpris_server["MPRIS publishing<br/>[mpris_server.py]"]
  node_timing["Track timing<br/>[timing.py]"]
  node_cover_fetcher["Cover lookup<br/>[cover_fetcher.py]"]
end

subgraph group_delivery["Status and scrobbling"]
  node_discord_rpc["Discord IPC<br/>[discord_rpc.py]"]
  node_discord_application["Discord app lookup<br/>[discord_app.py]"]
  node_scrobbling["Last.fm scrobbling<br/>[scrobble.py]"]
  node_scrobble_queue["Offline queue<br/>[scrobble_queue.py]"]
  node_service_status["Service status<br/>[service_status.py]"]
end

subgraph group_interface["Desktop experience"]
  node_tray["System tray<br/>[tray.py]"]
  node_status_window["Status window<br/>[status_window.py]"]
  node_settings["Settings window<br/>[settings_window.py]"]
  node_welcome["Welcome wizard<br/>[welcome_dialog.py]"]
  node_history_window["Recently played UI<br/>[history_window.py]"]
end

subgraph group_state["Local data and setup"]
  node_history[("Listening history<br/>[history.py]")]
  node_config[("Configuration<br/>[config.py]")]
  node_paths["Runtime paths<br/>[paths.py]"]
  node_cover_art["Cover art cache<br/>[cover_art.py]"]
  node_updater["Update checking<br/>[updater.py]"]
end

node_user(("User"))
node_browser_player["Browser player"]
node_bluetooth_device["Bluetooth device"]
node_discord_client["Discord client"]
node_lastfm_service["Last.fm"]

node_user -->|"launches"| node_application
node_browser_player -->|"publishes metadata"| node_mpris_source
node_bluetooth_device -->|"reports track"| node_bluetooth_source
node_application -->|"starts"| node_daemon
node_application -->|"wires controls"| node_tray
node_application -->|"loads settings"| node_config
node_application -.->|"shows setup"| node_welcome
node_daemon -->|"polls"| node_mpris_source
node_daemon -->|"polls"| node_bluetooth_source
node_daemon -->|"updates status"| node_discord_rpc
node_daemon -->|"looks up cover"| node_cover_fetcher
node_daemon -->|"records tracks"| node_history
node_daemon -->|"scrobbles tracks"| node_scrobbling
node_daemon -->|"publishes playback"| node_mpris_server
node_daemon -->|"resolves position"| node_timing
node_mpris_source -->|"produces tracks"| node_track_model
node_bluetooth_source -->|"produces tracks"| node_track_model
node_daemon -->|"reports state"| node_service_status
node_service_status -->|"shows state"| node_tray
node_service_status -->|"shows state"| node_status_window
node_discord_rpc -->|"sends presence"| node_discord_client
node_scrobbling -->|"sends scrobbles"| node_lastfm_service
node_scrobbling -->|"queues offline"| node_scrobble_queue
node_tray -->|"controls playback"| node_daemon
node_user -->|"configures"| node_settings
node_user -->|"controls playback"| node_tray
node_status_window -->|"opens history"| node_history_window
node_history_window -->|"reads entries"| node_history
node_daemon -.->|"uses artwork"| node_cover_art
node_settings -->|"edits settings"| node_config
node_settings -->|"resolves app"| node_discord_application
node_settings -->|"selects device"| node_bluetooth_source
node_settings -->|"checks updates"| node_updater
node_welcome -->|"checks application"| node_discord_application
node_cover_fetcher -.->|"uses cache"| node_cover_art
node_config -->|"uses paths"| node_paths
node_history -->|"stores locally"| node_paths

click node_application "https://github.com/rockykln/refrain/blob/main/src/refrain/app.py"
click node_daemon "https://github.com/rockykln/refrain/blob/main/src/refrain/daemon.py"
click node_track_model "https://github.com/rockykln/refrain/blob/main/src/refrain/sources/base.py"
click node_mpris_source "https://github.com/rockykln/refrain/blob/main/src/refrain/sources/mpris.py"
click node_bluetooth_source "https://github.com/rockykln/refrain/blob/main/src/refrain/sources/bluetooth.py"
click node_mpris_server "https://github.com/rockykln/refrain/blob/main/src/refrain/sources/mpris_server.py"
click node_timing "https://github.com/rockykln/refrain/blob/main/src/refrain/timing.py"
click node_cover_fetcher "https://github.com/rockykln/refrain/blob/main/src/refrain/cover_fetcher.py"
click node_discord_rpc "https://github.com/rockykln/refrain/blob/main/src/refrain/discord_rpc.py"
click node_discord_application "https://github.com/rockykln/refrain/blob/main/src/refrain/discord_app.py"
click node_scrobbling "https://github.com/rockykln/refrain/blob/main/src/refrain/scrobble.py"
click node_scrobble_queue "https://github.com/rockykln/refrain/blob/main/src/refrain/scrobble_queue.py"
click node_service_status "https://github.com/rockykln/refrain/blob/main/src/refrain/service_status.py"
click node_tray "https://github.com/rockykln/refrain/blob/main/src/refrain/ui/tray.py"
click node_status_window "https://github.com/rockykln/refrain/blob/main/src/refrain/ui/status_window.py"
click node_settings "https://github.com/rockykln/refrain/blob/main/src/refrain/ui/settings_window.py"
click node_welcome "https://github.com/rockykln/refrain/blob/main/src/refrain/ui/welcome_dialog.py"
click node_history_window "https://github.com/rockykln/refrain/blob/main/src/refrain/ui/history_window.py"
click node_history "https://github.com/rockykln/refrain/blob/main/src/refrain/history.py"
click node_config "https://github.com/rockykln/refrain/blob/main/src/refrain/config.py"
click node_paths "https://github.com/rockykln/refrain/blob/main/src/refrain/paths.py"
click node_cover_art "https://github.com/rockykln/refrain/blob/main/src/refrain/cover_art.py"
click node_updater "https://github.com/rockykln/refrain/blob/main/src/refrain/updater.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_application,node_daemon,node_track_model,node_mpris_source,node_bluetooth_source,node_mpris_server,node_timing,node_cover_fetcher,node_user,node_browser_player,node_discord_client toneBlue
class node_discord_rpc,node_discord_application,node_scrobbling,node_scrobble_queue,node_service_status toneAmber
class node_tray,node_status_window,node_settings,node_welcome,node_history_window toneMint
class node_history,node_config,node_paths,node_cover_art,node_updater toneRose
class node_bluetooth_device,node_lastfm_service toneIndigo
```

## Components

### Playback and coordination

- [Application startup](https://github.com/rockykln/refrain/blob/main/src/refrain/app.py): `src/refrain/app.py`
- [Playback daemon](https://github.com/rockykln/refrain/blob/main/src/refrain/daemon.py): `src/refrain/daemon.py`
- [Track model](https://github.com/rockykln/refrain/blob/main/src/refrain/sources/base.py): `src/refrain/sources/base.py`
- [MPRIS source](https://github.com/rockykln/refrain/blob/main/src/refrain/sources/mpris.py): `src/refrain/sources/mpris.py`
- [Bluetooth source](https://github.com/rockykln/refrain/blob/main/src/refrain/sources/bluetooth.py): `src/refrain/sources/bluetooth.py`
- [MPRIS publishing](https://github.com/rockykln/refrain/blob/main/src/refrain/sources/mpris_server.py): `src/refrain/sources/mpris_server.py`
- [Track timing](https://github.com/rockykln/refrain/blob/main/src/refrain/timing.py): `src/refrain/timing.py`
- [Cover lookup](https://github.com/rockykln/refrain/blob/main/src/refrain/cover_fetcher.py): `src/refrain/cover_fetcher.py`

### Status and scrobbling

- [Discord IPC](https://github.com/rockykln/refrain/blob/main/src/refrain/discord_rpc.py): `src/refrain/discord_rpc.py`
- [Discord app lookup](https://github.com/rockykln/refrain/blob/main/src/refrain/discord_app.py): `src/refrain/discord_app.py`
- [Last.fm scrobbling](https://github.com/rockykln/refrain/blob/main/src/refrain/scrobble.py): `src/refrain/scrobble.py`
- [Offline queue](https://github.com/rockykln/refrain/blob/main/src/refrain/scrobble_queue.py): `src/refrain/scrobble_queue.py`
- [Service status](https://github.com/rockykln/refrain/blob/main/src/refrain/service_status.py): `src/refrain/service_status.py`

### Desktop experience

- [System tray](https://github.com/rockykln/refrain/blob/main/src/refrain/ui/tray.py): `src/refrain/ui/tray.py`
- [Status window](https://github.com/rockykln/refrain/blob/main/src/refrain/ui/status_window.py): `src/refrain/ui/status_window.py`
- [Settings window](https://github.com/rockykln/refrain/blob/main/src/refrain/ui/settings_window.py): `src/refrain/ui/settings_window.py`
- [Welcome wizard](https://github.com/rockykln/refrain/blob/main/src/refrain/ui/welcome_dialog.py): `src/refrain/ui/welcome_dialog.py`
- [Recently played UI](https://github.com/rockykln/refrain/blob/main/src/refrain/ui/history_window.py): `src/refrain/ui/history_window.py`

### Local data and setup

- [Listening history](https://github.com/rockykln/refrain/blob/main/src/refrain/history.py): `src/refrain/history.py`
- [Configuration](https://github.com/rockykln/refrain/blob/main/src/refrain/config.py): `src/refrain/config.py`
- [Runtime paths](https://github.com/rockykln/refrain/blob/main/src/refrain/paths.py): `src/refrain/paths.py`
- [Cover art cache](https://github.com/rockykln/refrain/blob/main/src/refrain/cover_art.py): `src/refrain/cover_art.py`
- [Update checking](https://github.com/rockykln/refrain/blob/main/src/refrain/updater.py): `src/refrain/updater.py`

### Other components

- **User**
- **Browser player**
- **Bluetooth device**
- **Discord client**
- **Last.fm**

## Connections

- User → Application startup: launches
- Browser player → MPRIS source: publishes metadata
- Bluetooth device → Bluetooth source: reports track
- Application startup → Playback daemon: starts (evidence: `src/refrain/app.py`)
- Application startup → System tray: wires controls (evidence: `src/refrain/app.py`)
- Application startup → Configuration: loads settings (evidence: `src/refrain/app.py`)
- Application startup → Welcome wizard: shows setup (evidence: `src/refrain/app.py`)
- Playback daemon → MPRIS source: polls (evidence: `src/refrain/daemon.py`)
- Playback daemon → Bluetooth source: polls (evidence: `src/refrain/daemon.py`)
- Playback daemon → Discord IPC: updates status (evidence: `src/refrain/daemon.py`)
- Playback daemon → Cover lookup: looks up cover (evidence: `src/refrain/daemon.py`)
- Playback daemon → Listening history: records tracks (evidence: `src/refrain/daemon.py`)
- Playback daemon → Last.fm scrobbling: scrobbles tracks (evidence: `src/refrain/daemon.py`)
- Playback daemon → MPRIS publishing: publishes playback (evidence: `src/refrain/daemon.py`)
- Playback daemon → Track timing: resolves position (evidence: `src/refrain/daemon.py`)
- MPRIS source → Track model: produces tracks (evidence: `src/refrain/sources/mpris.py`)
- Bluetooth source → Track model: produces tracks (evidence: `src/refrain/sources/bluetooth.py`)
- Playback daemon → Service status: reports state (evidence: `src/refrain/service_status.py`)
- Service status → System tray: shows state (evidence: `src/refrain/ui/tray.py`)
- Service status → Status window: shows state (evidence: `src/refrain/ui/status_window.py`)
- Discord IPC → Discord client: sends presence
- Last.fm scrobbling → Last.fm: sends scrobbles
- Last.fm scrobbling → Offline queue: queues offline (evidence: `src/refrain/scrobble.py`)
- System tray → Playback daemon: controls playback (evidence: `src/refrain/app.py`)
- User → Settings window: configures
- User → System tray: controls playback
- Status window → Recently played UI: opens history (evidence: `src/refrain/ui/status_window.py`)
- Recently played UI → Listening history: reads entries (evidence: `src/refrain/ui/history_window.py`)
- Playback daemon → Cover art cache: uses artwork
- Settings window → Configuration: edits settings (evidence: `src/refrain/ui/settings_window.py`)
- Settings window → Discord app lookup: resolves app (evidence: `src/refrain/ui/settings_window.py`)
- Settings window → Bluetooth source: selects device (evidence: `src/refrain/ui/settings_window.py`)
- Settings window → Update checking: checks updates (evidence: `src/refrain/ui/settings_window.py`)
- Welcome wizard → Discord app lookup: checks application (evidence: `src/refrain/ui/welcome_dialog.py`)
- Cover lookup → Cover art cache: uses cache
- Configuration → Runtime paths: uses paths (evidence: `src/refrain/config.py`)
- Listening history → Runtime paths: stores locally (evidence: `src/refrain/history.py`)
