- Introduced a new `ScreenShareController` to manage YouTube screenshare functionality. - Updated `DiscordPlayer` to track ownership of audio streams, preventing conflicts between music playback and screenshare. - Added error handling for various states including voice connection checks and media busy states. - Created unit tests for `ScreenShareController` and `DiscordPlayer` ownership rules to ensure correct functionality. - Added documentation for the new media echo fix and screenshare design.
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { EventEmitter } from "node:events";
|
|
import { PassThrough } from "node:stream";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { createYtDlp } from "../../src/media/ytdlp";
|
|
|
|
class FakeProcess extends EventEmitter {
|
|
stdout = new PassThrough();
|
|
stderr = new PassThrough();
|
|
}
|
|
|
|
describe("createYtDlp", () => {
|
|
it("reads YouTube metadata as JSON", async () => {
|
|
const proc = new FakeProcess();
|
|
const spawn = vi.fn(() => proc);
|
|
const ytdlp = createYtDlp({ spawn });
|
|
|
|
const result = ytdlp.getMetadata("https://youtu.be/video");
|
|
proc.stdout.write(
|
|
JSON.stringify({
|
|
title: "Song Title",
|
|
webpage_url: "https://youtube.com/watch?v=video",
|
|
}),
|
|
);
|
|
proc.stdout.end();
|
|
proc.emit("close", 0);
|
|
|
|
await expect(result).resolves.toEqual({
|
|
title: "Song Title",
|
|
webpageUrl: "https://youtube.com/watch?v=video",
|
|
});
|
|
expect(spawn).toHaveBeenCalledWith(
|
|
"yt-dlp",
|
|
[
|
|
"https://youtu.be/video",
|
|
"--dump-single-json",
|
|
"--no-playlist",
|
|
"--no-warnings",
|
|
"--quiet",
|
|
],
|
|
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
);
|
|
});
|
|
|
|
it("reads direct audio URL", async () => {
|
|
const proc = new FakeProcess();
|
|
const spawn = vi.fn(() => proc);
|
|
const ytdlp = createYtDlp({ spawn });
|
|
|
|
const result = ytdlp.getDirectAudioUrl("https://youtu.be/video");
|
|
proc.stdout.write("https://audio.example.com/stream\n");
|
|
proc.stdout.end();
|
|
proc.emit("close", 0);
|
|
|
|
await expect(result).resolves.toBe("https://audio.example.com/stream");
|
|
expect(spawn).toHaveBeenCalledWith(
|
|
"yt-dlp",
|
|
[
|
|
"https://youtu.be/video",
|
|
"--get-url",
|
|
"--format",
|
|
"bestaudio[protocol^=http]/bestaudio/best",
|
|
"--no-playlist",
|
|
"--no-warnings",
|
|
"--quiet",
|
|
],
|
|
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
);
|
|
});
|
|
|
|
it("reads direct video URL", async () => {
|
|
const proc = new FakeProcess();
|
|
const spawn = vi.fn(() => proc);
|
|
const ytdlp = createYtDlp({ spawn });
|
|
|
|
const result = ytdlp.getDirectVideoUrl("https://youtu.be/video");
|
|
proc.stdout.write("https://video.example.com/stream\n");
|
|
proc.stdout.end();
|
|
proc.emit("close", 0);
|
|
|
|
await expect(result).resolves.toBe("https://video.example.com/stream");
|
|
expect(spawn).toHaveBeenCalledWith(
|
|
"yt-dlp",
|
|
[
|
|
"https://youtu.be/video",
|
|
"--get-url",
|
|
"--format",
|
|
"bestvideo[protocol^=http]+bestaudio[protocol^=http]/best[protocol^=http]/best",
|
|
"--no-playlist",
|
|
"--no-warnings",
|
|
"--quiet",
|
|
],
|
|
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
);
|
|
});
|
|
|
|
it("rejects when yt-dlp exits non-zero", async () => {
|
|
const proc = new FakeProcess();
|
|
const ytdlp = createYtDlp({ spawn: vi.fn(() => proc) });
|
|
|
|
const result = ytdlp.getMetadata("https://youtu.be/video");
|
|
proc.stderr.write("failed");
|
|
proc.stderr.end();
|
|
proc.emit("close", 1);
|
|
|
|
await expect(result).rejects.toThrow("yt-dlp failed with code 1");
|
|
});
|
|
});
|