refactor(media): remove volume control from FE & BE

Volume sudah di-set default 0.3 di gateway (suara kecil saat play), dan
user bisa naikin sendiri di Discord (command media:volume) — jadi kontrol
volume lewat dashboard tak perlu. Hapus:
- BE: POST /api/media/volume route, mediaVolumeSchema, setVolume service
- FE: useMediaVolume hook, mediaApi.volume, slider volume di music-player
  + mini-player, field volume/setVolume di MediaPlayerProvider
Pertahankan COMMAND_MEDIA_VOLUME di gateway (masih dipakai command DC)
dan mic volume (terpisah, tetap di voice page).
This commit is contained in:
asepharyana
2026-08-07 14:27:33 +07:00
parent 62ffb676f9
commit a690e5b63e
9 changed files with 6 additions and 106 deletions
@@ -2,8 +2,8 @@ import type { Request, Response, Router } from "express";
import express from "express";
import { createChildLogger } from "@/shared/logger/index";
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
import { mediaQueueSchema, mediaVolumeSchema } from "./media.schema.js";
import { getStatus, queue, setVolume, skip, stop } from "./media.service.js";
import { mediaQueueSchema } from "./media.schema.js";
import { getStatus, queue, skip, stop } from "./media.service.js";
const logger = createChildLogger("media.routes");
@@ -55,17 +55,5 @@ export function createMediaRouter(): Router {
}),
);
// POST /api/media/volume
router.post(
"/media/volume",
validateBody(mediaVolumeSchema),
asyncHandler(async (req: Request, res: Response) => {
const { volume } = req.body as { volume: number };
logger.debug({ volume }, "Media volume requested");
const state = await setVolume(volume);
res.json(state);
}),
);
return router;
}
@@ -5,9 +5,4 @@ export const mediaQueueSchema = z.object({
mode: z.enum(["music", "screen"]).default("music"),
});
export const mediaVolumeSchema = z.object({
volume: z.number().min(0).max(1).default(0.3),
});
export type MediaQueueInput = z.infer<typeof mediaQueueSchema>;
export type MediaVolumeInput = z.infer<typeof mediaVolumeSchema>;
@@ -6,7 +6,6 @@ import {
COMMAND_MEDIA_QUEUE,
COMMAND_MEDIA_SKIP,
COMMAND_MEDIA_STOP,
COMMAND_MEDIA_VOLUME,
MEDIA_STATUS_KEY,
} from "../../shared/index.js";
import { publishCommand, readRedisStatus } from "../../shared/redis/index.js";
@@ -149,20 +148,3 @@ export async function stop(): Promise<MediaState> {
"stop",
);
}
/**
* Set volume via Redis command to discord-gateway.
*/
export async function setVolume(volume: number): Promise<MediaState> {
logger.info({ volume }, "setVolume called");
return tryCommandThenFallback(
() =>
publishCommand<MediaState>(
COMMAND_MEDIA_VOLUME,
{ volume },
DEFAULT_COMMAND_TIMEOUT_MS,
),
() => readStatusFallback(),
"setVolume",
);
}
@@ -1,11 +1,10 @@
"use client";
import { Disc3, Music, SkipForward, Square, Volume2 } from "lucide-react";
import { Disc3, Music, SkipForward, Square } from "lucide-react";
import { useMediaPlayer } from "@/lib/hooks/use-media-player";
export function MiniPlayer() {
const { playing, current, queue, volume, pending, skip, stop, setVolume } =
useMediaPlayer();
const { playing, current, queue, pending, skip, stop } = useMediaPlayer();
// Nothing to show if no track is playing and nothing is queued
if (!current && queue.length === 0) return null;
@@ -61,22 +60,6 @@ export function MiniPlayer() {
</button>
)}
</div>
{/* Volume */}
<div className="flex items-center gap-2 shrink-0 ml-2">
<Volume2 className="size-3.5 text-text-secondary/60" />
<input
type="range"
min="0"
max="1"
step="0.05"
value={volume}
onChange={(e) => setVolume(Number(e.target.value))}
className="w-20 h-1 appearance-none rounded-full bg-glass-bg accent-primary cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary"
aria-label="Volume"
/>
</div>
</div>
);
}
@@ -1,24 +1,21 @@
"use client";
import { Disc3, Music, Play, SkipForward, Square, Volume2 } from "lucide-react";
import { Disc3, Music, Play, SkipForward, Square } from "lucide-react";
import Image from "next/image";
import { useCallback, useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Slider } from "@/components/ui/slider";
import {
useMediaQueue,
useMediaSkip,
useMediaState,
useMediaStop,
useMediaVolume,
useMediaWsSync,
} from "@/hooks";
import type { WsHook } from "@/lib/ws-hook";
import type { MediaState } from "@/lib/types";
import type { WsHook } from "@/lib/ws-hook";
interface MusicPlayerProps {
ws: WsHook;
@@ -31,7 +28,6 @@ export function MusicPlayer({ ws, initialData }: MusicPlayerProps) {
const queueMut = useMediaQueue();
const skipMut = useMediaSkip();
const stopMut = useMediaStop();
const volumeMut = useMediaVolume();
const [queueUrl, setQueueUrl] = useState("");
const [screenMode, setScreenMode] = useState(false);
@@ -47,14 +43,6 @@ export function MusicPlayer({ ws, initialData }: MusicPlayerProps) {
setQueueUrl("");
}, [queueUrl, queueMut, screenMode]);
const handleVolume = useCallback(
(value: number | readonly number[]) => {
const vol = Array.isArray(value) ? value[0] : value;
volumeMut.mutate(vol);
},
[volumeMut],
);
return (
<Card>
<CardHeader>
@@ -143,18 +131,6 @@ export function MusicPlayer({ ws, initialData }: MusicPlayerProps) {
<SkipForward className="size-4 mr-1" />
Skip
</Button>
<div className="flex items-center gap-2 ml-auto">
<Volume2 className="size-4 text-muted-foreground" />
<Slider
className="w-24"
defaultValue={[mediaState?.musicVolume ?? 0.3]}
value={[mediaState?.musicVolume ?? 0.3]}
onValueChange={handleVolume}
min={0}
max={1}
step={0.05}
/>
</div>
</div>
{mediaState && mediaState.queue.length > 0 && (
-1
View File
@@ -15,7 +15,6 @@ export {
useMediaSkip,
useMediaState,
useMediaStop,
useMediaVolume,
useMediaWsSync,
} from "./use-media";
export {
-4
View File
@@ -38,10 +38,6 @@ export function useMediaStop() {
return useMediaAction(() => mediaApi.stop());
}
export function useMediaVolume() {
return useMediaAction((volume: number) => mediaApi.volume(volume));
}
/** Subscribe to WS media_state events to keep cache fresh */
export function useMediaWsSync(ws: WsHook) {
const { mutate } = useSWRConfig();
-2
View File
@@ -7,6 +7,4 @@ export const mediaApi = {
api.post<MediaState>("/api/media/queue", { source, mode }),
skip: () => api.post<MediaState>("/api/media/skip", {}),
stop: () => api.post<MediaState>("/api/media/stop", {}),
volume: (volume: number) =>
api.post<MediaState>("/api/media/volume", { volume }),
};
@@ -20,8 +20,6 @@ interface MediaPlayerContextValue {
current: MediaItem | null;
/** Upcoming queue */
queue: MediaItem[];
/** Current volume [0-1] */
volume: number;
/** True while a mutation is in flight */
pending: boolean;
@@ -29,8 +27,6 @@ interface MediaPlayerContextValue {
skip: () => void;
/** Stop playback */
stop: () => void;
/** Set volume [0-1] */
setVolume: (vol: number) => void;
/** Queue a URL for playback */
queueUrl: (url: string) => void;
}
@@ -96,17 +92,6 @@ export function MediaPlayerProvider({ children }: { children: ReactNode }) {
.finally(() => setPending(false));
}, []);
const setVolume = useCallback((vol: number) => {
mediaApi
.volume(vol)
.then((data) => {
if (data) setState(data as MediaState);
})
.catch(() => {
// ignore
});
}, []);
const queueUrl = useCallback((url: string) => {
setPending(true);
mediaApi
@@ -126,11 +111,9 @@ export function MediaPlayerProvider({ children }: { children: ReactNode }) {
playing: state.playing,
current: state.current,
queue: state.queue,
volume: state.musicVolume,
pending,
skip,
stop,
setVolume,
queueUrl,
}}
>