Update bundled yt-dlp and report source failures in chat

The image shipped a year-old yt-dlp, which YouTube now rejects with
"The page needs to be reloaded". Bumped to 2026.08.19 and documented
rebuilding as the standard fix, including how to pass a newer tag
without waiting for a repository update.

A downloader dying mid-stream also looked exactly like a very short
track: ffmpeg saw EOF, the player advanced, and the channel only got
"queue finished". The failure reason now reaches the chat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-09-08 23:45:25 +03:00
co-authored by Claude Opus 5
parent 823a9f1565
commit 315760e076
5 changed files with 341 additions and 293 deletions
+8 -1
View File
@@ -110,6 +110,8 @@ export interface PlaybackInput {
input: string | Readable;
inputOptions: string[];
cleanup(): void;
/** Resolves with a reason if the downloader died on its own, for reporting. */
failure?: Promise<string | null>;
}
const HTTP_RESILIENCE = [
@@ -139,5 +141,10 @@ export async function openPlayback(track: Track, seekSeconds = 0): Promise<Playb
}
const proc = ytdlp.openAudioStream(track.url);
return { input: proc.stream, inputOptions: [], cleanup: () => proc.kill() };
return {
input: proc.stream,
inputOptions: [],
cleanup: () => proc.kill(),
failure: proc.failure,
};
}
+15 -3
View File
@@ -209,6 +209,8 @@ export async function resolveStreamUrl(pageUrl: string): Promise<string> {
export interface AudioProcess {
stream: Readable;
kill(): void;
/** Resolves with a reason when the download fails, or null when it was fine. */
failure: Promise<string | null>;
}
/** Spawns yt-dlp writing the best audio to stdout, for piping straight into ffmpeg. */
@@ -220,19 +222,29 @@ export function openAudioStream(pageUrl: string): AudioProcess {
);
let stderr = "";
let killed = false;
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr = (stderr + chunk).slice(-2000);
});
child.on("close", (code) => {
if (code !== 0 && code !== null && stderr.trim()) {
const failure = new Promise<string | null>((resolve) => {
child.on("close", (code) => {
if (killed || code === 0 || code === null) {
resolve(null);
return;
}
log.warn({ code, stderr: stderr.slice(0, 500) }, "yt-dlp stream exited with error");
}
resolve(firstUsefulError(stderr));
});
child.on("error", (err: Error) => resolve(err.message));
});
return {
stream: child.stdout,
failure,
kill: () => {
killed = true;
if (child.exitCode === null) child.kill("SIGKILL");
},
};