Changeset 4c9f29a in Klonkt
- Timestamp:
- 06/15/2026 03:23:14 AM (3 months ago)
- Branches:
- main
- Children:
- 3e86f1c
- Parents:
- 650b601
- git-author:
- roboburr <roboburr@…> (06/15/2026 03:22:47 AM)
- git-committer:
- roboburr <roboburr@…> (06/15/2026 03:23:14 AM)
- Location:
- src
- Files:
-
- 2 added
- 4 edited
-
assets/css/embed.css (added)
-
assets/js/audio-player.js (modified) (4 diffs)
-
assets/js/embed-player.js (added)
-
server.js (modified) (2 diffs)
-
services/AudioEmbedService.js (modified) (5 diffs)
-
views/shell.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/audio-player.js
r650b601 r4c9f29a 386 386 function close() { 387 387 pause(); 388 mediaRegistry().release(registrySelf); 388 389 root.classList.add('audio-player-hidden'); 389 390 document.body.classList.remove('has-audio-player'); … … 425 426 426 427 // ============================================================ 428 // 4b. Mutual exclusion — gedeelde media-registry (zie embed-player.js). 429 // ============================================================ 430 // Alle spelers (deze site-speler + de YouTube/SoundCloud/Spotify-embeds) 431 // registreren zich in window.pcmsMediaRegistry. Start er één, dan pauzeert de 432 // vorige. Dit is de precieze vervanger van de oude focus/blur-heuristiek voor 433 // de embeds met een echte JS-API. (De blur-fallback hieronder blijft staan 434 // voor iframe-only embeds zonder API: Bandcamp/Apple Music/Vimeo.) 435 function mediaRegistry() { 436 if (window.pcmsMediaRegistry) return window.pcmsMediaRegistry; 437 const r = { 438 _active: null, 439 setActive(player) { 440 if (this._active && this._active !== player && this._active.pause) { 441 try { this._active.pause(); } catch (e) {} 442 } 443 this._active = player; 444 }, 445 release(player) { if (this._active === player) this._active = null; }, 446 }; 447 window.pcmsMediaRegistry = r; 448 return r; 449 } 450 const registrySelf = { pause() { try { audio.pause(); } catch (e) {} } }; 451 452 // ============================================================ 427 453 // 5. Audio element events → UI sync 428 454 // ============================================================ … … 434 460 root.classList.add('is-playing'); 435 461 root.classList.remove('audio-needs-tap'); // verstop tap-hint 462 mediaRegistry().setActive(registrySelf); // pauzeer eventueel spelende embeds 436 463 }); 437 464 // Reset de error-teller pas bij ECHTE playback-start (`playing`), niet bij … … 547 574 }); 548 575 549 // Geen dubbel geluid: élke embed (YouTube/Vimeo/SoundCloud/Spotify/Apple Music/ 550 // Bandcamp) is een iframe. Zodra de gebruiker er een aanklikt om af te spelen, 551 // gaat de focus naar dat iframe -> window 'blur'. Speelt onze speler dan? 552 // Pauzeer 'm. (Op de site zijn iframes per definitie embeds.) 576 // Vangnet voor mutual exclusion. Voor YouTube/SoundCloud/Spotify-embeds doet de 577 // registry dit al precies (echte play-events). Maar voor iframe-only embeds 578 // ZONDER JS-API (Bandcamp/Apple/Vimeo) én voor de iframe-FALLBACK (als een 579 // ad-blocker de player-API blokkeert) is er geen play-event: daar vangen we het 580 // af via focus. Klikt de gebruiker zo'n iframe aan → window 'blur' → pauzeer 581 // onze speler. (Voor de API-embeds is dit hooguit een onschadelijke dubbele 582 // pauze.) 553 583 window.addEventListener('blur', () => { 554 584 setTimeout(() => { 555 585 const el = document.activeElement; 556 if (el && el.tagName === 'IFRAME' && audio.src && !audio.paused) { 586 // Alleen embed-iframes (binnen .folio-embed) pauzeren de speler — niet een 587 // willekeurig iframe (captcha/reclame/kaart) dat per ongeluk focus krijgt. 588 if (el && el.tagName === 'IFRAME' && el.closest('.folio-embed') && audio.src && !audio.paused) { 557 589 pause(); 558 590 } -
src/server.js
r650b601 r4c9f29a 65 65 directives: { 66 66 defaultSrc: ["'self'"], 67 scriptSrc: ["'self'", "'unsafe-inline'"], 67 scriptSrc: [ 68 "'self'", 69 "'unsafe-inline'", 70 // Eigen custom-embeds (embed-player.js) laden de OFFICIELE player-API's 71 // van deze hosts. Zonder deze whitelist blokkeert de CSP ze stil (alleen 72 // een console-fout) en faalt de embed-speler. 73 "https://www.youtube.com", // YouTube IFrame Player API (+ www-widgetapi.js) 74 "https://s.ytimg.com", // YouTube player-assets 75 "https://w.soundcloud.com", // SoundCloud Widget API (api.js) 76 "https://open.spotify.com", // Spotify iFrame API 77 ], 68 78 // Helmet's default zet script-src-attr op 'none', wat ALLE inline event- 69 79 // handlers (onchange/onclick/onsubmit) blokkeert — daardoor deed o.a. de … … 88 98 "https://embed.music.apple.com", 89 99 "https://www.youtube-nocookie.com", 100 "https://www.youtube.com", // YouTube IFrame API maakt soms een www.youtube.com-iframe 90 101 "https://player.vimeo.com", 91 102 ], -
src/services/AudioEmbedService.js
r650b601 r4c9f29a 16 16 url = url.trim(); 17 17 18 // Alleen http(s)-URL's embedden. De provider-regexes hieronder zijn NIET 19 // verankerd, dus zonder deze check zou bv. `javascript:alert(1)//youtu.be/x` 20 // matchen en als embed-URL belanden (stored XSS via een [[embed:...]]- 21 // shortcode — die tekst gaat niet langs de HTML-sanitizer omdat 'ie in een 22 // text-node zit). De scheme-guard sluit javascript:/data:/vbscript: enz. uit. 23 if (!/^https?:\/\//i.test(url)) return null; 24 18 25 // Spotify 19 26 if (/open\.spotify\.com\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/i.test(url)) { 20 27 const match = url.match(/\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/i); 21 return { provider: 'spotify', type: match[1], id: match[2] };28 return { provider: 'spotify', type: match[1], id: match[2], url }; 22 29 } 23 30 … … 37 44 } 38 45 39 // YouTube 40 if (/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([A-Za-z0-9_-]{6,20})/i.test(url)) { 41 const match = url.match(/(?:v=|youtu\.be\/|embed\/)([A-Za-z0-9_-]{6,20})/i); 42 return { provider: 'youtube', id: match[1] }; 46 // YouTube — video-id is altijd exact 11 tekens (lijnt uit met de client-side 47 // ytId() in embed-player.js, die ook {11} verwacht). 48 if (/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/|youtube\.com\/live\/)([A-Za-z0-9_-]{11})/i.test(url)) { 49 const match = url.match(/(?:v=|youtu\.be\/|embed\/|shorts\/|live\/)([A-Za-z0-9_-]{11})/i); 50 return { provider: 'youtube', id: match[1], url }; 43 51 } 44 52 … … 46 54 if (/vimeo\.com\/(?:video\/)?(\d+)/i.test(url)) { 47 55 const match = url.match(/\d+/); 48 return { provider: 'vimeo', id: match[0] };56 return { provider: 'vimeo', id: match[0], url }; 49 57 } 50 58 … … 54 62 static generateIframe(provider, config) { 55 63 switch (provider) { 64 // Eigen custom-spelers (client-side via embed-player.js + de echte 65 // platform-API's). We renderen een placeholder met data-attributen i.p.v. 66 // het kale platform-iframe, zodat de embed in ÓNZE huisstijl verschijnt. 67 case 'youtube': 68 return this.embedPlaceholder('youtube', config.id, 'video', 69 config.url || `https://youtu.be/${config.id}`); 70 case 'soundcloud': 71 return this.embedPlaceholder('soundcloud', config.url, 'track', config.url); 56 72 case 'spotify': 57 return this.spotifyIframe(config); 73 return this.embedPlaceholder('spotify', `spotify:${config.type}:${config.id}`, 74 config.type, config.url || `https://open.spotify.com/${config.type}/${config.id}`); 75 // Geen JS-API (Bandcamp/Apple) of niet-prioritair (Vimeo): blijven een 76 // iframe; mutual-exclusion loopt voor deze via de blur-fallback. 58 77 case 'bandcamp': 59 78 return this.bandcampIframe(config); 60 case 'soundcloud':61 return this.soundcloudIframe(config);62 79 case 'applemusic': 63 80 return this.applemusicIframe(config); 64 case 'youtube':65 return this.youtubeIframe(config);66 81 case 'vimeo': 67 82 return this.vimeoIframe(config); … … 69 84 return null; 70 85 } 86 } 87 88 /** 89 * Placeholder voor een eigen custom-speler. embed-player.js pikt 90 * .folio-embed[data-embed-provider] op en bouwt de kaart + speler client-side. 91 * ALLE waarden via escape() — post.content_html wordt ongeescaped uitgevoerd. 92 */ 93 static embedPlaceholder(provider, ref, type, url) { 94 const attrs = [ 95 `data-embed-provider="${this.escape(provider)}"`, 96 `data-embed-ref="${this.escape(ref)}"`, 97 type ? `data-embed-type="${this.escape(type)}"` : '', 98 `data-embed-url="${this.escape(url)}"`, 99 ].filter(Boolean).join(' '); 100 return `<div class="folio-embed folio-embed--${this.escape(provider)} pcms-embed pcms-embed-card pcms-embed-loading" ${attrs}></div>`; 71 101 } 72 102 -
src/views/shell.ejs
r650b601 r4c9f29a 169 169 a singleton — see the script tag near </body>. --> 170 170 <link rel="stylesheet" href="/assets/css/audio.css?v=5"> 171 <!-- Eigen custom media-embeds (YouTube/SoundCloud/Spotify) in huisstijl. --> 172 <link rel="stylesheet" href="/assets/css/embed.css?v=1"> 171 173 172 174 <%- include('partials/shared-styles') %> … … 309 311 ?v=N — cache-buster: bump bij elke audio-player.js wijziging zodat 310 312 Cloudflare (max-age=1y) niet de oude versie blijft serveren. --> 311 <script src="/assets/js/audio-player.js?v=13"></script> 313 <script src="/assets/js/audio-player.js?v=15"></script> 314 <!-- Eigen custom media-embeds (YouTube/SoundCloud/Spotify) via de echte 315 player-API's + gedeelde mutual-exclusion registry met de site-speler. --> 316 <script src="/assets/js/embed-player.js?v=3" defer></script> 312 317 <% if (site && site.enable_audio_player && audioTracks && audioTracks.length > 0) { %> 313 318 <script>window.PCMS_SITE_TRACKS = <%- JSON.stringify(audioTracks) %>;</script>
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)