ytdlp-react-native 1.0.0 → 1.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -1
- package/README.md +422 -267
- package/android/src/main/AndroidManifest.xml +20 -1
- package/android/src/main/java/expo/modules/ytdlp/ExpoYtDlpModule.kt +8 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpDownloadManager.kt +177 -16
- package/android/src/main/java/expo/modules/ytdlp/YtDlpEngine.kt +35 -4
- package/android/src/main/java/expo/modules/ytdlp/YtDlpForegroundService.kt +241 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpTask.kt +44 -2
- package/build/ExpoYtDlpModule.d.ts +2 -0
- package/build/ExpoYtDlpModule.d.ts.map +1 -1
- package/build/ExpoYtDlpModule.js.map +1 -1
- package/build/YtDlp.d.ts +10 -0
- package/build/YtDlp.d.ts.map +1 -1
- package/build/YtDlp.js +22 -77
- package/build/YtDlp.js.map +1 -1
- package/build/constants.d.ts +1 -4
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +2 -1
- package/build/constants.js.map +1 -1
- package/build/downloadTask.d.ts +2 -0
- package/build/downloadTask.d.ts.map +1 -1
- package/build/downloadTask.js +7 -0
- package/build/downloadTask.js.map +1 -1
- package/build/errors.js +3 -3
- package/build/errors.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -1
- package/build/index.js.map +1 -1
- package/build/mappers.js +3 -2
- package/build/mappers.js.map +1 -1
- package/build/serializers.d.ts +5 -0
- package/build/serializers.d.ts.map +1 -0
- package/build/serializers.js +96 -0
- package/build/serializers.js.map +1 -0
- package/build/types.d.ts +39 -1
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/package.json +7 -5
- package/src/ExpoYtDlpModule.ts +2 -0
- package/src/YtDlp.ts +21 -57
- package/src/__tests__/errors.test.ts +117 -0
- package/src/__tests__/mappers.test.ts +167 -0
- package/src/__tests__/serializers.test.ts +72 -0
- package/src/constants.ts +3 -1
- package/src/downloadTask.ts +9 -0
- package/src/errors.ts +3 -3
- package/src/index.ts +3 -1
- package/src/mappers.ts +3 -2
- package/src/serializers.ts +78 -0
- package/src/types.ts +47 -1
package/README.md
CHANGED
|
@@ -1,267 +1,422 @@
|
|
|
1
|
-
# ytdlp-react-native
|
|
2
|
-
|
|
3
|
-
A native **Android** Expo module providing a modern TypeScript API around
|
|
4
|
-
[yt-dlp](https://github.com/yt-dlp/yt-dlp). It embeds the Python runtime,
|
|
5
|
-
yt-dlp and all site extractors through the
|
|
6
|
-
[yt-dlp-android](https://github.com/ffmpegkit-maintained/yt-dlp-android)
|
|
7
|
-
library — no Python, yt-dlp, Chaquopy, FFmpeg or Termux setup is required in
|
|
8
|
-
your app.
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
import YtDlp from 'ytdlp-react-native';
|
|
12
|
-
|
|
13
|
-
const info = await YtDlp.extractInfo('https://www.youtube.com/watch?v=...');
|
|
14
|
-
console.log(info.title);
|
|
15
|
-
console.log(info.formats);
|
|
16
|
-
|
|
17
|
-
const task = await YtDlp.download({
|
|
18
|
-
url: 'https://www.youtube.com/watch?v=...',
|
|
19
|
-
format: '
|
|
20
|
-
output: { directory: 'Movies' },
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
task.addListener('progress', (progress) => {
|
|
24
|
-
console.log(progress.percent);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
await task.cancel();
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Requirements
|
|
31
|
-
|
|
32
|
-
- **Platform:** Android only. Importing on iOS/web throws
|
|
33
|
-
`YtDlpError` with code `UNSUPPORTED_PLATFORM`.
|
|
34
|
-
- **Expo SDK:** 57 (tested against Expo 57 / React Native 0.86).
|
|
35
|
-
- **Minimum Android:** API 24.
|
|
36
|
-
- **Native build required.** This is a custom native module. It does **not**
|
|
37
|
-
work inside standard Expo Go — Expo Go cannot load arbitrary custom native
|
|
38
|
-
modules. Use a development build:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx expo prebuild
|
|
42
|
-
npx expo run:android
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Installation
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npx expo install ytdlp-react-native
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
If `expo install` does not resolve the package (e.g. before it is indexed),
|
|
52
|
-
fall back to:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm install ytdlp-react-native
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Usage
|
|
59
|
-
|
|
60
|
-
### Extract media information
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
import YtDlp from 'ytdlp-react-native';
|
|
64
|
-
|
|
65
|
-
const info = await YtDlp.extractInfo(url);
|
|
66
|
-
console.log(info.title); // string | undefined
|
|
67
|
-
console.log(info.duration);
|
|
68
|
-
console.log(info.thumbnail);
|
|
69
|
-
console.log(info.formats);
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
`extractInfo` never downloads media. Fields that a site does not provide are
|
|
73
|
-
`undefined` — no field is guaranteed for every site.
|
|
74
|
-
|
|
75
|
-
### List formats
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
const formats = await YtDlp.getFormats(url);
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
`getFormats` reuses the extraction result, so it does not extract twice.
|
|
82
|
-
|
|
83
|
-
### Download
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
const task = await YtDlp.download({
|
|
87
|
-
url,
|
|
88
|
-
format: '
|
|
89
|
-
output: {
|
|
90
|
-
directory: 'Movies',
|
|
91
|
-
filename: '%(title)s.%(ext)s',
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
task.addListener('progress', (progress) => {
|
|
96
|
-
console.log(progress.percent, progress.speedBytesPerSecond, progress.etaSeconds);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
task.addListener('completed', (result) => {
|
|
100
|
-
console.log(result.path);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
task.addListener('error', (error) => {
|
|
104
|
-
console.log(error.code, error.message);
|
|
105
|
-
});
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
`download` resolves as soon as the task is registered; progress and the final
|
|
109
|
-
result arrive through the task's listeners. Multiple downloads can run at the
|
|
110
|
-
same time — every event carries a `taskId`.
|
|
111
|
-
|
|
112
|
-
### Cancel
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
await task.cancel();
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Cancellation calls the native cancellation path and aborts the underlying
|
|
119
|
-
yt-dlp download. To cancel a task after your JS task object is gone:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
await YtDlp.cancel(taskId);
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Version
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
const version = await YtDlp.getVersion();
|
|
129
|
-
// { ytDlp: '2026.xx.xx', library: '
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
The embedded yt-dlp version and the npm package version are independent.
|
|
133
|
-
|
|
134
|
-
## Supported download options
|
|
135
|
-
|
|
136
|
-
| Option | Description |
|
|
137
|
-
| --- | --- |
|
|
138
|
-
| `url` | Required. Any URL supported by yt-dlp. |
|
|
139
|
-
| `format` | Raw yt-dlp format expression, e.g. `best`, `bestaudio`, `best[height<=720]`. |
|
|
140
|
-
| `output.directory` | Subdirectory under the app's `yt-dlp` folder. Sanitized. |
|
|
141
|
-
| `output.filename` | yt-dlp output template, e.g. `%(title)s.%(ext)s`. Sanitized. |
|
|
142
|
-
| `headers` | Extra HTTP headers, e.g. `{ Referer: '...' }`. |
|
|
143
|
-
| `userAgent` | Custom `User-Agent`. |
|
|
144
|
-
| `referer` | Custom `Referer`. |
|
|
145
|
-
| `proxy` | Proxy URL. |
|
|
146
|
-
| `cookies.path` | Path to a Netscape-format cookies file. |
|
|
147
|
-
| `playlist.enabled` | Default `false` — a playlist URL downloads only the first item unless enabled. |
|
|
148
|
-
| `playlist.start` / `playlist.end` | Playlist item range (1-based). |
|
|
149
|
-
| `subtitles.enabled` | Write subtitles. |
|
|
150
|
-
| `subtitles.languages` | e.g. `['en', 'bn']`. |
|
|
151
|
-
| `subtitles.autoGenerated` | Also write auto-generated subtitles. |
|
|
152
|
-
| `network.timeout` | Socket timeout in seconds. |
|
|
153
|
-
| `network.retries` | Number of retries. |
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
-
|
|
231
|
-
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1
|
+
# ytdlp-react-native
|
|
2
|
+
|
|
3
|
+
A native **Android** Expo module providing a modern TypeScript API around
|
|
4
|
+
[yt-dlp](https://github.com/yt-dlp/yt-dlp). It embeds the Python runtime,
|
|
5
|
+
yt-dlp and all site extractors through the
|
|
6
|
+
[yt-dlp-android](https://github.com/ffmpegkit-maintained/yt-dlp-android)
|
|
7
|
+
library — no Python, yt-dlp, Chaquopy, FFmpeg or Termux setup is required in
|
|
8
|
+
your app.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import YtDlp from 'ytdlp-react-native';
|
|
12
|
+
|
|
13
|
+
const info = await YtDlp.extractInfo('https://www.youtube.com/watch?v=...');
|
|
14
|
+
console.log(info.title);
|
|
15
|
+
console.log(info.formats);
|
|
16
|
+
|
|
17
|
+
const task = await YtDlp.download({
|
|
18
|
+
url: 'https://www.youtube.com/watch?v=...',
|
|
19
|
+
format: 'best', // prefer a single progressive stream (no FFmpeg merge)
|
|
20
|
+
output: { directory: 'Movies' },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
task.addListener('progress', (progress) => {
|
|
24
|
+
console.log(progress.percent);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await task.cancel();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- **Platform:** Android only. Importing on iOS/web throws
|
|
33
|
+
`YtDlpError` with code `UNSUPPORTED_PLATFORM`.
|
|
34
|
+
- **Expo SDK:** 57 (tested against Expo 57 / React Native 0.86).
|
|
35
|
+
- **Minimum Android:** API 24.
|
|
36
|
+
- **Native build required.** This is a custom native module. It does **not**
|
|
37
|
+
work inside standard Expo Go — Expo Go cannot load arbitrary custom native
|
|
38
|
+
modules. Use a development build:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx expo prebuild
|
|
42
|
+
npx expo run:android
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx expo install ytdlp-react-native
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If `expo install` does not resolve the package (e.g. before it is indexed),
|
|
52
|
+
fall back to:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install ytdlp-react-native
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### Extract media information
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import YtDlp from 'ytdlp-react-native';
|
|
64
|
+
|
|
65
|
+
const info = await YtDlp.extractInfo(url);
|
|
66
|
+
console.log(info.title); // string | undefined
|
|
67
|
+
console.log(info.duration);
|
|
68
|
+
console.log(info.thumbnail);
|
|
69
|
+
console.log(info.formats);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`extractInfo` never downloads media. Fields that a site does not provide are
|
|
73
|
+
`undefined` — no field is guaranteed for every site.
|
|
74
|
+
|
|
75
|
+
### List formats
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const formats = await YtDlp.getFormats(url);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`getFormats` reuses the extraction result, so it does not extract twice.
|
|
82
|
+
|
|
83
|
+
### Download
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const task = await YtDlp.download({
|
|
87
|
+
url,
|
|
88
|
+
format: 'best', // or 'bestaudio', 'best[height<=720]', etc.
|
|
89
|
+
output: {
|
|
90
|
+
directory: 'Movies',
|
|
91
|
+
filename: '%(title)s.%(ext)s',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
task.addListener('progress', (progress) => {
|
|
96
|
+
console.log(progress.percent, progress.speedBytesPerSecond, progress.etaSeconds);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
task.addListener('completed', (result) => {
|
|
100
|
+
console.log(result.path);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
task.addListener('error', (error) => {
|
|
104
|
+
console.log(error.code, error.message);
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`download` resolves as soon as the task is registered; progress and the final
|
|
109
|
+
result arrive through the task's listeners. Multiple downloads can run at the
|
|
110
|
+
same time — every event carries a `taskId`.
|
|
111
|
+
|
|
112
|
+
### Cancel
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
await task.cancel();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Cancellation calls the native cancellation path and aborts the underlying
|
|
119
|
+
yt-dlp download. To cancel a task after your JS task object is gone:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
await YtDlp.cancel(taskId);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Version
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const version = await YtDlp.getVersion();
|
|
129
|
+
// { ytDlp: '2026.xx.xx', library: '1.0.0' }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The embedded yt-dlp version and the npm package version are independent.
|
|
133
|
+
|
|
134
|
+
## Supported download options
|
|
135
|
+
|
|
136
|
+
| Option | Description |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `url` | Required. Any URL supported by yt-dlp. |
|
|
139
|
+
| `format` | Raw yt-dlp format expression, e.g. `best`, `bestaudio`, `best[height<=720]`. |
|
|
140
|
+
| `output.directory` | Subdirectory under the app's `yt-dlp` folder. Sanitized. |
|
|
141
|
+
| `output.filename` | yt-dlp output template, e.g. `%(title)s.%(ext)s`. Sanitized. |
|
|
142
|
+
| `headers` | Extra HTTP headers, e.g. `{ Referer: '...' }`. |
|
|
143
|
+
| `userAgent` | Custom `User-Agent`. |
|
|
144
|
+
| `referer` | Custom `Referer`. |
|
|
145
|
+
| `proxy` | Proxy URL. |
|
|
146
|
+
| `cookies.path` | Path to a Netscape-format cookies file. |
|
|
147
|
+
| `playlist.enabled` | Default `false` — a playlist URL downloads only the first item unless enabled. |
|
|
148
|
+
| `playlist.start` / `playlist.end` | Playlist item range (1-based). |
|
|
149
|
+
| `subtitles.enabled` | Write subtitles. |
|
|
150
|
+
| `subtitles.languages` | e.g. `['en', 'bn']`. |
|
|
151
|
+
| `subtitles.autoGenerated` | Also write auto-generated subtitles. |
|
|
152
|
+
| `network.timeout` | Socket timeout in seconds. |
|
|
153
|
+
| `network.retries` | Number of retries. |
|
|
154
|
+
| `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. |
|
|
155
|
+
|
|
156
|
+
## FFmpeg support
|
|
157
|
+
|
|
158
|
+
The bundled `yt-dlp-android` build ships **no FFmpeg**, and this package does
|
|
159
|
+
not bundle one either — yt-dlp merges separate streams by spawning a real
|
|
160
|
+
`ffmpeg` executable, so an in-process JNI wrapper (e.g. FFmpegKit) is not
|
|
161
|
+
enough. If you have an ffmpeg binary on the device (e.g. extracted into your
|
|
162
|
+
app's files directory), pass its path and `bestvideo+bestaudio` works:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const task = await YtDlp.download({
|
|
166
|
+
url,
|
|
167
|
+
format: 'bestvideo+bestaudio',
|
|
168
|
+
merge: true,
|
|
169
|
+
ffmpeg: {
|
|
170
|
+
location: '/data/user/0/com.example.app/files/ffmpeg/ffmpeg',
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`yt-dlp` accepts either the binary path or the directory that contains the
|
|
176
|
+
`ffmpeg` executable. With `ffmpeg.location` set, the following features are
|
|
177
|
+
handed to yt-dlp instead of being rejected:
|
|
178
|
+
|
|
179
|
+
- `merge: true` (e.g. `bestvideo+bestaudio`)
|
|
180
|
+
- audio extraction / re-encoding
|
|
181
|
+
- metadata embedding
|
|
182
|
+
- thumbnail embedding
|
|
183
|
+
|
|
184
|
+
If the location does not exist, the download fails immediately with
|
|
185
|
+
`PROCESSING_FAILED`. Without `ffmpeg.location`, the features above are still
|
|
186
|
+
rejected up front with a clear error rather than silently ignored, and you
|
|
187
|
+
should request a single stream (`best`, `bestaudio`, `best[height<=720]`,
|
|
188
|
+
etc.) that needs no post-processing.
|
|
189
|
+
|
|
190
|
+
## Instagram, TikTok and other social sites
|
|
191
|
+
|
|
192
|
+
yt-dlp supports Instagram (posts, Reels, many stories) and TikTok, among
|
|
193
|
+
hundreds of other sites. Without FFmpeg you must request a **single
|
|
194
|
+
progressive stream**:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
format: 'best' // preferred
|
|
198
|
+
// or
|
|
199
|
+
format: 'best[height<=1080]'
|
|
200
|
+
format: 'bestaudio'
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Many Instagram Reels and TikTok videos already provide combined progressive
|
|
204
|
+
MP4 streams, so downloads often work without extra processing. If the only
|
|
205
|
+
high-quality options are separate video + audio streams, provide an ffmpeg
|
|
206
|
+
binary via `ffmpeg.location` (see above) or the download will fail with
|
|
207
|
+
`PROCESSING_FAILED`.
|
|
208
|
+
|
|
209
|
+
**Cookies tip:** Instagram almost always requires a valid logged-in session
|
|
210
|
+
(Netscape cookies file via `cookies.path`). TikTok sometimes needs them too
|
|
211
|
+
for full reliability.
|
|
212
|
+
|
|
213
|
+
## Download task
|
|
214
|
+
|
|
215
|
+
A `DownloadTask` exposes:
|
|
216
|
+
|
|
217
|
+
- `id: string`
|
|
218
|
+
- `cancel(): Promise<void>`
|
|
219
|
+
- `pause(): Promise<boolean>`
|
|
220
|
+
- `resume(): Promise<boolean>`
|
|
221
|
+
- `getStatus(): Promise<DownloadStatus>`
|
|
222
|
+
- `getProgress(): Promise<DownloadProgress | null>`
|
|
223
|
+
- `addListener(event, listener): Subscription`
|
|
224
|
+
|
|
225
|
+
Statuses: `queued | extracting | downloading | processing | paused | completed |
|
|
226
|
+
cancelled | failed`.
|
|
227
|
+
|
|
228
|
+
Events:
|
|
229
|
+
|
|
230
|
+
- `progress` → `DownloadProgress` (`percent`, `downloadedBytes`, `totalBytes`,
|
|
231
|
+
`speedBytesPerSecond`, `etaSeconds`, `filename`, `phase`)
|
|
232
|
+
- `state` → `{ taskId, status }`
|
|
233
|
+
- `completed` → `DownloadResult` (`taskId`, `path`, `filename`, `size`)
|
|
234
|
+
- `error` → `YtDlpError`
|
|
235
|
+
|
|
236
|
+
Progress events are throttled to ~200 ms and numeric fields are `undefined`
|
|
237
|
+
when the value is unknown (never `NaN`).
|
|
238
|
+
|
|
239
|
+
### Pause & resume
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
const task = await YtDlp.download({ url, format: 'bestvideo+bestaudio', ffmpeg: { location } });
|
|
243
|
+
|
|
244
|
+
await task.pause(); // true — yt-dlp aborts on the next progress tick
|
|
245
|
+
console.log(await task.getStatus()); // 'paused'
|
|
246
|
+
|
|
247
|
+
await task.resume(); // true — same options re-run; continues from the .part file
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`YtDlp.pause(taskId)` and `YtDlp.resume(taskId)` work the same way when you
|
|
251
|
+
only have a task id (e.g. after app re-creation).
|
|
252
|
+
|
|
253
|
+
How it works:
|
|
254
|
+
|
|
255
|
+
- `pause()` cooperatively aborts the download from the progress hook. yt-dlp
|
|
256
|
+
leaves its `.part` file on disk, and the task stays registered with status
|
|
257
|
+
`paused` (so a paused task still exists; cancel it if you're done).
|
|
258
|
+
- `resume()` re-runs the exact same download. yt-dlp continues by default
|
|
259
|
+
(`continue`): byte-range where the source server supports it, fragment-based
|
|
260
|
+
resumes (DASH/HLS) via the `.ytdl` sidecar. If a source does not support
|
|
261
|
+
continuing, yt-dlp restarts that file.
|
|
262
|
+
- Both return `false` when the action is not applicable (unknown task, not
|
|
263
|
+
paused, already finished). Cancelling a paused task finalizes it immediately.
|
|
264
|
+
- No network is consumed while paused, but the pause takes effect on the next
|
|
265
|
+
yt-dlp progress tick (~200 ms). Pausing does **not** cover the download's
|
|
266
|
+
extraction phase.
|
|
267
|
+
|
|
268
|
+
`pause()`/`resume()` are process-local and do not survive native restarts
|
|
269
|
+
(see "Limitations").
|
|
270
|
+
|
|
271
|
+
## Background downloads
|
|
272
|
+
|
|
273
|
+
While at least one download is running, the module promotes the app with a
|
|
274
|
+
native Android **foreground service** (`dataSync` type), so downloads keep
|
|
275
|
+
going when the app is backgrounded, the screen is off, or the app is swiped
|
|
276
|
+
away. No opt-in needed — starting a download shows an ongoing notification
|
|
277
|
+
with live progress and a **Cancel** action (cancels all active downloads);
|
|
278
|
+
tapping the notification reopens the app. When the last download finishes
|
|
279
|
+
(or fails / is cancelled), the service stops and the notification goes away.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const task = await YtDlp.download({ url });
|
|
283
|
+
// app backgrounded here — the download continues
|
|
284
|
+
task.addListener('progress', (p) => console.log(p.percent));
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
What it does and does not cover:
|
|
288
|
+
|
|
289
|
+
- **Survives:** backgrounding, screen off, task swipe-away.
|
|
290
|
+
- **Does not survive:** the process being killed by the system, device
|
|
291
|
+
reboot, or a native restart. Tasks are process-local: after a restart,
|
|
292
|
+
re-issue downloads (and consider `pause()`/`resume()` semantics for
|
|
293
|
+
partial files left on disk).
|
|
294
|
+
- The service holds a partial wake lock while active, so screen-off
|
|
295
|
+
downloads are not stalled by CPU sleep.
|
|
296
|
+
- On Android 13+ the host app should request the `POST_NOTIFICATIONS`
|
|
297
|
+
runtime permission, otherwise the progress notification is suppressed
|
|
298
|
+
(the download still runs).
|
|
299
|
+
- On Android 15+, the system may time-box `dataSync` foreground services
|
|
300
|
+
(around 6 hours); multi-hour downloads can be stopped by the OS.
|
|
301
|
+
- Cancellation from the notification stops every active download; per-task
|
|
302
|
+
control stays in the app UI via `task.cancel()`.
|
|
303
|
+
|
|
304
|
+
`react-native-continued-task` (see below) remains a complementary option if
|
|
305
|
+
you also want WorkManager-backed scheduling or system progress UI beyond
|
|
306
|
+
this built-in service.
|
|
307
|
+
|
|
308
|
+
## Errors
|
|
309
|
+
|
|
310
|
+
All failures normalize to `YtDlpError` with a `code`:
|
|
311
|
+
|
|
312
|
+
`INVALID_URL`, `EXTRACTION_FAILED`, `DOWNLOAD_FAILED`, `CANCELLED`,
|
|
313
|
+
`FORMAT_UNAVAILABLE`, `NETWORK_ERROR`, `AUTHENTICATION_REQUIRED`,
|
|
314
|
+
`GEO_RESTRICTED`, `PRIVATE_CONTENT`, `AGE_RESTRICTED`, `PROCESSING_FAILED`,
|
|
315
|
+
`STORAGE_ERROR`, `INIT_FAILED`, `UNSUPPORTED_PLATFORM`, `UNKNOWN`.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { YtDlpError } from 'ytdlp-react-native';
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
await YtDlp.extractInfo(url);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (error instanceof YtDlpError) {
|
|
324
|
+
console.log(error.code, error.message);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Raw native stack traces are never surfaced to the user.
|
|
330
|
+
|
|
331
|
+
## Storage
|
|
332
|
+
|
|
333
|
+
Files are written to app-specific external storage:
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
Android/data/<your-package>/files/yt-dlp/<output.directory>/...
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Filenames and directory segments are sanitized against illegal characters,
|
|
340
|
+
path traversal, excessive length and empty names. This avoids dangerous
|
|
341
|
+
permissions like `MANAGE_EXTERNAL_STORAGE`. The returned `DownloadResult.path`
|
|
342
|
+
is an absolute path inside your app's own storage.
|
|
343
|
+
|
|
344
|
+
## Limitations
|
|
345
|
+
|
|
346
|
+
- **Android only.**
|
|
347
|
+
- **FFmpeg is not bundled.** Merging separate video + audio streams and other
|
|
348
|
+
post-processing only work when you supply an ffmpeg executable via
|
|
349
|
+
`ffmpeg.location` (see "FFmpeg support"). Without it, those features are
|
|
350
|
+
rejected with `PROCESSING_FAILED`.
|
|
351
|
+
- **Background execution is best-effort within a live process.** Active
|
|
352
|
+
downloads run under a `dataSync` foreground service (see "Background
|
|
353
|
+
downloads"), but downloads do not survive the process being killed or the
|
|
354
|
+
device rebooting. Per-process pause/resume does not survive a native
|
|
355
|
+
restart. Persisting task IDs lets you re-issue cancellation later.
|
|
356
|
+
- yt-dlp site support changes frequently. Not every website works forever,
|
|
357
|
+
and not every site provides every field.
|
|
358
|
+
- This package does **not** bundle or provide a way to update the embedded
|
|
359
|
+
yt-dlp at runtime.
|
|
360
|
+
- Playlists are opt-in via `playlist.enabled` to avoid accidental bulk
|
|
361
|
+
downloads.
|
|
362
|
+
|
|
363
|
+
## Recommended production stack
|
|
364
|
+
|
|
365
|
+
`ytdlp-react-native` focuses on reliable extraction and downloading of single
|
|
366
|
+
streams. For a complete video-downloader experience you will usually combine
|
|
367
|
+
it with three complementary libraries:
|
|
368
|
+
|
|
369
|
+
| Concern | Package | Link |
|
|
370
|
+
|---------|---------|------|
|
|
371
|
+
| Long-running background downloads with system progress UI | **react-native-continued-task** | [GitHub](https://github.com/mahdidavoodi7/react-native-continued-task) |
|
|
372
|
+
| Merge video+audio, re-encode, burn subtitles, etc. | **munim-ffmpeg** | [GitHub](https://github.com/munimtechnologies/munim-ffmpeg) |
|
|
373
|
+
| Save finished files to the public Media Library / Gallery | **expo-media-library** | [Expo docs](https://docs.expo.dev/versions/latest/sdk/media-library/) |
|
|
374
|
+
|
|
375
|
+
### Suggested flow
|
|
376
|
+
|
|
377
|
+
1. Start a continued background task with `react-native-continued-task`
|
|
378
|
+
(shows Live Activity on iOS 26+ / foreground-service notification on Android).
|
|
379
|
+
2. Download with `ytdlp-react-native` using a single-stream format (`best` /
|
|
380
|
+
`bestaudio`). Report progress back to the continued task.
|
|
381
|
+
3. (Optional) If you need to merge separate streams or perform other
|
|
382
|
+
post-processing, use `munim-ffmpeg` (in-process FFmpegKit). Note that
|
|
383
|
+
FFmpegKit-style packages run FFmpeg in-process and **cannot** provide the
|
|
384
|
+
executable `ytdlp-react-native`'s `ffmpeg.location` option needs — that
|
|
385
|
+
option must point to a real ffmpeg binary on disk.
|
|
386
|
+
4. Move the final file into the user’s public gallery with `expo-media-library`.
|
|
387
|
+
|
|
388
|
+
All three packages (plus this one) require a **development build** — they do
|
|
389
|
+
not work inside Expo Go.
|
|
390
|
+
|
|
391
|
+
> Note: basic background continuity (foreground service + progress
|
|
392
|
+
> notification + cancel) is built into `ytdlp-react-native` (see "Background
|
|
393
|
+
> downloads"). Reach for `react-native-continued-task` when you additionally
|
|
394
|
+
> want WorkManager-backed scheduling or richer system UI.
|
|
395
|
+
|
|
396
|
+
## Legal & responsible use
|
|
397
|
+
|
|
398
|
+
`ytdlp-react-native` is a technical wrapper around yt-dlp. It does not circumvent
|
|
399
|
+
DRM (Widevine, FairPlay, PlayReady, ...), bypass authentication, or access
|
|
400
|
+
private or unauthorized content — content that requires DRM or authentication
|
|
401
|
+
will fail with an error.
|
|
402
|
+
|
|
403
|
+
You are responsible for complying with:
|
|
404
|
+
|
|
405
|
+
- website terms of service
|
|
406
|
+
- copyright law and content licenses
|
|
407
|
+
- authentication rules
|
|
408
|
+
- platform policies
|
|
409
|
+
|
|
410
|
+
Do not use this library to download content you do not have the right to
|
|
411
|
+
download.
|
|
412
|
+
|
|
413
|
+
## Third-party licenses
|
|
414
|
+
|
|
415
|
+
| Component | License |
|
|
416
|
+
| --- | --- |
|
|
417
|
+
| `ytdlp-react-native` (this package) | MIT |
|
|
418
|
+
| `yt-dlp-android` (Maven `dev.ffmpegkit-maintained:yt-dlp-android`) | MIT |
|
|
419
|
+
| [yt-dlp](https://github.com/yt-dlp/yt-dlp) | Unlicense |
|
|
420
|
+
| [Chaquopy](https://chaquo.com/chaquopy/) | BSD-style (per the embedded distribution) |
|
|
421
|
+
|
|
422
|
+
Re-verify third-party licenses at release time.
|