From d83cc9784b3f13fd3ed1f7668b8583cb5b451835 Mon Sep 17 00:00:00 2001 From: yuliusw Date: Thu, 9 Apr 2026 16:28:35 +0800 Subject: [PATCH 1/2] add mutil playger support --- README.md | 42 +++++++++---- README_ZH.md | 123 ++++++++++++++++++++++++++++++++++++++ config/config.go | 77 +++++++++++++++++++----- services/mopidy/mopidy.go | 11 +++- services/mpd/mpd.go | 9 ++- 5 files changed, 231 insertions(+), 31 deletions(-) create mode 100644 README_ZH.md diff --git a/README.md b/README.md index ecce786..aa76d9a 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,15 @@ Config file will be created at the first launch. On Linux it's located in `~/.co
Show config contents (with descriptions) -```yaml -### Global settings ### -# Player that will be used. Possible values: spotify, mpd, mopidy, mpris. -player: spotify -# Whether to ignore errors instead of showing them. -ignoreErrors: true +```yaml +### Global settings ### +# Player that will be used. Possible values: spotify, mpd, mopidy, mpris, browser. +# You can also provide a comma-separated list to try players in order, e.g.: +# player: mopidy ,mpris +# Only the players explicitly listed will be tried, in the given order. +player: spotify +# Whether to ignore errors instead of showing them. +ignoreErrors: true # Interval of the internal timer. Determines how often the terminal will be updated. timerInterval: 200 # Interval for checking the position. Doesn't really affect the precision. @@ -134,9 +137,9 @@ browser: local: # Folder for scanning .lrc files. Example: "~/Music". folder: "" -``` - -
+``` + + ### Spotify @@ -179,7 +182,7 @@ mopidy: Mopidy server will be used as a player. -### MPRIS +### MPRIS ```yaml # config.yaml @@ -188,9 +191,22 @@ mpris: players: [] ``` -Linux only. System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: `players: [rhythmbox, spotifyd, ncspot]`. Run `playerctl -l` to get the names. - -### Browser +Linux only. System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: `players: [rhythmbox, spotifyd, ncspot]`. Run `playerctl -l` to get the names. + +### Multiple Players (fallback by order) + +```yaml +# config.yaml +player: mopidy ,mpris +mopidy: + address: 127.0.0.1:6680 +mpris: + players: [] +``` + +The application will initialize players in the order specified by `player` and use the first one that initializes successfully. Players not listed will not be tried. + +### Browser ```yaml # config.yaml diff --git a/README_ZH.md b/README_ZH.md new file mode 100644 index 0000000..0cb25d5 --- /dev/null +++ b/README_ZH.md @@ -0,0 +1,123 @@ +
+ +

sptlrx

+

在终端中显示同步歌词

+ + + + + +
+ +## 功能 + +- 支持 Spotify、MPD、Mopidy、MPRIS 与浏览器扩展。 +- 适配长行与 Unicode 字符。 +- 易于自定义样式。 +- 可将歌词通过管道输出到 stdout。 +- 单一二进制,跨平台。 + +## 安装 + +Linux、Windows、macOS 等平台可从 [Releases](https://github.com/raitonoberu/sptlrx/releases/latest) 下载,或参见 [building.md](./building.md) 自行编译。 + +## 配置 + +首次运行会自动在配置目录创建 `config.yaml`。在 Linux 上路径通常为 `~/.config/sptlrx/config.yaml`。运行 `sptlrx -h` 可查看完整路径。 + +示例配置(节选): + +```yaml +### 全局设置 ### +# 使用的播放器。可选:spotify, mpd, mopidy, mpris, browser +# 也可以使用英文逗号分隔按顺序尝试多个播放器,例如: +# player: mopidy ,mpris +# 程序会按顺序初始化,直到有一个成功为止;未列出的播放器不会被尝试。 +player: spotify +# 是否忽略错误(为 true 时不在界面显示错误)。 +ignoreErrors: true +# 内部计时器间隔(毫秒),决定终端刷新频率。 +timerInterval: 200 +# 位置更新间隔(毫秒),不显著影响精度。 +updateInterval: 2000 + +### 样式设置 ### +style: + # 行的水平对齐方式:left, center, right + hAlignment: center + before: + background: "" + foreground: "" + bold: true + italic: false + underline: false + strikethrough: false + blink: false + faint: false + current: + background: "" + foreground: "" + bold: true + italic: false + underline: false + strikethrough: false + blink: false + faint: false + after: + background: "" + foreground: "" + bold: false + italic: false + underline: false + strikethrough: false + blink: false + faint: true + +### MPD 设置 ### +mpd: + address: 127.0.0.1:6600 + password: "" + +### Mopidy 设置 ### +mopidy: + address: 127.0.0.1:6680 + +### MPRIS 设置 ### +mpris: + # MPRIS 播放器白名单;为空则自动选第一个可用的。 + players: [] + +### 浏览器扩展 ### +browser: + port: 8974 + +### 本地歌词 ### +local: + # 扫描 .lrc 文件的目录,例如:"~/Music"。设置后将仅使用本地歌词源。 + folder: "" +``` + +### 多播放器顺序(回退)示例 + +```yaml +# config.yaml +player: mopidy ,mpris +mopidy: + address: 127.0.0.1:6680 +mpris: + players: [] +``` + +程序会按照 `player` 字段给定的顺序依次尝试初始化播放器,使用第一个初始化成功的播放器。仅尝试用户列出的播放器。 + +### Spotify 登录 + +将 `player` 设为 `spotify` 后,首次使用需要登录:执行 `sptlrx login`,根据提示提供 `Client ID`/`Client Secret`,或通过环境变量 `SPOTIFY_CLIENT_ID`/`SPOTIFY_CLIENT_SECRET` 或命令行参数 `--client-id`/`--client-secret` 提供。登录成功后凭据将保存到 `$XDG_STATE_HOME/sptlrx/spotify-auth.json`。 + +### 输出到管道 + +执行 `sptlrx pipe` 可将当前行输出到标准输出,便于与状态栏等其他程序集成。 + +## 许可证 + +**MIT License**,详见 [LICENSE](./LICENSE)。 diff --git a/config/config.go b/config/config.go index b7ef697..4a72988 100644 --- a/config/config.go +++ b/config/config.go @@ -173,19 +173,68 @@ func validateColor(color string) bool { return false } -// GetPlayer returns a player based on config values +// GetPlayer returns a player based on config values. +// It supports a comma-separated list in conf.Player for backward-compatible multi-player configuration. +// Example: "mopidy ,mpris" will try Mopidy first, then MPRIS if Mopidy init fails. +// Only players explicitly listed by the user will be tried. func GetPlayer(conf *Config) (player.Player, error) { - switch conf.Player { - case "spotify": - return spotify.New() - case "mpd": - return mpd.New(conf.Mpd.Address, conf.Mpd.Password), nil - case "mopidy": - return mopidy.New(conf.Mopidy.Address), nil - case "mpris": - return mpris.New(conf.Mpris.Players) - case "browser": - return browser.New(conf.Browser.Port) - } - return nil, fmt.Errorf("unknown player: \"%s\"", conf.Player) + // Split by comma to support multi-player configuration while keeping single value compatible + raw := strings.Split(conf.Player, ",") + // If no comma present, raw will contain the original single value + + var errs []string + for _, name := range raw { + n := strings.TrimSpace(name) + if n == "" { + continue + } + + switch n { + case "spotify": + p, err := spotify.New() + if err != nil { + errs = append(errs, fmt.Sprintf("spotify: %v", err)) + continue + } + return p, nil + case "mpd": + p, err := mpd.New(conf.Mpd.Address, conf.Mpd.Password) + if err != nil { + errs = append(errs, fmt.Sprintf("mpd: %v", err)) + continue + } + return p, nil + case "mopidy": + p, err := mopidy.New(conf.Mopidy.Address) + if err != nil { + errs = append(errs, fmt.Sprintf("mopidy: %v", err)) + continue + } + return p, nil + case "mpris": + p, err := mpris.New(conf.Mpris.Players) + if err != nil { + errs = append(errs, fmt.Sprintf("mpris: %v", err)) + continue + } + return p, nil + case "browser": + p, err := browser.New(conf.Browser.Port) + if err != nil { + errs = append(errs, fmt.Sprintf("browser: %v", err)) + continue + } + return p, nil + default: + // Unknown player name; record and continue to allow next specified players + errs = append(errs, fmt.Sprintf("unknown player: %q", n)) + continue + } + } + + // If nothing succeeded, return aggregated error for easier troubleshooting + if len(errs) == 0 { + return nil, fmt.Errorf("no player specified") + } + return nil, fmt.Errorf("no player initialized successfully; tried: %s", strings.Join(errs, "; ")) } diff --git a/services/mopidy/mopidy.go b/services/mopidy/mopidy.go index 9b93335..dfadb0f 100644 --- a/services/mopidy/mopidy.go +++ b/services/mopidy/mopidy.go @@ -8,8 +8,15 @@ import ( "net/http" ) -func New(address string) *Client { - return &Client{address: address} +func New(address string) (*Client, error) { + // Return a client without probing; but perform a lightweight health check to catch obvious misconfig + c := &Client{address: address} + // Try a simple RPC that should always be available; ignore body decoding errors, we just need connectivity + var out stateResponse + if err := c.get("core.playback.get_state", &out); err != nil { + return nil, err + } + return c, nil } // Client implements player.Player diff --git a/services/mpd/mpd.go b/services/mpd/mpd.go index bb1bd49..e2516bb 100644 --- a/services/mpd/mpd.go +++ b/services/mpd/mpd.go @@ -8,11 +8,16 @@ import ( "github.com/fhs/gompd/mpd" ) -func New(address, password string) *Client { - return &Client{ +func New(address, password string) (*Client, error) { + c := &Client{ address: address, password: password, } + // Attempt initial connection so callers can decide to skip on error + if err := c.connect(); err != nil { + return nil, err + } + return c, nil } // Client implements player.Player From 0de3ee874cbd8d3d5213f005b8c206279cd5c4d2 Mon Sep 17 00:00:00 2001 From: yuliusw Date: Fri, 10 Apr 2026 18:02:12 +0800 Subject: [PATCH 2/2] Add Fedora install Update README.md --- README.md | 67 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index aa76d9a..e87ded8 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,15 @@ nix-env -iA nixos.sptlrx nix-env -iA nixpkgs.sptlrx ``` +- Fedora ([@yuliusw](https://github.com/yuliusw)) + +```sh +sudo dnf copr enable yuliusw/sptlrx +sudo dnf install sptlrx +#some Fedora release do not support latest version of GO . if you want latest ,use +# @go-sig/golang-rawhide-dwarf5 copr to build +``` + **Windows**, **MacOS** & **Other** Download the binary from the [Releases](https://github.com/raitonoberu/sptlrx/releases/latest) page or [build it yourself](./building.md). @@ -52,15 +61,15 @@ Config file will be created at the first launch. On Linux it's located in `~/.co
Show config contents (with descriptions) -```yaml -### Global settings ### -# Player that will be used. Possible values: spotify, mpd, mopidy, mpris, browser. -# You can also provide a comma-separated list to try players in order, e.g.: -# player: mopidy ,mpris -# Only the players explicitly listed will be tried, in the given order. -player: spotify -# Whether to ignore errors instead of showing them. -ignoreErrors: true +```yaml +### Global settings ### +# Player that will be used. Possible values: spotify, mpd, mopidy, mpris, browser. +# You can also provide a comma-separated list to try players in order, e.g.: +# player: mopidy ,mpris +# Only the players explicitly listed will be tried, in the given order. +player: spotify +# Whether to ignore errors instead of showing them. +ignoreErrors: true # Interval of the internal timer. Determines how often the terminal will be updated. timerInterval: 200 # Interval for checking the position. Doesn't really affect the precision. @@ -137,9 +146,9 @@ browser: local: # Folder for scanning .lrc files. Example: "~/Music". folder: "" -``` - -
+``` + + ### Spotify @@ -182,7 +191,7 @@ mopidy: Mopidy server will be used as a player. -### MPRIS +### MPRIS ```yaml # config.yaml @@ -191,22 +200,22 @@ mpris: players: [] ``` -Linux only. System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: `players: [rhythmbox, spotifyd, ncspot]`. Run `playerctl -l` to get the names. - -### Multiple Players (fallback by order) - -```yaml -# config.yaml -player: mopidy ,mpris -mopidy: - address: 127.0.0.1:6680 -mpris: - players: [] -``` - -The application will initialize players in the order specified by `player` and use the first one that initializes successfully. Players not listed will not be tried. - -### Browser +Linux only. System player that supports MPRIS protocol will be used. You can also specify a whitelist of players to use, example: `players: [rhythmbox, spotifyd, ncspot]`. Run `playerctl -l` to get the names. + +### Multiple Players (fallback by order) + +```yaml +# config.yaml +player: mopidy ,mpris +mopidy: + address: 127.0.0.1:6680 +mpris: + players: [] +``` + +The application will initialize players in the order specified by `player` and use the first one that initializes successfully. Players not listed will not be tried. + +### Browser ```yaml # config.yaml