summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Andreoli <dave@gurumeditation.it>2020-05-02 12:10:39 +0200
committerDave Andreoli <dave@gurumeditation.it>2020-05-02 12:10:39 +0200
commitac8f0d942c1336cc8b948ed97057fcd9debe3bb7 (patch)
tree3603a5db42583c465d0e2f3c7f890a3657e0afe6
parent18c4be96dc3d8980c57b634abf8c3c840a3d738a (diff)
Audio: Better support for stereo channels
Also start to factorize Players and Channels, in preparation for the upcoming MPD support
-rw-r--r--gadgets/audio/__init__.py99
1 files changed, 65 insertions, 34 deletions
diff --git a/gadgets/audio/__init__.py b/gadgets/audio/__init__.py
index 8019029..216e936 100644
--- a/gadgets/audio/__init__.py
+++ b/gadgets/audio/__init__.py
@@ -1,7 +1,6 @@
1# This python file use the following encoding: utf-8 1# This python file use the following encoding: utf-8
2 2
3import os 3import os
4import sys
5import dbus 4import dbus
6from urllib.parse import unquote as url_unquote 5from urllib.parse import unquote as url_unquote
7 6
@@ -24,14 +23,48 @@ __gadget_vapi__ = 2
24__gadget_opts__ = {'popup_on_desktop': True} 23__gadget_opts__ = {'popup_on_desktop': True}
25 24
26 25
27# def DBG(msg): 26def DBG(*args):
28# print("AUDIO: %s" % msg) 27 import sys
29# sys.stdout.flush() 28 print("AUDIO:", *args)
29 sys.stdout.flush()
30 30
31 31
32_instance = None 32_instance = None
33 33
34 34
35class PlayerBase(object):
36 """ Define the interface that players must implement """
37 name = 'Player name'
38 label = 'Player label'
39 metadata = {} # metadata dict as per mpris2 specs
40 playback_status = 'Stopped' # or 'Playing' or 'Paused'
41
42 def play(self):
43 raise NotImplemented
44
45 def prev(self):
46 raise NotImplemented
47
48 def next(self):
49 raise NotImplemented
50
51 def rais(self):
52 raise NotImplemented
53
54
55class ChannelBase(object):
56 """ Define the interface that volume channels must implement """
57 name = 'Channel name'
58 volumes = [0.0, 0.0] # left/right channels, range: 0.0 - 1.0
59 muted = True
60
61 def volume_set(self, vols):
62 raise NotImplemented
63
64 def mute_toggle(self):
65 raise NotImplemented
66
67
35class Gadget(e.Gadget): 68class Gadget(e.Gadget):
36 69
37 def __init__(self): 70 def __init__(self):
@@ -67,20 +100,21 @@ class Gadget(e.Gadget):
67 def speaker_update(self, speaker): 100 def speaker_update(self, speaker):
68 if self.pulse and len(self.pulse.channels) > 0: 101 if self.pulse and len(self.pulse.channels) > 0:
69 ch = self.pulse.channels[0] 102 ch = self.pulse.channels[0]
70 left = right = ch.volume / 655 103 left, right = ch.volumes[0] * 100, ch.volumes[1] * 100
71 speaker.message_send(0, (ch.muted, left, right)) 104 speaker.message_send(0, (ch.muted, left, right))
72 105
73 def speaker_wheel_cb(self, obj, sig, source): 106 def speaker_wheel_cb(self, obj, sig, source):
74 if self.pulse.channels: 107 if self.pulse.channels:
75 ch = self.pulse.channels[0] 108 ch = self.pulse.channels[0]
76 vol = ch.volume 109 vol = (ch.volumes[0] + ch.volumes[1]) / 2
77 if sig == 'mouse,wheel,0,1': 110 if sig == 'mouse,wheel,0,1':
78 new_vol = vol - 3000 111 new_vol = vol - 0.03
79 elif sig == 'mouse,wheel,0,-1': 112 elif sig == 'mouse,wheel,0,-1':
80 new_vol = vol + 3000 113 new_vol = vol + 0.03
81 else: 114 else:
82 return 115 return
83 ch.volume_set(min(max(0, new_vol), 65500)) 116 new_vol = min(max(0.0, new_vol), 1.0)
117 ch.volume_set([new_vol, new_vol])
84 118
85 def popup_created(self, elm_parent): 119 def popup_created(self, elm_parent):
86 # DBG("POPUP CREATED") 120 # DBG("POPUP CREATED")
@@ -100,11 +134,11 @@ class Gadget(e.Gadget):
100 main_box.data['players_box'] = players_box 134 main_box.data['players_box'] = players_box
101 main_box.data['volumes_box'] = volumes_box 135 main_box.data['volumes_box'] = volumes_box
102 136
103 # add all the available players to the popup edje box 137 # add all the available mpris players to the popup edje box
104 for player in self.mpris.players: 138 for player in self.mpris.players:
105 self.popup_player_add(main_box, player) 139 self.popup_player_add(main_box, player)
106 140
107 # add all the channel sliders 141 # add all the pulse channels sliders
108 if self.pulse.conn is not None: 142 if self.pulse.conn is not None:
109 for ch in self.pulse.channels: 143 for ch in self.pulse.channels:
110 self.popup_volume_add(main_box, ch) 144 self.popup_volume_add(main_box, ch)
@@ -162,7 +196,7 @@ class Gadget(e.Gadget):
162 self.player_objs[player].append(o) 196 self.player_objs[player].append(o)
163 197
164 def player_changed(self, player): 198 def player_changed(self, player):
165 # the mpris player has changed, update all the relative edje objects 199 # the player has changed, update all the relative edje objects
166 for o in self.player_objs.get(player, []): 200 for o in self.player_objs.get(player, []):
167 self.player_update(o, player) 201 self.player_update(o, player)
168 202
@@ -213,11 +247,11 @@ class Gadget(e.Gadget):
213 pass 247 pass
214 248
215 def popup_volume_add(self, popup, channel): 249 def popup_volume_add(self, popup, channel):
216 sl = elm.Slider(popup, text=channel.name, min_max=(0, 65500), 250 sl = elm.Slider(popup, text=channel.name, min_max=(0.0, 1.0),
217 span_size=150, indicator_show=False, 251 span_size=150, indicator_show=False,
218 size_hint_expand=EXPAND_HORIZ, 252 size_hint_expand=EXPAND_HORIZ,
219 size_hint_fill=FILL_HORIZ) 253 size_hint_fill=FILL_HORIZ)
220 sl.value = channel.volume 254 sl.value = (channel.volumes[0] + channel.volumes[1]) / 2
221 sl.disabled = True if channel.muted else False 255 sl.disabled = True if channel.muted else False
222 sl.callback_changed_add(self.popup_slider_changed_cb, channel) 256 sl.callback_changed_add(self.popup_slider_changed_cb, channel)
223 sl.callback_slider_drag_start_add(self.popup_slider_drag_start_cb) 257 sl.callback_slider_drag_start_add(self.popup_slider_drag_start_cb)
@@ -236,7 +270,7 @@ class Gadget(e.Gadget):
236 270
237 @staticmethod 271 @staticmethod
238 def popup_slider_changed_cb(slider, channel): 272 def popup_slider_changed_cb(slider, channel):
239 channel.volume_set(slider.value) 273 channel.volume_set([slider.value, slider.value])
240 274
241 @staticmethod 275 @staticmethod
242 def popup_slider_click_cb(slider, event, channel): 276 def popup_slider_click_cb(slider, event, channel):
@@ -256,7 +290,7 @@ class Gadget(e.Gadget):
256 if channel in self.channel_objs: 290 if channel in self.channel_objs:
257 for sl in self.channel_objs[channel]: 291 for sl in self.channel_objs[channel]:
258 if 'dragging' not in sl.data: 292 if 'dragging' not in sl.data:
259 sl.value = channel.volume 293 sl.value = (channel.volumes[0] + channel.volumes[1]) / 2
260 # update all the speakers 294 # update all the speakers
261 for speaker in self._instances: 295 for speaker in self._instances:
262 self.speaker_update(speaker) 296 self.speaker_update(speaker)
@@ -318,7 +352,7 @@ class Mpris2_Client(object):
318 break 352 break
319 353
320 354
321class Mpris2_Player(object): 355class Mpris2_Player(PlayerBase):
322 MAIN_IFACE = 'org.mpris.MediaPlayer2' 356 MAIN_IFACE = 'org.mpris.MediaPlayer2'
323 PLAYER_IFACE = 'org.mpris.MediaPlayer2.Player' 357 PLAYER_IFACE = 'org.mpris.MediaPlayer2.Player'
324 358
@@ -362,21 +396,22 @@ class Mpris2_Player(object):
362 self.proxy.Raise(dbus_interface=self.MAIN_IFACE) 396 self.proxy.Raise(dbus_interface=self.MAIN_IFACE)
363 397
364 398
365class AudioChannel(object): 399class PulseAudioChannel(ChannelBase):
366 def __init__(self, obj, iface, name, volume, muted): 400 def __init__(self, obj, iface, name, volumes, muted):
367 self.obj = obj 401 self.obj = obj
368 self.iface = iface 402 self.iface = iface
369 self.name = name 403 self.name = name
370 self.volume = int(volume[0]) 404 self.volumes = [float(volumes[0]) / 65536, float(volumes[1]) / 65536]
371 self.muted = muted 405 self.muted = muted
372 406
373 # This do not work, only connection on the main pulse obj work... 407 # This do not work, only connection on the main pulse obj work...
374 # so for the moment dispatch the callback from there 408 # so for the moment dispatch the callback from there
375 # obj.connect_to_signal('VolumeUpdated', self.volume_changed_signal_cb) 409 # obj.connect_to_signal('VolumeUpdated', self.volume_changed_signal_cb)
376 410
377 def volume_set(self, value): 411 def volume_set(self, vols): # values 0.0 - 1.0
378 self.volume = value 412 self.volumes = vols
379 self.obj.Set(self.iface, 'Volume', [dbus.UInt32(value)], 413 values = [dbus.UInt32(vols[0] * 65536), dbus.UInt32(vols[1] * 65536)]
414 self.obj.Set(self.iface, 'Volume', values,
380 dbus_interface=dbus.PROPERTIES_IFACE) 415 dbus_interface=dbus.PROPERTIES_IFACE)
381 416
382 def mute_toggle(self): 417 def mute_toggle(self):
@@ -385,17 +420,13 @@ class AudioChannel(object):
385 dbus_interface=dbus.PROPERTIES_IFACE) 420 dbus_interface=dbus.PROPERTIES_IFACE)
386 421
387 def volume_changed_signal_cb(self, volume): 422 def volume_changed_signal_cb(self, volume):
388 self.volume = int(volume[0]) 423 self.volumes = [volume[0] / 65536, volume[1] / 65536]
389 _instance.volume_changed(self) 424 _instance.volume_changed(self)
390 425
391 def mute_changed_signal_cb(self, muted): 426 def mute_changed_signal_cb(self, muted):
392 self.muted = muted 427 self.muted = muted
393 _instance.mute_changed(self) 428 _instance.mute_changed(self)
394 429
395 def __str__(self):
396 return '[%s]: "%s" volume: %s' % \
397 (self.iface.split('.')[-1], self.name, self.volume[:])
398
399 430
400class PulseAudio_Client(object): 431class PulseAudio_Client(object):
401 PULSE_OBJ = '/org/pulseaudio/core1' 432 PULSE_OBJ = '/org/pulseaudio/core1'
@@ -541,8 +572,8 @@ class PulseAudio_Client(object):
541 def stream_add(self, obj_path): 572 def stream_add(self, obj_path):
542 try: 573 try:
543 obj = self.conn.get_object(self.STREAM_IFACE, obj_path) 574 obj = self.conn.get_object(self.STREAM_IFACE, obj_path)
544 volume = obj.Get(self.STREAM_IFACE, 'Volume', 575 volumes = obj.Get(self.STREAM_IFACE, 'Volume',
545 dbus_interface=dbus.PROPERTIES_IFACE) 576 dbus_interface=dbus.PROPERTIES_IFACE)
546 mute = obj.Get(self.STREAM_IFACE, 'Mute', 577 mute = obj.Get(self.STREAM_IFACE, 'Mute',
547 dbus_interface=dbus.PROPERTIES_IFACE) 578 dbus_interface=dbus.PROPERTIES_IFACE)
548 props = obj.Get(self.STREAM_IFACE, 'PropertyList', 579 props = obj.Get(self.STREAM_IFACE, 'PropertyList',
@@ -555,7 +586,7 @@ class PulseAudio_Client(object):
555 except: 586 except:
556 name = 'Unknown app' 587 name = 'Unknown app'
557 588
558 ch = AudioChannel(obj, self.STREAM_IFACE, name, volume, mute) 589 ch = PulseAudioChannel(obj, self.STREAM_IFACE, name, volumes, mute)
559 self.channels.append(ch) 590 self.channels.append(ch)
560 _instance.channel_added(ch) 591 _instance.channel_added(ch)
561 return ch 592 return ch
@@ -563,8 +594,8 @@ class PulseAudio_Client(object):
563 def sink_add(self, obj_path): 594 def sink_add(self, obj_path):
564 try: 595 try:
565 obj = self.conn.get_object(self.DEVICE_IFACE, obj_path) 596 obj = self.conn.get_object(self.DEVICE_IFACE, obj_path)
566 volume = obj.Get(self.DEVICE_IFACE, 'Volume', 597 volumes = obj.Get(self.DEVICE_IFACE, 'Volume',
567 dbus_interface=dbus.PROPERTIES_IFACE) 598 dbus_interface=dbus.PROPERTIES_IFACE)
568 mute = obj.Get(self.DEVICE_IFACE, 'Mute', 599 mute = obj.Get(self.DEVICE_IFACE, 'Mute',
569 dbus_interface=dbus.PROPERTIES_IFACE) 600 dbus_interface=dbus.PROPERTIES_IFACE)
570 props = obj.Get(self.DEVICE_IFACE, 'PropertyList', 601 props = obj.Get(self.DEVICE_IFACE, 'PropertyList',
@@ -580,7 +611,7 @@ class PulseAudio_Client(object):
580 except: 611 except:
581 name = 'Unknown device' 612 name = 'Unknown device'
582 613
583 ch = AudioChannel(obj, self.DEVICE_IFACE, name, volume, mute) 614 ch = PulseAudioChannel(obj, self.DEVICE_IFACE, name, volumes, mute)
584 self.channels.append(ch) 615 self.channels.append(ch)
585 _instance.channel_added(ch) 616 _instance.channel_added(ch)
586 return ch 617 return ch