Index: src/routes/admin-audio.js
===================================================================
--- src/routes/admin-audio.js	(revision 328d83725a5461bae2e5639ede37cbe4cf206b67)
+++ src/routes/admin-audio.js	(revision f2eacca5e0a92d8ef8516530c0a23ecd2b0a78ed)
@@ -96,5 +96,5 @@
   const rows = db.prepare(`
     SELECT t.id, t.title, t.artist, t.album, t.duration, t.cover_url,
-           t.position, t.created_at, t.downloadable, m.filename, m.size, m.mime_type
+           t.position, t.created_at, t.downloadable, t.fedi_open, m.filename, m.size, m.mime_type
     FROM audio_tracks t
     LEFT JOIN media m ON m.id = t.media_id
@@ -288,4 +288,18 @@
 });
 
+// Federate-the-file (fedi_open) per track on/off. When on, this track's audio file is shared
+// as a real AS2 Audio attachment + served ungated → it plays inline in EVERY fediverse client
+// (incl. the Mastodon apps), but the file is downloadable. Off (default) = gated, web-player only.
+router.post('/:id/fedi-open', requireGod, (req, res) => {
+  const site = res.locals.site;
+  if (!site) return res.status(404).send('Site required');
+  const row = db.prepare('SELECT fedi_open FROM audio_tracks WHERE id = ? AND site_id = ?').get(req.params.id, site.id);
+  if (row) {
+    db.prepare('UPDATE audio_tracks SET fedi_open = ? WHERE id = ? AND site_id = ?')
+      .run(row.fedi_open ? 0 : 1, req.params.id, site.id);
+  }
+  res.redirect('/admin/audio');
+});
+
 router.post('/:id/delete', requireGod, (req, res) => {
   const site = res.locals.site;
Index: src/routes/audio.js
===================================================================
--- src/routes/audio.js	(revision 328d83725a5461bae2e5639ede37cbe4cf206b67)
+++ src/routes/audio.js	(revision f2eacca5e0a92d8ef8516530c0a23ecd2b0a78ed)
@@ -54,9 +54,19 @@
 };
 
-// Access gate: allow only same-origin browser fetches / media loads.
-function isAllowedAudioRequest(req) {
+// Access gate: same-origin browser fetches / media loads — PLUS fediverse-shared tracks.
+function isAllowedAudioRequest(req, filename) {
   if (req.get('X-Audio-Player') === '1') return true;  // our blob fetch
   const site = req.get('Sec-Fetch-Site');              // set by modern browsers
-  return site === 'same-origin' || site === 'same-site';
+  if (site === 'same-origin' || site === 'same-site') return true;
+  // fedi_open tracks are deliberately served ungated so remote servers (Mastodon, …) can
+  // fetch + play the file inline. The operator opted this specific track in (per-track flag).
+  if (filename) {
+    try {
+      const r = db.prepare(`SELECT 1 FROM audio_tracks t JOIN media m ON t.media_id = m.id
+        WHERE t.fedi_open = 1 AND (m.storage_path = ? OR m.storage_path LIKE ?) LIMIT 1`).get(filename, '%' + filename);
+      if (r) return true;
+    } catch { /* ignore */ }
+  }
+  return false;
 }
 
@@ -64,5 +74,5 @@
   const { filename } = req.params;
 
-  if (!isAllowedAudioRequest(req)) {
+  if (!isAllowedAudioRequest(req, filename)) {
     return res.status(403).send('Direct access not allowed');
   }
