yt-dlp-ejs is a JavaScript solver library that extracts and executes YouTube’s player challenge functions. The library uses a three-stage architecture: preprocessing, solving, and bundling.
Preprocessing
Parse and transform YouTube’s player JavaScript using AST manipulation
Solving
Extract and execute signature and n-parameter functions
The main function accepts either raw player JavaScript or preprocessed player code, enabling caching of the preprocessing step for improved performance.
Scan statements to identify signature and n-parameter solver functions
export function getSolutions( statements: ESTree.Statement[],): Record<string, ESTree.ArrowFunctionExpression[]> { const found = { n: [] as ESTree.ArrowFunctionExpression[], sig: [] as ESTree.ArrowFunctionExpression[], }; for (const statement of statements) { const n = extractN(statement); if (n) { found.n.push(n); } const sig = extractSig(statement); if (sig) { found.sig.push(sig); } } return found;}
4
Setup Environment
Inject runtime setup code to emulate browser globals
The preprocessor implements a resilient multi-try strategy when multiple potential solver implementations are found:
function multiTry( generators: ESTree.ArrowFunctionExpression[],): ESTree.ArrowFunctionExpression { return { type: "ArrowFunctionExpression", params: [ { type: "Identifier", name: "_input", }, ], body: { type: "BlockStatement", body: [ // Try each generator and collect results in a Set // If exactly one unique result, return it // Otherwise, throw an error ], }, // ... };}
The multi-try strategy attempts all extracted solvers and validates that they produce a single consistent result, providing robustness against YouTube player variations.
The library integrates with yt-dlp through Python bindings:
Python Build Hook
Runtime Detection
hatch_build.py
class CustomBuildHook(BuildHookInterface): def initialize(self, version, build_data): name, cmds, env = build_bundle_cmds() if cmds is None: raise RuntimeError( "One of 'pnpm', 'deno', 'bun', or 'npm' could not be found. " "Please install one of them to proceed with the build." ) print(f"Building with {name}...") for cmd in cmds: subprocess.run(cmd, env=env, check=True) build_data["force_include"].update( { "dist/yt.solver.core.min.js": "yt_dlp_ejs/yt/solver/core.min.js", "dist/yt.solver.lib.min.js": "yt_dlp_ejs/yt/solver/lib.min.js", } )
The build process automatically detects and uses the first available JavaScript runtime (pnpm, Deno, Bun, or npm). All runtimes must produce identical output.