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