设置模式、默认值与用户偏好
本页说明 Hydrogen Music 如何定义设置默认值、规范化已保存的偏好,以及 Electron 主进程如何读取部分偏好。以已核实的共享设置模式和主进程入口为边界,不将播放器实现或各平台窗口细节混入设置契约。
Purpose and Scope
聚焦 settingsDefaults.json 中的设置形状、settingsSchema.cjs 中的兼容与归一化规则,以及 background.js 和 ipcMain.js 中可确认的持久化读取/初始化入口。播放器音质、快捷键注册、窗口状态及具体设置页面交互应由各自主题详述;本页只解释其设置字段和可验证的消费边界。当前读取范围未包含 settingsIpc 的实现及渲染进程调用方,因此不臆测具体 IPC 通道、保存时机或界面行为。
Overview
设置对象按 music、local、shortcuts、other 四块组织。JSON 同时给出默认音质和可选音质列表;共享模块从 JSON 生成独立副本,并在加载已有设置时按分组补齐默认值、限定部分字段的有效范围。主进程还直接从名为 settings 的 electron-store 中读取退出偏好,保证读取失败时仍有确定的最小化行为。参见 settingsDefaults.json、settingsSchema.cjs、background.js。
Architecture
Sources: settingsSchema.cjs, background.js, ipcMain.js, ipcMain.js
图中仅表示已核实的 require、导入以及 Store 构造关系;ipcMain.js 导入了 registerSettingsIpc,但当前证据没有展示它的具体注册调用,故图中“注册入口”不意味着某个未经核实的 IPC 消息流。共享模式的使用方也不在已读取片段内,不把共享模块与 Store 绘成未经证实的直接调用边。
默认值、合并与迁移
默认对象的边界
getDefaultSettings() 返回由 JSON 序列化再反序列化生成的副本;调用方修改返回对象时不会直接修改模块保存的 DEFAULT_SETTINGS。MUSIC_LEVEL_OPTIONS 对选项数组及元素分别调用 Object.freeze;有效音质由选项的 value 组成 Set,因此新增音质需要同步修改默认值文件中的选项。注意 Object.freeze(DEFAULT_SETTINGS) 只冻结顶层,而独立副本由 clonePlain 提供。参见 settingsSchema.cjs。
1const DEFAULT_SETTINGS = Object.freeze(clonePlain(settingsDefaults.defaultSettings))
2
3function clonePlain(value) {
4 return JSON.parse(JSON.stringify(value))
5}
6
7function getDefaultSettings() {
8 return clonePlain(DEFAULT_SETTINGS)
9}Source: settingsSchema.cjs
归一化流程
normalizeSettings(settings = {}) 对非对象输入采用空源对象;从新的默认值副本开始,在顶层保留源设置的其他属性。music、local、other 分别对默认字段与输入字段做浅合并;shortcuts 仅在输入为数组时复制该数组,否则使用默认列表。最后去重、过滤 local.localFolder,并规整两个可选文件夹路径。此处不是递归通用深合并:分组内嵌套结构的额外语义不能据此假定。参见 settingsSchema.cjs。
1function normalizeSettings(settings = {}) {
2 const defaults = getDefaultSettings()
3 const source = settings && typeof settings === 'object' ? settings : {}
4 const normalized = {
5 ...defaults,
6 ...source,
7 music: normalizeMusicSettings({
8 ...defaults.music,
9 ...(source.music && typeof source.music === 'object' ? source.music : {}),
10 }),
11 local: {
12 ...defaults.local,
13 ...(source.local && typeof source.local === 'object' ? source.local : {}),
14 },
15 shortcuts: Array.isArray(source.shortcuts) ? clonePlain(source.shortcuts) : defaults.shortcuts,
16 other: normalizeOtherSettings({
17 ...defaults.other,
18 ...(source.other && typeof source.other === 'object' ? source.other : {}),
19 }),
20 }Source: settingsSchema.cjs
Source: settingsSchema.cjs
兼容性与文本处理
normalizeMusicLevel只接受音质选项表中的值,不认识的值回落到lossless;normalizeSearchAssistLimit按十进制整数解析,非有限数回落到默认值,有限数下限为 1。参见settingsSchema.cjs。- 本地 HiFi 模式只认可
shared或exclusive;旧值auto、wasapi-shared映射到shared,wasapi-exclusive映射到exclusive,不认识的值回落到默认模式。normalizeMusicSettings删除遗留标记levelMigratedToLosslessV1。参见settingsSchema.cjs和settingsSchema.cjs、settingsSchema.cjs。 - 自定义字体文本和标签移除换行、回车、换页,去除首尾空白并截断至 120 字符;设备名使用同样处理但限长 260 字符,空设备名退回
auto。未设字体时清空字体标签。参见settingsSchema.cjs、settingsSchema.cjs。 localFolder非数组改为空数组;数组元素只保留非空字符串路径,经 trim 后用Set去重;videoFolder与downloadFolder对非字符串或空白输入返回null。参见settingsSchema.cjs、settingsSchema.cjs。
配置选项与用户偏好
以下是 JSON 中的初始默认值;归一化后的值可能因已保存输入与兼容处理而变化。完整快捷键列表及全部音质枚举见 settingsDefaults.json。
| 字段 | 类型 | 默认值 | 作用/规范化规则 |
|---|---|---|---|
defaultMusicLevel、music.level | string | lossless | 默认音质;无效值回落到该默认音质 |
music.searchAssistLimit | number | 8 | 搜索辅助数量,解析整数且最小为 1 |
music.lyricSize / tlyricSize / rlyricSize | string | 20 / 14 / 12 | 歌词、翻译和罗马音字号的初始字符串 |
music.lyricInterlude | number | 13 | 歌词间奏初始值 |
music.showSongTranslation | boolean | true | 只有严格的 false 会关闭 |
music.gaplessPlayback / audioVisualizer / localHifiOutput | boolean | false | 只有严格的 true 会开启 |
music.localHifiOutputMode | string | shared | shared / exclusive,支持旧模式映射 |
music.localHifiMpvPath / localHifiAudioDevice | string | "" / auto | 可选路径文本及设备名;设备名限长 260 |
local.videoFolder / downloadFolder | string | null | null | 可选目录;空字符串规范化为 null |
local.downloadCreateSongFolder / downloadSaveLyricFile | boolean | false | 下载相关初始偏好 |
local.localFolder | array | [] | 本地目录路径列表,规范化时去空和去重 |
shortcuts | array | 7 条预设 | 播放、上/下一首、音量和快进/后退;输入不是数组时重置为预设 |
other.globalShortcuts / rememberWindowSize | boolean | true / true | 全局快捷键与记住窗口大小;后者仅严格 true 被保留 |
other.quitApp | string | minimize | 主进程仅读取严格等于 quit 的值作为退出,其余按最小化处理 |
other.customFont / customFontLabel | string | "" / "" | 自定义字体与标签;不设字体时清空标签 |
字段初始值来自 settingsDefaults.json,规则来自 settingsSchema.cjs 与 background.js。shortcuts 虽有默认对象的 id、name、shortcut、globalShortcut,本次证据没有看到按 id 校验或修复数组内容的逻辑,不能将其描述为完整快捷键校验器。
主进程读取与运行路径
getQuitAppPreference() 在需要偏好时新建 Store({ name: 'settings' }),读取键 settings,只对 other.quitApp === 'quit' 返回 quit。未保存、字段缺失、不认识的值以及 Store 构造或读取抛错都会得到 minimize;这个读取路径没有调用共享的 normalizeSettings。见 background.js。
1function getQuitAppPreference() {
2 try {
3 const Store = require('electron-store').default
4 const settingsStore = new Store({ name: 'settings' })
5 const settings = settingsStore.get('settings')
6 return settings?.other?.quitApp === 'quit' ? 'quit' : 'minimize'
7 } catch (_) {
8 return 'minimize'
9 }
10}Source: background.js
ipcMain.js 导入 registerSettingsIpc,其 IpcMainEvent 初始化在 moduleState.initialized 为真时仅更新活动窗口/应用/歌词函数代理目标并返回;首次初始化后创建名为 settings 的 Store。已读片段不能确认设置 IPC 的消息名及实际写入过程,也不能把其他独立 Store (lastPlaylist 等) 当作设置键。参见 ipcMain.js、ipcMain.js。
Source: background.js
图中的调用方仅表示函数的调用位置,未声明具体事件或页面;异常情况下图示最后一步由 catch 实现。
Usage Examples
定义新的初始偏好
下面是实际默认值文件中 other 分组的片段;设置形状的修改应考虑归一化函数中的相应规则,不能仅依赖 UI 默认状态。参见 settingsDefaults.json 和 settingsSchema.cjs。
1"other": {
2 "globalShortcuts": true,
3 "rememberWindowSize": true,
4 "quitApp": "minimize",
5 "customFont": "",
6 "customFontLabel": ""
7}Source: settingsDefaults.json
处理旧版 HiFi 偏好
下面的真实代码先映射旧值,再检测新值集合;因此升级时能够接纳已保存的 WASAPI 模式,同时为无法识别的值提供默认回退。参见 settingsSchema.cjs。
1function normalizeLocalHifiOutputMode(mode) {
2 const value = typeof mode === 'string' ? mode.trim() : ''
3 const migratedValue = LEGACY_LOCAL_HIFI_OUTPUT_MODE_MAP[value] || value
4 return AVAILABLE_LOCAL_HIFI_OUTPUT_MODES.has(migratedValue) ? migratedValue : DEFAULT_SETTINGS.music.localHifiOutputMode
5}Source: settingsSchema.cjs
规范化本地目录
返回设置前会过滤空路径、去重并将两类可选目录统一为路径或 null;这避免持久化旧值造成重复目录条目。代码仅做文本规整,不包含目录是否存在的检查。见 settingsSchema.cjs 和 settingsSchema.cjs。
1normalized.local.localFolder = Array.isArray(normalized.local.localFolder)
2 ? Array.from(new Set(normalized.local.localFolder.map(normalizeOptionalPathText).filter(Boolean)))
3 : []
4normalized.local.videoFolder = normalizeOptionalPathText(normalized.local.videoFolder)
5normalized.local.downloadFolder = normalizeOptionalPathText(normalized.local.downloadFolder)
6return normalizedSource: settingsSchema.cjs
API Reference
共享模块导出清单见 settingsSchema.cjs。以下签名为 JavaScript 实现签名;源码没有 TypeScript 类型声明,也未显式抛出业务异常。
| 导出 | 参数 | 返回与约束 |
|---|---|---|
DEFAULT_MUSIC_LEVEL | — | 来自 JSON 的默认音质字符串 |
MUSIC_LEVEL_OPTIONS | — | 冻结的音质选项数组及选项对象 |
getDefaultSettings() | 无 | 默认设置的独立 JSON 副本 |
normalizeSearchAssistLimit(value) | 待解析的值 | 十进制整数,下限 1;非有限数采用默认值 |
normalizeMusicLevel(level) | 音质值 | 可用音质之一或默认音质 |
normalizeLocalHifiOutputMode(mode) | 候选模式 | 新模式、旧值映射结果或默认模式 |
normalizeMusicSettings(music = {}) | music 分组对象 | 新建的规范化 music 对象,删除旧迁移标记 |
normalizeSettings(settings = {}) | 整体设置对象 | 合并默认值并规范化后的对象 |
对应实现见 settingsSchema.cjs、settingsSchema.cjs、settingsSchema.cjs。本页不描述未读取的 registerSettingsIpc 参数与异常契约。
Failure Modes, Edge Cases & Concurrency
- 已保存设置缺失或类型不符时,
normalizeSettings对整体非对象输入使用空对象,对各分组作条件合并;但数组本身满足typeof === 'object',源码并未显式排斥将数组作为整体或分组输入。参见settingsSchema.cjs。 - 开关归一化并非一律回退默认值:翻译开关对非
false为真,三个音频开关及rememberWindowSize对非true为假。这意味着已保存的字符串"true"不等同于布尔true。参见settingsSchema.cjs。 - 字体文字、设备名会清除部分控制字符,路径只 trim 或清空;不要把这些操作解读为文件系统路径校验或安全授权。参见
settingsSchema.cjs。 - 主进程退出偏好读取捕获所有异常并默认为最小化,未在该函数记录失败原因。归一化函数没有异步队列、锁或事务代码;并发写入语义需查看未读取的 IPC 与 Store 写路径,不能据此推断。参见
background.js。
Performance, Operations & Extension Points
getDefaultSettings() 和输入 shortcuts 数组通过 JSON 序列化复制,代价与设置对象规模相关;设置均为普通 JSON 值的默认定义,不应依赖该复制方法保留特殊对象类型。localFolder 使用 Set 消除重复的规范化字符串;音质和模式检查使用 Set 判断有效值。参见 settingsSchema.cjs、settingsSchema.cjs。
扩展偏好时,把初始值放入 defaultSettings 的对应分组;需要约束、迁移或废弃字段时,在对应的 normalizeMusicSettings、normalizeOtherSettings 或 normalizeSettings 路径中明确处理,并核对读取偏好的主进程代码是否绕过归一化。比如退出行为读取直接走 Store 的原始值,而 HiFi 旧值映射位于共享模式内,这两条路径的兼容责任并不相同。参见 settingsDefaults.json、settingsSchema.cjs、background.js。当前读取范围内没有设置相关测试文件或 UI 实现,无法据此陈述测试覆盖与设置保存的时序。