Changeset fef781e in Klonkt for src/assets/js/embed-player.js
- Timestamp:
- 06/20/2026 04:46:35 AM (3 months ago)
- Branches:
- main
- Children:
- 0d7acdf
- Parents:
- 8ab20b6
- File:
-
- 1 edited
-
src/assets/js/embed-player.js (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/embed-player.js
r8ab20b6 rfef781e 162 162 if (!provider) return; 163 163 164 // Audio-only YouTube met een afspeellijst (?list=…) → album-kaart met tracklijst.165 if (provider === 'youtube' && el.dataset.embedMode === 'audio') {166 const lm = url.match(/[?&]list=([A-Za-z0-9_-]+)/);167 if (lm) { try { buildYtAlbum(el, lm[1]); } catch (e) { console.error('[pcms-embed] yt-album faalde', e); } return; }168 }169 170 164 el.classList.add('pcms-embed-card', 'pcms-embed-card--' + provider); 171 165 el.classList.remove('pcms-embed-loading'); … … 189 183 190 184 // --- UI ophangen (verschilt per provider-type) --- 191 // Audio-only YouTube (data-embed-mode="audio"): render als audio-kaart met 192 // onze controls; de YT-speler draait verborgen (off-screen) en levert alleen 193 // het geluid. Video blijft de default. 194 const audioOnly = provider === 'youtube' && el.dataset.embedMode === 'audio'; 195 if (audioOnly) el.classList.add('pcms-embed-audio-mode'); 196 const isVideo = provider === 'youtube' && !audioOnly; 197 // Audio-only YT speelt via de site-onderbalk → geen eigen in-kaart controlebar. 198 const custom = (provider === 'youtube' || provider === 'soundcloud') && !audioOnly; 185 const isVideo = provider === 'youtube'; 186 const custom = provider === 'youtube' || provider === 'soundcloud'; // eigen controls 199 187 const poster = provider === 'youtube' 200 188 ? `https://i.ytimg.com/vi/${ytId(ref, url)}/hqdefault.jpg` : ''; 201 // 16:9-thumbnail ZONDER zwarte balken (mqdefault) voor de vierkante cover —202 // hqdefault is 4:3 met letterbox en vult een vierkant niet schoon.203 const ytThumb = provider === 'youtube'204 ? `https://i.ytimg.com/vi/${ytId(ref, url)}/mqdefault.jpg` : '';205 189 206 190 el.innerHTML = '' … … 238 222 let vol = 100; // huidige volume 0-100 (wordt op de adapter toegepast) 239 223 let preMuteVol = 100; 240 // Audio-only YouTube: zet de thumbnail als albumhoes op de audio-kaart.241 let ytTitle = ''; // echte videotitel (via same-origin oEmbed-proxy)242 if (audioOnly && ytThumb) {243 const art0 = el.querySelector('.pcms-embed-art');244 if (art0) { art0.style.backgroundImage = `url('${ytThumb}')`; art0.classList.add('has-art'); }245 // Speler vast aanmaken → eerste klik valt binnen de user-gesture (autoplay).246 if (window.pcmsAudioPlayer && window.pcmsAudioPlayer.prewarmYouTube) window.pcmsAudioPlayer.prewarmYouTube();247 // Haal de echte titel op (server-proxy, want YT-oEmbed heeft geen CORS) en248 // toon 'm i.p.v. "YouTube".249 fetch('/audio/yt-title?url=' + encodeURIComponent(url))250 .then((r) => (r.ok ? r.json() : null))251 .then((d) => {252 if (d && d.title) {253 ytTitle = d.title;254 const tt = el.querySelector('.pcms-embed-title');255 if (tt) tt.textContent = d.title;256 }257 })258 .catch(() => {});259 }260 224 261 225 function setPlayingUI(on) { … … 332 296 // Eerste interactie → adapter mounten + spelen. Daarna toggelt de knop. 333 297 async function ensureMountedAndPlay() { 334 // Audio-only YouTube → speel via de site-audiospeler (onderbalk): zichtbaar335 // én doorspelen bij navigeren. De inline-kaart is enkel de launcher.336 if (audioOnly && window.pcmsAudioPlayer && typeof window.pcmsAudioPlayer.playYouTube === 'function') {337 window.pcmsAudioPlayer.playYouTube({ id: ytId(ref, url), title: ytTitle || undefined, cover: ytThumb, postUrl: location.pathname + location.search });338 el.classList.add('pcms-embed-elsewhere');339 return;340 }341 298 if (mounted) { if (adapter) adapter.play(); return; } 342 299 mounted = true; … … 391 348 } 392 349 393 // Haal de video-IDs van een YouTube-afspeellijst op via een verborgen,394 // tijdelijke speler (cuePlaylist → getPlaylist). Geen API-key nodig.395 function ytPlaylistIds(listId) {396 return ytApi().then((YT) => new Promise((resolve) => {397 if (!YT || !YT.Player) { resolve([]); return; }398 const host = document.createElement('div');399 host.style.cssText = 'position:absolute;left:-99999px;top:0;width:320px;height:180px;opacity:0;pointer-events:none';400 const mount = document.createElement('div'); host.appendChild(mount); document.body.appendChild(host);401 let done = false;402 const finish = (ids) => { if (done) return; done = true; try { p.destroy(); } catch (e) {} try { host.remove(); } catch (e) {} resolve(ids || []); };403 const p = new YT.Player(mount, {404 host: 'https://www.youtube-nocookie.com', playerVars: { playsinline: 1 },405 events: { onReady: () => {406 try { p.cuePlaylist({ listType: 'playlist', list: listId }); } catch (e) {}407 setTimeout(() => { let ids = []; try { ids = p.getPlaylist() || []; } catch (e) {} finish(ids); }, 2500);408 } },409 });410 setTimeout(() => finish([]), 9000);411 }));412 }413 414 // Render een audio-only YouTube-AFSPEELLIJST als album-kaart (zelfde stijl als415 // de eigen albums) met tracklijst; afspelen loopt via de site-audiospeler.416 async function buildYtAlbum(el, listId) {417 el.className = 'folio-embed pcms-ytalbum'; // geen losse card-chrome; .post-album stuurt de stijl418 const thumb = (id) => `https://i.ytimg.com/vi/${id}/mqdefault.jpg`;419 el.innerHTML = ''420 + '<div class="post-album">'421 + '<div class="post-album-header">'422 + '<button type="button" class="post-album-cover-btn" aria-label="Album afspelen">'423 + '<img class="post-album-cover-img" src="" alt="">'424 + '<span class="post-album-play-overlay" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 4l12 8-12 8z"/></svg></span>'425 + '</button>'426 + '<div class="post-album-info">'427 + '<h3 class="post-album-title">Album laden…</h3>'428 + '<p class="post-album-artist"></p>'429 + '<p class="post-album-count">YouTube</p>'430 + '</div>'431 + '</div>'432 + '<ol class="post-album-tracks"><li class="pcms-ytalbum-msg">Tracks laden…</li></ol>'433 + '</div>';434 const coverImg = el.querySelector('.post-album-cover-img');435 const titleEl = el.querySelector('.post-album-title');436 const countEl = el.querySelector('.post-album-count');437 const olEl = el.querySelector('.post-album-tracks');438 const coverBtn = el.querySelector('.post-album-cover-btn');439 440 // Albumnaam via de oEmbed-proxy (playlist-url).441 fetch('/audio/yt-title?url=' + encodeURIComponent('https://www.youtube.com/playlist?list=' + listId))442 .then((r) => (r.ok ? r.json() : null)).then((d) => { if (d && d.title) titleEl.textContent = d.title; }).catch(() => {});443 444 let ids = [];445 try { ids = await ytPlaylistIds(listId); } catch (e) { ids = []; }446 if (!ids.length) { olEl.innerHTML = '<li class="pcms-ytalbum-msg">Kon de afspeellijst niet laden.</li>'; return; }447 countEl.textContent = ids.length + ' track' + (ids.length === 1 ? '' : 's');448 if (coverImg) coverImg.src = thumb(ids[0]);449 // Speler vast aanmaken → eerste klik valt binnen de user-gesture (autoplay).450 if (window.pcmsAudioPlayer && window.pcmsAudioPlayer.prewarmYouTube) window.pcmsAudioPlayer.prewarmYouTube();451 452 olEl.innerHTML = ids.map((id, i) =>453 `<li class="post-album-track-compact" id="track-${id}" data-yt-id="${id}" data-yt-index="${i}">`454 + `<button type="button" class="pat-row" aria-label="Speel track ${i + 1}">`455 + `<span class="pat-cover" style="background-image:url('${thumb(id)}')" aria-hidden="true"></span>`456 + `<span class="pat-meta"><span class="pat-title">Track ${i + 1}</span><span class="pat-artist"></span></span>`457 + `<span class="pat-duration pat-duration-empty">—:—</span>`458 + `</button></li>`459 ).join('');460 461 // Tracktitels lazy invullen (oEmbed-proxy per video).462 ids.forEach((id, i) => {463 fetch('/audio/yt-title?url=' + encodeURIComponent('https://www.youtube.com/watch?v=' + id))464 .then((r) => (r.ok ? r.json() : null))465 .then((d) => { if (d && d.title) { const tt = olEl.querySelector(`[data-yt-index="${i}"] .pat-title`); if (tt) tt.textContent = d.title; } })466 .catch(() => {});467 });468 469 function playAt(i) {470 if (!(window.pcmsAudioPlayer && window.pcmsAudioPlayer.playYouTubeList)) return;471 // Expandeer naar losse YT-tracks (betrouwbaarder dan loadPlaylist).472 const items = ids.map((id, j) => ({473 yt: id,474 title: (olEl.querySelector(`[data-yt-index="${j}"] .pat-title`) || {}).textContent || ('Track ' + (j + 1)),475 cover: thumb(id),476 postUrl: location.pathname + location.search,477 }));478 window.pcmsAudioPlayer.playYouTubeList(items, i);479 }480 coverBtn.addEventListener('click', () => playAt(0));481 olEl.addEventListener('click', (e) => { const li = e.target.closest('[data-yt-index]'); if (li) playAt(parseInt(li.dataset.ytIndex, 10) || 0); });482 }483 484 350 // ============================================================ 485 351 // 4. Adapters — normaliseren de 3 zeer verschillende API's naar 1 vorm. … … 503 369 events: { 504 370 onReady() { 505 let d = 0; const meta = {}; 506 try { d = player.getDuration() || 0; } catch (e) {} 507 try { const vd = player.getVideoData(); if (vd && vd.title) meta.title = vd.title; } catch (e) {} 508 meta.duration = d; 509 hooks.onReady(meta); 371 let d = 0; try { d = player.getDuration() || 0; } catch (e) {} 372 hooks.onReady({ duration: d }); 510 373 resolve({ 511 374 play() { try { player.playVideo(); } catch (e) {} },
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)