state-routes.lua: Add new 'bluetooth.keep-volume-on-profile-changed' setting

If enabled, this setting will use the same volume levels as the previous
profile. This is useful on some bluetooth devices if the bluetooth profile
audioswitch is enabled.
This commit is contained in:
Julian Bouzas 2025-10-07 12:59:05 -04:00
parent 94aabdb370
commit d55680b929
4 changed files with 74 additions and 4 deletions

View file

@ -13,6 +13,16 @@ msgstr ""
msgid "Auto-switch to headset profile"
msgstr ""
#. /wireplumber.settings.schema/bluetooth.keep-volume-on-profile-changed/description
#: wireplumber.conf
msgid "Keep same volume levels between Bluetooth profile changes"
msgstr ""
#. /wireplumber.settings.schema/bluetooth.keep-volume-on-profile-changed/name
#: wireplumber.conf
msgid "Keep volume"
msgstr ""
#. /wireplumber.settings.schema/bluetooth.use-persistent-storage/description
#: wireplumber.conf
msgid "Remember and restore Bluetooth headset mode status"

View file

@ -847,6 +847,12 @@ wireplumber.settings.schema = {
type = "bool"
default = true
}
bluetooth.keep-volume-on-profile-changed = {
name = "Keep volume"
description = "Keep same volume levels between Bluetooth profile changes"
type = "bool"
default = false
}
## Device
device.restore-profile = {

View file

@ -21,6 +21,15 @@ log = Log.open_topic ("s-device")
state = nil
state_table = nil
-- utilities
function fillCycle (dest, src)
local size = #dest
local srcSize = #src
for i = 1, size do
dest[i] = src[((i - 1) % srcSize) + 1]
end
end
-- hook to restore routes selection for a newly selected profile
find_stored_routes_hook = SimpleEventHook {
name = "device/find-stored-routes",
@ -128,6 +137,24 @@ apply_route_props_hook = SimpleEventHook {
-- convert arrays to Json
if props.channelVolumes then
-- fill the channel volumes with the ones from the previous profile if
-- this is a BT device and the setting is enabled
if device.properties["device.api"] == "bluez5" and
Settings.get_boolean ("bluetooth.keep-volume-on-profile-changed") then
local prev_profile = dev_info.route_param_prev_profile
local route_dir = route_info.direction
if dev_info.route_param_profile_changed and
dev_info.route_param_channel_volumes[device.id] ~= nil and
dev_info.route_param_channel_volumes[device.id][prev_profile] ~= nil then
local vols = dev_info.route_param_channel_volumes[device.id][prev_profile][route_dir]
if vols ~= nil then
log:info (device, "using previous profile channel volumes on route " .. route_info.name)
fillCycle (props.channelVolumes, vols)
end
end
end
props.channelVolumes = Json.Array (props.channelVolumes)
end
if props.channelMap then
@ -169,6 +196,7 @@ store_or_restore_routes_hook = AsyncEventHook {
device:enum_params ("EnumRoute", function (enum_route_it, e)
local selected_routes = {}
local push_select_routes = false
local profile = nil
-- check for error
if e then
@ -189,6 +217,18 @@ store_or_restore_routes_hook = AsyncEventHook {
return
end
-- Update current and previous profiles, and check if they changed
for p in device:iterate_params ("Profile") do
profile = cutils.parseParam (p, "Profile")
if dev_info.route_param_curr_profile ~= profile.index then
dev_info.route_param_prev_profile = dev_info.route_param_curr_profile
dev_info.route_param_curr_profile = profile.index
dev_info.route_param_profile_changed = true
else
dev_info.route_param_profile_changed = false
end
end
local new_route_infos = {}
-- look at all the routes and update/reset cached information
@ -227,6 +267,18 @@ store_or_restore_routes_hook = AsyncEventHook {
goto skip_route
end
-- cache the device input and output route channel volumes for current profile
local curr_profile = dev_info.route_param_curr_profile
local route_dir = route.direction
if dev_info.route_param_channel_volumes [device.id] == nil then
dev_info.route_param_channel_volumes [device.id] = {}
end
if dev_info.route_param_channel_volumes [device.id][curr_profile] == nil then
dev_info.route_param_channel_volumes [device.id][curr_profile] = {}
end
dev_info.route_param_channel_volumes [device.id][curr_profile][route_dir] =
route.props.properties.channelVolumes
-- get cached route info and at the same time
-- ensure that the route is also in EnumRoute
local route_info = devinfo.find_route_info (dev_info, route, false)
@ -264,10 +316,8 @@ store_or_restore_routes_hook = AsyncEventHook {
end
-- save selected routes for the active profile
for p in device:iterate_params ("Profile") do
local profile = cutils.parseParam (p, "Profile")
saveProfileRoutes (dev_info, profile.name)
end
assert (profile)
saveProfileRoutes (dev_info, profile.name)
-- push a select-routes event to re-apply the routes with new properties
if push_select_routes then

View file

@ -44,6 +44,10 @@ function module.get_device_info (self, device)
name = device_name,
active_profile = -1,
route_infos = {},
route_param_curr_profile = -1,
route_param_prev_profile = -1,
route_param_profile_changed = false,
route_param_channel_volumes = {}
}
self.dev_infos [device_id] = dev_info
end