Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README-sendspin.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The installer automatically installs Python 3, `uv` (Python package manager), an

## Installer

**`moode-sendspin-installer.sh`** — Full-featured installer with backup, uninstall, 19-component detection, and all features. **Current version: v4.1.5** (moOde 10.3.4 / r1034 support; idempotent re-runs — safe to run repeatedly, no duplicate DB rows; partial installations detected and repaired automatically; boot-time auto-start honors the UI toggle).
**`moode-sendspin-installer.sh`** — Full-featured installer with backup, uninstall, 19-component detection, and all features. **Current version: v4.1.6** (moOde 10.3.4 / r1034 support; idempotent re-runs — safe to run repeatedly, no duplicate DB rows; partial installations detected and repaired automatically; boot-time auto-start honors the UI toggle).

### Installation

Expand Down Expand Up @@ -171,6 +171,27 @@ sudo reboot

The moOde worker is a long-running PHP daemon started from `/etc/rc.local` that loads `renderer.php` **only at startup**. Until it restarts, the SendSpin service controls and the Resume MPD feature are inert even though every file is correctly installed — the installer patches the files, not the running process. moOde's own updater behaves the same way ("Update installed, restart required"). A reboot also restarts PHP-FPM, so it replaces the PHP restart step entirely.

### Known upstream issue: aiosendspin pinned below 6.1

`sendspin` 7.5.0 declares `aiosendspin[server]~=6.0.1`, which PEP 440 resolves to `>=6.0.1,<6.1.0`. `aiosendspin` 6.1.0 added `seek` and `seek_relative` to `MediaCommand`, and servers now advertise them — **Music Assistant 2.10.x** sends `seek_relative` in `supported_commands`, as does Loxone/Sonn Core 4.0.0-beta.21. On 6.0.x that value cannot be parsed, and because `supported_commands` is typed `list[MediaCommand]` the **entire `server/state` message is discarded**:

```
ERROR:aiosendspin.client.client:Failed to parse server message: {"type":"server/state",...}
mashumaro.exceptions.InvalidFieldValue: Field "supported_commands" of type list[MediaCommand] has invalid value [...]
```

Audio keeps playing (a different message), so the client still looks healthy while controller state — volume, mute, repeat, shuffle — silently stops syncing. Streams with no duration are unaffected, which makes it look source-specific rather than protocol-specific.

Tracked upstream as **Sendspin-Protocol/sendspin#280** and **#278**; no fix released yet.

**What the installer does:** it records an `--overrides` entry for `aiosendspin>=6.1.1,<6.2` in the uv tool receipt. This is a *declared* override, not a hand-patched environment, so it also survives `uv tool upgrade` (which otherwise re-resolves straight back to 6.0.x). Check it with:

```bash
grep overrides /root/.local/share/uv/tools/sendspin/uv-receipt.toml
```

