Files
wireplumber/src/scripts/policy-endpoint-device.lua
Julian Bouzas b3b10db529 policy: don't link endpoints on startup
Sometimes the default device node might not exist when reevaluating endpoints
for the first time on startup, so the policy would link endpoints to another
device node. Then, the default device node appears and the policy moves the
endpoints to the default device node while the previous link has not finish its
activation yet. This race condition can cause endpoint links to fail when being
activated. Delaying the reevaluation of endpoint links until the first client
link is created avoids this issue.
2021-05-20 11:18:19 -04:00

219 lines
6.3 KiB
Lua

-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
-- @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT
target_class_assoc = {
["Audio/Source"] = "Audio/Source",
["Audio/Sink"] = "Audio/Sink",
["Video/Source"] = "Video/Source",
}
-- Receive script arguments from config.lua
local config = ...
-- ensure config.move and config.follow are not nil
config.move = config.move or false
config.follow = config.follow or false
function getSessionItemById (si_id, om)
return om:lookup {
Constraint { "id", "=", tonumber(si_id), type = "gobject" }
}
end
function findTargetByDefaultNode (target_media_class, om)
local def_id = default_nodes:call("get-default-node", target_media_class)
if def_id ~= Id.INVALID then
for si_target in om:iterate() do
local target_node = si_target:get_associated_proxy ("node")
if target_node["bound-id"] == def_id then
return si_target
end
end
end
return nil
end
function findTargetByFirstAvailable (target_media_class, om)
for si_target in om:iterate() do
local target_node = si_target:get_associated_proxy ("node")
if target_node.properties["media.class"] == target_media_class then
return si_target
end
end
return nil
end
function findUndefinedTarget (target_media_class, om)
local si_target = findTargetByDefaultNode (target_media_class, om)
if not si_target then
si_target = findTargetByFirstAvailable (target_media_class, om)
end
return si_target
end
function createLink (si_ep, si_target)
local target_node = si_target:get_associated_proxy ("node")
local target_media_class = target_node.properties["media.class"]
local out_item = nil
local out_context = nil
local in_item = nil
local in_context = nil
if string.find (target_media_class, "Input") or
string.find (target_media_class, "Sink") then
-- capture
in_item = si_target
out_item = si_ep
out_context = "reverse"
else
-- playback
in_item = si_ep
out_item = si_target
in_context = "reverse"
end
Log.info (string.format("link %s <-> %s",
si_ep.properties["name"],
target_node.properties["node.name"]))
-- create and configure link
local si_link = SessionItem ( "si-standard-link" )
if not si_link:configure {
["out.item"] = out_item,
["in.item"] = in_item,
["out.item.port.context"] = out_context,
["in.item.port.context"] = in_context,
["manage.lifetime"] = false,
["passive"] = true,
["is.policy.endpoint.device.link"] = true,
} then
Log.warning (si_link, "failed to configure si-standard-link")
return
end
-- register
si_link:register ()
-- activate
si_link:activate (Feature.SessionItem.ACTIVE)
end
function getSiLinkAndSiPeer (si_ep, target_media_class)
for silink in silinks_om:iterate() do
local out_id = tonumber(silink.properties["out.item.id"])
local in_id = tonumber(silink.properties["in.item.id"])
if out_id == si_ep.id or in_id == si_ep.id then
local is_out = out_id == si_ep.id and true or false
for peer in silinkables_om:iterate() do
if peer.id == (is_out and in_id or out_id) then
local peer_node = peer:get_associated_proxy ("node")
local peer_media_class = peer_node.properties["media.class"]
if peer_media_class == target_media_class then
return silink, peer
end
end
end
end
end
return nil, nil
end
function handleSiEndpoint (si_ep)
-- only handle endpoints that have a valid target media class
local media_class = si_ep.properties["media.class"]
local target_media_class = target_class_assoc[media_class]
if not target_media_class then
return
end
Log.info (si_ep, "handling endpoint " .. si_ep.properties["name"])
-- find proper target item
local si_target = findUndefinedTarget (target_media_class, silinkables_om)
if not si_target then
Log.info (si_ep, "target item not found")
return
end
-- Check if item is linked to proper target endpoint, otherwise re-link
local si_link, si_peer = getSiLinkAndSiPeer (si_ep, target_media_class)
if si_link then
if si_peer and si_peer.id == si_target.id then
Log.info (si_ep, "already linked to proper target")
return
end
si_link:remove ()
Log.info (si_ep, "moving to new target")
end
-- create new link
createLink (si_ep, si_target)
end
function reevaluateLinks ()
-- check endpoints and register new links
for si_ep in siendpoints_om:iterate() do
handleSiEndpoint (si_ep)
end
-- check link session items and unregister them if not used
for silink in silinks_om:iterate() do
local out_id = tonumber (silink.properties["out.item.id"])
local in_id = tonumber (silink.properties["in.item.id"])
if (getSessionItemById (out_id, siendpoints_om) and not getSessionItemById (in_id, silinkables_om)) or
(getSessionItemById (in_id, siendpoints_om) and not getSessionItemById (out_id, silinkables_om)) then
silink:remove ()
Log.info (silink, "link removed")
end
end
end
default_nodes = Plugin.find("default-nodes-api")
siendpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
silinkables_om = ObjectManager { Interest { type = "SiLinkable",
-- only handle device si-audio-adapter items
Constraint { "si.factory.name", "=", "si-audio-adapter", type = "pw-global" },
Constraint { "is.device", "=", true, type = "pw-global" },
}
}
silinks_om = ObjectManager { Interest { type = "SiLink",
-- only handle links created by this policy
Constraint { "is.policy.endpoint.device.link", "=", true, type = "pw-global" },
} }
clientlinks_om = ObjectManager {
Interest {
type = "SiLink",
Constraint { "is.policy.endpoint.client.link", "=", true },
},
}
-- listen for default node changes if config.follow is enabled
if config.follow then
default_nodes:connect("changed", function (p)
reevaluateLinks ()
end)
end
-- start reevaluating endpoints when the first client link is created
clientlinks_om:connect("object-added", function (om, l)
reevaluateLinks ()
-- stop listening to client links from now on
clientlinks_om = nil
-- only reevaluate endpoints when device nodes change from now on
silinkables_om:connect("objects-changed", function (om)
reevaluateLinks ()
end)
end)
siendpoints_om:activate()
silinkables_om:activate()
silinks_om:activate()
clientlinks_om:activate()