Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision ca25f360f73b889318b921f39ff70bb54eb6d853)
+++ src/services/ActivityPubService.js	(revision 2923a951b1ae82d7e0ece377c760c02bf001fc91)
@@ -116,8 +116,12 @@
     return ({ jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', webp: 'image/webp', avif: 'image/avif' })[(e || '').toLowerCase()] || 'image/jpeg';
   };
+  const hadAudio = /\[\[(track|album|playlist):/i.test(post.content || '');
   const urls = [];
-  if (post.cover_image_url) urls.push(abs(post.cover_image_url));
+  // Audio posts suppress image attachments so Mastodon renders the player CARD
+  // (twitter:player) instead of the cover — on Mastodon a media attachment and a
+  // link/player card are mutually exclusive, and the inline player is the point.
+  if (post.cover_image_url && !hadAudio) urls.push(abs(post.cover_image_url));
   let body = post.content || '';
-  for (const m of body.matchAll(/<img\b[^>]*\bsrc="([^"]+)"[^>]*>/gi)) urls.push(abs(m[1]));
+  if (!hadAudio) for (const m of body.matchAll(/<img\b[^>]*\bsrc="([^"]+)"[^>]*>/gi)) urls.push(abs(m[1]));
   body = body.replace(/<img\b[^>]*>/gi, '');
   // Audio shortcodes: do NOT federate the raw audio file — Klonkt deliberately
@@ -132,5 +136,4 @@
     for (const m of body.matchAll(/\[\[album:([^\]]+)\]\]/g)) audioLabels.push(m[1].trim());
   } catch { /* non-fatal */ }
-  const hadAudio = /\[\[(track|album|playlist):/i.test(body);
   body = body.replace(/\[\[(track|album|playlist):[^\]]+\]\]/gi, '');
   if (hadAudio) {
