fix: broadcast external volume/mute changes + fix browser stream volume
Issue 1 - Volume/mute changes from external apps (browser YT player, pulsemixer) not reflected in the frontend: - on_node_param and on_node_info updated node state but never called notifyChanged(), so the SSE broadcast was never triggered for external changes. - Add engine_ref back-pointer to Object (set in create_proxy_for_object). - Call notifyChanged() at the end of on_node_info and after updating Props in on_node_param, so any external volume/mute change immediately broadcasts. Issue 2 - Volume slider has no audible effect on browser/app streams: - setNodeVolume only set SPA_PROP_volume (single float). Browser streams (Chromium, Firefox) use SPA_PROP_channelVolumes (per-channel float array) and ignore the single-float property. - Now set both SPA_PROP_volume AND SPA_PROP_channelVolumes (using the node's known channel count, defaulting to stereo if unknown). ALSA hardware nodes respond to SPA_PROP_volume; app streams respond to SPA_PROP_channelVolumes. Note: wires auto-reconnecting is WirePlumber session policy (by design) — WirePlumber re-links any stream that loses its connection to the default sink. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,10 @@ static void on_node_info(void *data, const struct pw_node_info *info) {
|
|||||||
|
|
||||||
nobj->node.changed = true;
|
nobj->node.changed = true;
|
||||||
nobj->node.ready = true;
|
nobj->node.ready = true;
|
||||||
|
|
||||||
|
// Notify so frontend reflects property changes (sample rate, media name, etc.)
|
||||||
|
if (obj->engine_ref)
|
||||||
|
obj->engine_ref->notifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse audio format and Props params
|
// Parse audio format and Props params
|
||||||
@@ -227,6 +231,9 @@ static void on_node_param(void *data, int seq,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
nobj->node.changed = true;
|
nobj->node.changed = true;
|
||||||
|
// Broadcast live volume/mute changes from any source (browser, pulsemixer, etc.)
|
||||||
|
if (obj->engine_ref)
|
||||||
|
obj->engine_ref->notifyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +340,7 @@ static void create_proxy_for_object(GraphEngine::Object *obj, GraphEngine *engin
|
|||||||
obj->proxy = proxy;
|
obj->proxy = proxy;
|
||||||
obj->destroy_info = destroy_info;
|
obj->destroy_info = destroy_info;
|
||||||
obj->pending_seq = 0;
|
obj->pending_seq = 0;
|
||||||
|
obj->engine_ref = engine;
|
||||||
pw_proxy_add_object_listener(proxy,
|
pw_proxy_add_object_listener(proxy,
|
||||||
&obj->object_listener, events, obj);
|
&obj->object_listener, events, obj);
|
||||||
pw_proxy_add_listener(proxy,
|
pw_proxy_add_listener(proxy,
|
||||||
@@ -591,7 +599,7 @@ static const struct pw_registry_events registry_events = {
|
|||||||
|
|
||||||
GraphEngine::Object::Object(uint32_t id, Type type)
|
GraphEngine::Object::Object(uint32_t id, Type type)
|
||||||
: id(id), type(type), proxy(nullptr), info(nullptr),
|
: id(id), type(type), proxy(nullptr), info(nullptr),
|
||||||
destroy_info(nullptr), pending_seq(0)
|
destroy_info(nullptr), pending_seq(0), engine_ref(nullptr)
|
||||||
{
|
{
|
||||||
spa_zero(proxy_listener);
|
spa_zero(proxy_listener);
|
||||||
spa_zero(object_listener);
|
spa_zero(object_listener);
|
||||||
@@ -959,7 +967,14 @@ bool GraphEngine::setNodeVolume(uint32_t node_id, float volume) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Props param with volume
|
// Build Props param with both SPA_PROP_volume and SPA_PROP_channelVolumes.
|
||||||
|
// Hardware ALSA sinks respond to SPA_PROP_volume; browser/app streams use
|
||||||
|
// SPA_PROP_channelVolumes (per-channel). Setting both covers all node types.
|
||||||
|
uint32_t n_ch = (nobj->node.channels > 0 && nobj->node.channels <= 32)
|
||||||
|
? nobj->node.channels : 2;
|
||||||
|
float ch_vols[32];
|
||||||
|
for (uint32_t i = 0; i < n_ch; i++) ch_vols[i] = volume;
|
||||||
|
|
||||||
uint8_t buf[1024];
|
uint8_t buf[1024];
|
||||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
||||||
struct spa_pod_frame f;
|
struct spa_pod_frame f;
|
||||||
@@ -967,6 +982,8 @@ bool GraphEngine::setNodeVolume(uint32_t node_id, float volume) {
|
|||||||
spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
|
spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
|
||||||
spa_pod_builder_prop(&b, SPA_PROP_volume, 0);
|
spa_pod_builder_prop(&b, SPA_PROP_volume, 0);
|
||||||
spa_pod_builder_float(&b, volume);
|
spa_pod_builder_float(&b, volume);
|
||||||
|
spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0);
|
||||||
|
spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, n_ch, ch_vols);
|
||||||
struct spa_pod *param = (struct spa_pod*)spa_pod_builder_pop(&b, &f);
|
struct spa_pod *param = (struct spa_pod*)spa_pod_builder_pop(&b, &f);
|
||||||
|
|
||||||
int res = pw_node_set_param((pw_node*)nobj->proxy,
|
int res = pw_node_set_param((pw_node*)nobj->proxy,
|
||||||
|
|||||||
@@ -72,15 +72,16 @@ public:
|
|||||||
// Object management (called from C callbacks)
|
// Object management (called from C callbacks)
|
||||||
struct Object {
|
struct Object {
|
||||||
enum Type { ObjNode, ObjPort, ObjLink };
|
enum Type { ObjNode, ObjPort, ObjLink };
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
Type type;
|
Type type;
|
||||||
void *proxy;
|
void *proxy;
|
||||||
void *info;
|
void *info;
|
||||||
void (*destroy_info)(void*);
|
void (*destroy_info)(void*);
|
||||||
spa_hook proxy_listener;
|
spa_hook proxy_listener;
|
||||||
spa_hook object_listener;
|
spa_hook object_listener;
|
||||||
int pending_seq;
|
int pending_seq;
|
||||||
spa_list pending_link;
|
spa_list pending_link;
|
||||||
|
GraphEngine *engine_ref; // back-pointer set in create_proxy_for_object
|
||||||
|
|
||||||
Object(uint32_t id, Type type);
|
Object(uint32_t id, Type type);
|
||||||
virtual ~Object();
|
virtual ~Object();
|
||||||
|
|||||||
Reference in New Issue
Block a user