src — screen-capture
src — screen-capture
The src/screen-capture module provides a comprehensive, event-driven API for programmatically taking screenshots and recording screen activity. It offers capabilities to discover available displays and windows, capture specific regions, and manage the lifecycle of recording sessions.
Important Note: This module currently implements a mock version of the screen capture functionality. It simulates operations like taking screenshots and recording, generating mock data and paths, but does not interact with the actual operating system's screen capture APIs. A real implementation would integrate with native modules or external tools (e.g., ffmpeg, scrot).
1. Core Concepts and Types
The module defines several key types to represent capture sources, options, and results. These are exported from src/screen-capture/types.ts.
Capture Sources and Regions
CaptureType:'screenshot' | 'recording'- Denotes the type of capture operation.CaptureSource:'screen' | 'window' | 'region' | 'display'- Specifies what part of the screen is being captured.CaptureRegion: An interface defining a rectangular area withx,y,width, andheightproperties.
Display and Window Information
DisplayInfo: An interface providing details about a connected display, including itsid,name,bounds(asCaptureRegion),isPrimarystatus,scaleFactor, andrefreshRate.WindowInfo: An interface providing details about an open window, including itsid,title,processName,bounds,isMinimizedstatus,isVisiblestatus, andpid(process ID).
Screenshot Types
ScreenshotOptions: An interface to configure a screenshot operation. It includes properties likepath(output file),format,quality,source(e.g., 'window', 'display'),delayMs, andscale.DEFAULT_SCREENSHOT_OPTIONSprovides sensible default values for these options.ScreenshotResult: An interface containing the outcome of a screenshot operation. It includes the captureddata(as aBuffer),format,width,height,path,timestamp, andsourceinformation.
Recording Types
RecordingOptions: An interface to configure a recording operation. It includes properties likepath,format,codec,fps,quality,bitrate,source,includeAudio, andmaxDurationMs.DEFAULT_RECORDING_OPTIONSprovides sensible default values for these options.RecordingResult: An interface summarizing a completed recording. It includes thepath,format,durationMs,frameCount,avgFps,size,startedAt,endedAt,sourceinformation, andresolution.RecordingState: A union type representing the current state of a recording:'idle' | 'starting' | 'recording' | 'paused' | 'stopping' | 'stopped'.RecordingStatus: An interface providing real-time information about an ongoing recording, including itsstate,durationMs,frameCount,currentFps,currentSize,droppedFrames,startedAt, andpausedAt.
Configuration
ScreenCaptureConfig: An interface for global configuration of the capture manager. It allows settingscreenshotDefaults,recordingDefaults,outputDir,namingPattern,maxConcurrentcaptures, andhardwareAccelerationpreferences.DEFAULT_SCREEN_CAPTURE_CONFIGdefines the initial configuration.
Events
ScreenCaptureEvents: An interface defining the custom events emitted by theCaptureManagerduring various stages of capture operations (e.g.,screenshot-start,recording-progress,recording-complete).
2. CaptureManager Class
The CaptureManager class (src/screen-capture/capture-manager.ts) is the central interface for all screen capture and recording operations. It extends Node.js's EventEmitter to provide event-driven feedback on capture progress and status.
classDiagram
EventEmitter <|-- CaptureManager
CaptureManager "1" *-- "0..1" RecordingContext
CaptureManager "1" *-- "N" DisplayInfo
CaptureManager "1" *-- "N" WindowInfo
CaptureManager "1" *-- "1" ScreenCaptureConfig
class CaptureManager {
+constructor(config?: Partial<ScreenCaptureConfig>)
+getDisplays(): Promise<DisplayInfo[]>
+getPrimaryDisplay(): Promise<DisplayInfo | undefined>
+getWindows(): Promise<WindowInfo[]>
+findWindows(titlePattern: string | RegExp): Promise<WindowInfo[]>
+takeScreenshot(options?: Partial<ScreenshotOptions>): Promise<ScreenshotResult>
+takeScreenshots(count: number, intervalMs: number, options?: Partial<ScreenshotOptions>): Promise<ScreenshotResult[]>
+startRecording(options: RecordingOptions): Promise<void>
+pauseRecording(): void
+resumeRecording(): void
+stopRecording(): Promise<RecordingResult>
+cancelRecording(): void
+getRecordingStatus(): RecordingStatus
+isRecording(): boolean
+isPaused(): boolean
+getConfig(): ScreenCaptureConfig
+updateConfig(config: Partial<ScreenCaptureConfig>): void
+getStats(): object
-initializeMockData(): void
-resolveRegion(options): Promise<CaptureRegion>
-generateMockImage(region, options): Buffer
-generateFilePath(type, format): string
-calculateRecordingDuration(): number
}
class RecordingContext {
options: RecordingOptions
state: RecordingState
startedAt: Date
pausedAt?: Date
frameCount: number
droppedFrames: number
pausedDuration: number
region: CaptureRegion
interval: NodeJS.Timeout | null
}
class ScreenCaptureConfig {
screenshotDefaults: Partial<ScreenshotOptions>
recordingDefaults: Partial<RecordingOptions>
outputDir: string
namingPattern: string
maxConcurrent: number
hardwareAcceleration: boolean
}
class DisplayInfo {
id: string
name: string
bounds: CaptureRegion
isPrimary: boolean
scaleFactor: number
refreshRate?: number
}
class WindowInfo {
id: string
title: string
processName?: string
bounds: CaptureRegion
isMinimized: boolean
isVisible: boolean
pid?: number
}
class CaptureRegion {
x: number
y: number
width: number
height: number
}
2.1. Initialization and Configuration
constructor(config: Partial: Initializes the manager with provided configuration, merging it with= {}) DEFAULT_SCREEN_CAPTURE_CONFIG. It also callsinitializeMockData()to set up static display and window information.getConfig(): ScreenCaptureConfig: Returns a copy of the current configuration.updateConfig(config: Partial: Merges new configuration settings into the existing configuration.): void
2.2. Display and Window Discovery (Mocked)
These asynchronous methods provide information about available screens and windows. In this mock implementation, they return predefined static data initialized by initializeMockData().
getDisplays(): Promise: Returns a list of all mock displays.getPrimaryDisplay(): Promise: Returns the primary mock display.getDisplay(id: string): Promise: Finds a mock display by its ID.getWindows(): Promise: Returns a list of all visible, non-minimized mock windows.getWindow(id: string): Promise: Finds a mock window by its ID.findWindows(titlePattern: string | RegExp): Promise: Filters mock windows by title using a regular expression pattern.
2.3. Screenshot Functionality
The manager supports both single and multiple screenshot operations.
takeScreenshot(options: Partial:= {}): Promise - Takes a single screenshot based on the provided options, merging them with
DEFAULT_SCREENSHOT_OPTIONSandconfig.screenshotDefaults. - Emits
screenshot-startbefore processing. - Determines the target capture region using
resolveRegion(prioritizingregion, thenwindowId, thendisplayId, falling back to the primary display). - Generates mock image data using
generateMockImageand a mock file path usinggenerateFilePath. - Emits
screenshot-completeupon success orscreenshot-errorif an error occurs. takeScreenshots(count: number, intervalMs: number, options: Partial:= {}): Promise - Takes
countscreenshots withintervalMsdelay between each. - Internally calls
takeScreenshotfor each capture, adjusting the output path for sequential naming.
2.4. Recording Functionality
The manager provides a full lifecycle for screen recording, including start, pause, resume, and stop.
startRecording(options: RecordingOptions): Promise:- Initiates a recording session. Throws an error if a recording is already in progress.
- Merges options with
DEFAULT_RECORDING_OPTIONSandconfig.recordingDefaults. - Resolves the capture region using
resolveRegion. - Sets up an internal
setIntervalto simulate frame capture and progress updates, emittingrecording-progressevents. - Emits
recording-start. - Self-termination: If
maxDurationMsis set, the recording will automatically callstopRecording()when the active duration is reached. pauseRecording(): void:- Pauses an active recording. Throws an error if no recording is active or it's not in the
'recording'state. - Updates the internal
RecordingContextstate to'paused'and emitsrecording-pause. resumeRecording(): void:- Resumes a paused recording. Throws an error if no recording is paused.
- Updates the internal
RecordingContextstate to'recording', accounts for the duration of the pause, and emitsrecording-resume. stopRecording(): Promise:- Stops the current recording session. Throws an error if no recording is active.
- Clears the internal frame capture interval.
- Calculates final metrics (duration, FPS, size) using
calculateRecordingDuration. - Emits
recording-stopandrecording-complete. cancelRecording(): void:- Immediately stops and discards the current recording without generating a result.
- Clears the interval and resets the recording state.
- Emits
recording-errorwith a "Recording cancelled" message. getRecordingStatus(): RecordingStatus:- Returns the current state and metrics of the ongoing or last recording, calculated using
calculateRecordingDuration. isRecording(): boolean: Checks if a recording is currently active (state'recording').isPaused(): boolean: Checks if a recording is currently paused (state'paused').
2.5. Private Helpers
The CaptureManager uses several private methods for internal logic:
initializeMockData(): void: Populatesthis.displaysandthis.windowswith static mock data.resolveRegion(options: Partial: Determines the): Promise CaptureRegionbased on provided options, falling back to the primary display if no specific region, window, or display is specified.generateMockImage(region: CaptureRegion, options: ScreenshotOptions): Buffer: Creates aBufferof random bytes to simulate image data, scaled byoptions.scaleand capped at 1MB.generateFilePath(type: string, format: string): string: Constructs a file path based on the configuredoutputDirandnamingPattern, incorporating the capturetypeand a timestamp.calculateRecordingDuration(): number: Computes the active recording duration, accurately accounting for any paused periods.
2.6. Events Emitted
The CaptureManager emits the following events, which can be subscribed to using manager.on():
screenshot-start(options: ScreenshotOptions)screenshot-complete(result: ScreenshotResult)screenshot-error(error: Error)recording-start(options: RecordingOptions)recording-progress(status: RecordingStatus)recording-pause()recording-resume()recording-stop()recording-complete(result: RecordingResult)recording-error(error: Error)
3. Singleton Access
For convenience and to ensure a single point of control for screen capture operations, the module provides a singleton pattern:
getCaptureManager(config?: Partial:): CaptureManager - Returns the singleton instance of
CaptureManager. If no instance exists, it creates one, optionally applying initial configuration. Subsequent calls return the same instance. resetCaptureManager(): void:- Resets the singleton instance. If a recording is active, it will be cancelled via
cancelRecording(). This is primarily useful for testing or re-initialization scenarios.
4. Module Entry Point (src/screen-capture/index.ts)
This file serves as the public API for the screen capture module. It re-exports all necessary types, default options, and the CaptureManager class along with its singleton access functions, making them easily importable.
5. Usage Examples
Getting the Manager and Configuration
import { getCaptureManager, DEFAULT_SCREEN_CAPTURE_CONFIG } from 39;./screen-capture/index.js39;;
class="hl-cmt">// Get the singleton instance, optionally providing initial config
const manager = getCaptureManager({
outputDir: 39;./my-captures39;,
namingPattern: 39;capture_{type}_{timestamp}39;,
});
console.log(39;Current config:39;, manager.getConfig());
class="hl-cmt">// Update configuration later
manager.updateConfig({ hardwareAcceleration: false });
Discovering Displays and Windows
import { getCaptureManager } from 39;./screen-capture/index.js39;;
const manager = getCaptureManager();
async function discover() {
const displays = await manager.getDisplays();
console.log(39;Displays:39;, displays);
const primaryDisplay = await manager.getPrimaryDisplay();
console.log(39;Primary Display:39;, primaryDisplay);
const windows = await manager.getWindows();
console.log(39;Visible Windows:39;, windows);
class="hl-cmt">// Find windows by title pattern (case-insensitive regex)
const terminalWindows = await manager.findWindows(/terminal/i);
console.log(39;Terminal Windows:39;, terminalWindows);
}
discover();
Taking a Screenshot
import { getCaptureManager } from 39;./screen-capture/index.js39;;
const manager = getCaptureManager();
class="hl-cmt">// Listen for events
manager.on(39;screenshot-complete39;, (result) => {
console.log(`Screenshot saved to: ${result.path}`);
console.log(`Image size: ${result.width}x${result.height}, ${result.size} bytes`);
});
manager.on(39;screenshot-error39;, (error) => {
console.error(39;Screenshot failed:39;, error.message);
});
async function takeMyScreenshot() {
try {
const primaryDisplay = await manager.getPrimaryDisplay();
if (!primaryDisplay) {
console.error(39;No primary display found.39;);
return;
}
class="hl-cmt">// Take a single screenshot of the primary display with a delay
const result = await manager.takeScreenshot({
displayId: primaryDisplay.id,
format: 39;jpeg39;,
quality: 85,
delayMs: 1000, class="hl-cmt">// 1 second delay
});
console.log(39;Screenshot operation complete (promise resolved).39;);
class="hl-cmt">// Take multiple screenshots with an interval
const results = await manager.takeScreenshots(3, 2000, {
format: 39;png39;,
path: 39;./my-captures/series.png39; class="hl-cmt">// Will generate series_0.png, series_1.png, etc.
});
console.log(`Captured ${results.length} screenshots in a series.`);
} catch (error) {
console.error(39;Error during screenshot:39;, error);
}
}
takeMyScreenshot();
Recording the Screen
import { getCaptureManager } from 39;./screen-capture/index.js39;;
const manager = getCaptureManager();
class="hl-cmt">// Listen for recording events
manager.on(39;recording-start39;, (options) => console.log(39;Recording started with options:39;, options));
manager.on(39;recording-progress39;, (status) => {
console.log(`Recording state: ${status.state}, Duration: ${status.durationMs}ms, Frames: ${status.frameCount}`);
});
manager.on(39;recording-pause39;, () => console.log(39;Recording paused.39;));
manager.on(39;recording-resume39;, () => console.log(39;Recording resumed.39;));
manager.on(39;recording-complete39;, (result) => {
console.log(`Recording complete! Saved to: ${result.path}, Duration: ${result.durationMs}ms`);
});
manager.on(39;recording-error39;, (error) => console.error(39;Recording error:39;, error.message));
async function recordMyScreen() {
try {
const primaryDisplay = await manager.getPrimaryDisplay();
if (!primaryDisplay) {
console.error(39;No primary display found.39;);
return;
}
await manager.startRecording({
path: 39;./my-captures/my-recording.mp439;,
displayId: primaryDisplay.id,
fps: 25,
maxDurationMs: 10000, class="hl-cmt">// Record for 10 seconds of active time
});
console.log(39;Recording in progress...39;);
class="hl-cmt">// Simulate pausing after 3 seconds
setTimeout(() => {
if (manager.isRecording()) {
manager.pauseRecording();
console.log(39;Recording paused for 2 seconds.39;);
}
}, 3000);
class="hl-cmt">// Simulate resuming after 5 seconds (2 seconds after pause)
setTimeout(() => {
if (manager.isPaused()) {
manager.resumeRecording();
console.log(39;Recording resumed.39;);
}
}, 5000);
class="hl-cmt">// The recording will automatically stop after maxDurationMs (10 seconds total active recording)
class="hl-cmt">// You could also manually stop it:
class="hl-cmt">// setTimeout(async () => {
class="hl-cmt">// if (manager.isRecording() || manager.isPaused()) {
class="hl-cmt">// const result = await manager.stopRecording();
class="hl-cmt">// console.log(39;Manually stopped recording.39;);
class="hl-cmt">// }
class="hl-cmt">// }, 12000); // Stop after 12 seconds total elapsed time
} catch (error) {
console.error(39;Failed to start recording:39;, error);
}
}
recordMyScreen();
6. Mock Implementation Details
It's crucial to understand that this module is a mock. This means:
- No Actual OS Interaction: It does not capture pixels from the screen or interact with native windowing systems.
- Static Data:
getDisplays()andgetWindows()return hardcodedDisplayInfoandWindowInfoobjects defined ininitializeMockData(). - Simulated Output:
generateMockImage()creates aBufferfilled with random bytes, not actual image data. Recording "frames" are simulated by incrementing a counter and emitting progress events. - File Paths Only: While
pathproperties are generated, no actual files are written to disk. - Time-Based Simulation: Delays, durations, and FPS are simulated using
setTimeoutandsetInterval.
This mock implementation is valuable for:
- API Design and Validation: Testing the API surface and ensuring it meets requirements.
- Integration Testing: Allowing other parts of the application to integrate with the screen capture module without needing a full native implementation.
- Development Workflow: Enabling development on platforms where native screen capture might be complex or unavailable.
A real implementation would replace the mock logic in methods like initializeMockData, generateMockImage, and the setInterval loop in startRecording with calls to native OS APIs or external tools (e.g., scrot for screenshots, ffmpeg for recordings).
7. Connections to the Wider Codebase
This module is designed to be a self-contained utility for screen capture.
- Primary Consumer: The
capture.test.tsfile extensively usesCaptureManagerand its methods to validate its functionality, demonstrating its intended usage. - Internal Dependencies: It relies on Node.js's built-in
eventsmodule forEventEmitter,cryptofor mock data generation, andpathfor file path manipulation. - External Dependencies: The
findWindowsmethod usesRegExp.testfor pattern matching, which is a standard JavaScript feature. - No Direct Outgoing Calls (beyond standard library): The mock nature means it doesn't directly call out to other custom modules in the codebase for its core functionality, keeping it isolated.
- Type Definitions: The
types.tsfile is a central point for defining interfaces used throughout the module, ensuring type safety and clarity across the API.
This module provides a robust, albeit mocked, foundation for screen capture and recording, ready for integration with native capabilities when required.