Update app

This commit is contained in:
2026-01-25 12:03:08 +01:00
parent 4df004c18a
commit a5fe0f73a0
10 changed files with 611 additions and 54 deletions

View File

@@ -6,12 +6,12 @@ import time
from flask import Blueprint, Response, abort, jsonify, request, stream_with_context, url_for
from ..extensions import db
from ..models import Display, DisplaySession
from ..models import Display, DisplayPlaylist, DisplaySession, Playlist, PlaylistItem
bp = Blueprint("api", __name__, url_prefix="/api")
MAX_ACTIVE_SESSIONS_PER_DISPLAY = 2
MAX_ACTIVE_SESSIONS_PER_DISPLAY = 3
SESSION_TTL_SECONDS = 90
@@ -84,16 +84,49 @@ def _playlist_signature(display: Display) -> tuple[int | None, str]:
duration changes, and item adds/deletes trigger an update.
"""
playlist = display.assigned_playlist
if not playlist:
# Determine active playlists. If display_playlist has any rows, use those.
# Otherwise fall back to the legacy assigned_playlist_id.
mapped_ids = [
r[0]
for r in db.session.query(DisplayPlaylist.playlist_id)
.filter(DisplayPlaylist.display_id == display.id)
.order_by(DisplayPlaylist.position.asc(), DisplayPlaylist.playlist_id.asc())
.all()
]
use_mapping = bool(mapped_ids)
active_ids = mapped_ids
if not active_ids and display.assigned_playlist_id:
active_ids = [display.assigned_playlist_id]
use_mapping = False
if not active_ids:
raw = "no-playlist"
return None, hashlib.sha1(raw.encode("utf-8")).hexdigest()
# Pull items in a stable order so reordering affects signature.
if use_mapping:
items = (
PlaylistItem.query.join(DisplayPlaylist, DisplayPlaylist.playlist_id == PlaylistItem.playlist_id)
.filter(
DisplayPlaylist.display_id == display.id,
PlaylistItem.playlist_id.in_(active_ids),
)
.order_by(DisplayPlaylist.position.asc(), PlaylistItem.position.asc())
.all()
)
else:
items = (
PlaylistItem.query.filter(PlaylistItem.playlist_id == active_ids[0])
.order_by(PlaylistItem.position.asc())
.all()
)
payload = {
"playlist_id": playlist.id,
"playlist_ids": list(active_ids),
"items": [
{
"id": it.id,
"playlist_id": it.playlist_id,
"pos": it.position,
"type": it.item_type,
"title": it.title,
@@ -101,11 +134,15 @@ def _playlist_signature(display: Display) -> tuple[int | None, str]:
"file_path": it.file_path,
"url": it.url,
}
for it in playlist.items
for it in items
],
}
raw = json.dumps(payload, sort_keys=True, separators=(",", ":"))
return playlist.id, hashlib.sha1(raw.encode("utf-8")).hexdigest()
# signature returns a single playlist_id previously; now return None when multiple.
# callers only use it for changed-detection.
if len(set(active_ids)) == 1:
return active_ids[0], hashlib.sha1(raw.encode("utf-8")).hexdigest()
return None, hashlib.sha1(raw.encode("utf-8")).hexdigest()
@bp.get("/display/<token>/playlist")
@@ -114,21 +151,59 @@ def display_playlist(token: str):
if not display:
abort(404)
# Enforce: a display URL/token can be opened by max 2 concurrently active sessions.
# Enforce: a display URL/token can be opened by max 3 concurrently active sessions.
# Player sends a stable `sid` via querystring.
sid = request.args.get("sid")
ok, resp = _enforce_and_touch_display_session(display, sid)
if not ok:
return resp
playlist = display.assigned_playlist
if not playlist:
return jsonify({"display": display.name, "playlist": None, "items": []})
# Determine active playlists. If display_playlist has any rows, use those.
# Otherwise fall back to the legacy assigned_playlist_id.
mapped_ids = [
r[0]
for r in db.session.query(DisplayPlaylist.playlist_id)
.filter(DisplayPlaylist.display_id == display.id)
.order_by(DisplayPlaylist.position.asc(), DisplayPlaylist.playlist_id.asc())
.all()
]
use_mapping = bool(mapped_ids)
active_ids = mapped_ids
if not active_ids and display.assigned_playlist_id:
active_ids = [display.assigned_playlist_id]
use_mapping = False
if not active_ids:
return jsonify({"display": display.name, "playlists": [], "items": []})
playlists = Playlist.query.filter(Playlist.id.in_(active_ids)).all()
pl_by_id = {p.id: p for p in playlists}
ordered_playlists = [pl_by_id[x] for x in active_ids if x in pl_by_id]
# Merge items across active playlists.
if use_mapping:
merged = (
PlaylistItem.query.join(DisplayPlaylist, DisplayPlaylist.playlist_id == PlaylistItem.playlist_id)
.filter(
DisplayPlaylist.display_id == display.id,
PlaylistItem.playlist_id.in_(active_ids),
)
.order_by(DisplayPlaylist.position.asc(), PlaylistItem.position.asc())
.all()
)
else:
merged = (
PlaylistItem.query.filter(PlaylistItem.playlist_id == active_ids[0])
.order_by(PlaylistItem.position.asc())
.all()
)
items = []
for item in playlist.items:
for item in merged:
payload = {
"id": item.id,
"playlist_id": item.playlist_id,
"playlist_name": (pl_by_id.get(item.playlist_id).name if pl_by_id.get(item.playlist_id) else None),
"type": item.item_type,
"title": item.title,
"duration": item.duration_seconds,
@@ -142,7 +217,7 @@ def display_playlist(token: str):
return jsonify(
{
"display": display.name,
"playlist": {"id": playlist.id, "name": playlist.name},
"playlists": [{"id": p.id, "name": p.name} for p in ordered_playlists],
"items": items,
}
)