UNPKG

videokitten

Version:

A cross-platform Node.js library for recording videos from iOS simulators and Android devices/emulators

266 lines (197 loc) 8.4 kB
# Videokitten 📱🎬 **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 `scrcpy` tool - 🎥 **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. ## Installation ```bash npm install videokitten ``` ## Quick Start Here's a basic example of how to record a 5-second video from a booted iOS simulator: ```javascript 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); ``` ## API ### `videokitten(options)` Creates a new `Videokitten` instance. #### `options` An object with platform-specific configuration. ##### iOS Options (`VideokittenOptionsIOS`) ```typescript 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 }; ``` ##### Android Options (`VideokittenOptionsAndroid`) Videokitten uses `scrcpy` for Android recording, so the options are a direct mapping to `scrcpy`'s command-line arguments. ```typescript 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... }; ``` ### Base Options All platforms support these common options: ```typescript 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 }; ``` #### Delay Configuration 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 ```typescript // 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 }); ``` ### `kitten.startRecording(overrideOptions)` Starts a new recording session. - `overrideOptions`: An optional object to override the options provided to the `videokitten` constructor. Returns a `Promise<RecordingSession | undefined>`. Returns `undefined` if recording fails to start and `onError` is set to `'ignore'`. ### `RecordingSession` An object representing an active recording session. #### `session.stop()` 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'`. ## Advanced Usage ### Stopping with an `AbortSignal` 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. ```javascript 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(); ``` ### Error Handling Videokitten throws specific error classes to help you handle different failure scenarios. ```typescript 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) - `VideokittenDeviceNotFoundError` - `VideokittenXcrunNotFoundError` // xcrun tool not found (iOS) - `VideokittenScrcpyNotFoundError` // scrcpy tool not found (Android) - `VideokittenAdbNotFoundError` // adb tool not found (Android) - `VideokittenIOSSimulatorError` - `VideokittenAndroidDeviceError` - `VideokittenFileWriteError` - `VideokittenOperationAbortedError` - `VideokittenRecordingFailedError` ## Requirements - **Node.js**: v16.14.0 or higher - **iOS**: macOS with Xcode Command Line Tools installed. - `xcrun` tool (usually available at `/usr/bin/xcrun`) - **Android**: `scrcpy` and `adb` must be installed and available in your system's `PATH`. - [scrcpy releases](https://github.com/Genymobile/scrcpy/releases) - [Android SDK Platform-Tools](https://developer.android.com/studio/releases/platform-tools) ## License MIT ## Contributing Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).