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.
Files changed (112) hide show
  1. package/README.md +29 -9
  2. package/dist/bindings/context.d.ts +20 -0
  3. package/dist/bindings/context.d.ts.map +1 -0
  4. package/dist/bindings/context.js +12 -0
  5. package/dist/bindings/context.js.map +1 -0
  6. package/dist/bindings/core.d.ts +34 -0
  7. package/dist/bindings/core.d.ts.map +1 -0
  8. package/dist/bindings/core.js +36 -0
  9. package/dist/bindings/core.js.map +1 -0
  10. package/dist/bindings/dict.d.ts +3 -0
  11. package/dist/bindings/dict.d.ts.map +1 -0
  12. package/dist/bindings/dict.js +11 -0
  13. package/dist/bindings/dict.js.map +1 -0
  14. package/dist/bindings/hook.d.ts +3 -0
  15. package/dist/bindings/hook.d.ts.map +1 -0
  16. package/dist/bindings/hook.js +14 -0
  17. package/dist/bindings/hook.js.map +1 -0
  18. package/dist/bindings/index.d.ts +32 -0
  19. package/dist/bindings/index.d.ts.map +1 -0
  20. package/dist/bindings/index.js +16 -0
  21. package/dist/bindings/index.js.map +1 -0
  22. package/dist/bindings/list.d.ts +2 -0
  23. package/dist/bindings/list.d.ts.map +1 -0
  24. package/dist/bindings/list.js +7 -0
  25. package/dist/bindings/list.js.map +1 -0
  26. package/dist/bindings/loop.d.ts +33 -0
  27. package/dist/bindings/loop.d.ts.map +1 -0
  28. package/dist/bindings/loop.js +17 -0
  29. package/dist/bindings/loop.js.map +1 -0
  30. package/dist/bindings/main-loop.d.ts +47 -0
  31. package/dist/bindings/main-loop.d.ts.map +1 -0
  32. package/dist/bindings/main-loop.js +15 -0
  33. package/dist/bindings/main-loop.js.map +1 -0
  34. package/dist/bindings/node.d.ts +50 -0
  35. package/dist/bindings/node.d.ts.map +1 -0
  36. package/dist/bindings/node.js +81 -0
  37. package/dist/bindings/node.js.map +1 -0
  38. package/dist/bindings/param.d.ts +25 -0
  39. package/dist/bindings/param.d.ts.map +1 -0
  40. package/dist/bindings/param.js +33 -0
  41. package/dist/bindings/param.js.map +1 -0
  42. package/dist/bindings/pipewire.d.ts +19 -0
  43. package/dist/bindings/pipewire.d.ts.map +1 -0
  44. package/dist/bindings/pipewire.js +5 -0
  45. package/dist/bindings/pipewire.js.map +1 -0
  46. package/dist/bindings/pod.d.ts +2 -0
  47. package/dist/bindings/pod.d.ts.map +1 -0
  48. package/dist/bindings/pod.js +6 -0
  49. package/dist/bindings/pod.js.map +1 -0
  50. package/dist/bindings/properties.d.ts +2 -0
  51. package/dist/bindings/properties.d.ts.map +1 -0
  52. package/dist/bindings/properties.js +3 -0
  53. package/dist/bindings/properties.js.map +1 -0
  54. package/dist/bindings/props.d.ts +59 -0
  55. package/dist/bindings/props.d.ts.map +1 -0
  56. package/dist/bindings/props.js +59 -0
  57. package/dist/bindings/props.js.map +1 -0
  58. package/dist/bindings/proxy.d.ts +11 -0
  59. package/dist/bindings/proxy.d.ts.map +1 -0
  60. package/dist/bindings/proxy.js +5 -0
  61. package/dist/bindings/proxy.js.map +1 -0
  62. package/dist/bindings/registry.d.ts +20 -0
  63. package/dist/bindings/registry.d.ts.map +1 -0
  64. package/dist/bindings/registry.js +19 -0
  65. package/dist/bindings/registry.js.map +1 -0
  66. package/dist/bindings/thread-loop.d.ts +65 -0
  67. package/dist/bindings/thread-loop.d.ts.map +1 -0
  68. package/dist/bindings/thread-loop.js +18 -0
  69. package/dist/bindings/thread-loop.js.map +1 -0
  70. package/dist/index.d.ts +5 -15
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +5 -15
  73. package/dist/index.js.map +1 -1
  74. package/dist/node.d.ts +61 -37
  75. package/dist/node.d.ts.map +1 -1
  76. package/dist/node.js +104 -69
  77. package/dist/node.js.map +1 -1
  78. package/dist/object.d.ts +18 -0
  79. package/dist/object.d.ts.map +1 -0
  80. package/dist/object.js +22 -0
  81. package/dist/object.js.map +1 -0
  82. package/dist/pipewire.d.ts +50 -15
  83. package/dist/pipewire.d.ts.map +1 -1
  84. package/dist/pipewire.js +94 -3
  85. package/dist/pipewire.js.map +1 -1
  86. package/examples/basic.ts +20 -0
  87. package/examples/event-registry-async.ts +1 -1
  88. package/examples/event-registry-sync.ts +3 -2
  89. package/examples/node-events.ts +34 -0
  90. package/package.json +2 -3
  91. package/src/{core.ts → bindings/core.ts} +8 -6
  92. package/src/bindings/index.ts +16 -0
  93. package/src/bindings/node.ts +92 -0
  94. package/src/bindings/param.ts +33 -0
  95. package/src/bindings/pipewire.ts +6 -0
  96. package/src/{registry.ts → bindings/registry.ts} +2 -0
  97. package/src/index.ts +5 -15
  98. package/src/node.ts +178 -78
  99. package/src/object.ts +36 -0
  100. package/src/pipewire.ts +154 -3
  101. package/src/param.ts +0 -33
  102. /package/src/{context.ts → bindings/context.ts} +0 -0
  103. /package/src/{dict.ts → bindings/dict.ts} +0 -0
  104. /package/src/{hook.ts → bindings/hook.ts} +0 -0
  105. /package/src/{list.ts → bindings/list.ts} +0 -0
  106. /package/src/{loop.ts → bindings/loop.ts} +0 -0
  107. /package/src/{main-loop.ts → bindings/main-loop.ts} +0 -0
  108. /package/src/{pod.ts → bindings/pod.ts} +0 -0
  109. /package/src/{properties.ts → bindings/properties.ts} +0 -0
  110. /package/src/{props.ts → bindings/props.ts} +0 -0
  111. /package/src/{proxy.ts → bindings/proxy.ts} +0 -0
  112. /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
