Changeset fef781e in Klonkt for src/assets/js/audio-player.js


Ignore:
Timestamp:
06/20/2026 04:46:35 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
0d7acdf
Parents:
8ab20b6
Message:

revert: remove YouTube audio functionality (doesn't work)

YouTube blocks audio-only/hidden embed playback with a "Sign in to confirm
you're not a bot" gate → not reliably usable. All YT-audio reverted:

  • embed-player.js + embed.css → state before the feature (audio-only card, YT-album, buildYtAlbum, prewarm, mode-routing removed). Video embeds + the YT/SC volume button remain.
  • audio-player.js → YT backend (ytMode/ensureYT/playYouTube/playYouTubeList/…) removed; only the blob-<audio> player + existing features.
  • AudioEmbedService.js → audio:/video: mode parsing + data-embed-mode removed.
  • audio.js → /yt-title oEmbed proxy removed.
  • post-edit.ejs → audio/video choice on the embed button removed ([[embed:url]]

normal).

busters audio-player.js?v=28, embed-player.js?v=16, embed.css?v=8.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/audio-player.js

    r8ab20b6 rfef781e  
    289289  });
    290290
    291   // ============================================================
    292   // 4a. YouTube-backend — speelt YT-AUDIO via een verborgen IFrame-speler in de
    293   // (persistente) speler-root. Zo speelt YouTube net als de site-tracks door bij
    294   // htmx-navigatie en is 't bedienbaar in de onderbalk. Video wordt niet getoond.
    295   // ============================================================
    296   let ytMode = false, ytPlayer = null, ytReady = false, ytApiLoading = null, ytTick = null;
    297 
    298   function loadYTApi() {
    299     if (window.YT && window.YT.Player) return Promise.resolve(window.YT);
    300     if (ytApiLoading) return ytApiLoading;
    301     ytApiLoading = new Promise((resolve) => {
    302       const prev = window.onYouTubeIframeAPIReady;
    303       window.onYouTubeIframeAPIReady = function () { if (prev) { try { prev(); } catch (e) {} } resolve(window.YT); };
    304       if (!document.querySelector('script[src*="youtube.com/iframe_api"]')) {
    305         const s = document.createElement('script'); s.src = 'https://www.youtube.com/iframe_api'; s.async = true; document.head.appendChild(s);
    306       }
    307       setTimeout(() => { if (window.YT && window.YT.Player) resolve(window.YT); }, 8000);
    308     });
    309     return ytApiLoading;
    310   }
    311 
    312   function ensureYT() {
    313     if (ytPlayer) return Promise.resolve(ytPlayer);
    314     return loadYTApi().then((YT) => new Promise((resolve) => {
    315       if (!YT || !YT.Player) { resolve(null); return; }
    316       let host = document.getElementById('pcms-yt-host');
    317       if (!host) {
    318         host = document.createElement('div'); host.id = 'pcms-yt-host';
    319         const m = document.createElement('div'); m.id = 'pcms-yt-mount'; host.appendChild(m);
    320         root.appendChild(host);
    321       }
    322       ytPlayer = new YT.Player('pcms-yt-mount', {
    323         host: 'https://www.youtube-nocookie.com',
    324         playerVars: { controls: 0, playsinline: 1, rel: 0, modestbranding: 1, iv_load_policy: 3, origin: window.location.origin },
    325         events: {
    326           onReady: () => { ytReady = true; try { ytPlayer.setVolume(audio.muted ? 0 : Math.round(audio.volume * 100)); } catch (e) {} resolve(ytPlayer); },
    327           onStateChange: onYTState,
    328         },
    329       });
    330       setTimeout(() => resolve(ytPlayer), 8000);
    331     }));
    332   }
    333 
    334   function onYTState(e) {
    335     if (!ytMode) return;
    336     const s = e.data;  // 1 playing, 2 paused, 0 ended
    337     if (s === 1) {
    338       isPlaying = true; root.classList.add('is-playing'); root.classList.remove('audio-needs-tap');
    339       // We startten gedempt (autoplay-policy) → nu 't speelt het geluid terugzetten,
    340       // tenzij de gebruiker zelf gedempt heeft.
    341       try { if (!audio.muted) { ytPlayer.unMute(); ytPlayer.setVolume(Math.round(audio.volume * 100)); } } catch (e) {}
    342       mediaRegistry().setActive(registrySelf); startYTTick(); pullYTMeta();
    343     } else if (s === 2) {
    344       isPlaying = false; root.classList.remove('is-playing'); stopYTTick();
    345     } else if (s === 0) {
    346       stopYTTick();
    347       // Bij een afspeellijst regelt YouTube zelf het doorschakelen → niet dubbel.
    348       const t = queue[currentIndex];
    349       if (!(t && t.ytList)) next();
    350     }
    351   }
    352 
    353   function startYTTick() {
    354     if (ytTick) return;
    355     ytTick = setInterval(() => {
    356       if (!ytPlayer || !ytMode) return;
    357       try { updateProgressUI(ytPlayer.getCurrentTime() || 0, ytPlayer.getDuration() || 0); } catch (e) {}
    358     }, 250);
    359   }
    360   function stopYTTick() { if (ytTick) { clearInterval(ytTick); ytTick = null; } }
    361 
    362   function pullYTMeta() {
    363     try {
    364       const t = queue[currentIndex]; if (!t || (!t.yt && !t.ytList) || !ytPlayer.getVideoData) return;
    365       const vd = ytPlayer.getVideoData() || {};
    366       // Bij een afspeellijst wisselt de titel per nummer → altijd bijwerken.
    367       if (vd.title && (t.ytList || !t.title || t.title === 'YouTube')) {
    368         titleEl.textContent = vd.title; sheetTitle.textContent = vd.title;
    369         if (!t.ytList) t.title = vd.title;
    370       }
    371       if (vd.video_id) {
    372         setYtCover(vd.video_id);
    373         markPlaying(vd.video_id);  // licht de bijbehorende album-rij op (id="track-<videoId>")
    374       }
    375     } catch (e) {}
    376   }
    377 
    378   // Gedeelde voortgang-UI (beide backends). Audio gebruikt 'timeupdate', YT de tick.
    379   function updateProgressUI(cur, dur) {
    380     if (!dur || isNaN(dur)) return;
    381     const pct = Math.max(0, Math.min(100, (cur / dur) * 100));
    382     seekFill.style.width = pct + '%'; sheetSeekFill.style.width = pct + '%';
    383     currentEl.textContent = formatTime(cur); totalEl.textContent = formatTime(dur);
    384     sheetCurrent.textContent = formatTime(cur); sheetTotal.textContent = formatTime(dur);
    385   }
    386 
    387291  function loadTrack(index, autoplay, metaOnly) {
    388292    if (!queue[index]) {
     
    392296    currentIndex = index;
    393297    const t = queue[index];
    394     // Metadata + chrome update synchronously (geldt voor beide backends).
    395     titleEl.textContent  = t.title  || (t.yt ? 'YouTube' : 'Untitled');
     298    if (!t.url) {
     299      console.error('[pcms-audio] track has no url', t);
     300      return;
     301    }
     302    console.log('[pcms-audio] loading', t.title, t.url, metaOnly ? '(meta only)' : '');
     303    // Metadata + chrome update synchronously so the UI reacts instantly while
     304    // the bytes download.
     305    titleEl.textContent  = t.title  || 'Untitled';
    396306    artistEl.textContent = t.artist || '';
    397     sheetTitle.textContent  = t.title  || (t.yt ? 'YouTube' : 'Untitled');
     307    sheetTitle.textContent  = t.title  || 'Untitled';
    398308    sheetArtist.textContent = t.artist || '';
    399309    sheetAlbum.textContent  = albumName || '';
     
    406316
    407317    if (metaOnly) return;
    408 
    409     // YouTube-bron (los nummer óf afspeellijst/album) → verborgen YT-speler.
    410     if (t.yt || t.ytList) {
    411       ytMode = true;
    412       try { audio.pause(); } catch (e) {}
    413       loadSeq++; dropPreload();
    414       updateProgressUI(0, 1); currentEl.textContent = '0:00'; totalEl.textContent = '0:00';
    415       seekFill.style.width = '0%'; sheetSeekFill.style.width = '0%';
    416       root.classList.add('audio-loading');
    417       ensureYT().then((p) => {
    418         if (queue[currentIndex] !== t) return;  // inmiddels andere track gekozen
    419         root.classList.remove('audio-loading');
    420         if (!p) { onLoadError(new Error('YT API niet beschikbaar'), loadSeq); return; }
    421         try {
    422           // Gedempt starten = autoplay is ALTIJD toegestaan (YouTube blokkeert
    423           // anders het geluid). onYTState ontdempt zodra 't echt speelt.
    424           if (autoplay) { try { p.mute(); } catch (e) {} }
    425           if (t.ytList) {
    426             const idx = t.ytIndex || 0;
    427             // listType:'playlist' is vereist voor de object-vorm; zonder dit laadt
    428             // (en speelt) de afspeellijst niet betrouwbaar.
    429             if (autoplay) p.loadPlaylist({ listType: 'playlist', list: t.ytList, index: idx });
    430             else p.cuePlaylist({ listType: 'playlist', list: t.ytList, index: idx });
    431           } else {
    432             if (autoplay) p.loadVideoById(t.yt); else p.cueVideoById(t.yt);
    433           }
    434           p.setVolume(audio.muted ? 0 : Math.round(audio.volume * 100));
    435         } catch (e) {}
    436       });
    437       return;
    438     }
    439 
    440     // Normale blob-audio track.
    441     ytMode = false;
    442     stopYTTick();
    443     try { if (ytPlayer && ytPlayer.stopVideo) ytPlayer.stopVideo(); } catch (e) {}
    444     if (!t.url) { console.error('[pcms-audio] track has no url', t); return; }
    445318
    446319    const mySeq = ++loadSeq;
     
    494367
    495368  function play() {
    496     if (ytMode) {
    497       if (ytPlayer && ytReady) { try { ytPlayer.playVideo(); } catch (e) {} }
    498       else if (queue[currentIndex]) loadTrack(currentIndex, true);
    499       return;
    500     }
    501369    if (!audio.src) {
    502370      // Nothing fetched yet (pre-seed showed metadata only, or a load is still
     
    520388    }
    521389  }
    522   function pause()      { if (ytMode) { try { ytPlayer && ytPlayer.pauseVideo(); } catch (e) {} return; } audio.pause(); }
    523   function togglePlay() { if (ytMode) { isPlaying ? pause() : play(); return; } audio.paused ? play() : pause(); }
     390  function pause()      { audio.pause(); }
     391  function togglePlay() { audio.paused ? play() : pause(); }
    524392  function next() {
    525     const t = queue[currentIndex];
    526     if (t && t.ytList && ytPlayer && ytMode) { try { ytPlayer.nextVideo(); } catch (e) {} return; }
    527393    if (!queue.length) return;
    528394    loadTrack((currentIndex + 1) % queue.length, true);
    529395  }
    530396  function prev() {
    531     const t = queue[currentIndex];
    532     if (t && t.ytList && ytPlayer && ytMode) { try { ytPlayer.previousVideo(); } catch (e) {} return; }
    533397    if (!queue.length) return;
    534398    loadTrack(currentIndex === 0 ? queue.length - 1 : currentIndex - 1, true);
     
    540404    document.body.classList.remove('has-audio-player');
    541405    closeSheet();
    542     ytMode = false; stopYTTick();
    543     try { if (ytPlayer && ytPlayer.stopVideo) ytPlayer.stopVideo(); } catch (e) {}
    544406    queue = [];
    545407    albumName = '';
     
    600462    return r;
    601463  }
    602   const registrySelf = { pause() { try { audio.pause(); } catch (e) {} try { if (ytPlayer && ytPlayer.pauseVideo) ytPlayer.pauseVideo(); } catch (e) {} } };
     464  const registrySelf = { pause() { try { audio.pause(); } catch (e) {} } };
    603465
    604466  // ============================================================
     
    636498  audio.addEventListener('volumechange', () => { root.classList.toggle('is-muted', audio.muted || audio.volume === 0); });
    637499  audio.addEventListener('timeupdate', () => {
    638     if (ytMode) return;  // YT-voortgang loopt via de tick
    639     updateProgressUI(audio.currentTime, audio.duration);
     500    if (!audio.duration || isNaN(audio.duration)) return;
     501    const pct = (audio.currentTime / audio.duration) * 100;
     502    seekFill.style.width = pct + '%';
     503    sheetSeekFill.style.width = pct + '%';
     504    currentEl.textContent  = formatTime(audio.currentTime);
     505    totalEl.textContent    = formatTime(audio.duration);
     506    sheetCurrent.textContent = formatTime(audio.currentTime);
     507    sheetTotal.textContent   = formatTime(audio.duration);
    640508  });
    641509
     
    648516  function attachSeek(seekEl) {
    649517    seekEl.addEventListener('click', (e) => {
     518      if (!audio.duration) return;
    650519      const rect = seekEl.getBoundingClientRect();
    651520      const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
    652       if (ytMode) {
    653         try { const d = ytPlayer.getDuration() || 0; if (d) ytPlayer.seekTo(ratio * d, true); } catch (e2) {}
    654         return;
    655       }
    656       if (!audio.duration) return;
    657521      audio.currentTime = ratio * audio.duration;
    658522    });
     
    663527  audio.volume = 0.8;
    664528  volumeSlider.addEventListener('input', () => {
    665     const v = parseInt(volumeSlider.value, 10) || 0;
    666     audio.volume = v / 100;
    667     if (v > 0) audio.muted = false;
    668     if (ytPlayer) { try { ytPlayer.setVolume(v); if (v > 0 && ytPlayer.unMute) ytPlayer.unMute(); } catch (e) {} }
    669   });
    670   muteBtn.addEventListener('click', () => {
    671     audio.muted = !audio.muted;
    672     if (ytPlayer) { try { audio.muted ? ytPlayer.mute() : ytPlayer.unMute(); } catch (e) {} }
    673     root.classList.toggle('is-muted', audio.muted);
    674   });
     529    audio.volume = volumeSlider.value / 100;
     530    if (volumeSlider.value > 0) audio.muted = false;
     531  });
     532  muteBtn.addEventListener('click', () => { audio.muted = !audio.muted; });
    675533
    676534  // ============================================================
     
    939797  // 9. Public API
    940798  // ============================================================
    941   // Speel een YouTube-bron als AUDIO af in deze (persistente) site-speler — door
    942   // de embed-speler aangeroepen voor een audio-only YouTube-embed. Zo verschijnt
    943   // 'ie in de onderbalk én speelt 'ie door bij navigeren.
    944   function playYouTube(opts) {
    945     if (!opts || (!opts.id && !opts.list)) return;
    946     setQueue([{
    947       yt: opts.id || null,
    948       ytList: opts.list || null,     // YouTube-afspeellijst/album (OLAK…/PL…)
    949       ytIndex: opts.index || 0,      // startindex binnen de lijst
    950       title: opts.title || 'YouTube',
    951       artist: opts.artist || '',
    952       cover: opts.cover || '',
    953       postUrl: opts.postUrl || '',
    954     }], 0);
    955     // De scherpe cover + echte titel komen via pullYTMeta() zodra het nummer speelt.
    956   }
    957 
    958   // Zet de bar-cover op de thumbnail van een video (mqdefault = balkloos), en
    959   // upgrade async naar de scherpe maxresdefault als die bestaat.
    960   function setYtCover(videoId) {
    961     if (!videoId) return;
    962     const mq = 'https://i.ytimg.com/vi/' + videoId + '/mqdefault.jpg';
    963     setCoverImage(cover, mq); setCoverImage(sheetCover, mq);
    964     try {
    965       const maxres = 'https://i.ytimg.com/vi/' + videoId + '/maxresdefault.jpg';
    966       const im = new Image();
    967       im.onload = () => {
    968         if (im.naturalWidth <= 120) return;
    969         const cur = ytPlayer && ytPlayer.getVideoData ? (ytPlayer.getVideoData() || {}).video_id : null;
    970         if (cur === videoId) { setCoverImage(cover, maxres); setCoverImage(sheetCover, maxres); }
    971       };
    972       im.src = maxres;
    973     } catch (e) {}
    974   }
    975 
    976   // Speel een YouTube-AFSPEELLIJST af door 'm te expanderen naar onze eigen queue
    977   // van losse YouTube-tracks (elk via loadVideoById = bewezen betrouwbaar; next/
    978   // prev/ended lopen via onze queue). items = [{yt,title,cover,postUrl}, …].
    979   function playYouTubeList(items, startIndex) {
    980     if (!Array.isArray(items) || !items.length) return;
    981     setQueue(items.map((it) => ({
    982       yt: it.yt,
    983       title: it.title || 'YouTube',
    984       artist: it.artist || '',
    985       cover: it.cover || '',
    986       postUrl: it.postUrl || '',
    987     })), startIndex || 0);
    988   }
    989 
    990   // Maak de verborgen YT-speler vast aan (zonder iets te spelen), zodat 'ie bij de
    991   // eerste klik al klaar staat → loadVideoById valt dan binnen de user-gesture en
    992   // YouTube's autoplay-policy blokkeert het niet.
    993   function prewarmYouTube() { try { ensureYT(); } catch (e) {} }
    994 
    995799  window.pcmsAudioPlayer = {
    996     setQueue, play, pause, next, prev, close, openSheet, closeSheet, playYouTube, playYouTubeList, prewarmYouTube,
     800    setQueue, play, pause, next, prev, close, openSheet, closeSheet,
    997801    isPlaying: () => isPlaying,
    998802    currentTrack: () => queue[currentIndex] || null,
Note: See TracChangeset for help on using the changeset viewer.