Videokitten is a cross-platform Node.js library for recording videos from iOS simulators and Android emulators/devices. It provides a simple, unified API to automate screen recording for your integration tests, E2E tests, or any other automation needs.
- 🍎 iOS Simulator Support - Record videos using
xcrun simctl - 🤖 Android Emulator/Device Support - Record videos using the powerful
scrcpytool - 🎥 Flexible API - Start and stop recording programmatically with full control.
- 🛠️ Error Handling - Built-in error classification for common issues (e.g., device not found, tools not installed).
- ✨ TypeScript Support - Fully typed for a great developer experience.
npm install videokittenHere's a basic example of how to record a 5-second video from a booted iOS simulator:
import { videokitten } from 'videokitten';
async function main() {
const kitten = videokitten({ platform: 'ios' });
console.log('Starting iOS recording...');
const session = await kitten.startRecording();
if (!session) {
console.log('Recording failed to start (onError: ignore was set)');
return;
}
console.log('Recording started!');
// Let it record for 5 seconds
await new Promise(resolve => setTimeout(resolve, 5000));
console.log('Stopping recording...');
const videoPath = await session.stop();
if (videoPath) {
console.log(`Video saved to: ${videoPath}`);
} else {
console.log('Recording failed to complete');
}
}
main().catch(console.error);Creates a new Videokitten instance.
An object with platform-specific configuration.
const options = {
platform: 'ios',
deviceId?: string; // Default: 'booted' (the currently booted simulator)
outputPath?: string; // Default: a temporary file in /tmp
xcrunPath?: string; // Default: '/usr/bin/xcrun'
codec?: 'h264' | 'hevc'; // Default: 'hevc'
display?: 'internal' | 'external'; // Default: 'internal'
force?: boolean; // Overwrite existing file. Default: false
};Videokitten uses scrcpy for Android recording, so the options are a direct mapping to scrcpy's command-line arguments.
const options = {
platform: 'android',
deviceId?: string; // Target a specific device by serial
outputPath?: string; // Default: a temporary file in /tmp
scrcpyPath?: string; // Path to scrcpy executable. Default: 'scrcpy'
adbPath?: string; // Path to adb executable. Default: assumes in PATH
// See scrcpy documentation for all available options
recording?: {
bitRate?: number; // e.g., 8_000_000 for 8 Mbps
codec?: 'h264' | 'h265' | 'av1';
format?: 'mp4' | 'mkv';
timeLimit?: number; // In seconds
},
// And many more...
};All platforms support these common options:
const options = {
platform: 'ios' | 'android',
deviceId?: string; // Device identifier
outputPath?: string; // Output file path
abortSignal?: AbortSignal; // Signal to cancel recording
onError?: 'throw' | 'ignore' | ((error: Error) => void);
timeout?: number; // Recording timeout in seconds
delay?: number | [number, number]; // Frame buffering delays in milliseconds
};The delay option controls timing delays for frame buffering:
- Single number: Startup delay only (e.g.,
200= wait 200ms after process is ready) - Tuple
[startup, stop]: Both startup and stop delays (e.g.,[200, 100])
Startup delay: Waits after the process signals it's ready before considering recording started. This allows processes like scrcpy to initialize and buffer frames.
Stop delay: Waits before stopping the process to ensure all buffered frames are written. This prevents missing the last few frames of the recording.
Defaults:
- Android:
200- scrcpy needs time to buffer frames - iOS:
0- iOS handles buffering internally
// Custom delays for Android
const android = videokitten({
platform: 'android',
delay: [300, 150] // 300ms startup, 150ms stop delay
});
// Just startup delay
const android = videokitten({
platform: 'android',
delay: 250 // 250ms startup delay only
});Starts a new recording session.
overrideOptions: An optional object to override the options provided to thevideokittenconstructor.
Returns a Promise<RecordingSession | undefined>. Returns undefined if recording fails to start and onError is set to 'ignore'.
An object representing an active recording session.
Stops the recording gracefully and returns the path to the saved video file.
Returns a Promise<string | undefined>. Returns undefined if recording fails to complete and onError is set to 'ignore'.
You can use a standard AbortSignal to stop the recording. This is useful for integrating with other parts of your application that use abort controllers.
import { videokitten } from 'videokitten';
async function recordWithSignal() {
const kitten = videokitten({ platform: 'android' });
const controller = new AbortController();
// Abort after 10 seconds
setTimeout(() => controller.abort(), 10000);
try {
const session = await kitten.startRecording({ abortSignal: controller.signal });
if (!session) {
console.log('Recording failed to start (onError: ignore was set)');
return;
}
console.log('Recording... press Ctrl+C or wait 10s to stop.');
// The `stop()` promise will be rejected with an AbortError
// when the signal is aborted.
const videoPath = await session.stop();
if (videoPath) {
console.log(`Video saved to: ${videoPath}`);
} else {
console.log('Recording failed to complete');
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('Recording was aborted successfully.');
} else {
console.error('An unexpected error occurred:', error);
}
}
}
recordWithSignal();Videokitten throws specific error classes to help you handle different failure scenarios.
import { videokitten, VideokittenError, VideokittenXcrunNotFoundError } from 'videokitten';
try {
const kitten = videokitten({ platform: 'ios', xcrunPath: '/invalid/path' });
const session = await kitten.startRecording();
if (!session) {
console.log('Recording failed to start (onError: ignore was set)');
return;
}
const videoPath = await session.stop();
if (videoPath) {
console.log(`Video saved to: ${videoPath}`);
}
} catch (error) {
if (error instanceof VideokittenXcrunNotFoundError) {
console.error('xcrun is not installed or not in the correct path!');
} else if (error instanceof VideokittenError) {
console.error('A videokitten error occurred:', error.message);
} else {
console.error('An unknown error occurred:', error);
}
}Available error classes:
VideokittenError(base class)VideokittenDeviceNotFoundErrorVideokittenXcrunNotFoundError// xcrun tool not found (iOS)VideokittenScrcpyNotFoundError// scrcpy tool not found (Android)VideokittenAdbNotFoundError// adb tool not found (Android)VideokittenIOSSimulatorErrorVideokittenAndroidDeviceErrorVideokittenFileWriteErrorVideokittenOperationAbortedErrorVideokittenRecordingFailedError
- Node.js: v16.14.0 or higher
- iOS: macOS with Xcode Command Line Tools installed.
xcruntool (usually available at/usr/bin/xcrun)
- Android:
scrcpyandadbmust be installed and available in your system'sPATH.
MIT
Contributions are welcome! Please read our Contributing Guide and Code of Conduct.