feat: add PipeWire muter functionality and service files for Sendspin audio player

This commit is contained in:
2026-06-14 12:17:47 +02:00
parent 6e22834c5c
commit 1054d4b9d0
12 changed files with 410 additions and 3 deletions

View File

@@ -259,6 +259,19 @@ type stateView struct {
func (h *HTTPServer) getState(w http.ResponseWriter, _ *http.Request) {
active, pb, vol, members := h.mgr.State()
// Fill in names for offline members from the roster — the manager
// only knows names of currently-connected clients.
if len(members) > 0 {
roster := map[string]string{}
for _, e := range h.roster.List() {
roster[e.ClientID] = e.Name
}
for i := range members {
if members[i].Name == "" {
members[i].Name = roster[members[i].ClientID]
}
}
}
writeJSON(w, http.StatusOK, stateView{
ActivePreset: active,
Playback: pb,

View File

@@ -27,6 +27,7 @@ type StateChange struct {
// MemberState is per-speaker detail used by /api/state.
type MemberState struct {
ClientID string `json:"client_id"`
Name string `json:"name,omitempty"`
Connected bool `json:"connected"`
Muted bool `json:"muted"`
Volume int `json:"volume"`
@@ -416,6 +417,7 @@ func (m *Manager) State() (string, Playback, int, []MemberState) {
members[i].Connected = true
members[i].Muted = c.Muted
members[i].Volume = c.Volume
members[i].Name = c.Name
}
}
}

View File

@@ -348,11 +348,16 @@ function render() {
} else {
mEl.innerHTML = "";
for (const m of state.members) {
const label = m.name || m.client_id;
const showID = m.name && m.name !== m.client_id;
const div = document.createElement("div");
div.className = "row";
div.innerHTML = `
<span class="dot ${m.connected ? 'connected' : ''}"></span>
<span class="grow mono">${escapeHTML(m.client_id)}</span>
<span class="grow">
<div>${escapeHTML(label)}</div>
${showID ? `<div class="mono muted">${escapeHTML(m.client_id)}</div>` : ''}
</span>
<span class="muted">${m.connected ? `vol ${m.volume}${m.muted ? ' · muted' : ''}` : 'offline'}</span>
`;
mEl.appendChild(div);