跳转到内容
⌂ 回到首页

ECHO Next 开发规则

参与 ECHO Next 开发?这些规则是边界和护栏。新功能、重构、插件接口、构建脚本和文档说明都应遵守。

安全边界、架构分层、元数据优先级、封面规则和测试要求。改代码前过一遍,能少踩很多坑。

  1. 安全稳定优先。任何会影响用户本地音乐文件、播放链路、授权状态、数据库迁移、自动更新或远程服务的改动,都必须先说明风险,并优先选择可回退、可验证的实现。
  2. ECHO 是本地音乐播放器和合法扩展平台,不是破解、绕过授权、规避付费、绕过平台访问控制或获取侵权内容的工具。
  3. 明确禁止任何破解行为。ECHO 官方代码、插件接口、文档、示例、脚本、构建产物和支持流程不得提供、暗示、协助或鼓励破解软件、破解服务、破解音源、绕过 DRM、绕过账号/会员/地区/付费限制、伪造授权、篡改激活状态、移除水印、逆向第三方保护机制或分发侵权内容。
  4. 任何第三方来源、插件、脚本或用户自备 URL 都必须只处理用户有权访问和使用的内容。插件作者和集成者不得把 ECHO 接口包装成下载、盗链、破解或规避平台规则的能力。
  5. 授权和 ECHO Pro 相关逻辑必须保持主机/服务端签名链路为最终可信来源。前端和插件只能展示状态、发起请求或携带证明,不能成为授权真相源,也不能加入绕过校验的后门。
  6. 开发中发现可能被用于破解、侵权或绕过限制的需求时,应明确拒绝实现该用途,并把设计收敛到合法、本地、用户自有内容和公开可验证接口范围内。
  1. 不要巨型 App.tsx
  2. 不要巨型 main/index.ts
  3. 不要巨型全局 CSS 文件。
  4. 超过 500 行的页面必须拆分。
  5. 超过 800 行的 service 必须拆分。
  6. 共享抽象必须有明确的 owner 和用途。

src/renderer/app/App.tsx 只能组合:

  • providers
  • layout
  • routes
  • 未来的 error boundary

src/main/index.ts 只能组合:

  • app lifecycle
  • 通过 lifecycle 创建主窗口
  • IPC 注册
  • 必要的服务 bootstrap

Renderer 不得:

  • 扫文件夹
  • 读 metadata
  • 解析封面
  • 为列表加载完整封面
  • 决定专辑分组
  • 在 React state 里持有全库
  • 对全量内存曲目数组做重搜索
  • 让高频播放状态重渲染整个应用
  • 知道 library worker 是 TypeScript、Rust 还是 C++

歌曲、专辑、艺术家和搜索结果必须分页或虚拟化。

当前 Phase 1 列表默认值:

  • songs:pageSize = 100
  • albums:pageSize = 60
  • track 行虚拟化,估计行高 70px
  • 列表和专辑墙图片必须懒加载、异步解码
  • AlbumsPage 必须先请求第 1 页,只在接近滚动底部时追加;不能一开始循环拉完所有专辑页
  • AlbumWall 在 Phase 1.2 可以保持分页 + 懒图片;只有大曲库烟测证明需要时,才加网格虚拟化

Preload 必须:

  • 暴露 window.echo
  • API 按域分组
  • 返回 typed 结果

Preload 不得:

  • 暴露 raw ipcRenderer
  • 直接访问文件
  • 实现业务逻辑
  • 解析 metadata 或封面
  • 知道 Library Core 背后是哪个 worker 实现

Renderer 不得直接打开 Electron 对话框。选文件夹的 UX 必须走 preload 和 IPC,不能从 React 组件调 dialog

Renderer EQ UI 可以渲染控件、曲线、警告和预设操作。它不得处理音频 buffer、计算原生滤波系数、直接读写预设文件,或绕过 typed window.echo.eq preload API。

Library Core 重活必须通过稳定接口调用:

  • MetadataReader
  • CoverExtractor
  • FileScanner

LibraryService 可以组合具体默认实现,但编排必须依赖接口。IPC 和 Renderer 不得 import TsMetadataReaderTsCoverExtractorTsFileScanner

未来的 Go/C#/Rust worker 必须保持相同返回结构:

  • metadata 字段、field sources、warnings、errors 和 status
  • cover source、thumb path、album path、large path、original reference、source hash、warnings 和 errors
  • scanned file path、size 和 mtime

换 worker 实现时,SQLite schema、IPC payload 和 Renderer 列表视图不得改变。

Metadata 优先级固定:

  1. user manual edit
  2. embedded tags
  3. sidecar/info files
  4. folder structure
  5. network completion
  6. filename fallback

文件名猜测永远不能覆盖嵌入的 titleartistalbum

网络 metadata 永远不能覆盖嵌入标签。

embedded_metadata_statuspendingreading 时,网络 metadata 不得写字段。只有嵌入 metadata 为 missingerror,且当前字段来源为 unknownfilename_fallbacknetwork 时,才可以 missing-only 应用。

每条存储的 track 必须在 field_sources_json 里保留每字段来源信息。

Phase 1 至少持久化:

  • title
  • artist
  • album
  • albumArtist
  • trackNo
  • discNo
  • year
  • duration
  • codec
  • sampleRate
  • bitDepth
  • bitrate

长期封面优先级固定:

  1. user manual cover
  2. embedded cover
  3. local folder cover
  4. sidecar cover
  5. network cover
  6. generated placeholder

网络封面永远不能覆盖 manual、embedded 或 local 封面。

网络封面查找是手动的、弱的。embedded_cover_statuspendingreading 时不得写封面;只有当前封面来源为 default 时才可以应用。

