Repository navigation
Releases: HelloEarth520/Music-Player
Release list
MusicPlayer v2.22.19 (code 60)
MusicPlayer v2.22.19
- 版本号:versionName 2.22.19 / versionCode 60
- 日期:2026-09-18
- 本版主题:前台 CPU 占用优化——修复「炫彩流光文字」动画导致的每帧全视口重绘(实测 −26.5%)
需求背景
本版针对前台播放场景的 CPU / 内存占用做专项排查与优化。
真机(arm64-v8a)采样结论:
| 状态 | CPU 占用 |
|---|---|
| 前台亮屏(暂停) | 173.4% |
| 前台亮屏(播放) | 176.2% |
| 后台(HOME) | 14.8% |
| 锁屏 | 5.7% |
- 前台高占用与是否播放无关(差 2.8 点),说明不是音频解码开销。
dumpsys gfxinfo:稳定 121fps、无掉帧;每帧 CPU 侧约 22ms(高刷屏每帧预算 8.3ms)、GPU 仅 3ms
→ 瓶颈在 CPU 侧渲染管线。- 内存无泄漏:连续切歌 16 次 PSS 471→551→515MB(回落),导入 1387 首仅 +12MB,线程 75→77。
归因过程(关键:第一次判断是错的)
第一轮判断(v2.22.18,已证伪)
按"毛玻璃很贵"的直觉,把整屏模糊从 #bg-veil 的 backdrop-filter 迁到壁纸层 filter,
并下调各处模糊半径、加 contain: layout paint。实测只降约 7%,与预期严重不符。
第二轮:CDP 逐项 A/B 归因(决定性)
经 adb forward 接 WebView 的 webview_devtools_remote_<pid>,用 Runtime.evaluate
注入覆盖 CSS,在同一进程、同一首歌、同一次会话里逐项开关。
共 5 轮、47 个对照场景(工具:_perf/cdp_attr.mjs,Node 22 原生 WebSocket,零第三方依赖)。
| 场景 | CPU 均值 | Δ | 判读 |
|---|---|---|---|
| 基线 | 163.7% | 0 | — |
| 停 19 个列表项标题的 fx-flow | 136.4% | −31.5 | 唯一显著项 |
| ↳ 复测(重复性) | 136.6% | −31.3 | 复现性 ±0.2 |
| 停跑马灯环 ring-run | 170.5% | +7.5 | 无成本 |
| 停歌词流逝 lyric-flow | 169.5% | +1.6 | 无成本 |
| 停顶栏 / 栏头 / 数值类 | 164.6~166.6% | −1.3~−3.3 | 噪声 |
| 关全部 transition | 163.3% | +3.1 | 无成本 |
| 关全部 backdrop-filter | 153.2% | −10.5 | 约 −6%,非主因 |
| 关 filter / 壁纸模糊 | 165.5% | +1.8 | 无成本 |
| 关阴影 | 161.7% | −2.0 | 噪声 |
根因:播放列表 19 个曲目标题用 background-clip:text + 动画 background-position
实现「炫彩流动文字」(@keyframes fx-flow)。该动画不可合成到 GPU 合成线程,
每一帧都要在主线程重新栅格化文字渐变色。
非线性发现:顶栏 / 栏头 / 数值三项各自单停只值 1.3~3.3 点,但连同列表项标题一起停掉却 −54.2 点
("关全部动画"上限 −83.5)。说明成本主体是「这一帧到底有没有跨视口重绘」——
只要视口内任意一处持续变化,整条管线(主线程样式/布局/绘制 + 合成 + 所有 backdrop-filter
区域重新快照并模糊)就每帧全量运转;此时增删单个元素的边际成本很小。
实现
-
www/style.css:6 处fx-flow动画的时间函数linear→steps(240, end):/* 改前 */ animation: fx-flow var(--fx-speed, 10s) linear infinite; /* 改后 */ animation: fx-flow var(--fx-speed, 10s) steps(240, end) infinite;
渐变更新率由约 121 次/秒降到约 8 次/秒(
--fx-speed默认 10s 时约 24 次/秒)。
动画外观不变:位移速度仍约 70px/s,每步仅约 1% 背景宽(235px 标题约 7px),
柔和渐变下肉眼无差别。语法注意:steps()与linear互斥,不能同写(否则整条声明失效)。 -
www/player.js:v2.22.18 的性能改造保留(壁纸模糊迁移、coverTick30fps 闸门 + 页面隐藏门控、
通知推送四要素去重、EQ 渐变对象按高度量化缓存),并修正其中已证伪的注释。 -
web-demo/style.css、web-demo/demo.js:同步同一套改造。 -
清理:
www/_perf_bak_20260918/(会被cap copy打进 APK)已移出到项目根。 -
android/app/build.gradle:versionCode 59 → 60,versionName 2.22.18 → 2.22.19。 -
www/index.html:设置页版本号 v2.22.19。
验证
修复前预演(CDP A/B)
| 场景 | CPU | Δ |
|---|---|---|
| 基线 | 164.5% | 0 |
| 全部 fx-flow 元素加 steps(240, end) | 121.3% | −43.2(−26%) |
| 仅列表项标题完全停掉(对照) | 137.6% | −26.9 |
量化后的成本已与"完全删掉动画"持平 —— 拿到全部收益、零视觉损失。
修复后实测(真机,同方法)
| 场景 | v2.22.18 | v2.22.19 | 变化 |
|---|---|---|---|
| 前台亮屏播放(基线) | 164.5% | 120.9% | −26.5% |
| ↳ 基线复测 | 167.9% | 133.6% | −20.4% |
| 关全部动画(下限对照) | 79.5% | 98.0% / 86.5% | 差距收窄至约 23 点 |
与预演预测的 −26% 吻合。网页侧核对计算值确认修复生效:
TIMING_FN: {"titleName":"fx-flow","titleTF":"steps(240)","titleDur":"30s",
"clockName":"fx-flow","clockTF":"steps(240)"}
构建与归档
- cap copy →
assembleDebugBUILD SUCCESSFUL(18s + 13s 两次,JDK 21)。 - 装机:
adb install -r -d→ Success;dumpsys package确认 versionCode=60 / versionName=2.22.19。 - 归档:
python _release/archive_release.py 2.22.19 60
(source 快照 32.6MB + APK 本版/回滚基线 + source zip + website zip)。 - webdemo 包:
python _release/pack_webdemo.py 2.22.19 60(本版新增工具)。 - 回滚基线:
APK/app-v2.22.18-code59.apk(亦在<归档>/_apk_backup/)。
装机记录
- 覆盖安装成功,用户数据保留。
- 冷启动正常,无
FATAL/AndroidRuntime异常;播放正常。 - 修复后新会话实测前台 CPU 稳定在 100~119%。
测量局限(如实说明)
- 帧率不可锁:部分厂商 ROM 忽略
settings put system peak_refresh_rate,设备稳定在约 121fps。
若能在 60Hz 下运行,CPU 占用预计近似减半——这是一条尚未利用的优化路径,但系统层无可用接口。 - 存在状态依赖:同一组覆盖条件在两次独立测量中最大差 38 点(67.9% vs 106.0%),
疑与温度、调度、播放进度有关。故只采纳多轮重复一致的项;单次极端值不予采信。 - CPU 口径:为「相对单核」百分比,8 核满载为 800%;含进程内所有线程。
- GPU 归因有限:
dumpsys gfxinfo的 GPU 耗时(约 3ms)未完整覆盖 WebView 自身渲染管线。
构建备忘
同 v2.22.13:cap copy android → JAVA_HOME=<JDK 21> +
ANDROID_HOME=<ANDROID_SDK> + NODE_OPTIONS="--use-system-ca" → gradlew assembleDebug。
归因工具复现方式:
ADB=<adb路径>
PID=$("$ADB" shell pidof com.musicplayer.app | tr -d '\r')
"$ADB" forward tcp:9222 localabstract:webview_devtools_remote_$PID
NODE_OPTIONS="--use-system-ca" node _perf/cdp_attr.mjs com.musicplayer.app "$PID" runMusicPlayer v2.22.18 (code 59)
MusicPlayer v2.22.18
- 版本号:versionName 2.22.18 / versionCode 59
- 日期:2026-09-10
- 本版主题:移除 INTERNET 权限——系统级保证纯本地播放、零联网
需求背景
已厘清 INTERNET 权限的来源:Capacitor 默认设计是联网加载页面(支持 server.url
远程加载 / live-reload 热更新),模板无条件带入 android.permission.INTERNET——
与本应用 JS 是否发请求无关(权限是静态清单声明,不看运行时行为)。
核实结论(本版动前的勘察):
- 权限声明在
android/app/src/main/AndroidManifest.xml(cap add android一次性拷入,
cap sync不重写); capacitor.config.ts未配server.url,页面经 WebView 进程内 scheme 拦截
(https://localhost/_capacitor_file_)加载,不出网卡;- grep 全量
www/JS:仅有的fetch全是本地 URL(音轨 blob/content://、FFmpeg wasm
本地文件),零远程请求; - 本地播放、SAF 选歌、转码、媒体通知均不依赖该权限。
→ 结论:这条权限是「纯预留」,删除后系统将强制禁止本应用建立任何网络连接,
作为隐私卖点保留;未来加在线功能(歌词抓取/在线流/检查更新)或需 live-reload 调试时,
在 Manifest 注释处加回一行即可。
实现
AndroidManifest.xml:删除<uses-permission android:name="android.permission.INTERNET" />,
原位置留还原指引注释;android/app/build.gradle:versionCode 58 → 59,versionName 2.22.17 → 2.22.18;www/index.html:设置页版本号 v2.22.18。
验证(aapt 实测最终 APK)
aapt dump permissions app-debug.apk 输出(无 INTERNET,且无依赖库合并回注):
uses-permission: android.permission.READ_MEDIA_IMAGES
uses-permission: android.permission.POST_NOTIFICATIONS
uses-permission: android.permission.FOREGROUND_SERVICE
uses-permission: android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK
permission: com.musicplayer.app.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION(androidx 自动生成)
构建与归档
- cap copy →
assembleDebugBUILD SUCCESSFUL(20s,JDK 21)。 - 归档:
python _release/archive_release.py 2.22.18 59(source 快照 32.3MB + APK 本版/回滚基线 + 双 zip)。 - 回滚基线:
APK/app-v2.22.17-code58.apk(亦在<归档>/_apk_backup/)。
装机记录(待补)
adb push成功;pm install -r两次INSTALL_FAILED_ABORTED: User rejected permissions
(设备熄屏时弹窗弹不出直接秒拒;唤醒亮屏后 pm install 挂起等待设备端确认)。- 待在平板上确认厂商USB 安装弹窗后补装,并实测:
- 正常启动、扫描/播放本地歌曲(验证 scheme 拦截加载不受影响);
- 转码开关开启后 FFmpeg wasm 本地加载正常;
- 设置 → 关于,版本显示 v2.22.18;
- 系统设置 → 应用 → 权限,确认无「网络」相关项。
构建备忘
同 v2.22.13(cap copy → JAVA_HOME=<JDK 21> → assembleDebug
→ adb push /data/local/tmp + pm install -r)。
装机坑补充:厂商设备熄屏状态下 USB 安装确认弹窗不弹、直接秒拒
(INSTALL_FAILED_ABORTED: User rejected permissions);先唤醒亮屏再装。
MusicPlayer v2.22.17 (code 58)
MusicPlayer v2.22.17
- 版本号:versionName 2.22.17 / versionCode 58
- 日期:2026-09-16
- 本版主题:定位滚动改为「全过程可见 + 时长可自定义」(v2.22.16 实测反馈)
需求背景(v2.22.16 实测反馈)
v2.22.16 的定位按钮能定位,但距离大于 10 行就是瞬间跳转——看不到滚动过程,
既没有向上/向下的方向感,也看不到列表进度条移动,还是不知道跳到哪去了。
要求:
- 不管多远都慢慢滑过去,能看清是向上还是向下、进度条跟着走;
- 滚动时长可自定义:设置里加滑块 + 数字,1 ~ 15 秒。
实现
① 弃用 CSS scrollTo({behavior:'smooth'})
CSS smooth 的时长不可控(浏览器自定),且长距离会滚到数秒以上、短距离又太快。
改为自定义 rAF 动画逐帧写 scrollTop:
const ease = t => (t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t + 2, 2) / 2); // easeInOutQuad
playlistEl.scrollTop = start + delta * ease(p); // p = 已过时间/总时长- 逐帧改
scrollTop会照常触发scroll事件 → 驱动虚拟滚动渲染 → 列表与进度条实时可见地移动; - 落位闪烁仍挂在滚动停止回调(120ms)里,时序逻辑不变,另加
duration + 600ms兜底定时器。
② 时长规则
- 距离 ≤10 行:固定 0.6 秒(3 秒爬 10 行过于拖沓);
- 距离 >10 行:采用设置值,范围 1 ~ 15 秒,默认 3 秒。
③ 设置项「定位滚动 (Locate)」
- 设置面板新增滑块
set-locate-sec-slider(1~15,步进 1)+ 右侧数值set-locate-sec-val; - 存储:
appSettings.locateScrollSec,settingsVersion 5 → 6 并附迁移(非法值回退 3); - web-demo 同步持久化到
mp_appearance.locateScrollSec。
④ 可打断
pointerdown / wheel / touchstart 任一发生即 cancelAnimationFrame 取消动画,
用户中途触摸或滚轮可立刻接管滚动,不会跟用户抢。
同步范围
www/:index.html(设置组 + 版本号 v2.22.17)、player.js(rAF 动画 / 时长规则 / 设置绑定 / 打断 / v6 迁移)web-demo/:index.html(设置组 + 版本号)、demo.js(rAF 动画 + 滑块 + 持久化)_site_work/site/web-demo/:已cp+cmp -s逐字节校验一致
构建与装机
- cap copy →
assembleDebugBUILD SUCCESSFUL(15s,JDK 21)。 - 装机:Android 平板
pm install -rSuccess(保留数据,已核验 versionCode=58 / versionName=2.22.17)。 - 回滚基线:
APK/app-v2.22.16-code57.apk(亦在<归档>/_apk_backup/)。 - 新增归档工具:
_release/archive_release.py(python archive_release.py 2.22.17 58一条命令出 source/APK/两个 zip)。
待实测
- 距离远(如 1387 首跳到第 1000 首)时,是否能看到持续几秒的滚动过程与进度条移动。
- 设置里把时长拉到 1 秒 / 15 秒,滚动快慢是否随之改变。
- 距离 ≤10 行时是否快速到位(0.6 秒),不拖沓。
- 滚动途中用手指一碰,是否立即停下并交由手动控制。
- 落位闪烁是否仍然明显(v2.22.16 行为不变)。
构建备忘
同 v2.22.13(cap copy → JAVA_HOME=<JDK 21> → assembleDebug
→ adb push /data/local/tmp + pm install -r)。
MusicPlayer v2.22.16 (code 57)
MusicPlayer v2.22.16
- 版本号:versionName 2.22.16 / versionCode 57
- 日期:2026-09-15
- 本版主题:目录头新增「定位当前播放」按钮(长列表 / 随机播放场景定位)
需求背景(v2.22.15 实测反馈)
导入 1387 首音乐后:
- 随机播放时播放列表不跟随定位到当前曲目;
- 手动滑动很久才能找到,且快速滑动时看不清当前项的颜色变化(当前项仅有 2px 跑马灯细环),导致完全不知道播到哪一首。
实现
① 目录头新增定位按钮
- 位置:
音乐目录头栏右侧,「展开 ▾」与「+ 添加目录」之间(指定“加号左边”)。 - 样式:26×26 圆形、青色
#22d3ee,与.btn-add-dir同规格;内含靶心 SVG。
② 居中滚动(locateCurrentTrack)
target = idx * itemH - (viewH - itemH) / 2 // 居中
target = clamp(target, 0, playlistLen * itemH - viewH)- 距离 >40 行(≈2720px)用
behavior:'auto'直接跳(千首列表 smooth 会滚好几秒),
≤40 行用smooth;老 WebView 不支持 options 形式时回退scrollTop = target。
③ 落位后整卡闪烁 1.5s
.locate-flash关键帧:青色底 + 外扩光环脉冲两次后淡出,卡片边框同步高亮。- 时序要点:虚拟化列表滚动中会
container.innerHTML=''重建节点,闪烁类会丢,
因此挂在onPlaylistScroll的**滚动停止回调(120ms)**里,而不是滚动发起时。 - 兜底:目标位置与当前位置相同(不产生 scroll 事件)时,由 900ms(auto 时 400ms)
定时器强制触发一次闪烁。
④ 设计取舍:不做「切歌自动跟随」
历史结论(见 renderVisibleItems 注释):自动 scrollIntoView 会在用户手动浏览列表时
把列表强行拉回当前曲目。因此改为用户主动点击触发,不自动定位。
同步范围
www/:index.html(按钮 + 版本号)、style.css(按钮样式 +@keyframes locate-flash)、
player.js(locateCurrentTrack/flashCurrentPlaylistItem+ 按钮绑定 + 停稳回调钩子)web-demo/:同样三件套(全量渲染无虚拟滚动,直接scrollIntoView({block:'center'})+ flash)_site_work/site/web-demo/:已cp+cmp -s逐字节校验一致
构建与装机
- cap copy →
assembleDebugBUILD SUCCESSFUL(17s,JDK 21)。 - 装机:Android 平板
pm install -r覆盖安装 Success(保留数据)。 - 回滚基线:
APK/app-v2.22.15-code56.apk(亦在<归档>/_apk_backup/)。 - Android 手机 未连接,本版按钮在手机横屏下的表现待验证。
待实测
- 随机播放中途点定位按钮:当前曲目是否居中且闪烁明显可见。
- 距离极远(>40 行,如 1387 首跳到第 1000+ 首)时是否瞬间跳转而非长时间滚动。
- 连点两次是否每次都重新闪烁(强制重排重挂动画)。
- 未播放任何歌曲时点击,是否提示「当前没有正在播放的歌曲」。
- 回归:目录折叠、+ 添加目录、炫彩分区等 v2.22.13~15 内容不受影响。
构建备忘
同 v2.22.13(cap copy → JAVA_HOME=<JDK 21> → assembleDebug
→ adb push /data/local/tmp + pm install -r)。
MusicPlayer v2.22.15
MusicPlayer v2.22.15(versionCode 56)
Android 安装包 + 完整源码 + Web 演示站。跨平台本地音乐播放器(Capacitor 8 + Electron 30),彩色渐变极简风格,一切皆可自定。
本版亮点:自定义程度大幅提升 + 手机横屏适配(v2.22.13 ~ v2.22.15 三版累积)
- 主界面元素显隐开关:歌曲名称 / 创作者 / 进度条 / 音量条 / 歌词五项独立开关(设置 → 主界面元素显隐)。
- 布局调节:设置内进入编辑态,顶栏上下拖调高度、侧栏左右拖调宽度,实时跟随、松手保存、一键恢复。
- 音乐目录折叠:目录超过 2 个自动折叠并显示「展开 N 个 / 收起」,≤2 个保持原样。
- 封面左右滑切歌:左滑下一首、右滑上一首,跟手 30% 位移、回弹动画,纵向滑动不误触。
- 炫彩文字分区开关 + 自定义颜色:顶栏区 / 主界面区 / 设置与弹窗区三分区独立开关;关掉某区炫彩后可填颜色编号(如 #ffaa00)改用自定义纯色。Web 演示版已同步全部以上功能。
- 手机横屏修复:右半区 UI 可完整滚动到底(歌词封顶 26vh + 控件压缩);播放列表滑动性能优化(rAF 节流 + 区间缓存,滑动期禁用重阴影过渡)。
- 横屏裁剪修复(v2.22.15):自定义图片背景的裁剪候选框首开 4×4 无法拖动的时序 bug(隐藏态量尺寸为 0);裁剪盒在矮视口不再溢出屏幕、确认按钮可点;裁剪比例改用视口实测宽高比。
Assets 说明
app-v2.22.15-code56.apk— Android APK(arm64-v8a,Android 16 平板实测)MusicPlayer-source-v2.22.15.zip— 完整源码快照(android/www/web-demo + 根三件套,不含 node_modules)MusicPlayer-website-v2.22.15.zip— Web 演示站(首页 + 全功能演示播放器)
MusicPlayer v2.22.12
MusicPlayer v2.22.12
- 版本号:versionName 2.22.12 / versionCode 53
- 日期:2026-09-09
- 反馈问题(v2.22.11 实测):
- 锁屏/通知栏暂停 → 通知整个消失,需回应用手动播放才重新出现。
- 小窗展开后关闭 → 不在后台;播放中关闭后残留,只能长按应用→应用信息→强停。
- 原子岛仍未上(需用户确认已开「显示原子通知」开关)。
根因与修复
① 暂停通知消失(Java 原生,根因)
- 根因:
update()原逻辑"暂停 →MediaControlService.stopIfRunning()"摘除前台。
部分厂商 ROM 会清理"暂停且无前台服务"的媒体通知。此前 v2.22.10 靠 JS 每 500ms
轮询重推把通知"续命"顶回来 → 表现为"收起→重弹"闪烁;v2.22.11 暂停跳过轮询后
没人顶 → 通知被系统清掉即彻底消失。两版同一病根,v2.22.11 只是暴露了它。 - 修复:前台媒体服务生命周期跟随"媒体会话"而非"播放/暂停"——
update()无论
播放暂停都MediaControlService.start()(幂等),服务从首次播放一直保持到真正停止
(hide()/ 退出)。暂停态通知成为系统级常驻前台媒体通知(网易云等标准行为):
不被清理、无 ongoing 翻转、不闪烁。pushNow()(通知授权回调)同步为"有内容即保
持前台"。JS 端 v2.22.11 的"暂停跳过 500ms 轮询"保留(现在无需续命,纯省电)。
② 小窗关闭残留(Java 原生)
- 根因:音频在 WebView,Activity 被销毁(小窗 × / 划掉任务 / 返回退出)即播放
终止,但MediaControlService(前台服务)无人通知停止 → 通知/服务残留,
只能去应用信息强停。 - 修复:
MainActivity.onDestroy()中isFinishing()为 true 时调用新增的
MediaSessionPlugin.cleanupForExit():停服务 + 撤通知 + session 停用,干净退出。
Home 键回桌面仅 onStop 不销毁,后台播放不受影响。
装机
- Android 平板,
pm install -r覆盖安装 Success(保留数据)。 - dumpsys 确认 versionCode=53 / versionName=2.22.12。
待实测
- 播放 → 暂停(通知栏按钮 & 锁屏):通知是否不再消失、不再"收起→重弹"。
- 小窗:播放中划小窗 → 展开 → 关闭:是否干净退出(通知撤、播放停),无需强停。
- 原子岛:**设置→通知与状态栏→原子岛→开「显示原子通知」**后播放,是否上岛
(v2.22.12 起播放/暂停都保持前台媒体服务,上岛条件比以往更充分)。
MusicPlayer v2.14.0 (Android APK)
MusicPlayer 三端音乐播放器 v2.14.0 发布。
本 Release 提供安卓安装包 MusicPlayer-v2.14.0.apk(约 43.7 MB)。
安装方式:
- 下载 APK 到安卓设备;
- 在系统设置中允许「未知来源」安装;
- 点击 APK 完成安装。
默认界面标题:MusicPlayer-一切皆可自定。源码以 MIT 许可证开源,位于仓库主分支。