diff --git a/app/src/main/assets/h5/css/app.css b/app/src/main/assets/h5/css/app.css index 65c6e78..fa3622f 100644 --- a/app/src/main/assets/h5/css/app.css +++ b/app/src/main/assets/h5/css/app.css @@ -301,6 +301,16 @@ body { transition: width .3s linear; } +/* 时长文字:当前时长 / 总时长 */ +/* Time labels: current / total duration */ +.media-card .time-row { + display: flex; + justify-content: space-between; + font-size: 11px; + color: var(--text-dim); + margin-top: 4px; +} + /* 无时长信息时的活动脉冲进度条 */ /* Indeterminate pulse when no duration is available */ .media-card .bar.indeterminate { diff --git a/app/src/main/assets/h5/js/pages/home.js b/app/src/main/assets/h5/js/pages/home.js index a0958f4..17fc798 100644 --- a/app/src/main/assets/h5/js/pages/home.js +++ b/app/src/main/assets/h5/js/pages/home.js @@ -161,6 +161,7 @@ function renderMediaCard(info) { const playIcon = info.playing ? ICON_PAUSE : ICON_PLAY; // title/artist 来自任意应用元数据,转义后插入,防 XSS // title/artist come from arbitrary app metadata; escape before insert (XSS) + const pct = info.duration ? Math.min(100, (info.position / info.duration) * 100) : 0; el.innerHTML = `
${coverHtml} @@ -169,7 +170,11 @@ function renderMediaCard(info) {
${escapeHtml(info.artist)}
-
+
+
+ ${fmtDuration(info.position)} + ${fmtDuration(info.duration)} +
@@ -209,20 +214,35 @@ function updateMediaProgressState(info) { let mediaTicker = null; function startMediaTicker() { if (mediaTicker) return; - mediaTicker = setInterval(() => { + const tick = () => { + if (!mediaProgressState) return; const bar = document.querySelector('.media-card .bar'); - if (!bar || !mediaProgressState) return; + const cur = document.querySelector('.media-card .t-cur'); const { position, duration, playing, updatedAt } = mediaProgressState; if (!duration) { // 无时长信息(部分 App 不提供 duration):显示活动脉冲而非空白 // No duration (some apps omit it): show an indeterminate pulse instead of blank - bar.classList.add('indeterminate'); + if (bar) bar.classList.add('indeterminate'); return; } - bar.classList.remove('indeterminate'); const pos = playing ? position + (Date.now() - updatedAt) : position; - bar.style.width = Math.min(100, (pos / duration) * 100) + '%'; - }, 1000); + if (bar) { + bar.classList.remove('indeterminate'); + bar.style.width = Math.min(100, (pos / duration) * 100) + '%'; + } + if (cur) cur.textContent = fmtDuration(pos); + }; + tick(); // 立即执行一次,避免初始 1 秒内进度条空白 + mediaTicker = setInterval(tick, 1000); +} + +// 时长格式化:毫秒 -> m:ss(未知返回 --:--) +// Duration format: ms -> m:ss (--:-- when unknown) +function fmtDuration(ms) { + if (!ms || ms <= 0) return '--:--'; + const s = Math.floor(ms / 1000); + const m = Math.floor(s / 60); + return m + ':' + String(s % 60).padStart(2, '0'); } // 布局方向:默认上下(封面在上、信息在下),横条(宽>高)时左右