Repository Wiki
ldx123000/Hydrogen-Music

设置模式、默认值与用户偏好

本页说明 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

Loading diagram...

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。

javascript
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。

javascript
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

Loading diagram...

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.levelstringlossless默认音质;无效值回落到该默认音质
music.searchAssistLimitnumber8搜索辅助数量,解析整数且最小为 1
music.lyricSize / tlyricSize / rlyricSizestring20 / 14 / 12歌词、翻译和罗马音字号的初始字符串
music.lyricInterludenumber13歌词间奏初始值
music.showSongTranslationbooleantrue只有严格的 false 会关闭
music.gaplessPlayback / audioVisualizer / localHifiOutputbooleanfalse只有严格的 true 会开启
music.localHifiOutputModestringsharedshared / exclusive,支持旧模式映射
music.localHifiMpvPath / localHifiAudioDevicestring"" / auto可选路径文本及设备名;设备名限长 260
local.videoFolder / downloadFolderstring | nullnull可选目录;空字符串规范化为 null
local.downloadCreateSongFolder / downloadSaveLyricFilebooleanfalse下载相关初始偏好
local.localFolderarray[]本地目录路径列表,规范化时去空和去重
shortcutsarray7 条预设播放、上/下一首、音量和快进/后退;输入不是数组时重置为预设
other.globalShortcuts / rememberWindowSizebooleantrue / true全局快捷键与记住窗口大小;后者仅严格 true 被保留
other.quitAppstringminimize主进程仅读取严格等于 quit 的值作为退出,其余按最小化处理
other.customFont / customFontLabelstring"" / ""自定义字体与标签;不设字体时清空标签

字段初始值来自 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。

javascript
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。

Loading diagram...

Source: background.js

图中的调用方仅表示函数的调用位置,未声明具体事件或页面;异常情况下图示最后一步由 catch 实现。

Usage Examples

定义新的初始偏好

下面是实际默认值文件中 other 分组的片段;设置形状的修改应考虑归一化函数中的相应规则,不能仅依赖 UI 默认状态。参见 settingsDefaults.json 和 settingsSchema.cjs。

json
1"other": { 2 "globalShortcuts": true, 3 "rememberWindowSize": true, 4 "quitApp": "minimize", 5 "customFont": "", 6 "customFontLabel": "" 7}

Source: settingsDefaults.json

处理旧版 HiFi 偏好

下面的真实代码先映射旧值,再检测新值集合;因此升级时能够接纳已保存的 WASAPI 模式,同时为无法识别的值提供默认回退。参见 settingsSchema.cjs。

javascript
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。

javascript
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 normalized

Source: 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 实现,无法据此陈述测试覆盖与设置保存的时序。

Sources

(4 files)