npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

videokitten

v1.0.1

Published

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

Readme

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

npm install videokitten

Quick Start

Here'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);

API

videokitten(options)

Creates a new Videokitten instance.

options

An object with platform-specific configuration.

iOS Options (VideokittenOptionsIOS)
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.

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:

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
// 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.

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.

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.

License

MIT

Contributing

Contributions are welcome! Please read our Contributing Guide and Code of Conduct.