fixxes for background playbacks
This commit is contained in:
parent
f580d67ccb
commit
36d1a36549
@ -15,6 +15,9 @@ from app.utils.helpers import load_default_sounds, load_soundboard_configs, load
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
audio_lock = threading.Lock()
|
audio_lock = threading.Lock()
|
||||||
|
auto_random_lock = threading.Lock()
|
||||||
|
auto_random_timer = None
|
||||||
|
auto_random_params = {}
|
||||||
|
|
||||||
# Sound-Cache: path -> pygame.mixer.Sound
|
# Sound-Cache: path -> pygame.mixer.Sound
|
||||||
loaded_sounds = {}
|
loaded_sounds = {}
|
||||||
@ -82,6 +85,55 @@ def _play_sound_entry(sound_entry, channel_req=None, loop_req=0):
|
|||||||
return None # Erfolg
|
return None # Erfolg
|
||||||
|
|
||||||
|
|
||||||
|
def _pick_random_sound():
|
||||||
|
if state.current_soundboard is None:
|
||||||
|
return None, "Kein Soundboard geladen"
|
||||||
|
rnd_list = state.current_soundboard.get('random_pool') or state.current_soundboard.get('sounds', [])
|
||||||
|
if not rnd_list:
|
||||||
|
return None, "Keine Random-Sounds definiert"
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
candidates = []
|
||||||
|
for sound in rnd_list:
|
||||||
|
sid = sound.get('id') or sound.get('file')
|
||||||
|
dq = _prune_history(sid, now)
|
||||||
|
if len(dq) < MAX_PER_HOUR:
|
||||||
|
candidates.append(sound)
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
return None, "Limit erreicht (2x pro Stunde)"
|
||||||
|
|
||||||
|
sound_entry = random.choice(candidates)
|
||||||
|
sid = sound_entry.get('id') or sound_entry.get('file')
|
||||||
|
random_history[sid].append(now)
|
||||||
|
sound_entry.setdefault('id', sid)
|
||||||
|
return sound_entry, None
|
||||||
|
|
||||||
|
|
||||||
|
def _auto_random_tick():
|
||||||
|
global auto_random_timer
|
||||||
|
with auto_random_lock:
|
||||||
|
if not state.auto_random_active:
|
||||||
|
return
|
||||||
|
imin = auto_random_params.get('imin', 5)
|
||||||
|
imax = auto_random_params.get('imax', 10)
|
||||||
|
# Play one random sound
|
||||||
|
sound_entry, err = _pick_random_sound()
|
||||||
|
if sound_entry:
|
||||||
|
_play_sound_entry(sound_entry, sound_entry.get('channel'), sound_entry.get('loop'))
|
||||||
|
# schedule next
|
||||||
|
delay = rand_between(imin, imax) * 60
|
||||||
|
with auto_random_lock:
|
||||||
|
if state.auto_random_active:
|
||||||
|
auto_random_timer = threading.Timer(delay, _auto_random_tick)
|
||||||
|
auto_random_timer.daemon = True
|
||||||
|
auto_random_timer.start()
|
||||||
|
|
||||||
|
|
||||||
|
def rand_between(a, b):
|
||||||
|
return a + random.random() * (b - a)
|
||||||
|
|
||||||
|
|
||||||
def _prune_history(sound_id, now):
|
def _prune_history(sound_id, now):
|
||||||
dq = random_history[sound_id]
|
dq = random_history[sound_id]
|
||||||
while dq and now - dq[0] > WINDOW_SECONDS:
|
while dq and now - dq[0] > WINDOW_SECONDS:
|
||||||
@ -331,6 +383,8 @@ def api_soundboard_load():
|
|||||||
current_app.config['SOUNDBOARD_CONFIG_DIR'],
|
current_app.config['SOUNDBOARD_CONFIG_DIR'],
|
||||||
current_app.config['SOUNDS_DIR'])
|
current_app.config['SOUNDS_DIR'])
|
||||||
state.current_soundboard = sb
|
state.current_soundboard = sb
|
||||||
|
# Stoppe ggf. laufende Auto-Randoms beim Laden neuer Themen
|
||||||
|
_stop_auto_random_internal()
|
||||||
logger.info(f"Soundboard geladen: {filename}")
|
logger.info(f"Soundboard geladen: {filename}")
|
||||||
return jsonify({"success": True, "soundboard": sb})
|
return jsonify({"success": True, "soundboard": sb})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -343,30 +397,69 @@ def api_soundboard_play_random():
|
|||||||
if state.current_soundboard is None:
|
if state.current_soundboard is None:
|
||||||
return jsonify({"success": False, "message": "Kein Soundboard geladen"}), 400
|
return jsonify({"success": False, "message": "Kein Soundboard geladen"}), 400
|
||||||
|
|
||||||
rnd_list = state.current_soundboard.get('random_pool') or state.current_soundboard.get('sounds', [])
|
sound_entry, err = _pick_random_sound()
|
||||||
if not rnd_list:
|
if err:
|
||||||
return jsonify({"success": False, "message": "Keine Random-Sounds definiert"}), 400
|
return jsonify({"success": False, "message": err}), 429 if "Limit" in err else 400
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
candidates = []
|
|
||||||
for sound in rnd_list:
|
|
||||||
sid = sound.get('id') or sound.get('file')
|
|
||||||
dq = _prune_history(sid, now)
|
|
||||||
if len(dq) < MAX_PER_HOUR:
|
|
||||||
candidates.append(sound)
|
|
||||||
|
|
||||||
if not candidates:
|
|
||||||
return jsonify({"success": False, "message": "Limit erreicht (2x pro Stunde)"}), 429
|
|
||||||
|
|
||||||
sound_entry = random.choice(candidates)
|
|
||||||
sid = sound_entry.get('id') or sound_entry.get('file')
|
|
||||||
|
|
||||||
res = _play_sound_entry(sound_entry)
|
res = _play_sound_entry(sound_entry)
|
||||||
if res is not None:
|
if res is not None:
|
||||||
return res # already Response
|
return res # already Response
|
||||||
|
|
||||||
random_history[sid].append(now)
|
return jsonify({"success": True, "message": f"Random: {sound_entry.get('name', sound_entry.get('id'))}"})
|
||||||
return jsonify({"success": True, "message": f"Random: {sound_entry.get('name', sid)}"})
|
|
||||||
|
|
||||||
|
@api_bp.route('/soundboard/auto_start', methods=['POST'])
|
||||||
|
def api_soundboard_auto_start():
|
||||||
|
if state.current_soundboard is None:
|
||||||
|
return jsonify({"success": False, "message": "Kein Soundboard geladen"}), 400
|
||||||
|
data = request.get_json() or {}
|
||||||
|
imin = float(data.get('interval_min', 5))
|
||||||
|
imax = float(data.get('interval_max', 10))
|
||||||
|
dmin = float(data.get('delay_min', 3))
|
||||||
|
dmax = float(data.get('delay_max', 12))
|
||||||
|
if imax < imin: imax = imin
|
||||||
|
if dmax < dmin: dmax = dmin
|
||||||
|
|
||||||
|
delay = rand_between(dmin, dmax) * 60
|
||||||
|
with auto_random_lock:
|
||||||
|
_stop_auto_random_internal()
|
||||||
|
state.auto_random_active = True
|
||||||
|
auto_random_params['imin'] = imin
|
||||||
|
auto_random_params['imax'] = imax
|
||||||
|
auto_random_params['dmin'] = dmin
|
||||||
|
auto_random_params['dmax'] = dmax
|
||||||
|
global auto_random_timer
|
||||||
|
auto_random_timer = threading.Timer(delay, _auto_random_tick)
|
||||||
|
auto_random_timer.daemon = True
|
||||||
|
auto_random_timer.start()
|
||||||
|
logger.info(f"Auto-Random gestartet (delay {delay/60:.1f} min, intervall {imin}-{imax} min)")
|
||||||
|
return jsonify({"success": True, "message": "Auto-Random gestartet",
|
||||||
|
"next_seconds": delay})
|
||||||
|
|
||||||
|
|
||||||
|
@api_bp.route('/soundboard/auto_stop', methods=['POST'])
|
||||||
|
def api_soundboard_auto_stop():
|
||||||
|
stopped = _stop_auto_random_internal()
|
||||||
|
return jsonify({"success": True, "message": "Auto-Random gestoppt", "was_running": stopped})
|
||||||
|
|
||||||
|
|
||||||
|
@api_bp.route('/soundboard/status', methods=['GET'])
|
||||||
|
def api_soundboard_status():
|
||||||
|
with auto_random_lock:
|
||||||
|
active = state.auto_random_active
|
||||||
|
return jsonify({"active": active})
|
||||||
|
|
||||||
|
|
||||||
|
def _stop_auto_random_internal():
|
||||||
|
global auto_random_timer
|
||||||
|
stopped = False
|
||||||
|
with auto_random_lock:
|
||||||
|
if auto_random_timer:
|
||||||
|
auto_random_timer.cancel()
|
||||||
|
auto_random_timer = None
|
||||||
|
stopped = True
|
||||||
|
state.auto_random_active = False
|
||||||
|
return stopped
|
||||||
|
|
||||||
|
|
||||||
@api_bp.route('/stop_sound', methods=['POST'])
|
@api_bp.route('/stop_sound', methods=['POST'])
|
||||||
|
|||||||
@ -5,6 +5,7 @@ current_hub = None
|
|||||||
current_module = None
|
current_module = None
|
||||||
current_device = None
|
current_device = None
|
||||||
current_soundboard = None # aktives Soundboard-Thema
|
current_soundboard = None # aktives Soundboard-Thema
|
||||||
|
auto_random_active = False
|
||||||
|
|
||||||
# Optional: Funktionen zum Setzen/Resetten (für Klarheit)
|
# Optional: Funktionen zum Setzen/Resetten (für Klarheit)
|
||||||
def reset_state():
|
def reset_state():
|
||||||
|
|||||||
@ -156,44 +156,36 @@
|
|||||||
const stop = document.getElementById('auto-stop');
|
const stop = document.getElementById('auto-stop');
|
||||||
const status = document.getElementById('auto-status');
|
const status = document.getElementById('auto-status');
|
||||||
|
|
||||||
start.onclick = () => {
|
start.onclick = async () => {
|
||||||
const imin = Math.max(1, parseInt(document.getElementById('auto-interval-min').value, 10) || 5);
|
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 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 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 dmax = Math.max(dmin, parseInt(document.getElementById('auto-delay-max').value, 10) || 12);
|
||||||
|
|
||||||
const delayMs = randBetween(dmin, dmax) * 60 * 1000;
|
const res = await fetch('/api/soundboard/auto_start', {
|
||||||
scheduleAuto(imin, imax, delayMs, status, start, stop);
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ interval_min: imin, interval_max: imax, delay_min: dmin, delay_max: dmax })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
status.textContent = `Aktiv (nächster in ${(data.next_seconds/60).toFixed(1)} min)`;
|
||||||
|
start.disabled = true;
|
||||||
|
stop.disabled = false;
|
||||||
|
} else {
|
||||||
|
statusBox(data.message || 'Auto-Start fehlgeschlagen', 'warn');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stop.onclick = () => stopAuto(status, start, stop);
|
stop.onclick = async () => {
|
||||||
|
const res = await fetch('/api/soundboard/auto_stop', { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
status.textContent = 'Aus';
|
||||||
|
start.disabled = false;
|
||||||
|
stop.disabled = true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
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) {
|
async function playSound(sound) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user