@irithell-js/yt-play 0.2.8 → 0.3.3

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/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Readable } from 'node:stream';
2
+
1
3
  type MediaType = "audio" | "video";
2
4
  interface PlayMetadata {
3
5
  title: string;
@@ -16,6 +18,11 @@ interface DownloadInfo {
16
18
  quality: string;
17
19
  downloadUrl: string;
18
20
  }
21
+ interface StreamInfo {
22
+ stream: Readable;
23
+ quality: string;
24
+ format: string;
25
+ }
19
26
  interface CachedFile {
20
27
  path: string;
21
28
  size: number;
@@ -54,6 +61,65 @@ interface PlayEngineOptions {
54
61
  error?: (...args: any[]) => void;
55
62
  };
56
63
  }
64
+ interface StalkChannelOptions {
65
+ flat?: boolean;
66
+ tab?: "videos" | "shorts" | "streams" | "popular" | "featured";
67
+ startItem?: number;
68
+ endItem?: number;
69
+ playlistItems?: string;
70
+ }
71
+ interface YtDlpThumbnail {
72
+ url: string;
73
+ width?: number;
74
+ height?: number;
75
+ id?: string;
76
+ }
77
+ interface YtDlpVideoMetadata {
78
+ _type: "video";
79
+ id: string;
80
+ title: string;
81
+ description?: string;
82
+ channel: string;
83
+ channel_id: string;
84
+ channel_follower_count?: number;
85
+ duration?: number;
86
+ view_count?: number;
87
+ like_count?: number;
88
+ comment_count?: number;
89
+ tags?: string[];
90
+ categories?: string[];
91
+ upload_date?: string;
92
+ timestamp?: number;
93
+ thumbnails?: YtDlpThumbnail[];
94
+ webpage_url: string;
95
+ is_live?: boolean;
96
+ was_live?: boolean;
97
+ concurrent_view_count?: number;
98
+ live_status?: "is_live" | "was_live" | "is_upcoming" | "not_live";
99
+ release_timestamp?: number;
100
+ }
101
+ interface YtDlpFlatEntry {
102
+ _type: "url" | "url_transparent";
103
+ id: string;
104
+ title: string;
105
+ url: string;
106
+ duration?: number;
107
+ view_count?: number;
108
+ channel?: string;
109
+ }
110
+ interface YtDlpChannelMetadata {
111
+ _type: "playlist";
112
+ id: string;
113
+ title: string;
114
+ channel: string;
115
+ channel_id: string;
116
+ description?: string;
117
+ channel_follower_count?: number;
118
+ tags?: string[];
119
+ thumbnails?: YtDlpThumbnail[];
120
+ playlist_count?: number;
121
+ entries?: (YtDlpFlatEntry | YtDlpVideoMetadata)[];
122
+ }
57
123
 
58
124
  declare class CacheStore {
59
125
  private readonly opts;
@@ -74,35 +140,6 @@ declare class CacheStore {
74
140
  private cleanupEntry;
75
141
  }
76
142
 
77
- declare class PlayEngine {
78
- private readonly opts;
79
- private readonly paths;
80
- readonly cache: CacheStore;
81
- private readonly ytdlp;
82
- private static lastUpdateCheck;
83
- private static isUpdating;
84
- constructor(options?: PlayEngineOptions);
85
- private backgroundUpdateCheck;
86
- private forceUpdateCheck;
87
- generateRequestId(prefix?: string): string;
88
- search(query: string): Promise<PlayMetadata | null>;
89
- getFromCache(requestId: string): CacheEntry | undefined;
90
- preload(metadata: PlayMetadata, requestId: string): Promise<void>;
91
- getOrDownload(requestId: string, type: MediaType): Promise<{
92
- metadata: PlayMetadata;
93
- file: CachedFile;
94
- direct: boolean;
95
- }>;
96
- waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
97
- cleanup(requestId: string): void;
98
- private preloadOne;
99
- private downloadDirect;
100
- }
101
-
102
- declare function getYouTubeVideoId(input: string): string | null;
103
- declare function normalizeYoutubeUrl(input: string): string | null;
104
- declare function searchBest(query: string): Promise<PlayMetadata | null>;
105
-
106
143
  interface YtDlpClientOptions {
107
144
  binaryPath?: string;
108
145
  ffmpegPath?: string;
@@ -120,6 +157,29 @@ interface YtDlpVideoInfo {
120
157
  duration: number;
121
158
  thumbnail?: string;
122
159
  }
160
+ interface YtDlpPlaylistItem {
161
+ id: string;
162
+ title: string;
163
+ url: string;
164
+ duration?: number;
165
+ uploader?: string;
166
+ directUrl?: string;
167
+ }
168
+ interface YtDlpResolvedItem extends YtDlpPlaylistItem {
169
+ directUrl: string;
170
+ }
171
+ interface YtDlpPlaylistInfo {
172
+ id: string;
173
+ title: string;
174
+ uploader?: string;
175
+ entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];
176
+ }
177
+ interface YtDlpPlaylistOptions {
178
+ limit?: number;
179
+ resolveLinks?: boolean;
180
+ type?: "audio" | "video" | "both";
181
+ batchSize?: number;
182
+ }
123
183
  declare class YtDlpClient {
124
184
  private readonly binaryPath;
125
185
  private readonly ffmpegPath?;
@@ -132,12 +192,63 @@ declare class YtDlpClient {
132
192
  constructor(opts?: YtDlpClientOptions);
133
193
  private detectYtDlp;
134
194
  private detectAria2c;
135
- private exec;
195
+ exec(args: string[]): Promise<string>;
136
196
  getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo>;
197
+ getPlaylistInfo(youtubeUrl: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>;
198
+ getDirectUrl(youtubeUrl: string, type?: "audio" | "video" | "both"): Promise<string>;
199
+ resolvePlaylistItems(entries: YtDlpPlaylistItem[], limit: number, type?: "audio" | "video" | "both", batchSize?: number): Promise<YtDlpResolvedItem[]>;
137
200
  private buildOptimizationArgs;
138
201
  getAudio(youtubeUrl: string, qualityKbps: number, outputPath: string): Promise<DownloadInfo>;
139
202
  getVideo(youtubeUrl: string, qualityP: number, outputPath: string): Promise<DownloadInfo>;
140
203
  private formatDuration;
141
204
  }
142
205
 
143
- export { type CacheEntry, type CachedFile, type DownloadInfo, type MediaType, PlayEngine, type PlayEngineOptions, type PlayMetadata, YtDlpClient, getYouTubeVideoId, normalizeYoutubeUrl, searchBest };
206
+ declare class StreamEngine {
207
+ private readonly ytdlpClient;
208
+ constructor(ytdlpClient: YtDlpClient);
209
+ private getBinaryPath;
210
+ getAudioStream(youtubeUrl: string, qualityKbps?: number): Promise<StreamInfo>;
211
+ getVideoStream(youtubeUrl: string, qualityP?: number): Promise<StreamInfo>;
212
+ }
213
+
214
+ declare class PlayEngine {
215
+ private readonly opts;
216
+ private readonly paths;
217
+ readonly cache: CacheStore;
218
+ private readonly ytdlp;
219
+ private static lastUpdateCheck;
220
+ private static updatePromise;
221
+ constructor(options?: PlayEngineOptions);
222
+ readonly stream: StreamEngine;
223
+ private backgroundUpdateCheck;
224
+ private forceUpdateCheck;
225
+ generateRequestId(prefix?: string): string;
226
+ search(query: string): Promise<PlayMetadata | null>;
227
+ getFromCache(requestId: string): CacheEntry | undefined;
228
+ preload(metadata: PlayMetadata, requestId: string): Promise<void>;
229
+ getOrDownload(requestId: string, type: MediaType): Promise<{
230
+ metadata: PlayMetadata;
231
+ file: CachedFile;
232
+ direct: boolean;
233
+ }>;
234
+ waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
235
+ cleanup(requestId: string): void;
236
+ private preloadOne;
237
+ private _executePreload;
238
+ private downloadDirect;
239
+ private _executeDownloadDirect;
240
+ }
241
+
242
+ declare class StalkerEngine {
243
+ private readonly ytdlpClient;
244
+ constructor(ytdlpClient: YtDlpClient);
245
+ stalkVideoOrLive(youtubeUrl: string): Promise<YtDlpVideoMetadata>;
246
+ stalkChannel(youtubeUrl: string, opts?: StalkChannelOptions): Promise<YtDlpChannelMetadata>;
247
+ }
248
+
249
+ declare function getYouTubeVideoId(input: string): string | null;
250
+ declare function normalizeYoutubeUrl(input: string): string | null;
251
+ declare function searchBest(query: string): Promise<PlayMetadata | null>;
252
+ declare function searchPlaylistBest(query: string): Promise<PlayMetadata | null>;
253
+
254
+ export { type CacheEntry, type CachedFile, type DownloadInfo, type MediaType, PlayEngine, type PlayEngineOptions, type PlayMetadata, type StalkChannelOptions, StalkerEngine, StreamEngine, type StreamInfo, type YtDlpChannelMetadata, YtDlpClient, type YtDlpFlatEntry, type YtDlpVideoMetadata, getYouTubeVideoId, normalizeYoutubeUrl, searchBest, searchPlaylistBest };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Readable } from 'node:stream';
2
+
1
3
  type MediaType = "audio" | "video";
2
4
  interface PlayMetadata {
3
5
  title: string;
@@ -16,6 +18,11 @@ interface DownloadInfo {
16
18
  quality: string;
17
19
  downloadUrl: string;
18
20
  }
21
+ interface StreamInfo {
22
+ stream: Readable;
23
+ quality: string;
24
+ format: string;
25
+ }
19
26
  interface CachedFile {
20
27
  path: string;
21
28
  size: number;
@@ -54,6 +61,65 @@ interface PlayEngineOptions {
54
61
  error?: (...args: any[]) => void;
55
62
  };
56
63
  }
64
+ interface StalkChannelOptions {
65
+ flat?: boolean;
66
+ tab?: "videos" | "shorts" | "streams" | "popular" | "featured";
67
+ startItem?: number;
68
+ endItem?: number;
69
+ playlistItems?: string;
70
+ }
71
+ interface YtDlpThumbnail {
72
+ url: string;
73
+ width?: number;
74
+ height?: number;
75
+ id?: string;
76
+ }
77
+ interface YtDlpVideoMetadata {
78
+ _type: "video";
79
+ id: string;
80
+ title: string;
81
+ description?: string;
82
+ channel: string;
83
+ channel_id: string;
84
+ channel_follower_count?: number;
85
+ duration?: number;
86
+ view_count?: number;
87
+ like_count?: number;
88
+ comment_count?: number;
89
+ tags?: string[];
90
+ categories?: string[];
91
+ upload_date?: string;
92
+ timestamp?: number;
93
+ thumbnails?: YtDlpThumbnail[];
94
+ webpage_url: string;
95
+ is_live?: boolean;
96
+ was_live?: boolean;
97
+ concurrent_view_count?: number;
98
+ live_status?: "is_live" | "was_live" | "is_upcoming" | "not_live";
99
+ release_timestamp?: number;
100
+ }
101
+ interface YtDlpFlatEntry {
102
+ _type: "url" | "url_transparent";
103
+ id: string;
104
+ title: string;
105
+ url: string;
106
+ duration?: number;
107
+ view_count?: number;
108
+ channel?: string;
109
+ }
110
+ interface YtDlpChannelMetadata {
111
+ _type: "playlist";
112
+ id: string;
113
+ title: string;
114
+ channel: string;
115
+ channel_id: string;
116
+ description?: string;
117
+ channel_follower_count?: number;
118
+ tags?: string[];
119
+ thumbnails?: YtDlpThumbnail[];
120
+ playlist_count?: number;
121
+ entries?: (YtDlpFlatEntry | YtDlpVideoMetadata)[];
122
+ }
57
123
 
58
124
  declare class CacheStore {
59
125
  private readonly opts;
@@ -74,35 +140,6 @@ declare class CacheStore {
74
140
  private cleanupEntry;
75
141
  }
76
142
 
77
- declare class PlayEngine {
78
- private readonly opts;
79
- private readonly paths;
80
- readonly cache: CacheStore;
81
- private readonly ytdlp;
82
- private static lastUpdateCheck;
83
- private static isUpdating;
84
- constructor(options?: PlayEngineOptions);
85
- private backgroundUpdateCheck;
86
- private forceUpdateCheck;
87
- generateRequestId(prefix?: string): string;
88
- search(query: string): Promise<PlayMetadata | null>;
89
- getFromCache(requestId: string): CacheEntry | undefined;
90
- preload(metadata: PlayMetadata, requestId: string): Promise<void>;
91
- getOrDownload(requestId: string, type: MediaType): Promise<{
92
- metadata: PlayMetadata;
93
- file: CachedFile;
94
- direct: boolean;
95
- }>;
96
- waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
97
- cleanup(requestId: string): void;
98
- private preloadOne;
99
- private downloadDirect;
100
- }
101
-
102
- declare function getYouTubeVideoId(input: string): string | null;
103
- declare function normalizeYoutubeUrl(input: string): string | null;
104
- declare function searchBest(query: string): Promise<PlayMetadata | null>;
105
-
106
143
  interface YtDlpClientOptions {
107
144
  binaryPath?: string;
108
145
  ffmpegPath?: string;
@@ -120,6 +157,29 @@ interface YtDlpVideoInfo {
120
157
  duration: number;
121
158
  thumbnail?: string;
122
159
  }
160
+ interface YtDlpPlaylistItem {
161
+ id: string;
162
+ title: string;
163
+ url: string;
164
+ duration?: number;
165
+ uploader?: string;
166
+ directUrl?: string;
167
+ }
168
+ interface YtDlpResolvedItem extends YtDlpPlaylistItem {
169
+ directUrl: string;
170
+ }
171
+ interface YtDlpPlaylistInfo {
172
+ id: string;
173
+ title: string;
174
+ uploader?: string;
175
+ entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];
176
+ }
177
+ interface YtDlpPlaylistOptions {
178
+ limit?: number;
179
+ resolveLinks?: boolean;
180
+ type?: "audio" | "video" | "both";
181
+ batchSize?: number;
182
+ }
123
183
  declare class YtDlpClient {
124
184
  private readonly binaryPath;
125
185
  private readonly ffmpegPath?;
@@ -132,12 +192,63 @@ declare class YtDlpClient {
132
192
  constructor(opts?: YtDlpClientOptions);
133
193
  private detectYtDlp;
134
194
  private detectAria2c;
135
- private exec;
195
+ exec(args: string[]): Promise<string>;
136
196
  getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo>;
197
+ getPlaylistInfo(youtubeUrl: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>;
198
+ getDirectUrl(youtubeUrl: string, type?: "audio" | "video" | "both"): Promise<string>;
199
+ resolvePlaylistItems(entries: YtDlpPlaylistItem[], limit: number, type?: "audio" | "video" | "both", batchSize?: number): Promise<YtDlpResolvedItem[]>;
137
200
  private buildOptimizationArgs;
138
201
  getAudio(youtubeUrl: string, qualityKbps: number, outputPath: string): Promise<DownloadInfo>;
139
202
  getVideo(youtubeUrl: string, qualityP: number, outputPath: string): Promise<DownloadInfo>;
140
203
  private formatDuration;
141
204
  }
142
205
 
143
- export { type CacheEntry, type CachedFile, type DownloadInfo, type MediaType, PlayEngine, type PlayEngineOptions, type PlayMetadata, YtDlpClient, getYouTubeVideoId, normalizeYoutubeUrl, searchBest };
206
+ declare class StreamEngine {
207
+ private readonly ytdlpClient;
208
+ constructor(ytdlpClient: YtDlpClient);
209
+ private getBinaryPath;
210
+ getAudioStream(youtubeUrl: string, qualityKbps?: number): Promise<StreamInfo>;
211
+ getVideoStream(youtubeUrl: string, qualityP?: number): Promise<StreamInfo>;
212
+ }
213
+
214
+ declare class PlayEngine {
215
+ private readonly opts;
216
+ private readonly paths;
217
+ readonly cache: CacheStore;
218
+ private readonly ytdlp;
219
+ private static lastUpdateCheck;
220
+ private static updatePromise;
221
+ constructor(options?: PlayEngineOptions);
222
+ readonly stream: StreamEngine;
223
+ private backgroundUpdateCheck;
224
+ private forceUpdateCheck;
225
+ generateRequestId(prefix?: string): string;
226
+ search(query: string): Promise<PlayMetadata | null>;
227
+ getFromCache(requestId: string): CacheEntry | undefined;
228
+ preload(metadata: PlayMetadata, requestId: string): Promise<void>;
229
+ getOrDownload(requestId: string, type: MediaType): Promise<{
230
+ metadata: PlayMetadata;
231
+ file: CachedFile;
232
+ direct: boolean;
233
+ }>;
234
+ waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
235
+ cleanup(requestId: string): void;
236
+ private preloadOne;
237
+ private _executePreload;
238
+ private downloadDirect;
239
+ private _executeDownloadDirect;
240
+ }
241
+
242
+ declare class StalkerEngine {
243
+ private readonly ytdlpClient;
244
+ constructor(ytdlpClient: YtDlpClient);
245
+ stalkVideoOrLive(youtubeUrl: string): Promise<YtDlpVideoMetadata>;
246
+ stalkChannel(youtubeUrl: string, opts?: StalkChannelOptions): Promise<YtDlpChannelMetadata>;
247
+ }
248
+
249
+ declare function getYouTubeVideoId(input: string): string | null;
250
+ declare function normalizeYoutubeUrl(input: string): string | null;
251
+ declare function searchBest(query: string): Promise<PlayMetadata | null>;
252
+ declare function searchPlaylistBest(query: string): Promise<PlayMetadata | null>;
253
+
254
+ export { type CacheEntry, type CachedFile, type DownloadInfo, type MediaType, PlayEngine, type PlayEngineOptions, type PlayMetadata, type StalkChannelOptions, StalkerEngine, StreamEngine, type StreamInfo, type YtDlpChannelMetadata, YtDlpClient, type YtDlpFlatEntry, type YtDlpVideoMetadata, getYouTubeVideoId, normalizeYoutubeUrl, searchBest, searchPlaylistBest };
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- var I=(o=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(o,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):o)(function(o){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+o+'" is not supported')});import p from"fs";import u from"path";import{execSync as O}from"child_process";import C from"fs";var v=class{constructor(t){this.opts=t}store=new Map;cleanupTimer;get(t){return this.store.get(t)}set(t,r){this.store.set(t,r)}has(t){return this.store.has(t)}delete(t){this.cleanupEntry(t),this.store.delete(t)}markLoading(t,r){let e=this.store.get(t);e&&(e.loading=r)}setFile(t,r,e){let i=this.store.get(t);i&&(i[r]=e)}cleanupExpired(t=Date.now()){let r=0;for(let[e,i]of this.store.entries())t>i.expiresAt&&(this.delete(e),r++);return r}start(){this.cleanupTimer||(this.cleanupTimer=setInterval(()=>{this.cleanupExpired(Date.now())},this.opts.cleanupIntervalMs),this.cleanupTimer.unref())}stop(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=void 0)}cleanupEntry(t){let r=this.store.get(t);r&&["audio","video"].forEach(e=>{let i=r[e];if(i?.path&&C.existsSync(i.path))try{C.unlinkSync(i.path)}catch{}})}};import g from"fs";import b from"path";import j from"os";function F(o){g.mkdirSync(o,{recursive:!0,mode:511});try{g.chmodSync(o,511)}catch{}g.accessSync(o,g.constants.R_OK|g.constants.W_OK)}function U(o){let t=o?.trim()?o:b.join(j.tmpdir(),"yt-play"),r=b.resolve(t),e=b.join(r);return F(r),F(e),{baseDir:r,cacheDir:e}}import{spawn as _}from"child_process";import l from"path";import y from"fs";var h;try{h=l.dirname(new URL(import.meta.url).pathname)}catch{h=typeof h<"u"?h:process.cwd()}var w=class{binaryPath;ffmpegPath;aria2cPath;timeoutMs;useAria2c;concurrentFragments;cookiesPath;cookiesFromBrowser;constructor(t={}){this.binaryPath=t.binaryPath||this.detectYtDlp(),this.ffmpegPath=t.ffmpegPath,this.timeoutMs=t.timeoutMs??3e5,this.concurrentFragments=t.concurrentFragments??5,this.cookiesPath=t.cookiesPath,this.cookiesFromBrowser=t.cookiesFromBrowser,this.aria2cPath=t.aria2cPath||this.detectAria2c(),this.useAria2c=t.useAria2c??!!this.aria2cPath}detectYtDlp(){let t=l.resolve(h,"../.."),r=[l.join(t,"bin","yt-dlp"),l.join(t,"bin","yt-dlp.exe")];for(let e of r)if(y.existsSync(e))return e;try{let{execSync:e}=I("child_process"),i=process.platform==="win32"?"where yt-dlp":"which yt-dlp",n=e(i,{encoding:"utf-8"}).trim();if(n)return n.split(`
2
- `)[0]}catch{}return"yt-dlp"}detectAria2c(){let t=l.resolve(h,"../.."),r=[l.join(t,"bin","aria2c"),l.join(t,"bin","aria2c.exe")];for(let e of r)if(y.existsSync(e))return e;try{let{execSync:e}=I("child_process"),i=process.platform==="win32"?"where aria2c":"which aria2c",n=e(i,{encoding:"utf-8"}).trim();if(n)return n.split(`
3
- `)[0]}catch{}}async exec(t){return new Promise((r,e)=>{let i=[...t];this.ffmpegPath&&(i=["--ffmpeg-location",this.ffmpegPath,...i]),this.cookiesPath&&y.existsSync(this.cookiesPath)&&(i=["--cookies",this.cookiesPath,...i]),this.cookiesFromBrowser&&(i=["--cookies-from-browser",this.cookiesFromBrowser,...i]);let n=_(this.binaryPath,i,{stdio:["ignore","pipe","pipe"]}),a="",s="";n.stdout.on("data",d=>{a+=d.toString()}),n.stderr.on("data",d=>{s+=d.toString()});let c=setTimeout(()=>{n.kill("SIGKILL"),e(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`))},this.timeoutMs);n.on("close",d=>{clearTimeout(c),d===0?r(a):e(new Error(`yt-dlp exited with code ${d}. stderr: ${s.slice(0,500)}`))}),n.on("error",d=>{clearTimeout(c),e(d)})})}async getInfo(t){let r=await this.exec(["-J","--no-warnings","--no-playlist",t]);return JSON.parse(r)}buildOptimizationArgs(){let t=["--no-warnings","--no-playlist","--no-check-certificates","--concurrent-fragments",String(this.concurrentFragments)];return this.useAria2c&&this.aria2cPath&&(t.push("--downloader",this.aria2cPath),t.push("--downloader-args","aria2c:-x 16 -s 16 -k 1M")),t}async getAudio(t,r,e){let i=await this.getInfo(t),a=["-f","bestaudio[ext=m4a]/bestaudio/best","-o",e,...this.buildOptimizationArgs(),t];if(await this.exec(a),!y.existsSync(e))throw new Error(`yt-dlp failed to create audio file: ${e}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${r}kbps m4a`,filename:l.basename(e),downloadUrl:e}}async getVideo(t,r,e){let i=await this.getInfo(t),a=["-f",`bestvideo[height<=${r}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${r}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${r}]`,"--merge-output-format","mp4","-o",e,...this.buildOptimizationArgs(),t];if(await this.exec(a),!y.existsSync(e))throw new Error(`yt-dlp failed to create video file: ${e}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${r}p H.264`,filename:l.basename(e),downloadUrl:e}}formatDuration(t){if(!t)return"0:00";let r=Math.floor(t/3600),e=Math.floor(t%3600/60),i=Math.floor(t%60);return r>0?`${r}:${e.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${e}:${i.toString().padStart(2,"0")}`}};import E from"yt-search";function B(o){let t=(o||"").trim(),r=[...t.matchAll(/\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/gi)];return r.length>0?r[0][1].trim():(t=t.replace(/^<([^>]+)>$/,"$1").trim(),t=t.replace(/^["'`](.*)["'`]$/,"$1").trim(),t)}function P(o){let t=/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i,r=(o||"").match(t);return r?r[1]:null}function f(o){let t=B(o),r=t.match(/https?:\/\/[^\s)]+/i)?.[0]??t,e=P(r);return e?`https://www.youtube.com/watch?v=${e}`:null}async function k(o){let t=P(o);if(t){let a=await E({videoId:t});if(!a)return null;let s=a.duration?.seconds??0,c=f(a.url)??a.url;return{title:a.title||"Untitled",author:a.author?.name||void 0,duration:a.duration?.timestamp||void 0,thumb:a.image||a.thumbnail||void 0,videoId:a.videoId,url:c,durationSeconds:s}}let e=(await E(o))?.videos?.[0];if(!e)return null;let i=e.duration?.seconds??0,n=f(e.url)??e.url;return{title:e.title||"Untitled",author:e.author?.name||void 0,duration:e.duration?.timestamp||void 0,thumb:e.image||e.thumbnail||void 0,videoId:e.videoId,url:n,durationSeconds:i}}var x=[320,256,192,128,96,64],D=[1080,720,480,360],V=36e5;function m(o,t){return t.includes(o)?o:t[0]}function S(o){return(o||"").replace(/[\\/:*?"<>|]/g,"").replace(/[^\w\s-]/gi,"").trim().replace(/\s+/g," ").substring(0,100)}function Y(){try{return O("which node",{encoding:"utf-8"}).trim()||null}catch{return process.execPath||null}}function L(o,t,r){try{let e;if(o)e=u.dirname(o);else try{let s=new URL(import.meta.url);e=u.join(u.dirname(s.pathname),"..","bin")}catch{e=u.join(__dirname,"..","bin")}if(!p.existsSync(e))return;let i=u.join(e,"yt-dlp.conf"),n=[],a=Y();a&&n.push(`--js-runtimes node:${a}`),n.push("--remote-components ejs:npm"),t&&p.existsSync(t)?n.push(`--cookies ${t}`):r&&n.push(`--cookies-from-browser ${r}`),p.writeFileSync(i,n.join(`
1
+ var T=(a=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(a,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):a)(function(a){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+a+'" is not supported')});import m from"fs";import f from"path";import{execSync as J}from"child_process";import A from"fs";var x=class{constructor(t){this.opts=t}store=new Map;cleanupTimer;get(t){return this.store.get(t)}set(t,r){this.store.set(t,r)}has(t){return this.store.has(t)}delete(t){this.cleanupEntry(t),this.store.delete(t)}markLoading(t,r){let e=this.store.get(t);e&&(e.loading=r)}setFile(t,r,e){let i=this.store.get(t);i&&(i[r]=e)}cleanupExpired(t=Date.now()){let r=0;for(let[e,i]of this.store.entries())t>i.expiresAt&&(this.delete(e),r++);return r}start(){this.cleanupTimer||(this.cleanupTimer=setInterval(()=>{this.cleanupExpired(Date.now())},this.opts.cleanupIntervalMs),this.cleanupTimer.unref())}stop(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=void 0)}cleanupEntry(t){let r=this.store.get(t);r&&["audio","video"].forEach(e=>{let i=r[e];if(i?.path&&A.existsSync(i.path))try{A.unlinkSync(i.path)}catch{}})}};import b from"fs";import k from"path";import V from"os";function U(a){b.mkdirSync(a,{recursive:!0,mode:511});try{b.chmodSync(a,511)}catch{}b.accessSync(a,b.constants.R_OK|b.constants.W_OK)}function E(a){let t=a?.trim()?a:k.join(V.tmpdir(),"yt-play"),r=k.resolve(t),e=k.join(r);return U(r),U(e),{baseDir:r,cacheDir:e}}import{spawn as z}from"child_process";import p from"path";import u from"fs";var g;try{g=p.dirname(new URL(import.meta.url).pathname)}catch{g=typeof g<"u"?g:process.cwd()}var P=class{binaryPath;ffmpegPath;aria2cPath;timeoutMs;useAria2c;concurrentFragments;cookiesPath;cookiesFromBrowser;constructor(t={}){this.binaryPath=t.binaryPath||this.detectYtDlp(),this.ffmpegPath=t.ffmpegPath,this.timeoutMs=t.timeoutMs??3e5,this.concurrentFragments=t.concurrentFragments??5,this.cookiesPath=t.cookiesPath,this.cookiesFromBrowser=t.cookiesFromBrowser,this.aria2cPath=t.aria2cPath||this.detectAria2c(),this.useAria2c=t.useAria2c??!!this.aria2cPath}detectYtDlp(){let t=p.resolve(g,"../.."),r=[p.join(t,"bin","yt-dlp"),p.join(t,"bin","yt-dlp.exe")];for(let e of r)if(u.existsSync(e))return e;try{let{execSync:e}=T("child_process"),i=process.platform==="win32"?"where yt-dlp":"which yt-dlp",n=e(i,{encoding:"utf-8"}).trim();if(n)return n.split(`
2
+ `)[0]}catch{}return"yt-dlp"}detectAria2c(){let t=p.resolve(g,"../.."),r=[p.join(t,"bin","aria2c"),p.join(t,"bin","aria2c.exe")];for(let e of r)if(u.existsSync(e))return e;try{let{execSync:e}=T("child_process"),i=process.platform==="win32"?"where aria2c":"which aria2c",n=e(i,{encoding:"utf-8"}).trim();if(n)return n.split(`
3
+ `)[0]}catch{}}async exec(t){return new Promise((r,e)=>{let i=[...t];this.ffmpegPath&&(i=["--ffmpeg-location",this.ffmpegPath,...i]),this.cookiesPath&&u.existsSync(this.cookiesPath)&&(i=["--cookies",this.cookiesPath,...i]),this.cookiesFromBrowser&&(i=["--cookies-from-browser",this.cookiesFromBrowser,...i]);let n=z(this.binaryPath,i,{stdio:["ignore","pipe","pipe"]}),o="",s="";n.stdout.on("data",l=>{o+=l.toString()}),n.stderr.on("data",l=>{s+=l.toString()});let c=setTimeout(()=>{n.kill("SIGKILL"),e(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`))},this.timeoutMs);n.on("close",l=>{clearTimeout(c),l===0?r(o):e(new Error(`yt-dlp exited with code ${l}. stderr: ${s.slice(0,500)}`))}),n.on("error",l=>{clearTimeout(c),e(l)})})}async getInfo(t){let r=await this.exec(["-J","--no-warnings","--no-playlist",t]);return JSON.parse(r)}async getPlaylistInfo(t,r={}){let e=await this.exec(["-J","--flat-playlist","--no-warnings",t]),i=JSON.parse(e),n=(i.entries||[]).filter(o=>{let s=o.title||"";return o.id&&s&&!s.includes("[Deleted video]")&&!s.includes("[Private video]")}).map(o=>({id:o.id,title:o.title,url:`https://www.youtube.com/watch?v=${o.id}`,duration:o.duration,uploader:o.uploader||i.uploader}));return r.resolveLinks?n=await this.resolvePlaylistItems(n,r.limit&&r.limit>0?r.limit:n.length,r.type||"audio",r.batchSize||5):r.limit&&r.limit>0&&(n=n.slice(0,r.limit)),{id:i.id,title:i.title,uploader:i.uploader,entries:n}}async getDirectUrl(t,r="audio"){let e=r==="audio"?"bestaudio[ext=m4a]/bestaudio/best":"best[ext=mp4]/best";return(await this.exec(["-f",e,"-g","--no-warnings",t])).trim().split(`
4
+ `)[0]}async resolvePlaylistItems(t,r,e="audio",i=5){let n=[],o=0;for(;n.length<r&&o<t.length;){let s=r-n.length,c=Math.min(i,s,t.length-o),l=t.slice(o,o+c);o+=c;let h=l.map(async d=>{try{let w=await this.getDirectUrl(d.url,e);return{...d,directUrl:w}}catch{return null}}),D=await Promise.all(h);for(let d of D)d&&n.length<r&&n.push(d)}return n}buildOptimizationArgs(){let t=["--no-warnings","--no-playlist","--no-check-certificates","--concurrent-fragments",String(this.concurrentFragments)];return this.useAria2c&&this.aria2cPath&&(t.push("--downloader",this.aria2cPath),t.push("--downloader-args","aria2c:-x 16 -s 16 -k 1M")),t}async getAudio(t,r,e){let i=await this.getInfo(t),o=["-f","bestaudio[ext=m4a]/bestaudio/best","-o",e,...this.buildOptimizationArgs(),t];try{await this.exec(o)}catch(c){throw[e,`${e}.part`,`${e}.ytdl`].forEach(l=>{if(u.existsSync(l))try{u.unlinkSync(l)}catch{}}),c}if(!u.existsSync(e))throw new Error(`yt-dlp failed to create audio file: ${e}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${r}kbps m4a`,filename:p.basename(e),downloadUrl:e}}async getVideo(t,r,e){let i=await this.getInfo(t),o=["-f",`bestvideo[height<=${r}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${r}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${r}]`,"--merge-output-format","mp4","-o",e,...this.buildOptimizationArgs(),t];try{await this.exec(o)}catch(c){throw[e,`${e}.part`,`${e}.ytdl`].forEach(l=>{if(u.existsSync(l))try{u.unlinkSync(l)}catch{}}),c}if(!u.existsSync(e))throw new Error(`yt-dlp failed to create video file: ${e}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${r}p H.264`,filename:p.basename(e),downloadUrl:e}}formatDuration(t){if(!t)return"0:00";let r=Math.floor(t/3600),e=Math.floor(t%3600/60),i=Math.floor(t%60);return r>0?`${r}:${e.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${e}:${i.toString().padStart(2,"0")}`}};import S from"yt-search";function R(a){let t=(a||"").trim(),r=[...t.matchAll(/\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/gi)];return r.length>0?r[0][1].trim():(t=t.replace(/^<([^>]+)>$/,"$1").trim(),t=t.replace(/^["'`](.*)["'`]$/,"$1").trim(),t)}function M(a){let t=/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i,r=(a||"").match(t);return r?r[1]:null}function L(a){let t=/[?&]list=([a-zA-Z0-9_-]+)/i,r=(a||"").match(t);return r?r[1]:null}function y(a){let t=R(a),r=t.match(/https?:\/\/[^\s)]+/i)?.[0]??t,e=M(r);return e?`https://www.youtube.com/watch?v=${e}`:null}async function $(a){let t=M(a);if(t){let o=await S({videoId:t});if(!o)return null;let s=o.duration?.seconds??0,c=y(o.url)??o.url;return{title:o.title||"Untitled",author:o.author?.name||void 0,duration:o.duration?.timestamp||void 0,thumb:o.image||o.thumbnail||void 0,videoId:o.videoId,url:c,durationSeconds:s}}let e=(await S(a))?.videos?.[0];if(!e)return null;let i=e.duration?.seconds??0,n=y(e.url)??e.url;return{title:e.title||"Untitled",author:e.author?.name||void 0,duration:e.duration?.timestamp||void 0,thumb:e.image||e.thumbnail||void 0,videoId:e.videoId,url:n,durationSeconds:i}}async function K(a){let t=L(a);if(t)try{let i=await S({listId:t});return i?{title:i.title||"Untitled",author:i.author?.name||void 0,duration:void 0,durationSeconds:0,thumb:i.image||i.thumbnail||void 0,videoId:t,url:i.url||`https://www.youtube.com/playlist?list=${t}`}:null}catch{}let e=(await S(a))?.playlists?.[0];return e?{title:e.title||"Untitled",author:e.author?.name||void 0,duration:void 0,durationSeconds:0,thumb:e.image||e.thumbnail||void 0,videoId:e.listId,url:e.url||`https://www.youtube.com/playlist?list=${e.listId}`}:null}import{spawn as F}from"child_process";var v=class{constructor(t){this.ytdlpClient=t}getBinaryPath(){return this.ytdlpClient.binaryPath}async getAudioStream(t,r=128){let i=["-f","bestaudio[ext=m4a]/bestaudio/best","-o","-","--no-warnings","--no-playlist",t],n=F(this.getBinaryPath(),i,{stdio:["ignore","pipe","pipe"]});return n.stderr.on("data",()=>{}),{stream:n.stdout,quality:`${r}kbps`,format:"m4a"}}async getVideoStream(t,r=720){let i=["-f",`best[height<=${r}][ext=mp4]/best`,"-o","-","--no-warnings","--no-playlist",t],n=F(this.getBinaryPath(),i,{stdio:["ignore","pipe","pipe"]});return n.stderr.on("data",()=>{}),{stream:n.stdout,quality:`${r}p`,format:"mp4"}}};var O=[320,256,192,128,96,64],B=[1080,720,480,360],N=36e5;function I(a,t){return t.includes(a)?a:t[0]}function _(a){return(a||"").replace(/[\\/:*?"<>|]/g,"").replace(/[^\w\s-]/gi,"").trim().replace(/\s+/g," ").substring(0,100)}function W(){try{return J("which node",{encoding:"utf-8"}).trim()||null}catch{return process.execPath||null}}function Q(a,t,r){try{let e;if(a)e=f.dirname(a);else try{let s=new URL(import.meta.url);e=f.join(f.dirname(s.pathname),"..","bin")}catch{e=f.join(__dirname,"..","bin")}if(!m.existsSync(e))return;let i=f.join(e,"yt-dlp.conf"),n=[],o=W();o&&n.push(`--js-runtimes node:${o}`),n.push("--remote-components ejs:npm"),t&&m.existsSync(t)?n.push(`--cookies ${t}`):r&&n.push(`--cookies-from-browser ${r}`),m.writeFileSync(i,n.join(`
4
5
  `)+`
5
- `,"utf-8")}catch(e){console.warn("Failed to create yt-dlp.conf:",e)}}var $=class o{opts;paths;cache;ytdlp;static lastUpdateCheck=0;static isUpdating=!1;constructor(t={}){this.opts={ttlMs:t.ttlMs??3*6e4,maxPreloadDurationSeconds:t.maxPreloadDurationSeconds??1200,preferredAudioKbps:t.preferredAudioKbps??128,preferredVideoP:t.preferredVideoP??720,preloadBuffer:t.preloadBuffer??!0,cleanupIntervalMs:t.cleanupIntervalMs??3e4,concurrentFragments:t.concurrentFragments??5,useAria2c:t.useAria2c,logger:t.logger},this.paths=U(t.cacheDir),this.cache=new v({cleanupIntervalMs:this.opts.cleanupIntervalMs}),this.cache.start(),L(t.ytdlpBinaryPath,t.cookiesPath,t.cookiesFromBrowser),this.ytdlp=new w({binaryPath:t.ytdlpBinaryPath,ffmpegPath:t.ffmpegPath,aria2cPath:t.aria2cPath,useAria2c:this.opts.useAria2c,concurrentFragments:this.opts.concurrentFragments,timeoutMs:t.ytdlpTimeoutMs??3e5,cookiesPath:t.cookiesPath,cookiesFromBrowser:t.cookiesFromBrowser}),this.backgroundUpdateCheck()}backgroundUpdateCheck(){let t=Date.now();t-o.lastUpdateCheck<V||(async()=>{try{o.lastUpdateCheck=t;let r=new URL("../scripts/check-ytdlp-update.mjs",import.meta.url),{checkAndUpdate:e}=await import(r.href);await e()&&this.opts.logger?.info?.("\u2713 yt-dlp updated to latest version")}catch{this.opts.logger?.debug?.("Update check failed (will retry later)")}})()}async forceUpdateCheck(){if(o.isUpdating){this.opts.logger?.info?.("Update already in progress, skipping...");return}try{o.isUpdating=!0,this.opts.logger?.warn?.("<!> Download failed. Forcing yt-dlp update check...");let t=new URL("../scripts/check-ytdlp-update.mjs",import.meta.url),{checkAndUpdate:r}=await import(t.href);await r()?(this.opts.logger?.info?.("\u2713 yt-dlp updated successfully"),o.lastUpdateCheck=Date.now()):this.opts.logger?.info?.("yt-dlp is already up to date")}catch(t){this.opts.logger?.error?.("Failed to update yt-dlp:",t)}finally{o.isUpdating=!1}}generateRequestId(t="play"){return`${t}_${Date.now()}_${Math.random().toString(36).slice(2,8)}`}async search(t){return k(t)}getFromCache(t){return this.cache.get(t)}async preload(t,r){let e=f(t.url);if(!e)throw new Error("Invalid YouTube URL.");let i=t.durationSeconds>3600;t.durationSeconds>this.opts.maxPreloadDurationSeconds&&this.opts.logger?.warn?.(`Video too long for preload (${Math.floor(t.durationSeconds/60)}min). Will use direct download with reduced quality.`);let n={...t,url:e};this.cache.set(r,{metadata:n,audio:null,video:null,expiresAt:Date.now()+this.opts.ttlMs,loading:!0});let a=i?96:m(this.opts.preferredAudioKbps,x),s=this.preloadOne(r,"audio",e,a),c=i?[s]:[s,this.preloadOne(r,"video",e,m(this.opts.preferredVideoP,D))];i&&this.opts.logger?.info?.(`Long video detected (${Math.floor(t.durationSeconds/60)}min). Audio only mode (96kbps).`),await Promise.allSettled(c),this.cache.markLoading(r,!1)}async getOrDownload(t,r){let e=this.cache.get(t);if(!e)throw new Error("Request not found (cache miss).");let i=e[r];if(i?.path&&p.existsSync(i.path)&&i.size>0)return{metadata:e.metadata,file:i,direct:!1};let n=f(e.metadata.url);if(!n)throw new Error("Invalid YouTube URL.");let a=await this.downloadDirect(r,n);return{metadata:e.metadata,file:a,direct:!0}}async waitCache(t,r,e=8e3,i=500){let n=Date.now();for(;Date.now()-n<e;){let s=this.cache.get(t)?.[r];if(s?.path&&p.existsSync(s.path)&&s.size>0)return s;await new Promise(c=>setTimeout(c,i))}return null}cleanup(t){this.cache.delete(t)}async preloadOne(t,r,e,i){try{let n=S(`temp_${Date.now()}`),s=`${r}_${t}_${n}.${r==="audio"?"m4a":"mp4"}`,c=u.join(this.paths.cacheDir,s),d=r==="audio"?await this.ytdlp.getAudio(e,i,c):await this.ytdlp.getVideo(e,i,c),A=p.statSync(c).size,T;this.opts.preloadBuffer&&(T=await p.promises.readFile(c));let z={path:c,size:A,info:{quality:d.quality},buffer:T};this.cache.setFile(t,r,z),this.opts.logger?.debug?.(`preloaded ${r} ${A} bytes: ${s}`)}catch(n){throw this.opts.logger?.error?.(`preload ${r} failed`,n),await this.forceUpdateCheck(),n}}async downloadDirect(t,r){try{let e=m(this.opts.preferredAudioKbps,x),i=m(this.opts.preferredVideoP,D),n=t==="audio"?"m4a":"mp4",a=S(`direct_${Date.now()}`),s=u.join(this.paths.cacheDir,`${t}_${a}.${n}`),c=t==="audio"?await this.ytdlp.getAudio(r,e,s):await this.ytdlp.getVideo(r,i,s),d=p.statSync(s);return{path:s,size:d.size,info:{quality:c.quality}}}catch(e){this.opts.logger?.error?.("Direct download failed:",e),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying download after update...");let i=m(this.opts.preferredAudioKbps,x),n=m(this.opts.preferredVideoP,D),a=t==="audio"?"m4a":"mp4",s=S(`direct_retry_${Date.now()}`),c=u.join(this.paths.cacheDir,`${t}_${s}.${a}`),d=t==="audio"?await this.ytdlp.getAudio(r,i,c):await this.ytdlp.getVideo(r,n,c),M=p.statSync(c);return{path:c,size:M.size,info:{quality:d.quality}}}}};export{$ as PlayEngine,w as YtDlpClient,P as getYouTubeVideoId,f as normalizeYoutubeUrl,k as searchBest};
6
+ `,"utf-8")}catch{}}var C=class a{opts;paths;cache;ytdlp;static lastUpdateCheck=0;static updatePromise=null;constructor(t={}){this.opts={ttlMs:t.ttlMs??3*6e4,maxPreloadDurationSeconds:t.maxPreloadDurationSeconds??1200,preferredAudioKbps:t.preferredAudioKbps??128,preferredVideoP:t.preferredVideoP??720,preloadBuffer:t.preloadBuffer??!0,cleanupIntervalMs:t.cleanupIntervalMs??3e4,concurrentFragments:t.concurrentFragments??5,useAria2c:t.useAria2c,logger:t.logger},this.paths=E(t.cacheDir),this.cache=new x({cleanupIntervalMs:this.opts.cleanupIntervalMs}),this.cache.start(),Q(t.ytdlpBinaryPath,t.cookiesPath,t.cookiesFromBrowser),this.ytdlp=new P({binaryPath:t.ytdlpBinaryPath,ffmpegPath:t.ffmpegPath,aria2cPath:t.aria2cPath,useAria2c:this.opts.useAria2c,concurrentFragments:this.opts.concurrentFragments,timeoutMs:t.ytdlpTimeoutMs??3e5,cookiesPath:t.cookiesPath,cookiesFromBrowser:t.cookiesFromBrowser}),this.stream=new v(this.ytdlp),this.backgroundUpdateCheck()}stream;backgroundUpdateCheck(){let t=Date.now();t-a.lastUpdateCheck<N||a.updatePromise||(a.updatePromise=(async()=>{try{a.lastUpdateCheck=t;let r=new URL("../scripts/check-ytdlp-update.mjs",import.meta.url),{checkAndUpdate:e}=await import(r.href);await e()&&this.opts.logger?.info?.("\u2713 yt-dlp updated to latest version")}catch{this.opts.logger?.debug?.("Update check failed (will retry later)")}finally{a.updatePromise=null}})())}async forceUpdateCheck(){return a.updatePromise?(this.opts.logger?.info?.("Update already in progress, waiting..."),a.updatePromise):(a.updatePromise=(async()=>{try{this.opts.logger?.warn?.("<!> Download failed. Forcing yt-dlp update check...");let t=new URL("../scripts/check-ytdlp-update.mjs",import.meta.url),{checkAndUpdate:r}=await import(t.href);await r()&&(this.opts.logger?.info?.("\u2713 yt-dlp updated successfully"),a.lastUpdateCheck=Date.now())}catch(t){this.opts.logger?.error?.("Failed to update yt-dlp:",t)}finally{a.updatePromise=null}})(),a.updatePromise)}generateRequestId(t="play"){return`${t}_${Date.now()}_${Math.random().toString(36).slice(2,8)}`}async search(t){return $(t)}getFromCache(t){return this.cache.get(t)}async preload(t,r){let e=y(t.url);if(!e)throw new Error("Invalid YouTube URL.");let i=t.durationSeconds>3600;t.durationSeconds>this.opts.maxPreloadDurationSeconds&&this.opts.logger?.warn?.(`Video too long for preload (${Math.floor(t.durationSeconds/60)}min). Will use direct download with reduced quality.`);let n={...t,url:e};this.cache.set(r,{metadata:n,audio:null,video:null,expiresAt:Date.now()+this.opts.ttlMs,loading:!0});let o=i?96:I(this.opts.preferredAudioKbps,O),s=this.preloadOne(r,"audio",e,o),c=i?[s]:[s,this.preloadOne(r,"video",e,I(this.opts.preferredVideoP,B))];i&&this.opts.logger?.info?.(`Long video detected (${Math.floor(t.durationSeconds/60)}min). Audio only mode (96kbps).`),await Promise.allSettled(c),this.cache.markLoading(r,!1)}async getOrDownload(t,r){let e=this.cache.get(t);if(!e)throw new Error("Request not found (cache miss).");let i=e[r];if(i?.path&&m.existsSync(i.path)&&i.size>0)return{metadata:e.metadata,file:i,direct:!1};let n=y(e.metadata.url);if(!n)throw new Error("Invalid YouTube URL.");let o=await this.downloadDirect(r,n);return{metadata:e.metadata,file:o,direct:!0}}async waitCache(t,r,e=8e3,i=500){let n=Date.now();for(;Date.now()-n<e;){let s=this.cache.get(t)?.[r];if(s?.path&&m.existsSync(s.path)&&s.size>0)return s;await new Promise(c=>setTimeout(c,i))}return null}cleanup(t){this.cache.delete(t)}async preloadOne(t,r,e,i){try{await this._executePreload(t,r,e,i)}catch(n){this.opts.logger?.error?.(`preload ${r} failed, forcing update...`,n),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying preload after update..."),await this._executePreload(t,r,e,i)}}async _executePreload(t,r,e,i){let n=Math.random().toString(36).slice(2,8),o=_(`temp_${Date.now()}_${n}`),c=`${r}_${t}_${o}.${r==="audio"?"m4a":"mp4"}`,l=f.join(this.paths.cacheDir,c),h=r==="audio"?await this.ytdlp.getAudio(e,i,l):await this.ytdlp.getVideo(e,i,l),d=m.statSync(l).size,w;this.opts.preloadBuffer&&(w=await m.promises.readFile(l));let j={path:l,size:d,info:{quality:h.quality},buffer:w};this.cache.setFile(t,r,j),this.opts.logger?.debug?.(`preloaded ${r} ${d} bytes: ${c}`)}async downloadDirect(t,r){try{return await this._executeDownloadDirect(t,r,!1)}catch(e){return this.opts.logger?.error?.("Direct download failed:",e),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying download after update..."),await this._executeDownloadDirect(t,r,!0)}}async _executeDownloadDirect(t,r,e){let i=I(this.opts.preferredAudioKbps,O),n=I(this.opts.preferredVideoP,B),o=t==="audio"?"m4a":"mp4",s=e?"direct_retry":"direct",c=Math.random().toString(36).slice(2,8),l=_(`${s}_${Date.now()}_${c}`),h=f.join(this.paths.cacheDir,`${t}_${l}.${o}`),D=t==="audio"?await this.ytdlp.getAudio(r,i,h):await this.ytdlp.getVideo(r,n,h),d=m.statSync(h);return{path:h,size:d.size,info:{quality:D.quality}}}};var Y=class{constructor(t){this.ytdlpClient=t}async stalkVideoOrLive(t){let r=["-J","--no-warnings","--no-playlist",t],e=await this.ytdlpClient.exec(r);return JSON.parse(e)}async stalkChannel(t,r={}){let e=["-J","--no-warnings"];r.flat&&e.push("--flat-playlist"),r.playlistItems?e.push("--playlist-items",r.playlistItems):(r.startItem&&r.startItem>0&&e.push("--playlist-start",r.startItem.toString()),r.endItem&&r.endItem>0&&e.push("--playlist-end",r.endItem.toString()));let i=t.trim().replace(/\/$/,"");r.tab&&(i=i.replace(/\/(videos|shorts|streams|popular|featured)$/i,""),r.tab!=="featured"&&(i=`${i}/${r.tab}`)),e.push(i);try{let n=await this.ytdlpClient.exec(e);return JSON.parse(n)}catch(n){let o=n.message||"";if(o.includes("This channel does not have a")||o.includes("404")||o.includes("does not exist"))return{_type:"playlist",id:i,title:"Tab Not Found",channel:i.split("/").pop()||"Unknown",channel_id:"Unknown",entries:[]};throw n}}};export{C as PlayEngine,Y as StalkerEngine,v as StreamEngine,P as YtDlpClient,M as getYouTubeVideoId,y as normalizeYoutubeUrl,$ as searchBest,K as searchPlaylistBest};
6
7
  //# sourceMappingURL=index.mjs.map