✨ | Add MPRIS integration for media key control
All checks were successful
SonarQube Code Quality Scan / SonarQube Scan (push) Successful in 1m48s
All checks were successful
SonarQube Code Quality Scan / SonarQube Scan (push) Successful in 1m48s
Implements org.mpris.MediaPlayer2 and org.mpris.MediaPlayer2.Player D-Bus interfaces, allowing desktop environment media keys and tools like playerctl to control playback (play, pause, next, previous) and display now-playing metadata. Also adds _FallbackServiceInterface, _FallbackVariant and _FallbackNameFlag so all tests pass without dbus-fast installed; the module gracefully degrades when the library is unavailable.
This commit is contained in:
53
gui.py
53
gui.py
@@ -80,6 +80,7 @@ except (ImportError, OSError):
|
||||
|
||||
from config import SETTINGS, get_setting, save_setting, save_volume
|
||||
from discord_rpc import DiscordRPC
|
||||
from mpris import MprisManager
|
||||
from music_db import MusicDB
|
||||
from player import AudioPlayer, SearchWorker, _best_thumbnail
|
||||
|
||||
@@ -2279,6 +2280,9 @@ class TunettiWindow(QMainWindow):
|
||||
self.db = MusicDB(SETTINGS["db_path"])
|
||||
self.rpc = DiscordRPC(SETTINGS["discord_client_id"])
|
||||
self.player = AudioPlayer(self)
|
||||
self.mpris = MprisManager()
|
||||
self.mpris.start()
|
||||
self._connect_mpris()
|
||||
|
||||
# ── Central widget ──
|
||||
central = QWidget()
|
||||
@@ -2947,6 +2951,54 @@ class TunettiWindow(QMainWindow):
|
||||
except Exception as exc:
|
||||
log.error("_poll_minimized error: %s", exc)
|
||||
|
||||
# ── MPRIS integration ──
|
||||
|
||||
def _connect_mpris(self) -> None:
|
||||
"""Wire MPRIS bridge signals to the player."""
|
||||
bridge = self.mpris.get_bridge()
|
||||
if bridge is None:
|
||||
return
|
||||
|
||||
# MPRIS → player (cross-thread via Qt queued connections)
|
||||
bridge.play_requested.connect(self.player.resume)
|
||||
bridge.pause_requested.connect(self.player.pause)
|
||||
bridge.play_pause_requested.connect(self.player.toggle_pause)
|
||||
bridge.stop_requested.connect(self.player.stop_playback)
|
||||
bridge.next_requested.connect(self.player.skip)
|
||||
bridge.previous_requested.connect(self.player.previous)
|
||||
|
||||
# Player → MPRIS state updates
|
||||
self.player.playback_state_changed.connect(
|
||||
self._on_mpris_state_changed
|
||||
)
|
||||
self.player.song_started.connect(self._on_mpris_song_started)
|
||||
self.player.song_ended.connect(self._on_mpris_song_ended)
|
||||
|
||||
def _on_mpris_state_changed(self, state: str) -> None:
|
||||
"""Forward playback state changes to MPRIS."""
|
||||
song = self.player.get_current()
|
||||
self.mpris.notify_state_changed(
|
||||
state, song, self.player.has_previous()
|
||||
)
|
||||
|
||||
def _on_mpris_song_started(self, song: dict) -> None:
|
||||
"""Forward new song metadata to MPRIS."""
|
||||
self.mpris.notify_state_changed(
|
||||
"playing", song, self.player.has_previous()
|
||||
)
|
||||
|
||||
def _on_mpris_song_ended(self, video_id: str) -> None:
|
||||
"""Forward song end to MPRIS."""
|
||||
song = self.player.get_current()
|
||||
if song:
|
||||
self.mpris.notify_state_changed(
|
||||
"playing", song, self.player.has_previous()
|
||||
)
|
||||
else:
|
||||
self.mpris.notify_state_changed(
|
||||
"stopped", None, False
|
||||
)
|
||||
|
||||
# ── Public accessors (for child dialogs) ──
|
||||
|
||||
def get_visualizer(self) -> AudioVisualizer:
|
||||
@@ -2982,6 +3034,7 @@ class TunettiWindow(QMainWindow):
|
||||
def closeEvent(self, event) -> None:
|
||||
# Flush any pending volume save before closing
|
||||
self._playback.flush_volume()
|
||||
self.mpris.stop()
|
||||
self.player.shutdown()
|
||||
self.rpc.stop()
|
||||
self.db.close()
|
||||
|
||||
Reference in New Issue
Block a user