/**
* AudioEmbedService — Parse URLs and return embed HTML
* Supports: Spotify, Bandcamp, SoundCloud, Apple Music, YouTube, Vimeo
*
* Usage in post content:
*
https://open.spotify.com/track/123abc
* →
*
*
*
*/
// Dezelfde lijst soorten als de server en de editor gebruiken. Zie
// assets/js/shared/post-music-type.js: die module is puur, dus hij mag hier.
import { SOORTEN } from '../assets/js/shared/post-music-type.js';
// "Open in" icons (brand-colored via CSS .pat-link--).
const OPEN_IN_SVG = {
spotify: '',
youtube: '',
soundcloud: '',
};
class AudioEmbedService {
// Small "open in" links for a track (Spotify/YouTube/SoundCloud). The hrefs
// are already validated server-side (https + correct host only). Returns ''
// when no links exist. Placed next to the play button (outside the button →
// no conflict with playback).
static openInLinks(t) {
if (!t) return '';
const out = [];
const add = (url, key, label) => {
if (!url) return;
out.push(`${OPEN_IN_SVG[key]}`);
};
add(t.link_spotify, 'spotify', 'Spotify');
add(t.link_youtube, 'youtube', 'YouTube');
add(t.link_soundcloud, 'soundcloud', 'SoundCloud');
return out.length ? `${out.join('')}` : '';
}
static detectProvider(url) {
if (!url || typeof url !== 'string') return null;
url = url.trim();
// Only embed http(s) URLs. The provider regexes below are NOT anchored,
// so without this check e.g. `javascript:alert(1)//youtu.be/x` would match
// and land as an embed URL (stored XSS via an [[embed:...]] shortcode —
// that text never passes through the HTML sanitizer because it lives in a
// text node). The scheme guard excludes javascript:/data:/vbscript: etc.
if (!/^https?:\/\//i.test(url)) return null;
// Spotify
if (/open\.spotify\.com\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/i.test(url)) {
const match = url.match(/\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/i);
return { provider: 'spotify', type: match[1], id: match[2], url };
}
// Bandcamp
if (/bandcamp\.com\/(track|album)/i.test(url)) {
return { provider: 'bandcamp', url };
}
// SoundCloud
if (/soundcloud\.com/i.test(url)) {
return { provider: 'soundcloud', url };
}
// Apple Music
if (/music\.apple\.com\/([a-z]{2})\/(?:album|playlist|song)\//i.test(url)) {
return { provider: 'applemusic', url };
}
// YouTube — a video id is always exactly 11 characters (aligns with the
// client-side ytId() in embed-player.js, which also expects {11}).
//
// A link may carry a video, a playlist, or both, and until now we kept only
// the video and threw `list=` away -- so a link to an album played its first
// song and stopped. The ref now keeps whichever is there, in the same three
// shapes the Klonkt hub uses, so one ref travels between the two unchanged:
//
// "