Skip to content

Repository files navigation

ytdlp-react-native

A native Android Expo module providing a modern TypeScript API around yt-dlp. It embeds the Python runtime, yt-dlp and all site extractors through the yt-dlp-android library — no Python, yt-dlp, Chaquopy, FFmpeg or Termux setup is required in your app.

import YtDlp from 'ytdlp-react-native';

const info = await YtDlp.extractInfo('https://www.youtube.com/watch?v=...');
console.log(info.title);
console.log(info.formats);

const task = await YtDlp.download({
  url: 'https://www.youtube.com/watch?v=...',
  format: 'best', // prefer a single progressive stream (no FFmpeg merge)
  output: { directory: 'Movies' },
});

task.addListener('progress', (progress) => {
  console.log(progress.percent);
});

await task.cancel();

Requirements

  • Platform: Android only. Importing on iOS/web throws YtDlpError with code UNSUPPORTED_PLATFORM.

  • Expo SDK: 57 (tested against Expo 57 / React Native 0.86).

  • Minimum Android: API 24.

  • Native build required. This is a custom native module. It does not work inside standard Expo Go — Expo Go cannot load arbitrary custom native modules. Use a development build:

    npx expo prebuild
    npx expo run:android

Installation

npx expo install ytdlp-react-native

If expo install does not resolve the package (e.g. before it is indexed), fall back to:

npm install ytdlp-react-native

Usage

Extract media information

import YtDlp from 'ytdlp-react-native';

const info = await YtDlp.extractInfo(url);
console.log(info.title);   // string | undefined
console.log(info.duration);
console.log(info.thumbnail);
console.log(info.formats);

extractInfo never downloads media. Fields that a site does not provide are undefined — no field is guaranteed for every site.

List formats

const formats = await YtDlp.getFormats(url);

getFormats reuses the extraction result, so it does not extract twice.

Download

const task = await YtDlp.download({
  url,
  format: 'best', // or 'bestaudio', 'best[height<=720]', etc.
  output: {
    directory: 'Movies',
    filename: '%(title)s.%(ext)s',
  },
});

task.addListener('progress', (progress) => {
  console.log(progress.percent, progress.speedBytesPerSecond, progress.etaSeconds);
});

task.addListener('completed', (result) => {
  console.log(result.path);
});

task.addListener('error', (error) => {
  console.log(error.code, error.message);
});

download resolves as soon as the task is registered; progress and the final result arrive through the task's listeners. Multiple downloads can run at the same time — every event carries a taskId.

Cancel

await task.cancel();

Cancellation calls the native cancellation path and aborts the underlying yt-dlp download. To cancel a task after your JS task object is gone:

await YtDlp.cancel(taskId);

Version

const version = await YtDlp.getVersion();
// { ytDlp: '2026.xx.xx', library: '1.0.0' }

The embedded yt-dlp version and the npm package version are independent.

Supported download options

Option Description
url Required. Any URL supported by yt-dlp.
format Raw yt-dlp format expression, e.g. best, bestaudio, best[height<=720].
output.directory Subdirectory under the app's yt-dlp folder. Sanitized.
output.filename yt-dlp output template, e.g. %(title)s.%(ext)s. Sanitized.
headers Extra HTTP headers, e.g. { Referer: '...' }.
userAgent Custom User-Agent.
referer Custom Referer.
proxy Proxy URL.
cookies.path Path to a Netscape-format cookies file.
playlist.enabled Default false — a playlist URL downloads only the first item unless enabled.
playlist.start / playlist.end Playlist item range (1-based).
subtitles.enabled Write subtitles.
subtitles.languages e.g. ['en', 'bn'].
subtitles.autoGenerated Also write auto-generated subtitles.
network.timeout Socket timeout in seconds.
network.retries Number of retries.
ffmpeg.location Absolute path to an ffmpeg executable (or a directory containing one) already on the device. Enables merging and other FFmpeg-based post-processing. Not bundled — you must supply it.

FFmpeg support

