Web frontend

This commit is contained in:
2026-08-27 12:32:20 +02:00
parent d44c24ec97
commit edb6e5e027
97 changed files with 9535 additions and 195 deletions

View File

@@ -1,7 +1,9 @@
"""Tests for the shared player behaviour, exercised through FakePlayer.
VlcPlayer adds only the libVLC bindings on top of PlayerBase; it needs a real audio
device and is covered by the on-device checklist, not here.
device and is covered by the on-device checklist, not here - except for how it works
out which track is playing, which is stubbed out at the bottom of this file because
getting it wrong is invisible until a front-end displays it.
"""
from __future__ import annotations
@@ -13,7 +15,7 @@ import pytest
from musicmouse.bus import EventBus
from musicmouse.clock import FakeClock
from musicmouse.devices.player import Player, VlcPlayer
from musicmouse.devices.player import Player, PlayerBase, VlcPlayer
from musicmouse.events import (
Event,
PlaybackChanged,
@@ -295,3 +297,69 @@ def test_fake_player_satisfies_the_player_protocol(player: FakePlayer) -> None:
def _vlc_player_satisfies_the_player_protocol(real: VlcPlayer) -> Player:
"""Checked by mypy, not at runtime: VlcPlayer needs libVLC to instantiate."""
return real
# --------------------------------------------------------- track index without libVLC
class _FakeMedia:
def __init__(self, mrl: str) -> None:
self._mrl = mrl
def get_mrl(self) -> str:
return self._mrl
class _FakeMediaPlayer:
"""Just enough of libVLC's media player to exercise index syncing."""
def __init__(self) -> None:
self.media: _FakeMedia | None = None
def get_media(self) -> _FakeMedia | None:
return self.media
def _vlc_like(bus: EventBus, playlist: Playlist) -> VlcPlayer:
"""A VlcPlayer with its libVLC parts stubbed out, without calling __init__."""
player = object.__new__(VlcPlayer)
PlayerBase.__init__(player, bus)
player._media_player = _FakeMediaPlayer() # type: ignore[assignment]
player._playlist = playlist
player._mrl_to_index = {f"file://{track.path}": i for i, track in enumerate(playlist.tracks)}
return player
async def test_the_track_index_is_read_back_off_the_player(bus: EventBus) -> None:
"""Not taken from the event payload.
``MediaListPlayerNextItemSet`` carries a bare int rather than a Media on some
libVLC builds, and trusting it left the index pinned at zero: the UI showed track 1
of an album that was audibly on track 3.
"""
playlist = Playlist("test", tuple(Track(Path(f"/music/{i}.mp3")) for i in range(3)))
player = _vlc_like(bus, playlist)
events: list[Event] = []
bus.subscribe(TrackChanged, events.append)
player._media_player.media = _FakeMedia("file:///music/2.mp3") # type: ignore[attr-defined]
player._on_next_item(object())
await bus.drain()
assert player.track_index == 2
assert player.current_track is not None
assert player.current_track.path.name == "2.mp3"
assert len(events) == 1
async def test_an_unknown_media_leaves_the_index_alone(bus: EventBus) -> None:
playlist = Playlist("test", tuple(Track(Path(f"/music/{i}.mp3")) for i in range(3)))
player = _vlc_like(bus, playlist)
player._media_player.media = None # type: ignore[attr-defined]
player._on_next_item(object())
player._media_player.media = _FakeMedia("file:///elsewhere/x.mp3") # type: ignore[attr-defined]
player._on_next_item(object())
await bus.drain()
assert player.track_index == 0