diff --git a/po/conf.pot b/po/conf.pot index 7512585a..66c4ad80 100644 --- a/po/conf.pot +++ b/po/conf.pot @@ -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" diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf index f3b70477..1227c9eb 100644 --- a/src/config/wireplumber.conf +++ b/src/config/wireplumber.conf @@ -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 = { diff --git a/src/scripts/device/state-routes.lua b/src/scripts/device/state-routes.lua index b4a7e64e..f08dbebe 100644 --- a/src/scripts/device/state-routes.lua +++ b/src/scripts/device/state-routes.lua @@ -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 diff --git a/src/scripts/lib/device-info-cache.lua b/src/scripts/lib/device-info-cache.lua index 80c7d766..d8961c5e 100644 --- a/src/scripts/lib/device-info-cache.lua +++ b/src/scripts/lib/device-info-cache.lua @@ -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