完成音量调节

master
yutent 2023-08-24 18:21:45 +08:00
parent 1a4cb930d1
commit 427bbef0db
8 changed files with 153 additions and 83 deletions

10
mpd.py
View File

@ -448,12 +448,10 @@ class MPDClient(MPDClientBase, GObject.Object):
'playing': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'playing': (GObject.SignalFlags.RUN_FIRST, None, (object, object)),
'song_changed': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'song_changed': (GObject.SignalFlags.RUN_FIRST, None, (object, object)),
'state_changed': (GObject.SignalFlags.RUN_FIRST, None, (object,)), 'state_changed': (GObject.SignalFlags.RUN_FIRST, None, (object,)),
'volume_changed': (GObject.SignalFlags.RUN_FIRST, None, (int,)),
'error': (GObject.SignalFlags.RUN_FIRST, None, (object,)) 'error': (GObject.SignalFlags.RUN_FIRST, None, (object,))
} }
# __events__ = {}
connected = False connected = False
try_conn_timer = None try_conn_timer = None
@ -461,6 +459,7 @@ class MPDClient(MPDClientBase, GObject.Object):
current_song_id = None current_song_id = None
current_state = 'stop' current_state = 'stop'
current_volume = 100
idletimeout = None idletimeout = None
_timeout = None _timeout = None
@ -790,6 +789,11 @@ class MPDClient(MPDClientBase, GObject.Object):
status = self.status() status = self.status()
song = self.currentsong() song = self.currentsong()
state = status.get('state') state = status.get('state')
vol = int(status.get('volume') or 100)
if vol != self.current_volume:
self.emit('volume_changed', vol)
self.current_volume = vol
if state != self.current_state: if state != self.current_state:
self.emit('state_changed', state) self.emit('state_changed', state)

View File

@ -6,11 +6,13 @@ from gi.repository import Gtk, GObject
from .image_button import ImageButton from .image_button import ImageButton
from .toggle_button import ToggleButton from .toggle_button import ToggleButton
from .volume import Volume
class CtrlBox(Gtk.Box): class CtrlBox(Gtk.Box):
__gsignals__ = { __gsignals__ = {
'clicked': (GObject.SignalFlags.RUN_FIRST, None, (str,)) 'clicked': (GObject.SignalFlags.RUN_FIRST, None, (str,)),
'volume_changed': (GObject.SignalFlags.RUN_FIRST, None, (int,))
} }
def __init__(self, spacing = 6): def __init__(self, spacing = 6):
@ -36,10 +38,12 @@ class CtrlBox(Gtk.Box):
self.mode_btn.connect('clicked', self.on_btn_clicked) self.mode_btn.connect('clicked', self.on_btn_clicked)
self.prev_btn.connect('clicked', self.on_btn_clicked) self.prev_btn.connect('clicked', self.on_btn_clicked)
# self.play_btn.connect('toggled', self.on_btn_clicked)
self.play_btn.connect('clicked', self.on_btn_clicked) self.play_btn.connect('clicked', self.on_btn_clicked)
self.next_btn.connect('clicked', self.on_btn_clicked) self.next_btn.connect('clicked', self.on_btn_clicked)
self.vol_btn.connect('clicked', self.on_btn_clicked) self.vol_btn.connect('clicked', self.on_btn_clicked)
self.volume_bar = Volume(self.vol_btn)
self.volume_bar.connect('volume_changed', lambda bar, vol: self.emit('volume_changed', vol))
def on_btn_clicked(self, btn): def on_btn_clicked(self, btn):
@ -64,7 +68,11 @@ class CtrlBox(Gtk.Box):
self.emit('clicked', 'next_btn') self.emit('clicked', 'next_btn')
elif btn == self.vol_btn: elif btn == self.vol_btn:
self.emit('clicked', 'vol_btn') self.volume_bar.show_all()
def set_volume(self, vol):
self.volume_bar.set_volume(vol)
def toggle_play_btn(self, on = True): def toggle_play_btn(self, on = True):

View File

@ -24,26 +24,6 @@ class ImageButton(Gtk.Button):
self.set_image(filepath) self.set_image(filepath)
css_provider = Gtk.CssProvider()
style = f"""
#ImageButton {{
border: 0;
border-radius: 50%;
background-color: transparent;
border-color:transparent;
outline: transparent;
}}
#ImageButton:hover {{
background-color: rgba(255,255,255,.1);
outline: transparent;
box-shadow:none;
}}
"""
css_provider.load_from_data(style.encode('UTF-8'))
context = self.get_style_context()
context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
def set_image(self, filepath): def set_image(self, filepath):
if self._image_path == filepath: if self._image_path == filepath:

View File