The bundled yt-dlp-android build ships no FFmpeg, and this package does not bundle one either — yt-dlp merges separate streams by spawning a real ffmpeg executable, so an in-process JNI wrapper (e.g. FFmpegKit) is not enough. If you have an ffmpeg binary on the device (e.g. extracted into your app's files directory), pass its path and bestvideo+bestaudio works:

const task = await YtDlp.download({
  url,
  format: 'bestvideo+bestaudio',
  merge: true,
  ffmpeg: {
    location: '/data/user/0/com.example.app/files/ffmpeg/ffmpeg',
  },
});

yt-dlp accepts either the binary path or the directory that contains the ffmpeg executable. With ffmpeg.location set, the following features are handed to yt-dlp instead of being rejected:

  • merge: true (e.g. bestvideo+bestaudio)
  • audio extraction / re-encoding
  • metadata embedding
  • thumbnail embedding

If the location does not exist, the download fails immediately with PROCESSING_FAILED. Without ffmpeg.location, the features above are still rejected up front with a clear error rather than silently ignored, and you should request a single stream (best, bestaudio, best[height<=720], etc.) that needs no post-processing.

Instagram, TikTok and other social sites

yt-dlp supports Instagram (posts, Reels, many stories) and TikTok, among hundreds of other sites. Without FFmpeg you must request a single progressive stream:

format: 'best'               // preferred
// or
format: 'best[height<=1080]'
format: 'bestaudio'

Many Instagram Reels and TikTok videos already provide combined progressive MP4 streams, so downloads often work without extra processing. If the only high-quality options are separate video + audio streams, provide an ffmpeg binary via ffmpeg.location (see above) or the download will fail with PROCESSING_FAILED.

Cookies tip: Instagram almost always requires a valid logged-in session (Netscape cookies file via cookies.path). TikTok sometimes needs them too for full reliability.

Download task

A DownloadTask exposes:

  • id: string
  • cancel(): Promise<void>
  • pause(): Promise<boolean>
  • resume(): Promise<boolean>
  • getStatus(): Promise<DownloadStatus>
  • getProgress(): Promise<DownloadProgress | null>
  • addListener(event, listener): Subscription

Statuses: queued | extracting | downloading | processing | paused | completed | cancelled | failed.

Events:

  • progressDownloadProgress (percent, downloadedBytes, totalBytes, speedBytesPerSecond, etaSeconds, filename, phase)
  • state{ taskId, status }
  • completedDownloadResult (taskId, path, filename, size)
  • errorYtDlpError

Progress events are throttled to ~200 ms and numeric fields are undefined when the value is unknown (never NaN).

Pause & resume

const task = await YtDlp.download({ url, format: 'bestvideo+bestaudio', ffmpeg: { location } });

await task.pause();   // true — yt-dlp aborts on the next progress tick
console.log(await task.getStatus()); // 'paused'

await task.resume();  // true — same options re-run; continues from the .part file

YtDlp.pause(taskId) and YtDlp.resume(taskId) work the same way when you only have a task id (e.g. after app re-creation).

How it works:

  • pause() cooperatively aborts the download from the progress hook. yt-dlp leaves its .part file on disk, and the task stays registered with status paused (so a paused task still exists; cancel it if you're done).
  • resume() re-runs the exact same download. yt-dlp continues by default (continue): byte-range where the source server supports it, fragment-based resumes (DASH/HLS) via the .ytdl sidecar. If a source does not support continuing, yt-dlp restarts that file.
  • Both return false when the action is not applicable (unknown task, not paused, already finished). Cancelling a paused task finalizes it immediately.
  • No network is consumed while paused, but the pause takes effect on the next yt-dlp progress tick (~200 ms). Pausing does not cover the download's extraction phase.

pause()/resume() are process-local and do not survive native restarts (see "Limitations").

Background downloads

While at least one download is running, the module promotes the app with a native Android foreground service (dataSync type), so downloads keep going when the app is backgrounded, the screen is off, or the app is swiped away. No opt-in needed — starting a download shows an ongoing notification with live progress and a Cancel action (cancels all active downloads); tapping the notification reopens the app. When the last download finishes (or fails / is cancelled), the service stops and the notification goes away.

const task = await YtDlp.download({ url });
// app backgrounded here — the download continues
task.addListener('progress', (p) => console.log(p.percent));

What it does and does not cover:

  • Survives: backgrounding, screen off, task swipe-away.
  • Does not survive: the process being killed by the system, device reboot, or a native restart. Tasks are process-local: after a restart, re-issue downloads (and consider pause()/resume() semantics for partial files left on disk).
  • The service holds a partial wake lock while active, so screen-off downloads are not stalled by CPU sleep.
  • On Android 13+ the host app should request the POST_NOTIFICATIONS runtime permission, otherwise the progress notification is suppressed (the download still runs).
  • On Android 15+, the system may time-box dataSync foreground services (around 6 hours); multi-hour downloads can be stopped by the OS.
  • Cancellation from the notification stops every active download; per-task control stays in the app UI via task.cancel().

react-native-continued-task (see below) remains a complementary option if you also want WorkManager-backed scheduling or system progress UI beyond this built-in service.

Errors

All failures normalize to YtDlpError with a code:

INVALID_URL, EXTRACTION_FAILED, DOWNLOAD_FAILED, CANCELLED, FORMAT_UNAVAILABLE, NETWORK_ERROR, AUTHENTICATION_REQUIRED, GEO_RESTRICTED, PRIVATE_CONTENT, AGE_RESTRICTED, PROCESSING_FAILED, STORAGE_ERROR, INIT_FAILED, UNSUPPORTED_PLATFORM, UNKNOWN.

import { YtDlpError } from 'ytdlp-react-native';

try {
  await YtDlp.extractInfo(url);
} catch (error) {
  if (error instanceof YtDlpError) {
    console.log(error.code, error.message);
  }
}

Raw native stack traces are never surfaced to the user.

Storage

Files are written to app-specific external storage:

Android/data/<your-package>/files/yt-dlp/<output.directory>/...

Filenames and directory segments are sanitized against illegal characters, path traversal, excessive length and empty names. This avoids dangerous permissions like MANAGE_EXTERNAL_STORAGE. The returned DownloadResult.path is an absolute path inside your app's own storage.

Limitations

  • Android only.
  • FFmpeg is not bundled. Merging separate video + audio streams and other post-processing only work when you supply an ffmpeg executable via ffmpeg.location (see "FFmpeg support"). Without it, those features are rejected with PROCESSING_FAILED.
  • Background execution is best-effort within a live process. Active downloads run under a dataSync foreground service (see "Background downloads"), but downloads do not survive the process being killed or the device rebooting. Per-process pause/resume does not survive a native restart. Persisting task IDs lets you re-issue cancellation later.
  • yt-dlp site support changes frequently. Not every website works forever, and not every site provides every field.
  • This package does not bundle or provide a way to update the embedded yt-dlp at runtime.
  • Playlists are opt-in via playlist.enabled to avoid accidental bulk downloads.

Recommended production stack

ytdlp-react-native focuses on reliable extraction and downloading of single streams. For a complete video-downloader experience you will usually combine it with three complementary libraries:

Concern Package Link
Long-running background downloads with system progress UI react-native-continued-task GitHub
Merge video+audio, re-encode, burn subtitles, etc. munim-ffmpeg GitHub
Save finished files to the public Media Library / Gallery expo-media-library Expo docs

Suggested flow

  1. Start a continued background task with react-native-continued-task (shows Live Activity on iOS 26+ / foreground-service notification on Android).
  2. Download with ytdlp-react-native using a single-stream format (best / bestaudio). Report progress back to the continued task.
  3. (Optional) If you need to merge separate streams or perform other post-processing, use munim-ffmpeg (in-process FFmpegKit). Note that FFmpegKit-style packages run FFmpeg in-process and cannot provide the executable ytdlp-react-native's ffmpeg.location option needs — that option must point to a real ffmpeg binary on disk.
  4. Move the final file into the user’s public gallery with expo-media-library.

All three packages (plus this one) require a development build — they do not work inside Expo Go.

Note: basic background continuity (foreground service + progress notification + cancel) is built into ytdlp-react-native (see "Background downloads"). Reach for react-native-continued-task when you additionally want WorkManager-backed scheduling or richer system UI.

Legal & responsible use

ytdlp-react-native is a technical wrapper around yt-dlp. It does not circumvent DRM (Widevine, FairPlay, PlayReady, ...), bypass authentication, or access private or unauthorized content — content that requires DRM or authentication will fail with an error.

You are responsible for complying with:

  • website terms of service
  • copyright law and content licenses
  • authentication rules
  • platform policies

Do not use this library to download content you do not have the right to download.

Third-party licenses

Component License
ytdlp-react-native (this package) MIT
yt-dlp-android (Maven dev.ffmpegkit-maintained:yt-dlp-android) MIT
yt-dlp Unlicense
Chaquopy BSD-style (per the embedded distribution)

Re-verify third-party licenses at release time.

About

A native Android Expo module providing a modern TypeScript API around yt-dlp. It embeds the Python runtime, yt-dlp and all site extractors through the yt-dlp-android library — no Python, yt-dlp, Chaquopy, FFmpeg or Termux setup is required in your app.

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages