feat: Medien im Player mit ?v=<mtime> und langem Cache ausliefern

Bisher lieferte Flask Medien mit no-cache aus; der Player musste jede Datei
bei jeder Anzeige neu anfragen. Versionierte URLs duerfen jetzt ein Jahr
ohne Rueckfrage aus dem Cache kommen, unversionierte bleiben unveraendert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-09-25 12:45:16 +02:00
parent 2c72f48e28
commit 96355f6897
2 changed files with 34 additions and 6 deletions

37
app.py
View File

@@ -1208,17 +1208,41 @@ def api_add_customer():
# -------------------------------------------------
# Medien ausliefern (statische Dateien)
# -------------------------------------------------
# Traegt die URL ein ?v=<mtime>, darf der Player die Datei ein Jahr lang ohne
# Rueckfrage aus dem Cache nehmen: eine geaenderte Datei bekommt eine neue URL.
# Ohne ?v bleibt es beim Flask-Standard (no-cache + ETag), sonst saehe ein
# unversionierter Aufruf nach dem Ersetzen einer Datei weiter die alte Fassung.
MEDIA_CACHE_MAX_AGE = 365 * 24 * 3600
def media_version(path):
"""Liefert die mtime einer Datei als Versionskennung fuer ?v=, 0 wenn sie fehlt."""
try:
return int(os.path.getmtime(path))
except OSError:
return 0
def send_media(directory, filename):
"""send_from_directory mit langem Cache, sofern die URL versioniert ist."""
if not request.args.get("v"):
return send_from_directory(directory, filename)
resp = send_from_directory(directory, filename, max_age=MEDIA_CACHE_MAX_AGE)
resp.cache_control.immutable = True
return resp
@app.route("/media/<site>/priority/<path:filename>")
def media_priority(site, filename):
"""Liefert die Priority-Medien eines Standorts aus media/<site>/priority/ aus."""
return send_from_directory(priority_dir(site), filename)
return send_media(priority_dir(site), filename)
@app.route("/media/<site>/<screen>/<path:filename>")
def media(site, screen, filename):
"""Liefert Medien-Dateien für einen bestimmten Screen aus."""
path = os.path.join(MEDIA_DIR, site, screen)
return send_from_directory(path, filename)
return send_media(path, filename)
# -------------------------------------------------
@@ -1279,7 +1303,8 @@ def player(site, screen):
continue
if item_name and os.path.exists(os.path.join(folder, item_name)) and allowed_file(item_name):
normal_files.append({"kind": "file", "name": item_name, "enabled": True})
normal_files.append({"kind": "file", "name": item_name, "enabled": True,
"v": media_version(os.path.join(folder, item_name))})
existing_file_names = {
item["name"] for item in normal_files if item["kind"] == "file"
@@ -1291,7 +1316,8 @@ def player(site, screen):
if f in existing_file_names or f.startswith("._"):
continue
if allowed_file(f):
normal_files.append({"kind": "file", "name": f})
normal_files.append({"kind": "file", "name": f,
"v": media_version(os.path.join(folder, f))})
# ─── Priority-Playlist (global) ───
priority_cfg = get_priority_config(config, site)
@@ -1311,7 +1337,8 @@ def player(site, screen):
file_path = os.path.join(priority_dir(site), item_name)
if os.path.exists(file_path):
prio_files.append({"kind": "file", "name": item_name, "enabled": True})
prio_files.append({"kind": "file", "name": item_name, "enabled": True,
"v": media_version(file_path)})
else:
prio_files = []

View File

@@ -472,7 +472,8 @@ function playNext() {
}
// Datei aus dem Medienverzeichnis
const src = `/media/${site}/${basePath}/${item.name}`;
// ?v=<mtime> erlaubt dem Browser, die Datei ohne Rueckfrage aus dem Cache zu nehmen
const src = `/media/${site}/${basePath}/${item.name}` + (item.v ? `?v=${item.v}` : "");
if (isVideo(item)) {
vid.style.display = "block";
vid.src = src;