@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.
- package/BUILD_LINUX.md +61 -0
- package/LICENSE.md +22 -0
- package/README.md +662 -0
- package/build_mac_local.sh +69 -0
- package/dist/api/audio-frame-buffer.d.ts +205 -0
- package/dist/api/audio-frame-buffer.js +287 -0
- package/dist/api/audio-frame-buffer.js.map +1 -0
- package/dist/api/bitstream-filter.d.ts +820 -0
- package/dist/api/bitstream-filter.js +1242 -0
- package/dist/api/bitstream-filter.js.map +1 -0
- package/dist/api/constants.d.ts +44 -0
- package/dist/api/constants.js +45 -0
- package/dist/api/constants.js.map +1 -0
- package/dist/api/data/test_av1.ivf +0 -0
- package/dist/api/data/test_h264.h264 +0 -0
- package/dist/api/data/test_hevc.h265 +0 -0
- package/dist/api/data/test_mjpeg.mjpeg +0 -0
- package/dist/api/data/test_vp8.ivf +0 -0
- package/dist/api/data/test_vp9.ivf +0 -0
- package/dist/api/decoder.d.ts +1088 -0
- package/dist/api/decoder.js +1775 -0
- package/dist/api/decoder.js.map +1 -0
- package/dist/api/demuxer.d.ts +1219 -0
- package/dist/api/demuxer.js +2081 -0
- package/dist/api/demuxer.js.map +1 -0
- package/dist/api/device.d.ts +586 -0
- package/dist/api/device.js +961 -0
- package/dist/api/device.js.map +1 -0
- package/dist/api/encoder.d.ts +1132 -0
- package/dist/api/encoder.js +1988 -0
- package/dist/api/encoder.js.map +1 -0
- package/dist/api/filter-complex.d.ts +821 -0
- package/dist/api/filter-complex.js +1604 -0
- package/dist/api/filter-complex.js.map +1 -0
- package/dist/api/filter-presets.d.ts +1286 -0
- package/dist/api/filter-presets.js +2152 -0
- package/dist/api/filter-presets.js.map +1 -0
- package/dist/api/filter.d.ts +1234 -0
- package/dist/api/filter.js +1976 -0
- package/dist/api/filter.js.map +1 -0
- package/dist/api/fmp4-stream.d.ts +426 -0
- package/dist/api/fmp4-stream.js +739 -0
- package/dist/api/fmp4-stream.js.map +1 -0
- package/dist/api/hardware.d.ts +651 -0
- package/dist/api/hardware.js +1260 -0
- package/dist/api/hardware.js.map +1 -0
- package/dist/api/index.d.ts +17 -0
- package/dist/api/index.js +32 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/io-stream.d.ts +307 -0
- package/dist/api/io-stream.js +282 -0
- package/dist/api/io-stream.js.map +1 -0
- package/dist/api/muxer.d.ts +957 -0
- package/dist/api/muxer.js +2002 -0
- package/dist/api/muxer.js.map +1 -0
- package/dist/api/pipeline.d.ts +607 -0
- package/dist/api/pipeline.js +1145 -0
- package/dist/api/pipeline.js.map +1 -0
- package/dist/api/utilities/async-queue.d.ts +120 -0
- package/dist/api/utilities/async-queue.js +211 -0
- package/dist/api/utilities/async-queue.js.map +1 -0
- package/dist/api/utilities/audio-sample.d.ts +117 -0
- package/dist/api/utilities/audio-sample.js +112 -0
- package/dist/api/utilities/audio-sample.js.map +1 -0
- package/dist/api/utilities/channel-layout.d.ts +76 -0
- package/dist/api/utilities/channel-layout.js +80 -0
- package/dist/api/utilities/channel-layout.js.map +1 -0
- package/dist/api/utilities/electron-shared-texture.d.ts +328 -0
- package/dist/api/utilities/electron-shared-texture.js +503 -0
- package/dist/api/utilities/electron-shared-texture.js.map +1 -0
- package/dist/api/utilities/image.d.ts +207 -0
- package/dist/api/utilities/image.js +213 -0
- package/dist/api/utilities/image.js.map +1 -0
- package/dist/api/utilities/index.d.ts +12 -0
- package/dist/api/utilities/index.js +25 -0
- package/dist/api/utilities/index.js.map +1 -0
- package/dist/api/utilities/media-type.d.ts +49 -0
- package/dist/api/utilities/media-type.js +53 -0
- package/dist/api/utilities/media-type.js.map +1 -0
- package/dist/api/utilities/pixel-format.d.ts +89 -0
- package/dist/api/utilities/pixel-format.js +97 -0
- package/dist/api/utilities/pixel-format.js.map +1 -0
- package/dist/api/utilities/sample-format.d.ts +129 -0
- package/dist/api/utilities/sample-format.js +141 -0
- package/dist/api/utilities/sample-format.js.map +1 -0
- package/dist/api/utilities/scheduler.d.ts +138 -0
- package/dist/api/utilities/scheduler.js +98 -0
- package/dist/api/utilities/scheduler.js.map +1 -0
- package/dist/api/utilities/streaming.d.ts +186 -0
- package/dist/api/utilities/streaming.js +309 -0
- package/dist/api/utilities/streaming.js.map +1 -0
- package/dist/api/utilities/timestamp.d.ts +193 -0
- package/dist/api/utilities/timestamp.js +206 -0
- package/dist/api/utilities/timestamp.js.map +1 -0
- package/dist/api/utilities/whisper-model.d.ts +310 -0
- package/dist/api/utilities/whisper-model.js +528 -0
- package/dist/api/utilities/whisper-model.js.map +1 -0
- package/dist/api/utils.d.ts +19 -0
- package/dist/api/utils.js +39 -0
- package/dist/api/utils.js.map +1 -0
- package/dist/api/whisper.d.ts +324 -0
- package/dist/api/whisper.js +362 -0
- package/dist/api/whisper.js.map +1 -0
- package/dist/constants/channel-layouts.d.ts +53 -0
- package/dist/constants/channel-layouts.js +57 -0
- package/dist/constants/channel-layouts.js.map +1 -0
- package/dist/constants/constants.d.ts +2325 -0
- package/dist/constants/constants.js +1887 -0
- package/dist/constants/constants.js.map +1 -0
- package/dist/constants/decoders.d.ts +633 -0
- package/dist/constants/decoders.js +641 -0
- package/dist/constants/decoders.js.map +1 -0
- package/dist/constants/encoders.d.ts +295 -0
- package/dist/constants/encoders.js +308 -0
- package/dist/constants/encoders.js.map +1 -0
- package/dist/constants/hardware.d.ts +26 -0
- package/dist/constants/hardware.js +27 -0
- package/dist/constants/hardware.js.map +1 -0
- package/dist/constants/index.d.ts +5 -0
- package/dist/constants/index.js +6 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +99 -0
- package/dist/ffmpeg/index.js +115 -0
- package/dist/ffmpeg/index.js.map +1 -0
- package/dist/ffmpeg/utils.d.ts +31 -0
- package/dist/ffmpeg/utils.js +68 -0
- package/dist/ffmpeg/utils.js.map +1 -0
- package/dist/ffmpeg/version.d.ts +6 -0
- package/dist/ffmpeg/version.js +7 -0
- package/dist/ffmpeg/version.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audio-fifo.d.ts +399 -0
- package/dist/lib/audio-fifo.js +431 -0
- package/dist/lib/audio-fifo.js.map +1 -0
- package/dist/lib/binding.d.ts +228 -0
- package/dist/lib/binding.js +60 -0
- package/dist/lib/binding.js.map +1 -0
- package/dist/lib/bitstream-filter-context.d.ts +379 -0
- package/dist/lib/bitstream-filter-context.js +441 -0
- package/dist/lib/bitstream-filter-context.js.map +1 -0
- package/dist/lib/bitstream-filter.d.ts +140 -0
- package/dist/lib/bitstream-filter.js +154 -0
- package/dist/lib/bitstream-filter.js.map +1 -0
- package/dist/lib/codec-context.d.ts +1071 -0
- package/dist/lib/codec-context.js +1354 -0
- package/dist/lib/codec-context.js.map +1 -0
- package/dist/lib/codec-parameters.d.ts +616 -0
- package/dist/lib/codec-parameters.js +761 -0
- package/dist/lib/codec-parameters.js.map +1 -0
- package/dist/lib/codec-parser.d.ts +201 -0
- package/dist/lib/codec-parser.js +213 -0
- package/dist/lib/codec-parser.js.map +1 -0
- package/dist/lib/codec.d.ts +586 -0
- package/dist/lib/codec.js +713 -0
- package/dist/lib/codec.js.map +1 -0
- package/dist/lib/device.d.ts +291 -0
- package/dist/lib/device.js +324 -0
- package/dist/lib/device.js.map +1 -0
- package/dist/lib/dictionary.d.ts +333 -0
- package/dist/lib/dictionary.js +372 -0
- package/dist/lib/dictionary.js.map +1 -0
- package/dist/lib/error.d.ts +242 -0
- package/dist/lib/error.js +303 -0
- package/dist/lib/error.js.map +1 -0
- package/dist/lib/fifo.d.ts +416 -0
- package/dist/lib/fifo.js +453 -0
- package/dist/lib/fifo.js.map +1 -0
- package/dist/lib/filter-context.d.ts +712 -0
- package/dist/lib/filter-context.js +789 -0
- package/dist/lib/filter-context.js.map +1 -0
- package/dist/lib/filter-graph-segment.d.ts +160 -0
- package/dist/lib/filter-graph-segment.js +171 -0
- package/dist/lib/filter-graph-segment.js.map +1 -0
- package/dist/lib/filter-graph.d.ts +641 -0
- package/dist/lib/filter-graph.js +704 -0
- package/dist/lib/filter-graph.js.map +1 -0
- package/dist/lib/filter-inout.d.ts +198 -0
- package/dist/lib/filter-inout.js +257 -0
- package/dist/lib/filter-inout.js.map +1 -0
- package/dist/lib/filter.d.ts +243 -0
- package/dist/lib/filter.js +272 -0
- package/dist/lib/filter.js.map +1 -0
- package/dist/lib/format-context.d.ts +1254 -0
- package/dist/lib/format-context.js +1379 -0
- package/dist/lib/format-context.js.map +1 -0
- package/dist/lib/frame-utils.d.ts +116 -0
- package/dist/lib/frame-utils.js +98 -0
- package/dist/lib/frame-utils.js.map +1 -0
- package/dist/lib/frame.d.ts +1222 -0
- package/dist/lib/frame.js +1435 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/hardware-device-context.d.ts +362 -0
- package/dist/lib/hardware-device-context.js +383 -0
- package/dist/lib/hardware-device-context.js.map +1 -0
- package/dist/lib/hardware-frames-context.d.ts +419 -0
- package/dist/lib/hardware-frames-context.js +477 -0
- package/dist/lib/hardware-frames-context.js.map +1 -0
- package/dist/lib/index.d.ts +35 -0
- package/dist/lib/index.js +60 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/input-format.d.ts +249 -0
- package/dist/lib/input-format.js +306 -0
- package/dist/lib/input-format.js.map +1 -0
- package/dist/lib/io-context.d.ts +696 -0
- package/dist/lib/io-context.js +769 -0
- package/dist/lib/io-context.js.map +1 -0
- package/dist/lib/log.d.ts +174 -0
- package/dist/lib/log.js +184 -0
- package/dist/lib/log.js.map +1 -0
- package/dist/lib/native-types.d.ts +946 -0
- package/dist/lib/native-types.js +2 -0
- package/dist/lib/native-types.js.map +1 -0
- package/dist/lib/option.d.ts +927 -0
- package/dist/lib/option.js +1583 -0
- package/dist/lib/option.js.map +1 -0
- package/dist/lib/output-format.d.ts +180 -0
- package/dist/lib/output-format.js +213 -0
- package/dist/lib/output-format.js.map +1 -0
- package/dist/lib/packet.d.ts +501 -0
- package/dist/lib/packet.js +590 -0
- package/dist/lib/packet.js.map +1 -0
- package/dist/lib/rational.d.ts +251 -0
- package/dist/lib/rational.js +278 -0
- package/dist/lib/rational.js.map +1 -0
- package/dist/lib/software-resample-context.d.ts +552 -0
- package/dist/lib/software-resample-context.js +592 -0
- package/dist/lib/software-resample-context.js.map +1 -0
- package/dist/lib/software-scale-context.d.ts +344 -0
- package/dist/lib/software-scale-context.js +366 -0
- package/dist/lib/software-scale-context.js.map +1 -0
- package/dist/lib/stream.d.ts +379 -0
- package/dist/lib/stream.js +526 -0
- package/dist/lib/stream.js.map +1 -0
- package/dist/lib/sync-queue.d.ts +179 -0
- package/dist/lib/sync-queue.js +197 -0
- package/dist/lib/sync-queue.js.map +1 -0
- package/dist/lib/types.d.ts +34 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utilities.d.ts +1127 -0
- package/dist/lib/utilities.js +1225 -0
- package/dist/lib/utilities.js.map +1 -0
- package/dist/utils/electron.d.ts +49 -0
- package/dist/utils/electron.js +63 -0
- package/dist/utils/electron.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/install/check.js +121 -0
- package/install/ffmpeg.js +66 -0
- package/jellyfin-ffmpeg.patch +181 -0
- 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
|