Changeset 557a76f in Klonkt for src/assets


Ignore:
Timestamp:
06/20/2026 03:04:22 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
7990e00
Parents:
2873b30
Message:

feat(embed): volume slider + mute for YouTube & SoundCloud

YT/SC use our own controls → volume is now adjustable: mute button + slider
in the control bar. Adapters got setVolume (YT mute/unMute+setVolume, SC
setVolume); the value is remembered and reapplied after (re)mount. Spotify
is excluded (cross-origin iframe, no API volume — as discussed). busters
embed-player.js?v=7, embed.css?v=4.

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

Location:
src/assets
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/assets/css/embed.css

    r2873b30 r557a76f  
    195195  text-align: center;
    196196}
     197/* Volume: mute-knop + compacte schuif (alleen YT/SC — eigen controls). */
     198.pcms-embed-mute {
     199  flex: 0 0 auto;
     200  width: 28px; height: 28px;
     201  border: 0; background: none; padding: 0;
     202  color: var(--ink-muted, #6b6560);
     203  cursor: pointer;
     204  display: grid; place-items: center;
     205}
     206.pcms-embed-mute svg { width: 18px; height: 18px; }
     207.pcms-embed-vol {
     208  flex: 0 0 auto;
     209  width: 64px;
     210  cursor: pointer;
     211  accent-color: var(--accent, #c2410c);
     212}
     213@media (max-width: 480px) {
     214  /* Smal scherm: schuif weg, alleen de mute-knop houden (ruimte). */
     215  .pcms-embed-vol { display: none; }
     216}
    197217.pcms-embed-seek {
    198218  position: relative;
  • src/assets/js/embed-player.js

    r2873b30 r557a76f  
    148148    play: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 4l12 8-12 8z" fill="currentColor"/></svg>',
    149149    pause: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 4h4v16H7zM13 4h4v16h-4z" fill="currentColor"/></svg>',
     150    volume: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3z" fill="currentColor"/><path d="M16 8a5 5 0 010 8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
     151    muted: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3z" fill="currentColor"/><path d="M16 9l5 6M21 9l-5 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
    150152  };
    151153  const LABEL = { youtube: 'YouTube', soundcloud: 'SoundCloud', spotify: 'Spotify' };
     
    202204            + `<div class="pcms-embed-seek" role="slider" aria-label="Voortgang" tabindex="0"><div class="pcms-embed-seek-fill"></div></div>`
    203205            + `<span class="pcms-embed-dur mono">0:00</span>`
     206            + `<button type="button" class="pcms-embed-mute" aria-label="Dempen">${ICON.volume}</button>`
     207            + `<input type="range" class="pcms-embed-vol" min="0" max="100" value="100" aria-label="Volume">`
    204208            + `<a class="pcms-embed-badge" href="${escAttr(safeHref(url))}" target="_blank" rel="noopener">${LABEL[provider] || provider}</a>`
    205209            + `</div>`
     
    214218    const posterBtn = el.querySelector('.pcms-embed-poster, .pcms-embed-art');
    215219    const subEl = el.querySelector('.pcms-embed-sub');
     220    const volEl = el.querySelector('.pcms-embed-vol');
     221    const muteBtn = el.querySelector('.pcms-embed-mute');
     222    let vol = 100;       // huidige volume 0-100 (wordt op de adapter toegepast)
     223    let preMuteVol = 100;
    216224
    217225    function setPlayingUI(on) {
     
    297305      try {
    298306        adapter = await MOUNTERS[provider](mountEl, { ref, url }, hooks);
     307        if (el._pcmsApplyVol) el._pcmsApplyVol();  // onthouden volume toepassen
    299308        // is-mounted: CSS verbergt de poster (video) of de Spotify-facade en
    300309        // toont de echte speler. Voor SoundCloud blijft onze kaart+balk staan en
     
    319328      adapter.seek(ratio * dur);
    320329    });
     330    // Volume: schuif zet vol (0-100) en past 'm toe op de adapter (YT/SC hebben
     331    // setVolume). De waarde wordt onthouden en na het mounten opnieuw toegepast.
     332    function applyVol() {
     333      if (adapter && adapter.setVolume) { try { adapter.setVolume(vol); } catch (e) {} }
     334      if (muteBtn) muteBtn.innerHTML = vol === 0 ? ICON.muted : ICON.volume;
     335      if (volEl && String(volEl.value) !== String(vol)) volEl.value = vol;
     336      el.classList.toggle('is-muted', vol === 0);
     337    }
     338    if (volEl) volEl.addEventListener('input', () => { vol = parseInt(volEl.value, 10) || 0; if (vol > 0) preMuteVol = vol; applyVol(); });
     339    if (muteBtn) muteBtn.addEventListener('click', () => {
     340      if (vol > 0) { preMuteVol = vol; vol = 0; } else { vol = preMuteVol || 100; }
     341      applyVol();
     342    });
     343    el._pcmsApplyVol = applyVol;  // door ensureMountedAndPlay aangeroepen na mount
    321344  }
    322345
     
    352375                pause() { try { player.pauseVideo(); } catch (e) {} },
    353376                seek(sec) { try { player.seekTo(sec, true); } catch (e) {} },
     377                setVolume(pct) { try { if (pct <= 0) player.mute(); else { player.unMute(); player.setVolume(pct); } } catch (e) {} },
    354378                destroy() {
    355379                  if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
     
    411435            pause() { widget.pause(); },
    412436            seek(sec) { widget.seekTo(sec * 1000); },
     437            setVolume(pct) { try { widget.setVolume(pct); } catch (e) {} },
    413438            destroy() {
    414439              try { ['READY', 'PLAY', 'PAUSE', 'FINISH', 'PLAY_PROGRESS', 'ERROR'].forEach((k) => E[k] && widget.unbind(E[k])); } catch (e) {}
Note: See TracChangeset for help on using the changeset viewer.