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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure serializers that translate public options into the plain map sent to
|
|
3
|
+
* the native layer. Kept free of native imports so they can be unit-tested
|
|
4
|
+
* (AGENTS.md §49, §41).
|
|
5
|
+
*/
|
|
6
|
+
import { YtDlpError } from './errors';
|
|
7
|
+
export function serializeExtractOptions(options) {
|
|
8
|
+
if (!options)
|
|
9
|
+
return {};
|
|
10
|
+
const out = {};
|
|
11
|
+
if (options.cookies)
|
|
12
|
+
out.cookies = serializeCookies(options.cookies);
|
|
13
|
+
if (options.proxy)
|
|
14
|
+
out.proxy = options.proxy;
|
|
15
|
+
if (options.userAgent || options.headers) {
|
|
16
|
+
const headers = { ...(options.headers ?? {}) };
|
|
17
|
+
if (options.userAgent)
|
|
18
|
+
headers['User-Agent'] = options.userAgent;
|
|
19
|
+
out.headers = headers;
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
export function serializeDownloadOptions(options) {
|
|
24
|
+
const out = { url: options.url };
|
|
25
|
+
if (options.format)
|
|
26
|
+
out.format = options.format;
|
|
27
|
+
if (options.output) {
|
|
28
|
+
const output = {};
|
|
29
|
+
if (options.output.directory)
|
|
30
|
+
output.directory = options.output.directory;
|
|
31
|
+
if (options.output.filename)
|
|
32
|
+
output.filename = options.output.filename;
|
|
33
|
+
if (Object.keys(output).length > 0)
|
|
34
|
+
out.output = output;
|
|
35
|
+
}
|
|
36
|
+
if (options.merge != null)
|
|
37
|
+
out.merge = options.merge;
|
|
38
|
+
if (options.ffmpeg?.location) {
|
|
39
|
+
const location = options.ffmpeg.location.trim();
|
|
40
|
+
if (!location) {
|
|
41
|
+
throw new YtDlpError('PROCESSING_FAILED', 'ffmpeg.location must not be empty.');
|
|
42
|
+
}
|
|
43
|
+
out.ffmpeg = { location };
|
|
44
|
+
}
|
|
45
|
+
if (options.cookies)
|
|
46
|
+
out.cookies = serializeCookies(options.cookies);
|
|
47
|
+
if (options.userAgent || options.headers) {
|
|
48
|
+
const headers = { ...(options.headers ?? {}) };
|
|
49
|
+
if (options.userAgent)
|
|
50
|
+
headers['User-Agent'] = options.userAgent;
|
|
51
|
+
out.headers = headers;
|
|
52
|
+
}
|
|
53
|
+
if (options.referer)
|
|
54
|
+
out.referer = options.referer;
|
|
55
|
+
if (options.proxy)
|
|
56
|
+
out.proxy = options.proxy;
|
|
57
|
+
if (options.playlist) {
|
|
58
|
+
const playlist = {};
|
|
59
|
+
if (options.playlist.enabled != null)
|
|
60
|
+
playlist.enabled = options.playlist.enabled;
|
|
61
|
+
if (options.playlist.start != null)
|
|
62
|
+
playlist.start = options.playlist.start;
|
|
63
|
+
if (options.playlist.end != null)
|
|
64
|
+
playlist.end = options.playlist.end;
|
|
65
|
+
if (Object.keys(playlist).length > 0)
|
|
66
|
+
out.playlist = playlist;
|
|
67
|
+
}
|
|
68
|
+
if (options.network) {
|
|
69
|
+
const network = {};
|
|
70
|
+
if (options.network.timeout != null)
|
|
71
|
+
network.timeout = options.network.timeout;
|
|
72
|
+
if (options.network.retries != null)
|
|
73
|
+
network.retries = options.network.retries;
|
|
74
|
+
if (Object.keys(network).length > 0)
|
|
75
|
+
out.network = network;
|
|
76
|
+
}
|
|
77
|
+
if (options.subtitles) {
|
|
78
|
+
const subtitles = {};
|
|
79
|
+
if (options.subtitles.enabled != null)
|
|
80
|
+
subtitles.enabled = options.subtitles.enabled;
|
|
81
|
+
if (options.subtitles.languages?.length)
|
|
82
|
+
subtitles.languages = options.subtitles.languages;
|
|
83
|
+
if (options.subtitles.autoGenerated != null)
|
|
84
|
+
subtitles.autoGenerated = options.subtitles.autoGenerated;
|
|
85
|
+
if (Object.keys(subtitles).length > 0)
|
|
86
|
+
out.subtitles = subtitles;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
export function serializeCookies(cookies) {
|
|
91
|
+
if (cookies.source === 'file' && cookies.path) {
|
|
92
|
+
return { path: cookies.path };
|
|
93
|
+
}
|
|
94
|
+
throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=serializers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializers.js","sourceRoot":"","sources":["../src/serializers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure serializers that translate public options into the plain map sent to\n * the native layer. Kept free of native imports so they can be unit-tested\n * (AGENTS.md §49, §41).\n */\nimport { YtDlpError } from './errors';\nimport type { CookieOptions, DownloadOptions, ExtractOptions } from './types';\n\nexport function serializeExtractOptions(\n options: ExtractOptions | undefined\n): Record<string, unknown> {\n if (!options) return {};\n const out: Record<string, unknown> = {};\n if (options.cookies) out.cookies = serializeCookies(options.cookies);\n if (options.proxy) out.proxy = options.proxy;\n if (options.userAgent || options.headers) {\n const headers: Record<string, string> = { ...(options.headers ?? {}) };\n if (options.userAgent) headers['User-Agent'] = options.userAgent;\n out.headers = headers;\n }\n return out;\n}\n\nexport function serializeDownloadOptions(options: DownloadOptions): Record<string, unknown> {\n const out: Record<string, unknown> = { url: options.url };\n if (options.format) out.format = options.format;\n if (options.output) {\n const output: Record<string, unknown> = {};\n if (options.output.directory) output.directory = options.output.directory;\n if (options.output.filename) output.filename = options.output.filename;\n if (Object.keys(output).length > 0) out.output = output;\n }\n if (options.merge != null) out.merge = options.merge;\n if (options.ffmpeg?.location) {\n const location = options.ffmpeg.location.trim();\n if (!location) {\n throw new YtDlpError('PROCESSING_FAILED', 'ffmpeg.location must not be empty.');\n }\n out.ffmpeg = { location };\n }\n if (options.cookies) out.cookies = serializeCookies(options.cookies);\n if (options.userAgent || options.headers) {\n const headers: Record<string, string> = { ...(options.headers ?? {}) };\n if (options.userAgent) headers['User-Agent'] = options.userAgent;\n out.headers = headers;\n }\n if (options.referer) out.referer = options.referer;\n if (options.proxy) out.proxy = options.proxy;\n if (options.playlist) {\n const playlist: Record<string, unknown> = {};\n if (options.playlist.enabled != null) playlist.enabled = options.playlist.enabled;\n if (options.playlist.start != null) playlist.start = options.playlist.start;\n if (options.playlist.end != null) playlist.end = options.playlist.end;\n if (Object.keys(playlist).length > 0) out.playlist = playlist;\n }\n if (options.network) {\n const network: Record<string, unknown> = {};\n if (options.network.timeout != null) network.timeout = options.network.timeout;\n if (options.network.retries != null) network.retries = options.network.retries;\n if (Object.keys(network).length > 0) out.network = network;\n }\n if (options.subtitles) {\n const subtitles: Record<string, unknown> = {};\n if (options.subtitles.enabled != null) subtitles.enabled = options.subtitles.enabled;\n if (options.subtitles.languages?.length) subtitles.languages = options.subtitles.languages;\n if (options.subtitles.autoGenerated != null)\n subtitles.autoGenerated = options.subtitles.autoGenerated;\n if (Object.keys(subtitles).length > 0) out.subtitles = subtitles;\n }\n return out;\n}\n\nexport function serializeCookies(cookies: CookieOptions): Record<string, unknown> {\n if (cookies.source === 'file' && cookies.path) {\n return { path: cookies.path };\n }\n throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');\n}\n"]}
|
package/build/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* leak into this surface (see AGENTS.md §68).
|
|
6
6
|
*/
|
|
7
7
|
import type { YtDlpError } from './errors';
|
|
8
|
-
export type DownloadStatus = 'queued' | 'extracting' | 'downloading' | 'processing' | 'completed' | 'cancelled' | 'failed';
|
|
8
|
+
export type DownloadStatus = 'queued' | 'extracting' | 'downloading' | 'processing' | 'paused' | 'completed' | 'cancelled' | 'failed';
|
|
9
9
|
export type DownloadPhase = 'extracting' | 'downloading' | 'processing';
|
|
10
10
|
export interface Thumbnail {
|
|
11
11
|
url: string;
|
|
@@ -95,6 +95,20 @@ export interface NetworkOptions {
|
|
|
95
95
|
timeout?: number;
|
|
96
96
|
retries?: number;
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Optional FFmpeg integration (see issue #2). The package does **not** bundle
|
|
100
|
+
* FFmpeg — yt-dlp needs a real executable to merge separate video+audio
|
|
101
|
+
* streams (`bestvideo+bestaudio`), extract/re-encode audio, or embed metadata
|
|
102
|
+
* and thumbnails. Point `location` at an ffmpeg binary already on the device.
|
|
103
|
+
*/
|
|
104
|
+
export interface FfmpegOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Absolute filesystem path to an `ffmpeg` executable, or to a directory
|
|
107
|
+
* that contains one. yt-dlp spawns the binary from here during
|
|
108
|
+
* post-processing. An in-process JNI wrapper (e.g. FFmpegKit) is not enough.
|
|
109
|
+
*/
|
|
110
|
+
location: string;
|
|
111
|
+
}
|
|
98
112
|
export interface PlaylistOptions {
|
|
99
113
|
enabled?: boolean;
|
|
100
114
|
start?: number;
|
|
@@ -114,7 +128,17 @@ export interface DownloadOptions {
|
|
|
114
128
|
/** Raw yt-dlp format expression, e.g. `best`, `bestvideo+bestaudio`. */
|
|
115
129
|
format?: string;
|
|
116
130
|
output?: OutputOptions;
|
|
131
|
+
/**
|
|
132
|
+
* Set to `true` to request merging (e.g. with `format: 'bestvideo+bestaudio'`).
|
|
133
|
+
* Merging requires FFmpeg — see `ffmpeg`.
|
|
134
|
+
*/
|
|
117
135
|
merge?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Optional FFmpeg integration. When unset, FFmpeg-dependent features are
|
|
138
|
+
* rejected with `PROCESSING_FAILED`. When set, `bestvideo+bestaudio` and
|
|
139
|
+
* other post-processing become available.
|
|
140
|
+
*/
|
|
141
|
+
ffmpeg?: FfmpegOptions;
|
|
118
142
|
subtitles?: SubtitleOptions;
|
|
119
143
|
cookies?: CookieOptions;
|
|
120
144
|
/** Additional HTTP headers. Validated; secrets are never logged. */
|
|
@@ -168,6 +192,20 @@ export interface Subscription {
|
|
|
168
192
|
export interface DownloadTask {
|
|
169
193
|
id: string;
|
|
170
194
|
cancel(): Promise<void>;
|
|
195
|
+
/**
|
|
196
|
+
* Pause an in-flight download. yt-dlp aborts on the next progress tick and
|
|
197
|
+
* leaves its `.part` file on disk; the task remains registered with status
|
|
198
|
+
* `paused`. Resolves `false` if the task is unknown, already paused, or
|
|
199
|
+
* already finished.
|
|
200
|
+
*/
|
|
201
|
+
pause(): Promise<boolean>;
|
|
202
|
+
/**
|
|
203
|
+
* Resume a paused download with the exact same options. yt-dlp continues
|
|
204
|
+
* from the `.part` file by default (byte-range where the source supports it,
|
|
205
|
+
* otherwise the file restarts). Resolves `false` if the task is unknown or
|
|
206
|
+
* not paused.
|
|
207
|
+
*/
|
|
208
|
+
resume(): Promise<boolean>;
|
|
171
209
|
getStatus(): Promise<DownloadStatus>;
|
|
172
210
|
getProgress(): Promise<DownloadProgress | null>;
|
|
173
211
|
addListener(event: 'progress', listener: (progress: DownloadProgress) => void): Subscription;
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/build/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Public types for expo-ytdlp-native.\n *\n * These types are the stable contract of the package. Native details never\n * leak into this surface (see AGENTS.md §68).\n */\nimport type { YtDlpError } from './errors';\n\nexport type DownloadStatus =\n 'queued'
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Public types for expo-ytdlp-native.\n *\n * These types are the stable contract of the package. Native details never\n * leak into this surface (see AGENTS.md §68).\n */\nimport type { YtDlpError } from './errors';\n\nexport type DownloadStatus =\n | 'queued'\n | 'extracting'\n | 'downloading'\n | 'processing'\n | 'paused'\n | 'completed'\n | 'cancelled'\n | 'failed';\n\nexport type DownloadPhase = 'extracting' | 'downloading' | 'processing';\n\nexport interface Thumbnail {\n url: string;\n width?: number;\n height?: number;\n resolution?: string;\n id?: string;\n}\n\n/** Normalized media information. Every field is optional except `formats`. */\nexport interface VideoInfo {\n id?: string;\n title?: string;\n description?: string;\n uploader?: string;\n uploaderId?: string;\n uploaderUrl?: string;\n channel?: string;\n channelId?: string;\n channelUrl?: string;\n webpageUrl?: string;\n originalUrl?: string;\n thumbnail?: string;\n thumbnails?: Thumbnail[];\n duration?: number;\n durationString?: string;\n uploadDate?: string;\n timestamp?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n ageLimit?: number;\n isLive?: boolean;\n wasLive?: boolean;\n extractor?: string;\n extractorKey?: string;\n webpageUrlDomain?: string;\n formats: Format[];\n}\n\n/** Normalized format model. Unknown native values become `undefined`. */\nexport interface Format {\n id: string;\n url?: string;\n ext?: string;\n protocol?: string;\n format?: string;\n formatNote?: string;\n width?: number;\n height?: number;\n fps?: number;\n vcodec?: string;\n acodec?: string;\n abr?: number;\n vbr?: number;\n tbr?: number;\n filesize?: number;\n filesizeApprox?: number;\n quality?: number;\n audioOnly: boolean;\n videoOnly: boolean;\n hasVideo: boolean;\n hasAudio: boolean;\n language?: string;\n container?: string;\n}\n\nexport interface OutputOptions {\n /**\n * Directory name, relative to the app-specific external storage directory.\n * Single path segments only; `..` is rejected.\n */\n directory?: string;\n /**\n * yt-dlp output template, e.g. `%(title)s.%(ext)s`.\n * Static parts are sanitized; template directives are preserved.\n */\n filename?: string;\n}\n\nexport interface SubtitleOptions {\n enabled?: boolean;\n languages?: string[];\n autoGenerated?: boolean;\n}\n\nexport interface CookieOptions {\n source: 'file';\n path: string;\n}\n\nexport interface NetworkOptions {\n timeout?: number;\n retries?: number;\n}\n\n/**\n * Optional FFmpeg integration (see issue #2). The package does **not** bundle\n * FFmpeg — yt-dlp needs a real executable to merge separate video+audio\n * streams (`bestvideo+bestaudio`), extract/re-encode audio, or embed metadata\n * and thumbnails. Point `location` at an ffmpeg binary already on the device.\n */\nexport interface FfmpegOptions {\n /**\n * Absolute filesystem path to an `ffmpeg` executable, or to a directory\n * that contains one. yt-dlp spawns the binary from here during\n * post-processing. An in-process JNI wrapper (e.g. FFmpegKit) is not enough.\n */\n location: string;\n}\n\nexport interface PlaylistOptions {\n enabled?: boolean;\n start?: number;\n end?: number;\n}\n\nexport interface ExtractOptions {\n /** Cookies used during extraction (e.g. for login-gated sources). */\n cookies?: CookieOptions;\n /** Additional HTTP headers sent during extraction. Never logged. */\n headers?: Record<string, string>;\n /** Custom `User-Agent`. */\n userAgent?: string;\n proxy?: string;\n}\n\nexport interface DownloadOptions {\n url: string;\n /** Raw yt-dlp format expression, e.g. `best`, `bestvideo+bestaudio`. */\n format?: string;\n output?: OutputOptions;\n /**\n * Set to `true` to request merging (e.g. with `format: 'bestvideo+bestaudio'`).\n * Merging requires FFmpeg — see `ffmpeg`.\n */\n merge?: boolean;\n /**\n * Optional FFmpeg integration. When unset, FFmpeg-dependent features are\n * rejected with `PROCESSING_FAILED`. When set, `bestvideo+bestaudio` and\n * other post-processing become available.\n */\n ffmpeg?: FfmpegOptions;\n subtitles?: SubtitleOptions;\n cookies?: CookieOptions;\n /** Additional HTTP headers. Validated; secrets are never logged. */\n headers?: Record<string, string>;\n userAgent?: string;\n referer?: string;\n proxy?: string;\n playlist?: PlaylistOptions;\n network?: NetworkOptions;\n}\n\nexport interface DownloadProgress {\n taskId: string;\n status: DownloadStatus;\n phase: DownloadPhase;\n percent?: number;\n downloadedBytes?: number;\n totalBytes?: number;\n speedBytesPerSecond?: number;\n etaSeconds?: number;\n filename?: string;\n}\n\nexport interface DownloadResult {\n taskId: string;\n path?: string;\n uri?: string;\n filename?: string;\n mimeType?: string;\n size?: number;\n duration?: number;\n}\n\nexport type DownloadEventType = 'progress' | 'state' | 'completed' | 'error' | 'cancelled';\n\nexport interface DownloadEvent {\n taskId: string;\n type: DownloadEventType;\n status: DownloadStatus;\n phase: DownloadPhase;\n progress?: DownloadProgress;\n result?: DownloadResult;\n error?: {\n code: string;\n message: string;\n };\n}\n\nexport type DownloadStateEvent = {\n taskId: string;\n status: DownloadStatus;\n};\n\nexport interface Subscription {\n remove(): void;\n}\n\nexport interface DownloadTask {\n id: string;\n cancel(): Promise<void>;\n /**\n * Pause an in-flight download. yt-dlp aborts on the next progress tick and\n * leaves its `.part` file on disk; the task remains registered with status\n * `paused`. Resolves `false` if the task is unknown, already paused, or\n * already finished.\n */\n pause(): Promise<boolean>;\n /**\n * Resume a paused download with the exact same options. yt-dlp continues\n * from the `.part` file by default (byte-range where the source supports it,\n * otherwise the file restarts). Resolves `false` if the task is unknown or\n * not paused.\n */\n resume(): Promise<boolean>;\n getStatus(): Promise<DownloadStatus>;\n getProgress(): Promise<DownloadProgress | null>;\n addListener(event: 'progress', listener: (progress: DownloadProgress) => void): Subscription;\n addListener(event: 'state', listener: (state: DownloadStateEvent) => void): Subscription;\n addListener(event: 'completed', listener: (result: DownloadResult) => void): Subscription;\n addListener(event: 'error', listener: (error: YtDlpError) => void): Subscription;\n}\n\nexport interface YtDlpVersion {\n ytDlp: string;\n library: string;\n}\n\nexport type YtDlpErrorCode =\n | 'INVALID_URL'\n | 'EXTRACTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'CANCELLED'\n | 'FORMAT_UNAVAILABLE'\n | 'NETWORK_ERROR'\n | 'AUTHENTICATION_REQUIRED'\n | 'GEO_RESTRICTED'\n | 'PRIVATE_CONTENT'\n | 'AGE_RESTRICTED'\n | 'PROCESSING_FAILED'\n | 'STORAGE_ERROR'\n | 'INIT_FAILED'\n | 'UNSUPPORTED_PLATFORM'\n | 'UNKNOWN';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ytdlp-react-native",
|
|
3
|
-
"version": "1.0.0",
|
|
3
|
+
"version": "1.1.0-beta.0",
|
|
4
4
|
"description": "A native Android Expo module providing a TypeScript API around yt-dlp.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"build": "node internal/module_scripts/build.js",
|
|
18
18
|
"clean": "node internal/module_scripts/clean.js",
|
|
19
19
|
"lint": "eslint src/",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
20
21
|
"test": "node internal/module_scripts/test.js",
|
|
21
22
|
"prepare": "node internal/module_scripts/prepare.js",
|
|
22
23
|
"open:ios": "node internal/module_scripts/open-ios.js",
|
|
@@ -42,15 +43,16 @@
|
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@babel/core": "^7.26.0",
|
|
44
45
|
"@types/jest": "^29.2.1",
|
|
45
|
-
"@types/react": "~19.
|
|
46
|
-
"babel-preset-expo": "~
|
|
46
|
+
"@types/react": "~19.2.18",
|
|
47
|
+
"babel-preset-expo": "~57.0.10",
|
|
47
48
|
"eslint": "~9.39.4",
|
|
48
49
|
"eslint-config-universe": "^15.0.3",
|
|
49
50
|
"expo": "^57.0.13",
|
|
50
51
|
"jest": "^29.7.0",
|
|
51
|
-
"jest-expo": "~
|
|
52
|
+
"jest-expo": "~57.0.5",
|
|
52
53
|
"prettier": "^3.0.0",
|
|
53
|
-
"react
|
|
54
|
+
"react": "~19.2.3",
|
|
55
|
+
"react-native": "~0.86.3",
|
|
54
56
|
"typescript": "^5.9.2"
|
|
55
57
|
},
|
|
56
58
|
"jest": {
|
package/src/ExpoYtDlpModule.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare class ExpoYtDlpNativeModule extends NativeModule<ExpoYtDlpModuleE
|
|
|
34
34
|
extractInfo(url: string, options: Record<string, unknown>): Promise<string>;
|
|
35
35
|
startDownload(options: Record<string, unknown>): Promise<DownloadTaskInfo>;
|
|
36
36
|
cancelDownload(taskId: string): Promise<boolean>;
|
|
37
|
+
pauseDownload(taskId: string): Promise<boolean>;
|
|
38
|
+
resumeDownload(taskId: string): Promise<boolean>;
|
|
37
39
|
getDownloadStatus(taskId: string): Promise<DownloadStatusInfo | null>;
|
|
38
40
|
}
|
|
39
41
|
|
package/src/YtDlp.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { LIBRARY_VERSION, SUPPORTED_PLATFORM_MESSAGE } from './constants';
|
|
|
3
3
|
import { YtDlpDownloadTask } from './downloadTask';
|
|
4
4
|
import { normalizeError, normalizeExtractionError, YtDlpError } from './errors';
|
|
5
5
|
import { mapVideoInfo } from './mappers';
|
|
6
|
+
import { serializeDownloadOptions, serializeExtractOptions } from './serializers';
|
|
6
7
|
import type {
|
|
7
|
-
CookieOptions,
|
|
8
8
|
DownloadOptions,
|
|
9
9
|
DownloadTask,
|
|
10
10
|
ExtractOptions,
|
|
@@ -88,64 +88,28 @@ export async function cancel(taskId: string): Promise<boolean> {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function serializeDownloadOptions(options: DownloadOptions): Record<string, unknown> {
|
|
105
|
-
const out: Record<string, unknown> = { url: options.url };
|
|
106
|
-
if (options.format) out.format = options.format;
|
|
107
|
-
if (options.output) {
|
|
108
|
-
const output: Record<string, unknown> = {};
|
|
109
|
-
if (options.output.directory) output.directory = options.output.directory;
|
|
110
|
-
if (options.output.filename) output.filename = options.output.filename;
|
|
111
|
-
if (Object.keys(output).length > 0) out.output = output;
|
|
112
|
-
}
|
|
113
|
-
if (options.merge != null) out.merge = options.merge;
|
|
114
|
-
if (options.cookies) out.cookies = serializeCookies(options.cookies);
|
|
115
|
-
if (options.userAgent || options.headers) {
|
|
116
|
-
const headers: Record<string, string> = { ...(options.headers ?? {}) };
|
|
117
|
-
if (options.userAgent) headers['User-Agent'] = options.userAgent;
|
|
118
|
-
out.headers = headers;
|
|
119
|
-
}
|
|
120
|
-
if (options.referer) out.referer = options.referer;
|
|
121
|
-
if (options.proxy) out.proxy = options.proxy;
|
|
122
|
-
if (options.playlist) {
|
|
123
|
-
const playlist: Record<string, unknown> = {};
|
|
124
|
-
if (options.playlist.enabled != null) playlist.enabled = options.playlist.enabled;
|
|
125
|
-
if (options.playlist.start != null) playlist.start = options.playlist.start;
|
|
126
|
-
if (options.playlist.end != null) playlist.end = options.playlist.end;
|
|
127
|
-
if (Object.keys(playlist).length > 0) out.playlist = playlist;
|
|
128
|
-
}
|
|
129
|
-
if (options.network) {
|
|
130
|
-
const network: Record<string, unknown> = {};
|
|
131
|
-
if (options.network.timeout != null) network.timeout = options.network.timeout;
|
|
132
|
-
if (options.network.retries != null) network.retries = options.network.retries;
|
|
133
|
-
if (Object.keys(network).length > 0) out.network = network;
|
|
134
|
-
}
|
|
135
|
-
if (options.subtitles) {
|
|
136
|
-
const subtitles: Record<string, unknown> = {};
|
|
137
|
-
if (options.subtitles.enabled != null) subtitles.enabled = options.subtitles.enabled;
|
|
138
|
-
if (options.subtitles.languages?.length) subtitles.languages = options.subtitles.languages;
|
|
139
|
-
if (options.subtitles.autoGenerated != null)
|
|
140
|
-
subtitles.autoGenerated = options.subtitles.autoGenerated;
|
|
141
|
-
if (Object.keys(subtitles).length > 0) out.subtitles = subtitles;
|
|
91
|
+
/**
|
|
92
|
+
* Pause an in-flight download by task id. Returns `false` if the task is
|
|
93
|
+
* unknown, already paused, or already finished.
|
|
94
|
+
*/
|
|
95
|
+
export async function pause(taskId: string): Promise<boolean> {
|
|
96
|
+
try {
|
|
97
|
+
const native = requireNative();
|
|
98
|
+
return await native.pauseDownload(taskId);
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
throw normalizeError(cause);
|
|
142
101
|
}
|
|
143
|
-
return out;
|
|
144
102
|
}
|
|
145
103
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Resume a paused download by task id. Returns `false` if the task is unknown
|
|
106
|
+
* or not paused. yt-dlp continues from the partial `.part` file by default.
|
|
107
|
+
*/
|
|
108
|
+
export async function resume(taskId: string): Promise<boolean> {
|
|
109
|
+
try {
|
|
110
|
+
const native = requireNative();
|
|
111
|
+
return await native.resumeDownload(taskId);
|
|
112
|
+
} catch (cause) {
|
|
113
|
+
throw normalizeError(cause);
|
|
149
114
|
}
|
|
150
|
-
throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');
|
|
151
115
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { YtDlpError, fromNativeCode, normalizeError, normalizeExtractionError } from '../errors';
|
|
2
|
+
|
|
3
|
+
describe('YtDlpError', () => {
|
|
4
|
+
it('exposes code, message and optional metadata', () => {
|
|
5
|
+
const error = new YtDlpError('CANCELLED', 'Download cancelled', { taskId: 'abc' });
|
|
6
|
+
expect(error).toBeInstanceOf(Error);
|
|
7
|
+
expect(error.name).toBe('YtDlpError');
|
|
8
|
+
expect(error.code).toBe('CANCELLED');
|
|
9
|
+
expect(error.message).toBe('Download cancelled');
|
|
10
|
+
expect(error.taskId).toBe('abc');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('keeps taskId/cause undefined when not provided', () => {
|
|
14
|
+
const error = new YtDlpError('UNKNOWN', 'boom');
|
|
15
|
+
expect(error.taskId).toBeUndefined();
|
|
16
|
+
expect(error.cause).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('normalizeError', () => {
|
|
21
|
+
it('passes through an existing YtDlpError unchanged', () => {
|
|
22
|
+
const original = new YtDlpError('INVALID_URL', 'nope');
|
|
23
|
+
expect(normalizeError(original)).toBe(original);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('parses native-prefixed messages into a typed error', () => {
|
|
27
|
+
const error = normalizeError(new Error('YTD_NATIVE|GEO_RESTRICTED|Blocked'));
|
|
28
|
+
expect(error).toBeInstanceOf(YtDlpError);
|
|
29
|
+
expect(error.code).toBe('GEO_RESTRICTED');
|
|
30
|
+
expect(error.message).toBe('Blocked');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('accepts native-prefixed string throws', () => {
|
|
34
|
+
const error = normalizeError('YTD_NATIVE|NETWORK_ERROR|timeout');
|
|
35
|
+
expect(error.code).toBe('NETWORK_ERROR');
|
|
36
|
+
expect(error.message).toBe('timeout');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('maps unknown native codes to UNKNOWN', () => {
|
|
40
|
+
const error = normalizeError(new Error('YTD_NATIVE|NOT_A_REAL_CODE|oops'));
|
|
41
|
+
expect(error.code).toBe('UNKNOWN');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('falls back to UNKNOWN for plain errors without leaking stack traces', () => {
|
|
45
|
+
const error = normalizeError(new Error('kaboom'));
|
|
46
|
+
expect(error.code).toBe('UNKNOWN');
|
|
47
|
+
expect(error.message).toBe('kaboom');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('uses the fallback message for non-error, non-string causes', () => {
|
|
51
|
+
const error = normalizeError({ weird: true }, 'Fallback');
|
|
52
|
+
expect(error.code).toBe('UNKNOWN');
|
|
53
|
+
expect(error.message).toBe('Fallback');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('fromNativeCode', () => {
|
|
58
|
+
it('builds an error from a code and message', () => {
|
|
59
|
+
const error = fromNativeCode('STORAGE_ERROR', 'No space');
|
|
60
|
+
expect(error.code).toBe('STORAGE_ERROR');
|
|
61
|
+
expect(error.message).toBe('No space');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('degrades unknown codes to UNKNOWN', () => {
|
|
65
|
+
const error = fromNativeCode('BOGUS', 'x');
|
|
66
|
+
expect(error.code).toBe('UNKNOWN');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('normalizeExtractionError', () => {
|
|
71
|
+
it('passes through a non-extraction normalized error untouched', () => {
|
|
72
|
+
const error = normalizeExtractionError(new Error('YTD_NATIVE|CANCELLED|stopped'));
|
|
73
|
+
expect(error.code).toBe('CANCELLED');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('classifies connection failures as NETWORK_ERROR', () => {
|
|
77
|
+
const error = normalizeExtractionError(
|
|
78
|
+
new Error('YTD_NATIVE|EXTRACTION_FAILED|HTTP Error 429')
|
|
79
|
+
);
|
|
80
|
+
expect(error.code).toBe('NETWORK_ERROR');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('classifies sign-in prompts as AUTHENTICATION_REQUIRED', () => {
|
|
84
|
+
const error = normalizeExtractionError(
|
|
85
|
+
new Error('YTD_NATIVE|EXTRACTION_FAILED|Please sign in to confirm you are not a bot')
|
|
86
|
+
);
|
|
87
|
+
expect(error.code).toBe('AUTHENTICATION_REQUIRED');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('classifies private content', () => {
|
|
91
|
+
const error = normalizeExtractionError(
|
|
92
|
+
new Error('YTD_NATIVE|EXTRACTION_FAILED|This video is private')
|
|
93
|
+
);
|
|
94
|
+
expect(error.code).toBe('PRIVATE_CONTENT');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('classifies age-restricted content', () => {
|
|
98
|
+
const error = normalizeExtractionError(
|
|
99
|
+
new Error('YTD_NATIVE|EXTRACTION_FAILED|Sign in to confirm your age')
|
|
100
|
+
);
|
|
101
|
+
expect(error.code).toBe('AGE_RESTRICTED');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('classifies geo-restricted content', () => {
|
|
105
|
+
const error = normalizeExtractionError(
|
|
106
|
+
new Error('YTD_NATIVE|EXTRACTION_FAILED|Not available in your country')
|
|
107
|
+
);
|
|
108
|
+
expect(error.code).toBe('GEO_RESTRICTED');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('keeps the base code when nothing matches', () => {
|
|
112
|
+
const error = normalizeExtractionError(
|
|
113
|
+
new Error('YTD_NATIVE|EXTRACTION_FAILED|Unsupported URL')
|
|
114
|
+
);
|
|
115
|
+
expect(error.code).toBe('EXTRACTION_FAILED');
|
|
116
|
+
});
|
|
117
|
+
});
|