Audio: Better support for stereo channels

Also start to factorize Players and Channels, in preparation for
the upcoming MPD support
This commit is contained in:
Davide Andreoli 2020-05-02 12:10:39 +02:00
parent 18c4be96dc
commit ac8f0d942c
1 changed files with 65 additions and 34 deletions

View File

@ -1,7 +1,6 @@
# This python file use the following encoding: utf-8 # This python file use the following encoding: utf-8
import os import os
import sys
import dbus import dbus
from urllib.parse import unquote as url_unquote from urllib.parse import unquote as url_unquote
@ -24,14 +23,48 @@ __gadget_vapi__ = 2
__gadget_opts__ = {'popup_on_desktop': True} __gadget_opts__ = {'popup_on_desktop': True}
# def DBG(msg): def DBG(*args):
# print("AUDIO: %s" % msg) import sys
# sys.stdout.flush() print("AUDIO:", *args)
sys.stdout.flush()
_instance = None _instance = None
class PlayerBase(object):
""" Define the interface that players must implement """
name = 'Player name'
label = 'Player label'
metadata = {} # metadata dict as per mpris2 specs
playback_status = 'Stopped' # or 'Playing' or 'Paused'
def play(self):
raise NotImplemented
def prev(self):
raise NotImplemented
def next(self):
raise NotImplemented
def rais(self):
raise NotImplemented
class ChannelBase(object):
""" Define the interface that volume channels must implement """
name = 'Channel name'
volumes = [0.0, 0.0] # left/right channels, range: 0.0 - 1.0
muted = True
def volume_set(self, vols):
raise NotImplemented
def mute_toggle(self):
raise NotImplemented
class Gadget(e.Gadget): class Gadget(e.Gadget):
def __init__(self): def __init__(self):
@ -67,20 +100,21 @@ class Gadget(e.Gadget):
def speaker_update(self, speaker): def speaker_update(self, speaker):
if self.pulse and len(self.pulse.channels) > 0: if self.pulse and len(self.pulse.channels) > 0:
ch = self.pulse.channels[0] ch = self.pulse.channels[0]
left = right = ch.volume / 655 left, right = ch.volumes[0] * 100, ch.volumes[1] * 100
speaker.message_send(0, (ch.muted, left, right)) speaker.message_send(0, (ch.muted, left, right))
def speaker_wheel_cb(self, obj, sig, source): def speaker_wheel_cb(self, obj, sig, source):
if self.pulse.channels: if self.pulse.channels:
ch = self.pulse.channels[0] ch = self.pulse.channels[0]
vol = ch.volume vol = (ch.volumes[0] + ch.volumes[1]) / 2
if sig == 'mouse,wheel,0,1': if sig == 'mouse,wheel,0,1':
new_vol = vol - 3000 new_vol = vol - 0.03
elif sig == 'mouse,wheel,0,-1': elif sig == 'mouse,wheel,0,-1':
new_vol = vol + 3000 new_vol = vol + 0.03
else: else:
return return
ch.volume_set(min(max(0, new_vol), 65500)) new_vol = min(max(0.0, new_vol), 1.0)
ch.volume_set([new_vol, new_vol])
def popup_created(self, elm_parent): def popup_created(self, elm_parent):
# DBG("POPUP CREATED") # DBG("POPUP CREATED")
@ -100,11 +134,11 @@ class Gadget(e.Gadget):
main_box.data['players_box'] = players_box main_box.data['players_box'] = players_box
main_box.data['volumes_box'] = volumes_box main_box.data['volumes_box'] = volumes_box
# add all the available players to the popup edje box # add all the available mpris players to the popup edje box
for player in self.mpris.players: for player in self.mpris.players:
self.popup_player_add(main_box, player) self.popup_player_add(main_box, player)
# add all the channel sliders # add all the pulse channels sliders
if self.pulse.conn is not None: if self.pulse.conn is not None:
for ch in self.pulse.channels: for ch in self.pulse.channels:
self.popup_volume_add(main_box, ch) self.popup_volume_add(main_box, ch)
@ -162,7 +196,7 @@ class Gadget(e.Gadget):
self.player_objs[player].append(o) self.player_objs[player].append(o)
def player_changed(self, player): def player_changed(self, player):
# the mpris player has changed, update all the relative edje objects # the player has changed, update all the relative edje objects
for o in self.player_objs.get(player, []): for o in self.player_objs.get(player, []):
self.player_update(o, player) self.player_update(o, player)
@ -213,11 +247,11 @@ class Gadget(e.Gadget):
pass pass
def popup_volume_add(self, popup, channel): def popup_volume_add(self, popup, channel):
sl = elm.Slider(popup, text=channel.name, min_max=(0, 65500), sl = elm.Slider(popup, text=channel.name, min_max=(0.0, 1.0),
span_size=150, indicator_show=False, span_size=150, indicator_show=False,
size_hint_expand=EXPAND_HORIZ, size_hint_expand=EXPAND_HORIZ,
size_hint_fill=FILL_HORIZ) size_hint_fill=FILL_HORIZ)
sl.value = channel.volume sl.value = (channel.volumes[0] + channel.volumes[1]) / 2
sl.disabled = True if channel.muted else False sl.disabled = True if channel.muted else False
sl.callback_changed_add(self.popup_slider_changed_cb, channel) sl.callback_changed_add(self.popup_slider_changed_cb, channel)
sl.callback_slider_drag_start_add(self.popup_slider_drag_start_cb) sl.callback_slider_drag_start_add(self.popup_slider_drag_start_cb)
@ -236,7 +270,7 @@ class Gadget(e.Gadget):
@staticmethod @staticmethod
def popup_slider_changed_cb(slider, channel): def popup_slider_changed_cb(slider, channel):
channel.volume_set(slider.value) channel.volume_set([slider.value, slider.value])
@staticmethod @staticmethod
def popup_slider_click_cb(slider, event, channel): def popup_slider_click_cb(slider, event, channel):
@ -256,7 +290,7 @@ class Gadget(e.Gadget):
if channel in self.channel_objs: if channel in self.channel_objs:
for sl in self.channel_objs[channel]: for sl in self.channel_objs[channel]:
if 'dragging' not in sl.data: if 'dragging' not in sl.data:
sl.value = channel.volume sl.value = (channel.volumes[0] + channel.volumes[1]) / 2
# update all the speakers # update all the speakers
for speaker in self._instances: for speaker in self._instances:
self.speaker_update(speaker) self.speaker_update(speaker)
@ -318,7 +352,7 @@ class Mpris2_Client(object):
break break
class Mpris2_Player(object): class Mpris2_Player(PlayerBase):
MAIN_IFACE = 'org.mpris.MediaPlayer2' MAIN_IFACE = 'org.mpris.MediaPlayer2'
PLAYER_IFACE = 'org.mpris.MediaPlayer2.Player' PLAYER_IFACE = 'org.mpris.MediaPlayer2.Player'
@ -362,21 +396,22 @@ class Mpris2_Player(object):
self.proxy.Raise(dbus_interface=self.MAIN_IFACE) self.proxy.Raise(dbus_interface=self.MAIN_IFACE)
class AudioChannel(object): class PulseAudioChannel(ChannelBase):
def __init__(self, obj, iface, name, volume, muted): def __init__(self, obj, iface, name, volumes, muted):
self.obj = obj self.obj = obj
self.iface = iface self.iface = iface
self.name = name self.name = name
self.volume = int(volume[0]) self.volumes = [float(volumes[0]) / 65536, float(volumes[1]) / 65536]
self.muted = muted self.muted = muted
# This do not work, only connection on the main pulse obj work... # This do not work, only connection on the main pulse obj work...
# so for the moment dispatch the callback from there # so for the moment dispatch the callback from there
# obj.connect_to_signal('VolumeUpdated', self.volume_changed_signal_cb) # obj.connect_to_signal('VolumeUpdated', self.volume_changed_signal_cb)
def volume_set(self, value): def volume_set(self, vols): # values 0.0 - 1.0
self.volume = value self.volumes = vols
self.obj.Set(self.iface, 'Volume', [dbus.UInt32(value)], values = [dbus.UInt32(vols[0] * 65536), dbus.UInt32(vols[1] * 65536)]
self.obj.Set(self.iface, 'Volume', values,
dbus_interface=dbus.PROPERTIES_IFACE) dbus_interface=dbus.PROPERTIES_IFACE)
def mute_toggle(self): def mute_toggle(self):
@ -385,17 +420,13 @@ class AudioChannel(object):
dbus_interface=dbus.PROPERTIES_IFACE) dbus_interface=dbus.PROPERTIES_IFACE)
def volume_changed_signal_cb(self, volume): def volume_changed_signal_cb(self, volume):
self.volume = int(volume[0]) self.volumes = [volume[0] / 65536, volume[1] / 65536]
_instance.volume_changed(self) _instance.volume_changed(self)
def mute_changed_signal_cb(self, muted): def mute_changed_signal_cb(self, muted):
self.muted = muted self.muted = muted
_instance.mute_changed(self) _instance.mute_changed(self)
def __str__(self):
return '[%s]: "%s" volume: %s' % \
(self.iface.split('.')[-1], self.name, self.volume[:])
class PulseAudio_Client(object): class PulseAudio_Client(object):
PULSE_OBJ = '/org/pulseaudio/core1' PULSE_OBJ = '/org/pulseaudio/core1'
@ -541,8 +572,8 @@ class PulseAudio_Client(object):
def stream_add(self, obj_path): def stream_add(self, obj_path):
try: try:
obj = self.conn.get_object(self.STREAM_IFACE, obj_path) obj = self.conn.get_object(self.STREAM_IFACE, obj_path)
volume = obj.Get(self.STREAM_IFACE, 'Volume', volumes = obj.Get(self.STREAM_IFACE, 'Volume',
dbus_interface=dbus.PROPERTIES_IFACE) dbus_interface=dbus.PROPERTIES_IFACE)
mute = obj.Get(self.STREAM_IFACE, 'Mute', mute = obj.Get(self.STREAM_IFACE, 'Mute',
dbus_interface=dbus.PROPERTIES_IFACE) dbus_interface=dbus.PROPERTIES_IFACE)
props = obj.Get(self.STREAM_IFACE, 'PropertyList', props = obj.Get(self.STREAM_IFACE, 'PropertyList',
@ -555,7 +586,7 @@ class PulseAudio_Client(object):
except: except:
name = 'Unknown app' name = 'Unknown app'
ch = AudioChannel(obj, self.STREAM_IFACE, name, volume, mute) ch = PulseAudioChannel(obj, self.STREAM_IFACE, name, volumes, mute)
self.channels.append(ch) self.channels.append(ch)
_instance.channel_added(ch) _instance.channel_added(ch)
return ch return ch
@ -563,8 +594,8 @@ class PulseAudio_Client(object):
def sink_add(self, obj_path): def sink_add(self, obj_path):
try: try:
obj = self.conn.get_object(self.DEVICE_IFACE, obj_path) obj = self.conn.get_object(self.DEVICE_IFACE, obj_path)
volume = obj.Get(self.DEVICE_IFACE, 'Volume', volumes = obj.Get(self.DEVICE_IFACE, 'Volume',
dbus_interface=dbus.PROPERTIES_IFACE) dbus_interface=dbus.PROPERTIES_IFACE)
mute = obj.Get(self.DEVICE_IFACE, 'Mute', mute = obj.Get(self.DEVICE_IFACE, 'Mute',
dbus_interface=dbus.PROPERTIES_IFACE) dbus_interface=dbus.PROPERTIES_IFACE)
props = obj.Get(self.DEVICE_IFACE, 'PropertyList', props = obj.Get(self.DEVICE_IFACE, 'PropertyList',
@ -580,7 +611,7 @@ class PulseAudio_Client(object):
except: except:
name = 'Unknown device' name = 'Unknown device'
ch = AudioChannel(obj, self.DEVICE_IFACE, name, volume, mute) ch = PulseAudioChannel(obj, self.DEVICE_IFACE, name, volumes, mute)
self.channels.append(ch) self.channels.append(ch)
_instance.channel_added(ch) _instance.channel_added(ch)
return ch return ch