Commit Graph
107 Commits
Author SHA1 Message Date
lxh2875931338 c1e720311e feat(ui): 文件列表收藏红心 + 首页收藏绑定 favorites 表
交互调整(按讨论):
- WebDAV 文件列表:整行点击 = 播放(原来的行为),右侧播放按钮改成收藏红心
  已收藏实心红,未收藏空心;点击即写/删 favorites 表
- 首页"我的收藏"改为读 favorites 表 + songs 元数据;
  从文件列表返回时自动刷新
- 收藏项右侧红心改为真正删除(原来只是内存 removeAt)

修复:
- 收藏列表项透明度绑定 _listReveal,首屏为 0 导致条目完全透明
  (收藏成功但看不到)。收藏项改为固定 opacity 1.0。

验证(真机):
  列表点红心 -> favorites 表 +1 -> 返回首页 -> 收藏区出现该条目
2026-10-24 21:38:36 +08:00
lxh2875931338 4a8b1f28db feat(ui): 设置页第一版(缓存管理 / 诊断导出 / 连接状态 / 关于)
设置页此前没有任何入口,且内容全是占位。现在:
- 首页右上角菜单按钮 → 设置页
- 缓存:标签条数、封面数量/占用、临时探测文件占用,一键清理(保留歌单与收藏)
- 诊断:导出 版本/连接/缓存/当前播放/最近错误 到 txt,经 FileProvider 走系统分享
- 连接:显示 WebDAV 地址与用户,支持测试连接、退出登录
- 关于:版本、构建号、播放内核与 UI 许可说明
- 删除未被引用的 FullPlayerPage(与 player_page 重复)

真机验证:设置页正常打开,诊断导出成功调起系统分享面板。
2026-10-24 21:28:22 +08:00
lxh2875931338 d07086aaa0 feat(ui): 歌曲列表改用封面图,替代统一的小图标
- 新增 SongArtwork 组件:内存字节 > 本地封面文件 > 图标兜底,圆角裁剪
- WebDAV 文件列表:前导图标换成封面(来自 songs.artwork_path)
- 播放列表页:批量查封面路径(getSongsByPaths),正在播放的歌优先用内存里的封面字节;
  切歌后重新拉一次,保证新补全的封面能显示
- 没有封面的歌仍回退为音符/播放图标,不影响列表结构
2026-10-24 20:47:12 +08:00
lxh2875931338 0a1ebfa5df feat(ui): WebDAV 文件列表改用元数据标题/艺术家显示
之前列表只显示文件名,即使 DB 里已经有标签。
- SongDatabase 新增 getSongsByPaths():一次批量按路径查(分块 400,避开变量上限)
- WebDAVFileListPage:
  * 加载目录后批量取元数据,title/artist 有值就替换文件名
  * 副标题显示 "艺术家 · 大小",没有标签时仍显示大小
  * 监听 AudioService,正在播放的歌补全元数据后立即刷新对应行
  * 没有标签的新歌自然回退文件名,不影响使用
2026-10-24 20:41:21 +08:00
lxh2875931338 18308d9d4e feat(metadata): 元数据分阶段探测(128KB → 512KB)+ song_key 改为纯 URL
渐进探测:
- FileProvider.getFile 增加 maxBytes 参数,WebDAV 按需只取头部
- MetadataService 先取 128KB 读标签;title/artist 都为空时再补到 512KB
- 缓存文件按上限区分(metadata_<hash>_128k.part / _512k.part)

song_key:
- 从 md5(url|size) 改为 md5(url)。分阶段探测的大小不同,
  带 size 会让同一首歌产生两个键;URL 在各阶段稳定。
- 代价:同 URL 换内容不自动失效,由清除单曲缓存/forceRefresh 兜底
- 数据库升到 v6,清空 songs / metadata_cache(可再生)

验证(release,真机):
  📦 取头部 128KB(上限 128KB,不再整文件下载)
  🧭 状态机: first_seen → pending(validations=1)
  📊 pending conf=0.92 evidence={...}
  ✅ saved: 两生关 - 谢安琪

