JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 28
  • Score
    100M100P100Q53284F
  • License MIT

YouTube search + download engine (audio/video) with optional caching.

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
  • 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-play

Binaries (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)
});

For headless environments (VPS, Docker), YouTube authentication is required:

Option 1: Export cookies from browser

  1. Install browser extension: "Get cookies.txt LOCALLY"
  2. Visit youtube.com and login
  3. Export cookies to cookies.txt
  4. 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 download

waitCache(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 cache

getFromCache(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.

Auto-Update System

The engine includes an intelligent auto-update system for yt-dlp:

How it works

  1. Constant Background Checks - Checks GitHub for new yt-dlp releases
  2. Instant Updates on Failure - If a download fails, immediately checks and updates yt-dlp
  3. Automatic Retry - After updating, automatically retries the failed download
  4. Version Detection - Compares installed binary version with latest GitHub release
  5. 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...
// ✓ Success

Manual 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 quality

Custom 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-play

If downloads fail with "Sign in to confirm you're not a bot":

  1. Export fresh cookies from your browser
  2. Ensure cookies.txt is in Netscape format
  3. Verify cookie file path is correct
  4. Try cookiesFromBrowser option 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.mjs

License

MIT

Contributing

Issues and PRs welcome!

Changelog

Deprecated versions have been removed to prevent errors during use.

0.3.2 (Latest)

  • 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 PlayEngine auto-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 .part and .ytdl temporary 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