pipewire 1.4.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -9
- package/dist/bindings/context.d.ts +20 -0
- package/dist/bindings/context.d.ts.map +1 -0
- package/dist/bindings/context.js +12 -0
- package/dist/bindings/context.js.map +1 -0
- package/dist/bindings/core.d.ts +34 -0
- package/dist/bindings/core.d.ts.map +1 -0
- package/dist/bindings/core.js +36 -0
- package/dist/bindings/core.js.map +1 -0
- package/dist/bindings/dict.d.ts +3 -0
- package/dist/bindings/dict.d.ts.map +1 -0
- package/dist/bindings/dict.js +11 -0
- package/dist/bindings/dict.js.map +1 -0
- package/dist/bindings/hook.d.ts +3 -0
- package/dist/bindings/hook.d.ts.map +1 -0
- package/dist/bindings/hook.js +14 -0
- package/dist/bindings/hook.js.map +1 -0
- package/dist/bindings/index.d.ts +32 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +16 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/list.d.ts +2 -0
- package/dist/bindings/list.d.ts.map +1 -0
- package/dist/bindings/list.js +7 -0
- package/dist/bindings/list.js.map +1 -0
- package/dist/bindings/loop.d.ts +33 -0
- package/dist/bindings/loop.d.ts.map +1 -0
- package/dist/bindings/loop.js +17 -0
- package/dist/bindings/loop.js.map +1 -0
- package/dist/bindings/main-loop.d.ts +47 -0
- package/dist/bindings/main-loop.d.ts.map +1 -0
- package/dist/bindings/main-loop.js +15 -0
- package/dist/bindings/main-loop.js.map +1 -0
- package/dist/bindings/node.d.ts +50 -0
- package/dist/bindings/node.d.ts.map +1 -0
- package/dist/bindings/node.js +81 -0
- package/dist/bindings/node.js.map +1 -0
- package/dist/bindings/param.d.ts +25 -0
- package/dist/bindings/param.d.ts.map +1 -0
- package/dist/bindings/param.js +33 -0
- package/dist/bindings/param.js.map +1 -0
- package/dist/bindings/pipewire.d.ts +19 -0
- package/dist/bindings/pipewire.d.ts.map +1 -0
- package/dist/bindings/pipewire.js +5 -0
- package/dist/bindings/pipewire.js.map +1 -0
- package/dist/bindings/pod.d.ts +2 -0
- package/dist/bindings/pod.d.ts.map +1 -0
- package/dist/bindings/pod.js +6 -0
- package/dist/bindings/pod.js.map +1 -0
- package/dist/bindings/properties.d.ts +2 -0
- package/dist/bindings/properties.d.ts.map +1 -0
- package/dist/bindings/properties.js +3 -0
- package/dist/bindings/properties.js.map +1 -0
- package/dist/bindings/props.d.ts +59 -0
- package/dist/bindings/props.d.ts.map +1 -0
- package/dist/bindings/props.js +59 -0
- package/dist/bindings/props.js.map +1 -0
- package/dist/bindings/proxy.d.ts +11 -0
- package/dist/bindings/proxy.d.ts.map +1 -0
- package/dist/bindings/proxy.js +5 -0
- package/dist/bindings/proxy.js.map +1 -0
- package/dist/bindings/registry.d.ts +20 -0
- package/dist/bindings/registry.d.ts.map +1 -0
- package/dist/bindings/registry.js +19 -0
- package/dist/bindings/registry.js.map +1 -0
- package/dist/bindings/thread-loop.d.ts +65 -0
- package/dist/bindings/thread-loop.d.ts.map +1 -0
- package/dist/bindings/thread-loop.js +18 -0
- package/dist/bindings/thread-loop.js.map +1 -0
- package/dist/index.d.ts +5 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -15
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +61 -37
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +104 -69
- package/dist/node.js.map +1 -1
- package/dist/object.d.ts +18 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +22 -0
- package/dist/object.js.map +1 -0
- package/dist/pipewire.d.ts +50 -15
- package/dist/pipewire.d.ts.map +1 -1
- package/dist/pipewire.js +94 -3
- package/dist/pipewire.js.map +1 -1
- package/examples/basic.ts +20 -0
- package/examples/event-registry-async.ts +1 -1
- package/examples/event-registry-sync.ts +3 -2
- package/examples/node-events.ts +34 -0
- package/package.json +2 -3
- package/src/{core.ts → bindings/core.ts} +8 -6
- package/src/bindings/index.ts +16 -0
- package/src/bindings/node.ts +92 -0
- package/src/bindings/param.ts +33 -0
- package/src/bindings/pipewire.ts +6 -0
- package/src/{registry.ts → bindings/registry.ts} +2 -0
- package/src/index.ts +5 -15
- package/src/node.ts +178 -78
- package/src/object.ts +36 -0
- package/src/pipewire.ts +154 -3
- package/src/param.ts +0 -33
- /package/src/{context.ts → bindings/context.ts} +0 -0
- /package/src/{dict.ts → bindings/dict.ts} +0 -0
- /package/src/{hook.ts → bindings/hook.ts} +0 -0
- /package/src/{list.ts → bindings/list.ts} +0 -0
- /package/src/{loop.ts → bindings/loop.ts} +0 -0
- /package/src/{main-loop.ts → bindings/main-loop.ts} +0 -0
- /package/src/{pod.ts → bindings/pod.ts} +0 -0
- /package/src/{properties.ts → bindings/properties.ts} +0 -0
- /package/src/{props.ts → bindings/props.ts} +0 -0
- /package/src/{proxy.ts → bindings/proxy.ts} +0 -0
- /package/src/{thread-loop.ts → bindings/thread-loop.ts} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import koffi from "koffi";
|
|
2
|
+
import { spa_hook } from "./hook.js";
|
|
3
|
+
import { spa_pod } from "./pod.js";
|
|
4
|
+
import { spa_dict } from "./dict.js";
|
|
5
|
+
import { spa_param_info } from "./param.js";
|
|
6
|
+
|
|
7
|
+
const pipewire = koffi.load("libpipewire-0.3.so.0");
|
|
8
|
+
|
|
9
|
+
export const PW_VERSION_NODE = 3;
|
|
10
|
+
export const PW_VERSION_NODE_EVENTS = 0;
|
|
11
|
+
|
|
12
|
+
export const PW_NODE_CHANGE_MASK = {
|
|
13
|
+
INPUT_PORTS: 1 << 0,
|
|
14
|
+
OUTPUT_PORTS: 1 << 1,
|
|
15
|
+
STATE: 1 << 2,
|
|
16
|
+
PROPS: 1 << 3,
|
|
17
|
+
PARAMS: 1 << 4
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export enum pw_node_state {
|
|
21
|
+
PW_NODE_STATE_ERROR = -1,
|
|
22
|
+
PW_NODE_STATE_CREATING = 0,
|
|
23
|
+
PW_NODE_STATE_SUSPENDED = 1,
|
|
24
|
+
PW_NODE_STATE_IDLE = 2,
|
|
25
|
+
PW_NODE_STATE_RUNNING = 3
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const pw_node = koffi.opaque("pw_node");
|
|
29
|
+
|
|
30
|
+
export const pw_node_state_enum = koffi.enumeration("pw_node_state", {
|
|
31
|
+
PW_NODE_STATE_ERROR: -1,
|
|
32
|
+
PW_NODE_STATE_CREATING: 0,
|
|
33
|
+
PW_NODE_STATE_SUSPENDED: 1,
|
|
34
|
+
PW_NODE_STATE_IDLE: 2,
|
|
35
|
+
PW_NODE_STATE_RUNNING: 3
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const pw_node_info = koffi.struct("pw_node_info", {
|
|
39
|
+
id: "uint32_t",
|
|
40
|
+
max_input_ports: "uint32_t",
|
|
41
|
+
max_output_ports: "uint32_t",
|
|
42
|
+
change_mask: "uint64_t",
|
|
43
|
+
n_input_ports: "uint32_t",
|
|
44
|
+
n_output_ports: "uint32_t",
|
|
45
|
+
state: pw_node_state_enum,
|
|
46
|
+
error: "const char*",
|
|
47
|
+
props: koffi.pointer(spa_dict),
|
|
48
|
+
params: koffi.pointer(spa_param_info),
|
|
49
|
+
n_params: "uint32_t"
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const pw_node_events_info = koffi.proto("void", [
|
|
53
|
+
"void*", // data
|
|
54
|
+
koffi.pointer(pw_node_info) // info
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
export const pw_node_events_param = koffi.proto("void", [
|
|
58
|
+
"void*", // data
|
|
59
|
+
"int", // seq
|
|
60
|
+
"uint32_t", // id
|
|
61
|
+
"uint32_t", // index
|
|
62
|
+
"uint32_t", // next
|
|
63
|
+
koffi.pointer(spa_pod) // param
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
export const pw_node_events = koffi.struct("pw_node_events", {
|
|
67
|
+
version: "uint32_t",
|
|
68
|
+
info: koffi.pointer(pw_node_events_info),
|
|
69
|
+
param: koffi.pointer(pw_node_events_param)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const pw_node_add_listener = pipewire.func("pw_node_add_listener", "int", [
|
|
73
|
+
koffi.pointer(pw_node), // object
|
|
74
|
+
koffi.pointer(spa_hook), // listener
|
|
75
|
+
koffi.pointer(pw_node_events), // events
|
|
76
|
+
"void*" // data
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
export const pw_node_enum_params = pipewire.func("pw_node_enum_params", "int", [
|
|
80
|
+
koffi.pointer(pw_node), // object
|
|
81
|
+
"int", // seq
|
|
82
|
+
"uint32_t", // id
|
|
83
|
+
"uint32_t", // start
|
|
84
|
+
"uint32_t", // num
|
|
85
|
+
koffi.pointer(spa_pod) // filter
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
export const pw_node_subscribe_params = pipewire.func("pw_node_subscribe_params", "int", [
|
|
89
|
+
koffi.pointer(pw_node), // object
|
|
90
|
+
"uint32_t*", // ids
|
|
91
|
+
"uint32_t" // n_ids
|
|
92
|
+
]);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import koffi from "koffi";
|
|
2
|
+
|
|
3
|
+
export const spa_param_info = koffi.struct("spa_param_info", {
|
|
4
|
+
id: "uint32_t",
|
|
5
|
+
flags: "uint32_t",
|
|
6
|
+
user: "uint32_t",
|
|
7
|
+
seq: "int32_t",
|
|
8
|
+
padding: koffi.array("uint32_t", 4)
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export enum spa_param_type {
|
|
12
|
+
SPA_PARAM_Invalid,
|
|
13
|
+
SPA_PARAM_PropInfo,
|
|
14
|
+
SPA_PARAM_Props,
|
|
15
|
+
SPA_PARAM_EnumFormat,
|
|
16
|
+
SPA_PARAM_Format,
|
|
17
|
+
SPA_PARAM_Buffers,
|
|
18
|
+
SPA_PARAM_Meta,
|
|
19
|
+
SPA_PARAM_IO,
|
|
20
|
+
SPA_PARAM_EnumProfile,
|
|
21
|
+
SPA_PARAM_Profile,
|
|
22
|
+
SPA_PARAM_EnumPortConfig,
|
|
23
|
+
SPA_PARAM_PortConfig,
|
|
24
|
+
SPA_PARAM_EnumRoute,
|
|
25
|
+
SPA_PARAM_Route,
|
|
26
|
+
SPA_PARAM_Control,
|
|
27
|
+
SPA_PARAM_Latency,
|
|
28
|
+
SPA_PARAM_ProcessLatency,
|
|
29
|
+
SPA_PARAM_Tag,
|
|
30
|
+
SPA_PARAM_PeerEnumFormat,
|
|
31
|
+
SPA_PARAM_Capability,
|
|
32
|
+
SPA_PARAM_PeerCapability
|
|
33
|
+
}
|
|
@@ -4,6 +4,8 @@ import { spa_hook } from "./hook.js";
|
|
|
4
4
|
|
|
5
5
|
const pipewire = koffi.load("libpipewire-0.3.so.0");
|
|
6
6
|
|
|
7
|
+
export const PW_VERSION_REGISTRY_EVENTS = 0;
|
|
8
|
+
|
|
7
9
|
export const pw_registry_add_listener = pipewire.func("pw_registry_add_listener", "int", [
|
|
8
10
|
koffi.pointer(pw_registry), // registry
|
|
9
11
|
koffi.pointer(spa_hook), // listener
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import * as loop from "./loop.js";
|
|
6
|
-
import * as main_loop from "./main-loop.js";
|
|
7
|
-
import * as node from "./node.js";
|
|
8
|
-
import * as param from "./param.js";
|
|
9
|
-
import * as pipewire from "./pipewire.js";
|
|
10
|
-
import * as pod from "./pod.js";
|
|
11
|
-
import * as props from "./props.js";
|
|
12
|
-
import * as proxy from "./proxy.js";
|
|
13
|
-
import * as registry from "./registry.js";
|
|
14
|
-
import * as thread_loop from "./thread-loop.js";
|
|
1
|
+
import Pipewire from "./pipewire.js";
|
|
2
|
+
import PipewireObject from "./object.js";
|
|
3
|
+
import PipewireNode from "./node.js";
|
|
4
|
+
import bindings from "./bindings/index.js";
|
|
15
5
|
|
|
16
|
-
export {
|
|
6
|
+
export { Pipewire, PipewireObject, PipewireNode, bindings };
|
package/src/node.ts
CHANGED
|
@@ -1,81 +1,181 @@
|
|
|
1
1
|
import koffi from "koffi";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
import type Pipewire from "./pipewire.js";
|
|
3
|
+
import type { pw_node_state } from "./bindings/node.js";
|
|
4
|
+
import type { spa_param_type } from "./bindings/param.js";
|
|
5
|
+
import PipewireObject from "./object.js";
|
|
6
|
+
import pw from "./bindings/index.js";
|
|
7
|
+
|
|
8
|
+
export default class PipewireNode extends PipewireObject {
|
|
9
|
+
static readonly pw_node_state = pw.node.pw_node_state_enum;
|
|
10
|
+
static readonly spa_param_type = pw.param.spa_param_type;
|
|
11
|
+
|
|
12
|
+
nodeName: string;
|
|
13
|
+
factoryId: number;
|
|
14
|
+
|
|
15
|
+
nodeProxy: unknown | null = null;
|
|
16
|
+
nodeListener: Buffer | null = null;
|
|
17
|
+
nodeEvents: Buffer | null = null;
|
|
18
|
+
nodeEventInfoRegister: bigint | null = null;
|
|
19
|
+
nodeEventParamRegister: bigint | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
pipewire: Pipewire,
|
|
23
|
+
id: number,
|
|
24
|
+
permissions: number,
|
|
25
|
+
type: string,
|
|
26
|
+
version: number,
|
|
27
|
+
props: { [key: string]: string }
|
|
28
|
+
) {
|
|
29
|
+
super(pipewire, id, permissions, type, version, props);
|
|
30
|
+
this.nodeName = props["node.name"]!;
|
|
31
|
+
this.factoryId = Number(props["factory.id"]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
attachListener(): void {
|
|
35
|
+
if (this.removed) return;
|
|
36
|
+
if (this.nodeProxy) throw new Error(`Listener already attached to node ${this.id}`);
|
|
37
|
+
|
|
38
|
+
this.nodeProxy = pw.registry.pw_registry_bind(
|
|
39
|
+
this.pipewire.registry,
|
|
40
|
+
this.id,
|
|
41
|
+
this.type,
|
|
42
|
+
pw.node.PW_VERSION_NODE,
|
|
43
|
+
0
|
|
44
|
+
);
|
|
45
|
+
this.nodeListener = Buffer.alloc(koffi.sizeof(pw.hook.spa_hook));
|
|
46
|
+
this.nodeEvents = Buffer.alloc(koffi.sizeof(pw.node.pw_node_events));
|
|
47
|
+
this.nodeEventInfoRegister = koffi.register(
|
|
48
|
+
this.nodeEventInfo.bind(this),
|
|
49
|
+
koffi.pointer(pw.node.pw_node_events_info)
|
|
50
|
+
);
|
|
51
|
+
this.nodeEventParamRegister = koffi.register(
|
|
52
|
+
this.nodeEventParam.bind(this),
|
|
53
|
+
koffi.pointer(pw.node.pw_node_events_param)
|
|
54
|
+
);
|
|
55
|
+
koffi.encode(this.nodeEvents, pw.node.pw_node_events, {
|
|
56
|
+
version: pw.node.PW_VERSION_NODE_EVENTS,
|
|
57
|
+
info: this.nodeEventInfoRegister,
|
|
58
|
+
param: this.nodeEventParamRegister
|
|
59
|
+
});
|
|
60
|
+
pw.node.pw_node_add_listener(this.nodeProxy, this.nodeListener, this.nodeEvents, null);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
detachListener(): void {
|
|
64
|
+
if (!this.nodeProxy) throw new Error(`Listener not attached to node ${this.id}`);
|
|
65
|
+
|
|
66
|
+
if (!this.removed) pw.proxy.pw_proxy_destroy(this.nodeProxy);
|
|
67
|
+
koffi.unregister(this.nodeEventParamRegister!);
|
|
68
|
+
koffi.unregister(this.nodeEventInfoRegister!);
|
|
69
|
+
this.nodeEventInfoRegister = null;
|
|
70
|
+
this.nodeEventParamRegister = null;
|
|
71
|
+
this.nodeEvents = null;
|
|
72
|
+
this.nodeListener = null;
|
|
73
|
+
this.nodeProxy = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
subscribeParams(params: spa_param_type[]) {
|
|
77
|
+
if (this.removed) return;
|
|
78
|
+
if (!this.nodeProxy) throw new Error(`Listener not attached to node ${this.id}`);
|
|
79
|
+
pw.node.pw_node_subscribe_params(this.nodeProxy, params, params.length);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
nodeEventInfo(_data: unknown, info: unknown): void {
|
|
83
|
+
this.emit("rawNodeInfo", { info });
|
|
84
|
+
|
|
85
|
+
const nodeInfo = koffi.decode(info, pw.node.pw_node_info);
|
|
86
|
+
|
|
87
|
+
const changesMasks = Object.keys(pw.node.PW_NODE_CHANGE_MASK) as (keyof typeof pw.node.PW_NODE_CHANGE_MASK)[];
|
|
88
|
+
const changes = changesMasks.filter((key) => nodeInfo.change_mask & pw.node.PW_NODE_CHANGE_MASK[key]);
|
|
89
|
+
|
|
90
|
+
const props = koffi.decode(nodeInfo.props, pw.dict.spa_dict);
|
|
91
|
+
if (changes.includes("PROPS") !== !!props.items) throw new Error(`Node ${this.id} props change mismatch`);
|
|
92
|
+
|
|
93
|
+
const propsObj: { [key: string]: string } | null = changes.includes("PROPS") ? {} : null;
|
|
94
|
+
if (propsObj) {
|
|
95
|
+
const propsArray = koffi.decode(props.items, pw.dict.spa_dict_item, props.n_items);
|
|
96
|
+
for (const item of propsArray) propsObj[item.key] = item.value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const paramsObj: spa_param_type[] = [];
|
|
100
|
+
if (nodeInfo.params) {
|
|
101
|
+
const params = koffi.decode(nodeInfo.params, pw.param.spa_param_info, nodeInfo.n_params);
|
|
102
|
+
for (const param of params) paramsObj.push(param.id as spa_param_type);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const nodeInfoObj: NodeInfo = {
|
|
106
|
+
id: nodeInfo.id,
|
|
107
|
+
maxInputPorts: nodeInfo.max_input_ports,
|
|
108
|
+
maxOutputPorts: nodeInfo.max_output_ports,
|
|
109
|
+
changes: changes,
|
|
110
|
+
inputPortsCount: nodeInfo.n_input_ports,
|
|
111
|
+
outputPortsCount: nodeInfo.n_output_ports,
|
|
112
|
+
state: nodeInfo.state as pw_node_state,
|
|
113
|
+
error: nodeInfo.error ? koffi.decode(nodeInfo.error, "string") : null,
|
|
114
|
+
props: propsObj,
|
|
115
|
+
params: paramsObj
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
this.emit("nodeInfo", nodeInfoObj);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
nodeEventParam(_data: unknown, seq: number, id: number, index: number, next: number, param: unknown): void {
|
|
122
|
+
this.emit("rawNodeParam", { seq, id, index, next, param });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
on(event: "nodeInfo", listener: (info: NodeInfo) => void): this;
|
|
126
|
+
on(event: "rawNodeInfo", listener: (data: RawNodeInfo) => void): this;
|
|
127
|
+
on(event: "rawNodeParam", listener: (data: RawNodeParam) => void): this;
|
|
128
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
129
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
130
|
+
return super.on(event, listener);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
once(event: "nodeInfo", listener: (info: NodeInfo) => void): this;
|
|
134
|
+
once(event: "rawNodeInfo", listener: (data: RawNodeInfo) => void): this;
|
|
135
|
+
once(event: "rawNodeParam", listener: (data: RawNodeParam) => void): this;
|
|
136
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
137
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
138
|
+
return super.once(event, listener);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
off(event: "nodeInfo", listener: (info: NodeInfo) => void): this;
|
|
142
|
+
off(event: "rawNodeInfo", listener: (data: RawNodeInfo) => void): this;
|
|
143
|
+
off(event: "rawNodeParam", listener: (data: RawNodeParam) => void): this;
|
|
144
|
+
off(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
145
|
+
off(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
146
|
+
return super.off(event, listener);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
emit(event: "nodeInfo", info: NodeInfo): boolean;
|
|
150
|
+
emit(event: "rawNodeInfo", data: RawNodeInfo): boolean;
|
|
151
|
+
emit(event: "rawNodeParam", data: RawNodeParam): boolean;
|
|
152
|
+
emit(event: string | symbol, ...args: any[]): boolean;
|
|
153
|
+
emit(event: string | symbol, ...args: any[]): boolean {
|
|
154
|
+
return super.emit(event, ...args);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type RawNodeInfo = {
|
|
159
|
+
info: unknown;
|
|
25
160
|
};
|
|
26
161
|
|
|
27
|
-
export
|
|
28
|
-
id:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
state: pw_node_state
|
|
35
|
-
error:
|
|
36
|
-
props:
|
|
37
|
-
params:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"void*", // data
|
|
48
|
-
"int", // seq
|
|
49
|
-
"uint32_t", // id
|
|
50
|
-
"uint32_t", // index
|
|
51
|
-
"uint32_t", // next
|
|
52
|
-
koffi.pointer(spa_pod) // param
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
export const pw_node_events = koffi.struct("pw_node_events", {
|
|
56
|
-
version: "uint32_t",
|
|
57
|
-
info: koffi.pointer(pw_node_events_info),
|
|
58
|
-
param: koffi.pointer(pw_node_events_param)
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
export const pw_node_add_listener = pipewire.func("pw_node_add_listener", "int", [
|
|
62
|
-
koffi.pointer(pw_node), // object
|
|
63
|
-
koffi.pointer(spa_hook), // listener
|
|
64
|
-
koffi.pointer(pw_node_events), // events
|
|
65
|
-
"void*" // data
|
|
66
|
-
]);
|
|
67
|
-
|
|
68
|
-
export const pw_node_enum_params = pipewire.func("pw_node_enum_params", "int", [
|
|
69
|
-
koffi.pointer(pw_node), // object
|
|
70
|
-
"int", // seq
|
|
71
|
-
"uint32_t", // id
|
|
72
|
-
"uint32_t", // start
|
|
73
|
-
"uint32_t", // num
|
|
74
|
-
koffi.pointer(spa_pod) // filter
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
export const pw_node_subscribe_params = pipewire.func("pw_node_subscribe_params", "int", [
|
|
78
|
-
koffi.pointer(pw_node), // object
|
|
79
|
-
"uint32_t*", // ids
|
|
80
|
-
"uint32_t" // n_ids
|
|
81
|
-
]);
|
|
162
|
+
export type NodeInfo = {
|
|
163
|
+
id: number;
|
|
164
|
+
maxInputPorts: number;
|
|
165
|
+
maxOutputPorts: number;
|
|
166
|
+
changes: (keyof typeof pw.node.PW_NODE_CHANGE_MASK)[];
|
|
167
|
+
inputPortsCount: number;
|
|
168
|
+
outputPortsCount: number;
|
|
169
|
+
state: pw_node_state;
|
|
170
|
+
error: string | null;
|
|
171
|
+
props: { [key: string]: string } | null;
|
|
172
|
+
params: spa_param_type[];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type RawNodeParam = {
|
|
176
|
+
seq: number;
|
|
177
|
+
id: number;
|
|
178
|
+
index: number;
|
|
179
|
+
next: number;
|
|
180
|
+
param: unknown;
|
|
181
|
+
};
|
package/src/object.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import type Pipewire from "./pipewire.js";
|
|
3
|
+
|
|
4
|
+
export default class PipewireObject extends EventEmitter {
|
|
5
|
+
pipewire: Pipewire;
|
|
6
|
+
removed: boolean = false;
|
|
7
|
+
|
|
8
|
+
id: number;
|
|
9
|
+
permissions: number;
|
|
10
|
+
type: string;
|
|
11
|
+
version: number;
|
|
12
|
+
props: { [key: string]: string };
|
|
13
|
+
|
|
14
|
+
objectSerial: number;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
pipewire: Pipewire,
|
|
18
|
+
id: number,
|
|
19
|
+
permissions: number,
|
|
20
|
+
type: string,
|
|
21
|
+
version: number,
|
|
22
|
+
props: { [key: string]: string }
|
|
23
|
+
) {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
this.pipewire = pipewire;
|
|
27
|
+
|
|
28
|
+
this.id = id;
|
|
29
|
+
this.permissions = permissions;
|
|
30
|
+
this.type = type;
|
|
31
|
+
this.version = version;
|
|
32
|
+
this.props = props;
|
|
33
|
+
|
|
34
|
+
this.objectSerial = Number(props["object.serial"]);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/pipewire.ts
CHANGED
|
@@ -1,6 +1,157 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
1
2
|
import koffi from "koffi";
|
|
3
|
+
import pw from "./bindings/index.js";
|
|
4
|
+
import PipewireObject from "./object.js";
|
|
5
|
+
import PipewireNode from "./node.js";
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
export default class Pipewire extends EventEmitter {
|
|
8
|
+
mainLoop: unknown;
|
|
9
|
+
loop: unknown;
|
|
10
|
+
context: unknown;
|
|
11
|
+
core: unknown;
|
|
12
|
+
registry: unknown;
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
registry_listener: Buffer;
|
|
15
|
+
registryEvents: Buffer;
|
|
16
|
+
registryEventGlobalRegister: bigint;
|
|
17
|
+
registryEventGlobalRemoveRegister: bigint;
|
|
18
|
+
|
|
19
|
+
loopInterval: NodeJS.Timeout | null = null;
|
|
20
|
+
|
|
21
|
+
objects: { [id: number]: PipewireObject } = {};
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
pw.pipewire.pw_init(0, null);
|
|
27
|
+
|
|
28
|
+
this.mainLoop = pw.main_loop.pw_main_loop_new(null);
|
|
29
|
+
this.loop = pw.main_loop.pw_main_loop_get_loop(this.mainLoop);
|
|
30
|
+
this.context = pw.context.pw_context_new(this.loop, null, 0);
|
|
31
|
+
this.core = pw.core.pw_context_connect(this.context, null, 0);
|
|
32
|
+
this.registry = pw.core.pw_core_get_registry(this.core, pw.core.PW_VERSION_REGISTRY, 0);
|
|
33
|
+
|
|
34
|
+
this.registry_listener = Buffer.alloc(koffi.sizeof(pw.hook.spa_hook));
|
|
35
|
+
this.registryEvents = Buffer.alloc(koffi.sizeof(pw.core.pw_registry_events));
|
|
36
|
+
this.registryEventGlobalRegister = koffi.register(
|
|
37
|
+
this.registryEventGlobal.bind(this),
|
|
38
|
+
koffi.pointer(pw.core.pw_registry_events_global)
|
|
39
|
+
);
|
|
40
|
+
this.registryEventGlobalRemoveRegister = koffi.register(
|
|
41
|
+
this.registryEventGlobalRemove.bind(this),
|
|
42
|
+
koffi.pointer(pw.core.pw_registry_events_global_remove)
|
|
43
|
+
);
|
|
44
|
+
koffi.encode(this.registryEvents, pw.core.pw_registry_events, {
|
|
45
|
+
version: pw.registry.PW_VERSION_REGISTRY_EVENTS,
|
|
46
|
+
global: this.registryEventGlobalRegister,
|
|
47
|
+
global_remove: this.registryEventGlobalRemoveRegister
|
|
48
|
+
});
|
|
49
|
+
pw.registry.pw_registry_add_listener(this.registry, this.registry_listener, this.registryEvents, null);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
registryEventGlobal(
|
|
53
|
+
_data: unknown,
|
|
54
|
+
id: number,
|
|
55
|
+
permissions: number,
|
|
56
|
+
type: string,
|
|
57
|
+
version: number,
|
|
58
|
+
props: unknown
|
|
59
|
+
): void {
|
|
60
|
+
this.emit("rawObjectAdded", { id, permissions, type, version, props });
|
|
61
|
+
|
|
62
|
+
if (this.objects[id]) throw new Error(`Object with id ${id} already exists`);
|
|
63
|
+
|
|
64
|
+
const propsDict = koffi.decode(props, pw.dict.spa_dict);
|
|
65
|
+
const propsArray = koffi.decode(propsDict.items, pw.dict.spa_dict_item, propsDict.n_items);
|
|
66
|
+
|
|
67
|
+
const propsObj: { [key: string]: string } = {};
|
|
68
|
+
for (const item of propsArray) propsObj[item.key] = item.value;
|
|
69
|
+
|
|
70
|
+
if (type === "PipeWire:Interface:Node")
|
|
71
|
+
this.objects[id] = new PipewireNode(this, id, permissions, type, version, propsObj);
|
|
72
|
+
else this.objects[id] = new PipewireObject(this, id, permissions, type, version, propsObj);
|
|
73
|
+
this.emit("objectAdded", this.objects[id]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
registryEventGlobalRemove(_data: unknown, id: number): void {
|
|
77
|
+
this.emit("rawObjectRemoved", { id });
|
|
78
|
+
|
|
79
|
+
if (!this.objects[id]) throw new Error(`Object with id ${id} does not exist`);
|
|
80
|
+
this.objects[id].removed = true;
|
|
81
|
+
|
|
82
|
+
if (this.objects[id] instanceof PipewireNode && this.objects[id].nodeProxy) this.objects[id].detachListener();
|
|
83
|
+
|
|
84
|
+
this.emit("objectRemoved", this.objects[id]);
|
|
85
|
+
delete this.objects[id];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
startLoop(pollInterval: number = 10, pollTimeout: number = 1): void {
|
|
89
|
+
if (this.loopInterval) throw new Error("Loop already started");
|
|
90
|
+
pw.loop.pw_loop_enter(this.loop);
|
|
91
|
+
this.loopInterval = setInterval(() => pw.loop.pw_loop_iterate(this.loop, pollTimeout), pollInterval);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
stopLoop(): void {
|
|
95
|
+
if (!this.loopInterval) throw new Error("Loop not started");
|
|
96
|
+
clearInterval(this.loopInterval);
|
|
97
|
+
this.loopInterval = null;
|
|
98
|
+
pw.loop.pw_loop_leave(this.loop);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
on(event: "objectAdded", listener: (object: PipewireObject) => void): this;
|
|
102
|
+
on(event: "objectRemoved", listener: (object: PipewireObject) => void): this;
|
|
103
|
+
on(event: "rawObjectAdded", listener: (data: RawObjectAdded) => void): this;
|
|
104
|
+
on(event: "rawObjectRemoved", listener: (data: RawObjectRemoved) => void): this;
|
|
105
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
106
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
107
|
+
return super.on(event, listener);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
once(event: "objectAdded", listener: (object: PipewireObject) => void): this;
|
|
111
|
+
once(event: "objectRemoved", listener: (object: PipewireObject) => void): this;
|
|
112
|
+
once(event: "rawObjectAdded", listener: (data: RawObjectAdded) => void): this;
|
|
113
|
+
once(event: "rawObjectRemoved", listener: (data: RawObjectRemoved) => void): this;
|
|
114
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
115
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
116
|
+
return super.once(event, listener);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
off(event: "objectAdded", listener: (object: PipewireObject) => void): this;
|
|
120
|
+
off(event: "objectRemoved", listener: (object: PipewireObject) => void): this;
|
|
121
|
+
off(event: "rawObjectAdded", listener: (data: RawObjectAdded) => void): this;
|
|
122
|
+
off(event: "rawObjectRemoved", listener: (data: RawObjectRemoved) => void): this;
|
|
123
|
+
off(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
124
|
+
off(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
125
|
+
return super.off(event, listener);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
emit(event: "objectAdded", object: PipewireObject): boolean;
|
|
129
|
+
emit(event: "objectRemoved", object: PipewireObject): boolean;
|
|
130
|
+
emit(event: "rawObjectAdded", data: RawObjectAdded): boolean;
|
|
131
|
+
emit(event: "rawObjectRemoved", data: RawObjectRemoved): boolean;
|
|
132
|
+
emit(event: string | symbol, ...args: any[]): boolean;
|
|
133
|
+
emit(event: string | symbol, ...args: any[]): boolean {
|
|
134
|
+
return super.emit(event, ...args);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
deinit(): void {
|
|
138
|
+
pw.proxy.pw_proxy_destroy(this.registry);
|
|
139
|
+
koffi.unregister(this.registryEventGlobalRegister);
|
|
140
|
+
koffi.unregister(this.registryEventGlobalRemoveRegister);
|
|
141
|
+
pw.core.pw_core_disconnect(this.core);
|
|
142
|
+
pw.context.pw_context_destroy(this.context);
|
|
143
|
+
pw.main_loop.pw_main_loop_destroy(this.mainLoop);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type RawObjectAdded = {
|
|
148
|
+
id: number;
|
|
149
|
+
permissions: number;
|
|
150
|
+
type: string;
|
|
151
|
+
version: number;
|
|
152
|
+
props: unknown;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export type RawObjectRemoved = {
|
|
156
|
+
id: number;
|
|
157
|
+
};
|
package/src/param.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import koffi from "koffi";
|
|
2
|
-
|
|
3
|
-
export const spa_param_info = koffi.struct("spa_param_info", {
|
|
4
|
-
id: "uint32_t",
|
|
5
|
-
flags: "uint32_t",
|
|
6
|
-
user: "uint32_t",
|
|
7
|
-
seq: "int32_t",
|
|
8
|
-
padding: koffi.array("uint32_t", 4)
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export const spa_param_type = {
|
|
12
|
-
SPA_PARAM_Invalid: 0,
|
|
13
|
-
SPA_PARAM_PropInfo: 1,
|
|
14
|
-
SPA_PARAM_Props: 2,
|
|
15
|
-
SPA_PARAM_EnumFormat: 3,
|
|
16
|
-
SPA_PARAM_Format: 4,
|
|
17
|
-
SPA_PARAM_Buffers: 5,
|
|
18
|
-
SPA_PARAM_Meta: 6,
|
|
19
|
-
SPA_PARAM_IO: 7,
|
|
20
|
-
SPA_PARAM_EnumProfile: 8,
|
|
21
|
-
SPA_PARAM_Profile: 9,
|
|
22
|
-
SPA_PARAM_EnumPortConfig: 10,
|
|
23
|
-
SPA_PARAM_PortConfig: 11,
|
|
24
|
-
SPA_PARAM_EnumRoute: 12,
|
|
25
|
-
SPA_PARAM_Route: 13,
|
|
26
|
-
SPA_PARAM_Control: 14,
|
|
27
|
-
SPA_PARAM_Latency: 15,
|
|
28
|
-
SPA_PARAM_ProcessLatency: 16,
|
|
29
|
-
SPA_PARAM_Tag: 17,
|
|
30
|
-
SPA_PARAM_PeerEnumFormat: 18,
|
|
31
|
-
SPA_PARAM_Capability: 19,
|
|
32
|
-
SPA_PARAM_PeerCapability: 20
|
|
33
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|