测试:新增 song_key 3 个用例,累计 19 个全绿。
2026-10-24 20:28:50 +08:00
lxh2875931338 b8138ad125 feat(metadata): 元数据引擎第 3 步之一——一致性与冲突感知的状态机
之前只要 confidence>=0.6 且 validation_count>=2 就升级 verified,
两次读到不同结果也会被当成"验证通过",错误标签一旦写入很难纠正。

新增 MetadataStability(纯函数,可单测):
- 首次见到 → pending(first_seen),次数 1
- 与上次记录标题/艺术家冲突 → pending(conflict),次数重置为 1
- 一致且次数 >= 2、置信度达标 → verified(stable)
- 置信度不足 → pending(low_confidence),次数继续累计
- 字段缺失不算冲突;比较时忽略大小写与首尾空格

metadata_service 改为调用该状态机,并打印判定原因:
  🧭 [MetadataService] 状态机: stable → verified(validations=2)

测试:新增 test/metadata_stability_test.dart 7 个用例,
连同原有 9 个共 16 个,flutter test 全绿。
已构建并安装 release 验证。
2026-10-24 20:24:55 +08:00
lxh2875931338 9f53d1c604 fix(playback): 打开失败后 1.5s 立即重试,切歌前摇从 ~20s 降到 ~7s
实测时间线(20:08:53 那次切歌):
  53.881 next -> play
  53.947 open 完成 65ms
  59.525 player error: tcp ffurl_read / Failed to open   (mpv 5.6s 后放弃)
  09:13.801 起播看门狗才触发重连                        (又等 14s)

原因:看门狗 5s 一跳、阈值 10s,最坏要两个 tick 才触发。

- error 流里识别 Failed to open / ffurl_read,1.5s 后立即重试一次
  (每首歌最多一次,force 模式跳过节流)
- 进度看门狗 tick 10s -> 5s,任何卡死最多多等 5s

预期:失败发生在 5~6s,重试在 7~8s 开始,前摇 ~10s 降到 7~8s;
若首次打开成功则完全无感。
2026-10-24 20:14:31 +08:00
lxh2875931338 98db2321fb fix(playback): 冷启动不再预加载音频,首次播放按保存进度打开
按今日日志定位:所谓"后台假死"其实是进程被系统回收
(13:14:46 ActivityManager: has died: prcp FGS,当时内存压力下批量杀进程)。
冷启动后旧代码会先联网 play→seek→pause,既慢又会和用户点播放打架。

- restorePlaybackState: 只恢复队列/进度/UI,不联网,不再调用 _loadRestoredPlayback
- 新增 playOrResume(): UI 按钮、通知栏、耳机键统一入口;
  冷启动首次播放走 _playCurrent(open + seek 到保存位置 + play)
- AudioPlayerHandler.play() 改走 playOrResume()
- 播放中每 10s 持久化一次进度,冷启动恢复更准确
- 窗口期收紧:网络轮询 10s→3s、切换去抖 3s→2s、
  起播看门狗 15s→10s、中段卡死 30s→20s

真机验证(release):
  冷启动 8s 内 play 调用 0 次(不再预加载)
  按键播放:冷启动首次播放 → open 21ms → restored position 15.751s → 1.5s 出声
2026-10-24 20:04:42 +08:00
lxh2875931338 ff41769c56 build(android): 接入正式签名配置 + 版本号改为 1.1.0-dev.1 (2)
- app/build.gradle.kts 读取 android/key.properties(不存在时回退 debug keystore)
- .gitignore 忽略 key.properties / keystore / *.jks
- pubspec: version 1.1.0-dev.1+2

注意:key.properties 与 keystore 文件由开发者本地生成,不入库。
生成后 flutter build apk --release 即为正式签名包。
2026-10-24 00:11:53 +08:00
lxh2875931338 30111a5f0c feat(metadata): 元数据引擎第 2 步——置信度模型 + pending/verified 状态机
之前 evaluate() 写死 0.8、decide() 直接透传,history 参数没人用,
"自学习"只有骨架。这次补上:

