diff --git a/src/graph_engine.cpp b/src/graph_engine.cpp index cb69f36..2b78c7c 100644 --- a/src/graph_engine.cpp +++ b/src/graph_engine.cpp @@ -165,6 +165,10 @@ static void on_node_info(void *data, const struct pw_node_info *info) { nobj->node.changed = 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 @@ -227,6 +231,9 @@ static void on_node_param(void *data, int seq, } } 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->destroy_info = destroy_info; obj->pending_seq = 0; + obj->engine_ref = engine; pw_proxy_add_object_listener(proxy, &obj->object_listener, events, obj); 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) : 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(object_listener); @@ -959,7 +967,14 @@ bool GraphEngine::setNodeVolume(uint32_t node_id, float volume) { 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]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); 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_prop(&b, SPA_PROP_volume, 0); 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); int res = pw_node_set_param((pw_node*)nobj->proxy, diff --git a/src/graph_engine.h b/src/graph_engine.h index 6d5d842..aaf2bcc 100644 --- a/src/graph_engine.h +++ b/src/graph_engine.h @@ -72,15 +72,16 @@ public: // Object management (called from C callbacks) struct Object { enum Type { ObjNode, ObjPort, ObjLink }; - uint32_t id; - Type type; - void *proxy; - void *info; - void (*destroy_info)(void*); - spa_hook proxy_listener; - spa_hook object_listener; - int pending_seq; - spa_list pending_link; + uint32_t id; + Type type; + void *proxy; + void *info; + void (*destroy_info)(void*); + spa_hook proxy_listener; + spa_hook object_listener; + int pending_seq; + spa_list pending_link; + GraphEngine *engine_ref; // back-pointer set in create_proxy_for_object Object(uint32_t id, Type type); virtual ~Object();