restore-stream.lua: optimize for event-stack

- Sharpen the hooks.
- Make settings live, apply them when they are changed.
- Move common funtions to common-utils.lua
This commit is contained in:
Ashok Sidipotu
2022-09-05 05:14:08 +05:30
committed by Julian Bouzas
parent 4dd8dd6ce5
commit 335fe69461
4 changed files with 243 additions and 218 deletions

View File

@@ -5,14 +5,14 @@ wireplumber.components = [
] ]
wireplumber.settings = { wireplumber.settings = {
stream_default.restore-props = true stream.restore-props = true
stream_default.restore-target = true stream.restore-target = true
## The default channel volume for new streams whose props were never saved ## The default channel volume for new streams whose props were never saved
## previously. This is only used if "restore-props" is set to true. ## previously. This is only used if "restore-props" is set to true.
# stream.default-channel-volume = 1.0, # stream.default-channel-volume = 1.0,
stream_default = [ stream = [
# Rules to override settings per application/node # Rules to override settings per application/node
{ {
# matches = [ # matches = [

View File

@@ -14,9 +14,9 @@ function cutils.parseBool (var)
end end
function cutils.parseParam (param, id) function cutils.parseParam (param, id)
local route = param:parse () local props = param:parse ()
if route.pod_type == "Object" and route.object_id == id then if props.pod_type == "Object" and props.object_id == id then
return route.properties return props.properties
else else
return nil return nil
end end
@@ -43,4 +43,56 @@ end
default_nodes = Plugin.find ("default-nodes-api") default_nodes = Plugin.find ("default-nodes-api")
cutils.default_metadata_om = ObjectManager {
Interest {
type = "metadata",
Constraint { "metadata.name", "=", "default" },
}
}
function cutils.evaluateRulesApplyProperties (properties, name)
local matched, mprops = Settings.apply_rule (name, properties)
if (matched and mprops) then
for k, v in pairs (mprops) do
properties [k] = v
end
end
end
-- simple serializer {"foo", "bar"} -> "foo;bar;"
function cutils.serializeArray (a)
local str = ""
for _, v in ipairs (a) do
str = str .. tostring (v):gsub (";", "\\;") .. ";"
end
return str
end
-- simple deserializer "foo;bar;" -> {"foo", "bar"}
function cutils.parseArray (str, convert_value, with_type)
local array = {}
local val = ""
local escaped = false
for i = 1, #str do
local c = str:sub (i, i)
if c == '\\' then
escaped = true
elseif c == ';' and not escaped then
val = convert_value and convert_value (val) or val
table.insert (array, val)
val = ""
else
val = val .. tostring (c)
escaped = false
end
end
if with_type then
array ["pod_type"] = "Array"
end
return array
end
cutils.default_metadata_om:activate ()
return cutils return cutils

View File

@@ -291,15 +291,6 @@ linkables_om = ObjectManager {
linkables_om:activate () linkables_om:activate ()
metadata_om = ObjectManager {
Interest {
type = "metadata",
Constraint { "metadata.name", "=", "default" },
}
}
metadata_om:activate ()
links_om = ObjectManager { links_om = ObjectManager {
Interest { Interest {
type = "SiLink", type = "SiLink",
@@ -311,7 +302,7 @@ links_om = ObjectManager {
links_om:activate () links_om:activate ()
function putils.get_default_metadata_object () function putils.get_default_metadata_object ()
return metadata_om:lookup () return cutils.default_metadata_om:lookup ()
end end
return putils return putils

View File

@@ -18,70 +18,17 @@
-- settings file: stream.conf -- settings file: stream.conf
config_restore_props = local cutils = require ("common-utils")
Settings.parse_boolean_safe ("stream_default.restore-props", false)
config_restore_target =
Settings.parse_boolean_safe ("stream_default.restore-target", false)
config_default_channel_volume =
Settings.parse_float_safe ("stream.default-channel-volume", 1.0)
function rulesApplyProperties (properties) restore_props = Settings.parse_boolean_safe ("stream.restore-props", false)
local matched, mprops = Settings.apply_rule ("stream_default", properties) restore_target = Settings.parse_boolean_safe ("stream.restore-target", false)
default_channel_volume = Settings.parse_float_safe (
if (matched and mprops) then "stream.default-channel-volume", 1.0)
for k, v in pairs (mprops) do
properties [k] = v
end
end
end
-- the state storage -- the state storage
state = State ("restore-stream") state = State ("restore-stream")
state_table = state:load () state_table = state:load ()
-- simple serializer {"foo", "bar"} -> "foo;bar;"
function serializeArray (a)
local str = ""
for _, v in ipairs (a) do
str = str .. tostring (v):gsub (";", "\\;") .. ";"
end
return str
end
-- simple deserializer "foo;bar;" -> {"foo", "bar"}
function parseArray (str, convert_value, with_type)
local array = {}
local val = ""
local escaped = false
for i = 1, #str do
local c = str:sub (i,i)
if c == '\\' then
escaped = true
elseif c == ';' and not escaped then
val = convert_value and convert_value (val) or val
table.insert (array, val)
val = ""
else
val = val .. tostring (c)
escaped = false
end
end
if with_type then
array ["pod_type"] = "Array"
end
return array
end
function parseParam (param, id)
local route = param:parse ()
if route.pod_type == "Object" and route.object_id == id then
return route.properties
else
return nil
end
end
function storeAfterTimeout () function storeAfterTimeout ()
if timeout_source then if timeout_source then
timeout_source:destroy () timeout_source:destroy ()
@@ -129,7 +76,7 @@ function saveTarget (subject, target_key, type, value)
end end
local stream_props = node.properties local stream_props = node.properties
rulesApplyProperties (stream_props) cutils.evaluateRulesApplyProperties (stream_props, "stream")
if stream_props ["state.restore-target"] == "false" then if stream_props ["state.restore-target"] == "false" then
return return
@@ -144,7 +91,7 @@ function saveTarget (subject, target_key, type, value)
local target_name = nil local target_name = nil
if not target_value then if not target_value then
local metadata = metadata_om:lookup () local metadata = cutils.default_metadata_om:lookup ()
if metadata then if metadata then
target_value = metadata:find (node ["bound-id"], target_key) target_value = metadata:find (node ["bound-id"], target_key)
end end
@@ -167,8 +114,7 @@ function saveTarget (subject, target_key, type, value)
state_table [key_base .. ":target"] = target_name state_table [key_base .. ":target"] = target_name
Log.info (node, "saving stream target for " .. Log.info (node, "saving stream target for " ..
tostring (stream_props ["node.name"]) .. tostring (stream_props ["node.name"]) .. " -> " .. tostring (target_name))
" -> " .. tostring (target_name))
storeAfterTimeout () storeAfterTimeout ()
end end
@@ -197,7 +143,7 @@ function restoreTarget(node, target_name)
} }
if target_node then if target_node then
local metadata = metadata_om:lookup () local metadata = cutils.default_metadata_om:lookup ()
if metadata then if metadata then
metadata:set (node ["bound-id"], "target.node", "Spa:Id", metadata:set (node ["bound-id"], "target.node", "Spa:Id",
target_node ["bound-id"]) target_node ["bound-id"])
@@ -260,12 +206,12 @@ function moveToMetadata (key_base, metadata)
end end
local str = state_table [key_base .. ":channelVolumes"] local str = state_table [key_base .. ":channelVolumes"]
if str then if str then
route_table ["volumes"] = parseArray (str, tonumber, true) route_table ["volumes"] = cutils.parseArray (str, tonumber, true)
count = count + 1; count = count + 1;
end end
local str = state_table [key_base .. ":channelMap"] local str = state_table [key_base .. ":channelMap"]
if str then if str then
route_table ["channels"] = parseArray (str, nil, true) route_table ["channels"] = cutils.parseArray (str, nil, true)
count = count + 1; count = count + 1;
end end
@@ -277,9 +223,9 @@ end
function saveStream (node) function saveStream (node)
local stream_props = node.properties local stream_props = node.properties
rulesApplyProperties (stream_props) cutils.evaluateRulesApplyProperties (stream_props, "stream")
if config_restore_props and stream_props ["state.restore-props"] ~= "false" if restore_props and stream_props ["state.restore-props"] ~= "false"
then then
local key_base = formKeyBase (stream_props) local key_base = formKeyBase (stream_props)
@@ -291,7 +237,7 @@ function saveStream (node)
tostring (stream_props["node.name"])) tostring (stream_props["node.name"]))
for p in node:iterate_params ("Props") do for p in node:iterate_params ("Props") do
local props = parseParam (p, "Props") local props = cutils.parseParam (p, "Props")
if not props then if not props then
goto skip_prop goto skip_prop
end end
@@ -304,11 +250,11 @@ function saveStream (node)
end end
if props.channelVolumes then if props.channelVolumes then
state_table [key_base .. ":channelVolumes"] = state_table [key_base .. ":channelVolumes"] =
serializeArray (props.channelVolumes) cutils.serializeArray (props.channelVolumes)
end end
if props.channelMap then if props.channelMap then
state_table [key_base .. ":channelMap"] = state_table [key_base .. ":channelMap"] =
serializeArray (props.channelMap) cutils.serializeArray (props.channelMap)
end end
::skip_prop:: ::skip_prop::
@@ -319,7 +265,7 @@ function saveStream (node)
end end
function build_default_channel_volumes (node) function build_default_channel_volumes (node)
local def_vol = config_default_channel_volume local def_vol = default_channel_volume
local channels = 2 local channels = 2
local res = {} local res = {}
@@ -345,14 +291,14 @@ end
function restoreStream (node) function restoreStream (node)
local stream_props = node.properties local stream_props = node.properties
rulesApplyProperties (stream_props) cutils.evaluateRulesApplyProperties (stream_props, "stream")
local key_base = formKeyBase (stream_props) local key_base = formKeyBase (stream_props)
if not key_base then if not key_base then
return return
end end
if config_restore_props and stream_props["state.restore-props"] ~= "false" if restore_props and stream_props ["state.restore-props"] ~= "false"
then then
local props = { "Spa:Pod:Object:Param:Props", "Props" } local props = { "Spa:Pod:Object:Param:Props", "Props" }
@@ -363,11 +309,11 @@ function restoreStream (node)
props.mute = str and (str == "true") or nil props.mute = str and (str == "true") or nil
local str = state_table [key_base .. ":channelVolumes"] local str = state_table [key_base .. ":channelVolumes"]
props.channelVolumes = str and parseArray (str, tonumber) or props.channelVolumes = str and cutils.parseArray (str, tonumber) or
build_default_channel_volumes (node) build_default_channel_volumes (node)
local str = state_table [key_base .. ":channelMap"] local str = state_table [key_base .. ":channelMap"]
props.channelMap = str and parseArray (str) or nil props.channelMap = str and cutils.parseArray (str) or nil
-- convert arrays to Spa Pod -- convert arrays to Spa Pod
if props.channelVolumes then if props.channelVolumes then
@@ -381,11 +327,11 @@ function restoreStream (node)
Log.info (node, "restore values from " .. key_base) Log.info (node, "restore values from " .. key_base)
local param = Pod.Object (props) local param = Pod.Object (props)
Log.debug (param, "setting props on " .. tostring (node)) Log.debug (param, "setting props on " .. tostring (stream_props ["node.name"]))
node:set_param ("Props", param) node:set_param ("Props", param)
end end
if config_restore_target and stream_props["state.restore-target"] ~= "false" if restore_target and stream_props["state.restore-target"] ~= "false"
then then
local str = state_table [key_base .. ":target"] local str = state_table [key_base .. ":target"]
if str then if str then
@@ -394,9 +340,15 @@ function restoreStream (node)
end end
end end
-- save "targe.node" if it is present in default metadata local restore_target_hook_handles = nil
if config_restore_target then
SimpleEventHook { local function handleRestoreTargetSetting (enable)
if (restore_target_hook_handles == nil) and (enable == true) then
restore_target_hook_handles = {}
-- save "targe.node" if it is present in default metadata
restore_target_hook_handles [1] = SimpleEventHook {
name = "metadata-added@restore-stream-save-target", name = "metadata-added@restore-stream-save-target",
type = "on-event", type = "on-event",
priority = "default-metadata-added-restore-stream", priority = "default-metadata-added-restore-stream",
@@ -408,17 +360,17 @@ if config_restore_target then
}, },
}, },
execute = function (event) execute = function (event)
local metadata = event:get_subject() local metadata = event:get_subject ()
-- process existing metadata -- process existing metadata
for s, k, t, v in metadata:iterate (Id.ANY) do for s, k, t, v in metadata:iterate (Id.ANY) do
saveTarget (s, k, t, v) saveTarget (s, k, t, v)
end end
end end
}:register() }
-- save "target.node" on metadata changes -- save "target.node" on metadata changes
SimpleEventHook { restore_target_hook_handles [2] = SimpleEventHook {
name = "metadata-changed@restore-stream-save-target", name = "metadata-changed@restore-stream-save-target",
type = "on-event", type = "on-event",
priority = "default-metadata-changed-restore-stream", priority = "default-metadata-changed-restore-stream",
@@ -427,6 +379,13 @@ if config_restore_target then
Constraint { "event.type", "=", "object-changed" }, Constraint { "event.type", "=", "object-changed" },
Constraint { "event.subject.type", "=", "metadata" }, Constraint { "event.subject.type", "=", "metadata" },
Constraint { "metadata.name", "=", "default" }, Constraint { "metadata.name", "=", "default" },
Constraint { "event.subject.key", "=", "target.node" },
},
EventInterest {
Constraint { "event.type", "=", "object-changed" },
Constraint { "event.subject.type", "=", "metadata" },
Constraint { "metadata.name", "=", "default" },
Constraint { "event.subject.key", "=", "target.object" },
}, },
}, },
execute = function (event) execute = function (event)
@@ -440,28 +399,18 @@ if config_restore_target then
saveTarget (subject_id, key, type, value) saveTarget (subject_id, key, type, value)
end end
}:register ()
metadata_om = ObjectManager {
Interest {
type = "metadata",
Constraint { "metadata.name", "=", "default" },
} }
} restore_target_hook_handles[1]:register()
restore_target_hook_handles[2]:register()
-- metadata_om:connect ("object-added", function (om, metadata) elseif (restore_target_hook_handles ~= nil) and (enable == false) then
-- -- process existing metadata restore_target_hook_handles [1]:remove ()
-- for s, k, t, v in metadata:iterate (Id.ANY) do restore_target_hook_handles [2]:remove ()
-- saveTarget (s, k, t, v) restore_target_hook_handles = nil
-- end end
-- -- and watch for changes
-- metadata:connect ("changed", function (m, subject, key, type, value)
-- saveTarget (subject, key, type, value)
-- end)
-- end)
metadata_om:activate ()
end end
handleRestoreTargetSetting (restore_target)
function handleRouteSettings (subject, key, type, value) function handleRouteSettings (subject, key, type, value)
if type ~= "Spa:String:JSON" then if type ~= "Spa:String:JSON" then
return return
@@ -490,10 +439,10 @@ function handleRouteSettings (subject, key, type, value)
state_table [key_base .. ":mute"] = tostring (vparsed.mute) state_table [key_base .. ":mute"] = tostring (vparsed.mute)
end end
if vparsed.channels ~= nil then if vparsed.channels ~= nil then
state_table [key_base .. ":channelMap"] = serializeArray (vparsed.channels) state_table [key_base .. ":channelMap"] = cutils.serializeArray (vparsed.channels)
end end
if vparsed.volumes ~= nil then if vparsed.volumes ~= nil then
state_table [key_base .. ":channelVolumes"] = serializeArray (vparsed.volumes) state_table [key_base .. ":channelVolumes"] = cutils.serializeArray (vparsed.volumes)
end end
storeAfterTimeout () storeAfterTimeout ()
@@ -518,8 +467,14 @@ end)
allnodes_om = ObjectManager { Interest { type = "node" } } allnodes_om = ObjectManager { Interest { type = "node" } }
allnodes_om:activate () allnodes_om:activate ()
-- restore-stream properties local restore_stream_hook_handles = nil
SimpleEventHook {
local function handleRestoreStreamSetting (enable)
if (restore_stream_hook_handles == nil) and (enable == true) then
restore_stream_hook_handles = {}
-- restore-stream properties
restore_stream_hook_handles [1] = SimpleEventHook {
name = "node-added@restore-stream", name = "node-added@restore-stream",
type = "on-event", type = "on-event",
priority = "node-added-restore-stream", priority = "node-added-restore-stream",
@@ -546,10 +501,10 @@ SimpleEventHook {
execute = function (event) execute = function (event)
restoreStream (event:get_subject ()) restoreStream (event:get_subject ())
end end
}:register () }
-- save-stream if any of the stream parms changes -- save-stream if any of the stream parms changes
SimpleEventHook { restore_stream_hook_handles [2] = SimpleEventHook {
name = "node-parms-changed@restore-stream-save-stream", name = "node-parms-changed@restore-stream-save-stream",
type = "on-event", type = "on-event",
priority = "node-changed-restore-stream", priority = "node-changed-restore-stream",
@@ -557,18 +512,21 @@ SimpleEventHook {
EventInterest { EventInterest {
Constraint { "event.type", "=", "params-changed" }, Constraint { "event.type", "=", "params-changed" },
Constraint { "event.subject.type", "=", "node" }, Constraint { "event.subject.type", "=", "node" },
Constraint { "event.subject.param-id", "=", "Props" },
Constraint { "media.class", "matches", "Stream/*", type = "pw-global" }, Constraint { "media.class", "matches", "Stream/*", type = "pw-global" },
}, },
-- and device nodes that are not associated with any routes -- and device nodes that are not associated with any routes
EventInterest { EventInterest {
Constraint { "event.type", "=", "params-changed" }, Constraint { "event.type", "=", "params-changed" },
Constraint { "event.subject.type", "=", "node" }, Constraint { "event.subject.type", "=", "node" },
Constraint { "event.subject.param-id", "=", "Props" },
Constraint { "media.class", "matches", "Audio/*", type = "pw-global" }, Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
Constraint { "device.routes", "is-absent", type = "pw" }, Constraint { "device.routes", "is-absent", type = "pw" },
}, },
EventInterest { EventInterest {
Constraint { "event.type", "=", "params-changed" }, Constraint { "event.type", "=", "params-changed" },
Constraint { "event.subject.type", "=", "node" }, Constraint { "event.subject.type", "=", "node" },
Constraint { "event.subject.param-id", "=", "Props" },
Constraint { "media.class", "matches", "Audio/*", type = "pw-global" }, Constraint { "media.class", "matches", "Audio/*", type = "pw-global" },
Constraint { "device.routes", "equals", "0", type = "pw" }, Constraint { "device.routes", "equals", "0", type = "pw" },
}, },
@@ -576,7 +534,34 @@ SimpleEventHook {
execute = function (event) execute = function (event)
saveStream (event:get_subject()) saveStream (event:get_subject())
end end
}:register () }
restore_stream_hook_handles[1]:register()
restore_stream_hook_handles[2]:register()
elseif (restore_stream_hook_handles ~= nil) and (enable == false) then
restore_stream_hook_handles [1]:remove ()
restore_stream_hook_handles [2]:remove ()
restore_stream_hook_handles = nil
end
end
handleRestoreStreamSetting (restore_props)
local function settingsChangedCallback (_, setting, _)
if setting == "stream.restore-props" then
restore_props = Settings.parse_boolean_safe ("stream.restore-props",
restore_props)
handleRestoreStreamSetting (restore_props)
elseif setting == "stream.restore-target" then
restore_target = Settings.parse_boolean_safe ("stream.restore-target",
restore_target)
handleRestoreTargetSetting (restore_target)
end
end
local settings_sub_id = Settings.subscribe ("stream*", settingsChangedCallback)
streams_om = ObjectManager { streams_om = ObjectManager {
-- match stream nodes -- match stream nodes
@@ -596,8 +581,5 @@ streams_om = ObjectManager {
Constraint { "device.routes", "equals", "0", type = "pw" }, Constraint { "device.routes", "equals", "0", type = "pw" },
}, },
} }
-- streams_om:connect ("object-added", function (streams_om, node)
-- node:connect ("params-changed", saveStream)
-- restoreStream (node)
-- end)
streams_om:activate () streams_om:activate ()