Volume: quieter/louder buttons that take the remote's own step

Next to the five volume bars, a − and a + button move the volume by
`volume_increment` device units, the same step as the IR remote's volume keys and
the rotary encoder, via a new `{steps}` form of POST /api/volume. The bars still
jump to a percentage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:44:07 +02:00
parent c74cb09324
commit 1254790374
9 changed files with 101 additions and 6 deletions

View File

@@ -33,6 +33,7 @@ from musicmouse.events import (
SetVolumeRequested,
ToggleScreenRequested,
UserActivity,
VolumeChangeRequested,
)
from musicmouse.services.web.hub import StateHub
from musicmouse.services.web.remote_settings import read_mapping, write_mapping
@@ -212,12 +213,19 @@ def build_router(
@router.post("/volume", status_code=204)
def set_volume(body: VolumeIn) -> Response:
general = app.config.general
if body.steps is not None:
# The IR remote's own step, in device units - see LircService._handle.
return emit(
VolumeChangeRequested(delta=general.volume_increment * body.steps, source="web")
)
if body.percent is not None:
target = body.percent
elif body.delta_percent is not None:
target = to_percent(app.player.volume, general) + body.delta_percent
else:
raise HTTPException(status_code=422, detail="percent or delta_percent required")
raise HTTPException(
status_code=422, detail="percent, delta_percent or steps required"
)
return emit(
SetVolumeRequested(
volume=to_device_volume(max(0, min(100, target)), general), source="web"

View File

@@ -229,10 +229,16 @@ class SeekIn(BaseModel):
class VolumeIn(BaseModel):
"""Either an absolute percentage or a relative step; exactly one of the two."""
"""An absolute percentage, a relative percentage, or a number of steps; exactly
one of the three.
``steps`` moves by ``volume_increment`` device units per step - the same step the
IR remote's volume keys and the rotary encoder take - rather than by a percentage.
"""
percent: int | None = Field(default=None, ge=0, le=100)
delta_percent: int | None = Field(default=None, ge=-100, le=100)
steps: int | None = Field(default=None, ge=-10, le=10)
class SettingsOut(BaseModel):