Screen_Recording.mp4
⭐ If this project helps you, a star would make my day — it keeps the project going and helps others find it!
Transparent (alpha-channel) video for React Native — as a plain H.264 MP4 that plays on every iOS and Android hardware decoder.
No codec-alpha support needed, no giant animated WebP/GIF files, no per-frame CPU decoding. One <TransparentVideo> component plays an "alpha-packed" MP4 and recombines color + alpha on the GPU — via a Skia shader on iOS and a native ExoPlayer + OpenGL view on Android.
1. Pack your video — any video with an alpha channel (e.g. ProRes 4444), any resolution (ffmpeg required, see below):
npx pack-transparent-video hero-4444.mov
# → hero-packed.mp42. Install:
npx expo install react-native-transparent-video-player @shopify/react-native-skia react-native-reanimatedNative rebuild required: the Android side is a native Expo module, so after installing it rebuild your development build —
npx expo prebuild+ a new dev client / EAS build. Not available in Expo Go (neither is Skia). iOS adds no native code from this package.
3. Play it:
import { TransparentVideo } from 'react-native-transparent-video-player';
<TransparentVideo
source={require('./assets/hero-packed.mp4')}
width={300}
height={300}
/>That's it — the video renders with real transparency over whatever is behind it.
Export tip: deliver ProRes 4444 with the alpha channel enabled from your editor or motion tool, at display resolution (e.g. 900×900), 24–30 fps. Every professional tool can do this.
npx pack-transparent-video <input-with-alpha> [more inputs ...] [options]| Option | Default | What it does |
|---|---|---|
--fps <n> |
24 |
Output frame rate. 24 looks perfectly smooth for animations; 30+ only adds file size. |
--scale <percent> |
100 |
Resize to a percentage of the source size (e.g. --scale 50 halves width and height). |
--quality <0-100> |
75 |
Output quality in percent. 75 ≈ visually lossless; lower = smaller file. |
-o, --out-dir <dir> |
cwd | Where to write <name>-packed.mp4. |
--width <px> |
— | Alternative to --scale: resize to an exact pixel width (keeps aspect ratio). |
--size <WxH> |
— | Force an exact output frame (e.g. --size 900x900) from any aspect ratio: content is scaled to fit and the remainder is padded with transparent pixels — never stretched. |
--crf <n> |
— | Advanced: raw x264 CRF value, overrides --quality. |
Supported inputs — any video with a real alpha channel, at any resolution (odd dimensions are handled automatically):
- ProRes 4444
.movwith alpha channel (recommended — every editor/motion tool exports this) - VP9 / VP8
.webmwith alpha .mov/.mkv/.aviwith an alpha-capable codec: PNG, QuickTime Animation (QTRLE), FFV1, Ut Video- Files without alpha (plain H.264/HEVC exports) are rejected with a clear message — note that HEVC-with-alpha is not supported; use ProRes 4444 or VP9 WebM instead.
Requirements: Node.js and ffmpeg in your PATH — works on Windows, macOS and Linux
(macOS: brew install ffmpeg · Windows: winget install ffmpeg · Linux: sudo apt install ffmpeg).
Resolution rule: the output height must be divisible by 8 (the packed file by 16). Android hardware decoders align video buffers to 16 rows — a non-aligned packed video gets a crop transform that shifts the alpha mask ~1px against the color. The CLI enforces this and tells you the nearest valid resolution (e.g. --width 900 → "use --width 896 or --width 904").
Real measurement — the same 900×900 transparent character animation, exported three ways:
| Format | Size |
|---|---|
| Animated WebP | 11 MB |
| AVIF | 5.2 MB |
| Alpha-packed MP4 (this) | 1.1 MB ✅ |
Category by category:
| Alpha-packed MP4 (this package) | GIF | Animated WebP | APNG | Lottie | HEVC/VP9 codec-alpha | |
|---|---|---|---|---|---|---|
| File size | ✅ Inter-frame H.264 compression (10× smaller than WebP above) | ❌ Huge | ❌ Mostly per-frame, large | ❌ Largest | ✅ Tiny — but vector-only | ✅ Small |
| Color + alpha | ✅ 24-bit color, 8-bit alpha | ❌ 256 colors, 1-bit alpha | ✅ | ✅ | ✅ | ✅ |
| Decode cost / battery | ✅ Hardware video decoder + GPU shader | ❌ CPU | ❌ CPU, expensive at large sizes | ❌ CPU | ⚠ CPU (JS/native render) | ✅ Hardware (when supported) |
| Memory | ✅ Streams frames | ❌ Frame caches | ❌ Frame caches | ❌ Frame caches | ✅ | ✅ |
| Device compatibility | ✅ Plain H.264 — every iOS/Android hardware decoder ever shipped | ✅ | ✅ | ✅ | ✅ | ❌ Codec/vendor-dependent (HEVC-alpha ≈ Apple-only, VP9-alpha ≈ no iOS hardware) |
| Rendered from real footage / 3D / hand animation | ✅ Any video source | ✅ | ✅ | ✅ | ❌ Vector animations only | ✅ |
| Playback control | ✅ paused accepts a Reanimated SharedValue |
❌ | ❌ | ❌ | ✅ | ✅ |
Honest trade-offs: you add @shopify/react-native-skia + react-native-reanimated as dependencies, your asset needs a one-time packing step, and it's not a drop-in <Image> replacement.
Platform split (v0.3.0+): iOS renders through Skia (a RuntimeEffect shader on the GPU). Android uses a native ExoPlayer + OpenGL view — Skia's video decoding is bypassed entirely there, because its GPU frame import only understands RGBA buffers while Android hardware decoders emit vendor YUV formats (frames render black on many devices, e.g. recent Samsung flagships). The native view uses the same decoder→SurfaceTexture→samplerExternalOES pipeline every Android video player relies on, so it works on every device and GPU. No minSdkVersion override is needed (module minSdk 24).
Android consumers need expo-modules-core — already present in every Expo app; bare React Native apps can add it with npx install-expo-modules.
The packed MP4 is a completely normal H.264 video, twice as tall as your animation:
┌──────────────┐
│ color (RGB) │ ← premultiplied color, top half
├──────────────┤
│ alpha matte │ ← alpha channel as grayscale, bottom half
└──────────────┘
At render time a GPU shader samples the color from the top half and the alpha from the bottom half of the same frame and recombines them — a Skia RuntimeEffect on iOS, a samplerExternalOES GLSL shader on Android:
half4 main(float2 xy) {
half3 rgb = video.eval(xy).rgb;
half a = video.eval(float2(xy.x, xy.y + halfH)).r;
return half4(rgb, a);
}Because the transport is plain yuv420p H.264, the OS hardware decoder does all the heavy lifting — transparency support is never the codec's problem. The color is premultiplied by alpha during packing so bilinear sampling never produces dark edge fringes.
| Prop | Type | Description |
|---|---|---|
source |
number | string |
require(...) of a packed MP4, or a URI string. Asset modules resolve via expo-asset when installed, otherwise via Image.resolveAssetSource (bare RN). |
width |
number |
Display width. |
height |
number |
Display height (= half the packed video's pixel height). |
loop |
boolean |
Loop playback (default true). Set false for one-shot animations. |
paused |
boolean | SharedValue<boolean> |
Pause playback; accepts a Reanimated shared value for UI-thread control. |
onEnd |
() => void |
Fires when a non-looping video finishes. Currently Android-only (native player event). |
onFirstFrame |
() => void |
Fires when the view first has visible content: once per mount on iOS, once per source prepare on Android. |
playKey |
number |
Change this value (e.g. increment a counter) to restart the current source from frame 0 — the way to replay a finished one-shot clip without remounting. Changing source restarts playback by itself, and the view keeps showing the previous video's last frame until the new source's first frame is decoded, so runtime source switches are seamless. |
style |
StyleProp<ViewStyle> |
Extra styles for the view. |
MIT — Android GL/renderer code derived from alpha-movie (Apache-2.0) and react-native-transparent-video (MIT), see THIRD-PARTY-NOTICES.md.