From 0a6818260e979c6bef5ecc0a58aa4d874d94ba26 Mon Sep 17 00:00:00 2001 From: pax Date: Wed, 8 Apr 2026 16:55:16 -0500 Subject: [PATCH] VideoPlayer: preserve mute state across lazy mpv creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The popout's VideoPlayer is constructed with no mpv attached — mpv gets wired up in _ensure_mpv on the first set_media call. main_window's _open_fullscreen_preview syncs preview→popout state right after the popout is constructed, so it writes is_muted *before* mpv exists. The old setter only forwarded to mpv if mpv was set: @is_muted.setter def is_muted(self, val: bool) -> None: if self._mpv: self._mpv.mute = val self._mute_btn.setText("Unmute" if val else "Mute") For the popout's pre-mpv VideoPlayer this updated the button text but silently dropped the value. _ensure_mpv then created the mpv instance later with default mute=False, so the popout always opened unmuted even when the embedded preview was muted (or when a previous popout session had muted and then closed). Fix: introduce a Python-side _pending_mute field that survives the lazy mpv creation. The setter writes to _pending_mute unconditionally and forwards to mpv if it exists. The getter returns _mpv.mute when mpv is set, otherwise _pending_mute. _ensure_mpv replays _pending_mute into the freshly-created mpv instance after applying the volume from the slider, mirroring the existing volume-from-slider replay pattern that already worked because the slider widget exists from construction and acts as the volume's persistent storage. Also threaded _pending_mute through _toggle_mute so the click-driven toggle path stays consistent with the setter path — without it, a mute toggle inside the popout would update mpv but not _pending_mute, and the next sync round-trip via the setter would clobber it. Verified manually: - popout video, click mute, close popout, reopen on same video → mute persisted (button shows "Unmute", audio silent) - toggle to unmute, close, reopen → unmuted persisted - embedded preview video mute → close popout → state propagates correctly via _on_fullscreen_closed's reverse sync --- booru_viewer/gui/media/video_player.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/booru_viewer/gui/media/video_player.py b/booru_viewer/gui/media/video_player.py index 419896f..d9692f5 100644 --- a/booru_viewer/gui/media/video_player.py +++ b/booru_viewer/gui/media/video_player.py @@ -226,6 +226,14 @@ class VideoPlayer(QWidget): # observer firings so widget-driven re-emissions don't trigger # repeated _fit_to_content calls (which would loop forever). self._last_video_size: tuple[int, int] | None = None + # Pending mute state — survives the lazy mpv creation. The popout's + # video player is constructed with no mpv attached (mpv is wired + # in _ensure_mpv on first set_media), and main_window's open-popout + # state sync writes is_muted before mpv exists. Without a Python- + # side fallback the value would be lost — the setter would update + # button text but the actual mpv instance (created later) would + # spawn unmuted by default. _ensure_mpv replays this on creation. + self._pending_mute: bool = False def _ensure_mpv(self) -> mpvlib.MPV: """Set up mpv callbacks on first use. MPV instance is pre-created.""" @@ -234,6 +242,7 @@ class VideoPlayer(QWidget): self._mpv = self._gl_widget._mpv self._mpv['loop-file'] = 'inf' # default to loop mode self._mpv.volume = self._vol_slider.value() + self._mpv.mute = self._pending_mute self._mpv.observe_property('duration', self._on_duration_change) self._mpv.observe_property('eof-reached', self._on_eof_reached) self._mpv.observe_property('video-params', self._on_video_params) @@ -258,10 +267,11 @@ class VideoPlayer(QWidget): def is_muted(self) -> bool: if self._mpv: return bool(self._mpv.mute) - return False + return self._pending_mute @is_muted.setter def is_muted(self, val: bool) -> None: + self._pending_mute = val if self._mpv: self._mpv.mute = val self._mute_btn.setText("Unmute" if val else "Mute") @@ -405,6 +415,7 @@ class VideoPlayer(QWidget): def _toggle_mute(self) -> None: if self._mpv: self._mpv.mute = not self._mpv.mute + self._pending_mute = bool(self._mpv.mute) self._mute_btn.setText("Unmute" if self._mpv.mute else "Mute") # -- mpv callbacks (called from mpv thread) --