置信度(MetadataNormalizer.evaluate)
- 字段来源可追溯:NormalizedMetadata 新增 titleFromTag / artistFromTag / albumFromTag
- 基础分 0.25;标题 +0.20、标题来自标签再 +0.10(文件名当标题则 -0.10)
- 艺术家 +0.15、来自标签 +0.10;专辑 +0.10;年份 +0.03;封面 +0.02
- 历史一致性:同艺人 >=2 条 +0.05,同专辑 >=2 条 +0.03
- 结果 clamp 到 0.05~0.98,且每个加分项写入 evidence,便于日志解释

状态机(MetadataService)
- 只有 confidence>=0.6 且 status=verified 的记录才直接命中
- pending 记录下次播放会重新读取并合并(自修复)
- validation_count 累加;>=2 次且置信度达标才升级为 verified
- 新结果置信度低于旧结果时保留旧字段,只累加验证次数
- 日志输出 status / confidence / validation_count / evidence

测试
- 新增 test/metadata_normalizer_test.dart,9 个用例覆盖来源标记、分级、
  历史加权、证据、阈值;flutter test 全绿

真机验证
- 起播看门狗在真实弱网下触发:23:44:50 打开失败 → 15s 后自动重连 →
  1.4s 出声(open 仅 100ms)
2026-10-23 23:45:29 +08:00
lxh2875931338 a5849969d3 perf(metadata): 元数据探测从 2MB 降到 512KB
1Panel 访问日志显示:2MB 的探测请求在当前链路上要 10~18 秒,
期间 libmpv 的 206 吞吐被压到 ~1Mbps,直接拖慢起播。

- _maxBytes 2MB -> 512KB:常见标签与封面都在此范围内
- 读不到标签时仍由 80% 重试兜底
- 日志文案同步更新

