feat(volume): serverseitig pro Guild persistieren (state.json), API GET/POST /api/volume; Frontend lädt gespeicherte Lautstärke
This commit is contained in:
parent
fee1feaca4
commit
beeffb7605
3 changed files with 71 additions and 5 deletions
|
|
@ -39,6 +39,36 @@ if (!DISCORD_TOKEN) {
|
||||||
|
|
||||||
fs.mkdirSync(SOUNDS_DIR, { recursive: true });
|
fs.mkdirSync(SOUNDS_DIR, { recursive: true });
|
||||||
|
|
||||||
|
// Persistente Lautstärke pro Guild speichern
|
||||||
|
type PersistedState = { volumes: Record<string, number> };
|
||||||
|
const STATE_FILE = path.join(path.resolve(SOUNDS_DIR, '..'), 'state.json');
|
||||||
|
|
||||||
|
function readPersistedState(): PersistedState {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(STATE_FILE)) {
|
||||||
|
const raw = fs.readFileSync(STATE_FILE, 'utf8');
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return { volumes: parsed.volumes ?? {} } as PersistedState;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return { volumes: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePersistedState(state: PersistedState): void {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
||||||
|
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Persisted state konnte nicht geschrieben werden:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistedState: PersistedState = readPersistedState();
|
||||||
|
const getPersistedVolume = (guildId: string): number => {
|
||||||
|
const v = persistedState.volumes[guildId];
|
||||||
|
return typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
|
||||||
|
};
|
||||||
|
|
||||||
// --- Voice Abhängigkeiten prüfen ---
|
// --- Voice Abhängigkeiten prüfen ---
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
// init nacl to ensure it loads
|
// init nacl to ensure it loads
|
||||||
|
|
@ -284,7 +314,7 @@ app.post('/api/play', async (req: Request, res: Response) => {
|
||||||
});
|
});
|
||||||
const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
|
const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
|
||||||
connection.subscribe(player);
|
connection.subscribe(player);
|
||||||
state = { connection, player, guildId, channelId, currentVolume: 1 };
|
state = { connection, player, guildId, channelId, currentVolume: getPersistedVolume(guildId) };
|
||||||
guildAudioState.set(guildId, state);
|
guildAudioState.set(guildId, state);
|
||||||
|
|
||||||
// Connection State Logs
|
// Connection State Logs
|
||||||
|
|
@ -331,7 +361,7 @@ app.post('/api/play', async (req: Request, res: Response) => {
|
||||||
});
|
});
|
||||||
const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
|
const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
|
||||||
connection.subscribe(player);
|
connection.subscribe(player);
|
||||||
state = { connection, player, guildId, channelId, currentVolume: 1 };
|
state = { connection, player, guildId, channelId, currentVolume: getPersistedVolume(guildId) };
|
||||||
guildAudioState.set(guildId, state);
|
guildAudioState.set(guildId, state);
|
||||||
|
|
||||||
state.connection = await ensureConnectionReady(connection, channelId, guildId, guild);
|
state.connection = await ensureConnectionReady(connection, channelId, guildId, guild);
|
||||||
|
|
@ -363,6 +393,9 @@ app.post('/api/play', async (req: Request, res: Response) => {
|
||||||
state.player.play(resource);
|
state.player.play(resource);
|
||||||
state.currentResource = resource;
|
state.currentResource = resource;
|
||||||
state.currentVolume = volumeToUse;
|
state.currentVolume = volumeToUse;
|
||||||
|
// Persistieren
|
||||||
|
persistedState.volumes[guildId] = volumeToUse;
|
||||||
|
writePersistedState(persistedState);
|
||||||
console.log(`${new Date().toISOString()} | player.play() called for ${soundName}`);
|
console.log(`${new Date().toISOString()} | player.play() called for ${soundName}`);
|
||||||
return res.json({ ok: true });
|
return res.json({ ok: true });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
@ -381,13 +414,18 @@ app.post('/api/volume', (req: Request, res: Response) => {
|
||||||
const safeVolume = Math.max(0, Math.min(1, volume));
|
const safeVolume = Math.max(0, Math.min(1, volume));
|
||||||
const state = guildAudioState.get(guildId);
|
const state = guildAudioState.get(guildId);
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return res.status(404).json({ error: 'Kein Voice-State für diese Guild' });
|
// Kein aktiver Player: nur persistieren für nächste Wiedergabe
|
||||||
|
persistedState.volumes[guildId] = safeVolume;
|
||||||
|
writePersistedState(persistedState);
|
||||||
|
return res.json({ ok: true, volume: safeVolume, persistedOnly: true });
|
||||||
}
|
}
|
||||||
state.currentVolume = safeVolume;
|
state.currentVolume = safeVolume;
|
||||||
if (state.currentResource?.volume) {
|
if (state.currentResource?.volume) {
|
||||||
state.currentResource.volume.setVolume(safeVolume);
|
state.currentResource.volume.setVolume(safeVolume);
|
||||||
console.log(`${new Date().toISOString()} | live setVolume(${safeVolume}) guild=${guildId}`);
|
console.log(`${new Date().toISOString()} | live setVolume(${safeVolume}) guild=${guildId}`);
|
||||||
}
|
}
|
||||||
|
persistedState.volumes[guildId] = safeVolume;
|
||||||
|
writePersistedState(persistedState);
|
||||||
return res.json({ ok: true, volume: safeVolume });
|
return res.json({ ok: true, volume: safeVolume });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('Volume-Fehler:', e);
|
console.error('Volume-Fehler:', e);
|
||||||
|
|
@ -395,6 +433,15 @@ app.post('/api/volume', (req: Request, res: Response) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Aktuelle/gespeicherte Lautstärke abrufen
|
||||||
|
app.get('/api/volume', (req: Request, res: Response) => {
|
||||||
|
const guildId = String(req.query.guildId ?? '');
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId erforderlich' });
|
||||||
|
const state = guildAudioState.get(guildId);
|
||||||
|
const v = state?.currentVolume ?? getPersistedVolume(guildId);
|
||||||
|
return res.json({ volume: v });
|
||||||
|
});
|
||||||
|
|
||||||
// Static Frontend ausliefern (Vite build)
|
// Static Frontend ausliefern (Vite build)
|
||||||
const webDistPath = path.resolve(__dirname, '../../web/dist');
|
const webDistPath = path.resolve(__dirname, '../../web/dist');
|
||||||
if (fs.existsSync(webDistPath)) {
|
if (fs.existsSync(webDistPath)) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { fetchChannels, fetchSounds, playSound, setVolumeLive } from './api';
|
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume } from './api';
|
||||||
import type { VoiceChannelInfo, Sound } from './types';
|
import type { VoiceChannelInfo, Sound } from './types';
|
||||||
import { getCookie, setCookie } from './cookies';
|
import { getCookie, setCookie } from './cookies';
|
||||||
|
|
||||||
|
|
@ -68,7 +68,17 @@ export default function App() {
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selected) localStorage.setItem('selectedChannel', selected);
|
(async () => {
|
||||||
|
if (selected) {
|
||||||
|
localStorage.setItem('selectedChannel', selected);
|
||||||
|
// gespeicherte Lautstärke vom Server laden
|
||||||
|
try {
|
||||||
|
const [guildId] = selected.split(':');
|
||||||
|
const v = await getVolume(guildId);
|
||||||
|
setVolume(v);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
})();
|
||||||
}, [selected]);
|
}, [selected]);
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,15 @@ export async function setVolumeLive(guildId: string, volume: number): Promise<vo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getVolume(guildId: string): Promise<number> {
|
||||||
|
const url = new URL(`${API_BASE}/volume`, window.location.origin);
|
||||||
|
url.searchParams.set('guildId', guildId);
|
||||||
|
const res = await fetch(url.toString());
|
||||||
|
if (!res.ok) throw new Error('Fehler beim Laden der Lautstärke');
|
||||||
|
const data = await res.json();
|
||||||
|
return typeof data?.volume === 'number' ? data.volume : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue