Repository navigation
添加本地歌曲扫描 - #1
添加本地歌曲扫描#1
Conversation
播放控制优化: - 播放位置从墙钟时间改为采样计数,与实际音频输出精确同步 - 主进程轮询(33ms setInterval)改为 Rust 事件驱动(ThreadsafeFunction) - 状态变化/播放结束由 Rust 主动推送,位置更新 200ms 一次 - 前端 rAF 插值在两次同步之间保持 60fps 流畅进度条 - fade 渐变从阻塞式改为非阻塞异步线程,Mutex 持有时间从 200ms 降到微秒级 - position/status 事件分离,避免竞态覆盖播放状态 扫描优化: - 新增 lofty 依赖,扫描时用纯 tag 解析替代 FFmpeg probe - 扫描速度从 ~2s/文件 降到 ~20ms/文件 工程规范: - Cargo workspace 统一 [profile.release] 和 [lints.clippy],消除构建警告 - 全 workspace cargo clippy pedantic 零警告 - 数据库文件移至 userData/Database/ 子目录 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
该 PR 主要为应用新增“本地音乐库”能力:支持配置扫描目录、后台扫描提取元数据并落库、在前端展示/搜索/右键菜单播放;同时补充“音量均衡(响度归一化)”配置,并对部分 UI 组件进行能力增强以支撑新页面交互。
Changes:
- 新增本地音乐库:扫描目录管理、扫描进度广播、SQLite 持久化、Library 页面与 SongList 列表/菜单交互。
- 新增音量均衡(响度归一化):前端设置项 + IPC + Rust 音频引擎实时响度/ReplayGain 支持。
- UI 基础组件增强:DropdownMenu/ContextMenu、VirtualList empty/header、Input 圆角等。
Reviewed changes
Copilot reviewed 55 out of 60 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/time.ts | 抽出通用时间格式化工具供播放器/列表复用 |
| src/utils/format.ts | 新增文件大小格式化工具供本地库列表展示 |
| src/stores/library.ts | 新增 library store:曲目/目录加载、扫描控制、进度订阅 |
| src/settings/schema.ts | 新增 loudnessNormalization 设置项 |
| src/router/index.ts | 新增 /library 路由入口 |
| src/pages/Library.vue | 新增本地音乐库页面:扫描、目录管理、搜索、播放全部 |
| src/pages/Home.vue | 精简 Home 测试页内容与标题调整 |
| src/layouts/MainLayout.vue | 顶栏样式调整(去除底边框) |
| src/layouts/components/SideBar.vue | 侧边栏新增“本地音乐”入口 |
| src/layouts/components/NavHeader.vue | 顶部新增下拉菜单:主题切换/重载/DevTools/全局设置 |
| src/i18n/locales/zh-CN.json | 新增 library/songList/nav 相关中文文案与设置项文案 |
| src/i18n/locales/en-US.json | 新增 library/songList/nav 相关英文文案与设置项文案 |
| src/core/player.ts | 播放器核心:新增 togglePlay/playNow;playFrom/insertToQueue 行为调整;初始化应用归一化配置 |
| src/composables/useTrackMenu.ts | 新增歌曲右键/下拉共用操作菜单(播放/下一首/显示/复制路径) |
| src/components/ui/SVirtualList.vue | 支持 empty/header 插槽与可选禁用位移动画 |
| src/components/ui/SInput.vue | 支持 round 外观与输入高度调整 |
| src/components/ui/SImg.vue | fallback 改为打包资源导入(避免硬编码路径) |
| src/components/ui/SDropdownMenu.vue | 新增通用下拉菜单组件(Reka UI) |
| src/components/ui/SDialog.vue | 标题/描述布局微调以统一间距 |
| src/components/ui/SContextMenu.vue | 新增通用右键菜单组件(Reka UI) |
| src/components/player/FullPlayer/PlayerBar.vue | 复用 formatTime 工具;播放按钮改用 player.togglePlay |
| src/components/player/FullPlayer/PlayerBackground.vue | 默认封面改用打包资源导入 |
| src/components/player/FullPlayer/index.vue | 复用 formatTime 工具;播放按钮改用 player.togglePlay |
| src/components/list/SongList.vue | 新增通用歌曲列表:虚拟列表 + 搜索 + 右键菜单 + 音质/大小展示 |
| src/assets/images/song.jpg | 新增默认歌曲封面资源 |
| shared/types/settings.ts | SystemConfig 增加 library 配置;PlayerSettings 增加 loudnessNormalization |
| shared/types/player.ts | Track 扩展本地文件字段;PlayerApi 增加 setNormalizationEnabled 并移除 probe |
| shared/types/library.ts | 新增 LibraryApi 与 ScanProgress 类型 |
| shared/defaults/settings.ts | 默认配置增加 loudnessNormalization 与 library.scanDirs |
| public/fonts/font.css | logo 字体路径调整为根路径引用 |
| pnpm-lock.yaml | 锁文件更新:引入 better-sqlite3 及相关依赖 |
| package.json | 引入 better-sqlite3 / @types;pnpm onlyBuiltDependencies 增加 better-sqlite3 |
| native/media-ctrl/src/model.rs | Debug 输出补充 cover_url 字段 |
| native/media-ctrl/Cargo.toml | lints 配置切到 workspace 统一管理 |
| native/audio-engine/src/shared.rs | Shared 增加归一化增益/开关的原子字段与访问方法 |
| native/audio-engine/src/scanner.rs | 新增目录扫描器:WalkDir + FFmpeg probe + 增量比对 + 批量回调 |
| native/audio-engine/src/player.rs | 播放器内部同步归一化开关;seek 逻辑调整;输出流字段命名调整 |
| native/audio-engine/src/metadata.rs | 抽出 stream info/tags/replaygain/thumbnail 工具供 scanner/decoder 复用 |
| native/audio-engine/src/loudness.rs | 新增实时响度分析器(RMS)用于无 ReplayGain 的动态增益 |
| native/audio-engine/src/lib.rs | 暴露 set/get_normalization;新增 scan_dirs/cancel_scan N-API 接口;静默 FFmpeg 日志 |
| native/audio-engine/src/decoder.rs | 接入 ReplayGain 与实时响度;seek API 签名调整;复用 metadata 工具 |
| native/audio-engine/Cargo.toml | 引入 walkdir;lints 配置切到 workspace |
| electron/preload/index.ts | 暴露 system/library API;player 增加 setNormalizationEnabled |
| electron/preload/index.d.ts | Window.api 类型补齐 system/library |
| electron/main/utils/nativeLoader.ts | loadNativeModule 改为 const 泛型箭头函数写法 |
| electron/main/utils/logger.ts | 新增 libraryLog scope |
| electron/main/utils/config.ts | 缓存目录结构调整:app-cache/covers |
| electron/main/services/scanner.ts | 新增主进程扫描编排:调用 Rust 扫描 + 批量 upsert DB + 进度广播 |
| electron/main/services/engine.ts | 抽出 native engine/player 单例管理;提供 setNormalizationEnabled 同步 |
| electron/main/services/database.ts | 新增 SQLite 库:建表/索引、增量记录、upsert/search/delete |
| electron/main/ipc/system.ts | 新增 IPC:toggleDevTools / showInExplorer |
| electron/main/ipc/player.ts | 移除 probe IPC;接入 engine.ts;新增 setNormalizationEnabled IPC;事件类型改用 JsPlayerEvent |
| electron/main/ipc/library.ts | 新增音乐库 IPC:扫描/目录管理/曲目查询与搜索 |
| electron/main/ipc/index.ts | 注册 library IPC handlers |
| electron/main/ipc/config.ts | 配置变更副作用:同步 loudnessNormalization 到 native player |
| electron/main/core/index.ts | 启动时 initDatabase;设置封面缓存 env;退出时 closeDatabase |
| electron-builder.config.ts | 打包 files 规则调整(包含 public/out,排除更多源目录) |
| components.d.ts | 自动组件声明补齐:ContextMenu/DropdownMenu/SongList 与新图标 |
| Cargo.toml | workspace 统一 lints/profile 配置 |
| Cargo.lock | Rust 依赖锁:audio-engine 引入 walkdir |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
src/components/ui/SInput.vue:43
SInput根节点移除了w-full,这会改变默认布局行为:例如SettingsSearch.vue中未显式指定宽度时,输入框可能收缩为内容宽度而不是铺满容器。若希望保持向后兼容,建议保留w-full(或至少通过 prop/默认 class 确保在常见场景下占满可用宽度)。
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** 右键菜单 */ | ||
| const contextIndex = ref(-1); | ||
| const contextTrack = computed(() => props.items[contextIndex.value]); | ||
| const { items: contextMenuItems, handleSelect: onContextMenu } = useTrackMenu(contextTrack); |
There was a problem hiding this comment.
contextTrack 基于 props.items[contextIndex] 取值,但 contextIndex 是从(可能已过滤的)列表渲染索引传入的。开启搜索过滤后,右键菜单会指向错误的歌曲(header/操作都会错)。建议让 contextTrack 基于 filteredItems 取值,或在设置 contextIndex 时保存实际 track 引用/ID。
| d.seek(position_secs); | ||
| true |
There was a problem hiding this comment.
seek_ok 现在只要 decoder_data 存在就恒为 true(DecoderData::seek 已改为返回 (),且内部 seek 失败也被吞掉)。这会导致 FFmpeg seek 失败时不再触发回退到 load() 的逻辑,用户可能看到 seek 不生效/卡住且没有恢复路径。建议让 DecoderData::seek 返回 Result<()>/bool 并在此处据此决定是否回退。
| d.seek(position_secs); | |
| true | |
| // 使用 DecoderData::seek 的返回值判断是否 seek 成功 | |
| d.seek(position_secs).is_ok() |
| onScanProgress: (callback: (progress: unknown) => void) => { | ||
| ipcRenderer.removeAllListeners("library:scanProgress"); | ||
| const handler = (_event: Electron.IpcRendererEvent, data: unknown): void => { | ||
| callback(data); | ||
| }; | ||
| ipcRenderer.on("library:scanProgress", handler); | ||
| return () => { | ||
| ipcRenderer.removeListener("library:scanProgress", handler); | ||
| }; |
There was a problem hiding this comment.
onScanProgress 内部使用 ipcRenderer.removeAllListeners('library:scanProgress') 会移除该 channel 上所有监听器(包括其它模块/窗口可能注册的监听),容易造成隐式耦合问题。建议仅移除当前订阅创建的 handler(或使用 once/维护订阅引用),避免全量清空。
|
@copilot 重新审查 |
已按“重新审查”处理并补充修复: Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
No description provided.