|
| 1 | +# Media Controls |
| 2 | + |
| 3 | +Cliamp integrates with the operating system's media control infrastructure so that desktop environments, hardware media keys, and command line tools can control playback, read track metadata, and adjust volume without touching the TUI. |
| 4 | + |
| 5 | +## Platform Support |
| 6 | + |
| 7 | +| Platform | Backend | Requirements | |
| 8 | +|---|---|---| |
| 9 | +| Linux | [MPRIS2](https://specifications.freedesktop.org/mpris-spec/latest/) over D-Bus | A running D-Bus session bus (provided by most desktop environments and Wayland compositors) | |
| 10 | +| macOS | MPNowPlayingInfoCenter / MPRemoteCommandCenter | None (frameworks are built-in) | |
| 11 | +| Other | No-op stub | — | |
| 12 | + |
| 13 | +## Linux (MPRIS2) |
| 14 | + |
| 15 | +### Bus Name |
| 16 | + |
| 17 | +Cliamp registers itself as: |
| 18 | + |
| 19 | +``` |
| 20 | +org.mpris.MediaPlayer2.cliamp |
| 21 | +``` |
| 22 | + |
| 23 | +Only one instance can hold this name at a time. If a second Cliamp process tries to start, the MPRIS registration will fail silently and that instance will run without D-Bus integration. |
| 24 | + |
| 25 | +### Playback Control |
| 26 | + |
| 27 | +All standard transport commands are supported through the `org.mpris.MediaPlayer2.Player` interface: |
| 28 | + |
| 29 | +| playerctl command | Effect | |
| 30 | +|---|---| |
| 31 | +| `playerctl play-pause` | Toggle play / pause | |
| 32 | +| `playerctl play` | Resume playback | |
| 33 | +| `playerctl pause` | Pause playback | |
| 34 | +| `playerctl stop` | Stop playback | |
| 35 | +| `playerctl next` | Skip to the next track | |
| 36 | +| `playerctl previous` | Go to the previous track (or restart if more than 3 seconds in) | |
| 37 | + |
| 38 | +### Seeking |
| 39 | + |
| 40 | +Relative and absolute seeking are both supported: |
| 41 | + |
| 42 | +```sh |
| 43 | +playerctl position 30 # seek to 30 seconds |
| 44 | +playerctl position 5+ # seek forward 5 seconds |
| 45 | +playerctl position 5- # seek backward 5 seconds |
| 46 | +``` |
| 47 | + |
| 48 | +Desktop widgets that display a progress bar will receive `Seeked` signals and stay in sync. |
| 49 | + |
| 50 | +### Volume |
| 51 | + |
| 52 | +Volume is exposed as a linear value between 0.0 and 1.0. Internally Cliamp uses a decibel scale (from -30 dB to +6 dB), and the conversion happens automatically. |
| 53 | + |
| 54 | +```sh |
| 55 | +playerctl volume # print current volume (0.0 to 1.0) |
| 56 | +playerctl volume 0.5 # set volume to 50% |
| 57 | +``` |
| 58 | + |
| 59 | +Setting volume through `playerctl` updates the player immediately. Changing volume with the `+` and `-` keys in the TUI is reflected back to D-Bus clients on the next tick. |
| 60 | + |
| 61 | +### Metadata |
| 62 | + |
| 63 | +Track metadata is published under the standard MPRIS keys: |
| 64 | + |
| 65 | +| Key | Description | |
| 66 | +|---|---| |
| 67 | +| `mpris:trackid` | D-Bus object path identifying the current track | |
| 68 | +| `xesam:title` | Track title | |
| 69 | +| `xesam:artist` | Artist name (as a list with one entry) | |
| 70 | +| `xesam:album` | Album name, when available | |
| 71 | +| `xesam:url` | File path or stream URL | |
| 72 | +| `mpris:length` | Duration in microseconds | |
| 73 | + |
| 74 | +Query metadata with: |
| 75 | + |
| 76 | +```sh |
| 77 | +playerctl metadata # all keys |
| 78 | +playerctl metadata artist # just the artist |
| 79 | +playerctl metadata title # just the title |
| 80 | +``` |
| 81 | + |
| 82 | +For live radio streams that provide ICY metadata, the artist and title fields update dynamically as the station reports new track information. |
| 83 | + |
| 84 | +### Status |
| 85 | + |
| 86 | +```sh |
| 87 | +playerctl status # prints Playing, Paused, or Stopped |
| 88 | +``` |
| 89 | + |
| 90 | +## macOS |
| 91 | + |
| 92 | +On macOS, Cliamp publishes now-playing information to the system's MPNowPlayingInfoCenter. This enables: |
| 93 | + |
| 94 | +- Control Centre and Lock Screen media controls |
| 95 | +- Touch Bar playback buttons |
| 96 | +- Hardware media keys (play/pause, next, previous) |
| 97 | +- Bluetooth headphone buttons |
| 98 | + |
| 99 | +The macOS implementation requires the media-control runtime to pin the main goroutine to thread 0 (via `runtime.LockOSThread`) so that the Cocoa run loop can pump events. Bubbletea runs on a background goroutine instead. |
| 100 | + |
| 101 | +## Architecture |
| 102 | + |
| 103 | +The app-owned playback command and notifier boundary lives in `internal/playback`. The `mediactl` package translates platform APIs to and from that boundary and owns the platform-specific interactive runtime helper. |
| 104 | + |
| 105 | +Platform-specific `Service` implementations: |
| 106 | + |
| 107 | +- `internal/playback/*` — app-level playback commands and outbound notifier state. |
| 108 | +- `mediactl/service_linux.go` — connects to the session bus, claims the MPRIS bus name, translates D-Bus calls into playback commands, and publishes outbound state through MPRIS properties. |
| 109 | +- `mediactl/service_darwin.go` — initialises NSApplication as an accessory process, registers MPRemoteCommandCenter handlers, translates them into playback commands, and publishes now-playing state on the main-thread run loop. |
| 110 | +- `mediactl/service_stub.go` — no-op implementation for unsupported platforms. |
| 111 | + |
| 112 | +The model publishes playback state through the playback notifier whenever state changes. On Linux, `mediactl` uses `SetMust` rather than `Set` to bypass the property library's writable checks and callback triggers, which are intended for external D-Bus writes. For writable properties like Volume, the D-Bus callback is translated into an app playback command and dispatched back into the Bubbletea event loop. |
| 113 | + |
| 114 | +## Limitations |
| 115 | + |
| 116 | +Shuffle and loop status are not exposed. The `z` and `r` keys in the TUI control shuffle and repeat locally, but these states are not visible to or controllable from external tools. |
| 117 | + |
| 118 | +The `HasTrackList` property is set to false on Linux. Cliamp does not implement the optional `org.mpris.MediaPlayer2.TrackList` interface. |
0 commit comments