All work

On-device media for React Native · Independent builder

expo-yt-dlp

yt-dlp Inside React Native Apps

An Expo module that runs the real yt-dlp on an Android phone: a bundled, stripped-down Python runtime behind a typed JS API for info, formats, downloads, subtitles and playlists. No server.

Stack
TypeScriptKotlinPythonExpoAndroid
expo-yt-dlp · androidon-deviceconst task = YtDlp.download(url, {maxHeight: 720, onProgress })137 · 1080p · avc1136 · 720p · avc1140 · m4a · aacvideo0%audio0%mergegetInfo(url)…python 3.14 · real yt-dlp · no server in between

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

  1. 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 normal python to run under.
  2. A process per call - Every call is its own child process, so cancelling means killing that process, and partial files are cleaned up.
  3. Binaries named lib*.so - Android only lets an app execute files from its native library directory, and only extracts lib*.so files there. The launcher, QuickJS, ffmpeg and the stdlib C extensions are all packaged that way.
  4. Precompiled Python - The stdlib and yt-dlp are precompiled to .pyc, zipped into the APK, and unpacked on first use.
  5. 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 - estimateDownload reports 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 AbortSignal support.
  • 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, MediaMuxer merging.
  • 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.

Next project

Droidwright

Playwright-Style WebView Automation for Android