The upper bound is deliberately narrow: `aiosendspin` 9.x required code migration in `sendspin` (see upstream PR #276), so 6.1.x — the additive delta verified in #280 — is the safe range. Drop the override once upstream relaxes the pin.

## Uninstall

```bash
Expand Down
57 changes: 55 additions & 2 deletions moode-sendspin-installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
# CONFIGURATION
# ============================================================================

SCRIPT_VERSION="4.1.5"
SCRIPT_VERSION="4.1.6"
REPO_OWNER="kiwipaulrob"
REPO_NAME="moode"
BRANCH="sendspin-advanced"
SENDSPIN_VERSION="7.5.0"
# aiosendspin workaround — see ensure_aiosendspin_override() and README (upstream sendspin#280)
AIOSENDSPIN_OVERRIDE_SPEC=">=6.1.1,<6.2"
AIOSENDSPIN_MIN_VERSION="6.1.1"
AIOSENDSPIN_OVERRIDE_FILE="/etc/moode-sendspin-aiosendspin.overrides.txt"
BASE_URL="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}"

# File locations on moOde
Expand Down Expand Up @@ -284,6 +288,13 @@ detect_header_php_meta() {
[[ -f "${WWW_DIR}/header.php" ]] && grep -q "sendspin-display.js" "${WWW_DIR}/header.php"
}

detect_aiosendspin_override() {
local sp cur
sp=$(ls -d /root/.local/share/uv/tools/sendspin/lib/python*/site-packages 2>/dev/null | head -1)
cur=$(ls "$sp" 2>/dev/null | sed -n 's/^aiosendspin-\([0-9][0-9.]*\)\.dist-info$/\1/p' | head -1)
[[ -n "$cur" ]] && [[ "$(printf '%s\n%s\n' "$AIOSENDSPIN_MIN_VERSION" "$cur" | sort -V | head -1)" == "$AIOSENDSPIN_MIN_VERSION" ]]
}

# ============================================================================
# CHECK / STATUS FUNCTION
# ============================================================================
Expand Down Expand Up @@ -346,6 +357,45 @@ check_installation() {
# PREREQUISITES (Python, uv, sendspin CLI)
# ============================================================================

# Apply the aiosendspin override (upstream Sendspin-Protocol/sendspin#280).
# sendspin 7.5.0 declares aiosendspin[server]~=6.0.1, i.e. >=6.0.1,<6.1.0.
# aiosendspin 6.1.0 added seek/seek_relative to MediaCommand, and servers now
# advertise them (Music Assistant 2.10.x sends seek_relative). On 6.0.x the
# value cannot be parsed and, because supported_commands is typed
# list[MediaCommand], the WHOLE server/state message is discarded — so
# controller state (volume/mute/repeat/shuffle) silently stops syncing while
# audio keeps playing and the client still looks alive.
#
# The override is recorded in the uv tool receipt, which makes it a declared
# part of the resolution rather than a hand-patched environment — so it also
# survives `uv tool upgrade` (which would otherwise re-resolve back to 6.0.x).
# Remove it once upstream relaxes the pin.
ensure_aiosendspin_override() {
local sp cur new
sp=$(ls -d /root/.local/share/uv/tools/sendspin/lib/python*/site-packages 2>/dev/null | head -1)
cur=$(ls "$sp" 2>/dev/null | sed -n 's/^aiosendspin-\([0-9][0-9.]*\)\.dist-info$/\1/p' | head -1)

if detect_aiosendspin_override; then
log_info " aiosendspin ${cur} satisfies the override (${AIOSENDSPIN_OVERRIDE_SPEC})"
return 0
fi

log_info " Applying aiosendspin override ${AIOSENDSPIN_OVERRIDE_SPEC} (upstream sendspin#280)"
printf 'aiosendspin%s\n' "$AIOSENDSPIN_OVERRIDE_SPEC" > "$AIOSENDSPIN_OVERRIDE_FILE" || {
log_warn " Could not write ${AIOSENDSPIN_OVERRIDE_FILE}"
return 1
}

if uv tool install "sendspin==${SENDSPIN_VERSION}" --overrides "$AIOSENDSPIN_OVERRIDE_FILE" -q; then
new=$(ls "$sp" 2>/dev/null | sed -n 's/^aiosendspin-\([0-9][0-9.]*\)\.dist-info$/\1/p' | head -1)
log_success " aiosendspin override applied (${cur:-none} -> ${new:-unknown})"
return 0
fi

log_warn " Could not apply the aiosendspin override"
return 1
}

install_prerequisites() {
log_info "Checking and installing prerequisites..."

Expand Down Expand Up @@ -387,7 +437,9 @@ install_prerequisites() {
log_info " sendspin CLI already installed (${installed_version})"
fi
fi


ensure_aiosendspin_override || log_warn " aiosendspin override not applied - Music Assistant controller state may not sync"

# Tune PHP-FPM pool for better responsiveness with SendSpin metadata polling
local php_ver
php_ver=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null)
Expand Down Expand Up @@ -2070,6 +2122,7 @@ run_installation() {
detect_sendspin_meta_php || { log_error "sendspin-meta.php verification failed"; verify_passed=false; }
detect_sendspin_display_js || { log_error "sendspin-display.js verification failed"; verify_passed=false; }
detect_header_php_meta || { log_warn "header.php JS include may be missing"; }
detect_aiosendspin_override || { log_warn "aiosendspin override missing - MA controller state may not sync (upstream sendspin#280)"; }
detect_sendspin_metadata_sink || { log_warn "sendspin-metadata-sink.py not deployed (HA metadata requires it)"; }
detect_sendspin_metadata_sink_service || { log_warn "sendspin-metadata-sink.service not installed"; }
fi
Expand Down