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>
141 lines
3.7 KiB
C++
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
|