Changeset 21522ae in Klonkt for src/routes/admin-audio.js


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@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-audio.js

    r46f23fd r21522ae  
    2020import { requireGod } from '../middleware/auth.js';
    2121import { transcodeToMp3 } from '../services/AudioTranscoder.js';
    22 import { signUrl } from '../services/AudioStreamService.js';
     22import { audioUrl } from '../services/AudioStreamService.js';
    2323
    2424const __dirname = path.dirname(fileURLToPath(import.meta.url));
     
    8080  `).all(site.id);
    8181
    82   // Sign each track's stream URL so admins can preview audio inline.
    83   // Short TTL (default 10 min from AudioStreamService) means the URL on
    84   // the page expires if it sits open too long; a refresh re-signs.
     82  // Build each track's stream URL so admins can preview audio inline.
    8583  const tracks = rows.map(t => ({
    8684    ...t,
    87     stream_url: t.filename ? signUrl(t.filename).url : null,
     85    stream_url: t.filename ? audioUrl(t.filename) : null,
    8886  }));
    8987
     
    335333  `).get(req.params.id, site.id);
    336334  if (!t) return res.status(404).json({ error: 'Track niet gevonden' });
    337   // Sign the stream URL so the modal can render an inline preview player.
    338   const stream_url = t.filename ? signUrl(t.filename).url : null;
     335  // Stream URL so the modal can render an inline preview player.
     336  const stream_url = t.filename ? audioUrl(t.filename) : null;
    339337  res.json({ ok: true, track: { ...t, stream_url } });
    340338});
Note: See TracChangeset for help on using the changeset viewer.