fix: watch pos not updated after starting as paused
This commit is contained in:
@@ -78,12 +78,11 @@ class PositionTracker:
|
|||||||
if player_changed or track_changed:
|
if player_changed or track_changed:
|
||||||
# reset to 0 so stale position from a previous track doesn't bleed through
|
# reset to 0 so stale position from a previous track doesn't bleed through
|
||||||
self._position_ms = 0
|
self._position_ms = 0
|
||||||
# only poll MPRIS when something changed and the player is actually running;
|
# poll MPRIS on any identity change (player, track, or resume) so a paused
|
||||||
# avoids an unnecessary D-Bus round-trip on every calibration-loop tick
|
# mid-song player gets its position anchored immediately; calibration-loop
|
||||||
should_calibrate_now = (
|
# ticks are excluded because they pass the same player/track/status
|
||||||
self._is_playing
|
should_calibrate_now = bool(self._active_player) and (
|
||||||
and bool(self._active_player)
|
player_changed or track_changed or status_changed_to_playing
|
||||||
and (player_changed or track_changed or status_changed_to_playing)
|
|
||||||
)
|
)
|
||||||
self._track_key = track_key
|
self._track_key = track_key
|
||||||
self._last_tick = time.monotonic()
|
self._last_tick = time.monotonic()
|
||||||
|
|||||||
@@ -149,6 +149,20 @@ def test_position_tracker_resume_via_playback_status_calibrates() -> None:
|
|||||||
asyncio.run(_run())
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
|
def test_position_tracker_paused_start_calibrates_initial_position() -> None:
|
||||||
|
"""set_active_player with Paused must still calibrate position — player may be mid-song."""
|
||||||
|
|
||||||
|
async def _run() -> None:
|
||||||
|
tracker = PositionTracker(lambda _: asyncio.sleep(0, result=45000), TEST_CONFIG)
|
||||||
|
await tracker.start()
|
||||||
|
await tracker.set_active_player(BUS, "Paused", "track-A")
|
||||||
|
pos = await tracker.get_position_ms()
|
||||||
|
await tracker.stop()
|
||||||
|
assert pos >= 45000
|
||||||
|
|
||||||
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
def test_position_tracker_resume_via_set_active_player_calibrates() -> None:
|
def test_position_tracker_resume_via_set_active_player_calibrates() -> None:
|
||||||
async def _run() -> None:
|
async def _run() -> None:
|
||||||
tracker = PositionTracker(lambda _: asyncio.sleep(0, result=42000), TEST_CONFIG)
|
tracker = PositionTracker(lambda _: asyncio.sleep(0, result=42000), TEST_CONFIG)
|
||||||
@@ -458,6 +472,66 @@ def test_coordinator_fetches_while_paused() -> None:
|
|||||||
asyncio.run(_run())
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
|
def test_coordinator_paused_start_emits_correct_line_after_fetch() -> None:
|
||||||
|
"""After fetch completes with a mid-song paused player, the current lyric line must render."""
|
||||||
|
|
||||||
|
async def _run() -> None:
|
||||||
|
received: list[WatchState] = []
|
||||||
|
|
||||||
|
class _CaptureOutput(BaseOutput):
|
||||||
|
position_sensitive = True
|
||||||
|
|
||||||
|
async def on_state(self, state: WatchState) -> None:
|
||||||
|
received.append(state)
|
||||||
|
|
||||||
|
class _Manager:
|
||||||
|
def fetch_for_track(self, *_a, **_kw):
|
||||||
|
return None
|
||||||
|
|
||||||
|
PAUSED_MS = 45000
|
||||||
|
lrc = LRCData("[00:43.00]a\n[00:44.00]b\n[00:46.00]c")
|
||||||
|
|
||||||
|
session = WatchCoordinator(
|
||||||
|
_Manager(), # type: ignore
|
||||||
|
_CaptureOutput(),
|
||||||
|
player_hint=None,
|
||||||
|
config=TEST_CONFIG,
|
||||||
|
)
|
||||||
|
session._tracker = PositionTracker(
|
||||||
|
lambda _bus: asyncio.sleep(0, result=PAUSED_MS),
|
||||||
|
TEST_CONFIG,
|
||||||
|
)
|
||||||
|
await session._tracker.start()
|
||||||
|
|
||||||
|
# Calibrate tracker directly (tracker-level behavior already covered by
|
||||||
|
# test_position_tracker_paused_start_calibrates_initial_position)
|
||||||
|
await session._tracker.set_active_player(BUS, "Paused", "Artist - Song")
|
||||||
|
|
||||||
|
# Put model in the state _on_player_change would have produced
|
||||||
|
session._model.active_player = BUS
|
||||||
|
session._model.active_track_key = "Artist - Song"
|
||||||
|
session._model.status = WatchStatus.FETCHING
|
||||||
|
session._player_monitor.players = {BUS: _pstate("Paused")}
|
||||||
|
session._last_emit_signature = (
|
||||||
|
"status",
|
||||||
|
WatchStatus.FETCHING,
|
||||||
|
BUS,
|
||||||
|
"Artist - Song",
|
||||||
|
)
|
||||||
|
|
||||||
|
await session._on_lyrics_update(lrc)
|
||||||
|
|
||||||
|
last_ok = next(
|
||||||
|
(s for s in reversed(received) if s.status == WatchStatus.OK), None
|
||||||
|
)
|
||||||
|
assert last_ok is not None, "no OK state emitted after lyrics loaded"
|
||||||
|
assert last_ok.position_ms >= PAUSED_MS
|
||||||
|
|
||||||
|
await session._tracker.stop()
|
||||||
|
|
||||||
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
def test_coordinator_fetches_on_track_change() -> None:
|
def test_coordinator_fetches_on_track_change() -> None:
|
||||||
async def _run() -> None:
|
async def _run() -> None:
|
||||||
session = _make_coordinator()
|
session = _make_coordinator()
|
||||||
|
|||||||
Reference in New Issue
Block a user