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:
@@ -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 && (
|
||||
|
||||
@@ -15,7 +15,6 @@ export {
|
||||
useMediaSkip,
|
||||
useMediaState,
|
||||
useMediaStop,
|
||||
useMediaVolume,
|
||||
useMediaWsSync,
|
||||
} from "./use-media";
|
||||
export {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user