← BACK

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.

TypeScript ⭐ 0

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.

Using with munim-ffmpeg

ytdlp-react-native can be used together with munim-ffmpeg for applications that need native FFmpeg processing.

ytdlp-react-native is responsible for media extraction and downloading, while munim-ffmpeg can be used for merging, muxing, transcoding, subtitle processing, and other FFmpeg operations.

Recommended architecture

When downloading separate video and audio streams, you can download them independently with yt-dlp and then pass the resulting local file paths to munim-ffmpeg:

URL
 │
 ▼
ytdlp-react-native
 │
 ├── bestvideo → video file
 │
 └── bestaudio → audio file
                    │
                    ▼
              munim-ffmpeg
                    │
                    ▼
              final media file
                    │
                    ▼
             Media Library / Gallery

For example, first select the desired video and audio format IDs from extractInfo() / getFormats() and download them separately:

const videoTask = await YtDlp.download({
  url,
  format: videoFormatId,
  output: {
    directory: "downloads",
    filename: "video.%(ext)s",
  },
});

const audioTask = await YtDlp.download({
  url,
  format: audioFormatId,
  output: {
    directory: "downloads",
    filename: "audio.%(ext)s",
  },
});

After both downloads complete, use munim-ffmpeg to mux the streams without re-encoding:

video + audio
     │
     ▼
FFmpeg: -c copy
     │
     ▼
final.mp4

Using stream copy (-c copy) avoids unnecessary transcoding, so compatible video and audio streams can be merged quickly without reducing their original quality.

Why use both packages?

Package Responsibility
ytdlp-react-native Extraction, format selection, downloading, cookies, playlists, download progress
munim-ffmpeg Muxing, merging, transcoding, subtitles, FFprobe and media processing

This approach is especially useful when your application already includes munim-ffmpeg. In that case, ytdlp-react-native does not need to invoke an FFmpeg executable for the application's post-processing pipeline.

Note: The ffmpeg.location option remains available for standalone yt-dlp workflows where yt-dlp itself needs to invoke an FFmpeg executable, for example when using bestvideo+bestaudio directly. When using munim-ffmpeg, you can instead download the streams separately and perform the merge through its native FFmpeg API.

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:

  • progress → DownloadProgress (percent, downloadedBytes, totalBytes,
    speedBytesPerSecond, etaSeconds, filename, phase)
  • state → { taskId, status }
  • completed → DownloadResult (taskId, path, filename, size)
  • error → YtDlpError

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.