Changeset 21522ae in Klonkt for src/views


Ignore:
Timestamp:
05/20/2026 10:14:01 PM (4 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
353c39c
Parents:
46f23fd
git-author:
Robin Genis <roboburr@…> (05/20/2026 10:13:26 PM)
git-committer:
Robin Genis <roboburr@…> (05/20/2026 10:14:01 PM)
Message:

audio: Spotify-style blob playback + same-origin gate (fix playback loop)

Root cause of the "next-loops-but-never-plays after 4-5 songs" bug: every
track URL was HMAC-signed once at page-render time with a 10-min TTL. A whole
queue shared that single deadline, so tracks further down expired mid-session
-> /audio/stream returned 403 -> audio 'error' -> auto-skip -> next track also
expired -> infinite loop. The 3-strike guard never fired because the eager
'play' event reset the counter before each 403 landed.

Removed the expiring-token system entirely and replaced it with two
non-expiring layers:

  • Client fetch()es track bytes and plays from a blob: object URL (no shareable URL, no "save audio as"); blobs revoked to avoid leaks; loadSeq guards fast prev/next; pre-seed is metadata-only (no auto-download).
  • Server gates /audio/stream to same-origin browser fetches (X-Audio-Player header or Sec-Fetch-Site): blocks address-bar paste, hotlinks, curl.

Also: reset error counter on real 'playing' event (not eager 'play') so the
3-strike auto-skip-stop actually works; fix admin play-state detection to
compare logical currentTrack().url instead of the now-blob: audio.src; bump
audio-player.js cache-buster v5.

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

Location:
src/views
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/views/pages/admin-audio.ejs

    r46f23fd r21522ae  
    583583    function resyncAll() {
    584584      const audio = document.getElementById('audio-element');
     585      const player = window.pcmsAudioPlayer;
    585586      const playing = audio && !audio.paused && !audio.ended;
    586       const currentSrc = audio ? audio.src : '';
     587      // audio.src is now a blob: URL (Spotify-style playback), so compare
     588      // against the player's logical track URL, not the element src.
     589      const cur = player && player.currentTrack();
     590      const curUrl = cur ? cur.url : '';
    587591      buttons.forEach(b => {
    588         const isThisOne = playing && currentSrc.endsWith(b.dataset.streamUrl);
     592        const isThisOne = playing && curUrl === b.dataset.streamUrl;
    589593        setIcon(b, isThisOne);
    590594      });
     
    617621        };
    618622
    619         // If this exact track is already playing, toggle pause/play instead
    620         // of restarting from zero.
    621         const audio = document.getElementById('audio-element');
    622         if (audio && audio.src.endsWith(url)) {
    623           if (audio.paused) player.play();
    624           else              player.pause();
     623        // If this exact track is already current, toggle pause/play instead
     624        // of restarting from zero. Compare logical URLs (audio.src is a blob:).
     625        const cur = player.currentTrack();
     626        if (cur && cur.url === url) {
     627          if (player.isPlaying()) player.pause();
     628          else                    player.play();
    625629          return;
    626630        }
  • src/views/partials/track-editor.ejs

    r46f23fd r21522ae  
    445445      };
    446446      const isOurTrack = () => {
    447         const audio = document.getElementById('audio-element');
    448         return audio && track.stream_url && audio.src.endsWith(track.stream_url);
     447        // audio.src is a blob: URL (Spotify-style playback) — compare against
     448        // the player's logical current-track URL instead.
     449        const player = window.pcmsAudioPlayer;
     450        const cur = player && player.currentTrack();
     451        return !!(cur && track.stream_url && cur.url === track.stream_url);
    449452      };
    450453      const resync = () => {
  • src/views/shell.ejs

    r46f23fd r21522ae  
    277277     ?v=N — cache-buster: bump bij elke audio-player.js wijziging zodat
    278278     Cloudflare (max-age=1y) niet de oude versie blijft serveren. -->
    279 <script src="/assets/js/audio-player.js?v=3"></script>
     279<script src="/assets/js/audio-player.js?v=5"></script>
    280280<% if (site && site.enable_audio_player && audioTracks && audioTracks.length > 0) { %>
    281281  <script>window.PCMS_SITE_TRACKS = <%- JSON.stringify(audioTracks) %>;</script>
Note: See TracChangeset for help on using the changeset viewer.