diff --git a/app/src/main/assets/h5/js/pages/home.js b/app/src/main/assets/h5/js/pages/home.js index 676d39e..d355da6 100644 --- a/app/src/main/assets/h5/js/pages/home.js +++ b/app/src/main/assets/h5/js/pages/home.js @@ -212,6 +212,8 @@ function renderMediaCard(info) { // Cover tap opens the media app's playback UI const coverEl = el.querySelector('.cover'); if (coverEl) coverEl.onclick = () => bridge.call('openMediaApp', currentMediaIndex); + // 进度条拖动 seek + setupMediaSeek(el); // 进度状态 + 全局 ticker updateMediaProgressState(info); startMediaTicker(); @@ -268,6 +270,43 @@ function fmtDuration(ms) { return m + ':' + String(s % 60).padStart(2, '0'); } +// 进度条拖动 seek:拖动预览进度,松手后 seek 到目标位置 +// Progress-bar drag seek: preview while dragging, seek on release +function setupMediaSeek(el) { + const prog = el.querySelector('.prog'); + if (!prog) return; + let dragging = false; + const ratioAt = (clientX) => { + const rect = prog.getBoundingClientRect(); + return Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)); + }; + const preview = (clientX) => { + const bar = el.querySelector('.bar'); + if (bar) bar.style.width = (ratioAt(clientX) * 100) + '%'; + }; + prog.addEventListener('touchstart', (e) => { + dragging = true; + preview(e.touches[0].clientX); + }, { passive: true }); + prog.addEventListener('touchmove', (e) => { + if (dragging) preview(e.touches[0].clientX); + }, { passive: true }); + prog.addEventListener('touchend', (e) => { + if (!dragging) return; + dragging = false; + const ratio = ratioAt(e.changedTouches[0].clientX); + const duration = mediaProgressState ? mediaProgressState.duration : 0; + if (duration > 0) { + const pos = Math.floor(ratio * duration); + bridge.call('mediaSeekTo', currentMediaIndex, pos); + if (mediaProgressState) { + mediaProgressState.position = pos; + mediaProgressState.updatedAt = Date.now(); + } + } + }, { passive: true }); +} + // 布局方向:默认上下(封面在上、信息在下),横条(宽>高)时左右 // Layout direction: vertical (cover on top, info below) by default; horizontal // (cover left, info right) when the card is wider than tall diff --git a/app/src/main/assets/h5/js/pages/settings.js b/app/src/main/assets/h5/js/pages/settings.js index 293a958..89eaffb 100644 --- a/app/src/main/assets/h5/js/pages/settings.js +++ b/app/src/main/assets/h5/js/pages/settings.js @@ -116,12 +116,10 @@ function setupMask(page) { window.mask.setEnabled(!window.mask.enabled()); refresh(); }; - slider.onclick = (e) => { - const rect = slider.getBoundingClientRect(); - const a = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width)); + makeSliderDraggable(slider, (a) => { window.mask.setAlpha(Math.round(a * 100) / 100); refresh(); - }; + }); } // 通知使用权授权项:异步查状态,未授权点击跳转系统授权页 @@ -155,12 +153,10 @@ function setupBrightness(page) { const pct = value / 255 * 100; sliderEl.style.setProperty('--fill', pct + '%'); sliderEl.querySelector('i').style.left = pct + '%'; - sliderEl.onclick = (e) => { - e.stopPropagation(); - const rect = sliderEl.getBoundingClientRect(); - const v = Math.round(Math.min(255, Math.max(0, (e.clientX - rect.left) / rect.width)) * 255); + makeSliderDraggable(sliderEl, (ratio) => { + const v = Math.round(ratio * 255); bridge.call('setSystemBrightness', v).then(() => renderSlider(v)).catch(() => {}); - }; + }); item.appendChild(sliderEl); }; @@ -237,3 +233,22 @@ function setupCheckUpdate(page) { }); }; } + +// 通用滑块拖动:touch 拖动实时回调(保留 click 兼容桌面调试) +// Generic draggable slider: touch-drag with live callback (click kept for desktop) +function makeSliderDraggable(slider, onRatio) { + let dragging = false; + const ratioAt = (clientX) => { + const rect = slider.getBoundingClientRect(); + return Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)); + }; + slider.addEventListener('touchstart', (e) => { + dragging = true; + onRatio(ratioAt(e.touches[0].clientX)); + }, { passive: true }); + slider.addEventListener('touchmove', (e) => { + if (dragging) onRatio(ratioAt(e.touches[0].clientX)); + }, { passive: true }); + slider.addEventListener('touchend', () => { dragging = false; }, { passive: true }); + slider.addEventListener('click', (e) => onRatio(ratioAt(e.clientX))); +} diff --git a/app/src/main/java/top/yeij/hearth/media/MediaSessionSource.kt b/app/src/main/java/top/yeij/hearth/media/MediaSessionSource.kt index dbd3880..6f31cf4 100644 --- a/app/src/main/java/top/yeij/hearth/media/MediaSessionSource.kt +++ b/app/src/main/java/top/yeij/hearth/media/MediaSessionSource.kt @@ -154,6 +154,13 @@ class MediaSessionSource(context: Context) { Log.d(TAG, "playPause: index=$index playing=$playing") } + // 拖动进度条 seek 到指定位置(毫秒) + // Seek to the given position (ms) when dragging the progress bar + fun seekTo(index: Int, position: Long) { + controllers.getOrNull(index)?.transportControls?.seekTo(position) + Log.d(TAG, "seekTo: index=$index position=$position") + } + // 跳转到指定会话的播放界面:优先 sessionActivity(PendingIntent), // 失败则回退到启动该 App // Jump to the session's playback UI: prefer sessionActivity (PendingIntent), diff --git a/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt b/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt index 8a60194..f5ca985 100644 --- a/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt +++ b/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt @@ -355,6 +355,13 @@ class JsBridge( mediaSourceRef?.next(index) } + // 拖动进度条 seek 到指定位置(毫秒) + // Seek to the given position (ms) when dragging the progress bar + @android.webkit.JavascriptInterface + fun mediaSeekTo(index: Int, position: Long) { + mediaSourceRef?.seekTo(index, position) + } + // 跳转到指定会话的播放界面(封面点击) // Jump to the session's playback UI (cover tap) @android.webkit.JavascriptInterface