Files
pwweb/src/graph_engine.h
joren 0d3cfb5f86 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>
2026-03-30 12:30:56 +02:00

141 lines
3.7 KiB
C++

#pragma once
#include "graph_types.h"
#include <functional>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <thread>
#include <atomic>
#include <pipewire/utils.h> // pw_thread_loop, etc.
#include <spa/utils/list.h> // spa_list
#include <spa/utils/hook.h> // spa_hook
struct pw_thread_loop;
struct pw_context;
struct pw_core;
struct pw_registry;
namespace pwgraph {
class GraphEngine {
public:
using ChangeCallback = std::function<void()>;
GraphEngine();
~GraphEngine();
bool open();
void close();
// Set callback invoked when graph changes
void setOnChange(ChangeCallback cb);
// Thread-safe snapshot of the current graph state
struct Snapshot {
std::vector<Node> nodes;
std::vector<Port> ports;
std::vector<Link> links;
};
Snapshot snapshot() const;
// Connect/disconnect two ports (thread-safe, blocks until done)
bool connectPorts(uint32_t output_port_id, uint32_t input_port_id);
bool disconnectPorts(uint32_t output_port_id, uint32_t input_port_id);
// Volume control
bool setNodeVolume(uint32_t node_id, float volume);
bool setNodeMute(uint32_t node_id, bool mute);
// Module loading (virtual devices)
uint32_t loadModule(const char *name, const char *args);
bool unloadModule(uint32_t module_id);
// PipeWire internal data (exposed for C callbacks)
struct PwData {
pw_thread_loop *loop;
pw_context *context;
pw_core *core;
spa_hook core_listener;
pw_registry *registry;
spa_hook registry_listener;
int pending_seq;
spa_list pending; // doubly-linked list head for pending syncs
int last_seq;
int last_res;
bool error;
};
PwData& pwData() { return m_pw; }
// 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;
GraphEngine *engine_ref; // back-pointer set in create_proxy_for_object
Object(uint32_t id, Type type);
virtual ~Object();
};
struct NodeObj : Object {
NodeObj(uint32_t id) : Object(id, ObjNode) {}
Node node;
};
struct PortObj : Object {
PortObj(uint32_t id) : Object(id, ObjPort) {}
Port port;
};
struct LinkObj : Object {
LinkObj(uint32_t id) : Object(id, ObjLink) {}
Link link;
};
void addObject(uint32_t id, Object *obj);
Object *findObject(uint32_t id) const;
void removeObject(uint32_t id);
void clearObjects();
NodeObj *findNode(uint32_t node_id) const;
PortObj *findPort(uint32_t port_id) const;
LinkObj *findLink(uint32_t link_id) const;
void notifyChanged();
private:
PwData m_pw;
mutable std::mutex m_mutex; // protects m_objects and graph data
std::unordered_map<uint32_t, Object*> m_objects_by_id;
std::vector<Object*> m_objects;
ChangeCallback m_on_change;
std::atomic<bool> m_running;
// Port type hashes
uint32_t m_audio_type;
uint32_t m_midi_type;
uint32_t m_midi2_type;
uint32_t m_video_type;
uint32_t m_other_type;
public:
uint32_t audioPortType() const { return m_audio_type; }
uint32_t midiPortType() const { return m_midi_type; }
uint32_t videoPortType() const { return m_video_type; }
uint32_t otherPortType() const { return m_other_type; }
};
} // namespace pwgraph