@revizly/node-av 5.2.2-beta.1

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 (254) hide show
  1. package/BUILD_LINUX.md +61 -0
  2. package/LICENSE.md +22 -0
  3. package/README.md +662 -0
  4. package/build_mac_local.sh +69 -0
  5. package/dist/api/audio-frame-buffer.d.ts +205 -0
  6. package/dist/api/audio-frame-buffer.js +287 -0
  7. package/dist/api/audio-frame-buffer.js.map +1 -0
  8. package/dist/api/bitstream-filter.d.ts +820 -0
  9. package/dist/api/bitstream-filter.js +1242 -0
  10. package/dist/api/bitstream-filter.js.map +1 -0
  11. package/dist/api/constants.d.ts +44 -0
  12. package/dist/api/constants.js +45 -0
  13. package/dist/api/constants.js.map +1 -0
  14. package/dist/api/data/test_av1.ivf +0 -0
  15. package/dist/api/data/test_h264.h264 +0 -0
  16. package/dist/api/data/test_hevc.h265 +0 -0
  17. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  18. package/dist/api/data/test_vp8.ivf +0 -0
  19. package/dist/api/data/test_vp9.ivf +0 -0
  20. package/dist/api/decoder.d.ts +1088 -0
  21. package/dist/api/decoder.js +1775 -0
  22. package/dist/api/decoder.js.map +1 -0
  23. package/dist/api/demuxer.d.ts +1219 -0
  24. package/dist/api/demuxer.js +2081 -0
  25. package/dist/api/demuxer.js.map +1 -0
  26. package/dist/api/device.d.ts +586 -0
  27. package/dist/api/device.js +961 -0
  28. package/dist/api/device.js.map +1 -0
  29. package/dist/api/encoder.d.ts +1132 -0
  30. package/dist/api/encoder.js +1988 -0
  31. package/dist/api/encoder.js.map +1 -0
  32. package/dist/api/filter-complex.d.ts +821 -0
  33. package/dist/api/filter-complex.js +1604 -0
  34. package/dist/api/filter-complex.js.map +1 -0
  35. package/dist/api/filter-presets.d.ts +1286 -0
  36. package/dist/api/filter-presets.js +2152 -0
  37. package/dist/api/filter-presets.js.map +1 -0
  38. package/dist/api/filter.d.ts +1234 -0
  39. package/dist/api/filter.js +1976 -0
  40. package/dist/api/filter.js.map +1 -0
  41. package/dist/api/fmp4-stream.d.ts +426 -0
  42. package/dist/api/fmp4-stream.js +739 -0
  43. package/dist/api/fmp4-stream.js.map +1 -0
  44. package/dist/api/hardware.d.ts +651 -0
  45. package/dist/api/hardware.js +1260 -0
  46. package/dist/api/hardware.js.map +1 -0
  47. package/dist/api/index.d.ts +17 -0
  48. package/dist/api/index.js +32 -0
  49. package/dist/api/index.js.map +1 -0
  50. package/dist/api/io-stream.d.ts +307 -0
  51. package/dist/api/io-stream.js +282 -0
  52. package/dist/api/io-stream.js.map +1 -0
  53. package/dist/api/muxer.d.ts +957 -0
  54. package/dist/api/muxer.js +2002 -0
  55. package/dist/api/muxer.js.map +1 -0
  56. package/dist/api/pipeline.d.ts +607 -0
  57. package/dist/api/pipeline.js +1145 -0
  58. package/dist/api/pipeline.js.map +1 -0
  59. package/dist/api/utilities/async-queue.d.ts +120 -0
  60. package/dist/api/utilities/async-queue.js +211 -0
  61. package/dist/api/utilities/async-queue.js.map +1 -0
  62. package/dist/api/utilities/audio-sample.d.ts +117 -0
  63. package/dist/api/utilities/audio-sample.js +112 -0
  64. package/dist/api/utilities/audio-sample.js.map +1 -0
  65. package/dist/api/utilities/channel-layout.d.ts +76 -0
  66. package/dist/api/utilities/channel-layout.js +80 -0
  67. package/dist/api/utilities/channel-layout.js.map +1 -0
  68. package/dist/api/utilities/electron-shared-texture.d.ts +328 -0
  69. package/dist/api/utilities/electron-shared-texture.js +503 -0
  70. package/dist/api/utilities/electron-shared-texture.js.map +1 -0
  71. package/dist/api/utilities/image.d.ts +207 -0
  72. package/dist/api/utilities/image.js +213 -0
  73. package/dist/api/utilities/image.js.map +1 -0
  74. package/dist/api/utilities/index.d.ts +12 -0
  75. package/dist/api/utilities/index.js +25 -0
  76. package/dist/api/utilities/index.js.map +1 -0
  77. package/dist/api/utilities/media-type.d.ts +49 -0
  78. package/dist/api/utilities/media-type.js +53 -0
  79. package/dist/api/utilities/media-type.js.map +1 -0
  80. package/dist/api/utilities/pixel-format.d.ts +89 -0
  81. package/dist/api/utilities/pixel-format.js +97 -0
  82. package/dist/api/utilities/pixel-format.js.map +1 -0
  83. package/dist/api/utilities/sample-format.d.ts +129 -0
  84. package/dist/api/utilities/sample-format.js +141 -0
  85. package/dist/api/utilities/sample-format.js.map +1 -0
  86. package/dist/api/utilities/scheduler.d.ts +138 -0
  87. package/dist/api/utilities/scheduler.js +98 -0
  88. package/dist/api/utilities/scheduler.js.map +1 -0
  89. package/dist/api/utilities/streaming.d.ts +186 -0
  90. package/dist/api/utilities/streaming.js +309 -0
  91. package/dist/api/utilities/streaming.js.map +1 -0
  92. package/dist/api/utilities/timestamp.d.ts +193 -0
  93. package/dist/api/utilities/timestamp.js +206 -0
  94. package/dist/api/utilities/timestamp.js.map +1 -0
  95. package/dist/api/utilities/whisper-model.d.ts +310 -0
  96. package/dist/api/utilities/whisper-model.js +528 -0
  97. package/dist/api/utilities/whisper-model.js.map +1 -0
  98. package/dist/api/utils.d.ts +19 -0
  99. package/dist/api/utils.js +39 -0
  100. package/dist/api/utils.js.map +1 -0
  101. package/dist/api/whisper.d.ts +324 -0
  102. package/dist/api/whisper.js +362 -0
  103. package/dist/api/whisper.js.map +1 -0
  104. package/dist/constants/channel-layouts.d.ts +53 -0
  105. package/dist/constants/channel-layouts.js +57 -0
  106. package/dist/constants/channel-layouts.js.map +1 -0
  107. package/dist/constants/constants.d.ts +2325 -0
  108. package/dist/constants/constants.js +1887 -0
  109. package/dist/constants/constants.js.map +1 -0
  110. package/dist/constants/decoders.d.ts +633 -0
  111. package/dist/constants/decoders.js +641 -0
  112. package/dist/constants/decoders.js.map +1 -0
  113. package/dist/constants/encoders.d.ts +295 -0
  114. package/dist/constants/encoders.js +308 -0
  115. package/dist/constants/encoders.js.map +1 -0
  116. package/dist/constants/hardware.d.ts +26 -0
  117. package/dist/constants/hardware.js +27 -0
  118. package/dist/constants/hardware.js.map +1 -0
  119. package/dist/constants/index.d.ts +5 -0
  120. package/dist/constants/index.js +6 -0
  121. package/dist/constants/index.js.map +1 -0
  122. package/dist/ffmpeg/index.d.ts +99 -0
  123. package/dist/ffmpeg/index.js +115 -0
  124. package/dist/ffmpeg/index.js.map +1 -0
  125. package/dist/ffmpeg/utils.d.ts +31 -0
  126. package/dist/ffmpeg/utils.js +68 -0
  127. package/dist/ffmpeg/utils.js.map +1 -0
  128. package/dist/ffmpeg/version.d.ts +6 -0
  129. package/dist/ffmpeg/version.js +7 -0
  130. package/dist/ffmpeg/version.js.map +1 -0
  131. package/dist/index.d.ts +4 -0
  132. package/dist/index.js +9 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/lib/audio-fifo.d.ts +399 -0
  135. package/dist/lib/audio-fifo.js +431 -0
  136. package/dist/lib/audio-fifo.js.map +1 -0
  137. package/dist/lib/binding.d.ts +228 -0
  138. package/dist/lib/binding.js +60 -0
  139. package/dist/lib/binding.js.map +1 -0
  140. package/dist/lib/bitstream-filter-context.d.ts +379 -0
  141. package/dist/lib/bitstream-filter-context.js +441 -0
  142. package/dist/lib/bitstream-filter-context.js.map +1 -0
  143. package/dist/lib/bitstream-filter.d.ts +140 -0
  144. package/dist/lib/bitstream-filter.js +154 -0
  145. package/dist/lib/bitstream-filter.js.map +1 -0
  146. package/dist/lib/codec-context.d.ts +1071 -0
  147. package/dist/lib/codec-context.js +1354 -0
  148. package/dist/lib/codec-context.js.map +1 -0
  149. package/dist/lib/codec-parameters.d.ts +616 -0
  150. package/dist/lib/codec-parameters.js +761 -0
  151. package/dist/lib/codec-parameters.js.map +1 -0
  152. package/dist/lib/codec-parser.d.ts +201 -0
  153. package/dist/lib/codec-parser.js +213 -0
  154. package/dist/lib/codec-parser.js.map +1 -0
  155. package/dist/lib/codec.d.ts +586 -0
  156. package/dist/lib/codec.js +713 -0
  157. package/dist/lib/codec.js.map +1 -0
  158. package/dist/lib/device.d.ts +291 -0
  159. package/dist/lib/device.js +324 -0
  160. package/dist/lib/device.js.map +1 -0
  161. package/dist/lib/dictionary.d.ts +333 -0
  162. package/dist/lib/dictionary.js +372 -0
  163. package/dist/lib/dictionary.js.map +1 -0
  164. package/dist/lib/error.d.ts +242 -0
  165. package/dist/lib/error.js +303 -0
  166. package/dist/lib/error.js.map +1 -0
  167. package/dist/lib/fifo.d.ts +416 -0
  168. package/dist/lib/fifo.js +453 -0
  169. package/dist/lib/fifo.js.map +1 -0
  170. package/dist/lib/filter-context.d.ts +712 -0
  171. package/dist/lib/filter-context.js +789 -0
  172. package/dist/lib/filter-context.js.map +1 -0
  173. package/dist/lib/filter-graph-segment.d.ts +160 -0
  174. package/dist/lib/filter-graph-segment.js +171 -0
  175. package/dist/lib/filter-graph-segment.js.map +1 -0
  176. package/dist/lib/filter-graph.d.ts +641 -0
  177. package/dist/lib/filter-graph.js +704 -0
  178. package/dist/lib/filter-graph.js.map +1 -0
  179. package/dist/lib/filter-inout.d.ts +198 -0
  180. package/dist/lib/filter-inout.js +257 -0
  181. package/dist/lib/filter-inout.js.map +1 -0
  182. package/dist/lib/filter.d.ts +243 -0
  183. package/dist/lib/filter.js +272 -0
  184. package/dist/lib/filter.js.map +1 -0
  185. package/dist/lib/format-context.d.ts +1254 -0
  186. package/dist/lib/format-context.js +1379 -0
  187. package/dist/lib/format-context.js.map +1 -0
  188. package/dist/lib/frame-utils.d.ts +116 -0
  189. package/dist/lib/frame-utils.js +98 -0
  190. package/dist/lib/frame-utils.js.map +1 -0
  191. package/dist/lib/frame.d.ts +1222 -0
  192. package/dist/lib/frame.js +1435 -0
  193. package/dist/lib/frame.js.map +1 -0
  194. package/dist/lib/hardware-device-context.d.ts +362 -0
  195. package/dist/lib/hardware-device-context.js +383 -0
  196. package/dist/lib/hardware-device-context.js.map +1 -0
  197. package/dist/lib/hardware-frames-context.d.ts +419 -0
  198. package/dist/lib/hardware-frames-context.js +477 -0
  199. package/dist/lib/hardware-frames-context.js.map +1 -0
  200. package/dist/lib/index.d.ts +35 -0
  201. package/dist/lib/index.js +60 -0
  202. package/dist/lib/index.js.map +1 -0
  203. package/dist/lib/input-format.d.ts +249 -0
  204. package/dist/lib/input-format.js +306 -0
  205. package/dist/lib/input-format.js.map +1 -0
  206. package/dist/lib/io-context.d.ts +696 -0
  207. package/dist/lib/io-context.js +769 -0
  208. package/dist/lib/io-context.js.map +1 -0
  209. package/dist/lib/log.d.ts +174 -0
  210. package/dist/lib/log.js +184 -0
  211. package/dist/lib/log.js.map +1 -0
  212. package/dist/lib/native-types.d.ts +946 -0
  213. package/dist/lib/native-types.js +2 -0
  214. package/dist/lib/native-types.js.map +1 -0
  215. package/dist/lib/option.d.ts +927 -0
  216. package/dist/lib/option.js +1583 -0
  217. package/dist/lib/option.js.map +1 -0
  218. package/dist/lib/output-format.d.ts +180 -0
  219. package/dist/lib/output-format.js +213 -0
  220. package/dist/lib/output-format.js.map +1 -0
  221. package/dist/lib/packet.d.ts +501 -0
  222. package/dist/lib/packet.js +590 -0
  223. package/dist/lib/packet.js.map +1 -0
  224. package/dist/lib/rational.d.ts +251 -0
  225. package/dist/lib/rational.js +278 -0
  226. package/dist/lib/rational.js.map +1 -0
  227. package/dist/lib/software-resample-context.d.ts +552 -0
  228. package/dist/lib/software-resample-context.js +592 -0
  229. package/dist/lib/software-resample-context.js.map +1 -0
  230. package/dist/lib/software-scale-context.d.ts +344 -0
  231. package/dist/lib/software-scale-context.js +366 -0
  232. package/dist/lib/software-scale-context.js.map +1 -0
  233. package/dist/lib/stream.d.ts +379 -0
  234. package/dist/lib/stream.js +526 -0
  235. package/dist/lib/stream.js.map +1 -0
  236. package/dist/lib/sync-queue.d.ts +179 -0
  237. package/dist/lib/sync-queue.js +197 -0
  238. package/dist/lib/sync-queue.js.map +1 -0
  239. package/dist/lib/types.d.ts +34 -0
  240. package/dist/lib/types.js +2 -0
  241. package/dist/lib/types.js.map +1 -0
  242. package/dist/lib/utilities.d.ts +1127 -0
  243. package/dist/lib/utilities.js +1225 -0
  244. package/dist/lib/utilities.js.map +1 -0
  245. package/dist/utils/electron.d.ts +49 -0
  246. package/dist/utils/electron.js +63 -0
  247. package/dist/utils/electron.js.map +1 -0
  248. package/dist/utils/index.d.ts +4 -0
  249. package/dist/utils/index.js +5 -0
  250. package/dist/utils/index.js.map +1 -0
  251. package/install/check.js +121 -0
  252. package/install/ffmpeg.js +66 -0
  253. package/jellyfin-ffmpeg.patch +181 -0
  254. package/package.json +129 -0