另:访问日志 CSV 属于本地参考,加入 .gitignore
2026-10-23 23:16:07 +08:00
lxh2875931338 af895c18e1 fix(metadata): 元数据回退到文件名时,在 80% 处重试一次
云服日志显示:对 /dav/.../*.flac 的 Range GET 在 10.3s 后返回 502,
是反代等待上游首包超时(清远源站响应慢),与我方 15s 超时无关。
影响仅限该曲的标签/封面,播放不受影响,但会一直回退文件名。

- _loadMetadataForCurrentSong: source == 'filename' 时置 _metadataNeedsRetry
- 播放到 80% 且该曲未重试过 → 再抓一次(每首歌最多一次)
- 因"播放未稳定"被跳过抓取的情况也标记为待重试
- 换歌时清空标记
2026-10-23 23:11:39 +08:00
lxh2875931338 2d571eaae4 feat(playback): 下一首预热(60% 时 HEAD 预握手)
日志实测:open 完成仅 37~60ms,但起播耗时 2.1~2.8s,
说明等待发生在 mpv 拿首批数据,而不是建连。

- 新增 PrefetchService:HEAD + Range 0-0,只走 DNS/TLS/反代,
  不下载正文;失败静默,不影响播放
- AudioService 在进度达到 60%(且时长 > 60s)时预热下一首,
  每首歌只触发一次;shuffle 模式下按当前随机索引推算

待验证:播到 60% 时应出现
  🔥 [AudioService] 预热下一首:xxx
  🔥 [Prefetch] 预热完成 xxxms,status=200/206
对比下一首的 "起播耗时" 是否下降。
2026-10-23 23:06:18 +08:00
lxh2875931338 111369c20c feat(playback): 起播看门狗 + 起播耗时打点
现象(Plastic Love):state=PLAYING、position 恒为 0,75 秒内没有任何
buffering/error 日志,mpv 既不报错也不推进。通用看门狗要求"近期有缓冲",
因此完全没反应。

- 新增起播看门狗:position 持续为 0 超过 15s 且 playing=true → 强制重连
  (不再要求 buffering 事件;暂停时 playing=false,不会误触发)
- 中段卡死判定也放宽为 playing || recentlyBuffering
- 打点:
  * PlaybackService: "open 完成 xxxms,play 完成 xxxms"
  * AudioService: "起播耗时 xxxms(open 完成 -> 出声)"
  下次再出现"下一首很久",日志能直接区分是 open 慢还是数据到得慢。
2026-10-23 21:55:13 +08:00
lxh2875931338 b517c75cdd feat(playback): 网络切换后自动重连当前歌曲
mpv 的 HTTP 长连接不会跟随网络迁移:换 Wi-Fi / Wi-Fi<->移动数据
会换掉出口 IP,旧连接失效但不一定报错,表现为进度停住、一直缓冲。

- 新增 NetworkWatcher:每 10s 枚举 IPv4 接口,签名变化即视为网络切换
  (不依赖插件和额外权限,也能覆盖"同类型网络换 AP"的情况)
- AudioService 收到变化后 3s 去抖,然后强制重连当前歌曲并 seek 回原位置
  (force 参数跳过 2 分钟节流,因为这是明确的外部事件)

真机验证(公网链路 + 移动数据):
  21:43:23.123  检测到网络变化
  21:43:26.124  主动重连(3s 去抖)
  21:43:26.289  自动恢复完成(165ms)
  随后 position 35.5s -> 41.7s 正常推进
2026-10-23 21:44:55 +08:00
lxh2875931338 937bcdd63d fix(playback): 缓冲抖动下看门狗失效 + 元数据改到播放稳定后再抓
实测(公网链路,自然播完 -> 自动下一首)暴露两个新问题:

1. 看门狗在 buffering 抖动时永远不触发
   原实现每次 buffering=true 都重置 30s 计时器,而弱网下它会 true/false
   每 2~5s 抖一次,于是计时器一直被重置,卡死 60s+ 也不会自愈。
   改为记录"最近一次缓冲时间",用「进度 30s 未推进 + 20s 内发生过缓冲」
   两个条件判定,暂停时不会误触发。

2. 元数据抓取在起播瞬间抢带宽
   新歌 open 后 mpv 还在持续填缓冲,此时插入 2MB 标签下载会把缓冲打回 0。
   实测下一首因此要等 60s+。
   改为等播放真正稳定(进度 >= 5s 且连续 2s 不在 buffering,最多等 30s)
   再抓元数据;等不到就跳过本次(下次播放再试),优先保证出声。

验证:公网 + 移动数据下切歌,next 发起后 2ms 即 open;
未稳定时日志出现"播放未稳定,跳过本次元数据抓取",不再有 2MB 下载与播放抢带宽。
2026-10-23 21:35:40 +08:00
lxh2875931338 2b33c17aab fix(playback): 增加进度看门狗,兜住不产生 buffering 事件的半开连接
公网链路实测(清远->华为云->香港CN2->openresty->OpenList HTTPS)中
出现过 tcp: ffurl_read 错误但未触发 buffering=true 的情况,
仅靠缓冲事件无法覆盖这类"连接半死"的卡顿。

- 每 10s 检查一次播放进度;播放中且 30s 未推进 → 触发自愈(重开 + seek 回原位)
- 切歌中/暂停时重置基准,避免误触发
- 与 buffering 看门狗共用 2 分钟节流

验证:公网路径连续播放 65s,无 false positive,进度正常推进。
2026-10-23 21:18:02 +08:00
lxh2875931338 9762569832 chore(tools): device-check 兼容 Flyme 解冻,重试时延长等待 2026-10-23 21:09:40 +08:00
lxh2875931338 9e58d52678 fix(playback): 修复切歌永久假死,并让元数据抓取不再抢播放带宽
问题(后台播一段时间后无法缓冲,点下一首也没反应,只能重启):
1. _isChangingTrack 是没有超时的锁:_playCurrent() 置 true 后,
   只有走完 finally 才会清。mpv 的 open() 在弱网下可能挂很久,
   锁就一直不释放,之后 next()/previous() 全被静默拒绝。
2. 元数据用 dio.download 整文件下载,和 mpv 流式播放抢带宽,
   20Mbps FRP 下播放流只剩一两百 K/s,缓冲长期为 0。
3. 没有订阅 mpv 的 error/buffering,失败无感知、无法自愈。

改动:
- playback_service: open 20s / 控制命令 5s 超时;失败时 stop 回空闲并上抛异常
- audio_service:
  * 去掉「正在切歌就拒绝」的逻辑,改为 latest-wins(epoch 校验,
    旧请求自行放弃,只有最新请求解锁)
  * 播放失败不再吞掉:记录 lastPlaybackError 并 notifyListeners
  * 订阅 stream.error / stream.buffering
  * 缓冲超过 30s 自动重连当前歌曲并 seek 回原位置(2 分钟内最多一次)
- file_provider: 元数据改为 Range 只取头部 2MB,流式写入并在切歌时取消上一请求
- home_page: 去掉重复的 PlaybackService().play()(同一首歌会被 open 两次),
  登录校验提前

验证:dart analyze 无 error;gradlew assembleDebug BUILD SUCCESSFUL。
真机验证待设备重新连接后进行。
2026-10-23 21:04:45 +08:00
lxh2875931338 eab4a12ccc chore: 忽略本地个人笔记 bug-todo.txt 2026-10-23 12:33:31 +08:00
lxh2875931338 46a9add5cb build+fix: debug 包独立为 .dev,并修复元数据缓存永远查不到
包名:
- debug 使用 com.lxh.qingting_player.dev(应用名"清听 Dev"),可与 release 并存
- release 保持 com.lxh.qingting_player
- Android 包名每一段必须以字母开头,因此用 .dev 而非 _dev
- AGP 9 需显式开启 buildFeatures.resValues;移除 manifest 中已废弃的 package 属性

元数据(两个独立原因叠加):
1. song_key 之前用 String.hashCode / 本地临时文件 mtime,都不稳定;
2. songs.remote_path 存的是完整 URL,而所有查询都用相对路径(song.id),
   getSongByPath 永远 miss,UI 只能回退到文件名。

- 新增 utils/song_key.dart:songKeyFor(url,size)=md5(url|size),跨进程稳定
- metadata_service: remote_path 改存 fileId(相对路径),并改用稳定 song_key
- audio_service / file_provider: 同步使用稳定键
- 数据库升级 v5:清空 songs / metadata_cache(可再生缓存;歌单与收藏按路径存储,不受影响)
- device-check.ps1: 支持 -Package,默认校验 dev 包,启动组件改为向 PackageManager 解析

验证:两次独立 dart 进程 song_key 输出一致;真机 debug/release 并存安装,单实例自检通过。
2026-10-22 23:47:32 +08:00
lxh2875931338 87011c7adc 多实例问题已修复,代码已合并到main分支,release打包ing~ 2026-10-22 23:23:51 +08:00
lxh2875931338 6445ee94bf fix(playback): 播放中不再向系统上报 idle,修复播放设备页显示未在播放
audio_service 把 idle 映射成 PlaybackState.STATE_NONE,之前用 duration>0 判断,
时长尚未解析出来时会一直上报 NONE。改为 playing || duration>0 即视为 ready。

设备验证(MEIZU 21 Pro / Android 16):
  播放 -> state=PLAYING(3),暂停 -> state=PAUSED(2),不再出现 NONE(0)。

同时加固 device-check.ps1:确认进程真正退出后再抓冷启动日志,最多重试两次。
2026-10-22 22:57:34 +08:00
lxh2875931338 6f9fe6262d chore(tools): 增加真机自检脚本 device-check.ps1
一键完成:安装 debug APK → 冷启动 → 抓 logcat → 校验单实例
(PlaybackService / serviceId / Player / MediaSession 各只允许 1 个),
并检查崩溃与 libmediakitandroidhelper.so 加载失败。
2026-10-22 22:46:51 +08:00
lxh2875931338 2252b72a14 build(android): 修复模板覆盖导致的打包失败
- 恢复 versionCode/versionName 取自 pubspec(模板硬编码为 1/1.0.0)
- extractNativeLibs 从 Manifest 移到 packaging.jniLibs.useLegacyPackaging(AGP 9 要求)
- 恢复 kotlin.incremental=false / kotlin.parallel.tasks.in.project=false(模板覆盖时丢失)

已用 gradlew assembleDebug 验证:BUILD SUCCESSFUL,APK 内每个 ABI 仅一份 libmpv.so + libmediakitandroidhelper.so。
依赖解析已用 :app:dependencies --refresh-dependencies 验证可直连 Maven Central,无需镜像。
2026-10-22 22:40:45 +08:00
lxh2875931338 083a1612d5 chore: 停止跟踪构建日志与 IDE 生成文件 2026-10-22 22:14:20 +08:00
lxh2875931338 b62af9648b snapshot: 存档 flutter create 模板覆盖后的现场,作为多实例修复前的基线 2026-10-22 22:04:57 +08:00
lxh2875931338 56c26e2fd5 snapshot: 存档 flutter create 模板覆盖后的现场,作为多实例修复前的基线 2026-10-22 21:59:09 +08:00
lxh2875931338 40d4af322b 开始进入修复阶段 2026-09-02 21:42:42 +08:00
lxh2875931338 1aba004e43 定住这个commit,切换至test分支进行部分破坏性变更:涉及到app内多实例异常问题修复 2026-09-02 21:17:59 +08:00
lxh2875931338 cdf77b308d 开始给原先适配期的状态擦屁股:现在是修复播放页与播放列表页的显示 2026-09-02 20:44:46 +08:00
lxh2875931338 200067a3b2 回归GPL主线的第一版本,后续LGPL仍会继续测试,但是将不会在主分支加入测试 2026-09-02 20:23:06 +08:00
lxh2875931338 974dee2995 revert 99917bac87
revert Core Freeze:核心链路功能已经完成设计,后续进入beta功能开发模式,接下来会在UI部分微调后,进入首个Release版本(1.1.0-Release)
2026-09-02 10:15:54 +08:00
lxh2875931338 b86a5d2b46 准备进入新的测试阶段,先做一个snapshot(此版本下不可用) 2026-08-31 20:51:09 +08:00
lxh2875931338 eb46c53f9a 不考虑上架问题,确认此次更新 2026-08-31 00:05:13 +08:00
lxh2875931338 53f60d393f Merge remote-tracking branch 'origin/test' 2026-08-30 23:57:44 +08:00
lxh2875931338 08ea8a4508 test通道确认功能完整
libmpv.so(LGPL版本)
SHA256 = E56776A1BA03892C9A037A534A7114BA54F6EE202083A706CF878924BAA9D7AA
2026-08-30 23:57:24 +08:00
lxh2875931338 abffb77167 主通道同步进行更新版本号 2026-08-30 21:56:32 +08:00
lxh2875931338 21a1ad1d05 确定这次重构的版本号 2026-08-30 21:55:51 +08:00
lxh2875931338 02ca590c65 LGPL库引入后导致app整体处于全面重适配状态,将会处于长期不可用状态 2026-08-30 21:51:19 +08:00
lxh2875931338 e102e07378 更新一套LGPL版核心库,进入底层变更适配期 2026-08-30 20:53:52 +08:00
lxh2875931338 99917bac87 Core Freeze:核心链路功能已经完成设计,后续进入beta功能开发模式,接下来会在UI部分微调后,进入首个Release版本(1.1.0-Release) 2026-08-28 22:59:04 +08:00
lxh2875931338 86b1d0df42 正在播放的自滑动刹车动画动作优化完成,同时调整播放页进入动作 2026-08-28 22:45:14 +08:00
lxh2875931338 2e55f94df2 优化正在播放的自滑动刹车动画动作中 2026-08-28 22:27:44 +08:00
lxh2875931338 fa396ce681 播放列表持久化设计、播放列表点击显示当前播放歌曲的列表内位置 2026-08-28 22:16:55 +08:00
lxh2875931338 f91846be9a 优化下一曲检测时长,窗口放宽到800ms 2026-08-28 20:52:03 +08:00
lxh2875931338 bb74cd1351 顺序播放跳曲与首次启动跳曲问题的metadata部分修复完成,测试已通过 2026-08-27 23:27:05 +08:00
lxh2875931338 6cb1167a21 顺序播放跳曲与首次启动跳曲问题已部分修复,播放逻辑正确,metadata部分仍需修复 2026-08-27 23:18:40 +08:00
lxh2875931338 9fec9b98c5 现在正在修复首次启动点击跳曲问题与顺序播放的跳曲问题 2026-08-27 22:54:18 +08:00
lxh2875931338 2a29e30940 修复表写入问题,同时增加对读库动作的log可视化 2026-08-27 22:32:38 +08:00