当前 TS+sharp v0.2 封面必须存为:

  • thumb.webp 96x96,给 LibraryTrack.coverThumb
  • album.webp 320x320,给 LibraryAlbum.coverThumb
  • large.webp 最大 768x768,给 NowPlaying/详情
  • original

sharp 做真正的 resize。TypeScript 管封面优先级、缓存目录调度和兜底行为。

列表视图只用 track thumb。专辑墙只用 album thumb。完整封面在列表滚动外按需加载。

列表 API 不得返回 cover_largecover_originallargePathoriginalRef、原始 binary 封面数据或 base64 封面 payload。

在 benchmark 或烟测数据证明 TS+sharp 不够之前,不要启动 Go/C#/Rust CoverWorker。决策信号包括:生成 1000 张专辑缩略图时 CPU 持续高于 50%、3000/10000 封面时内存峰值不可接受、Electron sharp 打包/rebuild 不稳定、或衍生图已存在后封面缓存命中仍然慢。

所有长任务必须:

  • 后台运行
  • 可取消
  • 可报进度
  • 可收集错误

包括扫描、metadata 提取、封面生成、音频分析和未来的网络 enrichment。

网络 enrichment 不得在应用启动时自动跑,不得为每条扫描曲目发请求,必须使用 provider 超时,并发保持在 2 或以下,provider 失败不得影响本地曲库行。

本地曲库扫描在 path + size_bytes + mtime_ms 未变时必须跳过 metadata 解析。

扫描任务必须报告以下阶段之一:

  • discovering
  • checking_cache
  • reading_metadata
  • extracting_covers
  • grouping_albums
  • writing_database
  • finished
  • failed
  • cancelled

单文件 metadata 或封面错误必须收集,但不能让整个扫描失败。

Metadata 和 cover worker 必须有并发限制。封面缩略图必须在扫描时创建,不能在列表滚动时创建。

扫描后 SQLite 是唯一事实来源。重启应用不得重新解析全库。

桌面 dev 跑之前,better-sqlite3 必须为 Electron 运行时 ABI rebuild。npm run dev 通过 npm run rebuild:native 负责这个检查;在 Electron 里测文件夹导入或曲库扫描时,不要依赖为系统 Node.js ABI 编译的二进制。Vitest 用系统 Node.js ABI,所以 Vitest 全局 setup 负责相反的 rebuild——即使用 vitest、编辑器或 npm test 直接启动测试也一样。scripts/ensure-native-abi.mjsnode_modules/.echo-native-cache 下缓存 ABI 专用二进制,让 Node/Electron 切换更快。

必须持久化的表:

  • folders
  • tracks
  • albums
  • album_tracks
  • artists
  • covers
  • scan_jobs

专辑墙视图必须读 albums 表。不得在 renderer 里对全量 track 表重新分组。

文件从已扫描文件夹移除后,下次扫描必须在列表 API 里隐藏它,但不能动磁盘上的文件。

当前 v0.1 策略:缺失文件标 missing = 1 并从列表 API 过滤。这样保留缓存历史,又不删用户磁盘上的文件。

专辑分组必须在 Library Core 里做并持久化。

规则:

  • 同 album + 同 album artist → 合并
  • 同 album + 不同 album artist → 不合并
  • album artist 缺失或 unknown 时,用文件夹路径做弱分隔
  • 空或 unknown 的 album 值不得塌成一张巨型专辑
  • 有 year 时 year 参与 album key

改动涉及 metadata、封面、音频、曲库、编码、数据库迁移或文件扫描行为时,必须包含针对性测试。

Library Core 测试优先用真实 SQLite 和 mock metadata reader,除非解析器集成 bug 明确需要真实媒体文件。

碰 Library Core 的测试必须用假的 MetadataReaderCoverExtractorFileScanner 实现覆盖 worker 边界,保持架构对 Rust/C++ 就绪。

文件夹导入 UX 必须保持 library.chooseFolder() 在 main/preload,重复导入视为幂等重扫,导入或扫描完成后通过共享 library:changed 事件刷新 SongsPage / AlbumsPage。侧边栏导入项是直接动作:Import Folder 打开文件夹选择器而不是导航,Import File 打开本地音频文件选择器,不向 Renderer 暴露 Electron 对话框。

SongsPage 必须保持列表视图,不是导入向导。它的文件夹加号按钮可以通过轻量 app:navigate:import-folder 事件导航到 ImportFolderPageFoldersPageImportFolderPage 和 Settings 复用 LibraryFoldersPanel

TrackRow 可以通过从 SongsPage 传下来的回调启动单曲本地播放。SongsPage 可以存 currentTrackId,但高频播放位置和音频状态必须留在 App.tsx 外,不得重渲染歌曲列表。

当前播放队列只是 SongsPage 可见/已加载窗口。在 LibraryService 支撑的队列服务存在之前,不要扩成完整播放队列。

PlayerBar 轮询是临时的。未来播放/音频状态应走节流的 IPC 推送事件,如 playback:onStatusaudio:onStatus;位置更新不得重渲染 SongsPage 或 TrackList。

曲库诊断仅 dev 可用。必须用 library.getDiagnostics(),不得触发扫描,不得返回全量 track 列表、全量封面记录、binary 封面数据或 base64 封面数据。

EQ 改动必须保持音频线程边界。预设 JSON 存储属于 main/native 非实时代码,不属于 JUCE 回调。原生 EQ 参数必须通过 atomic 或 lock-free 状态传递,使用前平滑,禁用/bypass 后 bypass fade 完成时必须保持输出 bit-transparent。