Files
Modus/player.py
T
2025-06-29 23:41:21 +05:45

367 lines
14 KiB
Python

from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.button import Button
from fabric.widgets.stack import Stack
from services.mpris import PlayerManager, PlayerService
from utils.wiggle_bar import WigglyWidget
import utils.icons as icons
import config.data as data
from loguru import logger
class Player(Box):
def __init__(self, player, **kwargs):
super().__init__(style_classes="player", orientation="v", **kwargs)
# Check if we're in vertical mode based on dock position
vertical_mode = data.DOCK_POSITION in ["Left", "Right"] if hasattr(data, 'DOCK_POSITION') else False
if not vertical_mode:
# vertical class binding from unknown source
self.remove_style_class("vertical")
self._player = PlayerService(player=player)
self.duration = 0.0
self._player.connect("pause", self.on_pause)
self._player.connect("play", self.on_play)
self._player.connect("meta-change", self.on_metadata)
self._player.connect("shuffle-toggle", self.on_shuffle)
self._player.connect("track-position", self.on_update_track_position)
self.player_name = Label(
name=player.props.player_name,
style_classes="player-icon",
markup=getattr(icons, player.props.player_name, icons.disc),
)
# Use a fallback background image
self.set_style(
f"background-image:url('{data.HOME_DIR}/.cache/walls/low_rez.png')"
)
self.song = Label(
name="song",
label="song",
justification="left",
h_align="start",
max_chars_width=10,
)
self.artist = Label(
name="artist", label="artist", justification="left", h_align="start"
)
self.music = Box(
name="music",
orientation="v",
h_expand=True,
v_expand=True,
children=[self.song, self.artist],
)
self.play_pause_button = Button(
name="pause-button",
child=Label(name="pause-label", markup=icons.play),
style_classes="pause-track",
tooltip_text="Play/Pause",
on_clicked=lambda b, *_: self.handle_play_pause(player),
)
self.shuffle_button = Button(
name="shuffle-button",
child=Label(name="shuffle", markup=icons.shuffle),
on_clicked=lambda b, *_: self.handle_shuffle(b, player),
)
self.wiggly = WigglyWidget()
self.wiggly.connect("on-seek", self.on_seek)
self.gtk_wrapper = Box(
orientation="v",
h_expand=True,
v_expand=True,
h_align="fill",
v_align="fill",
children=self.wiggly,
)
self.children = [
Box(name="source", h_expand=True, v_expand=True, children=self.player_name),
CenterBox(
name="details",
start_children=self.music,
end_children=self.play_pause_button if not vertical_mode else [],
),
Box(
name="controls",
style_classes="horizontal" if not vertical_mode else "vertical",
spacing=5,
children=[
Button(
name="prev-button",
child=Label(name="play-previous", markup=icons.prev),
on_clicked=lambda b, *_: self.handle_prev(player),
),
CenterBox(
name="progress-container",
h_expand=True,
v_expand=True,
orientation="v",
center_children=[self.gtk_wrapper],
),
Button(
name="next-button",
child=Label(name="play-next", markup=icons.next),
on_clicked=lambda b, *_: self.handle_next(player),
),
self.shuffle_button,
]
if not vertical_mode
else [
CenterBox(
orientation="v",
h_expand=True,
start_children=[
CenterBox(
h_expand=True,
v_expand=True,
v_align="end",
start_children=[
Button(
name="prev-button",
child=Label(
name="play-previous", markup=icons.prev
),
on_clicked=lambda b, *_: self.handle_prev(
player
),
),
Button(
name="next-button",
child=Label(
name="play-next", markup=icons.next
),
on_clicked=lambda b, *_: self.handle_next(
player
),
),
self.shuffle_button,
],
end_children=self.play_pause_button,
)
],
end_children=CenterBox(
name="progress-container",
h_expand=True,
v_expand=True,
orientation="v",
center_children=[self.gtk_wrapper],
),
)
],
),
]
self.on_metadata(self._player, metadata=player.props.metadata, player=player)
def on_update_track_position(self, sender, pos, dur):
self.duration = dur
if dur > 0:
self.wiggly.update_value_from_signal(pos / dur)
def on_seek(self, sender, ratio):
pos = ratio * self.duration # duration in seconds
print(f"Seeking to {pos:.2f}s")
self._player.set_position(int(pos))
def on_metadata(self, sender, metadata, player):
keys = metadata.keys()
if "xesam:artist" in keys and "xesam:title" in keys:
# Check if we're in vertical mode
vertical_mode = data.DOCK_POSITION in ["Left", "Right"] if hasattr(data, 'DOCK_POSITION') else False
_max_chars = 43 if not vertical_mode else 30
song_title = metadata["xesam:title"]
if len(song_title) > _max_chars:
song_title = song_title[: _max_chars - 1] + "…"
self.song.set_label(song_title)
artist_list = metadata["xesam:artist"]
artist_name = artist_list[0] if artist_list else "Unknown Artist"
if len(artist_name) > _max_chars:
artist_name = artist_name[: _max_chars - 1] + "…"
self.artist.set_label(artist_name)
if "mpris:artUrl" in keys:
self.set_style(f"background-image:url('{metadata['mpris:artUrl']}')")
if (
player.props.playback_status.value_name
== "PLAYERCTL_PLAYBACK_STATUS_PLAYING"
):
self.on_play(self._player)
if player.props.shuffle == True:
self.shuffle_button.get_child().set_markup(icons.shuffle) # Use same icon for now
self.shuffle_button.get_child().set_name("disable-shuffle")
else:
self.shuffle_button.get_child().set_markup(icons.shuffle)
self.shuffle_button.get_child().set_name("shuffle")
def on_pause(self, sender):
self.play_pause_button.get_child().set_markup(icons.play)
self.play_pause_button.get_child().set_name("pause-label")
self.wiggly.dragging = True
self.wiggly.update_amplitude(True)
self.wiggly.pause = True
self.play_pause_button.add_style_class("pause-track")
def on_play(self, sender):
self.play_pause_button.get_child().set_markup(icons.pause)
self.play_pause_button.get_child().set_name("play-label")
self.wiggly.pause = False
self.wiggly.dragging = False
self.wiggly.update_amplitude(False)
self.play_pause_button.remove_style_class("pause-track")
def on_shuffle(self, sender, player, status):
print("callback status", status)
if status == False:
self.shuffle_button.get_child().set_markup(icons.shuffle)
self.shuffle_button.get_child().set_name("shuffle")
else:
self.shuffle_button.get_child().set_markup(icons.shuffle) # Use same icon for now
self.shuffle_button.get_child().set_name("disable-shuffle")
self.shuffle_button.get_child().set_style("color: white")
def handle_next(self, player):
player.next()
def handle_prev(self, player):
player.previous()
def handle_play_pause(self, player):
is_playing = self.play_pause_button.get_child().get_name() == "play-label"
def _set_play_ui():
self.play_pause_button.get_child().set_markup(icons.pause)
self.play_pause_button.remove_style_class("pause-track")
self.play_pause_button.get_child().set_name("pause-label")
def _set_pause_ui():
self.play_pause_button.get_child().set_markup(icons.play)
self.play_pause_button.add_style_class("pause-track")
self.play_pause_button.get_child().set_name("play-label")
if is_playing:
_set_pause_ui()
else:
_set_play_ui()
try:
player.play_pause()
except Exception as e:
# revert if signal failed
if is_playing:
_set_pause_ui()
else:
_set_play_ui()
logger.warning("Failed to toggle playback:", e)
def handle_shuffle(self, shuffle_button, player):
print("shuffle", player.props.shuffle)
if player.props.shuffle == False:
player.set_shuffle(True)
print("setting to true", player.props.player_name)
else:
player.set_shuffle(False)
shuffle_button.get_child().set_style("color: var(--outline)")
class PlayerContainer(Box):
def __init__(self, **kwargs):
super().__init__(name="player-container", orientation="v", **kwargs)
self.manager = PlayerManager()
self.manager.connect("new-player", self.new_player)
self.manager.connect("player-vanish", self.on_player_vanish)
self.stack = Stack(
name="player-container",
transition_type="crossfade",
transition_duration=100,
children=[],
)
self.player_stack = Stack(
name="player-stack",
transition_type="crossfade",
transition_duration=100,
children=[],
)
# Check if we're in vertical mode
vertical_mode = data.DOCK_POSITION in ["Left", "Right"] if hasattr(data, 'DOCK_POSITION') else False
self.player_switch_container = CenterBox(
name="player-switch-container",
orientation="h",
style_classes="horizontal-player"
if not vertical_mode
else "vertical-player",
center_children=[],
)
self.children = [self.stack, self.player_switch_container]
self.player = []
self.manager.init_all_players()
def new_player(self, manager, player):
print(player.props.player_name, "new player")
print(player)
new_player = Player(player=player)
new_player.gtk_wrapper.queue_draw()
new_player.set_name(player.props.player_name)
self.player.append(new_player)
print("stacking player")
self.stack.add_named(new_player, player.props.player_name)
self.player_switch_container.add_center(
Button(
name=player.props.player_name,
style_classes="player-button",
on_clicked=lambda b: self.switch_player(player.props.player_name, b),
)
)
self.update_player_list()
def switch_player(self, player_name, button):
self.stack.set_visible_child_name(player_name)
for btn in self.player_switch_container.center_children:
btn.remove_style_class("active")
button.add_style_class("active")
def on_player_vanish(self, manager, player):
for player_instance in self.player:
if player_instance.get_name() == player.props.player_name:
self.stack.remove(player_instance)
self.player.remove(player_instance)
for btn in self.player_switch_container.center_children:
if btn.get_name() == player_instance.get_name():
self.player_switch_container.remove_center(btn)
self.update_player_list()
break
def update_player_list(self):
curr = self.stack.get_visible_child()
if curr:
for btn in self.player_switch_container.center_children:
print(btn.get_name())
if btn.get_name() == curr.get_name():
btn.add_style_class("active")
else:
btn.remove_style_class("active")