From 4856f0ca57d8e8558bb3601b5637ef5b594e92e9 Mon Sep 17 00:00:00 2001 From: Honey Date: Mon, 18 May 2026 22:42:58 +0200 Subject: [PATCH] fix: bottom bar no touch seek; lyrics seek on Android; smoother NP animation - initProgressScrub: pass noTouch=true for the bottom mini-bar so touch events are never registered on it; only mouse events remain (desktop-only). The now-playing scrubber is unchanged and keeps full touch support. - Export seekToMs(ms) from player.js; use it in the lyrics click handler instead of S.audio.currentTime which has no effect when HAS_BRIDGE (native ExoPlayer handles playback, not the HTML audio element). - styles.css: add will-change:transform to .player and .np-overlay to keep them on GPU compositor layers during the swipe gesture; tweak NP open transition to cubic-bezier(.4,0,.2,1) for a snappier feel. Co-Authored-By: Claude Sonnet 4.6 --- src/nowplaying.js | 4 ++-- src/player.js | 56 +++++++++++++++++++++++++++++------------------ styles.css | 5 +++-- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/nowplaying.js b/src/nowplaying.js index 0bc6cba..e21c8b7 100644 --- a/src/nowplaying.js +++ b/src/nowplaying.js @@ -2,7 +2,7 @@ import { S } from './state.js'; import { esc } from './utils.js'; import { coverUrl } from './api.js'; // Circular imports — safe (only used inside function bodies) -import { playCurrent, _syncNpDirect, dlSongLocal, npToggleStar, _syncOverlayState, isPlaying } from './player.js'; +import { playCurrent, _syncNpDirect, dlSongLocal, npToggleStar, _syncOverlayState, isPlaying, seekToMs } from './player.js'; import { navigate } from './views.js'; import { openPlModal, showSongInfo } from './ui.js'; @@ -116,7 +116,7 @@ function _renderSyncedLines(content, lines) { const el = document.createElement('div'); el.className = 'lyrics-line'; el.textContent = line.value; - el.onclick = () => { S.audio.currentTime = line.start / 1000; }; + el.onclick = () => { seekToMs(line.start); }; content.appendChild(el); return { start: line.start, el }; }); diff --git a/src/player.js b/src/player.js index ee4c1bc..d957985 100644 --- a/src/player.js +++ b/src/player.js @@ -656,10 +656,12 @@ export function seekTo(e) { } export function initProgressScrub() { + // Bottom mini-bar: mouse-only (touch disabled — too easy to accidentally seek on mobile). _initBarScrub( document.getElementById('progress-bar'), document.getElementById('progress-fill'), - pos => { document.getElementById('time-cur').textContent = formatDur(pos * _dur()); } + pos => { document.getElementById('time-cur').textContent = formatDur(pos * _dur()); }, + /* noTouch= */ true ); _initBarScrub( document.getElementById('np-bar'), @@ -668,7 +670,17 @@ export function initProgressScrub() { ); } -function _initBarScrub(bar, fill, onMove) { +// Export for lyrics / other callers that need to seek by ms. +export function seekToMs(ms) { + if (HAS_BRIDGE) { + _setSkipping(); + _nsBridge('seekTo', Math.round(ms)); + } else { + S.audio.currentTime = ms / 1000; + } +} + +function _initBarScrub(bar, fill, onMove, noTouch = false) { 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) { S._scrubbing=true; fill.style.transition='none'; fill.style.width=(pos*100)+'%'; onMove(pos); } @@ -691,25 +703,27 @@ function _initBarScrub(bar, fill, onMove) { fill.style.transition = ''; } } - // Touch: require ≥8px horizontal drag before committing to scrub. - // A plain tap cancels cleanly without seeking. - let _touchStartX = 0, _touchMoved = false; - bar.addEventListener('touchstart', e => { - _touchStartX = e.touches[0].clientX; - _touchMoved = false; - e.stopPropagation(); - e.preventDefault(); // block synthetic mousedown/click that would trigger scrub on plain tap - }); - bar.addEventListener('touchmove', e => { - const dx = Math.abs(e.touches[0].clientX - _touchStartX); - if (!_touchMoved && dx >= 8) { _touchMoved = true; startScrub(getPos(e.touches[0].clientX)); } - if (_touchMoved && S._scrubbing) moveScrub(getPos(e.touches[0].clientX)); - }, {passive:true}); - bar.addEventListener('touchend', e => { - if (_touchMoved && S._scrubbing) endScrub(getPos(e.changedTouches[0].clientX)); - else S._scrubbing = false; - _touchMoved = false; - }, {passive:true}); + if (!noTouch) { + // Touch: require ≥8px horizontal drag before committing to scrub. + // A plain tap cancels cleanly without seeking. + let _touchStartX = 0, _touchMoved = false; + bar.addEventListener('touchstart', e => { + _touchStartX = e.touches[0].clientX; + _touchMoved = false; + e.stopPropagation(); + e.preventDefault(); // block synthetic mousedown/click that would trigger scrub on plain tap + }); + bar.addEventListener('touchmove', e => { + const dx = Math.abs(e.touches[0].clientX - _touchStartX); + if (!_touchMoved && dx >= 8) { _touchMoved = true; startScrub(getPos(e.touches[0].clientX)); } + if (_touchMoved && S._scrubbing) moveScrub(getPos(e.touches[0].clientX)); + }, {passive:true}); + bar.addEventListener('touchend', e => { + if (_touchMoved && S._scrubbing) endScrub(getPos(e.changedTouches[0].clientX)); + else S._scrubbing = false; + _touchMoved = false; + }, {passive:true}); + } bar.addEventListener('mousedown', e => { startScrub(getPos(e.clientX)); e.preventDefault(); const mm = e2 => { if(S._scrubbing) moveScrub(getPos(e2.clientX)); }; diff --git a/styles.css b/styles.css index 169a9c1..b516ee6 100644 --- a/styles.css +++ b/styles.css @@ -223,7 +223,7 @@ button:focus{outline:none} .btn-outline.blue:hover{background:var(--blue-dim)} /* ── PLAYER ── */ -.player{position:fixed;bottom:0;left:0;right:0;height:var(--player-h);background:rgba(8,8,20,.93);backdrop-filter:blur(28px);border-top:1px solid var(--border2);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:0;padding:0 20px;z-index:100} +.player{position:fixed;bottom:0;left:0;right:0;height:var(--player-h);background:rgba(8,8,20,.93);backdrop-filter:blur(28px);border-top:1px solid var(--border2);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:0;padding:0 20px;z-index:100;will-change:transform} .player-drag-handle{position:absolute;top:5px;left:50%;transform:translateX(-50%);width:32px;height:3px;border-radius:2px;background:rgba(255,255,255,.18);transition:background .2s,width .2s;pointer-events:none} .player.dragging .player-drag-handle{background:rgba(255,255,255,.45);width:44px} .player-track{min-width:0;display:flex;align-items:center;gap:10px;overflow:visible} @@ -521,8 +521,9 @@ button:focus{outline:none} display:flex;flex-direction:column; background:var(--bg); transform:translateY(100%); - transition:transform .4s cubic-bezier(.32,.72,0,1); + transition:transform .35s cubic-bezier(.4,0,.2,1); overflow:hidden; + will-change:transform; } .np-overlay.open{transform:translateY(0)}