- Loop: toggle via POST /api/media/loop → COMMAND_MEDIA_LOOP; gateway replays finished music track on natural end (queue untouched); status payload exposes loop flag; FE tombol Loop di music-player + mini-player. - Kualitas suara: music playback sekarang di-transcode sekali via ffmpeg ke OggOpus 48kHz stereo 192kbps dengan volume di-bake ke encode — menghindari double lossy encode (inlineVolume) yang bikin suara buram. Screen share tetap pakai jalur lama. - Backend: MediaState.loop, setLoop service, route + schema validation.
55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
import { useEffect } from "react";
|
|
import useSWR, { useSWRConfig } from "swr";
|
|
import { useAction } from "@/hooks/use-action";
|
|
import { mediaApi } from "@/lib/api";
|
|
import type { MediaState } from "@/lib/types";
|
|
import type { WsHook } from "@/lib/ws-hook";
|
|
|
|
const MEDIA_KEY = ["media-state"] as const;
|
|
|
|
export function useMediaState(initialData?: MediaState) {
|
|
return useSWR<MediaState>(MEDIA_KEY, () => mediaApi.getStatus(), {
|
|
refreshInterval: 10_000,
|
|
shouldRetryOnError: false,
|
|
fallbackData: initialData,
|
|
});
|
|
}
|
|
|
|
function useMediaAction<TArgs>(fn: (args: TArgs) => Promise<MediaState>) {
|
|
const { mutate } = useSWRConfig();
|
|
return useAction(fn, {
|
|
onSuccess: (data) => {
|
|
void mutate(MEDIA_KEY, data, { revalidate: false });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useMediaQueue() {
|
|
return useMediaAction((input: { url: string; mode?: "music" | "screen" }) =>
|
|
mediaApi.queue(input.url, input.mode ?? "music"),
|
|
);
|
|
}
|
|
|
|
export function useMediaSkip() {
|
|
return useMediaAction(() => mediaApi.skip());
|
|
}
|
|
|
|
export function useMediaStop() {
|
|
return useMediaAction(() => mediaApi.stop());
|
|
}
|
|
|
|
export function useMediaLoop() {
|
|
return useMediaAction((loop: boolean) => mediaApi.loop(loop));
|
|
}
|
|
|
|
/** Subscribe to WS media_state events to keep cache fresh */
|
|
export function useMediaWsSync(ws: WsHook) {
|
|
const { mutate } = useSWRConfig();
|
|
useEffect(() => {
|
|
const unsub = ws.on("media_state", (data) => {
|
|
void mutate(MEDIA_KEY, data as MediaState, { revalidate: false });
|
|
});
|
|
return unsub;
|
|
}, [ws, mutate]);
|
|
}
|