From 4b34bbc043facb1be5ca0ef3017e90a1c64132df Mon Sep 17 00:00:00 2001 From: Honey Date: Sat, 25 Apr 2026 18:45:23 +0200 Subject: [PATCH] feat: smooth progress bar scrubbing with touch drag support - initProgressScrub() adds touchstart/move/end + mousedown drag to both the player bar and NP overlay progress bars - CSS transition disabled during scrub so fill follows finger instantly - updateProgress() skips DOM updates while _scrubbing is active so timeupdate events don't fight the drag position - Works on both bars with a shared _initBarScrub helper Co-Authored-By: Claude Sonnet 4.6 --- app.js | 44 ++++++++++++++++++++++++++++++++++++++------ sw.js | 2 +- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/app.js b/app.js index bbb677a..5865773 100644 --- a/app.js +++ b/app.js @@ -122,6 +122,7 @@ window.addEventListener('load',()=>{ previewAudio.addEventListener('ended',stopPreview); audio.volume=0.8; injectPWA(); + initProgressScrub(); // Now Playing: click player-art to open const pa=document.getElementById('player-art'); if(pa)pa.addEventListener('click', openNowPlaying); @@ -1057,7 +1058,7 @@ function expandArtistBio(){ if(el&&window._artistBioFull)el.textContent=window._artistBioFull; } function updateProgress(){ - if(!audio.duration)return; + if(!audio.duration||_scrubbing)return; syncMediaSessionPosition(); const p=(audio.currentTime/audio.duration)*100; document.getElementById('progress-fill').style.width=p+'%'; @@ -1074,6 +1075,41 @@ function updateProgress(){ } } function seekTo(e){const bar=document.getElementById('progress-bar');const rect=bar.getBoundingClientRect();audio.currentTime=((e.clientX-rect.left)/rect.width)*audio.duration} + +let _scrubbing=false; +function initProgressScrub(){ + _initBarScrub( + document.getElementById('progress-bar'), + document.getElementById('progress-fill'), + pos=>{ + document.getElementById('time-cur').textContent=formatDur(pos*audio.duration); + } + ); + _initBarScrub( + document.getElementById('np-bar'), + document.getElementById('np-fill'), + pos=>{ + const el=document.getElementById('np-cur'); + if(el)el.textContent=formatDur(pos*audio.duration); + } + ); +} +function _initBarScrub(bar,fill,onMove){ + if(!bar||!fill)return; + function getPos(clientX){const r=bar.getBoundingClientRect();return Math.max(0,Math.min(1,(clientX-r.left)/r.width));} + function startScrub(pos){_scrubbing=true;fill.style.transition='none';fill.style.width=(pos*100)+'%';onMove(pos);} + function moveScrub(pos){fill.style.width=(pos*100)+'%';onMove(pos);} + function endScrub(pos){_scrubbing=false;fill.style.transition='';if(audio.duration)audio.currentTime=pos*audio.duration;} + bar.addEventListener('touchstart',e=>{startScrub(getPos(e.touches[0].clientX));e.stopPropagation();},{passive:true}); + bar.addEventListener('touchmove',e=>{if(_scrubbing)moveScrub(getPos(e.touches[0].clientX));},{passive:true}); + bar.addEventListener('touchend',e=>{if(_scrubbing)endScrub(getPos(e.changedTouches[0].clientX));},{passive:true}); + bar.addEventListener('mousedown',e=>{ + startScrub(getPos(e.clientX));e.preventDefault(); + const mm=e2=>{if(_scrubbing)moveScrub(getPos(e2.clientX));}; + const mu=e2=>{if(_scrubbing)endScrub(getPos(e2.clientX));document.removeEventListener('mousemove',mm);document.removeEventListener('mouseup',mu);}; + document.addEventListener('mousemove',mm);document.addEventListener('mouseup',mu); + }); +} function setVolume(v){audio.volume=v/100;previewAudio.volume=v/100} // ─── STAR ─── @@ -1610,11 +1646,7 @@ function npToggleStar() { .catch(e => toast('Fehler: ' + e.message)); } -function npSeek(e) { - const bar = document.getElementById('np-bar'); - const rect = bar.getBoundingClientRect(); - audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration; -} +function npSeek(e){} let npQueueMode = false; function switchNpTab(tab) { diff --git a/sw.js b/sw.js index 67d1049..37334da 100644 --- a/sw.js +++ b/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'ns-v24'; +const CACHE = 'ns-v25'; const PRECACHE = ['/', 'index.html', 'styles.css', 'app.js', 'manifest.json', 'icon.svg']; const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];