@@ -0,0 +1,961 @@
1
+ import { existsSync } from 'fs';
2
+ import { Device } from '../lib/device.js';
3
+ import { avGetPixFmtName } from '../lib/utilities.js';
4
+ import { Demuxer } from './demuxer.js';
5
+ /**
6
+ * Device capture API for webcams, microphones, and screen capture.
7
+ *
8
+ * Provides a high-level interface for accessing capture devices and screen recording.
9
+ * Automatically handles platform-specific input formats and device name conventions.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { DeviceAPI } from 'node-av/api';
14
+ *
15
+ * // List available devices
16
+ * const devices = await DeviceAPI.list();
17
+ * console.log('Video devices:', devices.filter(d => d.type === 'video'));
18
+ * console.log('Audio devices:', devices.filter(d => d.type === 'audio'));
19
+ *
20
+ * // Open webcam
21
+ * await using input = await DeviceAPI.openCamera({
22
+ * videoDevice: 0,
23
+ * width: 1920,
24
+ * height: 1080,
25
+ * frameRate: 30,
26
+ * });
27
+ *
28
+ * // Open microphone
29
+ * await using input = await DeviceAPI.openMicrophone({
30
+ * audioDevice: 'default',
31
+ * sampleRate: 48000,
32
+ * channels: 2,
33
+ * });
34
+ *
35
+ * // Screen capture
36
+ * await using input = await DeviceAPI.openScreen({
37
+ * width: 1920,
38
+ * height: 1080,
39
+ * frameRate: 30,
40
+ * });
41
+ *
42
+ * // Combined video + audio capture
43
+ * await using input = await DeviceAPI.openDevice({
44
+ * videoDevice: 0,
45
+ * audioDevice: 0,
46
+ * width: 1280,
47
+ * height: 720,
48
+ * frameRate: 30,
49
+ * });
50
+ * ```
51
+ *
52
+ * @see {@link CameraOptions} For camera options
53
+ * @see {@link MicrophoneOptions} For microphone options
54
+ * @see {@link ScreenCaptureOptions} For screen capture options
55
+ * @see {@link DeviceOptions} For combined video+audio capture options
56
+ * @see {@link Demuxer} For processing captured streams
57
+ */
58
+ /**
59
+ * Standard ALSA configuration file search paths (same order as ALSA's configure.ac).
60
+ *
61
+ * @internal
62
+ */
63
+ const ALSA_CONFIG_SEARCH_PATHS = ['/usr/share/alsa/alsa.conf', '/usr/local/share/alsa/alsa.conf', '/etc/alsa/alsa.conf'];
64
+ export class DeviceAPI {
65
+ /**
66
+ * List all available capture devices.
67
+ *
68
+ * Enumerates video (cameras) and audio (microphones) capture devices
69
+ * on the system using platform-specific APIs.
70
+ *
71
+ * @returns Array of device information
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const devices = await DeviceAPI.list();
76
+ *
77
+ * // Filter by type
78
+ * const cameras = devices.filter(d => d.type === 'video');
79
+ * const microphones = devices.filter(d => d.type === 'audio');
80
+ *
81
+ * // Find default devices
82
+ * const defaultCamera = devices.find(d => d.type === 'video' && d.isDefault);
83
+ * ```
84
+ */
85
+ static async list() {
86
+ DeviceAPI.ensureAlsaConfig();
87
+ return Device.list();
88
+ }
89
+ /**
90
+ * List all available capture devices synchronously.
91
+ *
92
+ * @returns Array of device information
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const devices = DeviceAPI.listSync();
97
+ * console.log('Found', devices.length, 'devices');
98
+ * ```
99
+ *
100
+ * @see {@link list} For async version
101
+ */
102
+ static listSync() {
103
+ DeviceAPI.ensureAlsaConfig();
104
+ return Device.listSync();
105
+ }
106
+ /**
107
+ * Query supported capture modes for a video device.
108
+ *
109
+ * Returns supported resolutions and frame rate ranges, sorted descending
110
+ * by resolution (area), then by max frame rate.
111
+ *
112
+ * @param deviceName - Device name as returned by `list()` (e.g. uniqueID on macOS)
113
+ *
114
+ * @returns Array of supported device modes
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const devices = await DeviceAPI.list();
119
+ * const camera = devices.find(d => d.type === 'video');
120
+ * if (camera) {
121
+ * const modes = await DeviceAPI.modes(camera.name);
122
+ * for (const mode of modes) {
123
+ * console.log(`${mode.width}x${mode.height} @ ${mode.minFrameRate}-${mode.maxFrameRate} fps`);
124
+ * }
125
+ * }
126
+ * ```
127
+ *
128
+ * @see {@link modesSync} For sync version
129
+ */
130
+ static async modes(deviceName) {
131
+ return Device.modes(deviceName);
132
+ }
133
+ /**
134
+ * Query supported capture modes for a video device synchronously.
135
+ *
136
+ * @param deviceName - Device name as returned by `listSync()`
137
+ *
138
+ * @returns Array of supported device modes
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const devices = DeviceAPI.listSync();
143
+ * const camera = devices.find(d => d.type === 'video');
144
+ * if (camera) {
145
+ * const modes = DeviceAPI.modesSync(camera.name);
146
+ * for (const mode of modes) {
147
+ * console.log(`${mode.width}x${mode.height} @ ${mode.minFrameRate}-${mode.maxFrameRate} fps`);
148
+ * }
149
+ * }
150
+ * ```
151
+ *
152
+ * @see {@link modes} For async version
153
+ */
154
+ static modesSync(deviceName) {
155
+ return Device.modesSync(deviceName);
156
+ }
157
+ /**
158
+ * Query supported audio capture modes for an audio device.
159
+ *
160
+ * Returns supported sample rates, channel counts and sample formats,
161
+ * sorted descending by sample rate, then by channel count.
162
+ *
163
+ * @param deviceName - Device name as returned by `list()` (e.g. uniqueID on macOS)
164
+ *
165
+ * @returns Array of supported audio device modes
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * const devices = await DeviceAPI.list();
170
+ * const mic = devices.find(d => d.type === 'audio');
171
+ * if (mic) {
172
+ * const modes = await DeviceAPI.audioModes(mic.name);
173
+ * for (const mode of modes) {
174
+ * console.log(`${mode.sampleRate}Hz ${mode.channels}ch`);
175
+ * }
176
+ * }
177
+ * ```
178
+ *
179
+ * @see {@link audioModesSync} For sync version
180
+ */
181
+ static async audioModes(deviceName) {
182
+ DeviceAPI.ensureAlsaConfig();
183
+ return Device.audioModes(deviceName);
184
+ }
185
+ /**
186
+ * Query supported audio capture modes for an audio device synchronously.
187
+ *
188
+ * @param deviceName - Device name as returned by `listSync()`
189
+ *
190
+ * @returns Array of supported audio device modes
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const devices = DeviceAPI.listSync();
195
+ * const mic = devices.find(d => d.type === 'audio');
196
+ * if (mic) {
197
+ * const modes = DeviceAPI.audioModesSync(mic.name);
198
+ * for (const mode of modes) {
199
+ * console.log(`${mode.sampleRate}Hz ${mode.channels}ch`);
200
+ * }
201
+ * }
202
+ * ```
203
+ *
204
+ * @see {@link audioModes} For async version
205
+ */
206
+ static audioModesSync(deviceName) {
207
+ DeviceAPI.ensureAlsaConfig();
208
+ return Device.audioModesSync(deviceName);
209
+ }
210
+ /**
211
+ * Open a camera device for capture.
212
+ *
213
+ * Creates a Demuxer for the specified video capture device.
214
+ * Uses platform-specific format and device naming conventions.
215
+ *
216
+ * @param options - Camera capture options
217
+ *
218
+ * @returns Demuxer for the camera stream
219
+ *
220
+ * @throws {Error} If device cannot be opened
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * // Open first camera with default settings
225
+ * await using input = await DeviceAPI.openCamera({ videoDevice: 0 });
226
+ *
227
+ * // Open with specific resolution and frame rate
228
+ * await using input = await DeviceAPI.openCamera({
229
+ * videoDevice: 0,
230
+ * width: 1280,
231
+ * height: 720,
232
+ * frameRate: 30,
233
+ * });
234
+ *
235
+ * // Open by device name
236
+ * await using input = await DeviceAPI.openCamera({
237
+ * videoDevice: 'FaceTime HD Camera',
238
+ * });
239
+ * ```
240
+ */
241
+ static async openCamera(options = {}) {
242
+ const format = Device.getVideoFormat();
243
+ const deviceName = DeviceAPI.buildVideoDeviceName(options);
244
+ const formatOptions = DeviceAPI.buildVideoFormatOptions(options);
245
+ return Demuxer.open(deviceName, {
246
+ format,
247
+ options: formatOptions,
248
+ });
249
+ }
250
+ /**
251
+ * Open a camera device for capture synchronously.
252
+ *
253
+ * @param options - Camera capture options
254
+ *
255
+ * @returns Demuxer for the camera stream
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * using input = DeviceAPI.openCameraSync({ videoDevice: 0 });
260
+ * ```
261
+ *
262
+ * @see {@link openCamera} For async version
263
+ */
264
+ static openCameraSync(options = {}) {
265
+ const format = Device.getVideoFormat();
266
+ const deviceName = DeviceAPI.buildVideoDeviceName(options);
267
+ const formatOptions = DeviceAPI.buildVideoFormatOptions(options);
268
+ return Demuxer.openSync(deviceName, {
269
+ format,
270
+ options: formatOptions,
271
+ });
272
+ }
273
+ /**
274
+ * Open a microphone device for capture.
275
+ *
276
+ * Creates a Demuxer for the specified audio capture device.
277
+ * Uses platform-specific format and device naming conventions.
278
+ *
279
+ * @param options - Microphone capture options
280
+ *
281
+ * @returns Demuxer for the audio stream
282
+ *
283
+ * @throws {Error} If device cannot be opened
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * // Open first microphone
288
+ * await using input = await DeviceAPI.openMicrophone({ audioDevice: 0 });
289
+ *
290
+ * // Open with specific sample rate
291
+ * await using input = await DeviceAPI.openMicrophone({
292
+ * audioDevice: 'default',
293
+ * sampleRate: 48000,
294
+ * channels: 2,
295
+ * });
296
+ * ```
297
+ */
298
+ static async openMicrophone(options = {}) {
299
+ DeviceAPI.ensureAlsaConfig(options.alsa?.configPath);
300
+ const format = Device.getAudioFormat();
301
+ const deviceName = DeviceAPI.buildAudioDeviceName(options);
302
+ const formatOptions = DeviceAPI.buildAudioFormatOptions(options);
303
+ return Demuxer.open(deviceName, {
304
+ format,
305
+ options: formatOptions,
306
+ });
307
+ }
308
+ /**
309
+ * Open a microphone device for capture synchronously.
310
+ *
311
+ * @param options - Microphone capture options
312
+ *
313
+ * @returns Demuxer for the audio stream
314
+ *
315
+ * @example
316
+ * ```typescript
317
+ * using input = DeviceAPI.openMicrophoneSync({ audioDevice: 0 });
318
+ * ```
319
+ *
320
+ * @see {@link openMicrophone} For async version
321
+ */
322
+ static openMicrophoneSync(options = {}) {
323
+ DeviceAPI.ensureAlsaConfig(options.alsa?.configPath);
324
+ const format = Device.getAudioFormat();
325
+ const deviceName = DeviceAPI.buildAudioDeviceName(options);
326
+ const formatOptions = DeviceAPI.buildAudioFormatOptions(options);
327
+ return Demuxer.openSync(deviceName, {
328
+ format,
329
+ options: formatOptions,
330
+ });
331
+ }
332
+ /**
333
+ * Open screen capture.
334
+ *
335
+ * Creates a Demuxer for screen/display capture.
336
+ * Uses platform-specific format and capture conventions.
337
+ *
338
+ * @param options - Screen capture options
339
+ *
340
+ * @returns Demuxer for the screen capture stream
341
+ *
342
+ * @throws {Error} If screen capture cannot be opened
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * // Capture entire screen
347
+ * await using input = await DeviceAPI.openScreen({
348
+ * frameRate: 30,
349
+ * });
350
+ *
351
+ * // Capture specific region
352
+ * await using input = await DeviceAPI.openScreen({
353
+ * x: 100,
354
+ * y: 100,
355
+ * width: 800,
356
+ * height: 600,
357
+ * frameRate: 30,
358
+ * });
359
+ *
360
+ * // Capture specific screen (works cross-platform)
361
+ * await using input = await DeviceAPI.openScreen({
362
+ * screenIndex: 1,
363
+ * frameRate: 30,
364
+ * });
365
+ * ```
366
+ */
367
+ static async openScreen(options = {}) {
368
+ const format = Device.getScreenFormat();
369
+ const { deviceName, formatOptions } = DeviceAPI.buildScreenCaptureParams(options);
370
+ return Demuxer.open(deviceName, {
371
+ format,
372
+ options: formatOptions,
373
+ });
374
+ }
375
+ /**
376
+ * Open screen capture synchronously.
377
+ *
378
+ * @param options - Screen capture options
379
+ *
380
+ * @returns Demuxer for the screen capture stream
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * using input = DeviceAPI.openScreenSync({ frameRate: 30 });
385
+ * ```
386
+ *
387
+ * @see {@link openScreen} For async version
388
+ */
389
+ static openScreenSync(options = {}) {
390
+ const format = Device.getScreenFormat();
391
+ const { deviceName, formatOptions } = DeviceAPI.buildScreenCaptureParams(options);
392
+ return Demuxer.openSync(deviceName, {
393
+ format,
394
+ options: formatOptions,
395
+ });
396
+ }
397
+ /**
398
+ * Open a combined video + audio device for capture.
399
+ *
400
+ * Creates a single Demuxer that captures both video and audio simultaneously.
401
+ * Supported on macOS (AVFoundation) and Windows (DirectShow).
402
+ * Not supported on Linux — use separate `openCamera()` and `openMicrophone()` calls instead.
403
+ *
404
+ * @param options - Combined device capture options
405
+ *
406
+ * @returns Demuxer for the combined video + audio stream
407
+ *
408
+ * @throws {Error} If devices cannot be opened or platform does not support combined capture
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * await using input = await DeviceAPI.openDevice({
413
+ * videoDevice: 0,
414
+ * audioDevice: 0,
415
+ * width: 1280,
416
+ * height: 720,
417
+ * frameRate: 30,
418
+ * sampleRate: 48000,
419
+ * channels: 2,
420
+ * });
421
+ * ```
422
+ */
423
+ static async openDevice(options) {
424
+ const format = Device.getVideoFormat();
425
+ const deviceName = DeviceAPI.buildCombinedDeviceName(options);
426
+ const formatOptions = DeviceAPI.buildDeviceFormatOptions(options);
427
+ return Demuxer.open(deviceName, {
428
+ format,
429
+ options: formatOptions,
430
+ });
431
+ }
432
+ /**
433
+ * Open a combined video + audio device for capture synchronously.
434
+ *
435
+ * @param options - Combined device capture options
436
+ *
437
+ * @returns Demuxer for the combined video + audio stream
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * using input = DeviceAPI.openDeviceSync({
442
+ * videoDevice: 0,
443
+ * audioDevice: 0,
444
+ * });
445
+ * ```
446
+ *
447
+ * @see {@link openDevice} For async version
448
+ */
449
+ static openDeviceSync(options) {
450
+ const format = Device.getVideoFormat();
451
+ const deviceName = DeviceAPI.buildCombinedDeviceName(options);
452
+ const formatOptions = DeviceAPI.buildDeviceFormatOptions(options);
453
+ return Demuxer.openSync(deviceName, {
454
+ format,
455
+ options: formatOptions,
456
+ });
457
+ }
458
+ /**
459
+ * Get the platform-specific input format for video devices.
460
+ *
461
+ * @returns Format name (avfoundation/v4l2/dshow)
462
+ *
463
+ * @example
464
+ * ```typescript
465
+ * const format = DeviceAPI.getVideoFormat();
466
+ * // Returns 'avfoundation' on macOS, 'v4l2' on Linux, 'dshow' on Windows
467
+ * ```
468
+ */
469
+ static getVideoFormat() {
470
+ return Device.getVideoFormat();
471
+ }
472
+ /**
473
+ * Get the platform-specific input format for audio devices.
474
+ *
475
+ * @returns Format name (avfoundation/alsa/dshow)
476
+ *
477
+ * @example
478
+ * ```typescript
479
+ * const format = DeviceAPI.getAudioFormat();
480
+ * // Returns 'avfoundation' on macOS, 'alsa' on Linux, 'dshow' on Windows
481
+ * ```
482
+ */
483
+ static getAudioFormat() {
484
+ return Device.getAudioFormat();
485
+ }
486
+ /**
487
+ * Get the platform-specific input format for screen capture.
488
+ *
489
+ * @returns Format name (avfoundation/x11grab/gdigrab)
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * const format = DeviceAPI.getScreenFormat();
494
+ * // Returns 'avfoundation' on macOS, 'x11grab' on Linux, 'gdigrab' on Windows
495
+ * ```
496
+ */
497
+ static getScreenFormat() {
498
+ return Device.getScreenFormat();
499
+ }
500
+ /**
501
+ * Check if the application has screen capture permission.
502
+ *
503
+ * On macOS 11+, uses `CGPreflightScreenCaptureAccess()` to check
504
+ * whether the app has been granted screen recording permission.
505
+ * Always returns `true` on Linux, Windows, and macOS < 11.
506
+ *
507
+ * @returns `true` if screen capture is permitted
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * if (!DeviceAPI.hasScreenCapturePermission()) {
512
+ * console.log('Screen capture permission not granted');
513
+ * DeviceAPI.requestScreenCaptureAccess(); // triggers system dialog
514
+ * }
515
+ * ```
516
+ *
517
+ * @see {@link requestScreenCaptureAccess} To trigger the permission dialog
518
+ */
519
+ static hasScreenCapturePermission() {
520
+ return Device.hasScreenCapturePermission();
521
+ }
522
+ /**
523
+ * Request screen capture permission from the user.
524
+ *
525
+ * On macOS 11+, uses `CGRequestScreenCaptureAccess()` to trigger
526
+ * the system permission dialog if not already granted.
527
+ * Always returns `true` on Linux, Windows, and macOS < 11.
528
+ *
529
+ * Note: This function returns immediately. If permission was not previously
530
+ * granted, the system dialog is shown asynchronously. The return value
531
+ * indicates whether permission was already granted at the time of the call,
532
+ * not the result of the dialog.
533
+ *
534
+ * @returns `true` if permission was already granted
535
+ *
536
+ * @example
537
+ * ```typescript
538
+ * const alreadyGranted = DeviceAPI.requestScreenCaptureAccess();
539
+ * if (!alreadyGranted) {
540
+ * console.log('Permission dialog shown — restart the app after granting access');
541
+ * }
542
+ * ```
543
+ *
544
+ * @see {@link hasScreenCapturePermission} To check current permission status
545
+ */
546
+ static requestScreenCaptureAccess() {
547
+ return Device.requestScreenCaptureAccess();
548
+ }
549
+ /**
550
+ * Build platform-specific video device name.
551
+ *
552
+ * @param options - Camera options containing videoDevice
553
+ *
554
+ * @returns Platform-specific device name string
555
+ *
556
+ * @internal
557
+ */
558
+ static buildVideoDeviceName(options) {
559
+ const device = options.videoDevice ?? 0;
560
+ const format = Device.getVideoFormat();
561
+ switch (format) {
562
+ case 'avfoundation':
563
+ // macOS: "index" or "device name"
564
+ return typeof device === 'number' ? String(device) : device;
565
+ case 'v4l2':
566
+ // Linux: /dev/video0, /dev/video1, etc.
567
+ return typeof device === 'number' ? `/dev/video${device}` : device;
568
+ case 'dshow':
569
+ // Windows: "video=Device Name"
570
+ return typeof device === 'number' ? `video=${device}` : `video=${device}`;
571
+ default:
572
+ return String(device);
573
+ }
574
+ }
575
+ /**
576
+ * Build platform-specific audio device name.
577
+ *
578
+ * @param options - Microphone options containing audioDevice
579
+ *
580
+ * @returns Platform-specific device name string
581
+ *
582
+ * @internal
583
+ */
584
+ static buildAudioDeviceName(options) {
585
+ const device = options.audioDevice ?? 0;
586
+ const format = Device.getAudioFormat();
587
+ switch (format) {
588
+ case 'avfoundation':
589
+ // macOS: ":index" or ":device name" (colon prefix for audio)
590
+ return typeof device === 'number' ? `:${device}` : `:${device}`;
591
+ case 'alsa':
592
+ // Linux: "default", "hw:0", "hw:1", etc.
593
+ if (typeof device === 'number') {
594
+ return device === 0 ? 'default' : `hw:${device}`;
595
+ }
596
+ return device;
597
+ case 'dshow':
598
+ // Windows: "audio=Device Name"
599
+ return typeof device === 'number' ? `audio=${device}` : `audio=${device}`;
600
+ default:
601
+ return String(device);
602
+ }
603
+ }
604
+ /**
605
+ * Build platform-specific combined video + audio device name.
606
+ *
607
+ * @param options - Device options containing videoDevice and audioDevice
608
+ *
609
+ * @returns Platform-specific combined device name string
610
+ *
611
+ * @internal
612
+ */
613
+ static buildCombinedDeviceName(options) {
614
+ const video = options.videoDevice ?? 0;
615
+ const audio = options.audioDevice ?? 0;
616
+ const format = Device.getVideoFormat();
617
+ switch (format) {
618
+ case 'avfoundation':
619
+ return `${video}:${audio}`;
620
+ case 'dshow':
621
+ return `video=${video}:audio=${audio}`;
622
+ default:
623
+ throw new Error('Combined video+audio capture is not supported on Linux. ' + 'Use separate openCamera() and openMicrophone() calls.');
624
+ }
625
+ }
626
+ /**
627
+ * Build video format options.
628
+ *
629
+ * @param options - Camera options containing video parameters
630
+ *
631
+ * @returns FFmpeg format options dictionary
632
+ *
633
+ * @internal
634
+ */
635
+ static buildVideoFormatOptions(options) {
636
+ const formatOptions = {};
637
+ const format = Device.getVideoFormat();
638
+ if (options.frameRate) {
639
+ formatOptions.framerate = String(options.frameRate);
640
+ }
641
+ if (options.width && options.height) {
642
+ formatOptions.video_size = `${options.width}x${options.height}`;
643
+ }
644
+ if (options.pixelFormat !== undefined) {
645
+ const name = avGetPixFmtName(options.pixelFormat);
646
+ if (name)
647
+ formatOptions.pixel_format = name;
648
+ }
649
+ else if (format === 'avfoundation') {
650
+ formatOptions.pixel_format = 'nv12';
651
+ }
652
+ // Platform-specific
653
+ if (format === 'avfoundation' && options.avfoundation) {
654
+ if (options.avfoundation.captureRawData) {
655
+ formatOptions.capture_raw_data = 'true';
656
+ }
657
+ }
658
+ if (format === 'v4l2' && options.v4l2) {
659
+ if (options.v4l2.inputFormat) {
660
+ formatOptions.input_format = options.v4l2.inputFormat;
661
+ }
662
+ }
663
+ if (format === 'dshow' && options.dshow) {
664
+ if (options.dshow.videoDeviceNumber !== undefined) {
665
+ formatOptions.video_device_number = String(options.dshow.videoDeviceNumber);
666
+ }
667
+ if (options.dshow.videoPinName) {
668
+ formatOptions.video_pin_name = options.dshow.videoPinName;
669
+ }
670
+ }
671
+ // Escape hatch
672
+ if (options.formatOptions) {
673
+ Object.assign(formatOptions, options.formatOptions);
674
+ }
675
+ return formatOptions;
676
+ }
677
+ /**
678
+ * Build audio format options.
679
+ *
680
+ * @param options - Microphone options containing audio parameters
681
+ *
682
+ * @returns FFmpeg format options dictionary
683
+ *
684
+ * @internal
685
+ */
686
+ static buildAudioFormatOptions(options) {
687
+ const formatOptions = {};
688
+ const format = Device.getAudioFormat();
689
+ if (options.sampleRate) {
690
+ formatOptions.sample_rate = String(options.sampleRate);
691
+ }
692
+ if (options.channels) {
693
+ formatOptions.channels = String(options.channels);
694
+ }
695
+ // Platform-specific (dshow)
696
+ if (format === 'dshow' && options.dshow) {
697
+ if (options.dshow.sampleSize) {
698
+ formatOptions.sample_size = String(options.dshow.sampleSize);
699
+ }
700
+ if (options.dshow.audioDeviceNumber !== undefined) {
701
+ formatOptions.audio_device_number = String(options.dshow.audioDeviceNumber);
702
+ }
703
+ if (options.dshow.audioPinName) {
704
+ formatOptions.audio_pin_name = options.dshow.audioPinName;
705
+ }
706
+ if (options.dshow.audioBufferSize) {
707
+ formatOptions.audio_buffer_size = String(options.dshow.audioBufferSize);
708
+ }
709
+ }
710
+ // Escape hatch
711
+ if (options.formatOptions) {
712
+ Object.assign(formatOptions, options.formatOptions);
713
+ }
714
+ return formatOptions;
715
+ }
716
+ /**
717
+ * Build combined video + audio format options.
718
+ *
719
+ * @param options - Device options containing both video and audio parameters
720
+ *
721
+ * @returns FFmpeg format options dictionary
722
+ *
723
+ * @internal
724
+ */
725
+ static buildDeviceFormatOptions(options) {
726
+ const formatOptions = {};
727
+ const format = Device.getVideoFormat();
728
+ // Video
729
+ if (options.frameRate) {
730
+ formatOptions.framerate = String(options.frameRate);
731
+ }
732
+ if (options.width && options.height) {
733
+ formatOptions.video_size = `${options.width}x${options.height}`;
734
+ }
735
+ if (options.pixelFormat !== undefined) {
736
+ const name = avGetPixFmtName(options.pixelFormat);
737
+ if (name)
738
+ formatOptions.pixel_format = name;
739
+ }
740
+ else if (format === 'avfoundation') {
741
+ formatOptions.pixel_format = 'nv12';
742
+ }
743
+ // Audio
744
+ if (options.sampleRate) {
745
+ formatOptions.sample_rate = String(options.sampleRate);
746
+ }
747
+ if (options.channels) {
748
+ formatOptions.channels = String(options.channels);
749
+ }
750
+ // Platform-specific
751
+ if (format === 'avfoundation' && options.avfoundation?.captureRawData) {
752
+ formatOptions.capture_raw_data = 'true';
753
+ }
754
+ if (format === 'dshow' && options.dshow) {
755
+ if (options.dshow.videoDeviceNumber !== undefined) {
756
+ formatOptions.video_device_number = String(options.dshow.videoDeviceNumber);
757
+ }
758
+ if (options.dshow.audioDeviceNumber !== undefined) {
759
+ formatOptions.audio_device_number = String(options.dshow.audioDeviceNumber);
760
+ }
761
+ if (options.dshow.videoPinName) {
762
+ formatOptions.video_pin_name = options.dshow.videoPinName;
763
+ }
764
+ if (options.dshow.audioPinName) {
765
+ formatOptions.audio_pin_name = options.dshow.audioPinName;
766
+ }
767
+ if (options.dshow.audioBufferSize) {
768
+ formatOptions.audio_buffer_size = String(options.dshow.audioBufferSize);
769
+ }
770
+ }
771
+ // Escape hatch
772
+ if (options.formatOptions) {
773
+ Object.assign(formatOptions, options.formatOptions);
774
+ }
775
+ return formatOptions;
776
+ }
777
+ /**
778
+ * Build screen capture parameters.
779
+ *
780
+ * @param options - Screen capture options
781
+ *
782
+ * @returns Device name and format options for screen capture
783
+ *
784
+ * @internal
785
+ */
786
+ static buildScreenCaptureParams(options) {
787
+ const format = Device.getScreenFormat();
788
+ const formatOptions = {};
789
+ let deviceName;
790
+ // Resolve screen bounds from screenIndex if provided
791
+ const screenBounds = DeviceAPI.resolveScreenBounds(options.screenIndex);
792
+ switch (format) {
793
+ case 'avfoundation': {
794
+ const screenIdx = options.screenIndex ?? 0;
795
+ deviceName = `Capture screen ${screenIdx}`;
796
+ if (options.frameRate) {
797
+ formatOptions.framerate = String(options.frameRate);
798
+ }
799
+ if (options.width && options.height) {
800
+ formatOptions.video_size = `${options.width}x${options.height}`;
801
+ }
802
+ if (options.drawMouse !== undefined) {
803
+ formatOptions.capture_cursor = options.drawMouse ? '1' : '0';
804
+ }
805
+ if (options.pixelFormat !== undefined) {
806
+ const name = avGetPixFmtName(options.pixelFormat);
807
+ if (name)
808
+ formatOptions.pixel_format = name;
809
+ }
810
+ // Window capture via ScreenCaptureKit
811
+ if (options.windowId !== undefined) {
812
+ formatOptions.capture_window_id = String(options.windowId);
813
+ }
814
+ // AVFoundation-specific
815
+ if (options.avfoundation?.captureMouseClicks) {
816
+ formatOptions.capture_mouse_clicks = '1';
817
+ }
818
+ if (options.avfoundation?.captureSystemAudio) {
819
+ formatOptions.capture_system_audio = '1';
820
+ }
821
+ if (options.avfoundation?.excludeProcessAudio) {
822
+ formatOptions.exclude_process_audio = '1';
823
+ }
824
+ if (options.avfoundation?.audioSampleRate) {
825
+ formatOptions.sck_audio_sample_rate = String(options.avfoundation.audioSampleRate);
826
+ }
827
+ if (options.avfoundation?.audioChannels) {
828
+ formatOptions.sck_audio_channels = String(options.avfoundation.audioChannels);
829
+ }
830
+ break;
831
+ }
832
+ case 'x11grab': {
833
+ const display = options.x11grab?.display ?? ':0.0';
834
+ // Use screen bounds offset if screenIndex is set, otherwise use manual x/y
835
+ const offsetX = screenBounds ? screenBounds.x : (options.x ?? 0);
836
+ const offsetY = screenBounds ? screenBounds.y : (options.y ?? 0);
837
+ const hasOffset = screenBounds !== undefined || options.x !== undefined || options.y !== undefined;
838
+ const offset = hasOffset ? `+${offsetX},${offsetY}` : '';
839
+ deviceName = `${display}${offset}`;
840
+ if (options.frameRate) {
841
+ formatOptions.framerate = String(options.frameRate);
842
+ }
843
+ // Use screen bounds for video_size if screenIndex is set and no explicit size
844
+ if (options.width && options.height) {
845
+ formatOptions.video_size = `${options.width}x${options.height}`;
846
+ }
847
+ else if (screenBounds && screenBounds.width > 0 && screenBounds.height > 0) {
848
+ formatOptions.video_size = `${screenBounds.width}x${screenBounds.height}`;
849
+ }
850
+ if (options.drawMouse !== undefined) {
851
+ formatOptions.draw_mouse = options.drawMouse ? '1' : '0';
852
+ }
853
+ // Window capture
854
+ if (options.windowId !== undefined) {
855
+ formatOptions.window_id = String(options.windowId);
856
+ }
857
+ if (options.x11grab?.showRegion) {
858
+ formatOptions.show_region = '1';
859
+ }
860
+ // X11-specific
861
+ if (options.x11grab?.followMouse !== undefined) {
862
+ formatOptions.follow_mouse = typeof options.x11grab.followMouse === 'number' ? String(options.x11grab.followMouse) : options.x11grab.followMouse;
863
+ }
864
+ break;
865
+ }
866
+ case 'gdigrab': {
867
+ if (options.windowId !== undefined) {
868
+ deviceName = `hwnd=${options.windowId}`;
869
+ }
870
+ else if (options.gdigrab?.windowTitle) {
871
+ deviceName = `title=${options.gdigrab.windowTitle}`;
872
+ }
873
+ else {
874
+ deviceName = 'desktop';
875
+ }
876
+ if (options.frameRate) {
877
+ formatOptions.framerate = String(options.frameRate);
878
+ }
879
+ // Use screen bounds for offset/size if screenIndex is set
880
+ if (screenBounds) {
881
+ formatOptions.offset_x = String(screenBounds.x);
882
+ formatOptions.offset_y = String(screenBounds.y);
883
+ if (!options.width && !options.height && screenBounds.width > 0 && screenBounds.height > 0) {
884
+ formatOptions.video_size = `${screenBounds.width}x${screenBounds.height}`;
885
+ }
886
+ }
887
+ else {
888
+ if (options.x !== undefined) {
889
+ formatOptions.offset_x = String(options.x);
890
+ }
891
+ if (options.y !== undefined) {
892
+ formatOptions.offset_y = String(options.y);
893
+ }
894
+ }
895
+ if (options.width && options.height) {
896
+ formatOptions.video_size = `${options.width}x${options.height}`;
897
+ }
898
+ if (options.drawMouse !== undefined) {
899
+ formatOptions.draw_mouse = options.drawMouse ? '1' : '0';
900
+ }
901
+ if (options.gdigrab?.showRegion) {
902
+ formatOptions.show_region = '1';
903
+ }
904
+ break;
905
+ }
906
+ default:
907
+ deviceName = 'desktop';
908
+ }
909
+ // Escape hatch
910
+ if (options.formatOptions) {
911
+ Object.assign(formatOptions, options.formatOptions);
912
+ }
913
+ return { deviceName, formatOptions };
914
+ }
915
+ /**
916
+ * Resolve screen bounds for a given screen index.
917
+ *
918
+ * @param screenIndex - 0-based screen index
919
+ *
920
+ * @returns Screen bounds or undefined if not found
921
+ *
922
+ * @internal
923
+ */
924
+ static resolveScreenBounds(screenIndex) {
925
+ if (screenIndex === undefined)
926
+ return undefined;
927
+ const devices = Device.listSync();
928
+ const screens = devices.filter((d) => d.type === 'screen');
929
+ if (screenIndex < 0 || screenIndex >= screens.length)
930
+ return undefined;
931
+ return screens[screenIndex].bounds;
932
+ }
933
+ /**
934
+ * Ensure ALSA configuration is available on Linux.
935
+ *
936
+ * Auto-detects the ALSA configuration file from standard paths and sets
937
+ * `ALSA_CONFIG_PATH` if not already defined. This ensures ALSA can find
938
+ * its configuration even if it is installed at a non-standard location.
939
+ *
940
+ * @param configPath - Optional explicit path to alsa.conf
941
+ *
942
+ * @internal
943
+ */
944
+ static ensureAlsaConfig(configPath) {
945
+ if (process.platform !== 'linux')
946
+ return;
947
+ if (process.env.ALSA_CONFIG_PATH)
948
+ return;
949
+ if (configPath) {
950
+ process.env.ALSA_CONFIG_PATH = configPath;
951
+ return;
952
+ }
953
+ for (const searchPath of ALSA_CONFIG_SEARCH_PATHS) {
954
+ if (existsSync(searchPath)) {
955
+ process.env.ALSA_CONFIG_PATH = searchPath;
956
+ return;
957
+ }
958
+ }
959
+ }
960
+ }
961
+ //# sourceMappingURL=device.js.map