mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-04 23:38:01 +02:00
linking: role based priority system: hook to apply policy
This commit is contained in:
parent
fcaece85e9
commit
f6b77c7456
4 changed files with 233 additions and 26 deletions
|
|
@ -615,7 +615,7 @@ wireplumber.components = [
|
|||
|
||||
## Linking: Role-based priority system
|
||||
{
|
||||
name = linking/rescan-virtual-links.lua, type = script/lua
|
||||
name = linking/rescan-media-role-links.lua, type = script/lua
|
||||
provides = hooks.linking.role-priority-system.links.rescan
|
||||
requires = [ api.mixer ]
|
||||
}
|
||||
|
|
@ -732,6 +732,13 @@ wireplumber.settings.schema = {
|
|||
}
|
||||
|
||||
## Linking
|
||||
linking.duck-level = {
|
||||
description = "default duck level, the volume level to which the volume will be reduced"
|
||||
type = "float"
|
||||
default = 0.3
|
||||
min = 0.0
|
||||
max = 1.0
|
||||
}
|
||||
linking.allow-moving-streams = {
|
||||
description = "Whether to allow metadata to move streams at runtime or not"
|
||||
type = "bool"
|
||||
|
|
|
|||
|
|
@ -7,4 +7,7 @@ wireplumber.settings = {
|
|||
|
||||
## Moves session items to the default device when it has changed
|
||||
# linking.follow-default-target = true
|
||||
|
||||
## default duck level, the volume level to which the volume will be reduced
|
||||
# linking.duck-level = 0.3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,37 +53,25 @@ AsyncEventHook {
|
|||
return
|
||||
end
|
||||
|
||||
if si_props ["item.factory.name"] == "si-audio-virtual" then
|
||||
if si_props ["item.node.direction"] == "output" then
|
||||
-- playback
|
||||
out_item = target
|
||||
in_item = si
|
||||
else
|
||||
-- capture
|
||||
in_item = target
|
||||
out_item = si
|
||||
end
|
||||
if si_props["item.node.direction"] == "output" then
|
||||
-- playback
|
||||
out_item = si
|
||||
in_item = target
|
||||
else
|
||||
if si_props ["item.node.direction"] == "output" then
|
||||
-- playback
|
||||
out_item = si
|
||||
in_item = target
|
||||
else
|
||||
-- capture
|
||||
in_item = si
|
||||
out_item = target
|
||||
end
|
||||
-- capture
|
||||
in_item = si
|
||||
out_item = target
|
||||
end
|
||||
|
||||
local is_virtual_client_link = target_props ["item.factory.name"] == "si-audio-virtual"
|
||||
local is_media_role_link = target_props["device.intended-roles"] ~= nil
|
||||
|
||||
log:info (si,
|
||||
string.format ("link %s <-> %s passthrough:%s, exclusive:%s, virtual-client:%s",
|
||||
string.format ("link %s <-> %s passthrough:%s, exclusive:%s, media role link:%s",
|
||||
tostring (si_props ["node.name"]),
|
||||
tostring (target_props ["node.name"]),
|
||||
tostring (passthrough),
|
||||
tostring (exclusive),
|
||||
tostring (is_virtual_client_link)))
|
||||
tostring (is_media_role_link)))
|
||||
|
||||
-- create and configure link
|
||||
si_link = SessionItem ("si-standard-link")
|
||||
|
|
@ -94,9 +82,12 @@ AsyncEventHook {
|
|||
["exclusive"] = exclusive,
|
||||
["out.item.port.context"] = "output",
|
||||
["in.item.port.context"] = "input",
|
||||
["media.role"] = target_props["role"],
|
||||
["media.role"] = si_props["media.role"],
|
||||
["target.media.class"] = target_props["media.class"],
|
||||
["is.virtual.client.link"] = is_virtual_client_link,
|
||||
["policy.role-based.priority"] = target_props["policy.role-based.priority"],
|
||||
["policy.role-based.action.same-priority"] = target_props["policy.role-based.action.same-priority"],
|
||||
["policy.role-based.action.lower-priority"] = target_props["policy.role-based.action.lower-priority"],
|
||||
["is.media.role.link"] = is_media_role_link,
|
||||
["main.item.id"] = si.id,
|
||||
["target.item.id"] = target.id,
|
||||
} then
|
||||
|
|
@ -135,7 +126,7 @@ AsyncEventHook {
|
|||
|
||||
-- only activate non virtual links because virtual links activation is
|
||||
-- handled by rescan-virtual-links.lua
|
||||
if not is_virtual_client_link then
|
||||
if not is_media_role_link then
|
||||
si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
|
||||
if e then
|
||||
transition:return_error (tostring (l) .. " link failed: "
|
||||
|
|
|
|||
206
src/scripts/linking/rescan-media-role-links.lua
Normal file
206
src/scripts/linking/rescan-media-role-links.lua
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2024 Collabora Ltd.
|
||||
-- @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
lutils = require("linking-utils")
|
||||
cutils = require("common-utils")
|
||||
log = Log.open_topic("s-linking")
|
||||
|
||||
config = {}
|
||||
config.duck_level = Settings.get_float("linking.duck-level")
|
||||
|
||||
function restoreVolume (om, link)
|
||||
setVolume(om, link, 1.0)
|
||||
end
|
||||
|
||||
function duckVolume (om, link)
|
||||
setVolume(om, link, config.duck_level)
|
||||
end
|
||||
|
||||
function setVolume (om, link, level)
|
||||
local lprops = link.properties
|
||||
local media_role_si_id = nil
|
||||
local dir = lprops ["item.node.direction"]
|
||||
|
||||
if dir == "output" then
|
||||
media_role_si_id = lprops ["out.item.id"]
|
||||
else
|
||||
media_role_si_id = lprops ["in.item.id"]
|
||||
end
|
||||
|
||||
local media_role_lnkbl = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "id", "=", media_role_si_id, type = "gobject" },
|
||||
}
|
||||
|
||||
-- apply volume control on the stream node of the loopback module, instead of
|
||||
-- the sink/source node as it simplyfies the volume ducking and
|
||||
-- restoration.
|
||||
local media_role_other_lnkbl = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "node.link-group", "=", media_role_lnkbl.properties ["node.link-group"] },
|
||||
Constraint { "id", "!", media_role_lnkbl.id, type = "gobject" },
|
||||
}
|
||||
|
||||
if media_role_other_lnkbl then
|
||||
local n = media_role_other_lnkbl:get_associated_proxy("node")
|
||||
if n then
|
||||
log:info(string.format(".. %s volume of media role node \"%s(%d)\" to %f",
|
||||
level < 1.0 and "duck" or "restore", n.properties ["node.name"],
|
||||
n ["bound-id"], level))
|
||||
|
||||
local props = {
|
||||
"Spa:Pod:Object:Param:Props",
|
||||
"Props",
|
||||
volume = level,
|
||||
}
|
||||
|
||||
local param = Pod.Object(props)
|
||||
n:set_param("Props", param)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getSuspendPlaybackFromMetadata (om)
|
||||
local suspend = false
|
||||
local metadata = om:lookup {
|
||||
type = "metadata",
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
}
|
||||
if metadata then
|
||||
local value = metadata:find(0, "suspend.playback")
|
||||
if value then
|
||||
suspend = value == "1" and true or false
|
||||
end
|
||||
end
|
||||
return suspend
|
||||
end
|
||||
|
||||
AsyncEventHook {
|
||||
name = "linking/rescan-media-role-links",
|
||||
interests = {
|
||||
EventInterest {
|
||||
-- on media client link added and removed
|
||||
Constraint { "event.type", "c", "session-item-added", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "link" },
|
||||
Constraint { "is.media.role.link", "=", true },
|
||||
},
|
||||
EventInterest {
|
||||
-- on default metadata suspend.playback changed
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "=", "suspend.playback" },
|
||||
}
|
||||
},
|
||||
steps = {
|
||||
start = {
|
||||
next = "none",
|
||||
execute = function(event, transition)
|
||||
local source, om, _, si_props, _, _ =
|
||||
lutils:unwrap_select_target_event(event)
|
||||
|
||||
local metadata_om = source:call("get-object-manager", "metadata")
|
||||
local suspend = getSuspendPlaybackFromMetadata(metadata_om)
|
||||
local pending_activations = 0
|
||||
local mc = si_props ["target.media.class"]
|
||||
local pmrl_active = nil
|
||||
pmrl = lutils.getPriorityMediaRoleLink(mc)
|
||||
|
||||
log:debug("Rescanning media role links...")
|
||||
|
||||
local function onMediaRoleLinkActivated (l, e)
|
||||
local si_id = tonumber(l.properties ["main.item.id"])
|
||||
local target_id = tonumber(l.properties ["target.item.id"])
|
||||
local si_flags = lutils:get_flags(si_id)
|
||||
|
||||
if e then
|
||||
log:warning(l, "failed to activate media role link: " .. e)
|
||||
if si_flags ~= nil then
|
||||
si_flags.peer_id = nil
|
||||
end
|
||||
l:remove()
|
||||
else
|
||||
log:info(l, "media role link activated successfully")
|
||||
si_flags.failed_peer_id = nil
|
||||
if si_flags.peer_id == nil then
|
||||
si_flags.peer_id = target_id
|
||||
end
|
||||
si_flags.failed_count = 0
|
||||
end
|
||||
|
||||
-- advance only when all pending activations are completed
|
||||
pending_activations = pending_activations - 1
|
||||
if pending_activations <= 0 then
|
||||
log:info("All media role links activated")
|
||||
transition:advance()
|
||||
end
|
||||
end
|
||||
|
||||
for link in om:iterate {
|
||||
type = "SiLink",
|
||||
Constraint { "is.media.role.link", "=", true },
|
||||
Constraint { "target.media.class", "=", mc },
|
||||
} do
|
||||
-- deactivate all links if suspend playback metadata is present
|
||||
if suspend then
|
||||
link:deactivate(Feature.SessionItem.ACTIVE)
|
||||
end
|
||||
|
||||
local active = ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0)
|
||||
|
||||
log:debug(string.format(" .. looking at link(%d) active %s pmrl %s", link.id, tostring(active),
|
||||
tostring(link == pmrl)))
|
||||
|
||||
if link == pmrl then
|
||||
pmrl_active = active
|
||||
restoreVolume(om, pmrl)
|
||||
goto continue
|
||||
end
|
||||
|
||||
local action = lutils.getAction(pmrl, link)
|
||||
|
||||
log:debug(string.format(" .. apply action(%s) on link(%d)", action, link.id, tostring(active)))
|
||||
|
||||
if action == "cork" then
|
||||
if active then
|
||||
link:deactivate(Feature.SessionItem.ACTIVE)
|
||||
end
|
||||
elseif action == "mix" then
|
||||
if not active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
link:activate(Feature.SessionItem.ACTIVE, onMediaRoleLinkActivated)
|
||||
end
|
||||
restoreVolume(om, link)
|
||||
elseif action == "duck" then
|
||||
if not active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
link:activate(Feature.SessionItem.ACTIVE, onMediaRoleLinkActivated)
|
||||
end
|
||||
duckVolume(om, link)
|
||||
else
|
||||
log:warning("Unknown action: " .. action)
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
if pmrl and not pmrl_active then
|
||||
pending_activations = pending_activations + 1
|
||||
pmrl:activate(Feature.SessionItem.ACTIVE, onMediaRoleLinkActivated)
|
||||
restoreVolume(om, pmrl)
|
||||
end
|
||||
|
||||
-- just advance transition if no pending activations are needed
|
||||
if pending_activations <= 0 then
|
||||
log:debug("All media role links rescanned")
|
||||
transition:advance()
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
}:register()
|
||||
Loading…
Add table
Reference in a new issue