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

react-native-lossless-trim

v0.1.1

Published

Lossless, ffmpeg-free video trimming for React Native and Expo. Stream-copy passthrough on the platform-native APIs (AVAssetExportSession / MediaMuxer): no re-encode, near-instant, ~zero binary weight.

Readme

react-native-lossless-trim

Lossless, ffmpeg-free video trimming for React Native and Expo.

Trims a clip by stream-copying the encoded frames on the platform's own native APIs (AVAssetExportSession passthrough on iOS, MediaExtractor + MediaMuxer on Android). No re-encode, no ffmpeg, no multi-megabyte binary. The output is bit-for-bit the same quality as the source, and the trim finishes in roughly the time it takes to copy the bytes.

npm CI license

Why this exists

Nearly every popular React Native trimming library bundles ffmpeg. That carries real costs:

  • Size. ffmpeg adds tens of megabytes of native binary to your app.
  • Quality. ffmpeg-based trimmers re-encode the clip, so every trim is a fresh round of lossy compression (generational quality loss).
  • Speed and battery. Re-encoding is CPU-bound and slow, and it drains battery, especially on longer clips.
  • Maintenance. ffmpeg-kit, the foundation most of these libraries were built on, was retired in 2025. Libraries sitting on it are now on borrowed time.

Every mobile OS already ships a native, hardware-friendly way to copy encoded media samples from one container into another without touching the pixels. This package uses exactly that. The result:

| | ffmpeg-based trimmers | react-native-lossless-trim | | --- | --- | --- | | Re-encode | Yes (lossy) | No (lossless) | | Added binary size | Tens of MB | ~0 | | Speed | CPU-bound, slow | Near-instant (byte copy) | | Battery | Heavy | Light | | Depends on retired ffmpeg-kit | Often | Never |

Installation

npx expo install react-native-lossless-trim

This is an Expo module. It needs a custom dev client or a release build (npx expo prebuild then run, or an EAS build). It does not run in Expo Go, because Expo Go cannot load custom native code. Bare React Native projects with autolinking work too.

New Architecture (Fabric) is fully supported.

Usage

import { trimAsync, isAvailable } from 'react-native-lossless-trim';

// Keep the range from 1.0s to 5.0s of the source, losslessly.
const { uri } = await trimAsync(sourceUri, { startMs: 1000, endMs: 5000 });

// `uri` is a file:// path in the app cache directory. You own the file: move,
// upload, or delete it when you are done.

Guard against builds where the native module is missing (for example Expo Go):

if (!isAvailable()) {
  // fall back to another engine, or tell the user to use a dev/release build
}

Errors are typed:

import { trimAsync, TrimError } from 'react-native-lossless-trim';

try {
  const { uri } = await trimAsync(sourceUri, { startMs, endMs });
} catch (e) {
  if (e instanceof TrimError) {
    // e.code: 'ERR_INVALID_URI' | 'ERR_INVALID_RANGE' | 'ERR_UNAVAILABLE' | 'ERR_TRIM'
  }
}

Platform differences (read this)

Because passthrough copies whole encoded frames and never re-encodes, the two platforms differ at the start of the cut:

  • iOS is frame-precise. AVAssetExportSession writes an edit list, so the output begins exactly at startMs.
  • Android starts at the nearest keyframe at or before startMs. MediaMuxer has no edit-list API, and a video stream can only begin on a sync (key) frame without re-encoding. So the Android output may include up to about one GOP (group of pictures, commonly 1 to 2 seconds) of extra footage before your requested start.

This is the fundamental trade of a no-re-encode trim: lossless and fast, but keyframe-bound on Android. A frame-exact cut would require re-encoding the leading GOP, which this library deliberately does not do. If you need frame-exact output on Android, an ffmpeg-based re-encode is the tool for that job, at the cost of quality, size, and speed.

The end of the cut is precise on both platforms.

The Android A/V-sync fix

A naive Android passthrough desyncs audio and video. Video snaps back to its keyframe while audio starts at the exact cut, so with absolute timestamps the two tracks begin at different content times and the clip plays audio ahead of the picture.

This library fixes it: it picks the video keyframe as a single shared origin and rebases every sample of both tracks by that one value, which preserves the real audio-to-video offset. Samples that rebase before the origin are dropped (MediaMuxer rejects negative presentation times), and the source rotation is carried through as the muxer's orientation hint. The timing math lives in a pure, unit-tested TrimTimeline (see the JVM regression tests) so the invariant stays locked in.

API

trimAsync(uri, options): Promise<TrimResult>

| Param | Type | Description | | --- | --- | --- | | uri | string | file:// (both platforms) or content:// (Android) uri of the source video. | | options.startMs | number | Start of the kept range, in milliseconds. | | options.endMs | number | End of the kept range, in milliseconds. Must be > startMs. |

Resolves to { uri }, a file:// uri to the trimmed clip in the cache directory. Rejects with a TrimError.

isAvailable(): boolean

Whether the native module is present in this build.

TrimError

An Error subclass with a stable code field of type TrimErrorCode.

Example app

A runnable demo lives in example/: pick a video, set a range, trim it, and watch the source and the (near-instant) result side by side.

cd example
npm install
npx expo prebuild
npx expo run:ios      # or: npx expo run:android

Development

npm install
npm run build   # compile the TypeScript API
npm run lint    # eslint
npm test        # jest (JS API surface)

The Android A/V-sync math is unit-tested on the JVM via the example app's Gradle:

cd example/android
./gradlew :react-native-lossless-trim:testDebugUnitTest

License

MIT (c) Iyegere Success Karboloo. See LICENSE.