+ }
@@ -0,0 +1,6 @@
1
+ import koffi from "koffi";
2
+
3
+ const pipewire = koffi.load("libpipewire-0.3.so.0");
4
+
5
+ export const pw_init = pipewire.func("pw_init", "void", ["int**", "char***"]);
6
+ export const pw_deinit = pipewire.func("pw_deinit", "void", []);
@@ -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 * as context from "./context.js";
2
- import * as core from "./core.js";
3
- import * as dict from "./dict.js";
4
- import * as hook from "./hook.js";
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 { context, core, dict, hook, loop, main_loop, node, param, pod, props, pipewire, proxy, registry, thread_loop };
6
+ export { Pipewire, PipewireObject, PipewireNode, bindings };
package/src/node.ts CHANGED
@@ -1,81 +1,181 @@
1
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_node = koffi.opaque("pw_node");
10
-
11
- export const pw_node_state = koffi.enumeration("pw_node_state", {
12
- PW_NODE_STATE_ERROR: -1,
13
- PW_NODE_STATE_CREATING: 0,
14
- PW_NODE_STATE_SUSPENDED: 1,
15
- PW_NODE_STATE_IDLE: 2,
16
- PW_NODE_STATE_RUNNING: 3
17
- });
18
-
19
- export const PW_NODE_CHANGE_MASK = {
20
- INPUT_PORTS: 1 << 0,
21
- OUTPUT_PORTS: 1 << 1,
22
- STATE: 1 << 2,
23
- PROPS: 1 << 3,
24
- PARAMS: 1 << 4
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 const pw_node_info = koffi.struct("pw_node_info", {
28
- id: "uint32_t",
29
- max_input_ports: "uint32_t",
30
- max_output_ports: "uint32_t",
31
- change_mask: "uint64_t",
32
- n_input_ports: "uint32_t",
33
- n_output_ports: "uint32_t",
34
- state: pw_node_state,
35
- error: "const char*",
36
- props: koffi.pointer(spa_dict),
37
- params: koffi.pointer(spa_param_info),
38
- n_params: "uint32_t"
39
- });
40
-
41
- export const pw_node_events_info = koffi.proto("void", [
42
- "void*", // data
43
- koffi.pointer(pw_node_info) // info
44
- ]);
45
-
46
- export const pw_node_events_param = koffi.proto("void", [
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
- const pipewire = koffi.load("libpipewire-0.3.so.0");
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
- export const pw_init = pipewire.func("pw_init", "void", ["int**", "char***"]);
6
- export const pw_deinit = pipewire.func("pw_deinit", "void", []);
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