Package Exports
- @irithell-js/yt-play
Readme
@irithell-js/yt-play
High-performance YouTube audio/video download engine with intelligent caching, built-in yt-dlp and aria2c binaries for blazing fast downloads.
Features
- Bundled Binaries - yt-dlp and aria2c included (no system dependencies)
- Auto-Update System - yt-dlp updates automatically when needed
- Ultra Fast Downloads - aria2c acceleration (up to 5x faster)
- Playlist Support - Search, extract, and resolve direct media URLs from entire playlists in batches
- Shorts Support - Download and convertion for more compatibility
- Metadata Stalking - Deep extraction of channels, videos, and live streams (real-time viewers, hidden tags, etc).
- Native Streaming - Get raw
Readablestreams for audio or video without saving to disk first, perfect for Discord bots or streaming directly to HTTP responses. - Intelligent Caching - TTL-based cache with automatic cleanup
- Smart Quality - Auto-reduces quality for long videos (>1h)
- Container Ready - Works in Docker/isolated environments with cookie support
- Auto-Configuration - Creates yt-dlp.conf with optimal settings
- Cross-Platform - Linux (x64/arm64), macOS, Windows
- Zero Config - Auto-detects binaries and optimizes settings
- TypeScript - Full type definitions included
- Dual Format - ESM and CommonJS support
Installation
npm install @irithell-js/yt-playBinaries (yt-dlp + aria2c) are automatically downloaded during installation (this may take a few seconds during the first use).
Quick Start
Basic Usage (ESM)
import { PlayEngine } from "@irithell-js/yt-play";
const engine = new PlayEngine();
// Search and download
const metadata = await engine.search("linkin park numb");
if (!metadata) throw new Error("Not found");
const requestId = engine.generateRequestId();
await engine.preload(metadata, requestId);
// Get audio file
const { file: audioFile } = await engine.getOrDownload(requestId, "audio");
console.log("Audio:", audioFile.path);
// Get video file
const { file: videoFile } = await engine.getOrDownload(requestId, "video");
console.log("Video:", videoFile.path);
// Cleanup cache
engine.cleanup(requestId);Basic Usage (CommonJS)
const { PlayEngine } = require("@irithell-js/yt-play");
const engine = new PlayEngine();
async function download() {
const metadata = await engine.search("song name");
const requestId = engine.generateRequestId();
await engine.preload(metadata, requestId);
const { file } = await engine.getOrDownload(requestId, "audio");
console.log("Downloaded:", file.path);
engine.cleanup(requestId);
}
download();Direct URL Support
// Search by name
const metadata1 = await engine.search("never gonna give you up");
// Or use direct YouTube URL
const metadata2 = await engine.search(
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
);
// Both return the same metadata format
console.log(metadata2.title); // "Rick Astley - Never Gonna Give You Up"Playlist Support
You can search and extract direct media URLs from playlists with automatic batch processing:
import { YtDlpClient, searchPlaylistBest } from "@irithell-js/yt-play";
// 1. Find a playlist (by search term or direct URL)
const metadata = await searchPlaylistBest("Lofi hip hop");
if (metadata) {
const ytdlp = new YtDlpClient();
// 2. Extract and resolve direct streaming URLs
const playlistInfo = await ytdlp.getPlaylistInfo(metadata.url, {
limit: 5, // Get exactly 5 valid tracks
resolveLinks: true, // Automatically resolve direct media URLs
type: "audio", // "audio", "video", or "both"
batchSize: 5, // Process 5 tracks concurrently for maximum speed
});
playlistInfo.entries.forEach((track: any) => {
console.log(`Title: ${track.title}`);
console.log(`Direct URL: ${track.directUrl}\n`);
});
}Example Output:
{
"id": "PLjNlQ2vXx1xbt30X8TcUfNzw_akVISXEu",
"title": "BEST Anime Openings and Endings Songs *Playlist*",
"uploader": "by - AnimeEnfermos -",
"entries": [
{
"id": "jIfogFtgV-o",
"title": "Noragami / Opening 2",
"url": "https://www.youtube.com/watch?v=jIfogFtgV-o",
"duration": 103,
"uploader": "reeaaas",
"directUrl": "https://rr2---sn-103oxu-bg0l.go..."
},
{
"id": "0YF8vecQWYs",
"title": "Crying for Rain - 美波 (Minami) MV",
"url": "https://www.youtube.com/watch?v=0YF8vecQWYs",
"duration": 254,
"uploader": "Minami",
"directUrl": "https://rr1---sn-103oxu-bg0l.go..."
},
{
"id": "pmanD_s7G3U",
"title": "Demon Slayer | OP | Gurenge by LiSA HD",
"url": "https://www.youtube.com/watch?v=pmanD_s7G3U",
"duration": 90,
"uploader": "animelab",
"directUrl": "https://rr1---sn-103oxu-bg0l.go"
},
{
"id": "atxYe-nOa9w",
"title": "One Punch Man - Official Opening - The Hero!! Set Fire to the Furious Fist",
"url": "https://www.youtube.com/watch?v=atxYe-nOa9w",
"duration": 90,
"uploader": "animelab",
"directUrl": "https://rr1---sn-103oxu-bg0l.go..."
},
{
"id": "792vg0amsuQ",
"title": "Steins;Gate 0 - op pt-br legendado",
"url": "https://www.youtube.com/watch?v=792vg0amsuQ",
"duration": 243,
"uploader": "Animes Nii-san",
"directUrl": "https://rr1---sn-103oxu-bg0l.go..."
}
]
}Configuration
Constructor Options
const engine = new PlayEngine({
// Cache settings
cacheDir: "./cache", // Cache directory (default: OS temp)
ttlMs: 5 * 60_000, // Cache TTL in ms (default: 3min)
cleanupIntervalMs: 30_000, // Cleanup interval (default: 30s)
preloadBuffer: true, // Load files into RAM (default: true)
// Quality settings
preferredAudioKbps: 128, // Audio quality: 320|256|192|128|96|64
preferredVideoP: 720, // Video quality: 1080|720|480|360
maxPreloadDurationSeconds: 1200, // Max duration for preload (default: 20min)
// Performance settings (auto-optimized)
useAria2c: true, // Use aria2c for downloads (default: auto)
concurrentFragments: 8, // Parallel fragments (default: 5)
ytdlpTimeoutMs: 300_000, // yt-dlp timeout (default: 5min)
// Binary paths (optional - auto-detected)
ytdlpBinaryPath: "./bin/yt-dlp",
aria2cPath: "./bin/aria2c",
ffmpegPath: "/usr/bin/ffmpeg", // Optional
// Cookies (required for VPS/Docker environments)
cookiesPath: "./cookies.txt", // Path to Netscape cookies file
// OR
cookiesFromBrowser: "firefox", // Extract from browser: chrome, firefox, edge, safari
// Logging
logger: console, // Logger instance (optional)
});Cookie Configuration (VPS/Docker)
For headless environments (VPS, Docker), YouTube authentication is required:
Option 1: Export cookies from browser
- Install browser extension: "Get cookies.txt LOCALLY"
- Visit youtube.com and login
- Export cookies to
cookies.txt - Use in PlayEngine:
const engine = new PlayEngine({
cookiesPath: "/path/to/cookies.txt",
});Option 2: Extract directly from browser (requires browser installed)
const engine = new PlayEngine({
cookiesFromBrowser: "firefox", // chrome, firefox, edge, safari
});The engine automatically creates yt-dlp.conf with:
- Node.js runtime detection
- EJS remote components
- Cookie authentication
- Optimal download settings
Quality Presets
// High quality (larger files)
const hq = new PlayEngine({
preferredAudioKbps: 320,
preferredVideoP: 1080,
});
// Balanced (recommended)
const balanced = new PlayEngine({
preferredAudioKbps: 128,
preferredVideoP: 720,
});
// Low quality (faster, smaller)
const lq = new PlayEngine({
preferredAudioKbps: 96,
preferredVideoP: 480,
});API Reference
Exported Functions
searchBest(query: string): Promise<PlayMetadata | null>
Search for a video on YouTube or get metadata from a URL.
searchPlaylistBest(query: string): Promise<PlayMetadata | null>
Search for a playlist on YouTube or get metadata from a direct playlist URL. Returns the list ID and base URL.
PlayEngine Methods
search(query: string): Promise<PlayMetadata | null>
Search for a video on YouTube or get metadata from URL.
// Search by name
const metadata = await engine.search("artist - song name");
// Or use direct URL
const metadata = await engine.search(
"https://www.youtube.com/watch?v=VIDEO_ID",
);
// Returns: { title, author, duration, durationSeconds, thumb, videoId, url }generateRequestId(prefix?: string): string
Generate unique request ID for caching.
const requestId = engine.generateRequestId("audio"); // "audio_1234567890_abc123"preload(metadata: PlayMetadata, requestId: string): Promise<void>
Pre-download audio and video in parallel (cached for TTL duration).
await engine.preload(metadata, requestId);
// Downloads audio + video if <1h, only audio if >1h (96kbps)getOrDownload(requestId: string, type: 'audio' | 'video'): Promise<Result>
Get file from cache or download directly.
const result = await engine.getOrDownload(requestId, "audio");
// Returns: { metadata, file: { path, size, info, buffer? }, direct: boolean }
console.log(result.file.path); // "/tmp/cache/audio_xxx.m4a"
console.log(result.file.size); // 8457234 (bytes)
console.log(result.file.info.quality); // "128kbps m4a"
console.log(result.direct); // false if from cache, true if direct downloadwaitCache(requestId: string, type: 'audio' | 'video', timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>
Wait for cache to be ready (useful for checking preload status).
const cached = await engine.waitCache(requestId, "audio", 8000, 500);
if (cached) {
console.log("Cache ready:", cached.path);
} else {
console.log("Timeout - falling back to direct download");
}cleanup(requestId: string): void
Remove cached files for a request.
engine.cleanup(requestId); // Deletes audio + video from cachegetFromCache(requestId: string): CacheEntry | undefined
Get cache entry metadata (without downloading).
const entry = engine.getFromCache(requestId);
if (entry) {
console.log(entry.metadata.title);
console.log(entry.audio?.path);
console.log(entry.loading); // true if preload in progress
}YtDlpClient Methods
getPlaylistInfo(url: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>
Fetches all entries from a playlist efficiently. If resolveLinks: true is provided in options, it will batch-resolve the direct playable streaming URLs for each valid track up to the provided limit.
StalkerEngine Methods
The StalkerEngine allows you to deeply inspect channels, videos, and live streams to extract valuable metadata like hidden tags, exact subscriber counts, and real-time live viewers.
import { YtDlpClient, StalkerEngine } from "@irithell-js/yt-play";
const client = new YtDlpClient({ cookiesFromBrowser: "firefox" });
const stalker = new StalkerEngine(client);stalkVideoOrLive(url: string): Promise<YtDlpVideoMetadata>
Extracts complete metadata from a single video or live stream.
const live = await stalker.stalkVideoOrLive(
"https://youtube.com/watch?v=LIVE_ID",
);
console.log(live.title);
console.log(live.view_count);
if (live.is_live) {
console.log(`Live right now with ${live.concurrent_view_count} viewers!`);
}stalkChannel(url: string, opts?: StalkChannelOptions): Promise<YtDlpChannelMetadata>
Scrapes channel data. You can target specific tabs (videos, shorts, streams, popular) and set specific ranges.
// Get the 5 most recent videos from a channel
const recentVideos = await stalker.stalkChannel(
"https://youtube.com/@MrBeast",
{
flat: true, // Faster extraction (basic info only)
tab: "videos", // "videos" | "shorts" | "streams" | "popular" | "featured"
endItem: 5, // Get items 1 to 5
},
);
// Get videos from index 500 to 510
const olderVideos = await stalker.stalkChannel("https://youtube.com/@MrBeast", {
flat: true,
tab: "videos",
startItem: 500,
endItem: 510,
});StreamEngine Methods
The StreamEngine bypasses disk caching completely and returns a Node.js Readable stream. This is ideal for Discord music bots (passing the stream directly to the voice connection) or proxying the download via an HTTP API.
It is automatically available inside the PlayEngine or can be initialized separately.
getAudioStream(url: string, qualityKbps?: number): Promise<StreamInfo>
Returns a readable audio stream (defaults to 128kbps M4A).
import fs from "node:fs";
const { stream, quality, format } = await engine.stream.getAudioStream(
"https://youtube.com/watch?v=VIDEO_ID",
128, // preferred kbps
);
console.log(`Streaming ${quality} in ${format} format...`);
// Pipe directly to a file, an HTTP response, or Discord voice
stream.pipe(fs.createWriteStream("output.m4a"));getVideoStream(url: string, qualityP?: number): Promise<StreamInfo>
Returns a readable video stream (defaults to 720p MP4).
const { stream, quality, format } = await engine.stream.getVideoStream(
"https://youtube.com/watch?v=VIDEO_ID",
1080, // preferred height (1080p, 720p, etc)
);
stream.pipe(fs.createWriteStream("output.mp4"));Auto-Update System
The engine includes an intelligent auto-update system for yt-dlp:
How it works
- Constant Background Checks - Checks GitHub for new yt-dlp releases
- Instant Updates on Failure - If a download fails, immediately checks and updates yt-dlp
- Automatic Retry - After updating, automatically retries the failed download
- Version Detection - Compares installed binary version with latest GitHub release
- Zero Downtime - Updates happen in background without blocking your application
Update Behavior
const engine = new PlayEngine();
// ✓ Checks for updates in background
// If download fails
await engine.getOrDownload(requestId, "audio");
// <!> Download failed. Forcing yt-dlp update check...
// >> Updating yt-dlp to 2026.02.04...
// ✓ Updated to 2026.02.04
// >> Retrying download after update...
// ✓ SuccessManual Update Check
To force an update check, simply trigger a download. The system will automatically update if needed.
Advanced Usage
Handle Long Videos (>1h)
const metadata = await engine.search("2h music mix");
// Automatically uses:
// - Audio: 96kbps (reduced quality)
// - Video: skipped (audio only)
await engine.preload(metadata, requestId);
const { file } = await engine.getOrDownload(requestId, "audio");
// Fast download with reduced qualityCustom Cache Directory
import path from "path";
const engine = new PlayEngine({
cacheDir: path.join(process.cwd(), "downloads"),
ttlMs: 10 * 60_000, // 10 minutes
});Performance Monitoring
const startTime = Date.now();
await engine.preload(metadata, requestId);
const preloadTime = Date.now() - startTime;
console.log(`Preload took ${(preloadTime / 1000).toFixed(2)}s`);Error Handling
try {
const metadata = await engine.search("non-existent-video");
if (!metadata) {
console.error("Video not found");
return;
}
await engine.preload(metadata, requestId);
const { file } = await engine.getOrDownload(requestId, "audio");
console.log("Success:", file.path);
} catch (error) {
console.error("Download failed:", error.message);
// Engine automatically tries to update yt-dlp and retry
}Performance
With aria2c enabled (default):
| Video Length | Audio Download | Video Download | Total Time |
|---|---|---|---|
| 5 min | ~3-5s | ~6-8s | ~8s |
| 1 hour | ~15-20s | Audio only | ~20s |
| 2 hours | ~25-30s | Audio only | ~30s |
Times may vary based on network speed and YouTube throttling
The values are based on local tests with optimized caching, for downloading long videos use direct download
File Formats
- Audio: M4A (native format, no conversion needed)
- Video: MP4 (with audio merged)
M4A provides better quality-to-size ratio and downloads 2-5x faster (no re-encoding).
Requirements
- Node.js >= 18.0.0
- ~50MB disk space for binaries (auto-downloaded)
- Optional: ffmpeg for advanced features
Binaries
The package automatically downloads and manages:
- yt-dlp (auto-updates to latest version) (~35 MB)
- aria2c v1.37.0 (12 MB)
Binaries are platform-specific and downloaded on first npm install.
Auto-Update Schedule
- Background Check: Constant version check
- On-Demand: Immediately when download fails
- Version Source: GitHub Releases API
Supported Platforms
- Linux x64 / arm64
- macOS x64 / arm64 (Apple Silicon)
- Windows x64
Manual Binary Paths
const engine = new PlayEngine({
ytdlpBinaryPath: "/custom/path/yt-dlp",
aria2cPath: "/custom/path/aria2c",
});Troubleshooting
Slow Downloads
// Enable aria2c explicitly
const engine = new PlayEngine({
useAria2c: true,
concurrentFragments: 10, // Increase parallelism
});Cache Issues
// Clear cache directory manually
import fs from "fs";
fs.rmSync("./cache", { recursive: true, force: true });Binary Not Found
Binaries are auto-downloaded to node_modules/@irithell-js/yt-play/bin/. If missing:
npm rebuild @irithell-js/yt-playCookie Authentication Issues
If downloads fail with "Sign in to confirm you're not a bot":
- Export fresh cookies from your browser
- Ensure cookies.txt is in Netscape format
- Verify cookie file path is correct
- Try
cookiesFromBrowseroption if browser is installed
Update Check Failures
If auto-update fails:
# Manually update binaries
cd node_modules/@irithell-js/yt-play
node scripts/setup-binaries.mjsLicense
MIT
Contributing
Issues and PRs welcome!
Changelog
Deprecated versions have been removed to prevent errors during use.
0.3.3 (Latest)
- Added
StalkerEnginefor deep metadata extraction from videos, live streams, and entire channels. - Added advanced channel scraping with support for specific tabs (
videos,shorts,streams,popular) and precise item ranges. - Added real-time live stream metadata detection (
is_live,concurrent_view_count, etc.). - Added
StreamEngineto return raw Node.jsReadablestreams for audio and video, bypassing disk storage entirely (ideal for Discord bots and HTTP streaming). - Improved error handling to gracefully return empty arrays when a requested channel tab does not exist instead of crashing.
0.3.2
- Added full support for YouTube Playlists (search by name or URL via
searchPlaylistBest) - Added fast batch processing to resolve direct media URLs from playlists (
resolveLinks) with auto-replenishment - Fixed race conditions in the
PlayEngineauto-update system during concurrent downloads - Fixed binary setup scripts to use atomic downloads (temp files), preventing missing or corrupted binaries on connection drops
- Added automatic cleanup of leftover
.partand.ytdltemporary files on download failure or timeout
0.2.8
- Fixed Codec for shorts to work in all plataforms
0.2.7
- Fixed parse for shorts
0.2.6
- Added auto-update system for yt-dlp
- Direct YouTube URL support in search()
- Auto-configuration system (creates yt-dlp.conf)
- Cookie authentication support (cookiesPath/cookiesFromBrowser)
- Fixed URL search returning wrong video
- Improved error handling with automatic retry after update
0.2.5
- Added support to direct cookies extraction in pre-built browsers
0.2.4
- Added support to cookies.txt
0.2.3
- Updated documentation
- Improved error messages
0.2.2
- Many syntax errors fixed
0.2.1
- Added auto-detection for yt-dlp and aria2c binaries
- Fixed CommonJS compatibility
- Improved error handling for long videos
0.2.0
- Initial release with bundled binaries
- aria2c acceleration support
- Intelligent caching system