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 <noreply@anthropic.com>
This commit is contained in:
1 parent
08d2f69720
commit
4b34bbc043
2 files changed
+39
-7
No files matched your search
@@ -122,6 +122,7 @@ window.addEventListener('load',()=>{
|
|||||||
previewAudio.addEventListener('ended',stopPreview);
|
previewAudio.addEventListener('ended',stopPreview);
|
||||||
audio.volume=0.8;
|
audio.volume=0.8;
|
||||||
injectPWA();
|
injectPWA();
|
||||||
|
initProgressScrub();
|
||||||
// Now Playing: click player-art to open
|
// Now Playing: click player-art to open
|
||||||
const pa=document.getElementById('player-art');
|
const pa=document.getElementById('player-art');
|
||||||
if(pa)pa.addEventListener('click', openNowPlaying);
|
if(pa)pa.addEventListener('click', openNowPlaying);
|
||||||
@@ -1057,7 +1058,7 @@ function expandArtistBio(){
|
|||||||
if(el&&window._artistBioFull)el.textContent=window._artistBioFull;
|
if(el&&window._artistBioFull)el.textContent=window._artistBioFull;
|
||||||
}
|
}
|
||||||
function updateProgress(){
|
function updateProgress(){
|
||||||
if(!audio.duration)return;
|
if(!audio.duration||_scrubbing)return;
|
||||||
syncMediaSessionPosition();
|
syncMediaSessionPosition();
|
||||||
const p=(audio.currentTime/audio.duration)*100;
|
const p=(audio.currentTime/audio.duration)*100;
|
||||||
document.getElementById('progress-fill').style.width=p+'%';
|
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}
|
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}
|
function setVolume(v){audio.volume=v/100;previewAudio.volume=v/100}
|
||||||
|
|
||||||
// ─── STAR ───
|
// ─── STAR ───
|
||||||
@@ -1610,11 +1646,7 @@ function npToggleStar() {
|
|||||||
.catch(e => toast('Fehler: ' + e.message));
|
.catch(e => toast('Fehler: ' + e.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
function npSeek(e) {
|
function npSeek(e){}
|
||||||
const bar = document.getElementById('np-bar');
|
|
||||||
const rect = bar.getBoundingClientRect();
|
|
||||||
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
let npQueueMode = false;
|
let npQueueMode = false;
|
||||||
function switchNpTab(tab) {
|
function switchNpTab(tab) {
|
||||||
|
|||||||
@@ -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 PRECACHE = ['/', 'index.html', 'styles.css', 'app.js', 'manifest.json', 'icon.svg'];
|
||||||
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user