On-device media for React Native · Independent builder
expo-yt-dlp
The real yt-dlp, running inside an Expo / React Native app on the phone itself. Video info, formats, downloads, subtitles, thumbnails and playlists, with no server in between.
01The Problem
yt-dlp is the best tool there is for getting media and metadata out of video sites, and it is a Python program. Mobile apps that want it usually end up running it on a server and proxying everything through, which means a backend to host, bandwidth to pay for twice, and a single point that every user's downloads depend on.
02The Solution
expo-yt-dlp bundles its own stripped-down Python runtime and a copy of yt-dlp cut down to the sites you need, and wraps them in a typed JavaScript API. Anything the wrapper doesn't cover is one exec([...]) away, because it's the real yt-dlp underneath.
import * as YtDlp from 'expo-yt-dlp'
const info = await YtDlp.getInfo(url)
const task = YtDlp.download(url, { maxHeight: 720, onProgress })
const { files } = await task.promise
03How It Works
- A real Python on Android - python.org's official Android build of CPython ships only as an embeddable
libpython. A ten-line launcher linked against it gives yt-dlp a normalpythonto run under. - A process per call - Every call is its own child process, so cancelling means killing that process, and partial files are cleaned up.
- Binaries named
lib*.so- Android only lets an app execute files from its native library directory, and only extractslib*.sofiles there. The launcher, QuickJS, ffmpeg and the stdlib C extensions are all packaged that way. - Precompiled Python - The stdlib and yt-dlp are precompiled to
.pyc, zipped into the APK, and unpacked on first use. - Merging without ffmpeg - The best qualities come as separate video and audio streams. The driver picks an H.264 + AAC pair and Kotlin merges it with Android's
MediaMuxer: a stream copy, not a re-encode.
04Key Features
- Lookups without downloading - Full info, every format with sizes, subtitles, playlists and YouTube search.
- Size estimates -
estimateDownloadreports exactly what a download would fetch before fetching it. - Downloads - Video, audio-only, subtitles, thumbnails, whole playlists, and batches with a concurrency limit.
- Progress and cancellation - Per-stream progress events (percent, speed, ETA, merging, retries) and
AbortSignalsupport. - Typed errors - Distinct codes for yt-dlp failures, cancellations, and unsupported devices.
- Sites in this build - YouTube, Facebook, Instagram, plus yt-dlp's generic extractor, in about 11 MB added to a single-ABI APK.
05Limits, Stated Plainly
- Android only for now: iOS apps can't spawn processes, so iOS needs an in-process interpreter.
- 64-bit devices only, because python.org doesn't build 32-bit Android.
- Downloads run while the app's process is alive; long background downloads need a foreground service.
- App stores reject apps that download from YouTube and similar sites without the rights holder's authorization.
06Technical Stack
- TypeScript - The public API and option handling.
- Kotlin - The Expo module: process management, progress events,
MediaMuxermerging. - Python 3.14 - CPython's Android build, running yt-dlp and the driver.
- QuickJS-ng - For YouTube's JavaScript challenges.
- Expo config plugin - Turns on native library extraction so the bundled binaries can run.
07Links
Next project
Droidwright
Playwright-Style WebView Automation for Android