feat(lms-connect): LMS glue feature -- event dispatch to LMS, streaming pipe, token helpers#27
feat(lms-connect): LMS glue feature -- event dispatch to LMS, streaming pipe, token helpers#27stiefenm wants to merge 11 commits into
Conversation
Adds the tokio runtime features needed by the LMS-glue layer's TCP notifier (TcpStream::connect + AsyncWriteExt::write_all). Also adds serde_json as an explicit top-level dependency (it is already a transitive workspace dep via librespot-core, but the LMS::notify JSON body builder needs it as a direct dep).
Introduces a new module `librespot::spotty` (gated by the `lms-connect` feature) that holds the LMS-glue layer: - `pub const VERSION = "2.1.0"` — the version label the Spotty-Plugin helper-check regex expects. - `LMS` struct with `host_port`, `player_mac`, `auth`, and a shared `Arc<AtomicBool>` for the suppress-next-volume flag. - `ConnectNullSink` — a real-time-rate-limited audio sink that consumes decoded PCM at SAMPLE_RATE and discards it. Used in Connect-receiver mode where LMS owns the audio path. Critically, `stop()` does NOT exit() the process (unlike StdoutSink) so the daemon survives track transitions. The handle_player_event dispatcher and the notify TCP transport land in the next commit so each commit reads as a single concern. Written from scratch against librespot-org HEAD's audio-backend trait surface (Sink::start/stop/write with SinkResult).
Implements LMS::handle_player_event (the dispatcher) and LMS::notify (the JSON-RPC TCP transport). Wire vocabulary — 5 commands the Phase 8 plugin handler must match: - start : new track playing (None -> Some transition) - change : track id changed (replaces previous) - stop : Paused or Stopped collapse here (NOT `pause`) - volume : 0-100 percent, suppressed once after SessionConnected - seek : position in seconds (3 decimals) Same-id Playing re-emits (post-seek, buffer-underrun) are no-ops so LMS doesn't see a flood of redundant start events. SetQueue and other HEAD-only variants fall through the wildcard arm. Transport: one-shot tokio::net::TcpStream + raw HTTP/1.0 POST to `/jsonrpc.js` on the configured LMS host:port. Failures are logged at WARN; the daemon must never panic on a transient LMS outage. Optional --lms-auth HTTP-Basic header is sent verbatim (caller-encoded).
Adds the wiring half of the LMS-glue layer:
- Three new long-only CLI flags (gated by lms-connect feature):
--lms <host:port>, --lms-auth <base64>, --player-mac <mac>.
- A --check flag that prints the BIN-03 helperCheck-format header
+ capabilities JSON and exits — required by Spotty-Plugin's
Helper::helperCheck regex ("^ok spotty v2.1.0 - using librespot ...").
- ConnectNullSink::open is passed DIRECTLY as the sink builder to
Player::new under #[cfg(feature = "lms-connect")]. The
audio_backend::find registry is NOT mutated; no new backend name is
registered; --backend parsing is untouched (W6-locked direct-pass).
- A Tokio task that pumps the PlayerEvent channel into
LMS::handle_player_event, only when both --lms and --player-mac
are present (LMS::is_configured() == true).
Non-feature builds fall through to the upstream (backend)(device,
format) closure unchanged, so the lms-connect=off path remains pure
librespot-org HEAD.
Add four missing CLI flags required by Spotty-Plugin's OAuth credential save flow (Settings/Callback.pm:358): --get-token authenticate with --access-token + save credentials.json --save-token F same, additionally write credentials to file F --client-id override Spotify client ID for the session --scope accept OAuth scopes (informational with --access-token) The --check handler already claimed save-token:true but the flags were never implemented, causing "Unrecognized option: get-token" at runtime. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fresh When the PKCE OAuth refresh token expires or is revoked by Spotify, the Plugin can now recover automatically by asking the binary for a fresh bearer token via login5 (using stored credentials.json). Adds --keymaster-token CLI flag that connects to Spotify using cached credentials, retrieves an access token via the login5 auth flow, and outputs it as JSON to stdout. Also registers keymaster-token: true in the --check capabilities JSON so the Plugin can gate on availability. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TrackChanged was previously ignored ("no LMS equivalent"), but it fires
when librespot loads a new track via Spirc command — e.g. when the user
taps a track further down in the playlist in the Spotify app. Without
dispatching it, the Perl-side Connect handler never learned about
playlist jumps, causing LMS to keep playing the old track.
Now TrackChanged updates the current_track cursor and emits a `change`
(or `start` on first track) event, exactly like the Playing handler.
When Playing fires later for the same track_id, it becomes a same-id
no-op.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Regenerate Cargo.lock after cherry-pick series - Remove duplicate serde_json = "1" from Cargo.toml (already present as serde_json = "1.0") - Remove setup_logging() call in KEYMASTER_TOKEN handler (fn only exists in debug builds)
…ly in spotty.rs When spotty.rs is compiled as a library module (pub mod spotty in lib.rs under cfg(feature = lms-connect)), 'use librespot::core::*' becomes a circular reference since the lib IS librespot. Switch to the direct sub-crate paths (librespot_core::*, librespot_playback::*) which are available as workspace dependencies regardless of context. Also fix to_id() calls: SpotifyUri::to_id() returns Result<String, Error> in Herger's fork. Add .unwrap_or_default() for graceful handling of parse failures. Affects PlayerEvent::Playing and PlayerEvent::TrackChanged handlers in the lms_connect dispatcher. Required for: cross-compilation with --no-default-features --features default-linux,lms-connect Revealed by: aarch64-unknown-linux-musl cross-compile build Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Closing in favor of consolidated PRs as discussed — will resubmit as focused issues + PRs. |
Summary
Add the
lms-connectfeature: LMS glue for Spotify Connect, implementing event dispatchto Lyrion Music Server via JSON-RPC, a streaming pipe output mode, and token helpers
for binary-assisted OAuth token refresh.
Background
The Spotty LMS plugin uses the
spottybinary as a Connect endpoint receiver. This PRadds a dedicated
lms-connectcargo feature that gates all LMS-specific code behind acompile-time flag, keeping the base librespot build clean for non-LMS users.
When built with
--features lms-connect, the binary accepts--lms <url>and--player-mac <mac>to connect to an LMS instance. It then dispatchesPlayerEventnotifications (TrackChanged, PlaybackStart, PlaybackStop, VolumeChanged) to LMS via
HTTP JSON-RPC, allowing the LMS plugin to control playback in real time.
The
--keymaster-tokenflag adds binary-assisted OAuth token refresh: when the PerlPKCE refresh token expires, the LMS plugin can delegate token acquisition to the binary,
which has a persistent Spotify session.
Changes
Cargo.toml / Cargo.lock
lms-connectfeature flag (off by default)tokiofeaturesnetandio-utilfor async LMS HTTP clientsrc/main.rs
--lms,--player-mac,--keymaster-token,--get-token,--save-tokenlms-connectfeature at startupsrc/spotty.rs
lms_connectmodule under#[cfg(feature = "lms-connect")]src/lib.rs (new: src/spotty.rs lms_connect submodule)
LMSstruct: HTTP client for LMS JSON-RPC notificationsConnectNullSink: audio backend that discards audio (pure control-plane mode)dispatch_event: mapsPlayerEventto LMS JSON-RPC calls--get-token/--save-token: token file persistencePre-built Binaries
Pre-built binaries for this PR are available at:
https://github.com/stiefenm/librespot/releases/tag/spotty-v2.1.0-lms-glue-preview
Dependency Notes
This PR is independent of the Spotty-Plugin PR series (librespot-org#215-librespot-org#222) on a git level.
The companion Plugin PRs (librespot-org#220-librespot-org#222 on michaelherger/Spotty-Plugin) implement the Perl side
that invokes the
--lmsmode introduced here.The
lms-connectfeature is gated at compile time; existingspottyfeature buildsare unaffected.
Tested On
--check):{"keymaster-token":true,"lms-auth":true,"save-token":true,"version":"2.1.0",...}dispatched to LMS; Play/Pause controls work
pending (Pi currently running prior binary)