Skip to content

LucaL1fe/react-native-transparent-video-player

Repository files navigation

react-native-transparent-video-player

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.

Quick start

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

2. Install:

npx expo install react-native-transparent-video-player @shopify/react-native-skia react-native-reanimated

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

The pack-transparent-video CLI

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 .mov with alpha channel (recommended — every editor/motion tool exports this)
  • VP9 / VP8 .webm with alpha
  • .mov/.mkv/.avi with 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").

Why this beats GIF and animated WebP

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.

How it works

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→SurfaceTexturesamplerExternalOES 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.

API

<TransparentVideo />

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.

License

MIT — Android GL/renderer code derived from alpha-movie (Apache-2.0) and react-native-transparent-video (MIT), see THIRD-PARTY-NOTICES.md.

About

Transparent (alpha) video for React Native — alpha-packed H.264 MP4s that play on every hardware decoder. Skia-rendered on iOS, native ExoPlayer+OpenGL on Android. Includes a cross-platform CLI converter.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors