@nonstrict/recordkit 0.87.2 → 0.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/bin/README.md +1 -1
  2. package/bin/recordkit-rpc +0 -0
  3. package/out/Errors.d.ts +91 -0
  4. package/out/Errors.js +63 -0
  5. package/out/Errors.js.map +1 -0
  6. package/out/InputEvents.d.ts +661 -0
  7. package/out/InputEvents.js +8 -0
  8. package/out/InputEvents.js.map +1 -0
  9. package/out/IpcRecordKit.js +13 -0
  10. package/out/IpcRecordKit.js.map +1 -1
  11. package/out/NonstrictRPC.d.ts +9 -0
  12. package/out/NonstrictRPC.js +27 -4
  13. package/out/NonstrictRPC.js.map +1 -1
  14. package/out/RecordKit.d.ts +267 -5
  15. package/out/RecordKit.js +243 -3
  16. package/out/RecordKit.js.map +1 -1
  17. package/out/Recorder.d.ts +451 -53
  18. package/out/Recorder.js +80 -2
  19. package/out/Recorder.js.map +1 -1
  20. package/out/RecordingMetadata.d.ts +96 -0
  21. package/out/RecordingMetadata.js +12 -0
  22. package/out/RecordingMetadata.js.map +1 -0
  23. package/out/WebAudioUtils.d.ts +35 -0
  24. package/out/WebAudioUtils.js +37 -0
  25. package/out/WebAudioUtils.js.map +1 -1
  26. package/out/WindowLevels.d.ts +46 -0
  27. package/out/WindowLevels.js +41 -0
  28. package/out/WindowLevels.js.map +1 -0
  29. package/out/browser.d.ts +8 -1
  30. package/out/browser.js +3 -1
  31. package/out/browser.js.map +1 -1
  32. package/out/index.cjs +603 -9
  33. package/out/index.cjs.map +1 -1
  34. package/out/index.d.ts +8 -0
  35. package/out/index.js +3 -0
  36. package/out/index.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/Errors.test.ts +38 -0
  39. package/src/Errors.ts +151 -0
  40. package/src/InputEvents.ts +695 -0
  41. package/src/IpcRecordKit.ts +12 -0
  42. package/src/NonstrictRPC.test.ts +24 -1
  43. package/src/NonstrictRPC.ts +29 -5
  44. package/src/RecordKit.ts +347 -11
  45. package/src/Recorder.schema.test.ts +167 -0
  46. package/src/Recorder.ts +558 -80
  47. package/src/RecordingMetadata.ts +125 -0
  48. package/src/WebAudioUtils.test.ts +78 -0
  49. package/src/WebAudioUtils.ts +57 -1
  50. package/src/WindowLevels.test.ts +34 -0
  51. package/src/WindowLevels.ts +47 -0
  52. package/src/browser.ts +12 -2
  53. package/src/index.ts +8 -0
  54. package/src/__snapshots__/NonstrictRPC.test.ts.snap +0 -24
package/src/Recorder.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import { NSRPC } from "./NonstrictRPC.js";
3
3
  import { EventEmitter } from "events";
4
- import { AppleDevice, Camera, Display, Microphone, RunningApplication, Window } from "./RecordKit.js";
4
+ import { AppleDevice, Bounds, Camera, Display, Microphone, RunningApplication, Window } from "./RecordKit.js";
5
+ import type { RecordKitErrorCode } from "./Errors.js";
5
6
 
6
7
  /**
7
8
  * Converts RPC audio buffer data to AudioStreamBuffer format
@@ -51,6 +52,22 @@ function convertRPCParamsToAudioStreamBuffer(params: any): AudioStreamBuffer | n
51
52
  }
52
53
  }
53
54
 
55
+ /**
56
+ * Registers the per-segment callback of a {@link JSONOutputOptions} (if any) as an RPC closure,
57
+ * replacing the function with the closure target so the options object can be serialized.
58
+ * @internal
59
+ */
60
+ function registerJSONOutputSegmentCallback(output: JSONOutputOptions | undefined, rpc: NSRPC, object: Recorder, prefix: string) {
61
+ if (output && output.output == 'segmented' && output.segmentCallback) {
62
+ const segmentHandler = output.segmentCallback;
63
+ (output as any).segmentCallback = rpc.registerClosure({
64
+ handler: (params) => { segmentHandler(params.path as string) },
65
+ prefix,
66
+ lifecycle: object
67
+ });
68
+ }
69
+ }
70
+
54
71
  /**
55
72
  * @group Recording
56
73
  */
@@ -62,9 +79,7 @@ export class Recorder extends EventEmitter {
62
79
  static async newInstance(rpc: NSRPC, schema: {
63
80
  output_directory?: string
64
81
  items: RecorderSchemaItem[]
65
- settings?: {
66
- allowFrameReordering?: boolean
67
- }
82
+ settings?: RecorderSettings
68
83
  }): Promise<Recorder> {
69
84
  const target = 'Recorder_' + randomUUID();
70
85
  const object = new Recorder(rpc, target);
@@ -98,6 +113,7 @@ export class Recorder extends EventEmitter {
98
113
  lifecycle: object
99
114
  });
100
115
  }
116
+ registerJSONOutputSegmentCallback(item.inputEventsOutput, rpc, object, 'Display.onInputEventsSegment')
101
117
  }
