Index: src/middleware/site.js
===================================================================
--- src/middleware/site.js	(revision 94a876f517dabd332337e800aa93955f593504df)
+++ src/middleware/site.js	(revision 6b8afb1fd6fd9cbf8a7e8d8d9c4c7a0bb99b5512)
@@ -11,4 +11,5 @@
 import db from '../config/database.js';
 import { getTenancy } from '../services/SettingsService.js';
+import { audioUrl } from '../services/AudioStreamService.js';
 
 /**
@@ -76,7 +77,9 @@
 
   try {
-    res.locals.audioTracks = db.prepare(`
-      SELECT t.id, t.title, t.artist, t.duration, t.position,
-             m.url AS media_url
+    // m.filename = de kale bestandsnaam; de speelbare URL is de gated stream-route
+    // (audioUrl). De media-tabel heeft GEEN url-kolom — de oude query selecteerde
+    // m.url en faalde dus altijd stil (lege speler). Nu bouwen we de URL uit filename.
+    const rows = db.prepare(`
+      SELECT t.id, t.title, t.artist, t.duration, t.position, m.filename
       FROM audio_tracks t
       LEFT JOIN media m ON m.id = t.media_id
@@ -84,4 +87,8 @@
       ORDER BY t.position ASC, t.created_at ASC
     `).all(site.id);
+    res.locals.audioTracks = rows.map((r) => ({
+      id: r.id, title: r.title, artist: r.artist, duration: r.duration, position: r.position,
+      media_url: r.filename ? audioUrl(r.filename) : null,
+    }));
   } catch (e) {
     // media table might not be queryable in some test setups — fall back gracefully
