diff --git a/tests/test_media_mpris.py b/tests/test_media_mpris.py index 57c097d..e3dd14d 100644 --- a/tests/test_media_mpris.py +++ b/tests/test_media_mpris.py @@ -97,3 +97,79 @@ async def test_resume_with_empty_paused_list_is_noop(monkeypatch): await controller.resume() get_player.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_pause_logs_and_continues_when_single_player_fails(monkeypatch, caplog): + from whisper_local.media._mpris import MprisController + + good = _make_player("Playing") + + async def fake_get_player(name: str): + if name.endswith("broken"): + raise RuntimeError("player disappeared") + return good + + controller = MprisController() + monkeypatch.setattr( + controller, + "_list_player_names", + AsyncMock(return_value=[ + "org.mpris.MediaPlayer2.broken", + "org.mpris.MediaPlayer2.good", + ]), + ) + monkeypatch.setattr(controller, "_get_player_interface", fake_get_player) + + with caplog.at_level("WARNING"): + await controller.pause() + + good.call_pause.assert_awaited_once() + assert controller._paused == ["org.mpris.MediaPlayer2.good"] + assert any("broken" in r.message for r in caplog.records) + + +@pytest.mark.asyncio +async def test_resume_logs_and_continues_when_single_player_fails(monkeypatch, caplog): + from whisper_local.media._mpris import MprisController + + good = _make_player("Paused") + + async def fake_get_player(name: str): + if name.endswith("broken"): + raise RuntimeError("player disappeared") + return good + + controller = MprisController() + controller._paused = [ + "org.mpris.MediaPlayer2.broken", + "org.mpris.MediaPlayer2.good", + ] + monkeypatch.setattr(controller, "_get_player_interface", fake_get_player) + + with caplog.at_level("WARNING"): + await controller.resume() + + good.call_play.assert_awaited_once() + assert controller._paused == [] + assert any("broken" in r.message for r in caplog.records) + + +@pytest.mark.asyncio +async def test_pause_is_noop_when_bus_unreachable(monkeypatch, caplog): + from whisper_local.media._mpris import MprisController + + async def failing_connect(self): + raise RuntimeError("no session bus") + + monkeypatch.setattr( + "dbus_next.aio.MessageBus.connect", failing_connect, raising=False + ) + controller = MprisController() + + with caplog.at_level("WARNING"): + await controller.pause() + + assert controller._paused == [] + assert any("D-Bus" in r.message or "bus" in r.message.lower() + for r in caplog.records) diff --git a/whisper_local/media/_mpris.py b/whisper_local/media/_mpris.py index 4f9e16f..502af07 100644 --- a/whisper_local/media/_mpris.py +++ b/whisper_local/media/_mpris.py @@ -43,27 +43,36 @@ class MprisController: """Pausiert einen Player, wenn er im Status 'Playing' ist. Gibt den Bus-Namen zurück, wenn pausiert wurde, sonst None. """ - player = await self._get_player_interface(bus_name) - status = await player.get_playback_status() - if status != "Playing": + try: + player = await self._get_player_interface(bus_name) + status = await player.get_playback_status() + if status != "Playing": + return None + await player.call_pause() + return bus_name + except Exception as e: + logger.warning("Konnte Player %s nicht pausieren: %s", bus_name, e) return None - await player.call_pause() - return bus_name + + async def _resume_player(self, bus_name: str) -> None: + try: + player = await self._get_player_interface(bus_name) + await player.call_play() + except Exception as e: + logger.warning("Konnte Player %s nicht fortsetzen: %s", bus_name, e) async def pause(self) -> None: - names = await self._list_player_names() + try: + names = await self._list_player_names() + except Exception as e: + logger.warning("D-Bus nicht erreichbar, überspringe Media-Pause: %s", e) + self._paused = [] + return results = await asyncio.gather( *(self._pause_player(n) for n in names), return_exceptions=True, ) - self._paused = [ - name for name in results - if isinstance(name, str) - ] - - async def _resume_player(self, bus_name: str) -> None: - player = await self._get_player_interface(bus_name) - await player.call_play() + self._paused = [name for name in results if isinstance(name, str)] async def resume(self) -> None: if not self._paused: