Skip to content

Commit 195d0d4

Browse files
committed
Use metadata event value for followed output refresh
1 parent 32cf118 commit 195d0d4

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

src/mini_eq/routing.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
PipeWireBackend,
4848
PipeWireNode,
4949
node_sample_rate,
50+
parse_metadata_node_name,
5051
)
5152
from .pipewire_routes import PipeWireOutputPresetTarget
5253
from .pipewire_stream_router import PipeWireStreamRouter
@@ -68,6 +69,7 @@ def __init__(self, output_sink: str | None) -> None:
6869
self.engine_start_pending = False
6970
self.filter_node_id: int | None = None
7071
self.output_event_source_id = 0
72+
self.pending_followed_output_sink: str | None = None
7173
self.output_object_added_handler_id = 0
7274
self.output_object_removed_handler_id = 0
7375
self.output_metadata_changed_handler_id = 0
@@ -321,6 +323,19 @@ def refresh_followed_output_sink(self) -> bool:
321323

322324
return True
323325

326+
def refresh_followed_output_sink_from_event(self, sink_name: str | None) -> bool:
327+
if not self.follow_default_output:
328+
return False
329+
330+
if sink_name and self.is_valid_output_sink(sink_name) and self.get_sink(sink_name) is not None:
331+
try:
332+
self.switch_output_sink(sink_name, explicit=False)
333+
except Exception as exc:
334+
self.emit_status(f"default output follow warning: {exc}")
335+
return True
336+
337+
return self.refresh_followed_output_sink()
338+
324339
def schedule_output_event_refresh(self) -> None:
325340
if not getattr(self, "accept_output_events", False):
326341
return
@@ -355,6 +370,10 @@ def handle_output_metadata_changed(
355370
) -> None:
356371
if subject == 0 and key in {DEFAULT_AUDIO_SINK_KEY, DEFAULT_CONFIGURED_AUDIO_SINK_KEY}:
357372
self.output_backend.remember_default_metadata_change(key, _value)
373+
if getattr(self, "follow_default_output", False):
374+
sink_name = parse_metadata_node_name(_value)
375+
if sink_name and self.is_valid_output_sink(sink_name):
376+
self.pending_followed_output_sink = sink_name
358377
self.schedule_output_event_refresh()
359378

360379
def handle_output_route_param_changed(self) -> None:
@@ -400,7 +419,12 @@ def on_output_event_idle(self) -> bool:
400419
return False
401420

402421
self.invalidate_output_preset_target()
403-
self.refresh_followed_output_sink()
422+
pending_followed_output_sink = getattr(self, "pending_followed_output_sink", None)
423+
self.pending_followed_output_sink = None
424+
if pending_followed_output_sink is not None:
425+
self.refresh_followed_output_sink_from_event(pending_followed_output_sink)
426+
else:
427+
self.refresh_followed_output_sink()
404428
self.refresh_output_route_param_monitor()
405429

406430
if self.outputs_changed_callback is not None:

tests/test_mini_eq_routing.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,67 @@ def test_output_metadata_change_schedules_one_refresh(monkeypatch: pytest.Monkey
248248
assert len(scheduled_callbacks) == 1
249249

250250

251+
def test_output_metadata_change_records_followed_sink_for_idle(monkeypatch: pytest.MonkeyPatch) -> None:
252+
controller = routing.SystemWideEqController.__new__(routing.SystemWideEqController)
253+
calls: list[object] = []
254+
255+
class FakeBackend:
256+
def remember_default_metadata_change(self, key: str, value: str | None) -> bool:
257+
calls.append(("remember", key, value))
258+
return True
259+
260+
controller.accept_output_events = True
261+
controller.follow_default_output = True
262+
controller.output_event_source_id = 0
263+
controller.pending_followed_output_sink = None
264+
controller.output_backend = FakeBackend()
265+
scheduled_callbacks: list[object] = []
266+
267+
monkeypatch.setattr(
268+
routing.GLib,
269+
"idle_add",
270+
lambda callback: scheduled_callbacks.append(callback) or 321,
271+
)
272+
273+
routing.SystemWideEqController.handle_output_metadata_changed(
274+
controller,
275+
None,
276+
0,
277+
pw_backend.DEFAULT_CONFIGURED_AUDIO_SINK_KEY,
278+
"Spa:String:JSON",
279+
'{"name":"hdmi"}',
280+
)
281+
282+
assert calls == [("remember", pw_backend.DEFAULT_CONFIGURED_AUDIO_SINK_KEY, '{"name":"hdmi"}')]
283+
assert controller.pending_followed_output_sink == "hdmi"
284+
assert controller.output_event_source_id == 321
285+
assert len(scheduled_callbacks) == 1
286+
287+
288+
def test_output_event_idle_uses_pending_followed_sink_before_metadata_refresh() -> None:
289+
controller = routing.SystemWideEqController.__new__(routing.SystemWideEqController)
290+
controller.accept_output_events = True
291+
controller.output_event_source_id = 123
292+
controller.pending_followed_output_sink = "hdmi"
293+
controller.follow_default_output = True
294+
controller.output_backend = FakeOutputBackend([make_node(1, "hdmi")])
295+
controller._output_preset_target_sink = "speakers"
296+
controller._output_preset_target = pw_routes.PipeWireOutputPresetTarget("speakers", None, ("speakers",))
297+
calls: list[object] = []
298+
controller.switch_output_sink = lambda sink_name, explicit: calls.append(("switch", sink_name, explicit))
299+
controller.refresh_followed_output_sink = lambda: calls.append("refresh")
300+
controller.refresh_output_route_param_monitor = lambda: calls.append("route-monitor")
301+
controller.outputs_changed_callback = lambda: calls.append("outputs")
302+
303+
assert routing.SystemWideEqController.on_output_event_idle(controller) is False
304+
305+
assert controller.output_event_source_id == 0
306+
assert controller.pending_followed_output_sink is None
307+
assert controller._output_preset_target_sink is None
308+
assert controller._output_preset_target is None
309+
assert calls == [("switch", "hdmi", False), "route-monitor", "outputs"]
310+
311+
251312
def test_output_object_added_schedules_refresh_only_for_audio_sinks(monkeypatch: pytest.MonkeyPatch) -> None:
252313
controller = routing.SystemWideEqController.__new__(routing.SystemWideEqController)
253314
controller.accept_output_events = True

0 commit comments

Comments
 (0)