ytdlp-react-native 1.0.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 +74 -0
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/android/build.gradle +25 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/ytdlp/ExpoYtDlpModule.kt +74 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpDownloadManager.kt +97 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpEngine.kt +277 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpException.kt +14 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpFileUtil.kt +89 -0
- package/android/src/main/java/expo/modules/ytdlp/YtDlpTask.kt +225 -0
- package/build/ExpoYtDlpModule.d.ts +35 -0
- package/build/ExpoYtDlpModule.d.ts.map +1 -0
- package/build/ExpoYtDlpModule.js +10 -0
- package/build/ExpoYtDlpModule.js.map +1 -0
- package/build/ExpoYtDlpModule.web.d.ts +11 -0
- package/build/ExpoYtDlpModule.web.d.ts.map +1 -0
- package/build/ExpoYtDlpModule.web.js +13 -0
- package/build/ExpoYtDlpModule.web.js.map +1 -0
- package/build/YtDlp.d.ts +23 -0
- package/build/YtDlp.d.ts.map +1 -0
- package/build/YtDlp.js +160 -0
- package/build/YtDlp.js.map +1 -0
- package/build/constants.d.ts +9 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +9 -0
- package/build/constants.js.map +1 -0
- package/build/downloadTask.d.ts +30 -0
- package/build/downloadTask.d.ts.map +1 -0
- package/build/downloadTask.js +111 -0
- package/build/downloadTask.js.map +1 -0
- package/build/errors.d.ts +27 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +111 -0
- package/build/errors.js.map +1 -0
- package/build/events.d.ts +20 -0
- package/build/events.d.ts.map +1 -0
- package/build/events.js +96 -0
- package/build/events.js.map +1 -0
- package/build/index.d.ts +14 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +13 -0
- package/build/index.js.map +1 -0
- package/build/mappers.d.ts +15 -0
- package/build/mappers.d.ts.map +1 -0
- package/build/mappers.js +149 -0
- package/build/mappers.js.map +1 -0
- package/build/types.d.ts +183 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/package.json +68 -0
- package/src/ExpoYtDlpModule.ts +41 -0
- package/src/ExpoYtDlpModule.web.ts +15 -0
- package/src/YtDlp.ts +151 -0
- package/src/constants.ts +11 -0
- package/src/downloadTask.ts +159 -0
- package/src/errors.ts +133 -0
- package/src/events.ts +116 -0
- package/src/index.ts +17 -0
- package/src/mappers.ts +168 -0
- package/src/types.ts +219 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NativeModule, registerWebModule } from 'expo';
|
|
2
|
+
import { SUPPORTED_PLATFORM_MESSAGE } from './constants';
|
|
3
|
+
/**
|
|
4
|
+
* Web placeholder. The package is Android-only (see AGENTS.md §3); every
|
|
5
|
+
* method throws a meaningful error instead of a silent no-op.
|
|
6
|
+
*/
|
|
7
|
+
class ExpoYtDlpModule extends NativeModule {
|
|
8
|
+
getVersion() {
|
|
9
|
+
throw new Error(SUPPORTED_PLATFORM_MESSAGE);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export default registerWebModule(ExpoYtDlpModule, 'ExpoYtDlp');
|
|
13
|
+
//# sourceMappingURL=ExpoYtDlpModule.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoYtDlpModule.web.js","sourceRoot":"","sources":["../src/ExpoYtDlpModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD;;;GAGG;AACH,MAAM,eAAgB,SAAQ,YAAY;IACxC,UAAU;QACR,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport { SUPPORTED_PLATFORM_MESSAGE } from './constants';\n\n/**\n * Web placeholder. The package is Android-only (see AGENTS.md §3); every\n * method throws a meaningful error instead of a silent no-op.\n */\nclass ExpoYtDlpModule extends NativeModule {\n getVersion(): Promise<string> {\n throw new Error(SUPPORTED_PLATFORM_MESSAGE);\n }\n}\n\nexport default registerWebModule(ExpoYtDlpModule, 'ExpoYtDlp');\n"]}
|
package/build/YtDlp.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DownloadOptions, DownloadTask, ExtractOptions, Format, VideoInfo, YtDlpVersion } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Read the yt-dlp version embedded in the native runtime plus this package's
|
|
4
|
+
* version. The two are independent (see AGENTS.md §35).
|
|
5
|
+
*/
|
|
6
|
+
export declare function getVersion(): Promise<YtDlpVersion>;
|
|
7
|
+
/**
|
|
8
|
+
* Extract normalized media information without downloading anything.
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractInfo(url: string, options?: ExtractOptions): Promise<VideoInfo>;
|
|
11
|
+
/**
|
|
12
|
+
* List usable download formats. Internally reuses the same extraction as
|
|
13
|
+
* `extractInfo` to avoid redundant work (see AGENTS.md §34).
|
|
14
|
+
*/
|
|
15
|
+
export declare function getFormats(url: string, options?: ExtractOptions): Promise<Format[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Start a download. Resolves with a [DownloadTask] immediately; progress and
|
|
18
|
+
* the final result arrive through the task's listeners (AGENTS.md §12).
|
|
19
|
+
*/
|
|
20
|
+
export declare function download(options: DownloadOptions): Promise<DownloadTask>;
|
|
21
|
+
/** Cancel a download by task id, e.g. after app re-creation (AGENTS.md §22). */
|
|
22
|
+
export declare function cancel(taskId: string): Promise<boolean>;
|
|
23
|
+
//# sourceMappingURL=YtDlp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YtDlp.d.ts","sourceRoot":"","sources":["../src/YtDlp.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,eAAe,EACf,YAAY,EACZ,cAAc,EACd,MAAM,EACN,SAAS,EACT,YAAY,EACb,MAAM,SAAS,CAAC;AAejB;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAQxD;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CAS3F;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGzF;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CAS9E;AAED,gFAAgF;AAChF,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7D"}
|
package/build/YtDlp.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { NativeExpoYtDlp } from './ExpoYtDlpModule';
|
|
2
|
+
import { LIBRARY_VERSION, SUPPORTED_PLATFORM_MESSAGE } from './constants';
|
|
3
|
+
import { YtDlpDownloadTask } from './downloadTask';
|
|
4
|
+
import { normalizeError, normalizeExtractionError, YtDlpError } from './errors';
|
|
5
|
+
import { mapVideoInfo } from './mappers';
|
|
6
|
+
function requireNative() {
|
|
7
|
+
if (!NativeExpoYtDlp) {
|
|
8
|
+
throw new YtDlpError('UNSUPPORTED_PLATFORM', SUPPORTED_PLATFORM_MESSAGE);
|
|
9
|
+
}
|
|
10
|
+
return NativeExpoYtDlp;
|
|
11
|
+
}
|
|
12
|
+
function validateUrl(url) {
|
|
13
|
+
if (!url || url.trim().length === 0) {
|
|
14
|
+
throw new YtDlpError('INVALID_URL', 'URL is required.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read the yt-dlp version embedded in the native runtime plus this package's
|
|
19
|
+
* version. The two are independent (see AGENTS.md §35).
|
|
20
|
+
*/
|
|
21
|
+
export async function getVersion() {
|
|
22
|
+
try {
|
|
23
|
+
const native = requireNative();
|
|
24
|
+
const ytDlp = await native.getVersion();
|
|
25
|
+
return { ytDlp, library: LIBRARY_VERSION };
|
|
26
|
+
}
|
|
27
|
+
catch (cause) {
|
|
28
|
+
throw normalizeError(cause);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract normalized media information without downloading anything.
|
|
33
|
+
*/
|
|
34
|
+
export async function extractInfo(url, options) {
|
|
35
|
+
validateUrl(url);
|
|
36
|
+
try {
|
|
37
|
+
const native = requireNative();
|
|
38
|
+
const json = await native.extractInfo(url, serializeExtractOptions(options));
|
|
39
|
+
return mapVideoInfo(JSON.parse(json));
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
throw normalizeExtractionError(cause);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* List usable download formats. Internally reuses the same extraction as
|
|
47
|
+
* `extractInfo` to avoid redundant work (see AGENTS.md §34).
|
|
48
|
+
*/
|
|
49
|
+
export async function getFormats(url, options) {
|
|
50
|
+
const info = await extractInfo(url, options);
|
|
51
|
+
return info.formats;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Start a download. Resolves with a [DownloadTask] immediately; progress and
|
|
55
|
+
* the final result arrive through the task's listeners (AGENTS.md §12).
|
|
56
|
+
*/
|
|
57
|
+
export async function download(options) {
|
|
58
|
+
validateUrl(options.url);
|
|
59
|
+
try {
|
|
60
|
+
const native = requireNative();
|
|
61
|
+
const info = await native.startDownload(serializeDownloadOptions(options));
|
|
62
|
+
return new YtDlpDownloadTask(native, info.taskId);
|
|
63
|
+
}
|
|
64
|
+
catch (cause) {
|
|
65
|
+
throw normalizeError(cause);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Cancel a download by task id, e.g. after app re-creation (AGENTS.md §22). */
|
|
69
|
+
export async function cancel(taskId) {
|
|
70
|
+
try {
|
|
71
|
+
const native = requireNative();
|
|
72
|
+
return await native.cancelDownload(taskId);
|
|
73
|
+
}
|
|
74
|
+
catch (cause) {
|
|
75
|
+
throw normalizeError(cause);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function serializeExtractOptions(options) {
|
|
79
|
+
if (!options)
|
|
80
|
+
return {};
|
|
81
|
+
const out = {};
|
|
82
|
+
if (options.cookies)
|
|
83
|
+
out.cookies = serializeCookies(options.cookies);
|
|
84
|
+
if (options.proxy)
|
|
85
|
+
out.proxy = options.proxy;
|
|
86
|
+
if (options.userAgent || options.headers) {
|
|
87
|
+
const headers = { ...(options.headers ?? {}) };
|
|
88
|
+
if (options.userAgent)
|
|
89
|
+
headers['User-Agent'] = options.userAgent;
|
|
90
|
+
out.headers = headers;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function serializeDownloadOptions(options) {
|
|
95
|
+
const out = { url: options.url };
|
|
96
|
+
if (options.format)
|
|
97
|
+
out.format = options.format;
|
|
98
|
+
if (options.output) {
|
|
99
|
+
const output = {};
|
|
100
|
+
if (options.output.directory)
|
|
101
|
+
output.directory = options.output.directory;
|
|
102
|
+
if (options.output.filename)
|
|
103
|
+
output.filename = options.output.filename;
|
|
104
|
+
if (Object.keys(output).length > 0)
|
|
105
|
+
out.output = output;
|
|
106
|
+
}
|
|
107
|
+
if (options.merge != null)
|
|
108
|
+
out.merge = options.merge;
|
|
109
|
+
if (options.cookies)
|
|
110
|
+
out.cookies = serializeCookies(options.cookies);
|
|
111
|
+
if (options.userAgent || options.headers) {
|
|
112
|
+
const headers = { ...(options.headers ?? {}) };
|
|
113
|
+
if (options.userAgent)
|
|
114
|
+
headers['User-Agent'] = options.userAgent;
|
|
115
|
+
out.headers = headers;
|
|
116
|
+
}
|
|
117
|
+
if (options.referer)
|
|
118
|
+
out.referer = options.referer;
|
|
119
|
+
if (options.proxy)
|
|
120
|
+
out.proxy = options.proxy;
|
|
121
|
+
if (options.playlist) {
|
|
122
|
+
const playlist = {};
|
|
123
|
+
if (options.playlist.enabled != null)
|
|
124
|
+
playlist.enabled = options.playlist.enabled;
|
|
125
|
+
if (options.playlist.start != null)
|
|
126
|
+
playlist.start = options.playlist.start;
|
|
127
|
+
if (options.playlist.end != null)
|
|
128
|
+
playlist.end = options.playlist.end;
|
|
129
|
+
if (Object.keys(playlist).length > 0)
|
|
130
|
+
out.playlist = playlist;
|
|
131
|
+
}
|
|
132
|
+
if (options.network) {
|
|
133
|
+
const network = {};
|
|
134
|
+
if (options.network.timeout != null)
|
|
135
|
+
network.timeout = options.network.timeout;
|
|
136
|
+
if (options.network.retries != null)
|
|
137
|
+
network.retries = options.network.retries;
|
|
138
|
+
if (Object.keys(network).length > 0)
|
|
139
|
+
out.network = network;
|
|
140
|
+
}
|
|
141
|
+
if (options.subtitles) {
|
|
142
|
+
const subtitles = {};
|
|
143
|
+
if (options.subtitles.enabled != null)
|
|
144
|
+
subtitles.enabled = options.subtitles.enabled;
|
|
145
|
+
if (options.subtitles.languages?.length)
|
|
146
|
+
subtitles.languages = options.subtitles.languages;
|
|
147
|
+
if (options.subtitles.autoGenerated != null)
|
|
148
|
+
subtitles.autoGenerated = options.subtitles.autoGenerated;
|
|
149
|
+
if (Object.keys(subtitles).length > 0)
|
|
150
|
+
out.subtitles = subtitles;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function serializeCookies(cookies) {
|
|
155
|
+
if (cookies.source === 'file' && cookies.path) {
|
|
156
|
+
return { path: cookies.path };
|
|
157
|
+
}
|
|
158
|
+
throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=YtDlp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YtDlp.js","sourceRoot":"","sources":["../src/YtDlp.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeExpoYtDlp, type ExpoYtDlpNativeModule } from './ExpoYtDlpModule';\nimport { LIBRARY_VERSION, SUPPORTED_PLATFORM_MESSAGE } from './constants';\nimport { YtDlpDownloadTask } from './downloadTask';\nimport { normalizeError, normalizeExtractionError, YtDlpError } from './errors';\nimport { mapVideoInfo } from './mappers';\nimport type {\n CookieOptions,\n DownloadOptions,\n DownloadTask,\n ExtractOptions,\n Format,\n VideoInfo,\n YtDlpVersion,\n} from './types';\n\nfunction requireNative(): ExpoYtDlpNativeModule {\n if (!NativeExpoYtDlp) {\n throw new YtDlpError('UNSUPPORTED_PLATFORM', SUPPORTED_PLATFORM_MESSAGE);\n }\n return NativeExpoYtDlp;\n}\n\nfunction validateUrl(url: string | undefined): void {\n if (!url || url.trim().length === 0) {\n throw new YtDlpError('INVALID_URL', 'URL is required.');\n }\n}\n\n/**\n * Read the yt-dlp version embedded in the native runtime plus this package's\n * version. The two are independent (see AGENTS.md §35).\n */\nexport async function getVersion(): Promise<YtDlpVersion> {\n try {\n const native = requireNative();\n const ytDlp = await native.getVersion();\n return { ytDlp, library: LIBRARY_VERSION };\n } catch (cause) {\n throw normalizeError(cause);\n }\n}\n\n/**\n * Extract normalized media information without downloading anything.\n */\nexport async function extractInfo(url: string, options?: ExtractOptions): Promise<VideoInfo> {\n validateUrl(url);\n try {\n const native = requireNative();\n const json = await native.extractInfo(url, serializeExtractOptions(options));\n return mapVideoInfo(JSON.parse(json));\n } catch (cause) {\n throw normalizeExtractionError(cause);\n }\n}\n\n/**\n * List usable download formats. Internally reuses the same extraction as\n * `extractInfo` to avoid redundant work (see AGENTS.md §34).\n */\nexport async function getFormats(url: string, options?: ExtractOptions): Promise<Format[]> {\n const info = await extractInfo(url, options);\n return info.formats;\n}\n\n/**\n * Start a download. Resolves with a [DownloadTask] immediately; progress and\n * the final result arrive through the task's listeners (AGENTS.md §12).\n */\nexport async function download(options: DownloadOptions): Promise<DownloadTask> {\n validateUrl(options.url);\n try {\n const native = requireNative();\n const info = await native.startDownload(serializeDownloadOptions(options));\n return new YtDlpDownloadTask(native, info.taskId);\n } catch (cause) {\n throw normalizeError(cause);\n }\n}\n\n/** Cancel a download by task id, e.g. after app re-creation (AGENTS.md §22). */\nexport async function cancel(taskId: string): Promise<boolean> {\n try {\n const native = requireNative();\n return await native.cancelDownload(taskId);\n } catch (cause) {\n throw normalizeError(cause);\n }\n}\n\nfunction serializeExtractOptions(options: ExtractOptions | undefined): 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\nfunction 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.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\nfunction 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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-level constants.
|
|
3
|
+
*/
|
|
4
|
+
export declare const LIBRARY_VERSION = "0.1.0";
|
|
5
|
+
export declare const SUPPORTED_PLATFORM = "android";
|
|
6
|
+
export declare const SUPPORTED_PLATFORM_MESSAGE = "expo-ytdlp-native is currently supported on android only.";
|
|
7
|
+
/** yt-dlp output template used when the caller does not provide a filename. */
|
|
8
|
+
export declare const DEFAULT_FILENAME_TEMPLATE = "%(title)s.%(ext)s";
|
|
9
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C,eAAO,MAAM,0BAA0B,8DAAiF,CAAC;AAEzH,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-level constants.
|
|
3
|
+
*/
|
|
4
|
+
export const LIBRARY_VERSION = '0.1.0';
|
|
5
|
+
export const SUPPORTED_PLATFORM = 'android';
|
|
6
|
+
export const SUPPORTED_PLATFORM_MESSAGE = `${'expo-ytdlp-native'} is currently supported on ${SUPPORTED_PLATFORM} only.`;
|
|
7
|
+
/** yt-dlp output template used when the caller does not provide a filename. */
|
|
8
|
+
export const DEFAULT_FILENAME_TEMPLATE = '%(title)s.%(ext)s';
|
|
9
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AAEvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE5C,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,mBAAmB,8BAA8B,kBAAkB,QAAQ,CAAC;AAEzH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,yBAAyB,GAAG,mBAAmB,CAAC","sourcesContent":["/**\n * Package-level constants.\n */\nexport const LIBRARY_VERSION = '0.1.0';\n\nexport const SUPPORTED_PLATFORM = 'android';\n\nexport const SUPPORTED_PLATFORM_MESSAGE = `${'expo-ytdlp-native'} is currently supported on ${SUPPORTED_PLATFORM} only.`;\n\n/** yt-dlp output template used when the caller does not provide a filename. */\nexport const DEFAULT_FILENAME_TEMPLATE = '%(title)s.%(ext)s';\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS-side `DownloadTask` implementation.
|
|
3
|
+
*
|
|
4
|
+
* Keeps this module dependency-light: all per-task state lives in the native
|
|
5
|
+
* task registry; the JS object only relays events and forwards commands
|
|
6
|
+
* (AGENTS.md §17, §47).
|
|
7
|
+
*/
|
|
8
|
+
import type { ExpoYtDlpNativeModule } from './ExpoYtDlpModule';
|
|
9
|
+
import { YtDlpError } from './errors';
|
|
10
|
+
import type { DownloadProgress, DownloadResult, DownloadStateEvent, DownloadStatus, DownloadTask, Subscription } from './types';
|
|
11
|
+
type Listener<T> = (value: T) => void;
|
|
12
|
+
export declare class YtDlpDownloadTask implements DownloadTask {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
private readonly native;
|
|
15
|
+
private currentStatus;
|
|
16
|
+
private finalized;
|
|
17
|
+
private readonly listeners;
|
|
18
|
+
private subscriptions;
|
|
19
|
+
constructor(native: ExpoYtDlpNativeModule, id: string);
|
|
20
|
+
cancel(): Promise<void>;
|
|
21
|
+
getStatus(): Promise<DownloadStatus>;
|
|
22
|
+
getProgress(): Promise<DownloadProgress | null>;
|
|
23
|
+
addListener(event: 'progress', listener: Listener<DownloadProgress>): Subscription;
|
|
24
|
+
addListener(event: 'state', listener: Listener<DownloadStateEvent>): Subscription;
|
|
25
|
+
addListener(event: 'completed', listener: Listener<DownloadResult>): Subscription;
|
|
26
|
+
addListener(event: 'error', listener: Listener<YtDlpError>): Subscription;
|
|
27
|
+
private teardown;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=downloadTask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadTask.d.ts","sourceRoot":"","sources":["../src/downloadTask.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAsB,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAStC,qBAAa,iBAAkB,YAAW,YAAY;IACpD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAKxB;IACF,OAAO,CAAC,aAAa,CAAsB;gBAE/B,MAAM,EAAE,qBAAqB,EAAE,EAAE,EAAE,MAAM;IAgC/C,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC;IAOpC,WAAW,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAMrD,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,YAAY;IAClF,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,YAAY;IACjF,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,YAAY;IACjF,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY;IAyBzE,OAAO,CAAC,QAAQ;CAIjB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { attachTaskListeners } from './events';
|
|
2
|
+
export class YtDlpDownloadTask {
|
|
3
|
+
id;
|
|
4
|
+
native;
|
|
5
|
+
currentStatus = 'queued';
|
|
6
|
+
finalized = false;
|
|
7
|
+
listeners = {
|
|
8
|
+
progress: new Set(),
|
|
9
|
+
state: new Set(),
|
|
10
|
+
completed: new Set(),
|
|
11
|
+
error: new Set(),
|
|
12
|
+
};
|
|
13
|
+
subscriptions = [];
|
|
14
|
+
constructor(native, id) {
|
|
15
|
+
this.native = native;
|
|
16
|
+
this.id = id;
|
|
17
|
+
this.subscriptions = attachTaskListeners(id, {
|
|
18
|
+
progress: (progress) => {
|
|
19
|
+
this.currentStatus = progress.status;
|
|
20
|
+
this.listeners.progress.forEach((listener) => listener(progress));
|
|
21
|
+
},
|
|
22
|
+
state: (state) => {
|
|
23
|
+
this.currentStatus = state.status;
|
|
24
|
+
this.listeners.state.forEach((listener) => listener(state));
|
|
25
|
+
},
|
|
26
|
+
completed: (result) => {
|
|
27
|
+
this.finalized = true;
|
|
28
|
+
this.currentStatus = 'completed';
|
|
29
|
+
this.teardown();
|
|
30
|
+
this.listeners.completed.forEach((listener) => listener(result));
|
|
31
|
+
},
|
|
32
|
+
cancelled: () => {
|
|
33
|
+
this.finalized = true;
|
|
34
|
+
this.currentStatus = 'cancelled';
|
|
35
|
+
this.teardown();
|
|
36
|
+
},
|
|
37
|
+
error: (error) => {
|
|
38
|
+
this.finalized = true;
|
|
39
|
+
this.currentStatus = 'failed';
|
|
40
|
+
this.teardown();
|
|
41
|
+
this.listeners.error.forEach((listener) => listener(error));
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async cancel() {
|
|
46
|
+
await this.native.cancelDownload(this.id);
|
|
47
|
+
}
|
|
48
|
+
async getStatus() {
|
|
49
|
+
if (this.finalized)
|
|
50
|
+
return this.currentStatus;
|
|
51
|
+
const info = await this.native.getDownloadStatus(this.id);
|
|
52
|
+
if (info?.status)
|
|
53
|
+
this.currentStatus = info.status;
|
|
54
|
+
return this.currentStatus;
|
|
55
|
+
}
|
|
56
|
+
async getProgress() {
|
|
57
|
+
const info = await this.native.getDownloadStatus(this.id);
|
|
58
|
+
if (!info)
|
|
59
|
+
return null;
|
|
60
|
+
return mapStatusInfo(this.id, this.currentStatus, info);
|
|
61
|
+
}
|
|
62
|
+
addListener(event, listener) {
|
|
63
|
+
const set = event === 'progress'
|
|
64
|
+
? this.listeners.progress
|
|
65
|
+
: event === 'state'
|
|
66
|
+
? this.listeners.state
|
|
67
|
+
: event === 'completed'
|
|
68
|
+
? this.listeners.completed
|
|
69
|
+
: this.listeners.error;
|
|
70
|
+
set.add(listener);
|
|
71
|
+
return {
|
|
72
|
+
remove: () => {
|
|
73
|
+
set.delete(listener);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
teardown() {
|
|
78
|
+
this.subscriptions.forEach((subscription) => subscription.remove());
|
|
79
|
+
this.subscriptions = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function mapStatusInfo(taskId, fallbackStatus, info) {
|
|
83
|
+
return {
|
|
84
|
+
taskId,
|
|
85
|
+
status: (info.status ?? fallbackStatus),
|
|
86
|
+
phase: phaseOf(info.status),
|
|
87
|
+
percent: finiteOrUndefined(info.percent),
|
|
88
|
+
downloadedBytes: finiteOrUndefined(info.downloadedBytes),
|
|
89
|
+
totalBytes: finiteOrUndefined(info.totalBytes),
|
|
90
|
+
speedBytesPerSecond: finiteOrUndefined(info.speedBytesPerSecond),
|
|
91
|
+
etaSeconds: finiteOrUndefined(info.etaSeconds),
|
|
92
|
+
filename: info.filename,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function phaseOf(status) {
|
|
96
|
+
switch (status) {
|
|
97
|
+
case 'downloading':
|
|
98
|
+
return 'downloading';
|
|
99
|
+
case 'completed':
|
|
100
|
+
case 'processing':
|
|
101
|
+
return 'processing';
|
|
102
|
+
default:
|
|
103
|
+
return 'extracting';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function finiteOrUndefined(value) {
|
|
107
|
+
if (value === undefined || value === null)
|
|
108
|
+
return undefined;
|
|
109
|
+
return Number.isFinite(value) ? value : undefined;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=downloadTask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadTask.js","sourceRoot":"","sources":["../src/downloadTask.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * JS-side `DownloadTask` implementation.\n *\n * Keeps this module dependency-light: all per-task state lives in the native\n * task registry; the JS object only relays events and forwards commands\n * (AGENTS.md §17, §47).\n */\nimport type { DownloadStatusInfo, ExpoYtDlpNativeModule } from './ExpoYtDlpModule';\nimport { YtDlpError } from './errors';\nimport { attachTaskListeners } from './events';\nimport type {\n DownloadProgress,\n DownloadResult,\n DownloadStateEvent,\n DownloadStatus,\n DownloadTask,\n Subscription,\n} from './types';\n\ntype Listener<T> = (value: T) => void;\n\ninterface TaskListenerSets {\n progress: Set<Listener<DownloadProgress>>;\n state: Set<Listener<DownloadStateEvent>>;\n completed: Set<Listener<DownloadResult>>;\n error: Set<Listener<YtDlpError>>;\n}\n\nexport class YtDlpDownloadTask implements DownloadTask {\n readonly id: string;\n\n private readonly native: ExpoYtDlpNativeModule;\n private currentStatus: DownloadStatus = 'queued';\n private finalized = false;\n private readonly listeners: TaskListenerSets = {\n progress: new Set(),\n state: new Set(),\n completed: new Set(),\n error: new Set(),\n };\n private subscriptions: Subscription[] = [];\n\n constructor(native: ExpoYtDlpNativeModule, id: string) {\n this.native = native;\n this.id = id;\n this.subscriptions = attachTaskListeners(id, {\n progress: (progress) => {\n this.currentStatus = progress.status;\n this.listeners.progress.forEach((listener) => listener(progress));\n },\n state: (state) => {\n this.currentStatus = state.status;\n this.listeners.state.forEach((listener) => listener(state));\n },\n completed: (result) => {\n this.finalized = true;\n this.currentStatus = 'completed';\n this.teardown();\n this.listeners.completed.forEach((listener) => listener(result));\n },\n cancelled: () => {\n this.finalized = true;\n this.currentStatus = 'cancelled';\n this.teardown();\n },\n error: (error) => {\n this.finalized = true;\n this.currentStatus = 'failed';\n this.teardown();\n this.listeners.error.forEach((listener) => listener(error));\n },\n });\n }\n\n async cancel(): Promise<void> {\n await this.native.cancelDownload(this.id);\n }\n\n async getStatus(): Promise<DownloadStatus> {\n if (this.finalized) return this.currentStatus;\n const info = await this.native.getDownloadStatus(this.id);\n if (info?.status) this.currentStatus = info.status;\n return this.currentStatus;\n }\n\n async getProgress(): Promise<DownloadProgress | null> {\n const info = await this.native.getDownloadStatus(this.id);\n if (!info) return null;\n return mapStatusInfo(this.id, this.currentStatus, info);\n }\n\n addListener(event: 'progress', listener: Listener<DownloadProgress>): Subscription;\n addListener(event: 'state', listener: Listener<DownloadStateEvent>): Subscription;\n addListener(event: 'completed', listener: Listener<DownloadResult>): Subscription;\n addListener(event: 'error', listener: Listener<YtDlpError>): Subscription;\n addListener(\n event: 'progress' | 'state' | 'completed' | 'error',\n listener:\n | Listener<DownloadProgress>\n | Listener<DownloadStateEvent>\n | Listener<DownloadResult>\n | Listener<YtDlpError>\n ): Subscription {\n const set =\n event === 'progress'\n ? this.listeners.progress\n : event === 'state'\n ? this.listeners.state\n : event === 'completed'\n ? this.listeners.completed\n : this.listeners.error;\n set.add(listener as never);\n return {\n remove: () => {\n set.delete(listener as never);\n },\n };\n }\n\n private teardown() {\n this.subscriptions.forEach((subscription) => subscription.remove());\n this.subscriptions = [];\n }\n}\n\nfunction mapStatusInfo(\n taskId: string,\n fallbackStatus: DownloadStatus,\n info: DownloadStatusInfo\n): DownloadProgress {\n return {\n taskId,\n status: (info.status ?? fallbackStatus) as DownloadStatus,\n phase: phaseOf(info.status),\n percent: finiteOrUndefined(info.percent),\n downloadedBytes: finiteOrUndefined(info.downloadedBytes),\n totalBytes: finiteOrUndefined(info.totalBytes),\n speedBytesPerSecond: finiteOrUndefined(info.speedBytesPerSecond),\n etaSeconds: finiteOrUndefined(info.etaSeconds),\n filename: info.filename,\n };\n}\n\nfunction phaseOf(status: DownloadStatus): DownloadProgress['phase'] {\n switch (status) {\n case 'downloading':\n return 'downloading';\n case 'completed':\n case 'processing':\n return 'processing';\n default:\n return 'extracting';\n }\n}\n\nfunction finiteOrUndefined(value: number | undefined): number | undefined {\n if (value === undefined || value === null) return undefined;\n return Number.isFinite(value) ? value : undefined;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { YtDlpErrorCode } from './types';
|
|
2
|
+
export declare class YtDlpError extends Error {
|
|
3
|
+
readonly code: YtDlpErrorCode;
|
|
4
|
+
readonly taskId?: string;
|
|
5
|
+
readonly cause?: unknown;
|
|
6
|
+
constructor(code: YtDlpErrorCode, message: string, options?: {
|
|
7
|
+
taskId?: string;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes anything thrown by the native layer into a `YtDlpError`.
|
|
13
|
+
* Unknown/missing codes map to `UNKNOWN`; raw stack traces are never surfaced.
|
|
14
|
+
*/
|
|
15
|
+
export declare function normalizeError(cause: unknown, fallbackMessage?: string): YtDlpError;
|
|
16
|
+
/**
|
|
17
|
+
* Builds a `YtDlpError` from a code+message pair received from native events.
|
|
18
|
+
* Unknown codes degrade to `UNKNOWN` (AGENTS.md §23).
|
|
19
|
+
*/
|
|
20
|
+
export declare function fromNativeCode(code: string, message: string): YtDlpError;
|
|
21
|
+
/**
|
|
22
|
+
* Refines an extraction failure into a more specific code by matching the
|
|
23
|
+
* yt-dlp error message. Kept conservative: no match falls back to the
|
|
24
|
+
* normalized code.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeExtractionError(cause: unknown, fallbackMessage?: string): YtDlpError;
|
|
27
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAK9C,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBAGvB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQjD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,eAAe,SAAyB,GACvC,UAAU,CAkBZ;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAExE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,eAAe,SAAwC,GACtD,UAAU,CAiCZ"}
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** Prefix used by the Kotlin layer to carry error codes across the bridge. */
|
|
2
|
+
const NATIVE_PREFIX = 'YTD_NATIVE|';
|
|
3
|
+
export class YtDlpError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
taskId;
|
|
6
|
+
cause;
|
|
7
|
+
constructor(code, message, options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'YtDlpError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
if (options?.taskId)
|
|
12
|
+
this.taskId = options.taskId;
|
|
13
|
+
if (options?.cause !== undefined)
|
|
14
|
+
this.cause = options.cause;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes anything thrown by the native layer into a `YtDlpError`.
|
|
19
|
+
* Unknown/missing codes map to `UNKNOWN`; raw stack traces are never surfaced.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeError(cause, fallbackMessage = 'Unknown yt-dlp error') {
|
|
22
|
+
if (cause instanceof YtDlpError)
|
|
23
|
+
return cause;
|
|
24
|
+
if (cause instanceof Error) {
|
|
25
|
+
const parsed = parseNativeMessage(cause.message);
|
|
26
|
+
if (parsed) {
|
|
27
|
+
return new YtDlpError(parsed.code, parsed.message, { cause });
|
|
28
|
+
}
|
|
29
|
+
return new YtDlpError('UNKNOWN', cause.message || fallbackMessage, { cause });
|
|
30
|
+
}
|
|
31
|
+
if (typeof cause === 'string') {
|
|
32
|
+
const parsed = parseNativeMessage(cause);
|
|
33
|
+
if (parsed)
|
|
34
|
+
return new YtDlpError(parsed.code, parsed.message);
|
|
35
|
+
return new YtDlpError('UNKNOWN', cause || fallbackMessage);
|
|
36
|
+
}
|
|
37
|
+
return new YtDlpError('UNKNOWN', fallbackMessage, { cause });
|
|
38
|
+
}
|
|
39
|
+
function parseNativeMessage(message) {
|
|
40
|
+
if (!message.startsWith(NATIVE_PREFIX))
|
|
41
|
+
return null;
|
|
42
|
+
const rest = message.slice(NATIVE_PREFIX.length);
|
|
43
|
+
const separator = rest.indexOf('|');
|
|
44
|
+
if (separator === -1)
|
|
45
|
+
return null;
|
|
46
|
+
const rawCode = rest.slice(0, separator);
|
|
47
|
+
const text = rest.slice(separator + 1);
|
|
48
|
+
const code = toKnownCode(rawCode);
|
|
49
|
+
return { code, message: text || rawCode };
|
|
50
|
+
}
|
|
51
|
+
function toKnownCode(rawCode) {
|
|
52
|
+
const known = [
|
|
53
|
+
'INVALID_URL',
|
|
54
|
+
'EXTRACTION_FAILED',
|
|
55
|
+
'DOWNLOAD_FAILED',
|
|
56
|
+
'CANCELLED',
|
|
57
|
+
'FORMAT_UNAVAILABLE',
|
|
58
|
+
'NETWORK_ERROR',
|
|
59
|
+
'AUTHENTICATION_REQUIRED',
|
|
60
|
+
'GEO_RESTRICTED',
|
|
61
|
+
'PRIVATE_CONTENT',
|
|
62
|
+
'AGE_RESTRICTED',
|
|
63
|
+
'PROCESSING_FAILED',
|
|
64
|
+
'STORAGE_ERROR',
|
|
65
|
+
'INIT_FAILED',
|
|
66
|
+
'UNSUPPORTED_PLATFORM',
|
|
67
|
+
'UNKNOWN',
|
|
68
|
+
];
|
|
69
|
+
if (known.includes(rawCode))
|
|
70
|
+
return rawCode;
|
|
71
|
+
return 'UNKNOWN';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Builds a `YtDlpError` from a code+message pair received from native events.
|
|
75
|
+
* Unknown codes degrade to `UNKNOWN` (AGENTS.md §23).
|
|
76
|
+
*/
|
|
77
|
+
export function fromNativeCode(code, message) {
|
|
78
|
+
return new YtDlpError(toKnownCode(code), message);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Refines an extraction failure into a more specific code by matching the
|
|
82
|
+
* yt-dlp error message. Kept conservative: no match falls back to the
|
|
83
|
+
* normalized code.
|
|
84
|
+
*/
|
|
85
|
+
export function normalizeExtractionError(cause, fallbackMessage = 'Failed to extract media information') {
|
|
86
|
+
const base = normalizeError(cause, fallbackMessage);
|
|
87
|
+
if (base.code !== 'EXTRACTION_FAILED' && base.code !== 'UNKNOWN')
|
|
88
|
+
return base;
|
|
89
|
+
const message = base.message.toLowerCase();
|
|
90
|
+
if (/http error|connection|timed out|unreachable|name or service not known|failed to establish|reset by peer|couldn't connect|503|429/.test(message)) {
|
|
91
|
+
return new YtDlpError('NETWORK_ERROR', base.message, { cause: base.cause });
|
|
92
|
+
}
|
|
93
|
+
if (message.includes('sign in to confirm') ||
|
|
94
|
+
message.includes('log in') ||
|
|
95
|
+
message.includes('sign up')) {
|
|
96
|
+
return new YtDlpError('AUTHENTICATION_REQUIRED', base.message, { cause: base.cause });
|
|
97
|
+
}
|
|
98
|
+
if (message.includes('private') || message.includes('members-only')) {
|
|
99
|
+
return new YtDlpError('PRIVATE_CONTENT', base.message, { cause: base.cause });
|
|
100
|
+
}
|
|
101
|
+
if (message.includes('age') || message.includes('mature') || message.includes('under 18')) {
|
|
102
|
+
return new YtDlpError('AGE_RESTRICTED', base.message, { cause: base.cause });
|
|
103
|
+
}
|
|
104
|
+
if (message.includes('geographic') ||
|
|
105
|
+
message.includes('geo-') ||
|
|
106
|
+
message.includes('not available in your country')) {
|
|
107
|
+
return new YtDlpError('GEO_RESTRICTED', base.message, { cause: base.cause });
|
|
108
|
+
}
|
|
109
|
+
return base;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC,MAAM,OAAO,UAAW,SAAQ,KAAK;IAC1B,IAAI,CAAiB;IACrB,MAAM,CAAU;IAChB,KAAK,CAAW;IAEzB,YACE,IAAoB,EACpB,OAAe,EACf,OAA8C;QAE9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,OAAO,EAAE,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAClD,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC/D,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAc,EACd,eAAe,GAAG,sBAAsB;IAExC,IAAI,KAAK,YAAY,UAAU;QAAE,OAAO,KAAK,CAAC;IAE9C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,IAAI,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,MAAM;YAAE,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/D,OAAO,IAAI,UAAU,CAAC,SAAS,EAAE,KAAK,IAAI,eAAe,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,IAAI,UAAU,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,SAAS,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,CAAC;AAC5C,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,KAAK,GAAqB;QAC9B,aAAa;QACb,mBAAmB;QACnB,iBAAiB;QACjB,WAAW;QACX,oBAAoB;QACpB,eAAe;QACf,yBAAyB;QACzB,gBAAgB;QAChB,iBAAiB;QACjB,gBAAgB;QAChB,mBAAmB;QACnB,eAAe;QACf,aAAa;QACb,sBAAsB;QACtB,SAAS;KACV,CAAC;IACF,IAAK,KAA2B,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAyB,CAAC;IACrF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAe;IAC1D,OAAO,IAAI,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAc,EACd,eAAe,GAAG,qCAAqC;IAEvD,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IACpD,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAE9E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAC3C,IACE,kIAAkI,CAAC,IAAI,CACrI,OAAO,CACR,EACD,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,eAAe,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,IACE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC3B,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,yBAAyB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QACpE,OAAO,IAAI,UAAU,CAAC,iBAAiB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1F,OAAO,IAAI,UAAU,CAAC,gBAAgB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IACE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QACxB,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EACjD,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,gBAAgB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import type { YtDlpErrorCode } from './types';\n\n/** Prefix used by the Kotlin layer to carry error codes across the bridge. */\nconst NATIVE_PREFIX = 'YTD_NATIVE|';\n\nexport class YtDlpError extends Error {\n readonly code: YtDlpErrorCode;\n readonly taskId?: string;\n readonly cause?: unknown;\n\n constructor(\n code: YtDlpErrorCode,\n message: string,\n options?: { taskId?: string; cause?: unknown }\n ) {\n super(message);\n this.name = 'YtDlpError';\n this.code = code;\n if (options?.taskId) this.taskId = options.taskId;\n if (options?.cause !== undefined) this.cause = options.cause;\n }\n}\n\n/**\n * Normalizes anything thrown by the native layer into a `YtDlpError`.\n * Unknown/missing codes map to `UNKNOWN`; raw stack traces are never surfaced.\n */\nexport function normalizeError(\n cause: unknown,\n fallbackMessage = 'Unknown yt-dlp error'\n): YtDlpError {\n if (cause instanceof YtDlpError) return cause;\n\n if (cause instanceof Error) {\n const parsed = parseNativeMessage(cause.message);\n if (parsed) {\n return new YtDlpError(parsed.code, parsed.message, { cause });\n }\n return new YtDlpError('UNKNOWN', cause.message || fallbackMessage, { cause });\n }\n\n if (typeof cause === 'string') {\n const parsed = parseNativeMessage(cause);\n if (parsed) return new YtDlpError(parsed.code, parsed.message);\n return new YtDlpError('UNKNOWN', cause || fallbackMessage);\n }\n\n return new YtDlpError('UNKNOWN', fallbackMessage, { cause });\n}\n\nfunction parseNativeMessage(message: string): { code: YtDlpErrorCode; message: string } | null {\n if (!message.startsWith(NATIVE_PREFIX)) return null;\n const rest = message.slice(NATIVE_PREFIX.length);\n const separator = rest.indexOf('|');\n if (separator === -1) return null;\n const rawCode = rest.slice(0, separator);\n const text = rest.slice(separator + 1);\n const code = toKnownCode(rawCode);\n return { code, message: text || rawCode };\n}\n\nfunction toKnownCode(rawCode: string): YtDlpErrorCode {\n const known: 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 ];\n if ((known as readonly string[]).includes(rawCode)) return rawCode as YtDlpErrorCode;\n return 'UNKNOWN';\n}\n\n/**\n * Builds a `YtDlpError` from a code+message pair received from native events.\n * Unknown codes degrade to `UNKNOWN` (AGENTS.md §23).\n */\nexport function fromNativeCode(code: string, message: string): YtDlpError {\n return new YtDlpError(toKnownCode(code), message);\n}\n\n/**\n * Refines an extraction failure into a more specific code by matching the\n * yt-dlp error message. Kept conservative: no match falls back to the\n * normalized code.\n */\nexport function normalizeExtractionError(\n cause: unknown,\n fallbackMessage = 'Failed to extract media information'\n): YtDlpError {\n const base = normalizeError(cause, fallbackMessage);\n if (base.code !== 'EXTRACTION_FAILED' && base.code !== 'UNKNOWN') return base;\n\n const message = base.message.toLowerCase();\n if (\n /http error|connection|timed out|unreachable|name or service not known|failed to establish|reset by peer|couldn't connect|503|429/.test(\n message\n )\n ) {\n return new YtDlpError('NETWORK_ERROR', base.message, { cause: base.cause });\n }\n if (\n message.includes('sign in to confirm') ||\n message.includes('log in') ||\n message.includes('sign up')\n ) {\n return new YtDlpError('AUTHENTICATION_REQUIRED', base.message, { cause: base.cause });\n }\n if (message.includes('private') || message.includes('members-only')) {\n return new YtDlpError('PRIVATE_CONTENT', base.message, { cause: base.cause });\n }\n if (message.includes('age') || message.includes('mature') || message.includes('under 18')) {\n return new YtDlpError('AGE_RESTRICTED', base.message, { cause: base.cause });\n }\n if (\n message.includes('geographic') ||\n message.includes('geo-') ||\n message.includes('not available in your country')\n ) {\n return new YtDlpError('GEO_RESTRICTED', base.message, { cause: base.cause });\n }\n return base;\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type YtDlpError } from './errors';
|
|
2
|
+
import type { DownloadEvent, DownloadProgress, DownloadResult, DownloadStateEvent, Subscription } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a per-task subscription backed by the native event stream.
|
|
5
|
+
* `taskId` is used to filter events that belong to another task.
|
|
6
|
+
*/
|
|
7
|
+
export declare function subscribeToTask(taskId: string, handler: (event: DownloadEvent) => void): Subscription;
|
|
8
|
+
export declare function subscribeToDownloadEvents(handler: (event: DownloadEvent) => void): Subscription;
|
|
9
|
+
/**
|
|
10
|
+
* Adds task-level listeners to a [DownloadTask] from a raw subscription
|
|
11
|
+
* factory. Keeps the event filtering in one place.
|
|
12
|
+
*/
|
|
13
|
+
export declare function attachTaskListeners(taskId: string, listeners: {
|
|
14
|
+
progress?: (progress: DownloadProgress) => void;
|
|
15
|
+
state?: (state: DownloadStateEvent) => void;
|
|
16
|
+
completed?: (result: DownloadResult) => void;
|
|
17
|
+
cancelled?: () => void;
|
|
18
|
+
error?: (error: YtDlpError) => void;
|
|
19
|
+
}): Subscription[];
|
|
20
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACb,MAAM,SAAS,CAAC;AAIjB;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GACtC,YAAY,CAMd;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,YAAY,CAe/F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE;IACT,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACrC,GACA,YAAY,EAAE,CA8BhB"}
|