mkcontrol-app/templates/soundboard.html

254 lines
9.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Soundboard Themen{% endblock %}
{% block content %}
<div class="container my-5">
<h1>Soundboard</h1>
<p class="lead mb-4">Themenbezogene Soundsets, unabhängig vom Hub.</p>
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label fw-bold">Thema wählen</label>
<select id="soundboard-select" class="form-select">
{% for sb in soundboard_configs %}
<option value="{{ sb.filename }}">{{ sb.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6 d-flex align-items-end">
<div class="btn-group">
<button id="btn-load-sb" class="btn btn-primary">Laden</button>
<button id="btn-play-random" class="btn btn-outline-secondary" disabled>1x Zufällig</button>
</div>
</div>
</div>
<div id="sb-content" style="display:none">
<div class="mb-4">
<label class="form-label fw-bold">Lautstärke</label>
<input type="range" class="form-range" id="sb-volume" min="0" max="100" value="80" step="5">
<small class="text-muted">Wirkt global auf alle Soundboard-Kanäle.</small>
</div>
<div id="sb-backgrounds" class="mb-4"></div>
<div id="sb-sounds" class="mb-4"></div>
<div id="sb-random" class="mb-4"></div>
<div id="sb-auto" class="mb-4"></div>
</div>
<div class="mt-4">
<a href="{{ url_for('main.index') }}" class="btn btn-secondary">Zurück</a>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const selectEl = document.getElementById('soundboard-select');
const loadBtn = document.getElementById('btn-load-sb');
const randBtn = document.getElementById('btn-play-random');
const content = document.getElementById('sb-content');
let currentSB = null;
let autoTimer = null;
const statusBox = (msg, type='info') => {
console.log(msg);
};
async function loadSoundboard(filename) {
loadBtn.disabled = true;
try {
const res = await fetch('/api/soundboard/load', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename })
});
const data = await res.json();
if (!data.success) throw new Error(data.message || 'Load failed');
currentSB = data.soundboard;
renderSoundboard(currentSB);
content.style.display = 'block';
randBtn.disabled = false;
} catch (e) {
alert('Konnte Soundboard nicht laden: ' + e.message);
} finally {
loadBtn.disabled = false;
}
}
function renderSoundboard(sb) {
renderBackgrounds(sb.backgrounds || []);
renderSounds(sb.sounds || []);
renderRandom(sb.random_pool || []);
}
function renderBackgrounds(list) {
const container = document.getElementById('sb-backgrounds');
if (!list.length) { container.innerHTML=''; return; }
container.innerHTML = '<h4>Hintergrund (Loop)</h4><div class="d-flex flex-wrap gap-2"></div>';
const wrap = container.querySelector('div');
list.forEach(s => {
const btn = document.createElement('button');
btn.className = 'btn btn-outline-secondary';
btn.textContent = s.name || s.id || s.file;
btn.onclick = () => playSound(s);
const stop = document.createElement('button');
stop.className = 'btn btn-outline-danger';
stop.textContent = 'Stop';
stop.onclick = () => stopSound(s.channel);
wrap.append(btn, stop);
});
}
function renderSounds(list) {
const container = document.getElementById('sb-sounds');
if (!list.length) { container.innerHTML=''; return; }
container.innerHTML = '<h4>Einzelsounds</h4><div class="d-flex flex-wrap gap-2"></div>';
const wrap = container.querySelector('div');
list.forEach(s => {
const btn = document.createElement('button');
btn.className = 'btn btn-primary';
btn.textContent = s.name || s.id || s.file;
btn.onclick = () => playSound(s);
wrap.append(btn);
});
}
function renderRandom(list) {
const container = document.getElementById('sb-random');
if (!list.length) { container.innerHTML=''; randBtn.disabled=true; return; }
const items = list.map(s => `<li>${s.name || s.id || s.file}</li>`).join('');
container.innerHTML = '<h4>Zufallspool</h4><p class="text-muted">max 2x pro Stunde je Datei.</p><ul>' + items + '</ul>';
renderAutoRandomControls();
}
function renderAutoRandomControls() {
const container = document.getElementById('sb-auto');
container.innerHTML = `
<h4>Automatik (Random)</h4>
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label">Intervall min (Minuten)</label>
<input id="auto-interval-min" type="number" class="form-control" value="5" min="1" max="60">
</div>
<div class="col-md-3">
<label class="form-label">Intervall max (Minuten)</label>
<input id="auto-interval-max" type="number" class="form-control" value="10" min="1" max="120">
</div>
<div class="col-md-3">
<label class="form-label">Startverzögerung min (Minuten)</label>
<input id="auto-delay-min" type="number" class="form-control" value="3" min="0" max="60">
</div>
<div class="col-md-3">
<label class="form-label">Startverzögerung max (Minuten)</label>
<input id="auto-delay-max" type="number" class="form-control" value="12" min="0" max="120">
</div>
</div>
<div class="mt-3 d-flex gap-2 align-items-center">
<button id="auto-start" class="btn btn-success" ${!randBtn.disabled ? '' : 'disabled'}>Auto-Start</button>
<button id="auto-stop" class="btn btn-outline-danger" disabled>Stop</button>
<span id="auto-status" class="text-muted ms-2">Aus</span>
</div>
`;
const start = document.getElementById('auto-start');
const stop = document.getElementById('auto-stop');
const status = document.getElementById('auto-status');
start.onclick = () => {
const imin = Math.max(1, parseInt(document.getElementById('auto-interval-min').value, 10) || 5);
const imax = Math.max(imin, parseInt(document.getElementById('auto-interval-max').value, 10) || 10);
const dmin = Math.max(0, parseInt(document.getElementById('auto-delay-min').value, 10) || 3);
const dmax = Math.max(dmin, parseInt(document.getElementById('auto-delay-max').value, 10) || 12);
const delayMs = randBetween(dmin, dmax) * 60 * 1000;
scheduleAuto(imin, imax, delayMs, status, start, stop);
};
stop.onclick = () => stopAuto(status, start, stop);
}
function randBetween(min, max) {
return min + Math.random() * (max - min);
}
function scheduleAuto(imin, imax, delayMs, status, startBtn, stopBtn) {
stopAuto(status, startBtn, stopBtn);
status.textContent = `Geplant in ${(delayMs/60000).toFixed(1)} min`;
startBtn.disabled = true;
stopBtn.disabled = false;
autoTimer = setTimeout(async function tick() {
await playRandom();
const nextMs = randBetween(imin, imax) * 60 * 1000;
status.textContent = `Nächster in ${(nextMs/60000).toFixed(1)} min`;
autoTimer = setTimeout(tick, nextMs);
}, delayMs);
}
function stopAuto(status, startBtn, stopBtn) {
if (autoTimer) {
clearTimeout(autoTimer);
autoTimer = null;
}
if (status) status.textContent = 'Aus';
if (startBtn) startBtn.disabled = false;
if (stopBtn) stopBtn.disabled = true;
}
async function playSound(sound) {
const payload = {
sound_id: sound.id || sound.file,
channel: sound.channel ?? null,
loop: !!sound.loop
};
const res = await fetch('/api/play_sound', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (!data.success) statusBox(data.message || 'Fehler beim Abspielen', 'warn');
}
async function stopSound(channel) {
const res = await fetch('/api/stop_sound', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel })
});
const data = await res.json();
if (!data.success) statusBox(data.message || 'Fehler beim Stoppen', 'warn');
}
loadBtn.onclick = () => loadSoundboard(selectEl.value);
randBtn.onclick = async () => {
await playRandom();
};
async function playRandom() {
const res = await fetch('/api/soundboard/play_random', { method: 'POST' });
const data = await res.json();
if (!data.success) statusBox(data.message || 'Random fehlgeschlagen', 'warn');
}
// Lautstärke
const vol = document.getElementById('sb-volume');
if (vol) {
vol.addEventListener('input', async () => {
const v = parseInt(vol.value, 10) / 100;
await fetch('/api/set_volume', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ volume: v })
});
});
}
// Autoload erstes Thema (falls vorhanden)
if (selectEl && selectEl.value) {
loadSoundboard(selectEl.value);
}
</script>
{% endblock %}