102
118
  if (item.type == 'windowBasedCrop') {
103
119
  if (typeof item.window != 'number') {
@@ -111,6 +127,21 @@ export class Recorder extends EventEmitter {
111
127
  lifecycle: object
112
128
  });
113
129
  }
130
+ registerJSONOutputSegmentCallback(item.inputEventsOutput, rpc, object, 'WindowBasedCrop.onInputEventsSegment')
131
+ }
132
+ if (item.type == 'desktopIndependentWindow') {
133
+ if (typeof item.window != 'number') {
134
+ item.window = item.window.id
135
+ }
136
+ if (item.output == 'segmented' && item.segmentCallback) {
137
+ const segmentHandler = item.segmentCallback;
138
+ (item as any).segmentCallback = rpc.registerClosure({
139
+ handler: (params) => { segmentHandler(params.path as string) },
140
+ prefix: 'DesktopIndependentWindow.onSegment',
141
+ lifecycle: object
142
+ });
143
+ }
144
+ registerJSONOutputSegmentCallback(item.inputEventsOutput, rpc, object, 'DesktopIndependentWindow.onInputEventsSegment')
114
145
  }
115
146
  if (item.type == 'appleDeviceStaticOrientation') {
116
147
  if (typeof item.device != 'string') {
@@ -202,11 +233,16 @@ export class Recorder extends EventEmitter {
202
233
  prefix: 'Recorder.onAbort',
203
234
  lifecycle: object
204
235
  });
236
+ const onSignalsChangedInstance = rpc.registerClosure({
237
+ handler: (params) => { weakRefObject.deref()?.emit('signals', (params as { signals: Signal[] }).signals) },
238
+ prefix: 'Recorder.onSignalsChanged',
239
+ lifecycle: object
240
+ });
205
241
 
206
242
  await rpc.initialize({
207
243
  target,
208
244
  type: 'Recorder',
209
- params: { schema, onAbortInstance },
245
+ params: { schema, onAbortInstance, onSignalsChangedInstance },
210
246
  lifecycle: object
211
247
  });
212
248
 
@@ -220,23 +256,164 @@ export class Recorder extends EventEmitter {
220
256
  this.target = target;
221
257
  }
222
258
 
223
- async prepare() {
224
- await this.rpc.perform({ target: this.target, action: 'prepare' });
259
+ /**
260
+ * Prepares the recording session for instant recording, allocating resources and validating the
261
+ * configuration.
262
+ *
263
+ * Preparing ahead of time lets {@link start} begin recording instantly; without it, starting incurs
264
+ * a setup delay.
265
+ *
266
+ * @returns The expected {@link BundleInfo} describing the file assets that will be produced by this
267
+ * recording, allowing you to inspect the planned output (filenames, asset types, sizes) before
268
+ * recording starts.
269
+ */
270
+ async prepare(): Promise<BundleInfo> {
271
+ return await this.rpc.perform({ target: this.target, action: 'prepare' }) as BundleInfo;
225
272
  }
226
273
 
274
+ /**
275
+ * Starts recording. If the session was not already {@link prepare}d this performs setup first,
276
+ * incurring a short delay; call {@link prepare} ahead of time to start instantly.
277
+ */
227
278
  async start() {
228
279
  await this.rpc.perform({ target: this.target, action: 'start' });
229
280
  }
230
281
 
282
+ /**
283
+ * Pauses the recording. The capture hardware remains active so recording can be resumed quickly.
284
+ *
285
+ * Call {@link resume} to continue recording, or {@link stop} to finish.
286
+ */
287
+ async pause() {
288
+ await this.rpc.perform({ target: this.target, action: 'pause' });
289
+ }
290
+
291
+ /**
292
+ * Resumes a recording that was previously paused with {@link pause}.
293
+ */
294
+ async resume() {
295
+ await this.rpc.perform({ target: this.target, action: 'resume' });
296
+ }
297
+
298
+ /**
299
+ * Stops the recording, finalizes the output files, and returns the {@link RecordingResult}
300
+ * describing the completed bundle. The recorder cannot be reused after stopping.
301
+ *
302
+ * @remarks Known limitation: when the recording failed, the returned promise rejects with the
303
+ * failure but the partial recording result is not available over the RPC bridge (the Swift API
304
+ * surfaces it as `PartialResultError`). Any partially-written files do remain on disk in the
305
+ * bundle inside the schema's `output_directory`.
306
+ */
231
307
  async stop(): Promise<RecordingResult> {
232
308
  return await this.rpc.perform({ target: this.target, action: 'stop' }) as RecordingResult;
233
309
  }
234
310
 
311
+ /**
312
+ * Cancels the recording and releases its resources without finalizing output. Use this to discard
313
+ * an in-progress or prepared recording; call {@link stop} instead to keep the result.
314
+ */
235
315
  async cancel() {
236
316
  await this.rpc.manualRelease(this.target)
237
317
  }
238
318
  }
239
319
 
320
+ /**
321
+ * Typed event overloads for {@link Recorder}. Declaration-merges with the class so that
322
+ * `recorder.on('abort', reason => …)` receives a typed {@link AbortReason} instead of `any`.
323
+ *
324
+ * @group Recording
325
+ */
326
+ export interface Recorder {
327
+ /** Fires when the recording is aborted by an error or a system interruption. */
328
+ on(event: 'abort', listener: (reason: AbortReason) => void): this;
329
+ /** @see {@link Recorder.on} */
330
+ once(event: 'abort', listener: (reason: AbortReason) => void): this;
331
+ /** @see {@link Recorder.on} */
332
+ off(event: 'abort', listener: (reason: AbortReason) => void): this;
333
+ /** @see {@link Recorder.on} */
334
+ emit(event: 'abort', reason: AbortReason): boolean;
335
+
336
+ /**
337
+ * Fires whenever the set of active {@link Signal}s changes, with all signals active at that
338
+ * moment. An empty array means everything is healthy again. Events are asynchronous, so one can
339
+ * still arrive just after {@link Recorder.stop} resolved; the last one is always an empty array.
340
+ */
341
+ on(event: 'signals', listener: (signals: Signal[]) => void): this;
342
+ /** @see {@link Recorder.on} */
343
+ once(event: 'signals', listener: (signals: Signal[]) => void): this;
344
+ /** @see {@link Recorder.on} */
345
+ off(event: 'signals', listener: (signals: Signal[]) => void): this;
346
+ /** @see {@link Recorder.on} */
347
+ emit(event: 'signals', signals: Signal[]): boolean;
348
+ }
349
+
350
+ /**
351
+ * Settings that apply to the whole recording session.
352
+ *
353
+ * @group Recording
354
+ */
355
+ export interface RecorderSettings {
356
+ /**
357
+ * Specifies if RecordKit is allowed to do frame reordering in video files. Defaults to `true`.
358
+ *
359
+ * When enabled, to achieve the best compression some video encoders can reorder frames and
360
+ * generate B-frames.
361
+ */
362
+ allowFrameReordering?: boolean
363
+ /**
364
+ * Whether a successful recording updates the user's preferred devices for the sources it used.
365
+ * Defaults to `true`.
366
+ *
367
+ * When enabled, starting a recording sets the devices in the schema as the user's preferred
368
+ * devices (for the device types that support this).
369
+ */
370
+ updatesUserPreferred?: boolean
371
+ /** Target duration, in whole seconds, of each audio segment when using segmented output. Defaults to `6`. Fractional values are not supported. */
372
+ audioSegmentDuration?: number
373
+ /** Target duration, in whole seconds, of each video segment when using segmented output. Defaults to `2`. Fractional values are not supported. */
374
+ videoSegmentDuration?: number
375
+ /**
376
+ * Maximum interval, in seconds, between keyframes in the video stream. Defaults to no forced
377
+ * interval.
378
+ *
379
+ * When set, the encoder places a keyframe at least every this many seconds. The frame-count based
380
+ * maximum keyframe interval is computed automatically from the video frame rate. When omitted, the
381
+ * encoder uses its default keyframe-placement heuristic.
382
+ */
383
+ keyframeIntervalDuration?: number
384
+ /**
385
+ * Free disk space, in bytes, below which a running recording is aborted.
386
+ * Defaults to `104857600` (100 MB).
387
+ *
388
+ * While recording, RecordKit periodically checks the actual available capacity of the volume the
389
+ * recording is written to (purgeable/opportunistic space is not counted). Shortly after the
390
+ * available capacity drops below this level the recording is aborted with an
391
+ * `insufficientDiskSpace` error. Keep it high enough to absorb whatever is still written between
392
+ * two checks and to leave room to finalize the recording.
393
+ * Set to `0` to never abort on disk space (record until the disk is full), which may result in a
394
+ * corrupt recording. A `diskSpaceWarningLevel` keeps being reported either way.
395
+ *
396
+ * @see {@link RecorderSettings.diskSpaceWarningLevel}, the higher level that warns instead of
397
+ * aborting.
398
+ */
399
+ diskSpaceAbortLevel?: number
400
+ /**
401
+ * Free disk space, in bytes, below which the recording volume counts as running low.
402
+ * Defaults to `157286400` (150 MB).
403
+ *
404
+ * Checked once during `prepare()`, which fails with an `insufficientDiskSpace` error when the
405
+ * volume is already below it, and then periodically while recording. Dropping below it during a
406
+ * recording does not interrupt anything, it raises a `lowDiskSpace` {@link Signal} on the
407
+ * `signals` event so your app can warn the user; the signal clears again once enough space is
408
+ * freed up.
409
+ *
410
+ * Set this higher than `diskSpaceAbortLevel` so there is room to warn before the recording is
411
+ * aborted at that lower level. Set to `0` to disable both the prepare-time check and the
412
+ * signal.
413
+ */
414
+ diskSpaceWarningLevel?: number
415
+ }
416
+
240
417
  /**
241
418
  * @group Recording
242
419
  */
@@ -244,6 +421,7 @@ export type RecorderSchemaItem =
244
421
  | WebcamSchema
245
422
  | DisplaySchema
246
423
  | WindowBasedCropSchema
424
+ | DesktopIndependentWindowSchema
247
425
  | AppleDeviceStaticOrientationSchema
248
426
  | AppleDeviceSchema
249
427
  | SystemAudioSchema
@@ -251,101 +429,302 @@ export type RecorderSchemaItem =
251
429
  | MicrophoneSchema
252
430
 
253
431
  /**
254
- * Creates a recorder item for a webcam movie file, using the provided microphone and camera. Output is stored in a RecordKit bundle.
432
+ * A width/height pair. The unit (pixels or points) depends on the consuming API — see the
433
+ * documentation of the specific method or option that takes this value.
255
434
  *
256
435
  * @group Recording Schemas
257
436
  */
258
- export type WebcamSchema = {
259
- type: 'webcam'
260
- camera: Camera | string
261
- microphone: Microphone | string
262
- /** Video codec for the recording. Defaults to 'h264'. */
263
- videoCodec?: VideoCodec
264
- preserveActiveCameraConfiguration?: boolean
265
- leftAudioChannelOnly?: boolean
266
- audioDelay?: number
267
- output?: 'singleFile'
268
- filename?: string
269
- } | {
437
+ export interface Size {
438
+ width: number
439
+ height: number
440
+ }
441
+
442
+ /**
443
+ * Content that can be excluded from a screen recording.
444
+ *
445
+ * - `currentProcess`: Exclude the windows of the process hosting the recorder from the recording.
446
+ * - `screenRecordingIndicator`: Exclude the orange screen-recording indicator from the recording.
447
+ *
448
+ * @remarks From Electron the process hosting the recorder is the bundled `recordkit-rpc` helper,
449
+ * not your app — so `currentProcess` does not exclude your app's own windows. To exclude those,
450
+ * pass your process IDs (e.g. `process.pid`) via the schema item's `excludedProcessIDs`.
451
+ *
452
+ * @group Recording Schemas
453
+ */
454
+ export type ScreenRecordingExcludeOption = 'currentProcess' | 'screenRecordingIndicator'
455
+
456
+ /**
457
+ * Output configuration for JSON sidecar files such as the mouse/keyboard input-event log.
458
+ *
459
+ * - `singleFile`: Write all events to a single JSON file (optionally named via `filename`).
460
+ * - `segmented`: Write events to multiple segmented JSON files; `segmentCallback` is invoked with the
461
+ * path of each segment as it is written to disk.
462
+ *
463
+ * @group Recording Schemas
464
+ */
465
+ export type JSONOutputOptions =
466
+ | { output?: 'singleFile', filename?: string }
467
+ | { output: 'segmented', filenamePrefix?: string, segmentCallback?: (url: string) => void }
468
+
469
+ interface WebcamSchemaBase {
270
470
  type: 'webcam'
471
+ /** The camera to record, either a {@link Camera} or its `id`. */
271
472
  camera: Camera | string
473
+ /** The microphone to record alongside the camera, either a {@link Microphone} or its `id`. */
272
474
  microphone: Microphone | string
475
+ /** Caps the output video to at most these dimensions (in pixels), preserving aspect ratio. */
476
+ maxVideoDimensions?: Size
273
477
  /** Video codec for the recording. Defaults to 'h264'. */
274
478
  videoCodec?: VideoCodec
479
+ /**
480
+ * When `true`, leaves the camera's active format/configuration untouched instead of letting
481
+ * RecordKit reconfigure the device for the requested recording. Defaults to `false`.
482
+ */
275
483
  preserveActiveCameraConfiguration?: boolean
484
+ /** Record only the left channel of the microphone (useful for mono lavalier mics). Defaults to `false`. */
276
485
  leftAudioChannelOnly?: boolean
486
+ /** Delay applied to the microphone audio relative to the video, in seconds, to correct lip-sync. Defaults to `0`. */
277
487
  audioDelay?: number
278
- output: 'segmented'
279
- filenamePrefix?: string
280
- segmentCallback?: (url: string) => void
488
+ /** Echo cancellation applied to the microphone audio. Defaults to `'off'`. */
489
+ echoCancellation?: EchoCancellation
490
+ /** Background blur applied to the camera video. Defaults to `'off'`. */
491
+ backgroundBlur?: BackgroundBlur
281
492
  }
282
493
 
283
494
  /**
284
- * Creates a recorder item for recording a single display. Output is stored in a RecordKit bundle.
495
+ * Creates a recorder item for a webcam movie file, using the provided microphone and camera. Output is stored in a RecordKit bundle.
285
496
  *
286
497
  * @group Recording Schemas
287
498
  */
288
- export type DisplaySchema = {
289
- type: 'display'
290
- display: Display | number // UInt32
291
- /** Color space for the recording. Defaults to 'sRGB'. Note: 'displayP3' requires 'hevc' video codec. */
292
- colorSpace?: ColorSpace
293
- /** Video codec for the recording. Defaults to 'h264'. */
294
- videoCodec?: VideoCodec
295
- shows_cursor?: boolean
296
- mouse_events?: boolean
297
- keyboard_events?: boolean
298
- include_audio?: boolean
299
- output?: 'singleFile'
300
- filename?: string
301
- } | {
499
+ export type WebcamSchema =
500
+ | (WebcamSchemaBase & {
501
+ output?: 'singleFile'
502
+ filename?: string
503
+ })
504
+ | (WebcamSchemaBase & {
505
+ output: 'segmented'
506
+ filenamePrefix?: string
507
+ segmentCallback?: (url: string) => void
508
+ })
509
+
510
+ /**
511
+ * Acoustic echo cancellation applied to microphone audio. Captures system/playback audio via Core
512
+ * Audio process taps and removes it from the microphone signal in real time using WebRTC AEC3.
513
+ *
514
+ * - `'off'`: No echo cancellation (default).
515
+ * - `'aggressive'`: Maximum echo removal at the cost of speech quality — aggressive suppression,
516
+ * high-pass filter, and residual echo gate at 16 kHz. Optimized for speech-to-text pipelines where
517
+ * echo removal matters more than audio fidelity.
518
+ * - `'balanced'`: Balanced echo removal that preserves speech quality — balanced suppression with
519
+ * high-pass filter but no residual echo gate at 48 kHz. Optimized for recording pipelines where
520
+ * audio fidelity is more important than pure echo removal.
521
+ * - An object for full control over the AEC3 configuration; omitted fields default to the `'balanced'`
522
+ * preset.
523
+ *
524
+ * @remarks Requires macOS 14.2 or later (Core Audio process taps). On older versions, preparing the
525
+ * recorder fails with a `configurationNotSupported` error.
526
+ *
527
+ * @group Recording Schemas
528
+ */
529
+ export type EchoCancellation =
530
+ | 'off'
531
+ | 'aggressive'
532
+ | 'balanced'
533
+ | {
534
+ /**
535
+ * Sample rate the echo canceller runs at, in Hz. Defaults to `48000`. 16 kHz is sufficient for
536
+ * speech-to-text; 48 kHz preserves full audio fidelity.
537
+ */
538
+ sampleRate?: 16000 | 32000 | 48000
539
+ /**
540
+ * Echo suppression mode. Defaults to `'balanced'`.
541
+ *
542
+ * - `'balanced'`: Conservative suppression that allows AEC3's transparent mode (passes audio
543
+ * through unchanged when no echo is detected) and protects near-end speech during double-talk.
544
+ * Best where speech naturalness matters.
545
+ * - `'aggressive'`: Disables near-end speech detection so echo is suppressed even during
546
+ * double-talk; removes ~2 dB more echo but attenuates speech by 3-5 dB.
547
+ */
548
+ suppressionMode?: 'balanced' | 'aggressive'
549
+ /**
550
+ * Apply a high-pass filter on the capture signal to remove DC offset and low-frequency noise
551
+ * that can interfere with the adaptive filter. Defaults to `true`.
552
+ */
553
+ highPassFilter?: boolean
554
+ /**
555
+ * Apply a post-AEC3 gate that attenuates output when the speaker is active but output is quiet
556
+ * (likely residual echo, not speech). Catches echo the suppressor misses, at the cost of
557
+ * occasional clipping of quiet speech. Defaults to `false`.
558
+ */
559
+ residualEchoGate?: boolean
560
+ }
561
+
562
+ /**
563
+ * Background blur applied to camera video. The person is segmented from each frame using Apple's
564
+ * Vision person segmentation and composited over a Gaussian-blurred copy of the background, keeping
565
+ * the foreground subject sharp while blurring the background.
566
+ *
567
+ * - `'off'`: No background blur (default).
568
+ * - `'balanced'`: Default-quality blur with a moderate background blur radius and soft mask edges.
569
+ * - `'fast'`: Faster, lower-quality blur for lower-end hardware.
570
+ * - An object for full control over the Vision-based configuration; omitted fields default to the
571
+ * `'balanced'` preset.
572
+ *
573
+ * @group Recording Schemas
574
+ */
575
+ export type BackgroundBlur =
576
+ | 'off'
577
+ | 'balanced'
578
+ | 'fast'
579
+ | {
580
+ /**
581
+ * Trade-off between segmentation speed and accuracy. Defaults to `'balanced'`.
582
+ *
583
+ * - `'accurate'`: Best segmentation quality, highest cost.
584
+ * - `'balanced'`: Default trade-off between quality and cost.
585
+ * - `'fast'`: Lowest cost, suitable for real-time on lower-end hardware.
586
+ */
587
+ quality?: 'accurate' | 'balanced' | 'fast'
588
+ /**
589
+ * Sigma for the Gaussian blur applied to the background. Larger values produce a stronger blur;
590
+ * `0` disables the blur (the background is the original image). Defaults to `10`.
591
+ */
592
+ blurRadius?: number
593
+ /**
594
+ * Sigma for the Gaussian blur applied to the segmentation mask to soften foreground/background
595
+ * transitions; `0` keeps the raw mask edges. Defaults to `3`.
596
+ */
597
+ featherRadius?: number
598
+ }
599
+
600
+ interface DisplaySchemaBase {
302
601
  type: 'display'
602
+ /** The display to record, either a {@link Display} or its numeric id. */
303
603
  display: Display | number // UInt32
604
+ /** Crop rectangle (in points, top-left origin) within the display. Defaults to the full display. */
605
+ crop?: Bounds
606
+ /**
607
+ * Content to exclude from the recording. Defaults to `['currentProcess', 'screenRecordingIndicator']`.
608
+ * Pass an explicit (possibly empty) array to override the default.
609
+ */
610
+ excludeOptions?: ScreenRecordingExcludeOption[]
611
+ /** Process IDs of applications to exclude from the recording. */
612
+ excludedProcessIDs?: number[] // Int32
304
613
  /** Color space for the recording. Defaults to 'sRGB'. Note: 'displayP3' requires 'hevc' video codec. */
305
614
  colorSpace?: ColorSpace
615
+ /** Minimum interval between frames, in seconds. Caps the frame rate (e.g. `1/30` for 30 fps). Defaults to uncapped. */
616
+ minimumFrameInterval?: number
617
+ /** Caps the output video to at most these dimensions (in pixels), preserving aspect ratio. */
618
+ maxVideoDimensions?: Size
306
619
  /** Video codec for the recording. Defaults to 'h264'. */
307
620
  videoCodec?: VideoCodec
621
+ /** Whether to draw the mouse cursor into the recording. Defaults to `true`. */
308
622
  shows_cursor?: boolean
623
+ /** Whether to capture mouse input events into a JSON sidecar file. Defaults to `false`. */
309
624
  mouse_events?: boolean
625
+ /** Whether to capture keyboard input events into a JSON sidecar file. Defaults to `false`. */
310
626
  keyboard_events?: boolean
627
+ /** Output configuration for the mouse/keyboard input-event JSON sidecar files. */
628
+ inputEventsOutput?: JSONOutputOptions
629
+ /** Whether to also record the display's audio. Defaults to `false`. */
311
630
  include_audio?: boolean
312
- output: 'segmented'
313
- filenamePrefix?: string
314
- segmentCallback?: (url: string) => void
315
631
  }
316
632
 
317
633
  /**
318
- * Creates a recorder item for recording the initial crop of a window on a display. Output is stored in a RecordKit bundle.
634
+ * Creates a recorder item for recording a single display. Output is stored in a RecordKit bundle.
319
635
  *
320
636
  * @group Recording Schemas
321
637
  */
322
- export type WindowBasedCropSchema = {
638
+ export type DisplaySchema =
639
+ | (DisplaySchemaBase & {
640
+ output?: 'singleFile'
641
+ filename?: string
642
+ })
643
+ | (DisplaySchemaBase & {
644
+ output: 'segmented'
645
+ filenamePrefix?: string
646
+ segmentCallback?: (url: string) => void
647
+ })
648
+
649
+ interface WindowBasedCropSchemaBase {
323
650
  type: 'windowBasedCrop'
651
+ /** The window to record, either a {@link Window} or its numeric id. */
324
652
  window: Window | number // UInt32
325
653
  /** Color space for the recording. Defaults to 'sRGB'. Note: 'displayP3' requires 'hevc' video codec. */
326
654
  colorSpace?: ColorSpace
655
+ /** Minimum interval between frames, in seconds. Caps the frame rate (e.g. `1/30` for 30 fps). Defaults to uncapped. */
656
+ minimumFrameInterval?: number
657
+ /** Caps the output video to at most these dimensions (in pixels), preserving aspect ratio. */
658
+ maxVideoDimensions?: Size
327
659
  /** Video codec for the recording. Defaults to 'h264'. */
328
660
  videoCodec?: VideoCodec
661
+ /** Whether to draw the mouse cursor into the recording. Defaults to `true`. */
329
662
  shows_cursor?: boolean
663
+ /** Whether to capture mouse input events into a JSON sidecar file. Defaults to `false`. */
330
664
  mouse_events?: boolean
665
+ /** Whether to capture keyboard input events into a JSON sidecar file. Defaults to `false`. */
331
666
  keyboard_events?: boolean
332
- output?: 'singleFile'
333
- filename?: string
334
- } | {
335
- type: 'windowBasedCrop'
667
+ /** Output configuration for the mouse/keyboard input-event JSON sidecar files. */
668
+ inputEventsOutput?: JSONOutputOptions
669
+ /** Whether to also record the audio of the window's application. Defaults to `false`. */
670
+ include_audio?: boolean
671
+ }
672
+
673
+ /**
674
+ * Creates a recorder item for recording the initial crop of a window on a display. Output is stored in a RecordKit bundle.
675
+ *
676
+ * @group Recording Schemas
677
+ */
678
+ export type WindowBasedCropSchema =
679
+ | (WindowBasedCropSchemaBase & {
680
+ output?: 'singleFile'
681
+ filename?: string
682
+ })
683
+ | (WindowBasedCropSchemaBase & {
684
+ output: 'segmented'
685
+ filenamePrefix?: string
686
+ segmentCallback?: (url: string) => void
687
+ })
688
+
689
+ interface DesktopIndependentWindowSchemaBase {
690
+ type: 'desktopIndependentWindow'
336
691
  window: Window | number // UInt32
337
692
  /** Color space for the recording. Defaults to 'sRGB'. Note: 'displayP3' requires 'hevc' video codec. */
338
693
  colorSpace?: ColorSpace
694
+ /** Minimum interval between frames, in seconds. Caps the frame rate (e.g. `1/30` for 30 fps). Defaults to uncapped. */
695
+ minimumFrameInterval?: number
696
+ /** Caps the output video to at most these dimensions (in pixels), preserving aspect ratio. */
697
+ maxVideoDimensions?: Size
339
698
  /** Video codec for the recording. Defaults to 'h264'. */
340
699
  videoCodec?: VideoCodec
341
700
  shows_cursor?: boolean
342
701
  mouse_events?: boolean
343
702
  keyboard_events?: boolean
344
- output: 'segmented'
345
- filenamePrefix?: string
346
- segmentCallback?: (url: string) => void
703
+ /** Output configuration for the mouse/keyboard input-event JSON sidecar files. */
704
+ inputEventsOutput?: JSONOutputOptions
705
+ /** Whether to also record the audio of the window's application. Defaults to `false`. */
706
+ include_audio?: boolean
347
707
  }
348
708
 
709
+ /**
710
+ * Creates a recorder item that records a single window, following it across the desktop independently of
711
+ * what is drawn on screen (the window can be moved or partially off-screen and is still captured in full).
712
+ * Output is stored in a RecordKit bundle.
713
+ *
714
+ * @remarks Requires macOS 13.1 or later.
715
+ * @group Recording Schemas
716
+ */
717
+ export type DesktopIndependentWindowSchema =
718
+ | (DesktopIndependentWindowSchemaBase & {
719
+ output?: 'singleFile'
720
+ filename?: string
721
+ })
722
+ | (DesktopIndependentWindowSchemaBase & {
723
+ output: 'segmented'
724
+ filenamePrefix?: string
725
+ segmentCallback?: (url: string) => void
726
+ })
727
+
349
728
  /**
350
729
  * Creates a recorder item for an Apple device screen recording, using the provided deviceID. Output is stored in a RecordKit bundle.
351
730
  *
@@ -418,10 +797,14 @@ export type MicrophoneOutputOptionsType = 'singleFile' | 'segmented' | 'stream'
418
797
 
419
798
  /**
420
799
  * Creates a recorder item for recording system audio. By default current process audio is excluded. Output is stored in a RecordKit bundle.
421
- *
800
+ *
422
801
  * When using `mode: 'exclude'`, all system audio is recorded except for excluded applications.
423
802
  * When using `mode: 'include'`, only audio from specified applications is recorded.
424
- *
803
+ *
804
+ * @remarks The default `excludeOptions: ['currentProcess']` refers to the process hosting the
805
+ * recorder — from Electron that is the bundled `recordkit-rpc` helper, which plays no audio. To
806
+ * exclude your own app's audio, pass its process IDs via `excludedProcessIDs`.
807
+ *
425
808
  * @group Recording Schemas
426
809
  */
427
810
  export type SystemAudioSchema = {
@@ -502,33 +885,37 @@ export type ApplicationAudioSchema = {
502
885
  streamCallback?: (audioBuffer: AudioStreamBuffer) => void
503
886
  }
504
887
 
888
+ interface MicrophoneSchemaCommon {
889
+ type: 'microphone'
890
+ microphone: Microphone | string
891
+ /** Echo cancellation applied to the microphone audio. Defaults to `'off'`. */
892
+ echoCancellation?: EchoCancellation
893
+ }
894
+
505
895
  /**
506
896
  * Creates a recorder item for an audio file, using the provided microphone. Output is stored in a RecordKit bundle.
507
- *
897
+ *
508
898
  * @group Recording Schemas
509
899
  */
510
- export type MicrophoneSchema = {
511
- type: 'microphone'
512
- microphone: Microphone | string
513
- leftChannelOnly?: boolean
514
- audioDelay?: number
515
- output?: 'singleFile'
516
- filename?: string
517
- } | {
518
- type: 'microphone'
519
- microphone: Microphone | string
520
- leftChannelOnly?: boolean
521
- audioDelay?: number
522
- output: 'segmented'
523
- filenamePrefix?: string
524
- segmentCallback?: (url: string) => void
525
- } | {
526
- type: 'microphone'
527
- microphone: Microphone | string
528
- output: 'stream'
529
- /** Called with real-time audio buffer data compatible with Web Audio API */
530
- streamCallback?: (audioBuffer: AudioStreamBuffer) => void
531
- }
900
+ export type MicrophoneSchema =
901
+ | (MicrophoneSchemaCommon & {
902
+ leftChannelOnly?: boolean
903
+ audioDelay?: number
904
+ output?: 'singleFile'
905
+ filename?: string
906
+ })
907
+ | (MicrophoneSchemaCommon & {
908
+ leftChannelOnly?: boolean
909
+ audioDelay?: number
910
+ output: 'segmented'
911
+ filenamePrefix?: string
912
+ segmentCallback?: (url: string) => void
913
+ })
914
+ | (MicrophoneSchemaCommon & {
915
+ output: 'stream'
916
+ /** Called with real-time audio buffer data compatible with Web Audio API */
917
+ streamCallback?: (audioBuffer: AudioStreamBuffer) => void
918
+ })
532
919
 
533
920
  /**
534
921
  * Audio buffer compatible with Web Audio API
@@ -547,13 +934,54 @@ export interface AudioStreamBuffer {
547
934
  }
548
935
 
549
936
 
937
+ /**
938
+ * A condition detected during a recording that might need the user's attention.
939
+ *
940
+ * Unlike an {@link AbortReason} a signal never ends the recording, it reports something being off
941
+ * while recording continues, so your app can warn the user and let them fix it. A signal stays in
942
+ * the list emitted on the `signals` event for as long as the condition holds.
943
+ *
944
+ * @group Recording
945
+ */
946
+ export interface Signal {
947
+ /** The kind of condition that was detected. */
948
+ kind: SignalKind
949
+ // The only signal reported today concerns the recording as a whole, so no source is ever sent.
950
+ // The audio signals are what will populate this, along with the SignalSource type below:
951
+ // /** What the signal is about, omitted when it concerns the recording as a whole. */
952
+ // source?: SignalSource
953
+ }
954
+
955
+ /**
956
+ * The kind of condition a {@link Signal} reports.
957
+ *
958
+ * @group Recording
959
+ */
960
+ export type SignalKind =
961
+ // Waiting on the audio signals, kept here so the shape is settled:
962
+ // /** The recorded audio is silent, or so quiet it is very likely unusable. */
963
+ // | 'audioSilence'
964
+ /**
965
+ * Free space on the recording volume dropped below
966
+ * {@link RecorderSettings.diskSpaceWarningLevel}. The recording continues, but keeps eating into
967
+ * the space that is left; below {@link RecorderSettings.diskSpaceAbortLevel} it is aborted with
968
+ * an `insufficientDiskSpace` error instead. A paused recording is never aborted, since nothing is
969
+ * being written; it is reported on and aborts after resuming.
970
+ */
971
+ | 'lowDiskSpace'
972
+
973
+ // The part of the recording a Signal originated from. No cases yet, and TypeScript has no empty
974
+ // union, so it stays commented out until the audio signals land:
975
+ // export type SignalSource =
976
+ // | { type: 'microphone'; id: string; }
977
+
550
978
  /**
551
979
  * @group Recording
552
980
  */
553
981
  export type AbortReason =
554
982
  | { reason: 'userStopped'; result: RecordingResult; }
555
- | { reason: 'interrupted'; result: RecordingResult; error: any; }
556
- | { reason: 'failed'; error: any; }
983
+ | { reason: 'interrupted'; result: RecordingResult; error: RecordKitError | NSErrorPayload; }
984
+ | { reason: 'failed'; result: RecordingResult; error: RecordKitError | NSErrorPayload; }
557
985
 
558
986
  /**
559
987
  * @group Recording
@@ -570,9 +998,9 @@ export interface RecordingResult {
570
998
  */
571
999
  export interface RecordKitError {
572
1000
  name: "RecordKitError"
573
- /** Error code, used for grouping related errors */
574
- code: string
575
- /** Error code number */
1001
+ /** Error code, used for grouping related errors. See {@link RecordKitErrorCode} for the full list of codes. */
1002
+ code: RecordKitErrorCode
1003
+ /** Error code number. See {@link RECORDKIT_ERROR_CODE_NUMBERS}. */
576
1004
  codeNumber: number
577
1005
  /** Message describing the problem and possible recovery options, intended to be shown directly to the end-user. */
578
1006
  message: string
@@ -581,12 +1009,62 @@ export interface RecordKitError {
581
1009
  }
582
1010
 
583
1011
  /**
1012
+ * An error produced outside RecordKit's own error domain — a raw `NSError` surfaced over the bridge.
1013
+ *
1014
+ * Distinguished from {@link RecordKitError} by its `name` discriminator. See the
1015
+ * [Logging and Error Handling guide](https://recordkit.dev/guides/logging-and-errors#error-handling).
1016
+ *
1017
+ * @group Recording
1018
+ */
1019
+ export interface NSErrorPayload {
1020
+ name: "NSError"
1021
+ /** The `NSError` domain (e.g. `"NSOSStatusErrorDomain"`). */
1022
+ errorDomain: string
1023
+ /** The `NSError` code within {@link NSErrorPayload.errorDomain}. */
1024
+ errorCode: number
1025
+ /** Localized, user-facing description of the error. */
1026
+ message: string
1027
+ /** Detailed technical description of this error, used in debugging. */
1028
+ debugDescription: string
1029
+ }
1030
+
1031
+ /**
1032
+ * An error raised by the RPC bridge itself rather than by a RecordKit recording — for example
1033
+ * calling a method on a recorder that was already cancelled, requesting a feature that needs a
1034
+ * newer macOS version, or referencing a window or camera that cannot be found.
1035
+ *
1036
+ * Distinguished from {@link RecordKitError} and {@link NSErrorPayload} by its `name` discriminator.
1037
+ *
1038
+ * @group Recording
1039
+ */
1040
+ export interface RPCErrorPayload {
1041
+ name: "RPCError"
1042
+ /** Message describing the problem, intended to be shown directly to the end-user. */
1043
+ message: string
1044
+ /** The same message as {@link RPCErrorPayload.message}, kept under its legacy field name. */
1045
+ userMessage: string
1046
+ /** Detailed technical description of this error, used in debugging. */
1047
+ debugDescription: string
1048
+ }
1049
+
1050
+ /**
1051
+ * Describes a recording bundle's contents (the parsed `recordkit.json`). Mirrors the Swift
1052
+ * `RKBundleInfo`; the per-event sidecar types live in `RecordingMetadata.ts`.
1053
+ *
584
1054
  * @group Recording
585
1055
  */
586
1056
  export interface BundleInfo {
587
1057
  version: 1,
1058
+ /** Total duration of the recording, in seconds. */
1059
+ duration: number,
588
1060
  files: {
589
1061
  type: 'screen' | 'webcam' | 'audio' | 'mouse' | 'systemAudio' | 'appleDevice' | 'topWindow'
590
1062
  filename: string
1063
+ /** Filenames of related sidecar files for this asset (e.g. input-event JSON), relative to the bundle. */
1064
+ related?: string[]
1065
+ /** Logical size of the recorded area, in points (e.g. `2560x1440` for a Retina 5K display). */
1066
+ recordingSize?: { width: number, height: number }
1067
+ /** Dimensions of the output video, in pixels. */
1068
+ videoDimensions?: { width: number, height: number }
591
1069
  }[]
592
1070
  }