refactor: clean up code structure and improve error handling in media controllers

This commit is contained in:
MythEclipse
2026-05-16 23:11:46 +07:00
parent 9b211f05cf
commit 7dedac2094
6 changed files with 44 additions and 23 deletions

View File

@@ -2,7 +2,6 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Discord Moderation Dashboard</title> <title>Discord Moderation Dashboard</title>
</head> </head>

View File

@@ -144,16 +144,22 @@ export default function App() {
}, [isStreaming, startStreamingLocal, stopStreamingLocal, patchUIState]); }, [isStreaming, startStreamingLocal, stopStreamingLocal, patchUIState]);
useEffect(() => { useEffect(() => {
if (selectedVoiceGuild) voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined); if (selectedVoiceGuild) {
}, [selectedVoiceGuild, voice.loadVoiceChannels]); voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined);
}
}, [selectedVoiceGuild]);
useEffect(() => { useEffect(() => {
if (selectedTextGuild) voice.loadTextTargets(selectedTextGuild).catch(() => undefined); if (selectedTextGuild) {
}, [selectedTextGuild, voice.loadTextTargets]); voice.loadTextTargets(selectedTextGuild).catch(() => undefined);
}
}, [selectedTextGuild]);
useEffect(() => { useEffect(() => {
if (selectedTextChannel) {
messages.fetchMessages(selectedTextChannel).catch(() => undefined); messages.fetchMessages(selectedTextChannel).catch(() => undefined);
}, [selectedTextChannel, messages.fetchMessages]); }
}, [selectedTextChannel]);
const toggleListening = useCallback(async () => { const toggleListening = useCallback(async () => {
if (isListening) { if (isListening) {

View File

@@ -23,7 +23,7 @@ export function MessageCard({ message, onReanalyze }: MessageCardProps) {
<article className="rounded-2xl border border-border bg-card p-4 shadow-sm"> <article className="rounded-2xl border border-border bg-card p-4 shadow-sm">
<div className="flex gap-3"> <div className="flex gap-3">
<img <img
src={message.avatar_url ?? "/default-avatar.png"} src={message.avatar_url ?? "https://cdn.discordapp.com/embed/avatars/0.png"}
alt="" alt=""
className="h-10 w-10 rounded-full object-cover" className="h-10 w-10 rounded-full object-cover"
/> />

View File

@@ -18,11 +18,15 @@ export function useMessages() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const fetchMessages = useCallback(async (channelId?: string) => { const fetchMessages = useCallback(async (channelId?: string) => {
if (!channelId) {
setMessages([]);
return [];
}
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const params = new URLSearchParams({ limit: "80" }); const params = new URLSearchParams({ limit: "80" });
if (channelId) params.set("channel", channelId); params.set("channel", channelId);
const result = await listMessages(params); const result = await listMessages(params);
setMessages(result.data); setMessages(result.data);
return result.data; return result.data;

View File

@@ -52,9 +52,21 @@ export class MediaController {
): Promise<MediaState> { ): Promise<MediaState> {
const mode = options.mode ?? "music"; const mode = options.mode ?? "music";
if (mode === "screen") { if (mode === "screen") {
// Stop current music if any
this.playbackToken++;
this.playback?.stop();
this.playback = null;
return this.startScreen(source); return this.startScreen(source);
} }
// mode === "music"
// Stop screen if active
if (this.screenPlayback || this.dependencies.screenController?.isActive()) {
this.screenPlayback?.stop();
this.screenPlayback = null;
this.activeMode = null;
}
this.assertCanStartMusic(); this.assertCanStartMusic();
const resolved = await ( const resolved = await (
this.dependencies.resolveMediaSource ?? resolveMediaSource this.dependencies.resolveMediaSource ?? resolveMediaSource
@@ -108,10 +120,6 @@ export class MediaController {
); );
} }
if (this.screenPlayback || this.dependencies.screenController?.isActive()) {
throw new AppError("Another media mode is active", "MEDIA_BUSY", 409);
}
if (this.dependencies.isBrowserStreaming?.()) { if (this.dependencies.isBrowserStreaming?.()) {
throw new AppError( throw new AppError(
"Stop browser microphone streaming before playing media", "Stop browser microphone streaming before playing media",
@@ -122,14 +130,6 @@ export class MediaController {
} }
private async startScreen(source: string): Promise<MediaState> { private async startScreen(source: string): Promise<MediaState> {
if (
this.screenPlayback ||
this.dependencies.screenController?.isActive() ||
this.playback ||
this.queueStore.snapshot().current
) {
throw new AppError("Another media mode is active", "MEDIA_BUSY", 409);
}
const screenController = this.dependencies.screenController; const screenController = this.dependencies.screenController;
if (!screenController) { if (!screenController) {
throw new AppError( throw new AppError(

View File

@@ -6,7 +6,10 @@ import {
Utils, Utils,
} from "@dank074/discord-video-stream"; } from "@dank074/discord-video-stream";
import { AppError } from "../errors"; import { AppError } from "../errors";
import { createChildLogger } from "../logger";
import { discordPlayer } from "../player"; import { discordPlayer } from "../player";
const logger = createChildLogger("screen-share");
import type { DiscordPlayerOwner, ScreenSharePlayback } from "./mediaTypes"; import type { DiscordPlayerOwner, ScreenSharePlayback } from "./mediaTypes";
import { createYtDlp } from "./ytdlp"; import { createYtDlp } from "./ytdlp";
@@ -77,8 +80,8 @@ export function createScreenShareController(
); );
} }
if (active || getPlayerOwner() !== "none") { if (active) {
throw new AppError("Another media mode is active", "MEDIA_BUSY", 409); active.stop();
} }
try { try {
@@ -93,6 +96,15 @@ export function createScreenShareController(
videoCodec: Utils.normalizeVideoCodec("H264"), videoCodec: Utils.normalizeVideoCodec("H264"),
}); });
// Add FFmpeg error logging
if (command && "stderr" in command && (command as any).stderr) {
(command as any).stderr.on("data", (data: Buffer) => {
if (data.toString().includes("Error")) {
logger.error({ error: data.toString() }, "FFmpeg Screen Error");
}
});
}
let stopped = false; let stopped = false;
const done = playStream(output, dependencies.streamer, { const done = playStream(output, dependencies.streamer, {
type: "go-live", type: "go-live",