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 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-media-edit

v0.5.1

Published

Native video editing for Expo — trim, overlay text/images, mix audio. No FFmpeg.

Readme

expo-media-edit

Native video editing for Expo apps. Trim, overlay text/images, and mix audio — fully on-device via AVFoundation (iOS) and MediaCodec (Android). No FFmpeg, no cloud rendering.

Requirements

  • iOS 16+
  • Android API 26+
  • Expo SDK 54+
  • Requires a native build (EAS Build or expo run:ios / expo run:android). Does not work in Expo Go.

Installation

npx expo install expo-media-edit

Then rebuild your native app:

# iOS
npx expo run:ios
# or
eas build --platform ios

# Android
npx expo run:android

Usage

import { editVideo, addProgressListener, cancelEdit } from 'expo-media-edit';

// Subscribe to progress before starting
const sub = addProgressListener(({ progress }) => {
  console.log(`Progress: ${Math.round(progress * 100)}%`);
});

const outputUri = await editVideo({
  inputUri: 'file:///path/to/video.mp4',
  trim: { startMs: 0, endMs: 15000 },
  quality: 'medium',
  overlays: [
    {
      type: 'text',
      content: 'Hello World',
      x: 0.05,
      y: 0.85,
      fontSize: 48,
      color: '#FFFFFF',
      fontWeight: 'bold',
    },
    {
      type: 'image',
      uri: 'file:///path/to/logo.png',
      x: 0.7,
      y: 0.05,
      width: 0.2,
      height: 0.1,
      opacity: 0.9,
    },
  ],
  audio: {
    uri: 'file:///path/to/music.mp3',
    volume: 0.8,
    originalVolume: 0.2,
    trimToVideo: true,
  },
});

sub.remove(); // always clean up the listener

To cancel an in-progress edit:

await cancelEdit(); // the editVideo() promise rejects with code "CANCELLED"

API

editVideo(job: EditJob): Promise<string>

Edit a video and return the URI of the output file.

type EditJob = {
  inputUri: string;       // Local file URI (file://...) or https:// URL
  outputUri?: string;     // Optional output path; defaults to a temp file
  trim?: {
    startMs: number;      // Start time in milliseconds
    endMs: number;        // End time in milliseconds
  };
  overlays?: OverlayItem[];
  audio?: AudioMix;
  quality?: 'low' | 'medium' | 'high'; // Default: 'high'
};

type OverlayItem =
  | {
      type: 'text';
      content: string;
      x: number;              // 0.0–1.0 relative to video width
      y: number;              // 0.0–1.0 relative to video height
      fontSize?: number;      // Default: 32
      color?: string;         // CSS hex, e.g. '#FFFFFF'. Default: '#FFFFFF'
      fontWeight?: 'normal' | 'bold';
      backgroundColor?: string;
      startMs?: number;       // Show from this time (default: 0)
      endMs?: number;         // Hide after this time (default: video end)
    }
  | {
      type: 'image';
      uri: string;            // Local image URI
      x: number;
      y: number;
      width: number;          // 0.0–1.0 relative to video width
      height: number;         // 0.0–1.0 relative to video height
      opacity?: number;       // 0.0–1.0. Default: 1.0
      startMs?: number;
      endMs?: number;
    };

type AudioMix = {
  uri: string;                // Local audio URI (.mp3, .m4a, .wav)
  volume?: number;            // Music track volume 0.0–1.0. Default: 1.0
  originalVolume?: number;    // Original audio volume 0.0–1.0. Default: 0.0
  startMs?: number;           // Music start offset in ms. Default: 0
  trimToVideo?: boolean;      // Cut music at video end. Default: true
};

getVideoInfo(uri: string): Promise<VideoInfo>

Read metadata about a video file.

type VideoInfo = {
  durationMs: number;
  width: number;
  height: number;
  fps: number;
  fileSize: number;   // Bytes
  codec?: string;
};

generateThumbnail(uri: string, timeMs: number, options?: { width?: number; height?: number }): Promise<string>

Extract a JPEG thumbnail from a video at the given timestamp. Returns a file:// URI.

addProgressListener(callback: (event: ProgressEvent) => void): Subscription

Subscribe to progress events during editVideo(). Returns a subscription with a remove() method.

type ProgressEvent = { progress: number }; // 0.0–1.0

Always call sub.remove() after editVideo() resolves or rejects.

cancelEdit(): Promise<void>

Cancel an in-progress editVideo() call. The editVideo promise rejects with error code "CANCELLED".

cleanTempFiles(): Promise<number>

Delete all temporary files created by expo-media-edit. Returns the number of files deleted.

Platform notes

iOS

Uses AVFoundation:

  • Trim: AVAssetExportSession with a time range — lossless stream copy.
  • Overlays: AVVideoCompositionCoreAnimationTool with CATextLayer / CALayer burn-in.
  • Audio: AVMutableAudioMixInputParameters for volume control; additional AVMutableCompositionTrack for music.

Android

Uses MediaCodec + MediaMuxer:

  • Trim: MediaExtractor + MediaMuxer stream copy (no re-encode).
  • Overlays: Frame-by-frame decode via MediaMetadataRetriever, Canvas draw, YUV420 re-encode via MediaCodec. Quality controlled via bitrate (low: 1Mbps, medium: 2Mbps, high: 4Mbps).
  • Audio: Stream copy when volume == 1.0; PCM decode → scale → AAC re-encode for other volume values. Rotation metadata preserved via MediaMuxer.setOrientationHint().

Known limitations

  • Background processing is not supported (the app must stay in the foreground during editing).
  • Android audio mixing does not support simultaneous multi-track volume scaling (original + music both at non-1.0 volume in the same output file). Each track is scaled independently.

License

MIT