@ -1,39 +0,0 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Slider(Gtk.Scale):
def __init__(self, width = 256, height = 5):
Gtk.Scale.__init__(self)
self.set_name('Slider')
self.set_range(0, 100)
self.set_size_request(width, height)
self.set_draw_value(False)
css_provider = Gtk.CssProvider()
style = f"""
#Slider {{
outline: none;
}}
#Slider trough {{
background-color: rgba(129, 161, 193, 0.35);
outline: none;
}}
#Slider trough highlight {{
background-color: rgba(163, 190, 140, 0.75);
}}
#Slider slider {{
background-color: transparent;
border-color: transparent;
outline: none;
}}
"""
css_provider.load_from_data(style.encode('UTF-8'))
context = self.get_style_context()
context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)

View File

@ -4,8 +4,6 @@ import gi, threading
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject from gi.repository import Gtk, GObject
from .slider import Slider
def time_to_str(stamp = 0): def time_to_str(stamp = 0):
m = stamp // 60 m = stamp // 60
s = stamp % 60 s = stamp % 60
@ -16,6 +14,16 @@ def time_to_str(stamp = 0):
return f"{m}:{s}" return f"{m}:{s}"
class Slider(Gtk.Scale):
def __init__(self, width = 256, height = 5):
Gtk.Scale.__init__(self)
self.set_name('Slider')
self.set_range(0, 100)
self.set_size_request(width, height)
self.set_draw_value(False)
class Timebar(Gtk.Fixed): class Timebar(Gtk.Fixed):
__gsignals__ = { __gsignals__ = {

View File

@ -9,7 +9,7 @@ class TitleText(Gtk.Label):
def __init__(self, text = 'mpd loading...'): def __init__(self, text = 'mpd loading...'):
Gtk.Box.__init__(self) Gtk.Box.__init__(self)
self.set_size_request(256, 20) self.set_size_request(266, 20)
self.set_name('text') self.set_name('text')
self.set_hexpand(True) self.set_hexpand(True)
self.set_hexpand_set(True) self.set_hexpand_set(True)

54
ui/volume.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
import gi, threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
class Volume(Gtk.Popover):
__gsignals__ = {
'volume_changed': (GObject.SignalFlags.RUN_FIRST, None, (int,))
}
timer = None
volume = 100
def __init__(self, btn = None):
Gtk.Popover.__init__(self)
self.set_relative_to(btn)
slider = Gtk.Scale.new(Gtk.Orientation.VERTICAL)
slider.set_name('Slider')
slider.set_range(0, 100)
slider.set_size_request(5, 100)
slider.set_draw_value(False)
slider.set_inverted(True)
self.slider = slider
self.slider.connect('change-value', self.debounce)
self.set_volume(100)
self.add(slider)
def set_volume(self, vol = 0):
if self.volume == int(vol):
return
self.volume = int(vol)
self.slider.set_value(self.volume)
def on_volume_changed(self):
vol = int(self.slider.get_value())
self.emit('volume_changed', vol)
def debounce(self, slider, a, b):
if self.timer is not None:
self.timer.cancel()
self.timer = threading.Timer(0.2, self.on_volume_changed)
self.timer.start()

View File

@ -8,7 +8,6 @@ from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf, idle from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf, idle
from ui.image import ScaleImage from ui.image import ScaleImage
from ui.slider import Slider
from ui.image_button import ImageButton from ui.image_button import ImageButton
from ui.title_text import TitleText from ui.title_text import TitleText
from ui.ctrl_box import CtrlBox from ui.ctrl_box import CtrlBox
@ -30,12 +29,18 @@ class SonistWindow(Gtk.Window):
self.app = app self.app = app
self.mpd = app.mpd self.mpd = app.mpd
self.style_context = Gtk.StyleContext()
self.screen = Gdk.Screen.get_default()
self.css_provider = None
self.set_default_style()
self.connect("destroy", lambda win: app.remove_window(win)) self.connect("destroy", lambda win: app.remove_window(win))
self.mpd.connect('offline', lambda o: self.reset_player()) self.mpd.connect('offline', lambda o: self.reset_player())
self.mpd.connect('online', lambda o: self.sync_state(None, None, True)) self.mpd.connect('online', lambda o: self.sync_state(None, None, True))
self.mpd.connect('state_changed', lambda o, stat: self.update_play_stat(stat == 'play')) self.mpd.connect('state_changed', lambda o, stat: self.update_play_stat(stat == 'play'))
self.mpd.connect('song_changed', lambda o, stat, song: self.sync_state(stat, song, False)) self.mpd.connect('song_changed', lambda o, stat, song: self.sync_state(stat, song, False))
self.mpd.connect('volume_changed', lambda o, vol: self.update_volume(vol))
self.mpd.connect('playing', lambda o, stat, song: self.update_playtime(stat)) self.mpd.connect('playing', lambda o, stat, song: self.update_playtime(stat))
self.set_name('SonistWindow') self.set_name('SonistWindow')
@ -57,7 +62,7 @@ class SonistWindow(Gtk.Window):
# 菜单按钮 # 菜单按钮
menu_btn = ImageButton('./usr/share/sonist/menu.png') menu_btn = ImageButton('./usr/share/sonist/menu.png')
popup_menu = OptionMenu(app) popup_menu = OptionMenu(app)
menu_btn.connect('clicked', lambda w: popup_menu.show(w)) menu_btn.connect('clicked', lambda btn: popup_menu.show(btn))
layout.put(menu_btn, 276, 6) layout.put(menu_btn, 276, 6)
# 唱片 # 唱片
@ -81,7 +86,6 @@ class SonistWindow(Gtk.Window):
# title # title
self.title_box = TitleText() self.title_box = TitleText()
layout.put(self.title_box, 27, 244) layout.put(self.title_box, 27, 244)
@ -90,12 +94,13 @@ class SonistWindow(Gtk.Window):
# 播放进度 # 播放进度
self.timebar = Timebar() self.timebar = Timebar()
self.timebar.connect('seeked', lambda a,v: self.mpd.seekcur(v)) self.timebar.connect('seeked', lambda a,v: self.mpd.seekcur(v))
layout.put(self.timebar, 24, 276) layout.put(self.timebar, 24, 272)
# 控制条 # 控制条
self.ctrl_box = CtrlBox() self.ctrl_box = CtrlBox()
self.ctrl_box.connect('clicked', self.ctrl_clicked) self.ctrl_box.connect('clicked', self.ctrl_clicked)
self.ctrl_box.connect('volume_changed', lambda box, vol: self.mpd.setvol(vol))
layout.put(self.ctrl_box, 48, 312) layout.put(self.ctrl_box, 48, 312)
@ -103,7 +108,52 @@ class SonistWindow(Gtk.Window):
self.add(layout) self.add(layout)
def set_default_style(self):
provider = Gtk.CssProvider()
css = f"""
#SonistWindow {{
background-image: url('./usr/share/sonist/album.png');
background-size: 100% 100%;
background-position: center;
}}
#text {{
color: #f2f5fc;
}}
#ImageButton {{
border: 0;
border-radius: 50%;
background-color: transparent;
border-color:transparent;
outline: transparent;
}}
#ImageButton:hover {{
background-color: rgba(255,255,255,.1);
outline: transparent;
box-shadow:none;
}}
#Slider {{
outline: none;
}}
#Slider trough {{
background-color: rgba(129, 161, 193, 0.35);
outline: none;
}}
#Slider trough highlight {{
background-color: rgba(163, 190, 140, 0.75);
}}
#Slider slider {{
background-color: transparent;
border-color: transparent;
outline: none;
}}
"""
provider.load_from_data(css.encode('UTF-8'))
self.style_context.add_provider_for_screen(self.screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def get_mpd_stat(self): def get_mpd_stat(self):
@ -126,19 +176,17 @@ class SonistWindow(Gtk.Window):
css = f""" css = f"""
#SonistWindow {{ #SonistWindow {{
background-image: url('/tmp/sonist_album_cache'); background-image: url('/tmp/sonist_album_cache');
background-size: 100% 100%;
background-position: center;
}}
#text {{
color: #f2f5fc;
}} }}
""" """
# 加载CSS样式 # 加载CSS样式
css_provider = Gtk.CssProvider() if self.css_provider is None:
css_provider.load_from_data(css.encode('UTF-8')) self.css_provider = Gtk.CssProvider()
context = Gtk.StyleContext() else:
screen = Gdk.Screen.get_default() self.style_context.remove_provider_for_screen(self.screen, self.css_provider)
context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
self.css_provider.load_from_data(css.encode('UTF-8'))
self.style_context.add_provider_for_screen(self.screen, self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def ctrl_clicked(self, box, btn): def ctrl_clicked(self, box, btn):
@ -215,6 +263,10 @@ class SonistWindow(Gtk.Window):
def update_playtime(self, stat = {}): def update_playtime(self, stat = {}):
times = stat['time'].split(':') times = stat['time'].split(':')
self.timebar.update_time(int(times[0]), int(times[1])) self.timebar.update_time(int(times[0]), int(times[1]))
@idle
def update_volume(self, vol = 100):
self.ctrl_box.set_volume(vol)
@idle @idle
def sync_state(self, stat = None, song = None, first = False): def sync_state(self, stat = None, song = None, first = False):
@ -226,6 +278,7 @@ class SonistWindow(Gtk.Window):
if first: if first:
self.update_play_stat(played == 'play') self.update_play_stat(played == 'play')
self.update_volume(int(self.stat.get('volume')))
if self.stat.get('single') == '1': if self.stat.get('single') == '1':
self.ctrl_box.toggle_mode_btn(mode = 'single') self.ctrl_box.toggle_mode_btn(mode = 'single')
@ -266,6 +319,8 @@ class SonistWindow(Gtk.Window):
except Exception as err: except Exception as err:
pass pass
@idle @idle
def reset_player(self): def reset_player(self):
self.ctrl_box.disabled = True self.ctrl_box.disabled = True