183 lines
4.6 KiB
Python
Executable File
183 lines
4.6 KiB
Python
Executable File
#!/usr/bin/python
|
|
"""Alsa volume control functions for IPMPV."""
|
|
|
|
import subprocess
|
|
import alsaaudio
|
|
import threading
|
|
import traceback
|
|
import time
|
|
|
|
class VolumeControl:
|
|
"""
|
|
Class for controlling audio volume with ALSA.
|
|
|
|
This class provides an interface to control the volume with PyAlsa,
|
|
with methods to get current volume, increase/decrease volume,
|
|
and toggle mute.
|
|
"""
|
|
|
|
def __init__(self, mixer_name='Master', step=5, to_qt_queue=None):
|
|
"""
|
|
Initialize the volume control.
|
|
|
|
Args:
|
|
mixer_name (str): Name of the ALSA mixer to control
|
|
step (int): Default step size for volume increments
|
|
to_qt_queue: Queue for sending messages to Qt process for OSD
|
|
"""
|
|
self.mixer_name = mixer_name
|
|
self.step = step
|
|
self.to_qt_queue = to_qt_queue
|
|
self.volume_thread = None
|
|
self.volume_lock = threading.Lock()
|
|
self._init_mixer()
|
|
|
|
def _init_mixer(self):
|
|
"""Initialize the ALSA mixer."""
|
|
try:
|
|
# Try to get the requested mixer
|
|
self.mixer = alsaaudio.Mixer(self.mixer_name)
|
|
print(f"Successfully initialized ALSA mixer: {self.mixer_name}")
|
|
except alsaaudio.ALSAAudioError as e:
|
|
print(f"Error initializing mixer '{self.mixer_name}': {e}")
|
|
# Try to get a list of available mixers
|
|
available_mixers = alsaaudio.mixers()
|
|
print(f"Available mixers: {available_mixers}")
|
|
|
|
# Try some common alternatives
|
|
fallback_mixers = ['PCM', 'Speaker', 'Master', 'Front', 'Headphone']
|
|
for mixer in fallback_mixers:
|
|
if mixer in available_mixers:
|
|
try:
|
|
self.mixer = alsaaudio.Mixer(mixer)
|
|
self.mixer_name = mixer
|
|
print(f"Using fallback mixer: {mixer}")
|
|
return
|
|
except alsaaudio.ALSAAudioError:
|
|
continue
|
|
|
|
# If we still don't have a mixer, raise an exception
|
|
raise Exception("Could not find a suitable ALSA mixer")
|
|
|
|
def get_volume(self):
|
|
"""
|
|
Get the current volume level.
|
|
|
|
Returns:
|
|
int: Current volume as a percentage (0-100)
|
|
"""
|
|
try:
|
|
# Get all channels and average them
|
|
volumes = self.mixer.getvolume()
|
|
return sum(volumes) // len(volumes)
|
|
except Exception as e:
|
|
print(f"Error getting volume: {e}")
|
|
traceback.print_exc()
|
|
return 0
|
|
|
|
def is_muted(self):
|
|
"""
|
|
Check if audio is muted.
|
|
|
|
Returns:
|
|
bool: True if muted, False otherwise
|
|
"""
|
|
try:
|
|
# Get mute state for all channels (returns list of 0/1 values)
|
|
mutes = self.mixer.getmute()
|
|
# If any channel is muted (1), consider it muted
|
|
return any(mute == 1 for mute in mutes)
|
|
except Exception as e:
|
|
print(f"Error checking mute state: {e}")
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def volume_up(self, step=None):
|
|
"""
|
|
Increase volume.
|
|
|
|
Args:
|
|
step (int, optional): Amount to increase volume by. Defaults to self.step.
|
|
|
|
Returns:
|
|
int: New volume level
|
|
"""
|
|
return self._adjust_volume(step if step is not None else self.step)
|
|
|
|
def volume_down(self, step=None):
|
|
"""
|
|
Decrease volume.
|
|
|
|
Args:
|
|
step (int, optional): Amount to decrease volume by. Defaults to self.step.
|
|
|
|
Returns:
|
|
int: New volume level
|
|
"""
|
|
return self._adjust_volume(-(step if step is not None else self.step))
|
|
|
|
def toggle_mute(self):
|
|
"""
|
|
Toggle mute state.
|
|
|
|
Returns:
|
|
bool: New mute state
|
|
"""
|
|
try:
|
|
muted = self.is_muted()
|
|
# Set all channels to the opposite of current mute state
|
|
self.mixer.setmute(0 if muted else 1)
|
|
|
|
new_mute_state = not muted
|
|
|
|
# Update OSD if queue is available
|
|
if self.to_qt_queue is not None:
|
|
self.to_qt_queue.put({
|
|
'action': 'show_volume_osd',
|
|
'volume_level': 0 if new_mute_state else self.get_volume(),
|
|
'is_muted': new_mute_state
|
|
})
|
|
|
|
return new_mute_state
|
|
except Exception as e:
|
|
print(f"Error toggling mute: {e}")
|
|
traceback.print_exc()
|
|
return self.is_muted()
|
|
|
|
def _adjust_volume(self, change):
|
|
"""
|
|
Internal method to adjust volume.
|
|
|
|
Args:
|
|
change (int): Amount to change volume by (positive or negative)
|
|
|
|
Returns:
|
|
int: New volume level
|
|
"""
|
|
with self.volume_lock:
|
|
try:
|
|
current = self.get_volume()
|
|
new_volume = max(0, min(100, current + change))
|
|
|
|
# Set new volume on all channels
|
|
self.mixer.setvolume(new_volume)
|
|
|
|
# If we were muted and increasing volume, unmute
|
|
if change > 0 and self.is_muted():
|
|
self.mixer.setmute(0)
|
|
|
|
# Update OSD if queue is available
|
|
if self.to_qt_queue is not None:
|
|
# Always use 'show_volume_osd' action to ensure the OSD appears
|
|
self.to_qt_queue.put({
|
|
'action': 'show_volume_osd',
|
|
'volume_level': new_volume,
|
|
'is_muted': False
|
|
})
|
|
|
|
return new_volume
|
|
except Exception as e:
|
|
print(f"Error adjusting volume: {e}")
|
|
traceback.print_exc()
|
|
return self.get_volume()
|