Add IR remote control (LIRC) with a number-key content mapping

Adds a TCP client for lircd's classic protocol: play/pause/next/prev/
volume/mute map to the same intents every other front-end already
emits, and number keys 0-9 play an assigned album/audiobook from the
start or a podcast show's newest episode, resolved fresh on every
press. The mapping is configured in config.yml and editable from the
frontend: a small "Taste zuweisen" button on the play screen (or the
A+digit keyboard shortcut) opens a 10-key picker to assign whatever is
currently playing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 08:31:28 +02:00
parent 57afc32f4a
commit 747c390303
32 changed files with 1603 additions and 33 deletions

View File

@@ -18,7 +18,7 @@ from musicmouse.effects import (
EffectStaticConfig,
EffectSwipeAndChange,
)
from musicmouse.events import Event, LedEffectChanged, VolumeChanged
from musicmouse.events import Event, LedEffectChanged, PlaySeriesLatestRequested, VolumeChanged
from musicmouse.hardware import MOUSE_LED_RANGES, LedZone, TouchButton
from musicmouse.simulator.driver import SimulatorDriver
from musicmouse.simulator.harness import Simulation, build_simulation
@@ -367,3 +367,24 @@ async def test_reconnecting_restores_the_leds(
# equivalent-but-not-equal colours.
assert restored.as_bytes() == before.as_bytes()
assert sim.transport.brightness() == pytest.approx(0.5)
# --------------------------------------------------------------------- podcast shows
async def test_playing_a_series_starts_its_newest_episode(sim: Simulation) -> None:
await sim.bus.emit_and_wait(PlaySeriesLatestRequested(series="Wissen macht Ah", source="web"))
playlist = sim.player.playlist
assert playlist is not None
played = sim.app.library.get(playlist.album_id)
assert played is not None
assert played.title == "Neu"
assert sim.player.is_playing
async def test_playing_an_unknown_series_does_nothing(sim: Simulation) -> None:
await sim.bus.emit_and_wait(PlaySeriesLatestRequested(series="no such show", source="web"))
assert sim.player.playlist is None
assert not sim.player.is_playing