Compare commits

..
200 Commits
Author SHA1 Message Date
lyswhut dd23af7051 更新依赖
Build / Windows (push) Canceled after 0s
Build / Windows_7 (push) Canceled after 0s
Build / Mac (push) Canceled after 0s
Build / Linux (push) Canceled after 0s
2025-01-27 00:57:32 +08:00
lyswhut 7d5fc6de12 更新依赖&版本号(2.10.0-beta.12) 2025-01-25 20:11:32 +08:00
lyswhut 28233fbe58 更新依赖 2025-01-25 20:11:32 +08:00
3gf8jv4dv 32a35c6706 i18n cleanup 3 (#2229) 2025-01-18 22:15:39 +08:00
3gf8jv4dv 228ddace91 Update publish/changeLog.md (#2230) 2025-01-14 14:22:24 +08:00
lyswhut f9ffdbbce7 更新依赖&版本号(2.10.0-beta.11) 2024-12-28 13:17:38 +08:00
3gf8jv4dv 9b233683a2 i18n cleanup 2 (#2215) 2024-12-28 13:14:05 +08:00
3gf8jv4dv ff78fb6b30 i18n cleanup (#2211)
* i18n cleanup

- A final trimming of the language. (I hope it's the last.)
- For en-US and zh-TW, some of the terms have been revised again.
- For zh-CN, some of the terms have been revised to ensure that they do
not change the habits of users.

* update

* update

* update

* Apply suggestions from lyswhut

* Apply suggestions from lyswhut

* update

* update

* update

* Apply suggestions from lyswhut
2024-12-26 23:35:42 +08:00
3gf8jv4dv 8efda6478c Update publish/changeLog.md (#2212)
* Update publish/changeLog.md

* Apply suggestions from lyswhut

* update

* update

* update

* update
2024-12-26 23:31:57 +08:00
3gf8jv4dv 851c7871e3 Disable randomize popup animation option when animation is not turned on (#2210)
This option has no real purpose if "Show switch animation" is not turned
on. So I mimicked the option about lyrics in the download settings page.
2024-12-25 23:35:14 +08:00
3gf8jv4dv a78c78f3e5 Revise app language text (3) (#2209) 2024-12-25 00:17:46 +08:00
3gf8jv4dvandlyswhut 05e8442ff1 Fix change music source dialog layout (#2204)
* Fix change music source dialog layout

* Apply suggestions from lyswhut

Co-authored-by: lyswhut <lyswhut@qq.com>

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-24 13:53:05 +08:00
3gf8jv4dv 977b842df3 Add missing quotes to language files (#2206) 2024-12-24 09:33:37 +08:00
3gf8jv4dvandlyswhut 5bde54aac1 Fix edit theme dialog title logic (#2205)
* Fix edit theme dialog title logic

As you can see from the code and the actual behavior, both share the
same code whether adding or editing a theme. So why does the title show
up as "Edit Theme" no matter what? It does seem strange in a way.

This feature appears to have been added in v2.0.0, but no one seems to
have cared about the issue during the two years.

Nonetheless, when I realized this recently, I decided to fix it.
Hopefully this will make things clearer for those users who may not be
able to tell the difference.

Now, when the "Add theme" button is clicked, the dialog title will
show "Add theme". If you right-click on a user-created theme, the dialog
title will show "Edit theme".

* Apply suggestions from lyswhut

Simpler code

Co-authored-by: lyswhut <lyswhut@qq.com>

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-23 23:53:10 +08:00
3gf8jv4dv 0f27e3ea42 Fix duplicate songs dialog layout (#2203) 2024-12-23 23:41:31 +08:00
3gf8jv4dv d170725f0c Add scrollbar to "open shared playlist" dialog box (#2201) 2024-12-22 23:39:05 +08:00
3gf8jv4dv a26fcf0cd8 Fix search page UI (#2200) 2024-12-22 23:38:33 +08:00
3gf8jv4dv 18a4455511 Fix edit theme dialog layout 3 (#2199) 2024-12-22 23:37:59 +08:00
3gf8jv4dv 2ab3286d0e Add scrollbar to the Sort Songs dialog box (#2197) 2024-12-22 23:32:59 +08:00
3gf8jv4dv d4a411ab4a Fix list update management dialog UI (#2198) 2024-12-22 01:19:45 +08:00
lyswhut c7facb38b1 更新依赖&版本号(2.10.0-beta.10) 2024-12-22 00:09:31 +08:00
3gf8jv4dv b7434e9eaf Revise app language text (2) (#2195) 2024-12-21 23:57:50 +08:00
3gf8jv4dvandlyswhut 4a174c0084 Fix edit theme dialog layout again (#2192)
* Fix edit theme dialog layout again

Ensures that the layout looks better and doesn't get out of place in the
smallest windows and largest fonts, as well as in both English and
Chinese environments.

* update

* update

* fix

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-21 23:52:15 +08:00
3gf8jv4dvandlyswhut db8ad1e8eb Optimize theme item name display (#2189)
Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-19 00:27:06 +08:00
3gf8jv4dvandlyswhut 1feaac003a Revise app language text (#2187)
* Revise app language text

Some additional revisions.

* Apply suggestion from lyswhut

Co-authored-by: lyswhut <lyswhut@qq.com>

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-18 23:54:02 +08:00
3gf8jv4dvandlyswhut c42b2fb34c Fix edit theme dialog layout (#2188)
Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-18 23:37:59 +08:00
14K 5d48fa93ba fix: 修正拼写错误 inert* 为 insert* (#2180) 2024-12-17 19:49:19 +08:00
3gf8jv4dv 527ac04c4e Revise zh-TW language (#2174)
* Revise zh-TW language

Revise according to Taiwanese language habits

Special thanks: Fanhuaji <https://zhconvert.org/>

* update

* Apply suggestion from lyswhut
2024-12-17 19:44:48 +08:00
14Kandlyswhut 9644163dc5 fix: 修复了当listPosition未初始化时错误的删除操作 (#2178)
* feat: 新增了 当`列表歌曲位置`未初始化时删除列表时的判断

* fix

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-17 17:32:49 +08:00
3gf8jv4dv d0090abd01 Optimize sort songs dialog layout (#2176)
Slightly increased width to ensure no layout issues in various languages
2024-12-17 17:16:13 +08:00
3gf8jv4dv 918d2ad156 Fix English (#2173)
- Fix some `playlist` terms
2024-12-16 23:28:41 +08:00
3gf8jv4dv b028fb955f Optimize options with too long descriptions in settings (#2172) 2024-12-16 09:52:55 +08:00
3gf8jv4dv ba33d5a39b Fix i18n keys (#2170)
`setting__setting__desktop_lyric_font_weight` to
`setting__desktop_lyric_font_weight`
2024-12-15 14:45:38 +08:00
3gf8jv4dv fd0b9c55fc Revise zh-CN language (2) (#2169)
* Revise zh-CN language (2)

* Apply suggestion from lyswhut
2024-12-15 14:36:40 +08:00
3gf8jv4dv b28fb5c9b4 Overhaul English (#2166)
* Overhaul English

Let's go!!!

Overhaul with reference to Simplified Chinese text
and English user habits.

* Clarify `Music API` and `Song Source`

* fix
2024-12-15 14:08:11 +08:00
lyswhut 77ecd4be9b 更新依赖&版本号(2.10.0-beta.9) 2024-12-14 12:48:20 +08:00
lyswhut 6f74003e14 新增下载的歌曲按列表名分组的功能(#2145) 2024-12-14 12:48:19 +08:00
lyswhut 63b2c2fb2f 修复本地歌曲同名歌词文件调整偏移时间后,下次再播放时调整的设置未被应用的问题(#2139) 2024-12-14 12:47:59 +08:00
3gf8jv4dv 08da7a37e6 Revise zh-CN language (#2159)
* Revise zh-CN language

- Optimize text typesetting.
- Fix some typos.
- Unify some terms.

* update

* update

* update
2024-12-10 23:30:29 +08:00
3gf8jv4dv 6b87a8d91b Fix zh-TW language name (#2155)
Write it in the native language.
2024-12-08 13:48:14 +08:00
3gf8jv4dv 0f1364a8d7 Revise ISSUE_TEMPLATE [skip ci] (#2153)
* Revise ISSUE_TEMPLATE [skip ci]

- Optimize typesetting and fix links.
- Rename the template file.
  - I don't know why git shows it as deleted & new files.

* update [skip ci]

* update [skip ci]

* update [skip ci]
2024-12-08 13:47:16 +08:00
3gf8jv4dv 9dbf70780d Revise English license (#2152)
* Revise English license

Fix some terms and mistakes.

* update
2024-12-08 13:40:55 +08:00
lyswhut 58e5592c2b 修复构建 2024-12-07 16:27:12 +08:00
lyswhut 02cc842cc8 更新依赖&版本号(2.10.0-beta.8) 2024-12-07 15:59:33 +08:00
lyswhut e962e866b3 修复 Windows 任务管理器中的进程名显示为描述的问题(#2147) 2024-12-07 15:47:48 +08:00
lyswhut af3b30096d 移除多余逻辑 2024-12-07 15:47:48 +08:00
3gf8jv4dv aa7ab86b62 Fix some i18n keys (#2148) 2024-12-07 15:47:00 +08:00
3gf8jv4dvandlyswhut 7f864a9e35 Revise repo docs [skip ci] (#2146)
* Revise repo docs [skip ci]

Fix typos, optimize typesetting and descriptions.

* update

* update

* update

* update

* update

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-07 15:03:43 +08:00
lyswhut 609d345443 更新依赖&版本号(2.10.0-beta.7) 2024-12-04 09:07:04 +08:00
lyswhut 426815fa50 优化换源歌曲查找 2024-12-03 17:54:16 +08:00
lyswhut 5d1b1e48d7 Windows 下改用原生窗口调整方式(#2137) 2024-12-02 00:07:00 +08:00
lyswhut 28d0412adc 更新依赖&版本号(2.10.0-beta.6) 2024-11-30 18:50:11 +08:00
lyswhut 9d070b3671 修复kg id重复导致的问题 2024-11-30 11:54:26 +08:00
lyswhut cd423752d1 优化歌曲换源机制,提升换源正确率 2024-11-29 12:39:42 +08:00
lyswhut 1f8a0eaf67 更新依赖与更新日志 2024-11-28 08:52:39 +08:00
lyswhut 42d9fcd8b0 更新依赖&版本号(2.10.0-beta.5) 2024-11-26 18:04:06 +08:00
lyswhut 4e1510c840 更新软件默认使用的字体 2024-11-26 18:03:52 +08:00
lyswhut 7ebfd43f65 更新关于说明 2024-11-24 00:06:05 +08:00
lyswhut 41d6157116 修复歌曲检查事件清理 2024-11-23 17:41:45 +08:00
lyswhut f9f8297fd6 优化歌曲URL检查机制 2024-11-23 17:15:05 +08:00
lyswhut dfbe3f665a 修复构建 2024-11-23 14:49:34 +08:00
lyswhut b36df472e5 更新更新日志 2024-11-23 14:30:27 +08:00
lyswhut 0b147f708d 更新依赖&版本号(2.10.0-beta.4) 2024-11-23 14:29:31 +08:00
lyswhut 4e3bdbcc74 添加歌曲预加载功能 2024-11-23 14:21:59 +08:00
lyswhut 0345419ef3 客户端模式的同步服务连接允许重定向 5 次(#2109) 2024-11-10 11:05:59 +08:00
lyswhut 9237dbbc18 播放本地歌曲时,将优先尝试读取本地同名字的 jpg 或 png 图片作为播放封面显示 2024-10-28 23:04:48 +08:00
lyswhut 68ac1b7641 更新依赖&版本号(2.10.0-beta.3) 2024-10-25 20:13:30 +08:00
lyswhut 2cfe4d29ba 更新 electron 到 v32.2.2 2024-10-25 20:12:29 +08:00
lyswhut b436497555 添加debug日志 2024-10-25 19:56:59 +08:00
lyswhut 7ac638c52c 修复 m4a 文件歌曲内嵌歌词无法读取的问题(#2090) 2024-10-25 19:56:41 +08:00
lyswhut f21d2bc7c7 更新readme 2024-10-19 00:25:38 +08:00
lyswhut 550917bd16 更新依赖&版本号(2.10.0-beta.2) 2024-10-17 12:38:05 +08:00
lyswhut c1c0311586 不再缓存换源歌曲信息 2024-10-17 12:36:59 +08:00
lyswhut 489bbb6902 Open API 新增播放器播放/暂停、切歌、收藏当前播放歌曲调用,详情看开放API文档 (原始 PR #2077) 2024-10-12 09:59:04 +08:00
lyswhut 86af25bde3 Scheme URL 的播放歌曲允许更长的专辑名称 2024-09-27 21:25:46 +08:00
lyswhut a599e0716f 支持本地同名 krc 格式歌词文件的读取(#2053) 2024-09-25 18:04:42 +08:00
lyswhut 30fc818771 更新依赖&版本号(2.10.0-beta.1) 2024-09-24 18:49:46 +08:00
lyswhut df08f677bd 优化播放详情页UI,歌曲名字、歌手等文字过长时被截断的问题(#2049) 2024-09-24 18:47:40 +08:00
lyswhut 7247bcad1b 修正搜索歌曲提示框文案(#2050) 2024-09-24 18:40:32 +08:00
lyswhut c0bcac25ea fix build 2024-09-21 14:10:33 +08:00
lyswhut ae06019bdb 更新依赖&版本号(2.10.0-beta.0) 2024-09-21 14:01:59 +08:00
lyswhut 432861780a 修复获取自定义环境音效预设列表逻辑问题 2024-09-21 12:45:04 +08:00
lyswhut 111ee88833 更新依赖 2024-09-06 09:44:30 +08:00
lyswhut 487ee7015e 修复歌单详情页内歌单名字过长时的UI显示问题(#2028) 2024-09-04 13:04:41 +08:00
lyswhut 061cc16c27 新增托盘图标颜色 跟随系统亮暗模式 设置,可以在 设置-其他 启用 (#2016) 2024-08-30 20:21:18 +08:00
lyswhut af8d9dff2d 更新检查连接链接顺序 2024-08-24 13:39:27 +08:00
lyswhut cb16bd0324 发布 v2.9.0 2024-08-24 12:57:27 +08:00
lyswhut acf8171617 npm audit fix 2024-08-24 12:56:59 +08:00
lyswhut da7429f03e 将下载歌曲的歌手信息中的分隔符从 、 替换为 ; 以确保音乐元数据在写入时的兼容性和一致性(#1989 @qnnp-me) 2024-08-24 12:51:09 +08:00
lyswhut 4a121db39d 更新 better-sqlite3 11.2.1 二进制文件 2024-08-24 12:28:50 +08:00
lyswhut 0e8e287fe2 update actions cache dir 2024-08-23 21:22:31 +08:00
lyswhut cf942121dd 更新依赖&版本号(2.9.0-beta.12) 2024-08-23 20:07:09 +08:00
lyswhut 77c2e962ec 优化本地歌曲内嵌封面过大时的加载方式 2024-08-23 20:06:17 +08:00
lyswhut 05f5cc4fe8 更新依赖&版本号(2.9.0-beta.11) 2024-08-18 13:14:01 +08:00
lyswhut 684f09feaf 修复列表数据同步问题 2024-08-18 10:10:38 +08:00
lyswhut 38bea3ced1 优化换源机制 2024-08-17 20:30:07 +08:00
lyswhut 6a6f9a53bd 更新依赖&版本号(2.9.0-beta.10) 2024-08-17 13:45:33 +08:00
lyswhut 584c12e60f 新增 我的列表-换源播放 功能 2024-08-17 13:44:11 +08:00
lyswhut bb5b667613 更新版本号(2.9.0-beta.9) 2024-08-11 11:24:25 +08:00
lyswhut 0628d3533f 新增设置-下载设置-同时下载任务数设置(#1498) 2024-08-10 14:31:35 +08:00
lyswhut e1bd4c9a0a 更新依赖&版本号(2.9.0-beta.8) 2024-08-10 10:27:05 +08:00
lyswhut 85c9ec38db 新增鼠标在音量按钮使用滚轮时可以调整音量大小的功能(#2000) 2024-08-10 10:18:27 +08:00
lyswhut 6ff7cfde11 优化某些UI的字体可能出现被截断的问题 2024-08-04 11:33:44 +08:00
lyswhut c52034f992 更新版本号(2.9.0-beta.7) 2024-08-04 10:49:41 +08:00
lyswhut b982dd90e4 修复程序目录路径存在 # 或 % 时,自定义源、托盘等图标异常的问题(#1997) 2024-08-04 10:48:31 +08:00
lyswhut 80e36688e8 更新依赖 2024-08-03 12:26:53 +08:00
lyswhut c6c1071d2d 优化添加不喜欢歌曲时的提示文案 2024-08-02 12:32:20 +08:00
lyswhut 481817c607 更新依赖&版本号(2.9.0-beta.6) 2024-07-27 17:23:02 +08:00
lyswhut 2e5739f042 Revert "优化代码"
This reverts commit 72d44ec783.
2024-07-27 17:21:09 +08:00
lyswhut 0bb9f5ece5 更新依赖&版本号(2.9.0-beta.5) 2024-07-26 18:55:59 +08:00
lyswhut d454f51993 新增设置-播放详情页设置-延迟歌词滚动设置(#1985) 2024-07-26 18:52:27 +08:00
lyswhut 7f3ab31ad5 尝试修复某些情况下播放音频时,处于播放状态但是进度条不走的问题 2024-07-26 18:50:45 +08:00
lyswhut 98fe433819 更新依赖&版本号(2.9.0-beta.4) 2024-07-24 19:32:39 +08:00
lyswhut 4b01326420 更新依赖 2024-07-20 13:11:35 +08:00
lyswhut 3a4acd2693 更新依赖&版本号(2.9.0-beta.3) 2024-07-16 19:06:16 +08:00
lyswhut b8e9937083 托盘图标添加当前播放歌曲名字显示 2024-07-15 19:42:51 +08:00
lyswhut 72d44ec783 优化代码 2024-07-11 18:52:48 +08:00
lyswhut 680b60cb07 更新依赖&版本号(2.9.0-beta.2) 2024-07-04 19:49:39 +08:00
lyswhut 45527b320d 修复开发模式下 vue devtools 扩展没有加载的问题 2024-07-04 19:41:49 +08:00
lyswhut 0acc9a553e 优化侧栏图标显示,修复图标可能被裁切的问题(#1960) 2024-06-26 19:21:55 +08:00
lyswhut 19f81cf379 优化代码 2024-06-19 19:25:21 +08:00
lyswhut 96ddbdf0f0 更新依赖&版本号(2.9.0-beta.1) 2024-06-19 12:40:04 +08:00
lyswhut 9eb87911a6 修复界面不可见时状态栏歌词没有更新的问题 2024-06-19 12:39:44 +08:00
lyswhut b0881726fa 更新依赖&版本号(2.9.0-beta.0) 2024-06-18 19:33:46 +08:00
lyswhut 308248d8ec 新增 是否将歌词显示在状态栏 设置,默认关闭,该功能只在 MacOS 下可用(#1940) 2024-06-18 19:30:54 +08:00
lyswhut de5ea4f0aa 修复切换音源时可能出现切换死循环的问题 2024-06-17 12:37:26 +08:00
lyswhut 305ce6ff11 更新依赖&版本号(2.8.1-beta.3) 2024-06-11 19:47:40 +08:00
lyswhut bd4dbb55e4 现在如果在设置或者启动参数配置了代理服务,那么应用内的图片、音频加载,歌曲下载也将走代理 2024-06-11 19:41:10 +08:00
lyswhut 1fefb2bcaf 更新 electron 到 v29.4.2 2024-06-11 14:09:31 +08:00
3gf8jv4dv e094e80ecf Some fixes to lang files (#1935)
* `Closure` -> `Close`

* Fix `player__sound_effect_features_tip`
2024-06-10 22:44:00 +08:00
lyswhut 9715db1229 更新版本号(2.8.1-beta.2) 2024-06-04 18:44:46 +08:00
lyswhut 3c0daa2bf1 修复 OpenAPI 的 lyricLineAllText 在切换到无歌词的音乐时内容没有更新的问题(#1925) 2024-06-04 18:42:52 +08:00
lyswhut 435fec2f1c 更新版本号(2.8.1-beta.1) 2024-06-03 18:30:26 +08:00
lyswhut 69ba619f24 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923) 2024-06-03 18:29:54 +08:00
lyswhut b7bb21f091 更新版本号(2.8.1-beta.0) 2024-06-03 12:50:19 +08:00
lyswhut 77f51a3e9f 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923) 2024-06-03 12:49:26 +08:00
lyswhut 7799e511e8 修复构建 2024-06-01 14:15:35 +08:00
lyswhut 416f4a4dee 发布v2.8.0 2024-06-01 12:01:33 +08:00
lyswhut 2fd5158839 更新依赖 2024-06-01 11:45:27 +08:00
lyswhut b8287acff3 更新导入歌单的id生成方式 2024-05-25 19:54:46 +08:00
lyswhut 53b470ed09 更新ci npm缓存设置 2024-05-18 18:55:41 +08:00
lyswhut 8a20b773fb fix code 2024-05-17 09:55:02 +08:00
lyswhut aa576fe7db 修复依赖关系 2024-05-17 09:41:45 +08:00
lyswhut 582321c1ae 更新版本号(2.8.0-beta.5) 2024-05-16 19:23:37 +08:00
lyswhut 541f5041fb 更新依赖 2024-05-16 19:23:00 +08:00
lyswhut d16babefbe 修复翻页按钮栏切页按钮只显示前几页的问题 2024-05-15 13:21:26 +08:00
lyswhut 4ac6768b7a 修复潜在自定义源切换问题 2024-05-14 19:28:24 +08:00
lyswhut 97d3d3c616 添加媒体输出设备检测提示 2024-05-14 19:27:55 +08:00
lyswhut 2ee7572634 Scheme URL API新增 music/searchPlay 支持(#1886) 2024-05-14 09:58:07 +08:00
lyswhut 8e7fd1ecef 更新版本号(2.8.0-beta.4) 2024-05-03 18:54:47 +08:00
lyswhut 6b4e6533f5 修复文本提示气泡在内容过长时,文本未被换行而被截断的问题 2024-05-03 18:52:39 +08:00
lyswhut b680a89033 修复歌曲时文件名过长导致歌曲无法下载的问题(#1877) 2024-05-03 18:49:48 +08:00
lyswhut ee6a6f4b94 默认禁用 使用设备能处理的最大声道数输出音频 设置,修复高级音频API与自定义音频输出设备功能冲突的提示显示 2024-05-02 14:13:16 +08:00
lyswhut 6933baafca 修复事件监听潜在问题 2024-05-02 13:03:02 +08:00
lyswhut 0f3a6782b8 修复与优化繁体中文、英语翻译显示(#1845) 2024-05-02 12:56:41 +08:00
lyswhut f99cc021f8 更新版本号(2.8.0-beta.3) 2024-05-02 12:41:28 +08:00
lyswhut e4de4b8c1b 新增 设置-播放设置-使用设备能处理的最大声道数输出音频 设置(#1873) 2024-05-02 12:33:07 +08:00
lyswhut 793b5a679c 修复播放详情页的歌词无法使用触碰拖动的问题(#1865) 2024-05-01 17:58:36 +08:00
筱莱Cealeandlyswhut 1b75cf822d 开放API支持跨域请求 (#1872)
* 增加 Access-Control-Allow-Origin: *

* 完善

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-05-01 17:57:47 +08:00
lyswhut 60604020c7 更新版本号(2.8.0-beta.2) 2024-04-28 21:19:58 +08:00
lyswhut 09780d02e3 修复Mac下即使开启了托盘, cmd+w 仍会中断播放的问题(#1844) 2024-04-28 18:59:09 +08:00
lyswhut 4760f79ab5 修正描述,统一在线导入音源大小限制 2024-04-28 18:57:22 +08:00
zclorneandlyswhut 05363a0d22 fix #731 #887,修复桌面歌词与主窗口阴影残留 (#1869)
* fix #731

* fix #887

* Revert "在歌词滚动结束后清理窗口阴影,缓解Mac下桌面歌词出现残留阴影的问题"

This reverts commit 1f6a6bbb39.

* update

* fix

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-04-28 18:53:04 +08:00
lyswhut 49797f891c 修复构建 2024-04-27 15:40:14 +08:00
lyswhut 9d38fe6764 更新版本号(2.8.0-beta.1) 2024-04-27 15:07:52 +08:00
lyswhut f340b18408 允许添加 m4a、goa 格式的本地歌曲到列表中(#1864) 2024-04-27 15:06:27 +08:00
lyswhut 358c449b17 修正文案 2024-04-25 18:58:07 +08:00
lyswhut 1f6a6bbb39 在歌词滚动结束后清理窗口阴影,缓解Mac下桌面歌词出现残留阴影的问题 2024-04-24 18:51:53 +08:00
lyswhut 10028cf500 增大在线导入自定义源文件的大小限制问题(#1857) 2024-04-23 19:24:37 +08:00
lyswhut a977458881 暂时固定ci node版本 2024-04-17 00:44:15 +08:00
lyswhut dc811d8b84 更新版本号(2.8.0-beta.0) 2024-04-16 18:52:15 +08:00
lyswhut 5bc119bd9d 修复win7下的电源锁清理问题 2024-04-16 18:37:35 +08:00
lyswhut 9016796497 优化托盘图标显示(#1842) 2024-04-16 18:13:52 +08:00
lyswhut 32f47aaa34 开放API 添加歌词翻译字段 2024-04-16 16:18:07 +08:00
lyswhut c54b38a068 修复存在多级弹窗时的背景显示问题 2024-04-16 11:25:21 +08:00
lyswhut a9a708d0f6 更新描述 2024-04-15 20:22:21 +08:00
NKXingXhandlyswhut ab3f118588 允许设置其他优先播放的歌曲音质 (#1839)
* 在线播放更高音质

* 修改对应翻译

* 设置-播放设置-优先播放320k音质选项改为“优先播放的音质”

* 修复拼写

* 优化

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-04-15 19:46:28 +08:00
lyswhut 87852acb14 发布v2.7.0 2024-04-14 16:43:14 +08:00
lyswhut 65ab26365d 拒绝在CI环境使用不干净的工作区构建应用 2024-04-13 18:59:42 +08:00
lyswhut e7630e98d9 修复工作区不干净的问题 2024-04-13 18:46:36 +08:00
lyswhut 9779c1ebee 更新版本号(2.7.0-beta.7) 2024-04-13 18:11:37 +08:00
lyswhut 9cef7e3370 添加当前软件版本所对应的代码提交版本、提交时间的显示 2024-04-13 18:08:45 +08:00
lyswhut b6bc545e7d 更新默认设置 2024-04-13 12:08:32 +08:00
lyswhut 85326be7bb 更新依赖&版本号(2.7.0-beta.6) 2024-04-11 13:22:28 +08:00
lyswhut 75750ad38d 修复封面链接事件在封面链接相同时仍然触发的问题 2024-04-11 13:07:54 +08:00
lyswhut 0522114c5d 修复切歌时的状态栏进度条显示问题 2024-04-11 12:42:58 +08:00
lyswhut 6300236cdb 更新依赖 2024-04-10 11:42:09 +08:00
lyswhut 1dbb950b3c 修复开发模式启动问题 2024-04-08 19:25:48 +08:00
lyswhut bfd2ecd26c 更新electron到28.2.10,修复开发模式重启electron进程时偶现旧进程残留的问题 2024-04-05 13:10:45 +08:00
lyswhut e60c2f5f19 修复注册的事件未被清理的问题 2024-04-02 11:07:50 +08:00
lyswhut 274f71615f 更新依赖&版本号(2.7.0-beta.5) 2024-04-01 22:49:00 +08:00
lyswhut cd7d5ed665 添加开发API允许局域网访问选项(#1828) 2024-04-01 22:44:34 +08:00
lyswhut 703327ed41 添加SSE订阅字段过滤参数 2024-03-30 12:20:12 +08:00
lyswhut a7ce24c84b 更新版本号(2.7.0-beta.4) 2024-03-29 18:22:21 +08:00
lyswhut c3c42e33ee 添加SSE支持 2024-03-29 18:22:08 +08:00
lyswhut 530ace6b81 添加播放速率 2024-03-29 16:48:25 +08:00
lyswhut 5345b001e8 修复http服务关闭时没有断开所有连接的问题 2024-03-29 16:38:07 +08:00
lyswhut 07c9e2f675 更新依赖&版本号(2.7.0-beta.3) 2024-03-29 15:59:04 +08:00
lyswhut 7a08cb5ddd 托盘菜单新增播放、切歌、收藏控制 2024-03-29 15:59:00 +08:00
lyswhut 9c0ae12959 新增HTTP开放API服务 2024-03-29 14:43:32 +08:00
250 changed files with 10819 additions and 21111 deletions
-55
View File
@@ -1,55 +0,0 @@
name: 🐞报告Bug
description: 报告bug,请先查看常见问题及搜索issue列表中有无你要提的问题
title: "[Bug]: "
body:
- type: checkboxes
id: check-answer
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作
options:
- label: 我已阅读常见问题(<https://lyswhut.github.io/lx-music-doc/desktop/faq>),并没有找到解决方案
required: true
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),并没有发现类似的问题
required: true
- type: textarea
id: expected-behavior
attributes:
label: 预期行为
description: 对期望发生的事情的清晰简明描述
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: 实际行为
description: 对实际发生的事情的清晰简明描述
validations:
required: true
- type: input
id: version
attributes:
label: Lx Music 版本
description: 你使用什么版本的LX Music?
placeholder: 1.15.0
validations:
required: true
- type: input
id: last-known-working-version
attributes:
label: 最后正常的版本
description: 如果有,请在此处填写最后正常的版本是多少?
placeholder: 1.15.0
- type: input
id: operating-system-version
attributes:
label: 操作系统版本
description: 您使用的是什么操作系统版本?在 Windows 上,单击开始按钮 > 设置 > 系统 > 关于;在 macOS 上,单击 Apple 菜单 > 关于本机;在 Linux 上,使用 lsb_release 或 uname -a
placeholder: "例如 Windows 10 版本 1909、macOS Catalina 10.15.7 或 Ubuntu 20.04"
validations:
required: true
- type: textarea
id: additional-information
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者你所遇到的问题不容易重现,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)
+59
View File
@@ -0,0 +1,59 @@
name: 🐞 报告错误
description: 报告一个错误(Bug),请先查看常见问题及搜索 Issue 列表中有无你要提的问题。
title: "[Bug]: "
body:
- type: checkboxes
id: check-answer
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作。
options:
- label: 我已阅读 [常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq),并没有找到解决方案。
required: true
- label: 我已搜索 [Issue 列表](https://github.com/lyswhut/lx-music-desktop/issues?q=is%3Aissue+),并没有发现类似的问题。
required: true
- type: textarea
id: expected-behavior
attributes:
label: 预期行为
description: 对期望发生的事情的清晰简明描述。
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: 实际行为
description: 对实际发生的事情的清晰简明描述。
validations:
required: true
- type: input
id: version
attributes:
label: LX Music 版本
description: 你使用什么版本的 LX Music?
placeholder: 例如 2.9.0
validations:
required: true
- type: input
id: last-known-working-version
attributes:
label: 最后正常的版本
description: 如果有,请在此处填写最后正常的版本。
placeholder: 例如 2.8.0
- type: input
id: operating-system-version
attributes:
label: 操作系统版本
description: |
你使用什么版本的操作系统?
在 macOS 上,单击「Apple 菜单 > 关于本机」;
在 Linux 上,执行 `lsb_release` 或 `uname -a` 命令;
在 Windows 上,单击「开始按钮 > 设置 > 系统 > 关于」。
placeholder: "例如 Windows 11 版本 24H2、macOS Sequoia 15.1.1 或 Ubuntu 24.10"
validations:
required: true
- type: textarea
id: additional-information
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者你所遇到的问题不容易重现,请在此处添加更多信息。(直接把图片/视频拖到编辑框即可添加图片/视频)
@@ -1,40 +1,38 @@
name: ✨ 功能请求
description: 为这个项目提出一个想法,请先查看常见问题及搜索issue列表中有无你要提的问题
description: 为这个项目提出一个想法,请先查看常见问题及搜索 Issue 列表中有无你要提的问题。
title: "[Feature]: "
body:
- type: checkboxes
id: check-answer
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作
description: 请确保你已完成以下所有操作。
options:
- label: 我已阅读常见问题(<https://lyswhut.github.io/lx-music-doc/desktop/faq>),但没有找到解决方案
- label: 我已阅读 [常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq),但没有找到解决方案。
required: true
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),但没有发现类似的问题
- label: 我已搜索 [Issue 列表](https://github.com/lyswhut/lx-music-desktop/issues?q=is%3Aissue+),但没有发现类似的问题。
required: true
- type: textarea
id: problem-description
attributes:
label: 问题描述
description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题
description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题。
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: 描述你想要的解决方案
description: 简洁明了地描述你要发生的事情
description: 简洁明了地描述你要发生的事情。
validations:
required: true
- type: textarea
id: alternatives-considered
attributes:
label: 描述你考虑过的替代方案
description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述
validations:
required: false
description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述。
- type: textarea
id: additional-information
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)
description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片/视频拖到编辑框即可添加图片/视频)
+9 -7
View File
@@ -7,19 +7,21 @@ runs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'
# - name: Get npm cache directory
# run: node -p -e '`NPM_CACHE_DIR=${require("child_process").execSync("npm config get cache").toString()}`' >> $GITHUB_ENV
# run: echo "NPM_CACHE_DIR=$(npm config get cache)" >> $GITHUB_ENV
# https://docs.npmjs.com/cli/v10/configuring-npm/folders#cache
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
with:
path: |
${{ env.NPM_CACHE }}
${{ env.ELECTRON_CACHE }}
${{ env.ELECTRON_BUILDERCACHE }}
key: ${{ runner.os }}-node-modules-cache-${{ hashFiles('package-lock.json') }}
path: ${{ env.NPM_CACHE }}
key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-modules-cache-
${{ runner.os }}-npm-cache-
- name: Install dependencies
run: npm ci
+32 -17
View File
@@ -5,6 +5,9 @@ on:
branches:
- beta
env:
IS_CI: 'true'
jobs:
# CheckCode:
# name: Lint Code
@@ -16,7 +19,7 @@ jobs:
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '18'
# node-version: 20
# - name: Cache file
# uses: actions/cache@v4
@@ -40,20 +43,24 @@ jobs:
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Build Package Setup x64
run: npm run pack:win:setup:x64
@@ -127,22 +134,26 @@ jobs:
name: Mac
runs-on: macos-latest
# needs: CheckCode
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Install python setuptools
run: python3 -m pip install setuptools
run: brew install python-setuptools
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Build Package dmg
run: |
@@ -170,10 +181,6 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Install package
@@ -182,11 +189,19 @@ jobs:
- name: Check out git repository
uses: actions/checkout@v4
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Build Package deb amd64
run: npm run pack:linux:deb:amd64
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: 20
- name: Install Dependencies
run: npm ci
+49 -23
View File
@@ -5,6 +5,9 @@ on:
branches:
- master
env:
IS_CI: 'true'
jobs:
# CheckCode:
# name: Lint Code
@@ -16,7 +19,7 @@ jobs:
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '18'
# node-version: 20
# - name: Cache file
# uses: actions/cache@v4
@@ -40,20 +43,27 @@ jobs:
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Show Env
run: echo "${{ env.NPM_CACHE }}"
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Release package
run: |
@@ -72,22 +82,26 @@ jobs:
Windows_7:
name: Windows
name: Windows_7
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Prepare win7 electron env
run: |
@@ -111,23 +125,29 @@ jobs:
Mac:
name: Mac
runs-on: macos-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Install python3 setuptools
run: python3 -m pip install setuptools
run: brew install python-setuptools
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Show Env
run: echo "${{ env.NPM_CACHE }}"
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Release package
run: |
@@ -145,10 +165,6 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Install package
@@ -157,11 +173,21 @@ jobs:
- name: Check out git repository
uses: actions/checkout@v4
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Show Env
run: echo "${{ env.NPM_CACHE }}"
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
run: |
git status --porcelain
npm run build
- name: Release package
run: |
+6 -1
View File
@@ -9,8 +9,10 @@ module.exports = {
'image-size',
'message2call',
'@types/ws',
'eslint',
'@types/node',
'electron-debug',
// 'eslint-config-standard-with-typescript',
// 'typescript', // https://github.com/microsoft/TypeScript/pull/54567
],
// target: 'newest',
@@ -27,5 +29,8 @@ module.exports = {
// target: 'minor',
// filter: [
// 'electron',
// 'eslint',
// 'electron-debug',
// '@types/node',
// ],
}
+114
View File
@@ -6,6 +6,120 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [2.9.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.8.0...v2.9.0) - 2024-08-24
### 新增
- 新增 设置-播放设置-是否将歌词显示在状态栏 设置,默认关闭,该功能只在 MacOS 下可用(#1940)
- 新增设置-播放详情页设置-延迟歌词滚动设置(#1985)
- 新增鼠标在音量按钮使用滚轮时可以调整音量大小的功能(#2000)
- 新增设置-下载设置-同时下载任务数设置(#1498)
- 新增 我的列表-歌曲右击菜单-歌曲换源 功能,换源后下次再播放该列表的该歌曲时将优先尝试播放所选源的歌曲,该功能允许你手动指定来源以解决自动换源失败或者换源不准确的问题
### 优化
- 优化侧栏图标显示,修复图标可能被裁切的问题(#1960)
- 托盘图标添加当前播放歌曲名字显示
- 优化本地歌曲内嵌封面过大时的加载方式
- 将下载歌曲的歌手信息中的分隔符从 `、` 替换为 `;` 以确保音乐元数据在写入时的兼容性和一致性(#1989 @qnnp-me)
### 修复
- 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923)
- 修复 OpenAPI 的 `lyricLineAllText` 在切换到无歌词的音乐时内容没有更新的问题(#1925)
- 修复切换音源时可能出现切换死循环的问题
- 尝试修复某些情况下播放音频时,处于播放状态但是进度条不走的问题
- 修复程序目录路径存在 `#` 或 `%` 时,自定义源、托盘等图标异常的问题(#1997)
### 变更
- 简化了应用退出行为,据测试,现在 linux 下,若启用了托盘,dock 右键菜单的 退出、关闭所有 之类的功能将不再退出程序,需改用托盘的退出按钮退出程序
- 现在如果在设置或者启动参数配置了代理服务,那么应用内的图片、音频加载,歌曲下载也将走代理
### 其他
- 更新 electron 到 v30.4.0
## [2.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.7.0...v2.8.0) - 2024-06-01
我们发布了关于 LX Music 项目发展调整与新项目计划的说明,
详情看: https://github.com/lyswhut/lx-music-desktop/issues/1912
### 新增
- 新增 设置-播放设置-使用设备能处理的最大声道数输出音频 设置(未启用时固定为2声道输出),由于这用到高级音频API,考虑到在某些设备上的兼容问题,默认禁用(#1873)
- 允许添加 `m4a`、`oga` 格式的本地歌曲到列表中(#1864)
- 开放API支持跨域请求(#1872 @Ceale)
- Scheme URL API新增 `music/searchPlay` 支持,用于搜索并播放指定的歌曲名字,详细入参请阅读 Scheme URL 支持文档(#1886)
### 优化
- 优化白色托盘图标显示,修复windows下托盘图标不清晰的问题(#1842)
### 修复
- 修复存在多级弹窗时的背景显示问题
- 增大在线导入自定义源文件的大小限制问题(#1857)
- 修复Mac下窗口出现残留阴影的问题,这解决了Mac下桌面歌词出现残留阴影的远古bug,感谢 @zclorne (#1869, Thanks @zclorne)
- 增大在线导入自定义源文件的大小限制,解决某些音源无法导入的问题(#1857)
- 修复Mac下即使开启了托盘, `cmd+w` 仍会中断播放的问题(#1844)
- 修复播放详情页的歌词无法使用触碰拖动的问题(#1865)
- 修复与优化繁体中文、英语翻译显示(#1845)
- 修复歌曲时文件名过长导致歌曲无法下载的问题(#1877)
- 修复文本提示气泡在内容过长时,文本未被换行而被截断的问题
- 修复翻页按钮栏切页按钮只显示前几页的问题
### 变更
- 设置-播放设置-优先播放320k音质选项改为“优先播放的音质”,允许选择更高优先播放的音质,如果歌曲及音源支持的话(#1839)
### 开放API变更
- `/status` 的入参现在与 `/subscribe-player-status` 保持一致
- `/status` 新增 `filter` 入参用于过滤返回的字段,并内置了默认值,与之前相比默认不再返回 `picUrl`
- `/status` 及 `/subscribe-player-status` 的可用字段名添加了 `lyricLineAllText`,它对应的值是当前句歌词及扩展歌词文本(扩展歌词包含翻译、罗马音等,按换行符分割)
详情看开放API接入文档
### 其他
- 更新 electron 到 v28.3.3
## [2.7.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.6.0...v2.7.0) - 2024-04-14
### 新增
- 主题编辑器添加“深色字体”选项,启用后将减少字体颜色梯度,各类字体(正文、标签字体等)颜色将更接近,这有助于解决创建全透明主题时可能出现的字体配色问题(#1799)
- 新增在线自定义源导入功能,允许通过http/https链接导入自定义源
- 新增HTTP开放API服务,默认关闭,该服务可以为第三方软件提供调用LX的能力,可用API看[说明文档](https://lyswhut.github.io/lx-music-doc/desktop/open-api)(#1824)
- 托盘菜单新增播放、切歌、收藏控制
- 添加当前软件版本所对应的代码提交版本、提交时间的显示,可到设置-版本更新查看
### 优化
- 主题设置默认折叠其他主题以优化进入设置界面时的性能
- 不再丢弃kg源逐行歌词(@helloplhm-qwq)
- 支持kw源排行榜显示大小(revert @Folltoshe #1460)
- 托盘菜单添加多语言支持(#1802)
- 优化本地歌曲换源匹配机制
### 修复
- 修复某些情况下歌曲加载时间过长时不会自动跳到下一首的问题
- 修复mg歌词在某些情况下获取失败的问题(#1783)
- 修复mg歌单搜索(@helloplhm-qwq)
- 修复kg最新评论无法获取的问题(@helloplhm-qwq)
- 修复更新超时弹窗在非更新阶段意外弹出的问题(#1797)
- 修复网络代理设置没有对自定义源的网络请求生效的问题(#1814)
### 移除
- 移除未使用的网络代理设置用户名、密码设置,实际上在 v1.20.0 起这两个设置就没有在被内部使用
### 其他
- 更新 electron 到 v28.3.0
## [2.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.5.0...v2.6.0) - 2024-02-01
提交祝大家新年快乐!
+1 -1
View File
@@ -1,6 +1,6 @@
# lx-music-desktop 常见问题
本文档已迁移到:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
本文档已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
<!--
在阅读本常见问题后,仍然无法解决你的问题,请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
+52 -62
View File
@@ -1,5 +1,7 @@
<p align="center"><a href="https://github.com/lyswhut/lx-music-desktop"><img width="200" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/icon.png" alt="lx-music logo"></a></p>
<h1 align="center">LX Music 桌面版</h1>
<p align="center">
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/release.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build/badge.svg" alt="Build status"></a>
@@ -28,102 +30,90 @@
[9]: https://img.shields.io/github/license/lyswhut/lx-music-desktop
[10]: https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE -->
<h2 align="center">洛雪音乐助手桌面版</h2>
<p align="center">一个基于 Electron & Vue 开发的音乐软件</p>
### 说明
一个基于 Electron + Vue 开发的音乐软件。
## 说明
所用技术栈:
- Electron 15+
- Electron 30+
- Vue 3
已支持的平台:
- Windows 7 及以上
- Mac OS
- Linux
- macOS
- Windows 7 及以上
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
使用常见问题请转至:[常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)
*移动版项目地址:https://github.com/lyswhut/lx-music-mobile*
目前本项目的原始发布地址只有**GitHub**及**蓝奏网盘**,其他渠道均为第三方转载发布,与本项目无关!
软件变化请查看[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)。
为了提高使用门槛,本软件内的默认设置、UI操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)
软件下载请查看 [GitHub Releases](https://github.com/lyswhut/lx-music-desktop/releases)。
#### Scheme URL支持
使用常见问题请参阅[桌面版常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)。
从v1.17.0起支持 Scheme URL,可以使用此功能从浏览器等场景下调用LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br>
目前本项目的原始发布地址只有 [**GitHub**](https://github.com/lyswhut/lx-music-desktop/releases),其他渠道均为第三方转载发布,与本项目无关!
若你想自己调用LX Music,可以看[Scheme URL支持](https://lyswhut.github.io/lx-music-doc/desktop/scheme-url)
为了提高使用门槛,本软件内的默认设置、UI 操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)。
#### 数据同步服务
### Scheme URL 支持
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
从 v1.17.0 起支持 Scheme URL,可以使用此功能在浏览器等场景下调用 LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用。
#### 启动参数
脚本安装地址:[LX Music 辅助脚本](https://greasyfork.org/zh-CN/scripts/438148)。
目前软件已支持的启动参数如下:
若你想自己调用 LX Music,可以参考文档「[Scheme URL 支持](https://lyswhut.github.io/lx-music-doc/desktop/scheme-url)」部分。
- `-proxy-server` 设置代理服务器,代理应用的所有流量
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器
- `-play` 启动时播放指定列表的音乐
- `-search` 启动软件时自动在搜索框搜索指定的内容
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration)
- `-dt` 以非透明模式启动(Disable Transparent)
- `-dhmkh` 禁用硬件媒体密钥处理(Disable Hardware Media Key Handling)
### 数据同步服务
启动参数的详细说明请看[启动参数说明](https://lyswhut.github.io/lx-music-doc/desktop/run-params)
从 v2.2.0 起,我们发布了一个独立的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme)。如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明。
#### 数据存储路径
### 开放 API 支持
从 v2.7.0 起支持开放 API 服务。启用该功能后,将会在本地启动一个 HTTP 服务,提供播放器相关的接口供第三方软件调用,详情看文档「[开放 API 服务](https://lyswhut.github.io/lx-music-doc/desktop/open-api)」部分。
### 数据存储目录
默认情况下,软件的数据存储在:
- Windows:`%APPDATA%/lx-music-desktop`
- Linux:`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
- macOS:`~/Library/Application Support/lx-music-desktop`
- Windows:`%APPDATA%/lx-music-desktop`
在Windows平台下,若程序目录下存在`portable`目录,则自动使用此目录作为数据存储目录(v1.17.0新增)。
在 Windows 平台上,若程序文件夹中存在 `portable` 文件夹,则自动使用此文件夹作为数据存储文件夹(适用于 v1.17.0 及以上版本)。
### 源码使用方法
## 用户界面
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
<p><img width="100%" src="./doc/images/app.png" alt="lx-music desktop UI"></p>
### UI界面
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
### 常见问题
常见问题已移至:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
### 贡献代码
## 贡献代码
本项目欢迎 PR,但为了 PR 能顺利合并,需要注意以下几点:
- 对于添加新功能的PR,建议在PR前先创建issue说明,以确认该功能是否确实需要
- 对于修复Bug PR,请提供修复前后的说明及重现方式
- 其他类型的PR则适当附上说明
- 对于添加新功能的 PR,建议在提交 PR 前先创建 Issue 进行说明,以确认该功能是否确实需要。
- 对于修复 bug 的 PR,请提供修复前后的说明及重现方式。
- 对于其他类型的 PR,则适当附上说明。
贡献代码步骤:
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境
2. 克隆本仓库代码并切换到`dev`分支开发
3. 提交PR至`dev`分支
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境;
2. 克隆本仓库代码并切换至 `dev` 分支进行开发;
3. 提交 PR 至 `dev` 分支。
### 项目协议
## 源码使用方法
请参阅:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
## 项目协议
本项目基于 [Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
---
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
*词语约定:本协议中的“本项目”指 LX Music(洛雪音乐助手)桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。*
#### 一、数据来源
### 一、数据来源
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据(与未登录状态在官方平台 APP 获取的数据相同),经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
@@ -131,40 +121,40 @@
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
#### 二、版权数据
### 二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 **24 小时内** 清除使用本项目的过程中所产生的版权数据。
#### 三、音乐平台别名
### 三、音乐平台别名
3.1 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意。如果官方音乐平台觉得不妥,可联系本项目更改或移除。
#### 四、资源使用
### 四、资源使用
4.1 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网。如果出现侵权可联系本项目移除。
#### 五、免责声明
### 五、免责声明
5.1 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
#### 六、使用限制
### 六、使用限制
6.1 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流。本项目不对项目内的技术可能存在违反当地法律法规的行为作保证。
6.2 **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
#### 七、版权保护
### 七、版权保护
7.1 音乐平台不易,请尊重版权,支持正版。
#### 八、非商业性质
### 八、非商业性质
8.1 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
#### 九、接受协议
### 九、接受协议
9.1 若你使用了本项目,将代表你接受本协议。
9.1 若你使用了本项目,即代表你接受本协议。
---
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)
若对此有疑问请 mail to: lyswhut+qq.com (请将 `+` 替换为 `@`)
+15 -9
View File
@@ -10,6 +10,7 @@ const afterPack = require('./build-after-pack')
*/
const options = {
appId: 'cn.toside.music.desktop',
productName: 'lx-music-desktop',
beforePack,
afterPack,
protocols: {
@@ -65,7 +66,7 @@ const winOptions = {
oneClick: false,
language: '2052',
allowToChangeInstallationDirectory: true,
differentialPackage: true,
// differentialPackage: true,
license: './licenses/license.rtf',
shortcutName: 'LX Music',
},
@@ -81,6 +82,11 @@ const linuxOptions = {
icon: './resources/icons',
category: 'Utility;AudioVideo;Audio;Player;Music;',
desktop: {
// https://www.electron.build/app-builder-lib.interface.linuxdesktopfile
// https://www.electronjs.org/docs/latest/tutorial/linux-desktop-actions
// https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files
entry: {
Name: 'LX Music',
'Name[zh_CN]': 'LX Music',
'Name[zh_TW]': 'LX Music',
@@ -89,6 +95,7 @@ const linuxOptions = {
StartupNotify: 'false',
},
},
},
appImage: {
license: './licenses/license_zh.txt',
category: 'Utility;AudioVideo;Audio;Player;Music;',
@@ -106,23 +113,22 @@ const macOptions = {
},
dmg: {
window: {
width: 600,
height: 400,
width: 530,
height: 380,
},
contents: [
{
x: 106,
y: 252,
name: 'LX Music',
x: 140,
y: 200,
},
{
x: 490,
y: 252,
x: 390,
y: 200,
type: 'link',
path: '/Applications',
},
],
title: '洛雪音乐助手 v${version}',
title: 'LX Music v${version}',
},
}
+1
View File
@@ -4,6 +4,7 @@ module.exports = {
modules: {
localIdentName: isDev ? '[path][name]__[local]--[hash:base64:5]' : '[hash:base64:5]',
exportLocalsConvention: 'camelCase',
namedExport: false,
},
sourceMap: isDev,
}
+3 -3
View File
@@ -7,12 +7,12 @@ const baseConfig = require('./webpack.config.base')
// const { dependencies } = require('../../package.json')
const buildConfig = require('../webpack-build-config')
// const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
devtool: false,
entry: {
main: path.join(__dirname, '../../src/main/index.ts'),
// 'dbService.worker': path.join(__dirname, '../../src/main/worker/dbService/index.ts'),
@@ -45,6 +45,6 @@ module.exports = merge(baseConfig, {
maxAssetSize: 1024 * 1024 * 20,
},
optimization: {
minimize: buildConfig.minimize,
minimize: false,
},
})
@@ -5,6 +5,11 @@ const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const gitInfo = {
commit_id: '',
commit_date: '',
}
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
@@ -18,6 +23,8 @@ module.exports = merge(baseConfig, {
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
COMMIT_ID: `"${gitInfo.commit_id}"`,
COMMIT_DATE: `"${gitInfo.commit_date}"`,
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
@@ -1,4 +1,5 @@
const path = require('path')
const { execSync } = require('child_process')
const webpack = require('webpack')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
@@ -12,6 +13,19 @@ const buildConfig = require('../webpack-build-config')
// let whiteListedModules = ['vue', 'vue-router', 'vuex', 'vue-i18n']
const gitInfo = {
commit_id: '',
commit_date: '',
}
try {
if (!execSync('git status --porcelain').toString().trim()) {
gitInfo.commit_id = execSync('git log -1 --pretty=format:"%H"').toString().trim()
gitInfo.commit_date = execSync('git log -1 --pretty=format:"%ad" --date=iso-strict').toString().trim()
} else if (process.env.IS_CI) {
throw new Error('Working directory is not clean')
}
} catch {}
module.exports = merge(baseConfig, {
mode: 'production',
@@ -45,6 +59,8 @@ module.exports = merge(baseConfig, {
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
COMMIT_ID: `"${gitInfo.commit_id}"`,
COMMIT_DATE: `"${gitInfo.commit_date}"`,
}),
],
optimization: {
+16 -13
View File
@@ -16,9 +16,10 @@ const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
const rendererScriptConfig = require('./renderer-scripts/webpack.config.dev')
const { Arch } = require('electron-builder')
const replaceLib = require('./build-before-pack')
const treeKill = require('tree-kill')
const { debounce } = require('./utils')
let electronProcess = null
let manualRestart = false
let hotMiddlewareRenderer
let hotMiddlewareRendererLyric
@@ -60,7 +61,9 @@ function startRenderer() {
},
setupMiddlewares(middlewares, devServer) {
devServer.app.use(hotMiddlewareRenderer)
setImmediate(() => {
devServer.middleware.waitUntilValid(resolve)
})
return middlewares
},
@@ -106,7 +109,9 @@ function startRendererLyric() {
},
setupMiddlewares(middlewares, devServer) {
devServer.app.use(hotMiddlewareRenderer)
setImmediate(() => {
devServer.middleware.waitUntilValid(resolve)
})
return middlewares
},
}, compiler)
@@ -132,9 +137,11 @@ function startRendererScripts() {
}
function startMain() {
let firstRun = true
return new Promise((resolve, reject) => {
// mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
// mainConfig.mode = 'development'
const runElectronDelay = debounce(startElectron, 200)
const compiler = webpack(mainConfig)
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
@@ -146,23 +153,19 @@ function startMain() {
compiler.watch({}, (err, stats) => {
if (err) {
console.log(err)
reject(err)
return
}
// logStats('Main', stats)
if (electronProcess && electronProcess.kill) {
manualRestart = true
process.kill(electronProcess.pid)
electronProcess = null
startElectron()
setTimeout(() => {
manualRestart = false
}, 5000)
if (electronProcess) {
electronProcess.removeAllListeners()
treeKill(electronProcess.pid)
}
if (firstRun) {
firstRun = false
resolve()
} else runElectronDelay()
})
})
}
@@ -191,7 +194,7 @@ function startElectron() {
})
electronProcess.on('close', () => {
if (!manualRestart) process.exit()
process.exit()
})
}
+18 -1
View File
@@ -31,7 +31,12 @@ exports.mergeCSSLoader = beforeLoader => {
esModule: false,
},
},
'css-loader',
{
loader: 'css-loader',
options: {
esModule: false,
},
},
'postcss-loader',
],
},
@@ -63,3 +68,15 @@ exports.logStats = (proc, data) => {
console.log(log)
}
exports.debounce = (fn, delay = 100) => {
let timer = null
let _args
return (...args) => {
_args = args
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn(..._args)
}, delay)
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 487 KiB

+107 -97
View File
@@ -1,16 +1,19 @@
{\rtf1\adeflang1025\ansi\ansicpg936\uc2\adeff0\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1033\deflangfe2052\themelang1033\themelangfe2052\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}
{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'cb\'ce\'cc\'e5{\*\falt SimSun};}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}
{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@\'cb\'ce\'cc\'e5;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\f36\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}@\'cb\'ce\'cc\'e5;}
{\f45\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}@\'b5\'c8\'cf\'df;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbmajor\f31501\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}{\fhimajor\f31502\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbminor\f31505\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}{\fhiminor\f31506\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f65\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f66\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
{\f68\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f69\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f70\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f71\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}
{\f72\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f73\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f177\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f385\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}
{\f386\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f388\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f389\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f392\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}
{\f393\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f487\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f46\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f47\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f49\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f50\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f51\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f52\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f53\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f54\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f66\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f67\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
{\f69\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f70\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f71\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f72\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}
{\f73\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f74\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f178\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f386\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}
{\f387\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f389\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f390\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f393\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}
{\f394\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f408\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}{\f406\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}
{\f407\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\f409\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}{\f488\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\f498\fbidi \fnil\fcharset0\fprq2 @DengXian Western;}
{\f496\fbidi \fnil\fcharset238\fprq2 @DengXian CE;}{\f497\fbidi \fnil\fcharset204\fprq2 @DengXian Cyr;}{\f499\fbidi \fnil\fcharset161\fprq2 @DengXian Greek;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}{\fdbmajor\f31518\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}
@@ -38,12 +41,12 @@
\fs21\lang1033\langfe2052\kerning2\loch\f31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext15 \slink16 \sunhideused Plain Text;}{\*\cs16 \additive \rtlch\fcs1 \af2 \ltrch\fcs0 \loch\f31505\hich\af2 \sbasedon10 \slink15 \slocked
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}{\*\cs17 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid9533173 Hyperlink;}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21
\sbasedon10 \ssemihidden \sunhideused \styrsid9533173 Unresolved Mention;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf22 \sbasedon10 \ssemihidden \sunhideused \styrsid9533173 FollowedHyperlink;}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3758332\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid14511311\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0
\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2023\mo10\dy5\hr14\min42}{\version10}{\edmins4}{\nofpages1}{\nofwords193}{\nofchars1105}
{\nofcharsws1296}{\vern77}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3758332\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid14511311\rsid15225067\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0
\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2024\mo12\dy7\hr14\min56}{\version11}{\edmins5}{\nofpages2}{\nofwords195}
{\nofchars1116}{\nofcharsws1309}{\vern77}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
\deftab420\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\formshade\horzdoc\dgmargin\dghspace180\dgvspace156
\dghorigin450\dgvorigin0\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind5\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\dghorigin2253\dgvorigin1440\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind1\viewscale160\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat {\upr{\*\fchars
!%),.:\'3b>?]\'7d\'a1\'e9\'a1\'a7\'a1\'e3\'a1\'a4\'a1\'a6\'a1\'a5\'a8\'44\'a1\'ac\'a1\'af\'a1\'b1\'a1\'ad\'a1\'eb\'a1\'e4\'a1\'e5?\'a1\'e6\'a1\'c3\'a1\'a2\'a1\'a3\'a1\'a8\'a1\'b5\'a1\'b7\'a1\'b9\'a1\'bb\'a1\'bf\'a1\'b3\'a1\'bd\'a8\'95\'a6\'e1\'a6\'e3\'a6\'e7\'a6\'e5\'a6\'eb\'a9\'77\'a9\'79\'a9\'7b\'a3\'a1\'a3\'a2\'a3\'a5\'a3\'a7\'a3\'a9\'a3\'ac\'a3\'ae\'a3\'ba\'a3\'bb\'a3\'bf\'a3\'dd\'a3\'e0\'a3\'fc\'a3\'fd\'a1\'ab\'a1\'e9
}{\*\ud\uc0{\*\fchars
@@ -53,123 +56,130 @@ $([\'7b{\uc2\u163 \'a1\'ea\u165 \'a3\'a4\'a1\'a4\'a1\'ae\'a1\'b0\'a1\'b4\'a1\'b6
\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery851\footery992\colsx425\endnhere\sectlinegrid312\sectspecifyl\sectrsid2109456\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang
{\pntxta \dbch .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta \dbch )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl6
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl9
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf
\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2\'d0\'d0\'a3\'ac\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b5\'c4
\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid15225067 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf
\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2\'d0\'d0\'a3\'ac\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b5\'c4
\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'cf\'ee\'c4\'bf\'a1\'b1\'d6\'b8
\'c2\'e5\'d1\'a9\'d2\'f4\'c0\'d6\'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb\'a1\'b0\'ca\'b9\'d3\'c3\'d5\'df\'a1\'b1\'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb\'a1\'b0\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6
\'c6\'bd\'cc\'a8\'a1\'b1\'d6\'b8\'b6\'d4\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3
\'b3\'c6\'a3\'bb\'a1\'b0\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'b1\'d6\'b8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0
\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'cf\'ee\'c4\'bf\'a1\'b1\'d6\'b8}
{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 LX Music }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\loch\af13\hich\af13\dbch\f13 \'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'d5\'df}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d6\'b8\'b6\'d4
\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3\'b3\'c6\'a3\'bb}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d6\'b8\'b0\'fc
\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'bb\'a1\'a2\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d2\'bb\'a1\'a2\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 1.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b8\'f7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'d4\'da\'cf\'df
\par \hich\af13\dbch\af13\loch\f13 1.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b8\'f7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'d4\'da\'cf\'df
\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7\'b4\'d3\'c6\'e4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'a8\'d3\'eb\'ce\'b4\'b5\'c7\'c2\'bc\'d7\'b4\'cc\'ac\'d4\'da\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 APP}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 APP }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13
\'bb\'f1\'c8\'a1\'b5\'c4\'ca\'fd\'be\'dd\'cf\'e0\'cd\'ac\'a3\'a9\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be
\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.2 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b1\'be\'c9\'ed\'c3\'bb\'d3\'d0\'bb\'f1\'c8\'a1\'c4\'b3\'b8\'f6
\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 1.2 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b1\'be\'c9\'ed\'c3\'bb\'d3\'d0\'bb\'f1\'c8\'a1\'c4\'b3\'b8\'f6
\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b5\'c4\'c4\'dc\'c1\'a6\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'ca\'b9\'d3\'c3\'b5\'c4\'d4\'da\'cf\'df\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'c0\'b4\'d7\'d4\'c8\'ed\'bc\'fe\'c9\'e8\'d6\'c3\'c4\'da}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c0\'b4\'d4\'b4}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c9\'e8\'d6\'c3
\'cb\'f9\'d1\'a1\'d4\'f1\'b5\'c4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c0\'b4\'d4\'b4}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c9\'e8\'d6\'c3
\'cb\'f9\'d1\'a1\'d4\'f1\'b5\'c4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'b5\'c4\'d4\'da\'cf\'df\'c1\'b4\'bd\'d3\'a1\'a3\'c0\'fd\'c8\'e7\'b2\'a5\'b7\'c5\'c4\'b3\'ca\'d7\'b8\'e8\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d7\'f6\'b5\'c4\'d6\'bb\'ca\'c7\'bd\'ab\'cf\'a3\'cd\'fb\'b2\'a5
\'b7\'c5\'b5\'c4\'b8\'e8\'c7\'fa\'c3\'fb\'d7\'d6\'a1\'a2\'b8\'e8\'ca\'d6\'c3\'fb\'d7\'d6\'b5\'c8\'d0\'c5\'cf\'a2\'b4\'ab\'b5\'dd\'b8\'f8}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'a3\'ac\'c8\'f4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'c1\'cb\'d2\'bb\'b8\'f6\'c1\'b4\'bd\'d3\'a3\'ac\'d4\'f2\'b1\'be\'cf\'ee\'c4\'bf\'bd\'ab\'c8\'cf\'ce\'aa\'d5\'e2
\'b7\'c5\'b5\'c4\'b8\'e8\'c7\'fa\'c3\'fb\'d7\'d6\'a1\'a2\'b8\'e8\'ca\'d6\'c3\'fb\'d7\'d6\'b5\'c8\'d0\'c5\'cf\'a2\'b4\'ab\'b5\'dd\'b8\'f8}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'a3\'ac\'c8\'f4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'c1\'cb\'d2\'bb\'b8\'f6\'c1\'b4\'bd\'d3\'a3\'ac\'d4\'f2\'b1\'be\'cf\'ee\'c4\'bf\'bd\'ab\'c8\'cf\'ce\'aa\'d5\'e2
\'be\'cd\'ca\'c7\'b8\'c3\'b8\'e8\'c7\'fa\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b6\'f8\'bd\'f8\'d0\'d0\'ca\'b9\'d3\'c3\'a3\'ac\'d6\'c1\'d3\'da\'d5\'e2\'ca\'c7\'b2\'bb\'ca\'c7\'d5\'fd\'c8\'b7\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b1\'be\'cf\'ee
\'c4\'bf\'ce\'de\'b7\'a8\'d0\'a3\'d1\'e9\'c6\'e4\'d7\'bc\'c8\'b7\'d0\'d4\'a3\'ac\'cb\'f9\'d2\'d4\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc\'bb\'e1\'b3\'f6\'cf\'d6\'cf\'a3\'cd\'fb\'b2\'a5\'b7\'c5\'b5\'c4
\'d2\'f4\'c6\'b5\'d3\'eb\'ca\'b5\'bc\'ca\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'b2\'bb\'b6\'d4\'d3\'a6\'bb\'f2\'d5\'df\'ce\'de\'b7\'a8\'b2\'a5\'b7\'c5\'b5\'c4\'ce\'ca\'cc\'e2\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.3 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b7\'c7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'ca\'fd\'be\'dd
\'d2\'f4\'c6\'b5\'d3\'eb\'ca\'b5\'bc\'ca\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'b2\'bb\loch\af13\hich\af13\dbch\f13 \'b6\'d4\'d3\'a6\'bb\'f2\'d5\'df\'ce\'de\'b7\'a8\'b2\'a5\'b7\'c5\'b5\'c4\'ce\'ca\'cc\'e2\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 1.3 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b7\'c7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'ca\'fd\'be\'dd
\'a3\'a8\'c0\'fd\'c8\'e7\'ce\'d2\'b5\'c4\'ca\'d5\'b2\'d8\'c1\'d0\'b1\'ed\'a3\'a9\'c0\'b4\'d7\'d4\'ca\'b9\'d3\'c3\'d5\'df\'b1\'be\'b5\'d8\'cf\'b5\'cd\'b3\'bb\'f2\'d5\'df\'ca\'b9\'d3\'c3\'d5\'df\'c1\'ac\'bd\'d3\'b5\'c4\'cd\'ac\'b2\'bd\'b7\'fe\'ce\'f1
\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'d5\'e2\'d0\'a9\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'fe\'a1\'a2\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b6\'fe\'a1\'a2\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 2.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
\par \hich\af13\dbch\af13\loch\f13 2.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0\'c8\'a8\'a1\'a3\'ce\'aa
\'c1\'cb\'b1\'dc\'c3\'e2\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 **24}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\hich\af13\dbch\af13\loch\f13 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'cb\'f9
\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'c1\'cb\'b1\'dc\'c3\'e2\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 **24 }{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'cb\'f9
\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'fd\'a1\'a2\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0\'c3\'fb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c8\'fd\'a1\'a2\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0\'c3\'fb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 3.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\par \hich\af13\dbch\af13\loch\f13 3.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a1\'a3\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4
\'c0\'d6\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'cb\'c4\'a1\'a2\'d7\'ca\'d4\'b4\'ca\'b9\'d3\'c3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'cb\'c4\'a1\'a2\'d7\'ca\'d4\'b4\'ca\'b9\'d3\'c3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 4.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
\par \hich\af13\dbch\af13\loch\f13 4.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a1\'a3\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ce\'e5\'a1\'a2\'c3\'e2\'d4\'f0\'c9\'f9\'c3\'f7}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'ce\'e5\'a1\'a2\'c3\'e2\'d4\'f0\'c9\'f9\'c3\'f7}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 5.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
\par \hich\af13\dbch\af13\loch\f13 5.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
\'c0\'a8\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1
\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'cb\'f0\'ba\'a6\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d2\'f2\'c9\'cc\'d3\'fe\'cb\'f0\'ca\'a7\'a1\'a2\'cd\'a3\'b9\'a4
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\'cb\'f9\loch\af13\hich\af13\dbch\f13 \'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0
\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\'cb\'f9\'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0\'ba\'a6\'bb\'f2\'cb\'f0
\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c1\'f9\'a1\'a2\'ca\'b9\'d3\'c3\'cf\'de\'d6\'c6}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c1\'f9\'a1\'a2\'ca\'b9\'d3\'c3\'cf\'de\'d6\'c6}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 6.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 6.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\loch\af13\hich\af13\dbch\f13 \'c3\'e6\'cf\'f2\'c8\'ab\'ca\'c0\'bd\'e7\'c8\'cb\'d3\'c3\'d7\'f7\'b6\'d4\'bc\'bc\'ca\'f5\'b5\'c4\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'a1\'a3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'bc\'bc\'ca\'f5
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 6.2 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf
\'b2\'bb\'b3\'d0\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 6.2 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\loch\af13\hich\af13\dbch\f13 \'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac
\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b3\'d0\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid3758332 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c6\'df
\'a1\'a2\'b0\'e6\'c8\'a8\'b1\'a3\'bb\'a4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c6\'df\'a1\'a2\'b0\'e6\'c8\'a8\'b1\'a3\'bb\'a4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 7.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'c7\'eb\'d7\'f0\'d6\'d8\'b0\'e6\'c8\'a8
\'a3\'ac\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par \hich\af13\dbch\af13\loch\f13 7.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'c7\'eb\'d7\'f0\'d6\'d8\'b0\'e6
\'c8\'a8\'a3\'ac\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b0\'cb\'a1\'a2\'b7\'c7\'c9\'cc\'d2\'b5\'d0\'d4\'d6\'ca}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b0\'cb\'a1\'a2\'b7\'c7\'c9\'cc\'d2\'b5\'d0\'d4\'d6\'ca}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 8.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'bd\'f6\'d3\'c3\'d3\'da\'b6\'d4\'bc\'bc\'ca\'f5\'bf\'c9\'d0\'d0\'d0\'d4
\'b5\'c4\'cc\'bd\'cb\'f7\'bc\'b0\'d1\'d0\'be\'bf\'a3\'ac\'b2\'bb\'bd\'d3\'ca\'dc\'c8\'ce\'ba\'ce\'c9\'cc\'d2\'b5\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'b9\'e3\'b8\'e6\'b5\'c8\'a3\'a9\'ba\'cf\'d7\'f7\'bc\'b0\'be\'e8\'d4\'f9\'a1\'a3}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par \hich\af13\dbch\af13\loch\f13 8.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'bd\'f6\'d3\'c3\'d3\'da\'b6\'d4\'bc\'bc\'ca\'f5\'bf\'c9\'d0\'d0
\'d0\'d4\'b5\'c4\'cc\'bd\'cb\'f7\'bc\'b0\'d1\'d0\'be\'bf\'a3\'ac\'b2\'bb\'bd\'d3\'ca\'dc\'c8\'ce\'ba\'ce\'c9\'cc\'d2\'b5\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'b9\'e3\'b8\'e6\'b5\'c8\'a3\'a9\'ba\'cf\'d7\'f7\'bc\'b0\'be\'e8\'d4\'f9
\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'be\'c5\'a1\'a2\'bd\'d3\'ca\'dc\'d0\'ad\'d2\'e9}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'be\'c5\'a1\'a2\'bd\'d3\'ca\'dc\'d0\'ad\'d2\'e9}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 9.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'c4\'e3\'ca\'b9\'d3\'c3\'c1\'cb\'b1\'be\'cf\'ee\'c4\'bf\'a3\'ac\'bd\'ab\'b4\'fa\'b1\'ed
\'c4\'e3\'bd\'d3\'ca\'dc\'b1\'be\'d0\'ad\'d2\'e9\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid14511311
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 * }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\loch\af13\hich\af13\dbch\f13 \'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa
\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid9533173
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid15226681
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \hich\af13\dbch\af13\loch\f13 By: }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par \hich\af13\dbch\af13\loch\f13 9.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'c4\'e3\'ca\'b9\'d3\'c3\'c1\'cb\'b1\'be\'cf\'ee\'c4\'bf\'a3\'ac\'bc\'b4\'b4\'fa
\'b1\'ed\'c4\'e3\'bd\'d3\'ca\'dc\'b1\'be\'d0\'ad\'d2\'e9\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa
\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067
\par
\par \hich\af13\dbch\af13\loch\f13 By: }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15225067\charrsid15225067 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af0 \ltrch\fcs0
\insrsid12910709\charrsid15225067
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
@@ -311,8 +321,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000c03d
9b1957f7d901feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000f065
8a1b7548db01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000105000000000000}}
+27 -23
View File
@@ -1,48 +1,52 @@
This project is issued based on the Apache License 2.0 license. The following protocols are supplemented by Apache License 2.0.
This project is issued based on the Apache License 2.0 license. The following agreements are supplemental to the Apache License 2.0. In the event of a conflict, the following agreements shall prevail.
Words stipulate: "this project" in this agreement refers to the Luoxue Music Desktop Edition project; "user" refers to the user who signed this agreement; the "official music platform" refers to The official platform of the music source is collectively referred to; "copyright data" refers to the data that includes but not limited to images, audio, names, etc.
DEFINITIONS:
1. Data source
"This Project" refers to the LX Music Desktop Edition (aka lx-music-desktop) project. "User" refers to the user who signed this agreement. "Official Music Streaming Service" refers to collectively the official streaming service corresponding to the music source. "Copyrighted Data" refers to data including, but not limited to, pictures, audio, names, etc., for which others own the copyright.
1.1 The principle of online data sources of various official platforms of this project is to draw data from its open server (the same data obtained from the unsproting state in the official platform app). After simply screening and merging data, this project Responsible for the legitimacy and accuracy of the data.
1.2 The ability of this project itself does not obtain a certain audio data. The online audio data source used in this project comes from the online link of the "Source" selected in the software settings. For example, when playing a song, the project only transmits information such as the song name, singer name and other information to be played to "source". If the "source" returns a link, this project will think that this is the audio data of the song For use, as for whether this is the correct audio data, this project cannot verify its accuracy, so the audio that you want to play may occur during the process of using this project.
1.3 The unofficial platform data (such as my collection list) of this project comes from synchronous services connected by the user's local system or user connection. This project is not responsible for the legality and accuracy of these data.
1. Data Source
2. Copyright data
1.1 The principle of the online data sources of the various official streaming services of this project is to draw data from its open servers (the same as the data obtained in the unlogged-in state in the official streaming service app). After simply screening and merging the data, this project is responsible for the legitimacy and accuracy of the data.
2.1 During the process of using this project, copyright data may be generated. For these copyright data, this project does not have their ownership. In order to avoid infringement, users must remove copyright data generated during the process of using this project within ** 24 hours.
1.2 The ability of this project itself does not obtain certain audio data. The online audio data source used in this project comes from the online link of the "source" selected in the software settings. For example, when playing a song, this project only transmits information such as the song name, singer name, and other information to be played to "source". If the "source" returns a link, this project will think that this is the audio data of the song for use, as for whether this is the correct audio data, this project cannot verify its accuracy, so, in the process of using this project, there may be a problem that the audio you want to play does not correspond to the audio you actually play, or you cannot play it.
3. Alias of Music Platform
1.3 The unofficial streaming service data (such as my collection list) of this project comes from synchronous services connected by the user's local system or user connection. This project is not responsible for the legality and accuracy of this data.
3.1 The official music platform in this project is named a name for the official music platform in this project, without maliciousness. If the official music platform feels inappropriate, you can contact this project to change or remove.
2. Copyrighted Data
Fourth, resource use
2.1 During the process of using this project, copyrighted data may be generated. For this copyrighted data, this project does not have their ownership. In order to avoid infringement, users must remove copyrighted data generated during the process of using this project ** within 24 hours **.
4.1 The parts used in this project include, but not limited to fonts, pictures and other resources from the Internet. If infringement occurs, you can contact this project.
3. Alias of Music Streaming Service
3.1 The official music streaming service alias within this project is a term used within this project to refer to the official music streaming service and does not contain malicious intent. If the operator of the official music streaming service feels it is inappropriate, you can contact this project to request changes or removals.
4. Use of Resources
4.1 The parts used in this project include, but are not limited to, fonts, pictures, and other resources from the Internet. If infringement occurs, you can contact this project.
5. Disclaimer
5.1 The use of this project includes any direct, indirect, special, accidental, or result damage due to any nature caused by this agreement or use or unable to use this item (including but not limited to the loss of goodwill, stop work, computer, computer Damage compensation caused by faults or faults, or any or all other commercial damage or losses) is responsible for users.
5.1 The use of this project includes any direct, indirect, special, accidental, or result damage due to any nature caused by this agreement or use or inability to use this item (including but not limited to the loss of goodwill, stop work, computer, computer Damage compensation caused by faults or any or all other commercial damage or losses) is the responsibility of users.
6. Use restrictions
6. Use Restrictions
6.1 This project is completely free, and open source is published in GitHub for the learning exchanges of technology for people all over the world. This project does not guarantee that the technology in the project may violate local laws and regulations.
6.1 This project is completely free and open source and is published on GitHub for the learning exchanges of technology for people all over the world. This project does not guarantee that the technology in this project may violate local laws and regulations.
6.2 ** This project is prohibited in violation of local laws and regulations. ** For the user's use of any illegal and illegal acts caused by the use of the project if the user is not allowed to be allowed to the user, it is undertaken by the user. Sexual responsibility.
6.2 ** This project is prohibited in violation of local laws and regulations. ** The user is solely responsible for any violation of law caused by the use of this project that the user knows or does not know is not permitted by local law and regulations.
7. Copyright protection
7. Copyright Protection
7.1 Music platform is not easy, please respect the copyright and support genuine.
7.1 The operation of a music streaming service is not an easy task. Please respect copyrights and support the genuine.
8. Non -commercial nature
8. Non-commercial Nature
8.1 This project is only used for exploring and research on technical feasibility, and does not accept any business (including but not limited to advertising, etc.) cooperation and donations.
8.1 This project is only used for exploring and research on technical feasibility and does not accept any business (including but not limited to advertising, etc.) cooperation and donations.
9. Accepting agreement
9. Accepting Agreement
9.1 If you use this project, it will represent you accept this agreement.
* If the agreement is updated, you will not be notified separately, you can check the open source address.
* The content of this agreement is translated from the Chinese version of the agreement
* If the agreement is updated, you will not be notified separately. You can check it out by visiting the project address.
* The content of this agreement is translated from the Chinese version of the agreement.
By: lyswhut
+4 -2
View File
@@ -1,11 +1,13 @@
本项目基于 Apache License 2.0 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
词语约定:本协议中的“本项目”指 LX Music 桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
一、数据来源
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据(与未登录状态在官方平台 APP 获取的数据相同),经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
二、版权数据
@@ -40,7 +42,7 @@
九、接受协议
9.1 若你使用了本项目,将代表你接受本协议。
9.1 若你使用了本项目,即代表你接受本协议。
* 若协议更新,恕不另行通知,可到开源地址查看。
+5998 -18876
View File
File diff suppressed because it is too large Load Diff
+57 -52
View File
@@ -1,9 +1,8 @@
{
"name": "lx-music-desktop",
"version": "2.7.0-beta.2",
"version": "2.10.0-beta.12",
"description": "一个免费的音乐查找助手",
"main": "./dist/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x64 && npm run pack:win:setup:x86 && npm run pack:win:setup:arm64 && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
@@ -108,99 +107,102 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.24.3",
"@babel/eslint-parser": "^7.24.1",
"@babel/core": "^7.26.7",
"@babel/eslint-parser": "^7.26.5",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.24.1",
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.24.3",
"@babel/preset-typescript": "^7.24.1",
"@tsconfig/recommended": "^1.0.3",
"@types/better-sqlite3": "^7.6.9",
"@babel/plugin-transform-modules-umd": "^7.25.9",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.7",
"@babel/preset-typescript": "^7.26.0",
"@tsconfig/recommended": "^1.0.8",
"@types/better-sqlite3": "^7.6.12",
"@types/needle": "^3.3.0",
"@types/node": "^20.17.16",
"@types/tunnel": "^0.0.7",
"@types/ws": "8.5.4",
"@volar/vue-language-plugin-pug": "^1.6.5",
"@vue/language-plugin-pug": "^2.0.7",
"babel-loader": "^9.1.3",
"browserslist": "^4.23.0",
"@vue/language-plugin-pug": "^2.2.0",
"babel-loader": "^9.2.1",
"browserslist": "^4.24.4",
"chalk": "^4.1.2",
"changelog-parser": "^3.0.1",
"copy-webpack-plugin": "^12.0.2",
"core-js": "^3.36.1",
"core-js": "^3.40.0",
"cross-env": "^7.0.3",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"del": "^6.1.1",
"electron": "^27.3.7",
"electron-builder": "^24.13.3",
"electron": "^32.3.0",
"electron-builder": "^26.0.0-alpha.11",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.715",
"electron-updater": "^6.1.8",
"eslint": "^8.57.0",
"electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03",
"electron-to-chromium": "^1.5.88",
"electron-updater": "^6.4.0-alpha.4",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
"eslint-plugin-html": "^8.0.0",
"eslint-plugin-vue": "^9.23.0",
"eslint-plugin-html": "^8.1.2",
"eslint-plugin-vue": "^9.32.0",
"eslint-plugin-vue-pug": "^0.6.2",
"eslint-webpack-plugin": "^4.1.0",
"html-webpack-plugin": "^5.6.0",
"less": "^4.2.0",
"eslint-webpack-plugin": "^4.2.0",
"html-webpack-plugin": "^5.6.3",
"less": "^4.2.2",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.8.1",
"node-loader": "^2.0.0",
"postcss": "^8.4.38",
"mini-css-extract-plugin": "^2.9.2",
"node-loader": "^2.1.0",
"postcss": "^8.5.1",
"postcss-loader": "^8.1.1",
"postcss-pxtorem": "^6.1.0",
"pug": "^3.0.2",
"pug": "^3.0.3",
"pug-plain-loader": "^1.1.0",
"rimraf": "^5.0.5",
"rimraf": "^6.0.1",
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
"svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13",
"svgo-loader": "^4.0.0",
"terser": "^5.29.2",
"terser-webpack-plugin": "^5.3.10",
"ts-loader": "^9.5.1",
"typescript": "^5.4.3",
"vue-eslint-parser": "^9.4.2",
"terser": "^5.37.0",
"terser-webpack-plugin": "^5.3.11",
"tree-kill": "^1.2.2",
"ts-loader": "^9.5.2",
"typescript": "5.7.3",
"vue-eslint-parser": "^9.4.3",
"vue-loader": "^17.4.2",
"vue-template-compiler": "^2.7.16",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "5.2.0",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^5.10.0"
"webpack-merge": "^6.0.1"
},
"dependencies": {
"@simonwep/pickr": "^1.9.0",
"better-sqlite3": "^9.4.3",
"bufferutil": "^4.0.8",
"@simonwep/pickr": "^1.9.1",
"better-sqlite3": "^11.8.1",
"bufferutil": "^4.0.9",
"comlink": "~4.3.1",
"crypto-js": "^4.2.0",
"electron-log": "^5.1.2",
"electron-log": "^5.2.4",
"font-list": "^1.5.1",
"iconv-lite": "^0.6.3",
"image-size": "^1.1.0",
"jschardet": "^3.1.0",
"long": "^5.2.3",
"jschardet": "^3.1.4",
"long": "^5.2.4",
"message2call": "^0.1.3",
"music-metadata": "^8.1.4",
"music-metadata": "^10.7.1",
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
"node-id3": "^0.2.6",
"sortablejs": "^1.15.2",
"sortablejs": "^1.15.6",
"tunnel": "^0.0.6",
"utf-8-validate": "^6.0.3",
"utf-8-validate": "^6.0.5",
"vue": "~3.3.13",
"vue-router": "^4.3.0",
"ws": "^8.16.0"
"vue-router": "^4.5.0",
"ws": "^8.18.0"
},
"overrides": {
"got": "^11",
"json5": "latest",
"node-abi": "latest",
"minimatch": "latest",
"semver": "latest",
"svg-transform-loader": {
@@ -212,6 +214,9 @@
"svg-baker": {
"postcss": "latest"
},
"braces": "latest",
"node-gyp-build": "latest",
"micromatch": "latest",
"http-cache-semantics": "latest"
}
}
+36 -16
View File
@@ -1,29 +1,49 @@
### 不兼容性变更
Linux 系统至少需要 `GLIBC_2.29` 版本才能运行。
由于将 Electron 升级到 v32.x,原生库的编译被限制到不低于 C++ 20,试了几次无法在 docker 镜像 `node:16` 安装 gcc-10,最终将构建使用镜像更新到 `node:18`。
### 新增
- 主题编辑器添加“深色字体”选项,启用后将减少字体颜色梯度,各类字体(正文、标签字体等)颜色将更接近,这有助于解决创建全透明主题时可能出现的字体配色问题(#1799)
- 新增在线自定义源导入功能,允许通过http/https链接导入自定义源
- 新增下载的歌曲按列表名分组的功能,默认关闭,可以通过「设置 → 下载设置 → 将文件保存到以对应列表命名的子目录中」启用(#2145)
- 新增托盘图标样式「跟随系统亮暗模式」设置,可以在「设置 → 其他」里启用 (#2016)
- 支持本地同名 `.krc` 格式歌词文件的读取(#2053)
- 开放 API 新增播放器播放/暂停、切歌、收藏当前播放歌曲等接口调用,详情看文档「开放 API 服务」部分(#2077, @14Kay)
### 优化
- 主题设置默认折叠其他主题以优化进入设置界面时的性能
- 不再丢弃kg源逐行歌词(@helloplhm-qwq)
- 支持kw源排行榜显示大小(revert @Folltoshe #1460)
- 托盘菜单添加多语言支持(#1802)
- 优化本地歌曲换源匹配机制
- 优化正常播放结束时的下一首歌曲播放衔接度,在歌曲即将结束播放时将预获取下一首歌曲的播放链接,减少自动切歌时的等待时间(#2126)
- 优化歌曲换源机制,提升换源正确率
- 优化 Windows 平台上桌面歌词窗口大小调整机制,改用原生的窗口调整方式(#2137)
- 修正搜索歌曲提示框文案(#2050)
- 优化播放详情页 UI,修复「歌曲名」「艺术家」等文字过长时被截断的问题(#2049)
- Scheme URL 的播放歌曲允许更长的专辑名称
- 播放本地歌曲时,将优先尝试读取本地同名 `.jpg` 或 `.png` 图片作为播放封面显示,若文件不存在则从音频文件内读取,最后再尝试使用在线图片(#2096)
- 客户端模式的同步服务连接允许重定向 5 次(#2109)
- 更新软件默认使用的字体,修复 macOS Sequoia (15) 上界面出现乱码的问题(#2076)
- 优化简体、繁体中文文案编排,大幅修订英语文案编排(#2159, #2166, #2174 等, @3gf8jv4dv)
- 优化排序歌曲、主题名称、添加/编辑主题、列表更新管理等对话框布局及长文本显示效果(#2176, #2188, #2189, #2198 等, @3gf8jv4dv)
### 修复
- 修复某些情况下歌曲加载时间过长时不会自动跳到下一首的问题
- 修复mg歌词在某些情况下获取失败的问题(#1783)
- 修复mg歌单搜索(@helloplhm-qwq)
- 修复kg最新评论无法获取的问题(@helloplhm-qwq)
- 修复更新超时弹窗在非更新阶段意外弹出的问题(#1797)
- 修复网络代理设置没有对自定义源的网络请求生效的问题(#1814)
- 修复歌单详情页内歌单名字过长时的 UI 显示问题(#2028)
- 修复获取自定义环境音效预设列表逻辑问题
- 修复 `.m4a` 文件内嵌歌词无法读取的问题(#2090)
- 修复 Windows 任务管理器中的进程名显示为软件描述的问题(#2147)
- 修复本地歌曲同名歌词文件调整偏移时间后,下次再播放时调整的设置未被应用的问题(#2139)
- 修复首次打开软件后直接创建并删除列表时的报错问题(#2175, @14Kay)
### 移除
### 变更
- 移除未使用的网络代理设置用户名、密码设置,实际上在 v1.20.0 起这两个设置就没有在被内部使用
- 不再长期缓存换源歌曲信息
- 更新软件默认使用的字体,现在软件尽量使用系统自带的默认字体
- Linux 系统至少需要 `GLIBC_2.29` 版本才能运行
### 其他
- 更新 electron 到 v27.3.7
- 更新 Readme 文档,优化文案编排(#2146, Thanks @3gf8jv4dv)
- 更新 Issue 模板(#2153, @3gf8jv4dv)
- 更新项目文档(@3gf8jv4dv)
- 修订项目协议文件(#2146, #2152, @3gf8jv4dv)
- 更新 Electron 到 v32.3.0
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -80,3 +80,5 @@ export const DOWNLOAD_STATUS = {
} as const
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
export const TRAY_AUTO_ID = -1
+11 -3
View File
@@ -24,19 +24,21 @@ const defaultSetting: LX.AppSetting = {
'player.startupAutoPlay': false,
'player.togglePlayMethod': 'listLoop',
'player.highQuality': false,
'player.playQuality': '128k',
'player.isShowTaskProgess': true,
'player.isShowStatusBarLyric': false,
'player.volume': 1,
'player.powerSaveBlocker': true,
'player.isMute': false,
'player.playbackRate': 1,
'player.preservesPitch': true,
'player.isMaxOutputChannelCount': false,
'player.mediaDeviceId': 'default',
'player.isMediaDeviceRemovedStopPlay': false,
'player.isShowLyricTranslation': false,
'player.isShowLyricRoma': false,
'player.isS2t': false,
'player.isPlayLxlrc': isWin,
'player.isPlayLxlrc': !isMac,
'player.isSavePlayTime': false,
'player.audioVisualization': false,
'player.waitPlayEndStop': true,
@@ -63,8 +65,9 @@ const defaultSetting: LX.AppSetting = {
'playDetail.isZoomActiveLrc': false,
'playDetail.isShowLyricProgressSetting': false,
'playDetail.style.fontSize': 100,
'playDetail.style.fontSize': 140,
'playDetail.style.align': 'center',
'playDetail.isDelayScroll': true,
'desktopLyric.enable': false,
'desktopLyric.isLock': false,
@@ -104,6 +107,7 @@ const defaultSetting: LX.AppSetting = {
'list.actionButtonsVisible': false,
'download.enable': false,
'download.isSavePathGroupByListName': false,
'download.savePath': path.join(os.homedir(), 'Desktop'),
'download.fileName': '歌名 - 歌手',
'download.maxDownloadNum': 3,
@@ -136,6 +140,10 @@ const defaultSetting: LX.AppSetting = {
'sync.server.maxSsnapshotNum': 5,
'sync.client.host': '',
'openAPI.enable': false,
'openAPI.port': '23330',
'openAPI.bindLan': false,
// 'theme.id': 'blue_plus',
'theme.id': 'green',
'theme.lightId': 'green',
+2 -1
View File
@@ -59,7 +59,7 @@ const modules = {
open_dev_tools: 'open_dev_tools',
set_power_save_blocker: 'set_power_save_blocker',
progress: 'progress',
player_status: 'player_status',
change_tray: 'change_tray',
quit_update: 'quit_update',
update_check: 'update_check',
@@ -134,6 +134,7 @@ const modules = {
clear_music_url: 'clear_music_url',
get_music_url_count: 'get_music_url_count',
open_api_action: 'open_api_action',
sync_action: 'sync_action',
sync_get_server_devices: 'sync_get_server_devices',
sync_remove_server_device: 'sync_remove_server_device',
+39 -2
View File
@@ -89,15 +89,21 @@ declare global {
'player.togglePlayMethod': 'listLoop' | 'random' | 'list' | 'singleLoop' | 'none'
/**
* 是否优先播放320k音质
* 优先播放的音质
*/
'player.highQuality': boolean
'player.playQuality': LX.Quality
/**
* 是否显示任务栏进度条
*/
'player.isShowTaskProgess': boolean
/**
* 是否将歌词显示在状态栏
*/
'player.isShowStatusBarLyric': boolean
/**
* 音量大小
*/
@@ -123,6 +129,11 @@ declare global {
*/
'player.preservesPitch': boolean
/**
* 使用设备能处理的最大声道数输出音频
*/
'player.isMaxOutputChannelCount': boolean
/**
* 音频输出设备id
*/
@@ -288,6 +299,11 @@ declare global {
*/
'playDetail.style.align': 'center' | 'left' | 'right'
/**
* 播放详情页-是否延迟桌面歌词滚动
*/
'playDetail.isDelayScroll': boolean
/**
* 是否启用桌面歌词
@@ -469,6 +485,11 @@ declare global {
*/
'download.enable': boolean
/**
* 按列表名分组保存
*/
'download.isSavePathGroupByListName': boolean
/**
* 下载路径
*/
@@ -619,6 +640,22 @@ declare global {
*/
'sync.client.host': string
/**
* 是否启用开放API服务
*/
'openAPI.enable': boolean
/**
* API服务端口号
*/
'openAPI.port': '23330' | string
/**
* 是否绑定到局域网
*/
'openAPI.bindLan': boolean
/**
* 是否在离开搜索界面时自动清空搜索框
*/
+1
View File
@@ -59,6 +59,7 @@ declare global {
ext: FileExt
fileName: string
filePath: string
listId?: string
}
}
+1
View File
@@ -22,6 +22,7 @@ declare namespace LX {
songId: string | number // 歌曲ID,mg源为copyrightId,local为文件路径
albumName: string // 歌曲专辑名称
picUrl?: string | null // 歌曲图片链接
toggleMusicInfo?: MusicInfoOnline | null
}
interface MusicInfoMeta_online extends MusicInfoMetaBase {
+26
View File
@@ -0,0 +1,26 @@
declare namespace LX {
namespace OpenAPI {
interface Status {
status: boolean
message: string
address: string
}
interface EnableServer {
enable: boolean
port: string
bindLan: boolean
}
interface ActionBase <A> {
action: A
}
interface ActionData<A, D> extends ActionBase<A> {
data: D
}
type Action<A, D = undefined> = D extends undefined ? ActionBase<A> : ActionData<A, D>
type Actions = Action<'status'>
| Action<'enable', EnableServer>
}
}
+15
View File
@@ -15,5 +15,20 @@ declare namespace LX {
interface LyricInfo extends LX.Music.LyricInfo {
rawlrcInfo: LX.Music.LyricInfo
}
interface Status {
status: 'playing' | 'paused' | 'error' | 'stoped'
name: string
singer: string
albumName: string
picUrl: string
progress: number
duration: number
playbackRate: number
lyricLineText: string
lyricLineAllText: string
lyric: string
collect: boolean
}
}
}
+1 -1
View File
@@ -321,7 +321,7 @@ class Task extends EventEmitter {
if (err) {
console.log(err)
this.__handleError(err)
void this.stop()
return
}
if (this.closeWaiting && !this.dataWriteQueueLength) this.ws?.close()
})
+8
View File
@@ -6,6 +6,14 @@ export const isWin = process.platform == 'win32'
export const isMac = process.platform == 'darwin'
export const isProd = process.env.NODE_ENV == 'production'
export const getPlatform = (platform: NodeJS.Platform = process.platform) => {
switch (platform) {
case 'win32': return 'windows'
case 'darwin': return 'mac'
default: return 'linux'
}
}
// https://stackoverflow.com/a/53387532
export function compareVer(currentVer: string, targetVer: string): -1 | 0 | 1 {
@@ -227,4 +227,8 @@ export default class Lyric {
this._handleLinePlayerOnPlay(num, '', this.linePlayer._currentTime())
} else this.playingLineNum = 0
}
setDisabledAutoPause(autoPause) {
this.linePlayer.setDisabledAutoPause(autoPause)
}
}
@@ -224,4 +224,16 @@ export default class LinePlayer {
this.extendedLyrics = extendedLyrics
this._init()
}
setDisabledAutoPause(disabledAutoPause) {
if (disabledAutoPause) {
timeoutTools.nextTick = (handler) => {
return setTimeout(handler, 20)
}
timeoutTools.cancelNextTick = clearTimeout.bind(global)
} else {
timeoutTools.nextTick = window.requestAnimationFrame.bind(window)
timeoutTools.cancelNextTick = window.cancelAnimationFrame.bind(window)
}
}
}
+4 -2
View File
@@ -3,6 +3,8 @@ export const getNow = typeof performance == 'object' && window.performance.now ?
export class TimeoutTools {
constructor(thresholdTime = 80) {
this.nextTick = window.requestAnimationFrame.bind(window)
this.cancelNextTick = window.cancelAnimationFrame.bind(window)
this.invokeTime = 0
this.animationFrameId = null
this.timeoutId = null
@@ -11,7 +13,7 @@ export class TimeoutTools {
}
run() {
this.animationFrameId = window.requestAnimationFrame(() => {
this.animationFrameId = this.nextTick(() => {
this.animationFrameId = null
let diff = this.invokeTime - getNow()
// console.log('diff', diff)
@@ -39,7 +41,7 @@ export class TimeoutTools {
clear() {
if (this.animationFrameId) {
window.cancelAnimationFrame(this.animationFrameId)
this.cancelNextTick(this.animationFrameId)
this.animationFrameId = null
}
if (this.timeoutId) {
+74
View File
@@ -0,0 +1,74 @@
import { inflate } from 'zlib'
import { decodeName } from './util'
// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784
const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary')
const decodeLyric = str => new Promise((resolve, reject) => {
if (!str.length) return
const buf_str = Buffer.from(str, 'base64').subarray(4)
for (let i = 0, len = buf_str.length; i < len; i++) {
buf_str[i] = buf_str[i] ^ enc_key[i % 16]
}
inflate(buf_str, (err, result) => {
if (err) return reject(err)
resolve(result.toString())
})
})
const headExp = /^.*\[id:\$\w+\]\n/
const parseLyric = str => {
str = str.replace(/\r/g, '')
if (headExp.test(str)) str = str.replace(headExp, '')
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
let lyric
let rlyric
let tlyric
if (trans) {
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
let json = JSON.parse(Buffer.from(trans[1], 'base64').toString())
for (const item of json.content) {
switch (item.type) {
case 0:
rlyric = item.lyricContent
break
case 1:
tlyric = item.lyricContent
break
}
}
}
let i = 0
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
let result = str.match(/\[((\d+),\d+)\].*/)
let time = parseInt(result[2])
let ms = time % 1000
time /= 1000
let m = parseInt(time / 60).toString().padStart(2, '0')
time %= 60
let s = parseInt(time).toString().padStart(2, '0')
time = `${m}:${s}.${ms}`
if (rlyric) rlyric[i] = `[${time}]${rlyric[i]?.join('') ?? ''}`
if (tlyric) tlyric[i] = `[${time}]${tlyric[i]?.join('') ?? ''}`
i++
return str.replace(result[1], time)
})
rlyric = rlyric ? rlyric.join('\n') : ''
tlyric = tlyric ? tlyric.join('\n') : ''
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
lxlyric = decodeName(lxlyric)
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
rlyric = decodeName(rlyric)
tlyric = decodeName(tlyric)
return {
lyric,
tlyric,
rlyric,
lxlyric,
}
}
export const decodeKrc = async(data) => {
return decodeLyric(data).then(parseLyric)
}
+13
View File
@@ -0,0 +1,13 @@
const encodeNames = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': "'",
'&#039;': "'",
} as const
export const decodeName = (str: string | null = '') => {
return str?.replace(/(?:&amp;|&lt;|&gt;|&quot;|&apos;|&#039;|&nbsp;)/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
}
-1
View File
@@ -57,7 +57,6 @@ export default (setting: any): Partial<LX.AppSetting> => {
setting['common.controlBtnPosition'] = setting.controlBtnPosition
setting['player.togglePlayMethod'] = setting.player?.togglePlayMethod
setting['player.highQuality'] = setting.player?.highQuality
setting['player.isShowTaskProgess'] = setting.player?.isShowTaskProgess
setting['player.volume'] = setting.player?.volume
setting['player.isMute'] = setting.player?.isMute
+10 -3
View File
@@ -1,14 +1,21 @@
const http = require('http')
const https = require('https')
const fs = require('fs')
const { httpOverHttp, httpsOverHttp } = require('tunnel')
const sendRequest = (url) => {
const httpsRxp = /^https:/
const getRequestAgent = (url, proxy) => {
return proxy ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)({ proxy }) : undefined
}
const sendRequest = (url, proxy) => {
const urlParse = new URL(url)
const httpOptions = {
method: 'get',
host: urlParse.hostname,
port: urlParse.port,
path: urlParse.pathname + urlParse.search,
agent: getRequestAgent(url, proxy),
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
},
@@ -20,9 +27,9 @@ const sendRequest = (url) => {
: http.request(httpOptions)
}
module.exports = (url, filePath) => {
module.exports = (url, filePath, proxy) => {
return new Promise((resolve) => {
sendRequest(url)
sendRequest(url, proxy)
.on('response', response => {
// console.log(response.statusCode)
if (response.statusCode !== 200 && response.statusCode != 206) {
+2 -2
View File
@@ -57,7 +57,7 @@ const writeMeta = async(filePath, meta, picPath) => {
})
}
module.exports = (filePath, meta) => {
module.exports = (filePath, meta, proxy) => {
if (!meta.APIC) return writeMeta(filePath, meta)
let picUrl = meta.APIC
delete meta.APIC
@@ -68,7 +68,7 @@ module.exports = (filePath, meta) => {
let picPath = filePath.replace(/\.flac$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
download(picUrl, picPath, proxy).then(success => {
if (success) {
writeMeta(filePath, meta, picPath).finally(() => {
fs.unlink(picPath, err => {
+1 -1
View File
@@ -5,4 +5,4 @@ export interface MusicMeta {
APIC: string | null
lyrics: string | null
}
export function setMeta(filePath: string, meta: MusicMeta): void
export function setMeta(filePath: string, meta: MusicMeta, proxy?: { host: string, port: number }): void
+3 -3
View File
@@ -2,13 +2,13 @@ const path = require('path')
const mp3Meta = require('./mp3Meta')
const flacMeta = require('./flacMeta')
exports.setMeta = (filePath, meta) => {
exports.setMeta = (filePath, meta, proxy) => {
switch (path.extname(filePath)) {
case '.mp3':
mp3Meta(filePath, meta)
mp3Meta(filePath, meta, proxy)
break
case '.flac':
flacMeta(filePath, meta)
flacMeta(filePath, meta, proxy)
break
}
}
+2 -2
View File
@@ -15,7 +15,7 @@ const handleWriteMeta = (meta, filePath) => {
NodeID3.write(meta, filePath)
}
module.exports = (filePath, meta) => {
module.exports = (filePath, meta, proxy) => {
if (!meta.APIC) return handleWriteMeta(meta, filePath)
if (!/^http/.test(meta.APIC)) {
delete meta.APIC
@@ -26,7 +26,7 @@ module.exports = (filePath, meta) => {
let picUrl = meta.APIC
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
download(picUrl, picPath, proxy).then(success => {
if (success) {
meta.APIC = picPath
handleWriteMeta(meta, filePath)
+37
View File
@@ -2,6 +2,7 @@ import fs from 'node:fs'
import crypto from 'node:crypto'
import { gzip, gunzip } from 'node:zlib'
import path from 'node:path'
import { networkInterfaces } from 'node:os'
import { log } from '@common/utils'
export const joinPath = (...paths: string[]): string => path.join(...paths)
@@ -30,6 +31,25 @@ export const checkPath = async(path: string): Promise<boolean> => {
})
}
/**
* 检查路径并创建目录
* @param path
* @returns
*/
export const checkAndCreateDir = async(path: string) => {
return fs.promises.access(path, fs.constants.F_OK | fs.constants.W_OK)
.catch(async(err: NodeJS.ErrnoException) => {
if (err.code != 'ENOENT') throw err as Error
return fs.promises.mkdir(path, { recursive: true })
})
.then(() => true)
.catch((err) => {
console.error(err)
return false
})
}
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
return new Promise(resolve => {
if (!path) {
@@ -185,3 +205,20 @@ export const copyFile = async(sourcePath: string, distPath: string) => {
export const moveFile = async(sourcePath: string, distPath: string) => {
return fs.promises.rename(sourcePath, distPath)
}
export const getAddress = (): string[] => {
const nets = networkInterfaces()
const results: string[] = []
// console.log(nets)
for (const interfaceInfos of Object.values(nets)) {
if (!interfaceInfos) continue
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
for (const interfaceInfo of interfaceInfos) {
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
results.push(interfaceInfo.address)
}
}
}
return results
}
+28 -8
View File
@@ -6,6 +6,14 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
albumName: oldMusicInfo.albumName, // 歌曲专辑名称
picUrl: oldMusicInfo.img, // 歌曲图片链接
}
const newInfo = {
id: `${oldMusicInfo.source}_${oldMusicInfo.songmid}`,
name: oldMusicInfo.name,
singer: oldMusicInfo.singer,
source: oldMusicInfo.source,
interval: oldMusicInfo.interval,
meta: meta as LX.Music.MusicInfoOnline['meta'],
}
if (oldMusicInfo.source == 'local') {
meta.filePath = oldMusicInfo.filePath ?? oldMusicInfo.songmid ?? ''
@@ -27,6 +35,7 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
switch (oldMusicInfo.source) {
case 'kg':
meta.hash = oldMusicInfo.hash
newInfo.id = oldMusicInfo.songmid + '_' + oldMusicInfo.hash
break
case 'tx':
meta.strMediaMid = oldMusicInfo.strMediaMid
@@ -42,14 +51,7 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
}
}
return {
id: `${oldMusicInfo.source as string}_${oldMusicInfo.songmid as string}`,
name: oldMusicInfo.name,
singer: oldMusicInfo.singer,
source: oldMusicInfo.source,
interval: oldMusicInfo.interval,
meta: meta as LX.Music.MusicInfoOnline['meta'],
}
return newInfo
}
export const toOldMusicInfo = (minfo: LX.Music.MusicInfo) => {
@@ -128,3 +130,21 @@ export const filterMusicList = <T extends LX.Music.MusicInfo>(list: T[]): T[] =>
return true
})
}
const MAX_NAME_LENGTH = 80
const MAX_FILE_NAME_LENGTH = 150
export const clipNameLength = (name: string) => {
if (name.length <= MAX_NAME_LENGTH || !name.includes('、')) return name
const names = name.split('、')
let newName = names.shift()!
for (const name of names) {
if (newName.length + name.length > MAX_NAME_LENGTH) break
newName = newName + '、' + name
}
return newName
}
export const clipFileNameLength = (name: string) => {
return name.length > MAX_FILE_NAME_LENGTH ? name.substring(0, MAX_FILE_NAME_LENGTH) : name
}
+412 -381
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -18,7 +18,7 @@ const langs = [
message: zh_cn,
},
{
name: '繁体中文',
name: '繁體中文',
locale: 'zh-tw',
// alternate: 'zh-hk',
country: 'cn',
+1 -1
View File
@@ -7,7 +7,7 @@
"fallback": true
},
{
"name": "繁体中文",
"name": "繁體中文",
"locale": "zh-tw",
"alternate": "zh-hk",
"country": "cn"
+157 -126
View File
@@ -12,7 +12,7 @@
"cancel_button_text_2": "不不不,点错了",
"close": "关闭",
"comment__hot_load_error": "热门评论加载失败,点击尝试重新加载",
"comment__hot_loading": "热门评论加载中",
"comment__hot_loading": "热门评论加载中...",
"comment__hot_title": "热门评论",
"comment__location": "来自{location}",
"comment__new_load_error": "最新评论加载失败,点击尝试重新加载",
@@ -21,7 +21,7 @@
"comment__no_content": "暂无评论",
"comment__refresh": "刷新评论",
"comment__show": "歌曲评论",
"comment__title": "{name} 的评论",
"comment__title": "「{name}」的评论",
"comment__unavailable": "此歌曲不支持获取评论",
"confirm_button_text": "是的",
"copy_tip": "(点击复制)",
@@ -38,22 +38,22 @@
"desktop_lyric__lock": "锁定歌词",
"desktop_lyric__lrc_active_zoom_off": "取消缩放当前播放的歌词",
"desktop_lyric__lrc_active_zoom_on": "缩放当前播放的歌词",
"desktop_lyric__opactiy_decrease": "增加透明度(右击可微调)",
"desktop_lyric__opactiy_increase": "减小透明度(右击可微调)",
"desktop_lyric__opacity_decrease": "增加透明度(右击可微调)",
"desktop_lyric__opacity_increase": "减小透明度(右击可微调)",
"desktop_lyric__theme": "主题配色",
"desktop_lyric__unlock": "解锁歌词",
"desktop_lyric__win_top_off": "取消置顶歌词界面",
"desktop_lyric__win_top_on": "置顶歌词界面",
"download": "下载",
"download___status_complated": "下载完成",
"download___status_completed": "下载完成",
"download___status_error": "任务出错",
"download___status_paused": "暂停下载",
"download___status_runing": "正在下载",
"download___status_wating": "等待下载",
"download__all": "全部任务",
"download___status_running": "正在下载",
"download___status_waiting": "等待下载",
"download__all": "所有任务",
"download__error": "出错",
"download__finished": "下载完成",
"download__high_quality": "高品音质",
"download__high_quality": "高清音质",
"download__lossless": "无损音质",
"download__multiple_tip": "已选择 {len} 首歌曲",
"download__multiple_tip2": "请选择要优先下载的音质",
@@ -62,7 +62,7 @@
"download__paused": "已暂停",
"download__progress": "进度",
"download__quality": "品质",
"download__runing": "正在下载",
"download__running": "正在下载",
"download__status": "状态",
"download_status_error_check_path": "检查下载路径出错,请检查设置的下载目录是否正常",
"download_status_error_check_path_exist": "存在同名文件,跳过下载",
@@ -71,9 +71,9 @@
"download_status_error_url_failed": "获取音乐链接失败",
"download_status_error_write": "歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:",
"download_status_start": "开始下载",
"download_status_url_geting": "音乐链接获取中...",
"download_status_url_getting": "音乐链接获取中...",
"download_status_write_queue": "数据写入中({num})",
"duplicate_list_tip": "你之前已收藏过该列表 [{name}],是否需要更新里面的歌曲?",
"duplicate_list_tip": "你之前已收藏过列表「{name}」,是否需要更新里面的歌曲?",
"export": "导出",
"fullscreen_exit": "退出全屏",
"history_clear": "清空搜索历史",
@@ -90,7 +90,7 @@
"list__file": "定位文件",
"list__import_part_button_cancel": "不要啊",
"list__import_part_button_confirm": "覆盖掉",
"list__import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
"list__import_part_confirm": "导入的列表「{importName}」与本地列表「{localName}」的 ID 相同,是否覆盖本地列表?",
"list__import_part_desc": "选择列表文件",
"list__load_failed": "啊,加载失败了😭",
"list__loading": "列表加载中...⏳",
@@ -104,68 +104,70 @@
"list__pause": "暂停任务",
"list__play": "播放",
"list__play_later": "稍后播放",
"list__remove": "删除",
"list__remove_tip": "你真的想要移除 {name} 吗?",
"list__remove_tip_button": "是的 没错",
"list__remove": "移除",
"list__remove_tip": "你真的想要移除「{name}」吗?",
"list__remove_tip_button": "是的,没错",
"list__rename": "重命名",
"list__search": "搜索",
"list__sort": "调整位置",
"list__source_detail": "歌曲详情页",
"list__start": "开始任务",
"list__sync": "更新",
"list_add__btn_title": "把该歌曲添加到 {name}",
"list_add__multiple_btn_title": "把这些歌曲添加到 {name}",
"list__toggle_source": "歌曲换源",
"list_add__btn_title": "把该歌曲添加到「{name}」",
"list_add__multiple_btn_title": "把这些歌曲添加到「{name}」",
"list_add__multiple_title_add": "添加已选的 {num} 首歌曲到...",
"list_add__multiple_title_move": "移动已选的 {num} 首歌曲到...",
"list_add__title_first_add": "添加",
"list_add__title_first_move": "移动",
"list_add__title_last": "到...",
"list_duplicate_tip": "已存在同名列表,是否仍要继续创建?",
"list_import_tip__alldata": "导入失败,这是一个所有数据备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 所有数据 -> 导入",
"list_import_tip__playlist": "导入失败,这是一个列表备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 部分数据 -> 导入列表",
"list_import_tip__playlist_part": "导入失败,这是一个单列表文件,你需要去这里导入:\n我的列表 -> 右击任意一个列表名 -> 在弹出的菜单中选择导入",
"list_import_tip__setting": "导入失败,这是一个设置备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 部分数据 -> 设置导入",
"list_import_tip__alldata": "导入失败,这是一个「所有数据」备份文件,你需要去这里导入:\n\n设置 → 备份与恢复 → 所有数据 → 导入",
"list_import_tip__playlist": "导入失败,这是一个「列表」备份文件,你需要去这里导入:\n\n设置 → 备份与恢复 → 部分数据 → 导入列表",
"list_import_tip__playlist_part": "导入失败,这是一个「单列表」文件,你需要去这里导入:\n\n我的列表 → 右击任意一个列表名 → 在弹出的菜单中选择「导入」",
"list_import_tip__setting": "导入失败,这是一个「设置」备份文件,你需要去这里导入:\n\n设置 → 备份与恢复 → 部分数据 → 导入设置",
"list_import_tip__unknown": "导入失败,未知的文件类型,请尝试升级到最新版本后再试",
"list_sort_modal_by_album": "专辑名",
"list_sort_modal_by_down": "降序",
"list_sort_modal_by_field": "排序字段",
"list_sort_modal_by_name": "歌曲名",
"list_sort_modal_by_random": "随机乱序",
"list_sort_modal_by_singer": "歌手名",
"list_sort_modal_by_source": "歌曲源",
"list_sort_modal_by_singer": "艺术家",
"list_sort_modal_by_source": "歌曲来源平台",
"list_sort_modal_by_time": "时长",
"list_sort_modal_by_type": "排序类别",
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你确定要这么做吗?",
"list_update_modal__auto_update": "自动更新",
"list_update_modal__tips": "💡 每次启动软件时将会自动更新已勾选“自动更新”的列表",
"list_update_modal__tips": "💡 每次启动软件时将会自动更新已勾选「自动更新」的列表",
"list_update_modal__title": "列表更新管理",
"list_update_modal__update": "立即更新",
"lists__add_local_file_desc": "选择歌曲文件",
"lists__dislike_music_tip": "你真的不喜欢 {name} 吗?",
"lists__dislike_music_singer_tip": "你真的不喜欢「{singer}」的「{name}」吗?",
"lists__dislike_music_tip": "你真的不喜欢「{name}」吗?",
"lists__duplicate": "重复歌曲",
"lists__export": "导出",
"lists__export_part_desc": "选择列表文件保存位置",
"lists__import": "导入",
"lists__import_part_button_cancel": "不要啊",
"lists__import_part_button_confirm": "覆盖掉",
"lists__import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
"lists__import_part_confirm": "导入的列表「{importName}」与本地列表「{localName}」的 ID 相同,是否覆盖本地列表?",
"lists__import_part_desc": "选择列表文件",
"lists__new_list_btn": "新建列表",
"lists__new_list_input": "新列表...",
"lists__remove": "删除",
"lists__remove": "移除",
"lists__remove_music_tip": "你真的要移除所选的 {len} 首歌曲吗?",
"lists__remove_tip": "你真的想要移除 {name} 吗?",
"lists__remove_tip_button": "是的 没错",
"lists__remove_tip": "你真的想要移除「{name}」吗?",
"lists__remove_tip_button": "是的,没错",
"lists__rename": "重命名",
"lists__select_local_file": "添加本地歌曲",
"lists__sort_list": "排序歌曲",
"lists__source_detail": "歌单详情页",
"lists__sync": "更新",
"lists__sync_confirm_tip": "这将会把 {name} 内的歌曲替换成在线列表的歌曲,你确认要更新吗?",
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情:{detail}",
"load_list_file_error_title": "播放列表数据加载错误(建议到GitHub或加群反馈)",
"loding": "加载中...",
"lists__sync_confirm_tip": "这将会把「{name}」内的歌曲替换成在线列表的歌曲,你确认要更新吗?",
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到:{path}\n\n它以 JSON 格式存储,你可以尝试手动修复并恢复它。\n\n错误详情:{detail}",
"load_list_file_error_title": "播放列表数据加载错误(建议到 GitHub 反馈)",
"loading": "加载中...",
"love_list": "收藏",
"lyric__load_error": "歌词获取失败",
"lyric__select": "歌词文本选择",
@@ -182,15 +184,17 @@
"lyric_menu__size_add": "加大字体(右击可微调)",
"lyric_menu__size_dec": "减小字体(右击可微调)",
"lyric_menu__size_reset": "重置",
"media_device__empty_device_tip": "音频输出设备为空,若出现无法播放的情况,请检查声卡驱动是否已安装或重装声卡驱动!",
"min": "最小化",
"music_album": "专辑名",
"music_duplicate": "重复歌曲",
"music_name": "歌曲名",
"music_singer": "艺术家",
"music_sort__input_tip": "请输入要调整到第几个位置",
"music_sort__title": "将 {name} 的位置调整到:",
"music_sort__title": "将「{name}」的位置调整到:",
"music_sort__title_multiple": "将已选的 {num} 首歌曲的位置调整到:",
"music_time": "时长",
"music_toggle_clean": "取消换源",
"my_list": "我的列表",
"no_item": "列表竟然是空的...",
"not_agree": "不接受",
@@ -214,8 +218,8 @@
"player__desktop_lyric_unlock": "右击解锁歌词",
"player__end": "播放完毕",
"player__error": "音频加载出错,5 秒后切换下一首",
"player__geting_url": "歌曲链接获取中...",
"player__geting_url_delay_retry": "服务繁忙,{time}秒后重试...",
"player__getting_url": "歌曲链接获取中...",
"player__getting_url_delay_retry": "服务繁忙,{time} 秒后重试...",
"player__hide_detail_tip": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
"player__loading": "音乐加载中...",
"player__music_album": "专辑名:",
@@ -223,13 +227,13 @@
"player__music_singer": "艺术家:",
"player__next": "下一首",
"player__pause": "暂停",
"player__pic_tip": "播放详情页(右击在“我的列表”定位当前播放的歌曲)",
"player__pic_tip": "播放详情页(右击在「我的列表」定位当前播放的歌曲)",
"player__play": "播放",
"player__play_toggle_mode_list": "顺序播放",
"player__play_toggle_mode_list_loop": "列表循环",
"player__play_toggle_mode_off": "禁用",
"player__play_toggle_mode_random": "列表随机",
"player__play_toggle_mode_single_loop": "单曲循环",
"player__play_toggle_mode_list_loop": "列表循环播放",
"player__play_toggle_mode_off": "禁用歌曲切换",
"player__play_toggle_mode_random": "列表随机播放",
"player__play_toggle_mode_single_loop": "单曲循环播放",
"player__playback_preserves_pitch": "音调补偿",
"player__playback_rate": "当前播放速率:",
"player__playback_rate_reset_btn": "重置",
@@ -257,8 +261,8 @@
"player__sound_effect_convolution_file_dining_living_true_stereo": "餐厅",
"player__sound_effect_convolution_file_feedback_spring": "反馈弹簧",
"player__sound_effect_convolution_file_living_bedroom_leveled": "卫生间",
"player__sound_effect_convolution_file_matrix_1": "矩阵混响",
"player__sound_effect_convolution_file_matrix_2": "矩阵混响2",
"player__sound_effect_convolution_file_matrix_1": "矩阵混响(1)",
"player__sound_effect_convolution_file_matrix_2": "矩阵混响(2)",
"player__sound_effect_convolution_file_s2_r4_bd": "教堂",
"player__sound_effect_convolution_file_s3_r1_bd": "立体声",
"player__sound_effect_convolution_file_spreader50_65ms": "室内",
@@ -266,6 +270,7 @@
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "磁性立体声",
"player__sound_effect_convolution_main_gain": "原始音频增益",
"player__sound_effect_convolution_send_gain": "环境音效增益",
"player__sound_effect_features_tip": "提示:「音效设置」与「自定义音频输出设备」冲突,启用音效设置后音频输出设备将会被重置为默认的输出设备,目前此问题暂无法解决。",
"player__sound_effect_panner": "3D 立体环绕(需使用耳机)",
"player__sound_effect_panner_enabled": "启用",
"player__sound_effect_panner_sound_r": "声音距离",
@@ -273,7 +278,7 @@
"player__sound_effect_pitch_shifter": "音调升降调节",
"player__sound_effect_pitch_shifter_preset_semitones": "{num} 半音",
"player__sound_effect_pitch_shifter_reset_btn": "重置",
"player__sound_effect_pitch_shifter_tip": "由于升降调需要实时处理音频数据,这会导致额外的CPU占用\n\n已知问题:\n如果CPU资源不够时将处理导致任务堆积而出现声音异常,这时需要暂停播放一段时间等堆积的任务处理完毕再播放",
"player__sound_effect_pitch_shifter_tip": "由于升降调需要实时处理音频数据,这会导致额外的 CPU 占用。\n\n已知问题:\n如果 CPU 资源不够将导致处理任务堆积而出现声音异常。\n这时需要暂停播放一段时间等堆积的任务处理完毕再播放。",
"player__stop": "暂停播放",
"player__volume": "当前音量:",
"player__volume_mute_label": "静音",
@@ -284,7 +289,7 @@
"search__type_songlist": "歌单",
"search__welcome": "搜我所想~~😉",
"setting": "设置",
"setting__about": "关于洛雪音乐",
"setting__about": "关于 LX Music",
"setting__backup": "备份与恢复",
"setting__backup_all": "所有数据(列表数据与设置数据)",
"setting__backup_all_export": "导出",
@@ -293,10 +298,10 @@
"setting__backup_all_import_desc": "选择备份文件",
"setting__backup_other": "其他备份格式(目前不支持恢复此类备份文件)",
"setting__backup_other_export_dir": "选择文件保存位置",
"setting__backup_other_export_list_csv": "导出 CSV 格式的列表",
"setting__backup_other_export_list_text": "导出 TXT 格式的列表",
"setting__backup_other_export_list_csv": "导出 CSV 格式列表",
"setting__backup_other_export_list_text": "导出 TXT 格式列表",
"setting__backup_other_export_list_text_confirm": "是否将所有列表合并为一个文件?",
"setting__backup_part": "部分数据(列表数据包括试听列表、收藏列表、用户自定义列表,设置数据不包括快捷键设置)",
"setting__backup_part": "部分数据(列表数据包括「试听列表」、「我的收藏」和用户自行创建的列表;设置数据不包括快捷键设置)",
"setting__backup_part_export_list": "导出列表",
"setting__backup_part_export_list_desc": "选择歌单保存位置",
"setting__backup_part_export_setting": "导出设置",
@@ -325,22 +330,22 @@
"setting__basic_playbar_progress_style_full": "全宽",
"setting__basic_playbar_progress_style_middle": "中等",
"setting__basic_playbar_progress_style_mini": "迷你",
"setting__basic_show_animation": "显示切换动画",
"setting__basic_source": "音乐来源",
"setting__basic_show_animation": "显示动画效果",
"setting__basic_source": "自定义源",
"setting__basic_source_status_failed": "初始化失败",
"setting__basic_source_status_initing": "初始化中",
"setting__basic_source_status_initing": "初始化中...",
"setting__basic_source_status_success": "初始化成功",
"setting__basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)",
"setting__basic_source_test": "测试接口(几乎软件的所有功能都可用)",
"setting__basic_source_user_api_btn": "自定义源管理",
"setting__basic_sourcename": "音源名字",
"setting__basic_sourcename": "歌曲来源名称",
"setting__basic_sourcename_alias": "别名",
"setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "选择音源名字类型",
"setting__basic_sourcename_title": "选择歌曲来源名称类型",
"setting__basic_start_in_fullscreen": "以全屏模式启动",
"setting__basic_theme": "主题颜色",
"setting__basic_theme_auto_tip": "此乃动态主题,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。\n注:鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。",
"setting__basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
"setting__basic_theme_auto_tip": "此乃动态主题,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。\n\n注:鼠标右击此主题项即可打开亮、暗色主题设置窗口。",
"setting__basic_to_tray": "关闭窗口时不退出软件将其最小化到系统托盘",
"setting__basic_window_size": "窗口尺寸",
"setting__basic_window_size_big": "大",
"setting__basic_window_size_huge": "巨大",
@@ -358,7 +363,8 @@
"setting__desktop_lyric_align_left": "居左",
"setting__desktop_lyric_align_right": "居右",
"setting__desktop_lyric_always_on_top": "使歌词总是在其他窗口之上",
"setting__desktop_lyric_always_on_top_loop": "自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)",
"setting__desktop_lyric_always_on_top_loop": "自动刷新歌词置顶",
"setting__desktop_lyric_always_on_top_loop_tip": "当歌词置顶后仍被某些程序遮挡时可尝试启用此选项",
"setting__desktop_lyric_audio_visualization": "音频可视化(实验性)",
"setting__desktop_lyric_color": "歌词字体颜色",
"setting__desktop_lyric_color_reset": "重置颜色",
@@ -372,7 +378,8 @@
"setting__desktop_lyric_font_default": "默认",
"setting__desktop_lyric_font_weight": "加粗字体",
"setting__desktop_lyric_fullscreen_hide": "全屏时自动关闭歌词",
"setting__desktop_lyric_hover_hide": "鼠标移入歌词区域时降低歌词透明度(此功能存在平台兼容性问题)",
"setting__desktop_lyric_hover_hide": "鼠标移入歌词区域时降低歌词透明度",
"setting__desktop_lyric_hover_hide_tip": "此功能存在平台兼容性问题,在某些平台上可能会出现预料外的问题",
"setting__desktop_lyric_line_gap": "歌词间距({num})",
"setting__desktop_lyric_line_gap_add": "加大间距",
"setting__desktop_lyric_line_gap_dec": "减小间距",
@@ -385,28 +392,33 @@
"setting__desktop_lyric_scroll_align_center": "中心",
"setting__desktop_lyric_scroll_align_top": "顶部",
"setting__desktop_lyric_shadow_color": "阴影颜色",
"setting__desktop_lyric_show_taskbar": "在任务栏显示歌词进程(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)",
"setting__desktop_lyric_show_taskbar": "在任务栏显示歌词进程",
"setting__desktop_lyric_show_taskbar_tip": "此选项作为在录屏软件无法捕获歌词窗口时的变通解决方法",
"setting__desktop_lyric_unplay_color": "未播放颜色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"setting__dislike_list_input_tip": "歌曲名@艺术家\n歌曲名\n@艺术家",
"setting__dislike_list_save_btn": "保存",
"setting__dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:歌曲名@歌手名\n3. 指定某首歌:歌曲名\n4. 指定某歌手:@歌手名",
"setting__dislike_list_title": "不喜欢的歌曲规则列表",
"setting__dislike_list_tips": "1. 每条一行,若歌曲或者艺术家名字中存在「@」符号,需要将其替换成「#」\n2. 指定某艺术家的某首歌:歌曲名@艺术家\n3. 指定某首歌:歌曲名\n4. 指定某艺术家:@艺术家",
"setting__dislike_list_title": "「不喜欢的歌曲」规则列表",
"setting__download": "下载设置",
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
"setting__download_embed_lyric": "歌词嵌入",
"setting__download_embed_pic": "封面嵌入",
"setting__download_data_embed": "嵌入到音频文件中的内容",
"setting__download_embed_lyric": "嵌入歌词",
"setting__download_embed_pic": "嵌入封面",
"setting__download_embed_rlyric": "同时嵌入罗马音歌词(如果有)",
"setting__download_embed_tlyric": "同时嵌入翻译歌词(如果有)",
"setting__download_enable": "是否启用下载功能",
"setting__download_enable": "启用下载功能",
"setting__download_lyric": "歌词下载",
"setting__download_lyric_format": "下载的歌词文件编码格式",
"setting__download_lyric_format_gbk": "GBK(在某些设备上出现中文乱码时可尝试选择此格式)",
"setting__download_lyric_format_tip": "在某些设备上出现中文乱码时可尝试选择 GBK 格式",
"setting__download_lyric_format_gbk": "GBK",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "是否同时下载歌词文件",
"setting__download_lyric_title": "同时下载歌词文件",
"setting__download_max_num": "同时下载任务数",
"setting__download_max_num_tip": "过大的同时下载数量可能会导致你的 IP 被自定义源封禁,是否确认修改?",
"setting__download_max_num_tooltip": "设置过大可能会导致 IP 被封,这取决于自定义源",
"setting__download_name": "文件命名方式",
"setting__download_name1": "歌名 - 歌手",
"setting__download_name2": "歌手 - 歌名",
"setting__download_name3": "歌名",
"setting__download_name1": "歌曲名 - 艺术家",
"setting__download_name2": "艺术家 - 歌曲名",
"setting__download_name3": "歌曲名",
"setting__download_name_title": "下载歌曲时的命名方式",
"setting__download_path": "下载路径",
"setting__download_path_change_btn": "更改",
@@ -415,10 +427,10 @@
"setting__download_path_title": "下载歌曲保存的路径",
"setting__download_rlyric": "同时将罗马音歌词写入歌词文件中(如果有)",
"setting__download_select_save_path": "选择歌曲保存路径",
"setting__download_skip_exist_file": "下载目录存在同名的文件时跳过下载此任务",
"setting__download_skip_exist_file": "下载目录存在同名文件时跳过下载此任务",
"setting__download_tlyric": "同时将翻译歌词写入歌词文件中(如果有)",
"setting__download_use_other_source": "自动换源下载",
"setting__download_use_other_source_tip": "当无法从歌曲的原始源下载时,尝试切换到其他源下载,注:此功能不100%保证换源后的歌曲版本与原版一致",
"setting__download_use_other_source_tip": "当无法从歌曲的原始来源下载时,尝试切换到其他来源下载。\n注:此功能不 100% 保证换源后的歌曲版本与原版一致。",
"setting__hot_key": "快捷键设置",
"setting__hot_key_common_focus_search_input": "聚焦搜索框",
"setting__hot_key_common_min": "最小化程序",
@@ -441,16 +453,16 @@
"setting__hot_key_player_volume_up": "增加音量",
"setting__hot_key_tip_input": "请输入新的按键",
"setting__hot_key_unset_input": "未设置",
"setting__is_enable": "是否启用",
"setting__is_show": "是否显示",
"setting__is_enable": "启用",
"setting__is_show": "显示",
"setting__list": "列表设置",
"setting__list_action_btn": "显示列表操作按钮",
"setting__list_add_music_location_type": "添加歌曲到列表时的位置",
"setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "顶部",
"setting__list_click_action": "双击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)",
"setting__list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
"setting__list_source": "显示歌曲源(仅对我的音乐分类有效)",
"setting__list_click_action": "双击列表里的歌曲时自动切换到当前列表播放(仅对「歌单」和「排行榜」有效)",
"setting__list_scroll": "记住播放列表滚动条位置(仅对「我的列表」有效)",
"setting__list_source": "显示歌曲来源平台(仅对「我的列表」有效)",
"setting__network": "网络设置",
"setting__network_proxy_host": "主机",
"setting__network_proxy_password": "密码",
@@ -460,13 +472,21 @@
"setting__odc": "强迫症设置",
"setting__odc_clear_search_input": "离开搜索界面时清空搜索框",
"setting__odc_clear_search_list": "离开搜索界面时清空搜索列表",
"setting__open_api": "开放 API",
"setting__open_api_address": "服务地址:",
"setting__open_api_bind_lan": "允许来自局域网的访问",
"setting__open_api_enable": "启用开放 API 服务",
"setting__open_api_port": "服务端口",
"setting__open_api_port_tip": "请输入开放 API 服务端口",
"setting__open_api_tip": "该功能用于为第三方软件提供调用 LX Music 的能力,目前可用的功能可以看:",
"setting__open_api_tip_link": "接入文档",
"setting__other": "其他",
"setting__other_dislike_list": "不喜欢的歌曲规则",
"setting__other_dislike_list": "「不喜欢的歌曲」规则",
"setting__other_dislike_list_label": "规则数量:",
"setting__other_dislike_list_show_btn": "编辑不喜欢歌曲规则",
"setting__other_dislike_list_show_btn": "编辑规则",
"setting__other_listdata": "列表数据清理",
"setting__other_listdata_clear_btn": "清空我的列表数据",
"setting__other_listdata_clear_tip_confirm": "这将清理你创建的 所有列表 及收藏的 所有歌曲,是否真的要继续?",
"setting__other_listdata_clear_btn": "清空「我的列表」数据",
"setting__other_listdata_clear_tip_confirm": "这将清理你创建的「所有列表」及收藏的「所有歌曲」,是否真的要继续?",
"setting__other_lyric_edited_cache": "已调整过偏移时间的歌词管理",
"setting__other_lyric_edited_clear_btn": "清理已调整过时间的歌词",
"setting__other_lyric_edited_clear_tip_confirm": "这将清理所有你之前已调整过偏移时间的歌词,是否确认清理?(手抖确认🤪)",
@@ -482,52 +502,59 @@
"setting__other_resource_cache_clear_btn": "清理资源缓存",
"setting__other_resource_cache_confirm": "我要清掉",
"setting__other_resource_cache_label": "软件已使用缓存大小:",
"setting__other_resource_cache_tip": "图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小",
"setting__other_resource_cache_tip_confirm": "涉及图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小,是否仍要清理?",
"setting__other_resource_cache_tip": "包括图片、音频等缓存,清理后这些资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小",
"setting__other_resource_cache_tip_confirm": "涉及图片、音频等缓存,清理后这些资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小,是否仍要清理?",
"setting__other_tray_theme": "托盘图标样式",
"setting__other_tray_theme_auto": "跟随系统亮暗模式",
"setting__other_tray_theme_black": "黑色",
"setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色",
"setting__play": "播放设置",
"setting__play_auto_clean_played_list": "点击与播放列表相同的列表切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)",
"setting__play_advanced_audio_features_tip": "自定义音频输出设备与该功能冲突,启用该功能后音频输出设备将会被重置为默认,目前此问题暂无法解决。是否仍要开启?",
"setting__play_auto_clean_played_list": "点击与播放列表相同的列表切歌时清空已播放列表",
"setting__play_auto_clean_played_list_tip": "随机模式下列表内所有歌曲会重新参与随机",
"setting__play_auto_skip_on_error": "播放错误时自动切换歌曲",
"setting__play_detail": "播放详情页设置",
"setting__play_detail_align": "歌词对齐方式",
"setting__play_detail_align_center": "居中",
"setting__play_detail_align_left": "居左",
"setting__play_detail_align_right": "居右",
"setting__play_detail_font_size": "歌词字体大小(可以在播放详情页使用键盘的 + - 调整字体大小)",
"setting__play_detail_font_size": "歌词字体大小(可以在播放详情页使用键盘的「+」「-」调整字体大小)",
"setting__play_detail_font_size_current": "当前字体大小:{size}",
"setting__play_detail_font_size_reset": "重置",
"setting__play_detail_font_zoom": "缩放当前正在播放的歌词",
"setting__play_detail_lyric_progress": "允许通过歌词调整播放进度",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果可用,此功能比较耗性能,低配置电脑不建议开启)",
"setting__play_detail_lyric_delay_scroll": "延迟歌词滚动",
"setting__play_detail_lyric_progress": "允许通过拖拽歌词调整播放进度",
"setting__play_lyric_lxlrc": "使用卡拉 OK 式歌词播放(如果可用)",
"setting__play_lyric_lxlrc_tip": "此功能比较耗性能,低配置电脑不建议开启!",
"setting__play_lyric_roma": "显示歌词罗马音(如果可用)",
"setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文",
"setting__play_lyric_s2t": "将播放与下载的中文歌词转换为繁体",
"setting__play_lyric_transition": "显示歌词翻译(如果可用)",
"setting__play_max_output_channel_count": "使用设备能处理的最大声道数输出音频",
"setting__play_mediaDevice": "音频输出",
"setting__play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
"setting__play_mediaDevice_title": "选择声音输出的媒体设备",
"setting__play_media_device_error_tip": "此功能与高级音频功能(音频可视化、音效设置)冲突,你本次启动软件时已启用这些功能,此设置暂不可用,请 关闭这些功能 并 重启 软件后,再来修改此设置。",
"setting__play_media_device_tip": "此功能与音频可视化功能冲突,两者无法同时启用,是否将音频可视化关闭 并 应用所选音频输出设置?",
"setting__play_media_device_error_tip": "此功能与高级音频功能(音频可视化、音效设置、使用设备能处理的最大声道数输出音频功能)冲突。你本次启动软件时已启用这些功能,此选项暂不可用。请关闭这些功能并重启软件后,再修改此选项!",
"setting__play_media_device_tip": "此功能与音频可视化功能冲突,两者无法同时启用。是否关闭「音频可视化」并应用所选音频输出设置?",
"setting__play_playQuality": "优先播放的音质(如果可用)",
"setting__play_power_save_blocker": "播放歌曲时阻止电脑休眠",
"setting__play_quality": "优先播放320K品质的歌曲(如果可用)",
"setting__play_save_play_time": "记住播放进度",
"setting__play_startup_auto_play": "启动软件后自动播放音乐",
"setting__play_statusbar_lyric": "在状态栏显示歌词(需要启用托盘)",
"setting__play_task_bar": "在任务栏上显示当前歌曲播放进度",
"setting__play_timeout": "定时暂停",
"setting__player_audio_visualization_tip": "自定义音频输出设备与音频可视化功能会冲突,启用了音频可视化后音频输出设备将会被重置为默认的输出设备,目前此问题暂无法解决,是否仍要开启?",
"setting__player_audio_visualization_tip": "自定义音频输出设备与音频可视化功能冲突,启用「音频可视化」后音频输出设备将会被重置为默认,目前此问题暂无法解决。是否仍要开启?",
"setting__search": "搜索设置",
"setting__search_focus_search_box": "启动时自动聚焦搜索框",
"setting__search_history": "显示历史搜索记录",
"setting__search_hot": "显示热门搜索",
"setting__setting__desktop_lyric_font_weight_extended": "翻译、罗马音歌词",
"setting__setting__desktop_lyric_font_weight_font": "逐字歌词",
"setting__setting__desktop_lyric_font_weight_line": "逐行歌词",
"setting__desktop_lyric_font_weight_extended": "翻译、罗马音歌词",
"setting__desktop_lyric_font_weight_font": "逐字歌词",
"setting__desktop_lyric_font_weight_line": "逐行歌词",
"setting__sync": "数据同步",
"setting__sync_client_address": "当前设备地址:{address}",
"setting__sync_client_host": "同步服务地址",
"setting__sync_client_host_tip": "http://IP地址:端口号",
"setting__sync_client_host_tip": "http://<IP 地址>:<端口号>",
"setting__sync_client_mode": "客户端模式",
"setting__sync_client_status": "状态:{status}",
"setting__sync_code_blocked_ip": "当前设备的 IP 已被服务端封禁!",
@@ -549,9 +576,11 @@
"setting__sync_server_port_tip": "请输入同步服务端口号",
"setting__sync_server_refresh_code": "刷新连接码",
"setting__sync_server_show_device_list": "已认证设备列表",
"setting__sync_tip": "使用方式请看常见问题“同步功能”部分",
"setting__sync_tip": "使用方式请看常见问题「同步功能」部分",
"setting__update": "软件更新",
"setting__update_checking": "检查更新中...",
"setting__update_commit_date": "提交日期:",
"setting__update_commit_id": "代码版本:",
"setting__update_current_label": "当前版本:",
"setting__update_downloading": "发现新版本并在努力下载中,请稍后...⏳",
"setting__update_init": "处理更新中...",
@@ -563,18 +592,19 @@
"setting__update_show_change_log": "更新版本后的首次启动时显示更新日志",
"setting__update_try_auto_update": "发现新版本时尝试自动下载更新",
"setting__update_unknown": "未知",
"setting__update_unknown_tip": "❓ 获取最新版本信息失败,建议去关于界面打开项目发布地址查看当前版本是否最新",
"setting__update_unknown_tip": "❓ 获取最新版本信息失败,建议去「关于」页面打开项目发布地址查看当前版本是否最新",
"setting_download_save_group_list_name": "将文件保存到以对应列表命名的子目录中",
"setting_sync_status_enabled": "已连接",
"song_list": "歌单",
"songlist__import_input_btn_confirm": "打开",
"songlist__import_input_show_btn": "打开歌单",
"songlist__import_input_tip": "输入歌单链接或歌单 ID",
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前歌单源是否对应",
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前选择的歌单来源是否对应",
"songlist__import_input_tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
"songlist__import_input_tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
"songlist__import_input_tip_4": "网易源的“我喜欢”歌单需要 Token 才能打开,详情看 ",
"songlist__import_input_tip_3": "不支持用歌单 ID 打开酷狗源歌单,但支持用酷狗码打开",
"songlist__import_input_tip_4": "网易源的「我喜欢」歌单需要 Token 才能打开,详情看 ",
"songlist__import_input_title": "打开分享的歌单",
"songlist__open_list": "打开{name}歌单",
"songlist__open_list": "打开「{name}」歌单",
"songlist__tag_info_hot_tag": "热门标签",
"source_alias_all": "聚合大会",
"source_alias_bd": "小杜音乐",
@@ -595,24 +625,24 @@
"sync__auth_code_input_tip": "请输入连接码",
"sync__auth_code_title": "需要输入连接码",
"sync__dislike_merge_tip_desc": "合并两边列表内容并去重",
"sync__dislike_other_tip_desc": "“取消同步”将不使用不喜欢列表同步功能",
"sync__dislike_other_tip_desc": "「取消同步」将不使用「不喜欢的歌曲」列表同步功能",
"sync__dislike_overwrite_tip_desc": "被覆盖者的列表将被替换成覆盖者的列表",
"sync__dislike_title": "选择与 {name} 的不喜欢列表同步方式",
"sync__dislike_title": "选择与「{name}」的「不喜欢的歌曲」列表同步方式",
"sync__list_merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__list_other_tip_desc": "“取消同步”将不使用列表同步功能。",
"sync__list_overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表(列表ID不同的列表将被合并到一起),若勾选完全覆盖,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
"sync__list_title": "选择与 {name} 的列表同步方式",
"sync__merge_btn_local_remote": "本机列表 合并 远程列表",
"sync__merge_btn_remote_local": "远程列表 合并 本机列表",
"sync__list_other_tip_desc": "「取消同步」将不使用列表同步功能。",
"sync__list_overwrite_tip_desc": "被覆盖者与覆盖者列表 ID 相同的列表将被移除后替换成覆盖者的列表(列表 ID 不同的列表将被合并到一起)。若勾选「完全覆盖」,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
"sync__list_title": "选择与「{name}」的列表同步方式",
"sync__merge_btn_local_remote": "「本机列表」合并「远程列表」",
"sync__merge_btn_remote_local": "「远程列表」合并「本机列表」",
"sync__merge_label": "合并",
"sync__merge_tip": "合并:",
"sync__merge_tip": "合并:",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip": "其他:",
"sync__overwrite": "完全覆盖",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
"sync__overwrite_btn_local_remote": "「本机列表」覆盖「远程列表」",
"sync__overwrite_btn_none": "仅使用实时同步功能",
"sync__overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
"sync__overwrite_btn_remote_local": "「远程列表」覆盖「本机列表」",
"sync__overwrite_label": "覆盖",
"sync__overwrite_tip": "覆盖:",
"sync_status_disabled": "未连接",
@@ -652,11 +682,12 @@
"theme_edit_modal__pick_save": "确认",
"theme_edit_modal__preview": "预览主题",
"theme_edit_modal__primary": "主题色",
"theme_edit_modal__remove": "删除",
"theme_edit_modal__remove": "移除",
"theme_edit_modal__remove_tip": "是否真的要移除这个主题?",
"theme_edit_modal__save_new": "另存",
"theme_edit_modal__select_bg_file": "选择背景图片",
"theme_edit_modal__title": "编辑主题",
"theme_edit_modal__title_add": "添加主题",
"theme_edit_modal__title_edit": "编辑主题",
"theme_green": "绿意盎然",
"theme_grey": "灰常美丽",
"theme_happy_new_year": "新年快乐",
@@ -674,29 +705,29 @@
"theme_selector_modal__theme_name": "主题名称",
"theme_selector_modal__title": "跟随系统主题设置",
"theme_selector_modal__title_tip": "注:你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。",
"toggle_source_failed": "换源失败,请尝试手动在其他源搜索该歌曲播放",
"toggle_source_try": "尝试切换到其他源...",
"update__downgrade_tip": "我们发现你降级了版本({ver}),若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!\n注意:从新版本降级旧版时建议先备份歌单,若出现异常则可通过清理数据解决,数据目录路径文档有记录。",
"update__error_top": "自动下载新版本失败,你可以尝试重新下载更新或者手动去下载更新,\n新版地址在更新弹窗下面有写,下载新版直接覆盖安装即可,若安装失败则看常见问题解决,\n注意:目前只有Windows安装版可以自动更新(Linux的AppImage、deb版似乎也可以,未测试),其他版本请手动下载更新!",
"toggle_source_failed": "换源失败,请尝试手动在搜索页指定其他来源搜索该歌曲播放",
"toggle_source_try": "尝试切换到其他来源...",
"update__downgrade_tip": "我们发现你降级了版本({ver}),若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!\n\n注意:从新版本降级旧版时建议先备份歌单,若出现异常则可通过清理数据解决,数据目录路径文档有记录。",
"update__error_top": "自动下载新版本失败,你可以尝试重新下载更新或者手动去下载更新。\n\n新版地址在更新弹窗下面有写,下载新版直接覆盖安装即可,若安装失败则看常见问题解决。\n\n注意:目前只有 Windows 安装版可以自动更新(Linux 的 AppImage、deb 版似乎也可以,未测试),其他版本请手动下载更新!",
"update__ignore_cancel": "我就不想更新🤨",
"update__ignore_confirm": "好 去更新看看❤️",
"update__ignore_confirm": "好,去更新看看❤️",
"update__ignore_confirm_tip": "目前只有 Windows 安装版可以自动更新(Linux 的 AppImage、deb 版似乎也可以,未测试),其他版本请手动下载更新,\n新版地址在更新弹窗下面有写,下载新版直接覆盖安装即可,若安装失败看常见问题解决。",
"update__ignore_confirm_tip_confirm": "OK 已了解",
"update__ignore_tip": "你现在使用的版本距离最新版本已经落后了 {num} 个版本🤪,为了更好的使用体验,建议更新到最新版本哦~!\n注:若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!",
"update__ignore_confirm_tip_confirm": "OK,已了解",
"update__ignore_tip": "你现在使用的版本距离最新版本已经落后了 {num} 个版本🤪,为了更好的使用体验,建议更新到最新版本哦~!\n\n注:若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!",
"update__timeout_top": "下载时间过长提示\n\n你当前所在网络访问 GitHub 较慢,新版本已经下了一个钟了还没完成😳,你仍可选择继续等,但墙裂建议手动更新版本!",
"user_api__allow_show_update_alert": "允许显示更新弹窗",
"user_api__btn_export": "导出",
"user_api__btn_import": "本地导入",
"user_api__btn_import_online": "在线导入",
"user_api__btn_remove": "移除",
"user_api__import_file": "选择音乐API脚本文件",
"user_api__init_failed_alert": "自定义源 [{name}] 初始化失败:",
"user_api__import_file": "选择自定义源 API 脚本文件",
"user_api__init_failed_alert": "自定义源「{name}」初始化失败:",
"user_api__max_tip": "最多只能同时存在 20 个源哦🤪\n想要继续导入的话,请先移除一些旧的源腾出位置吧",
"user_api__noitem": "这里竟然是空的😲",
"user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
"user_api__readme": "源编写说明:",
"user_api__title": "自定义源管理",
"user_api__update_alert": "自定义源 [{name}] 发现新版本:",
"user_api__update_alert": "自定义源「{name}」发现新版本:",
"user_api__update_alert_open_url": "打开更新地址",
"user_api_import__failed": "自定义源导入失败:\n{message}",
"user_api_import_online__input_confirm": "导入",
+347 -316
View File
@@ -8,21 +8,21 @@
"btn_close": "關閉",
"btn_confirm": "確定",
"btn_save": "儲存",
"cancel_button_text": "取消",
"cancel_button_text": "我不",
"cancel_button_text_2": "不不不,點錯了",
"close": "關閉",
"comment__hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
"comment__hot_loading": "熱門評論加載中",
"comment__hot_load_error": "熱門評論載入失敗,點擊嘗試重新載入",
"comment__hot_loading": "熱門評論載入中...",
"comment__hot_title": "熱門評論",
"comment__location": "來自{location}",
"comment__new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
"comment__new_load_error": "最新評論載入失敗,點擊嘗試重新載入",
"comment__new_loading": "最新評論載入中",
"comment__new_title": "最新評論",
"comment__no_content": "暫無評論",
"comment__refresh": "刷新評論",
"comment__refresh": "重新整理評論",
"comment__show": "歌曲評論",
"comment__title": "{name} 的評論",
"comment__unavailable": "此歌曲不支持獲取評論",
"comment__title": "「{name}」的評論",
"comment__unavailable": "此歌曲不支援獲取評論",
"confirm_button_text": "是的",
"copy_tip": "(點擊複製)",
"date_format_hour": "{num} 小時前",
@@ -30,169 +30,173 @@
"date_format_second": "{num} 秒前",
"deep_link__handle_error_tip": "呼叫失敗:{message}",
"default": "預設",
"default_list": "試聽列表",
"default_list": "試聽清單",
"desktop_lyric__back": "返回",
"desktop_lyric__close": "關閉",
"desktop_lyric__font_decrease": "減小字體大小",
"desktop_lyric__font_increase": "增加字體大小",
"desktop_lyric__lock": "鎖定歌詞",
"desktop_lyric__lock": "鎖定歌詞視窗",
"desktop_lyric__lrc_active_zoom_off": "取消縮放目前播放的歌詞",
"desktop_lyric__lrc_active_zoom_on": "縮放目前播放的歌詞",
"desktop_lyric__opactiy_decrease": "增加透明度(右鍵可微調)",
"desktop_lyric__opactiy_increase": "減小透明度(右鍵可微調)",
"desktop_lyric__opacity_decrease": "增加透明度(右擊可微調)",
"desktop_lyric__opacity_increase": "減小透明度(右擊可微調)",
"desktop_lyric__theme": "主題配色",
"desktop_lyric__unlock": "解鎖歌詞",
"desktop_lyric__win_top_off": "取消置頂歌詞介面",
"desktop_lyric__win_top_on": "置頂歌詞介面",
"desktop_lyric__unlock": "解鎖歌詞視窗",
"desktop_lyric__win_top_off": "取消置頂歌詞視窗",
"desktop_lyric__win_top_on": "置頂歌詞視窗",
"download": "下載",
"download___status_complated": "下載完成",
"download___status_completed": "下載完成",
"download___status_error": "任務出錯",
"download___status_paused": "暫停下載",
"download___status_runing": "正在下載",
"download___status_wating": "等待下載",
"download__all": "全部任務",
"download___status_running": "正在下載",
"download___status_waiting": "等待下載",
"download__all": "所有任務",
"download__error": "出錯",
"download__finished": "下載完成",
"download__high_quality": "高品音質",
"download__high_quality": "高音質",
"download__lossless": "無損音質",
"download__multiple_tip": "已選擇 {len} 首歌曲",
"download__multiple_tip2": "請選擇要優先下載的音質",
"download__normal": "普通音質",
"download__multiple_tip": "已選取 {len} 首歌曲",
"download__multiple_tip2": "請選取要優先下載的音質",
"download__normal": "一般音質",
"download__not_available_tip": "該音質不可用",
"download__paused": "已暫停",
"download__progress": "進度",
"download__quality": "品質",
"download__runing": "正在下載",
"download__running": "正在下載",
"download__status": "狀態",
"download_status_error_check_path": "檢查下載路徑出錯,請檢查設定的下載目錄是否正常",
"download_status_error_check_path_exist": "存在同名文件,跳過下載",
"download_status_error_refresh_url": "連結失效,正在刷新連結",
"download_status_error_check_path_exist": "存在同名檔案,跳過下載",
"download_status_error_refresh_url": "連結失效,正在重新整理連結",
"download_status_error_response": "下載失敗:",
"download_status_error_url_failed": "取得音樂連結失敗",
"download_status_error_write": "歌曲儲存位置被佔用或沒有寫入權限,請嘗試變更歌曲儲存目錄或重新啟動軟體或重新啟動電腦,錯誤詳情:",
"download_status_error_url_failed": "獲取音樂連結失敗",
"download_status_error_write": "歌曲儲存位置被占用或沒有寫入權限,請嘗試變更歌曲儲存目錄或重啟軟體或重啟電腦,錯誤詳情:",
"download_status_start": "開始下載",
"download_status_url_geting": "音樂連結獲取中...",
"download_status_url_getting": "音樂連結獲取中...",
"download_status_write_queue": "資料寫入中({num})",
"duplicate_list_tip": "你之前已收藏過該清單 [{name}],是否需要更新裡面的歌曲?",
"duplicate_list_tip": "你之前已收藏過清單「{name}」,是否需要更新裡面的歌曲?",
"export": "匯出",
"fullscreen_exit": "退出全螢幕",
"history_clear": "清空搜尋紀錄",
"history_remove": "右鍵移除該歷史",
"history_clear": "清空搜尋歷史",
"history_remove": "右擊移除該歷史",
"history_search": "歷史搜尋",
"import": "導入",
"import": "匯入",
"leaderboard": "排行榜",
"list__add_to": "加到...",
"list__add_to": "加入到...",
"list__collect": "收藏",
"list__copy_name": "複製歌曲名",
"list__dislike": "不喜歡",
"list__download": "下載",
"list__export_part_desc": "選擇清單檔案儲存位置",
"list__file": "定位文件",
"list__export_part_desc": "選取清單檔案儲存位置",
"list__file": "定位檔案",
"list__import_part_button_cancel": "不要啊",
"list__import_part_button_confirm": "覆蓋掉",
"list__import_part_confirm": "導入的清單({importName})與本地列表({localName})的ID相同,是否覆寫本地列表?",
"list__import_part_desc": "選擇列表文件",
"list__import_part_button_confirm": "覆寫掉",
"list__import_part_confirm": "匯入的清單「{importName}」與本機清單「{localName}」的 ID 相同,是否覆寫本機清單?",
"list__import_part_desc": "選取清單檔案",
"list__load_failed": "啊,載入失敗了😭",
"list__loading": "清單加載中...⏳",
"list__loading": "清單載入中...⏳",
"list__move_to": "移動到...",
"list__movedown": "下移",
"list__moveup": "上移",
"list__name_default": "試聽列表",
"list__name_love": "我的收藏",
"list__new_list_btn": "新列表",
"list__new_list_input": "新列表...",
"list__name_default": "試聽清單",
"list__name_love": "我的最愛",
"list__new_list_btn": "建立清單",
"list__new_list_input": "新清單...",
"list__pause": "暫停任務",
"list__play": "播放",
"list__play_later": "稍後播放",
"list__remove": "刪除",
"list__remove_tip": "你真的想要移除 {name} 嗎?",
"list__remove_tip_button": "是的 沒錯",
"list__remove": "移除",
"list__remove_tip": "你真的想要移除「{name}」嗎?",
"list__remove_tip_button": "是的,沒錯",
"list__rename": "重新命名",
"list__search": "搜尋",
"list__sort": "調整位置",
"list__source_detail": "歌曲詳情頁",
"list__start": "開始任務",
"list__sync": "更新",
"list_add__btn_title": "把該歌曲加到 {name}",
"list_add__multiple_btn_title": "把這些歌曲加到 {name}",
"list_add__multiple_title_add": "新增已選取的 {num} 首歌曲到...",
"list_add__multiple_title_move": "移動已選的 {num} 首歌曲到...",
"list__toggle_source": "變更來源",
"list_add__btn_title": "把該歌曲加入到「{name}」",
"list_add__multiple_btn_title": "把這些歌曲加入到「{name}」",
"list_add__multiple_title_add": "將已選的 {num} 首歌曲加入到...",
"list_add__multiple_title_move": "將已選的 {num} 首歌曲移動到...",
"list_add__title_first_add": "添加",
"list_add__title_first_move": "移動",
"list_add__title_last": "到...",
"list_duplicate_tip": "已存在同名列表,是否仍要繼續建立?",
"list_import_tip__alldata": "導入失敗,這是一個所有資料備份文件,你需要去這裡導入:\n設定 -> 備份與還原 -> 所有資料 -> 匯入",
"list_import_tip__playlist": "導入失敗,這是一個列表備份文件,你需要去這裡導入:\n設定 -> 備份與還原 -> 部分資料 -> 匯入列表",
"list_import_tip__playlist_part": "導入失敗,這是一個單列表文件,你需要去這裡導入:\n我的清單 -> 右鍵點選任何一個清單名稱 -> 在彈出的選單中選擇導入",
"list_import_tip__setting": "導入失敗,這是一個設定備份文件,你需要去這裡導入:\n設定 -> 備份與還原 -> 部分資料 -> 設定導入",
"list_import_tip__unknown": "導入失敗,未知的文件類型,請嘗試升級到最新版本後再試",
"list_sort_modal_by_album": "專輯名",
"list_duplicate_tip": "已存在同名清單,是否仍要繼續建立?",
"list_import_tip__alldata": "匯入失敗,這是一個「所有資料」備份檔案,你需要在這裡匯入:\n\n設定 → 備份與復原 → 所有資料 → 匯入",
"list_import_tip__playlist": "匯入失敗,這是一個「清單」備份檔案,你需要在這裡匯入:\n\n設定 → 備份與復原 → 部分資料 → 匯入清單",
"list_import_tip__playlist_part": "匯入失敗,這是一個「僅清單」檔案,你需要在這裡匯入:\n\n我的清單 → 右擊任意一個清單名 → 在彈出的選單中選取「匯入」",
"list_import_tip__setting": "匯入失敗,這是一個「設定」備份檔案,你需要在這裡匯入:\n\n設定 → 備份與復原 → 部分資料 → 匯入設定",
"list_import_tip__unknown": "匯入失敗,未知的檔案類型,請嘗試升級到最新版本後再試",
"list_sort_modal_by_album": "專輯",
"list_sort_modal_by_down": "降序",
"list_sort_modal_by_field": "排序字段",
"list_sort_modal_by_name": "歌曲名",
"list_sort_modal_by_field": "排序欄位",
"list_sort_modal_by_name": "標題",
"list_sort_modal_by_random": "隨機亂序",
"list_sort_modal_by_singer": "歌手名",
"list_sort_modal_by_source": "歌曲源",
"list_sort_modal_by_time": "時長",
"list_sort_modal_by_singer": "演出者",
"list_sort_modal_by_source": "音樂串流平台",
"list_sort_modal_by_time": "長度",
"list_sort_modal_by_type": "排序類別",
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你確定要這麼做嗎?",
"list_update_modal__auto_update": "自動更新",
"list_update_modal__tips": "💡 每次啟動軟體時將會自動更新已勾選「自動更新」的列表",
"list_update_modal__tips": "💡 每次啟動軟體時將會自動更新已勾選「自動更新」的清單",
"list_update_modal__title": "清單更新管理",
"list_update_modal__update": "立即更新",
"lists__add_local_file_desc": "選擇歌曲文件",
"lists__dislike_music_tip": "你真的不喜歡 {name} 嗎?",
"lists__add_local_file_desc": "選取歌曲檔案",
"lists__dislike_music_singer_tip": "你真的不喜歡「{singer}」的「{name}」嗎?",
"lists__dislike_music_tip": "你真的不喜歡「{name}」嗎?",
"lists__duplicate": "重複歌曲",
"lists__export": "匯出",
"lists__export_part_desc": "選擇清單檔案儲存位置",
"lists__import": "導入",
"lists__export_part_desc": "選取清單檔案儲存位置",
"lists__import": "匯入",
"lists__import_part_button_cancel": "不要啊",
"lists__import_part_button_confirm": "覆蓋掉",
"lists__import_part_confirm": "導入的清單({importName})與本地列表({localName})的ID相同,是否覆寫本地列表?",
"lists__import_part_desc": "選擇列表文件",
"lists__new_list_btn": "新列表",
"lists__new_list_input": "新列表...",
"lists__remove": "刪除",
"lists__import_part_button_confirm": "覆寫掉",
"lists__import_part_confirm": "匯入的清單「{importName}」與本機清單「{localName}」的 ID 相同,是否覆寫本機清單?",
"lists__import_part_desc": "選取清單檔案",
"lists__new_list_btn": "建立清單",
"lists__new_list_input": "新清單...",
"lists__remove": "移除",
"lists__remove_music_tip": "你真的要移除所選的 {len} 首歌曲嗎?",
"lists__remove_tip": "你真的想要移除 {name} 嗎?",
"lists__remove_tip_button": "是的 沒錯",
"lists__remove_tip": "你真的想要移除「{name}」嗎?",
"lists__remove_tip_button": "是的,沒錯",
"lists__rename": "重新命名",
"lists__select_local_file": "新增本地歌曲",
"lists__select_local_file": "添加本機歌曲",
"lists__sort_list": "排序歌曲",
"lists__source_detail": "歌單詳情頁",
"lists__sync": "更新",
"lists__sync_confirm_tip": "這將會把 {name} 內的歌曲替換成線上清單的歌曲,你確認要更新嗎?",
"load_list_file_error_detail": "我們已經幫你把舊的清單檔案備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情:{detail}",
"load_list_file_error_title": "播放清單資料載入錯誤(建議到GitHub或加群回饋)",
"loding": "載入中...",
"lists__sync_confirm_tip": "這將會把「{name}」內的歌曲取代成線上清單的歌曲,你確認要更新嗎?",
"load_list_file_error_detail": "我們已經幫你把舊的清單檔案備份到:{path}\n\n它以 JSON 格式儲存,你可以嘗試手動修復並復原它。\n\n錯誤詳情:{detail}",
"load_list_file_error_title": "播放清單資料載入錯誤(建議到 GitHub 回報)",
"loading": "載入中...",
"love_list": "收藏",
"lyric__load_error": "歌詞獲取失敗",
"lyric__select": "歌詞文本選擇",
"lyric__select": "歌詞文字選取",
"lyric_menu__align": "歌詞對齊方式",
"lyric_menu__align_center": "居中",
"lyric_menu__align_left": "居左",
"lyric_menu__align_center": "置中",
"lyric_menu__align_left": "置左",
"lyric_menu__lrc_size": "字體大小 [ {size} ]",
"lyric_menu__offset": "歌詞偏移 [ {offset}ms ]",
"lyric_menu__offset_add_10": "加快 10 毫秒",
"lyric_menu__offset_add_100": "加快 100 毫秒",
"lyric_menu__offset_dec_10": "減慢 10 毫秒",
"lyric_menu__offset_dec_100": "減慢 100 毫秒",
"lyric_menu__offset_reset": "重置",
"lyric_menu__size_add": "加大字體(右鍵可微調)",
"lyric_menu__size_dec": "減小字體(右鍵可微調)",
"lyric_menu__size_reset": "重置",
"lyric_menu__offset_reset": "重設",
"lyric_menu__size_add": "加大字體(右擊可微調)",
"lyric_menu__size_dec": "減小字體(右擊可微調)",
"lyric_menu__size_reset": "重設",
"media_device__empty_device_tip": "音訊輸出裝置為空,若出現無法播放的狀況,請檢查音效卡驅動是否已安裝或重裝音效卡驅動!",
"min": "最小化",
"music_album": "專輯名",
"music_album": "專輯",
"music_duplicate": "重複歌曲",
"music_name": "歌曲名",
"music_singer": "藝術家",
"music_name": "標題",
"music_singer": "演出者",
"music_sort__input_tip": "請輸入要調整到第幾個位置",
"music_sort__title": "將 {name} 的位置調整到:",
"music_sort__title_multiple": "將已選取的 {num} 首歌曲的位置調整至:",
"music_time": "時長",
"my_list": "我的列表",
"no_item": "列表竟然是空的...",
"music_sort__title": "將「{name}」的位置調整到:",
"music_sort__title_multiple": "將已選取的 {num} 首歌曲的位置調整到:",
"music_time": "長度",
"music_toggle_clean": "取消變更",
"my_list": "我的清單",
"no_item": "清單竟然是空的...",
"not_agree": "不接受",
"ok": "我知道了",
"pagination__next": "下一頁",
@@ -206,36 +210,36 @@
"play_timeout_tip": "{time} 後暫停播放",
"play_timeout_unit": "分鐘",
"play_timeout_update": "更新定時",
"player__add_music_to": "新增目前歌曲到...",
"player__add_music_to": "將目前歌曲加入到...",
"player__buffering": "緩衝中...",
"player__desktop_lyric_lock": "右鍵鎖定歌詞",
"player__desktop_lyric_off": "關閉桌面歌詞",
"player__desktop_lyric_on": "開啟桌面歌詞",
"player__desktop_lyric_unlock": "右鍵解鎖歌詞",
"player__desktop_lyric_lock": "右擊鎖定歌詞",
"player__desktop_lyric_off": "關閉歌詞視窗",
"player__desktop_lyric_on": "開啟歌詞視窗",
"player__desktop_lyric_unlock": "右擊解鎖歌詞",
"player__end": "播放完畢",
"player__error": "音訊載入出錯,5 秒後切換下一首",
"player__geting_url": "歌曲連結獲取中...",
"player__geting_url_delay_retry": "服務繁忙,{time}秒後重試...",
"player__getting_url": "歌曲連結獲取中...",
"player__getting_url_delay_retry": "服務繁忙,{time} 秒後重試...",
"player__hide_detail_tip": "隱藏詳情頁(介面內右鍵雙擊可快速隱藏詳情頁)",
"player__loading": "音樂加載中...",
"player__music_album": "專輯名稱:",
"player__music_name": "歌曲名:",
"player__music_singer": "藝術家:",
"player__loading": "音樂載入中...",
"player__music_album": "專輯:",
"player__music_name": "標題:",
"player__music_singer": "演出者:",
"player__next": "下一首",
"player__pause": "暫停",
"player__pic_tip": "播放詳情頁(右鍵在「我的清單」上定位目前播放的歌曲)",
"player__pic_tip": "播放詳情頁(右擊在「我的清單」定位目前播放的歌曲)",
"player__play": "播放",
"player__play_toggle_mode_list": "順序播放",
"player__play_toggle_mode_list_loop": "清單循環",
"player__play_toggle_mode_off": "停用",
"player__play_toggle_mode_random": "清單隨機",
"player__play_toggle_mode_single_loop": "單曲循環",
"player__play_toggle_mode_list_loop": "重複播放清單",
"player__play_toggle_mode_off": "停用歌曲切換",
"player__play_toggle_mode_random": "隨機播放",
"player__play_toggle_mode_single_loop": "重複播放",
"player__playback_preserves_pitch": "音調補償",
"player__playback_rate": "目前播放速率:",
"player__playback_rate_reset_btn": "重置",
"player__playback_rate_reset_btn": "重設",
"player__playing": "播放中...",
"player__prev": "上一首",
"player__refresh_url": "URL過期,正在刷新URL...",
"player__refresh_url": "URL 過期,正在重新整理 URL...",
"player__sound_effect": "音效設定(實驗性)",
"player__sound_effect_biquad_filter": "均衡器",
"player__sound_effect_biquad_filter_preset_classical": "古典",
@@ -247,18 +251,18 @@
"player__sound_effect_biquad_filter_preset_soft": "柔和",
"player__sound_effect_biquad_filter_preset_subwoofer": "重低音",
"player__sound_effect_biquad_filter_preset_vocal": "人聲",
"player__sound_effect_biquad_filter_reset_btn": "重置",
"player__sound_effect_biquad_filter_save_btn": "另存預設",
"player__sound_effect_biquad_filter_reset_btn": "重設",
"player__sound_effect_biquad_filter_save_btn": "另存",
"player__sound_effect_biquad_filter_save_input": "新預設...",
"player__sound_effect_convolution": "環境混響音效",
"player__sound_effect_convolution_file_bright_hall": "大廳",
"player__sound_effect_convolution_file_cardiod_35_10_spread": "心形擴散",
"player__sound_effect_convolution_file_cinema_diningroom": "電影院",
"player__sound_effect_convolution_file_dining_living_true_stereo": "餐廳",
"player__sound_effect_convolution_file_feedback_spring": "反饋彈簧",
"player__sound_effect_convolution_file_living_bedroom_leveled": "廁所",
"player__sound_effect_convolution_file_matrix_1": "矩陣混響",
"player__sound_effect_convolution_file_matrix_2": "矩陣混響2",
"player__sound_effect_convolution_file_feedback_spring": "回饋彈簧",
"player__sound_effect_convolution_file_living_bedroom_leveled": "洗手間",
"player__sound_effect_convolution_file_matrix_1": "矩陣混響(1)",
"player__sound_effect_convolution_file_matrix_2": "矩陣混響(2)",
"player__sound_effect_convolution_file_s2_r4_bd": "教堂",
"player__sound_effect_convolution_file_s3_r1_bd": "立體聲",
"player__sound_effect_convolution_file_spreader50_65ms": "室內",
@@ -266,14 +270,15 @@
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "磁性立體聲",
"player__sound_effect_convolution_main_gain": "原始音訊增益",
"player__sound_effect_convolution_send_gain": "環境音效增益",
"player__sound_effect_features_tip": "提示:「音效設定」與「自訂音訊輸出裝置」衝突,啟用音效設定後音訊輸出裝置將會被重設為預設的輸出裝置,目前此問題暫無法解決。",
"player__sound_effect_panner": "3D 立體環繞(需使用耳機)",
"player__sound_effect_panner_enabled": "啟用",
"player__sound_effect_panner_sound_r": "聲音距離",
"player__sound_effect_panner_sound_speed": "環繞速度",
"player__sound_effect_pitch_shifter": "音調升降調節",
"player__sound_effect_pitch_shifter_preset_semitones": "{num} 半音",
"player__sound_effect_pitch_shifter_reset_btn": "重置",
"player__sound_effect_pitch_shifter_tip": "由於升降調需要即時處理音訊數據,這會導致額外的CPU佔用\n\n已知問題:\n如果CPU資源不夠時將處理導致任務堆積而出現聲音異常,這時需要暫停播放一段時間等堆積的任務處理完畢再播放",
"player__sound_effect_pitch_shifter_reset_btn": "重設",
"player__sound_effect_pitch_shifter_tip": "由於升降調需要即時處理音訊資料,這會導致額外的 CPU 占用。\n\n已知問題:\n如果 CPU 資源不夠將導致處理任務堆積而出現聲音異常。\n這時需要暫停播放一段時間等堆積的任務處理完畢再播放。",
"player__stop": "暫停播放",
"player__volume": "目前音量:",
"player__volume_mute_label": "靜音",
@@ -284,28 +289,28 @@
"search__type_songlist": "歌單",
"search__welcome": "搜我所想~~😉",
"setting": "設定",
"setting__about": "關於洛雪音樂",
"setting__about": "關於 LX Music",
"setting__backup": "備份與復原",
"setting__backup_all": "所有數據(列表數據與設定數據)",
"setting__backup_all": "所有資料(清單資料與設定資料)",
"setting__backup_all_export": "匯出",
"setting__backup_all_export_desc": "選擇備份保存位置",
"setting__backup_all_import": "導入",
"setting__backup_all_import_desc": "選擇備份文件",
"setting__backup_other": "其他備份格式(目前不支援還原此類備份檔案)",
"setting__backup_other_export_dir": "選擇檔案儲存位置",
"setting__backup_other_export_list_csv": "匯出 CSV 格式的列表",
"setting__backup_other_export_list_text": "匯出 TXT 格式的列表",
"setting__backup_all_export_desc": "選取備份儲存位置",
"setting__backup_all_import": "匯入",
"setting__backup_all_import_desc": "選取備份檔案",
"setting__backup_other": "其他備份格式(目前不支援復原此類備份檔案)",
"setting__backup_other_export_dir": "選取檔案儲存位置",
"setting__backup_other_export_list_csv": "匯出 CSV 格式清單",
"setting__backup_other_export_list_text": "匯出 TXT 格式清單",
"setting__backup_other_export_list_text_confirm": "是否將所有清單合併為一個檔案?",
"setting__backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自訂列表,設定數據不包括快捷鍵設定)",
"setting__backup_part_export_list": "導出列表",
"setting__backup_part_export_list_desc": "選擇歌單儲存位置",
"setting__backup_part": "部分資料(清單資料包括「試聽清單」、「我的最愛」和使用者自行建立的清單;設定資料不包括快速鍵設定)",
"setting__backup_part_export_list": "匯出清單",
"setting__backup_part_export_list_desc": "選取歌單儲存位置",
"setting__backup_part_export_setting": "匯出設定",
"setting__backup_part_export_setting_desc": "選擇設定儲存位置",
"setting__backup_part_import_list": "導入列表",
"setting__backup_part_import_list_confirm": "備份檔案中清單與現有清單ID相同時,現有清單內歌曲將會被覆蓋,是否要繼續?",
"setting__backup_part_import_list_desc": "選擇列表文件",
"setting__backup_part_import_setting": "導入設定",
"setting__backup_part_import_setting_desc": "選擇設定檔",
"setting__backup_part_export_setting_desc": "選取設定儲存位置",
"setting__backup_part_import_list": "匯入清單",
"setting__backup_part_import_list_confirm": "備份檔案中清單與現有清單 ID 相同時,現有清單內歌曲將會被覆寫,是否要繼續?",
"setting__backup_part_import_list_desc": "選取清單檔案",
"setting__backup_part_import_setting": "匯入設定",
"setting__backup_part_import_setting_desc": "選取設定檔",
"setting__basic": "基本設定",
"setting__basic_animation": "彈出層隨機動畫",
"setting__basic_control_btn_position": "控制按鈕位置",
@@ -325,23 +330,23 @@
"setting__basic_playbar_progress_style_full": "全寬",
"setting__basic_playbar_progress_style_middle": "中等",
"setting__basic_playbar_progress_style_mini": "迷你",
"setting__basic_show_animation": "顯示切換動畫",
"setting__basic_source": "音樂來源",
"setting__basic_show_animation": "顯示動畫效果",
"setting__basic_source": "自訂來源 API",
"setting__basic_source_status_failed": "初始化失敗",
"setting__basic_source_status_initing": "初始化中",
"setting__basic_source_status_initing": "初始化中...",
"setting__basic_source_status_success": "初始化成功",
"setting__basic_source_temp": "臨時介面(軟體的某些功能不可用,建議測試介面不可用再使用本介面)",
"setting__basic_source_test": "測試介面(幾乎軟體的所有功能都可用)",
"setting__basic_source_user_api_btn": "自訂來源管理",
"setting__basic_sourcename": "音源名字",
"setting__basic_source_temp": "臨時 API(軟體的某些功能不可用,建議測試 API 不可用時再使用這個 API)",
"setting__basic_source_test": "測試 API(幾乎軟體的所有功能都可用)",
"setting__basic_source_user_api_btn": "自訂來源 API 管理",
"setting__basic_sourcename": "音樂串流平台名稱",
"setting__basic_sourcename_alias": "別名",
"setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "選擇音源名字類型",
"setting__basic_sourcename_title": "選取音樂串流平台名稱類型",
"setting__basic_start_in_fullscreen": "以全螢幕模式啟動",
"setting__basic_theme": "主題顏色",
"setting__basic_theme_auto_tip": "此乃動態主題,你可以預先設定一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設定的相應主題。\n注意:滑鼠 右鍵 此主題項目即可開啟亮、暗色主題設定視窗。",
"setting__basic_to_tray": "關閉軟體時不退出軟體將其最小化到系統托盤",
"setting__basic_window_size": "窗口尺寸",
"setting__basic_theme_auto_tip": "此乃動態主題,你可以預先設定一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設定的相應主題。\n\n註:滑鼠右擊此主題項即可開啟亮、暗色主題設定視窗。",
"setting__basic_to_tray": "關閉視窗時不退出軟體將其最小化到系統匣",
"setting__basic_window_size": "視窗尺寸",
"setting__basic_window_size_big": "大",
"setting__basic_window_size_huge": "巨大",
"setting__basic_window_size_larger": "較大",
@@ -350,84 +355,91 @@
"setting__basic_window_size_small": "小",
"setting__basic_window_size_smaller": "較小",
"setting__basic_window_size_title": "設定軟體視窗尺寸",
"setting__click_copy": "點選複製",
"setting__click_open": "點擊打開",
"setting__click_copy": "點擊複製",
"setting__click_open": "點擊開啟",
"setting__desktop_lyric": "桌面歌詞設定",
"setting__desktop_lyric_align": "歌詞對齊方式",
"setting__desktop_lyric_align_center": "居中",
"setting__desktop_lyric_align_left": "居左",
"setting__desktop_lyric_align_right": "居右",
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他視窗之上",
"setting__desktop_lyric_always_on_top_loop": "自動刷新歌詞置頂(歌詞置頂後仍被某些程式遮擋時可嘗試啟用此設定)",
"setting__desktop_lyric_align_center": "置中",
"setting__desktop_lyric_align_left": "置左",
"setting__desktop_lyric_align_right": "置右",
"setting__desktop_lyric_always_on_top": "使歌詞視窗總是在其他視窗之上",
"setting__desktop_lyric_always_on_top_loop": "自動重新整理歌詞視窗置頂",
"setting__desktop_lyric_always_on_top_loop_tip": "當歌詞視窗置頂後仍被某些程式遮擋時可嘗試啟用此選項",
"setting__desktop_lyric_audio_visualization": "音訊視覺化(實驗性)",
"setting__desktop_lyric_color": "歌詞字體顏色",
"setting__desktop_lyric_color_reset": "重置顏色",
"setting__desktop_lyric_color_reset": "重設顏色",
"setting__desktop_lyric_delay_scroll": "延遲歌詞滾動",
"setting__desktop_lyric_direction": "歌詞顯示方向",
"setting__desktop_lyric_direction_horizontal": "水平方向",
"setting__desktop_lyric_direction_vertical": "垂直方向",
"setting__desktop_lyric_ellipsis": "不允許歌詞換行",
"setting__desktop_lyric_enable": "顯示歌詞",
"setting__desktop_lyric_enable": "顯示歌詞視窗",
"setting__desktop_lyric_font": "歌詞字體",
"setting__desktop_lyric_font_default": "預設",
"setting__desktop_lyric_font_weight": "加粗字體",
"setting__desktop_lyric_fullscreen_hide": "全螢幕時自動關閉歌詞",
"setting__desktop_lyric_hover_hide": "滑鼠移入歌詞區域時降低歌詞透明度(此功能有平台相容性問題)",
"setting__desktop_lyric_fullscreen_hide": "全螢幕時自動關閉歌詞視窗",
"setting__desktop_lyric_hover_hide": "滑鼠移入歌詞視窗時降低歌詞透明度",
"setting__desktop_lyric_hover_hide_tip": "此功能存在平台相容性問題,在某些平台上可能會出現預料外的問題",
"setting__desktop_lyric_line_gap": "歌詞間距({num})",
"setting__desktop_lyric_line_gap_add": "加大間距",
"setting__desktop_lyric_line_gap_dec": "減小間距",
"setting__desktop_lyric_lock": "鎖定歌詞",
"setting__desktop_lyric_lock_screen": "不允許歌詞視窗拖出主畫面之外",
"setting__desktop_lyric_lock": "鎖定歌詞視窗",
"setting__desktop_lyric_lock_screen": "不允許歌詞視窗拖出主螢幕之外",
"setting__desktop_lyric_played_color": "已播放顏色",
"setting__desktop_lyric_reset": "重置",
"setting__desktop_lyric_reset_window": "重置視窗設定",
"setting__desktop_lyric_reset": "重設",
"setting__desktop_lyric_reset_window": "重設視窗設定",
"setting__desktop_lyric_scroll_align": "正在播放歌詞滾動位置",
"setting__desktop_lyric_scroll_align_center": "中心",
"setting__desktop_lyric_scroll_align_top": "頂部",
"setting__desktop_lyric_shadow_color": "陰影顏色",
"setting__desktop_lyric_show_taskbar": "在工作列顯示歌詞進程(此設定用於在錄影軟體無法擷取歌詞視窗時的變通解決方法)",
"setting__desktop_lyric_show_taskbar": "在工具列顯示歌詞視窗處理程式",
"setting__desktop_lyric_show_taskbar_tip": "此選項作為在螢幕錄影軟體無法擷取歌詞視窗時的變通解決方法",
"setting__desktop_lyric_unplay_color": "未播放顏色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"setting__dislike_list_input_tip": "標題@演出者\n標題\n@演出者",
"setting__dislike_list_save_btn": "儲存",
"setting__dislike_list_tips": "1. 每條一行,若歌曲或歌手名字中存在“@”符號,則需要將其替換成“",
"setting__dislike_list_title": "不喜歡的歌曲規則列表",
"setting__dislike_list_tips": "1. 每條一行,若歌曲或者演出者名字中存在「@」符號,需要將其取代成「#」\n2. 指定某演出者的某首歌:標題@演出者\n3. 指定某首歌:標題\n4. 指定某演出者:@演出者",
"setting__dislike_list_title": "「不喜歡的歌曲」規則清單",
"setting__download": "下載設定",
"setting__download_data_embed": "是否將以下內容嵌入到音訊檔案中",
"setting__download_embed_lyric": "歌詞嵌入",
"setting__download_embed_pic": "封面嵌入",
"setting__download_data_embed": "嵌入到音訊檔案中的內容",
"setting__download_embed_lyric": "嵌入歌詞",
"setting__download_embed_pic": "嵌入封面",
"setting__download_embed_rlyric": "同時嵌入羅馬音歌詞(如果有)",
"setting__download_embed_tlyric": "同時嵌入翻譯歌詞(如果有)",
"setting__download_enable": "是否啟用下載功能",
"setting__download_enable": "啟用下載功能",
"setting__download_lyric": "歌詞下載",
"setting__download_lyric_format": "下載的歌詞檔案編碼格式",
"setting__download_lyric_format_gbk": "GBK(在某些裝置上出現中文亂碼時可嘗試選擇此格式)",
"setting__download_lyric_format_tip": "在某些裝置上出現中文亂碼時可嘗試選取 GBK 格式",
"setting__download_lyric_format_gbk": "GBK",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "是否同時下載歌詞文件",
"setting__download_name": "文件命名方式",
"setting__download_name1": "歌名 - 歌手",
"setting__download_name2": "歌手 - 歌名",
"setting__download_name3": "歌名",
"setting__download_lyric_title": "同時下載歌詞檔案",
"setting__download_max_num": "同時下載任務數",
"setting__download_max_num_tip": "過大的同時下載數量可能會導致你的 IP 被自訂來源 API 封禁,是否確認修改?",
"setting__download_max_num_tooltip": "設定過大可能會導致 IP 被封,這取決於自訂來源 API",
"setting__download_name": "檔案命名方式",
"setting__download_name1": "「標題 - 演出者」",
"setting__download_name2": "「演出者 - 標題」",
"setting__download_name3": "僅標題",
"setting__download_name_title": "下載歌曲時的命名方式",
"setting__download_path": "下載路徑",
"setting__download_path_change_btn": "變更",
"setting__download_path_label": "目前下載路徑:",
"setting__download_path_open_label": "點擊開啟目前路徑",
"setting__download_path_title": "下載歌曲已儲存的路徑",
"setting__download_rlyric": "同時將羅馬音歌詞寫入歌詞檔中(如果有)",
"setting__download_select_save_path": "選擇歌曲儲存路徑",
"setting__download_skip_exist_file": "下載目錄存在同名的檔案時跳過下載此任務",
"setting__download_path_title": "下載歌曲儲存的路徑",
"setting__download_rlyric": "同時將羅馬音歌詞寫入歌詞檔案中(如果有)",
"setting__download_select_save_path": "選取歌曲儲存路徑",
"setting__download_skip_exist_file": "下載目錄存在同名檔案時跳過下載此任務",
"setting__download_tlyric": "同時將翻譯歌詞寫入歌詞檔案中(如果有)",
"setting__download_use_other_source": "自動換源下載",
"setting__download_use_other_source_tip": "當無法從歌曲的原始來源下載時,嘗試切換到其他來源下載,附註:此功能不100%保證換源後的歌曲版本與原版一致",
"setting__download_use_other_source": "自動變更來源下載",
"setting__download_use_other_source_tip": "當無法從歌曲的原始來源下載時,嘗試變更到其他來源下載。\n註:此功能不 100% 保證變更來源後的歌曲與原版一致。",
"setting__hot_key": "快速鍵設定",
"setting__hot_key_common_focus_search_input": "聚焦搜尋框",
"setting__hot_key_common_min": "最小化程式",
"setting__hot_key_common_toggle_close": "退出程序",
"setting__hot_key_common_toggle_hide": "顯示/隱藏程式",
"setting__hot_key_common_toggle_min": "最小化/還原程序",
"setting__hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
"setting__hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
"setting__hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
"setting__hot_key_common_min": "最小化軟體",
"setting__hot_key_common_toggle_close": "退出軟體",
"setting__hot_key_common_toggle_hide": "顯示/隱藏軟體",
"setting__hot_key_common_toggle_min": "最小化/還原軟體",
"setting__hot_key_desktop_lyric_toggle_always_top": "歌詞視窗置頂切換",
"setting__hot_key_desktop_lyric_toggle_lock": "歌詞視窗鎖定切換",
"setting__hot_key_desktop_lyric_toggle_visible": "開啟/關閉歌詞視窗",
"setting__hot_key_global_title": "全域快速鍵",
"setting__hot_key_local_title": "軟體內快速鍵",
"setting__hot_key_player_music_dislike": "不喜歡該歌曲",
@@ -441,121 +453,138 @@
"setting__hot_key_player_volume_up": "增加音量",
"setting__hot_key_tip_input": "請輸入新的按鍵",
"setting__hot_key_unset_input": "未設定",
"setting__is_enable": "是否啟用",
"setting__is_show": "是否顯示",
"setting__is_enable": "啟用",
"setting__is_show": "顯示",
"setting__list": "清單設定",
"setting__list_action_btn": "顯示清單操作按鈕",
"setting__list_add_music_location_type": "新增歌曲到清單時的位置",
"setting__list_add_music_location_type": "加入歌曲到清單時的位置",
"setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "頂部",
"setting__list_click_action": "雙擊清單裡的歌曲時自動切換到目前清單播放(僅對歌單、排行榜有效)",
"setting__list_scroll": "記住播放清單滾動條位置(僅對我的音樂分類有效)",
"setting__list_source": "顯示歌曲來源(僅對我的音樂分類有效)",
"setting__list_click_action": "雙擊清單裡的歌曲時自動切換到目前清單播放(僅對「歌單」、「排行榜」有效)",
"setting__list_scroll": "記住播放清單滾動條位置(僅對「我的清單」有效)",
"setting__list_source": "顯示歌曲來自哪個音樂串流平台(僅對「我的清單」有效)",
"setting__network": "網路設定",
"setting__network_proxy_host": "主機",
"setting__network_proxy_password": "密碼",
"setting__network_proxy_port": "連接埠",
"setting__network_proxy_title": "HTTP代理設定(亂設定軟體將無法連網)",
"setting__network_proxy_port": "埠",
"setting__network_proxy_title": "HTTP 代理設定(亂設定軟體將無法聯網)",
"setting__network_proxy_username": "使用者名稱",
"setting__odc": "強迫症設定",
"setting__odc_clear_search_input": "離開搜尋介面時清空搜尋框",
"setting__odc_clear_search_list": "離開搜尋介面時清空搜尋列表",
"setting__odc_clear_search_input": "離開搜尋頁面時清空搜尋框",
"setting__odc_clear_search_list": "離開搜尋頁面時清空搜尋清單",
"setting__open_api": "開放 API",
"setting__open_api_address": "服務位址:",
"setting__open_api_bind_lan": "允許來自區域網路的訪問",
"setting__open_api_enable": "啟用開放 API 服務",
"setting__open_api_port": "服務埠",
"setting__open_api_port_tip": "請輸入開放 API 服務埠",
"setting__open_api_tip": "該功能用於為第三方軟體提供串接 LX Music 的能力,目前可用的功能可以看:",
"setting__open_api_tip_link": "串接教學文件",
"setting__other": "其他",
"setting__other_dislike_list": "不喜歡的歌曲規則",
"setting__other_dislike_list": "「不喜歡的歌曲」規則",
"setting__other_dislike_list_label": "規則數量:",
"setting__other_dislike_list_show_btn": "編輯不喜歡歌曲規則",
"setting__other_listdata": "列表資料清理",
"setting__other_listdata_clear_btn": "清空我的清單數據",
"setting__other_listdata_clear_tip_confirm": "這將清理你創建的 所有清單 及收藏的 所有歌曲,是否真的要繼續?",
"setting__other_dislike_list_show_btn": "編輯規則",
"setting__other_listdata": "清單資料清理",
"setting__other_listdata_clear_btn": "清空「我的清單」資料",
"setting__other_listdata_clear_tip_confirm": "這將清理你建立的「所有清單」及收藏的「所有歌曲」,是否真的要繼續?",
"setting__other_lyric_edited_cache": "已調整過偏移時間的歌詞管理",
"setting__other_lyric_edited_clear_btn": "清理已調整過時間的歌詞",
"setting__other_lyric_edited_clear_tip_confirm": "這將清理所有你之前已調整過偏移時間的歌詞,是否確認清理? \n(手抖確認🤪)",
"setting__other_lyric_edited_clear_tip_confirm": "這將清理所有你之前已調整過偏移時間的歌詞,是否確認清理?(手抖確認🤪)",
"setting__other_lyric_edited_label": "歌詞數量:",
"setting__other_lyric_raw_clear_btn": "清理歌詞快取",
"setting__other_lyric_raw_label": "歌詞數量:",
"setting__other_music_url_clear_btn": "清理歌曲URL緩存",
"setting__other_music_url_clear_btn": "清理歌曲 URL 快取",
"setting__other_music_url_label": "歌曲 URL 數量:",
"setting__other_other_cache": "其他快取管理",
"setting__other_other_source_clear_btn": "清理換源歌曲緩存",
"setting__other_other_source_label": "換源歌曲資訊數:",
"setting__other_other_source_clear_btn": "清理變更來源歌曲快取",
"setting__other_other_source_label": "變更來源歌曲資訊數量:",
"setting__other_resource_cache": "資源快取管理",
"setting__other_resource_cache_clear_btn": "清理資源快取",
"setting__other_resource_cache_confirm": "我要清掉",
"setting__other_resource_cache_label": "軟體已使用快取大小:",
"setting__other_resource_cache_tip": "圖片、音訊等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟體會根據磁碟空間動態管理快取大小",
"setting__other_resource_cache_tip_confirm": "涉及圖片、音訊等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟體會根據磁碟空間動態管理快取大小,是否仍要清理?",
"setting__other_tray_theme": "托盤圖示樣式",
"setting__other_resource_cache_tip": "包括圖片、音訊等快取,清理後這些資源將需要重新下載,不建議清理,軟體會根據磁碟空間動態管理快取大小",
"setting__other_resource_cache_tip_confirm": "涉及圖片、音訊等快取,清理後這些資源將需要重新下載,不建議清理,軟體會根據磁碟空間動態管理快取大小,是否仍要清理?",
"setting__other_tray_theme": "系統匣圖示樣式",
"setting__other_tray_theme_auto": "跟隨系統亮暗模式",
"setting__other_tray_theme_black": "黑色",
"setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色",
"setting__play": "播放設定",
"setting__play_auto_clean_played_list": "點選與播放清單相同的清單切歌時是否已清空已播放清單(隨機模式下清單內所有歌曲會重新參與隨機)",
"setting__play_advanced_audio_features_tip": "自訂音訊輸出裝置與該功能衝突,啟用該功能後音訊輸出裝置將會被重設為預設,目前此問題暫無法解決。是否仍要開啟?",
"setting__play_auto_clean_played_list": "點擊與播放清單相同的清單切歌時清空已播放清單",
"setting__play_auto_clean_played_list_tip": "隨機模式下清單內所有歌曲會重新參與隨機",
"setting__play_auto_skip_on_error": "播放錯誤時自動切換歌曲",
"setting__play_detail": "播放詳情頁設定",
"setting__play_detail_align": "歌詞對齊方式",
"setting__play_detail_align_center": "居中",
"setting__play_detail_align_left": "居左",
"setting__play_detail_align_right": "居右",
"setting__play_detail_font_size": "歌詞字體大小(可在播放詳情頁使用鍵盤的 - 調整字體大小)",
"setting__play_detail_align_center": "置中",
"setting__play_detail_align_left": "置左",
"setting__play_detail_align_right": "置右",
"setting__play_detail_font_size": "歌詞字體大小(可以在播放詳情頁使用鍵盤的「+」「-」調整字體大小)",
"setting__play_detail_font_size_current": "目前字體大小:{size}",
"setting__play_detail_font_size_reset": "重置",
"setting__play_detail_font_size_reset": "重設",
"setting__play_detail_font_zoom": "縮放目前正在播放的歌詞",
"setting__play_detail_lyric_progress": "允許透過歌詞調整播放進度",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果可用,此功能比較耗性能,低配置電腦不建議開啟)",
"setting__play_detail_lyric_delay_scroll": "延遲歌詞滾動",
"setting__play_detail_lyric_progress": "允許透過拖曳歌詞調整播放進度",
"setting__play_lyric_lxlrc": "使用卡拉 OK 式歌詞播放(如果可用)",
"setting__play_lyric_lxlrc_tip": "此功能比較耗性能,低配置電腦不建議開啟!",
"setting__play_lyric_roma": "顯示歌詞羅馬音(如果可用)",
"setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文",
"setting__play_lyric_s2t": "將播放與下載的中文歌詞轉換為繁體",
"setting__play_lyric_transition": "顯示歌詞翻譯(如果可用)",
"setting__play_max_output_channel_count": "使用裝置能處理的最大聲道數輸出音訊",
"setting__play_mediaDevice": "音訊輸出",
"setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出裝置被改變時暫停播放歌曲",
"setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備",
"setting__play_media_device_error_tip": "此功能與進階音訊功能(音訊視覺化、音效設定)衝突,你本次啟動軟體時已啟用這些功能,此設定暫不可用,請 關閉這些功能 並 重新啟動 軟體後,再來修改此設定。",
"setting__play_media_device_tip": "此功能與音訊視覺化功能衝突,兩者無法同時啟用,是否將音訊視覺化關閉 並 應用所選音訊輸出設定?",
"setting__play_mediaDevice_remove_stop_play": "目前的音訊輸出裝置被改變時暫停播放歌曲",
"setting__play_mediaDevice_title": "選取音訊輸出的媒體裝置",
"setting__play_media_device_error_tip": "此功能與進階音訊功能(音訊可視化、音效設定、使用裝置能處理的最大聲道數輸出音訊功能)衝突。你本次啟動軟體時已啟用這些功能,此選項暫不可用。請關閉這些功能並重啟軟體後,再修改此選項!",
"setting__play_media_device_tip": "此功能與音訊可視化功能衝突,兩者無法同時啟用。是否關閉「音訊可視化」並應用所選音訊輸出設定?",
"setting__play_playQuality": "優先播放的音質(如果可用)",
"setting__play_power_save_blocker": "播放歌曲時阻止電腦休眠",
"setting__play_quality": "優先播放320K品質的歌曲(如果可用)",
"setting__play_save_play_time": "記住播放進度",
"setting__play_startup_auto_play": "啟動軟體後自動播放音樂",
"setting__play_task_bar": "在工作列上顯示目前歌曲播放進度",
"setting__play_statusbar_lyric": "在狀態欄顯示歌詞(需要啟用系統匣)",
"setting__play_task_bar": "在工具列上顯示目前歌曲播放進度",
"setting__play_timeout": "定時暫停",
"setting__player_audio_visualization_tip": "自訂音訊輸出設備與音訊視覺化功能會衝突,啟用了音訊視覺化後音訊輸出設備將會被重設為預設的輸出設備,目前此問題暫無法解決,是否仍要開啟?",
"setting__player_audio_visualization_tip": "自訂音訊輸出裝置與音訊可視化功能衝突,啟用「音訊可視化」後音訊輸出裝置將會被重設為預設,目前此問題暫無法解決。是否仍要開啟?",
"setting__search": "搜尋設定",
"setting__search_focus_search_box": "啟動時自動聚焦搜尋框",
"setting__search_history": "顯示歷史搜尋記錄",
"setting__search_hot": "顯示熱門搜尋",
"setting__setting__desktop_lyric_font_weight_extended": "翻譯、羅馬音歌詞",
"setting__setting__desktop_lyric_font_weight_font": "逐字歌詞",
"setting__setting__desktop_lyric_font_weight_line": "逐行歌詞",
"setting__desktop_lyric_font_weight_extended": "翻譯、羅馬音歌詞",
"setting__desktop_lyric_font_weight_font": "逐字歌詞",
"setting__desktop_lyric_font_weight_line": "逐行歌詞",
"setting__sync": "資料同步",
"setting__sync_client_address": "目前設備位址:{address}",
"setting__sync_client_host": "同步服務地址",
"setting__sync_client_host_tip": "http://IP位址:連接埠號",
"setting__sync_client_mode": "客戶端模式",
"setting__sync_client_address": "目前裝置位址:{address}",
"setting__sync_client_host": "同步服務位址",
"setting__sync_client_host_tip": "http://<IP 位址>:<埠號>",
"setting__sync_client_mode": "用戶端模式",
"setting__sync_client_status": "狀態:{status}",
"setting__sync_code_blocked_ip": "目前設備的IP已被服務端封鎖!",
"setting__sync_code_fail": "連接碼無效",
"setting__sync_code_blocked_ip": "目前裝置的 IP 已被服務端封禁!",
"setting__sync_code_fail": "連線碼無效",
"setting__sync_enable": "啟用同步功能",
"setting__sync_mode": "同步模式",
"setting__sync_mode_client": "客戶端模式",
"setting__sync_mode_client": "用戶端模式",
"setting__sync_mode_server": "服務端模式",
"setting__sync_server_address": "同步服務位址:{address}",
"setting__sync_server_auth_code": "連接碼:{code}",
"setting__sync_server_device": "已連接的裝置:{devices}",
"setting__sync_server_auth_code": "連線碼:{code}",
"setting__sync_server_device": "已連線的裝置:{devices}",
"setting__sync_server_device_list_btn_remove": "移除",
"setting__sync_server_device_list_noitem": "這裡啥也沒有 ┗( ▔, ▔ )┛",
"setting__sync_server_device_list_time": "最後連結時間:{time}",
"setting__sync_server_device_list_tips": "💡 裝置移除後,再連線時需重新輸入連接碼",
"setting__sync_server_device_list_title": "已認證設備",
"setting__sync_server_device_list_noitem": "這裡什麼也沒有 ┗( ▔, ▔ )┛",
"setting__sync_server_device_list_time": "最後連線時間:{time}",
"setting__sync_server_device_list_tips": "💡 裝置被移除後,再連線時需要重新輸入連線碼",
"setting__sync_server_device_list_title": "已認證裝置",
"setting__sync_server_mode": "服務端模式(由於資料是明文傳輸,請在受信任的網路下使用)",
"setting__sync_server_port": "同步連接埠設定",
"setting__sync_server_port_tip": "請輸入同步服務連接埠號",
"setting__sync_server_refresh_code": "刷新連接碼",
"setting__sync_server_show_device_list": "已認證設備列表",
"setting__sync_server_port": "同步埠設定",
"setting__sync_server_port_tip": "請輸入同步服務埠號",
"setting__sync_server_refresh_code": "重新獲取連線碼",
"setting__sync_server_show_device_list": "已認證裝置清單",
"setting__sync_tip": "使用方式請看常見問題「同步功能」部分",
"setting__update": "軟體更新",
"setting__update_checking": "檢查更新中...",
"setting__update_commit_date": "提交日期:",
"setting__update_commit_id": "程式碼版本:",
"setting__update_current_label": "目前版本:",
"setting__update_downloading": "發現新版本並在努力下載中,請稍後...⏳",
"setting__update_init": "處理更新中...",
"setting__update_latest": "軟體已是最新,盡情體驗吧~🥂",
"setting__update_latest": "軟體已是最新,盡情地體驗吧~🥂",
"setting__update_latest_label": "最新版本:",
"setting__update_new_version": "發現新版本,趕快去更新吧~🚀🚀",
"setting__update_open_version_modal_btn": "開啟更新視窗",
@@ -563,18 +592,19 @@
"setting__update_show_change_log": "更新版本後的首次啟動時顯示更新日誌",
"setting__update_try_auto_update": "發現新版本時嘗試自動下載更新",
"setting__update_unknown": "未知",
"setting__update_unknown_tip": "❓ 取得最新版本資訊失敗,建議去關於介面開啟專案發佈位址查看目前版本是否最新",
"setting_sync_status_enabled": "已連接",
"setting__update_unknown_tip": "❓ 獲取最新版本資訊失敗,建議在「關於」頁面開啟項目發布位址查看目前版本是否最新",
"setting_download_save_group_list_name": "將檔案儲存到以對應清單命名的子目錄中",
"setting_sync_status_enabled": "已連線",
"song_list": "歌單",
"songlist__import_input_btn_confirm": "打開",
"songlist__import_input_btn_confirm": "開啟",
"songlist__import_input_show_btn": "開啟歌單",
"songlist__import_input_tip": "輸入歌單連結或歌單 ID",
"songlist__import_input_tip_1": "不支援跨來源開啟歌單,請確認要開啟的歌單與目前歌單來源是否對應",
"songlist__import_input_tip_2": "若遇到無法打開的歌單鏈接,歡迎回饋",
"songlist__import_input_tip_3": "酷狗來源不支援用歌單ID打開,但支援酷狗碼打開",
"songlist__import_input_tip_4": "網易源的「我喜歡」歌單需要 Token 才能打開,詳情看",
"songlist__import_input_title": "打開分享的歌單",
"songlist__open_list": "開啟{name}歌單",
"songlist__import_input_tip_1": "不支援跨平台開啟歌單,請確認要開啟的歌單與目前歌單的來源平台是否對應",
"songlist__import_input_tip_2": "若遇到無法開啟的歌單連結,歡迎回報",
"songlist__import_input_tip_3": "不支援用歌單 ID 開啟酷狗音樂歌單,但支援用酷狗碼開啟",
"songlist__import_input_tip_4": "網易雲音樂的「我喜歡」歌單需要 Token 才能開啟,詳情看 ",
"songlist__import_input_title": "開啟分享的歌單",
"songlist__open_list": "開啟「{name}」歌單",
"songlist__tag_info_hot_tag": "熱門標籤",
"source_alias_all": "聚合大會",
"source_alias_bd": "小杜音樂",
@@ -592,50 +622,50 @@
"source_tx": "企鵝音樂",
"source_wy": "網易音樂",
"source_xm": "蝦米音樂",
"sync__auth_code_input_tip": "請輸入連接碼",
"sync__auth_code_title": "需要輸入連接碼",
"sync__dislike_merge_tip_desc": "合併兩邊列表內容並去重",
"sync__dislike_other_tip_desc": "「取消同步」將不使用不喜歡清單同步功能",
"sync__dislike_overwrite_tip_desc": "被覆蓋者的列表將被替換成覆蓋者的列表",
"sync__dislike_title": "選擇與 {name} 的不喜歡清單同步方式",
"sync__list_merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__auth_code_input_tip": "請輸入連線碼",
"sync__auth_code_title": "需要輸入連線碼",
"sync__dislike_merge_tip_desc": "合併兩邊清單內容並去重",
"sync__dislike_other_tip_desc": "「取消同步」將不使用「不喜歡的歌曲」清單同步功能",
"sync__dislike_overwrite_tip_desc": "被覆寫者的清單將被取代成覆寫者的清單",
"sync__dislike_title": "選取與「{name}」的「不喜歡的歌曲」清單同步方式",
"sync__list_merge_tip_desc": "將兩邊的清單合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__list_other_tip_desc": "「取消同步」將不使用清單同步功能。",
"sync__list_overwrite_tip_desc": "被覆蓋者與覆蓋者清單ID相同的清單將被刪除後替換成覆蓋者的清單(清單ID不同的清單將合併為一起),若勾選完全覆蓋,則被覆蓋者的所有清單將會被移除,然後替換成覆蓋者的列表。",
"sync__list_title": "選擇與 {name} 的列表同步方式",
"sync__merge_btn_local_remote": "本機列表 合併 遠端列表",
"sync__merge_btn_remote_local": "遠端列表 合併 本機列表",
"sync__list_overwrite_tip_desc": "被覆寫者與覆寫者清單 ID 相同的清單將被移除後取代成覆寫者的清單(清單 ID 不同的清單將被合併到一起)。若勾選「完全覆寫」,則被覆寫者的所有清單將被移除,然後取代成覆寫者的清單。",
"sync__list_title": "選取與「{name}」的清單同步方式",
"sync__merge_btn_local_remote": "「本機清單」合併「遠端清單」",
"sync__merge_btn_remote_local": "「遠端清單」合併「本機清單」",
"sync__merge_label": "合併",
"sync__merge_tip": "合併:",
"sync__merge_tip": "合併:",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__overwrite": "完全覆蓋",
"sync__other_tip": "其他:",
"sync__overwrite": "完全覆寫",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠端列表",
"sync__overwrite_btn_local_remote": "「本機清單」覆寫「遠端清單」",
"sync__overwrite_btn_none": "僅使用即時同步功能",
"sync__overwrite_btn_remote_local": "遠端列表 覆蓋 本機列表",
"sync__overwrite_label": "覆蓋",
"sync__overwrite_tip": "覆蓋:",
"sync_status_disabled": "未連接",
"sync__overwrite_btn_remote_local": "「遠端清單」覆寫「本機清單」",
"sync__overwrite_label": "覆寫",
"sync__overwrite_tip": "覆寫:",
"sync_status_disabled": "未連線",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
"tag__lossless_24bit": "24bit",
"theme_add": "新增主題",
"theme_add": "加入主題",
"theme_auto": "道法自然",
"theme_auto_tip": "滑鼠 右鍵 可開啟亮、暗主題設定窗口",
"theme_auto_tip": "滑鼠右擊可開啟亮、暗主題設定視窗",
"theme_black": "黑燈瞎火",
"theme_blue": "藍田生玉",
"theme_blue2": "清熱版藍",
"theme_blue_plus": "蛋雅深藍",
"theme_china_ink": "近墨者黑",
"theme_edit_modal__app_bg": "應用背景顏色",
"theme_edit_modal__aside_color": "側邊欄按鈕顏色",
"theme_edit_modal__aside_color": "側欄按鈕顏色",
"theme_edit_modal__badge": "標籤顏色",
"theme_edit_modal__badge_primary": "主顏色",
"theme_edit_modal__badge_secondary": "次要顏色",
"theme_edit_modal__badge_tertiary": "第三顏色",
"theme_edit_modal__bg_image": "背景圖片",
"theme_edit_modal__bg_image_add": "背景圖片:",
"theme_edit_modal__bg_image_change": "更改背景圖片",
"theme_edit_modal__bg_image_change": "變更背景圖片",
"theme_edit_modal__bg_image_remove": "移除背景圖片",
"theme_edit_modal__close_btn": "關閉",
"theme_edit_modal__control_btn": "左側控制按鈕顏色",
@@ -646,61 +676,62 @@
"theme_edit_modal__hide_btn": "隱藏播放詳情頁",
"theme_edit_modal__main_bg": "內容區域背景顏色",
"theme_edit_modal__min_btn": "最小化",
"theme_edit_modal__pick_cancel": "重置",
"theme_edit_modal__pick_color": "選擇顏色",
"theme_edit_modal__pick_cancel": "重設",
"theme_edit_modal__pick_color": "選取顏色",
"theme_edit_modal__pick_last_color": "使用之前的顏色",
"theme_edit_modal__pick_save": "確認",
"theme_edit_modal__preview": "預覽主題",
"theme_edit_modal__primary": "主題色",
"theme_edit_modal__remove": "刪除",
"theme_edit_modal__remove": "移除",
"theme_edit_modal__remove_tip": "是否真的要移除這個主題?",
"theme_edit_modal__save_new": "另存",
"theme_edit_modal__select_bg_file": "選擇背景圖片",
"theme_edit_modal__title": "編輯主題",
"theme_edit_modal__select_bg_file": "選取背景圖片",
"theme_edit_modal__title_add": "新增主題",
"theme_edit_modal__title_edit": "編輯主題",
"theme_green": "綠意盎然",
"theme_grey": "灰常美麗",
"theme_happy_new_year": "新年快樂",
"theme_max_tip": "最多只能加10個主題哦,刪除一些再加吧 😜",
"theme_max_tip": "最多只能加入 10 個主題哦,刪掉一些再加吧😜",
"theme_mid_autumn": "月裡嫦娥",
"theme_ming": "青出於黑",
"theme_more_btn_show": "更多主題",
"theme_naruto": "木葉之村",
"theme_orange": "橙黃橘綠",
"theme_pink": "粉紅裝玉琢",
"theme_pink": "粉裝玉琢",
"theme_purple": "重斤球紫",
"theme_red": "熱情如火",
"theme_red": "熱情似火",
"theme_selector_modal__dark_title": "暗色主題",
"theme_selector_modal__light_title": "亮色主題",
"theme_selector_modal__theme_name": "主題名稱",
"theme_selector_modal__title": "跟隨系統主題設置",
"theme_selector_modal__title": "跟隨系統主題設定",
"theme_selector_modal__title_tip": "註:你可以預先設定一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設定的相應主題。",
"toggle_source_failed": "換源失敗,請嘗試手動在其他來源搜尋該歌曲播放",
"toggle_source_try": "嘗試切換到其他來源...",
"update__downgrade_tip": "我們發現你降級了版本({ver}),若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以透過文件中提\n到的回饋管道給我們回饋😘!\n注意:從新版本降級舊版時建議先備份歌單,若出現異常則可透過清理資料解決,資料目錄路徑文件有記錄。",
"update__error_top": "自動下載新版本失敗,你可以嘗試重新下載更新或手動去下載更新,\n新版地址在更新彈窗下方有寫,下載新版直接覆蓋安裝即可,若安裝失敗則看常見問題解決,\n注意:目前只有Windows安裝版可以自動更新(Linux的AppImage、deb版似乎也可以,未經測試),其他版本請手動下載更新!",
"toggle_source_failed": "變更來源失敗,請嘗試手動在搜尋頁指定其他音樂串流平台搜尋該歌曲播放",
"toggle_source_try": "嘗試變更到其他來源...",
"update__downgrade_tip": "我們發現你降級了版本({ver}),若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以透過教學文件中提到的回報管道向我們回報😘!\n\n注意:從新版本降級舊版時建議先備份歌單,若出現異常則可透過清理資料解決,資料目錄路徑在教學文件有記錄。",
"update__error_top": "自動下載新版本失敗,你可以嘗試重新下載更新或者手動去下載更新。\n\n新版位址在更新彈出視窗下面有寫,下載新版直接覆寫安裝即可,若安裝失敗則看常見問題解決。\n\n注意:目前只有 Windows 安裝版可以自動更新(Linux 的 AppImage、deb 版似乎也可以,未測試),其他版本請手動下載更新!",
"update__ignore_cancel": "我就不想更新🤨",
"update__ignore_confirm": "好 去更新看看❤️",
"update__ignore_confirm_tip": "目前只有Windows安裝版可以自動更新(Linux的AppImage、deb版似乎也可以,未經測試),其他版本請手動下載更新,\n新版位址在更新彈窗下方有寫,下載新版直接覆蓋安裝即可,若安裝失敗看常見問題解決。",
"update__ignore_confirm_tip_confirm": "OK 已了解",
"update__ignore_tip": "你現在使用的版本距離最新版本已經落後了 {num} 個版本🤪,為了更好的使用體驗,建議更新到最新版本哦~!\n附註:若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以透過文件中提到的回饋管道給我們回饋😘!",
"update__timeout_top": "下載時間過長提示\n\n你目前所在網路訪問GitHub較慢,新版本已經下了一個鐘了還沒完成😳,你仍可選擇繼續等,但牆裂建議手動更新版本!",
"user_api__allow_show_update_alert": "允許顯示更新彈窗",
"update__ignore_confirm": "好,去更新看看❤️",
"update__ignore_confirm_tip": "目前只有 Windows 安裝版可以自動更新(Linux 的 AppImage、deb 版似乎也可以,未測試),其他版本請手動下載更新,\n新版位址在更新彈出視窗下面有寫,下載新版直接覆寫安裝即可,若安裝失敗看常見問題解決。",
"update__ignore_confirm_tip_confirm": "OK,已了解",
"update__ignore_tip": "你現在使用的版本距離最新版本已經落後了 {num} 個版本🤪,為了更好的使用體驗,建議更新到最新版本哦~!\n\n註:若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以透過教學文件中提到的回報管道向我們回報😘!",
"update__timeout_top": "下載時間過長提示\n\n你目前所在網路訪問 GitHub 較慢,新版本已經下了一個鐘了還沒完成😳,你仍可以繼續等,但牆裂建議手動更新版本!",
"user_api__allow_show_update_alert": "允許顯示更新彈出視窗",
"user_api__btn_export": "匯出",
"user_api__btn_import": "本地導入",
"user_api__btn_import_online": "線上匯入",
"user_api__btn_import": "從本機匯入",
"user_api__btn_import_online": "從線上匯入",
"user_api__btn_remove": "移除",
"user_api__import_file": "選擇音樂API腳本文件",
"user_api__init_failed_alert": "自訂來源 [{name}] 初始化失敗:",
"user_api__max_tip": "最多只能同時存在20個來源哦🤪\n想要繼續導入的話,請先移除一些舊的源騰出位置吧",
"user_api__import_file": "選取自訂來源 API 腳本檔案",
"user_api__init_failed_alert": "自訂來源 API「{name}」初始化失敗:",
"user_api__max_tip": "最多只能同時存在 20 個 API 哦🤪\n想要繼續匯入的話,請先移除一些舊的 API 騰出位置吧",
"user_api__noitem": "這裡竟然是空的😲",
"user_api__note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
"user_api__readme": "來源編寫說明:",
"user_api__title": "自訂來源管理",
"user_api__update_alert": "自訂來源 [{name}] 發現新版本:",
"user_api__update_alert_open_url": "開啟更新地址",
"user_api_import__failed": "自訂來源導入失敗:\n{message}",
"user_api_import_online__input_confirm": "導入",
"user_api_import_online__input_loading": "導入中...",
"user_api__note": "提示:雖然我們已經盡可能地隔離了 API 腳本的執行環境,但匯入包含惡意行為的 API 腳本仍可能會影響你的系統,請謹慎匯入。",
"user_api__readme": "API 編寫說明:",
"user_api__title": "自訂來源 API 管理",
"user_api__update_alert": "自訂來源 API「{name}」發現新版本:",
"user_api__update_alert_open_url": "開啟更新位址",
"user_api_import__failed": "自訂來源 API 匯入失敗:\n{message}",
"user_api_import_online__input_confirm": "匯入",
"user_api_import_online__input_loading": "匯入中...",
"user_api_import_online__input_tip": "請輸入 HTTP 連結",
"user_api_import_online__title": "線上導入自訂來源"
"user_api_import_online__title": "從線上匯入自訂來源 API"
}
+58 -32
View File
@@ -5,12 +5,12 @@ import { URL_SCHEME_RXP } from '@common/constants'
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
import { navigationUrlWhiteList } from '@common/config'
import defaultSetting from '@common/defaultSetting'
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
import { isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
import { createAppEvent, createDislikeEvent, createListEvent } from '@main/event'
import { isMac, log } from '@common/utils'
import createWorkers from './worker'
import { migrateDBData } from './utils/migrate'
import { encodePath, openDirInExplorer } from '@common/utils/electron'
import { openDirInExplorer } from '@common/utils/electron'
export const initGlobalData = () => {
const envParams = parseEnvParams()
@@ -18,11 +18,58 @@ export const initGlobalData = () => {
cmdParams: envParams.cmdParams,
deeplink: envParams.deeplink,
}
global.lx = {
inited: false,
isSkipTrayQuit: false,
// mainWindowClosed: true,
event_app: createAppEvent(),
event_list: createListEvent(),
event_dislike: createDislikeEvent(),
appSetting: defaultSetting,
worker: createWorkers(),
hotKey: {
enable: true,
config: {
local: {
enable: false,
keys: {},
},
global: {
enable: false,
keys: {},
},
},
state: new Map(),
},
theme: {
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
theme: {
id: '',
name: '',
isDark: false,
colors: {},
},
},
player_status: {
status: 'stoped',
name: '',
singer: '',
albumName: '',
picUrl: '',
progress: 0,
duration: 0,
playbackRate: 1,
lyricLineText: '',
lyricLineAllText: '',
lyric: '',
collect: false,
},
}
global.staticPath =
process.env.NODE_ENV !== 'production'
? webpackStaticPath
: path.join(encodePath(__dirname), 'static')
: path.join(__dirname, 'static')
}
export const initSingleInstanceHandle = () => {
@@ -158,6 +205,9 @@ export const listenerAppEvent = (startApp: () => void) => {
}
})
app.on('before-quit', () => {
global.lx.isSkipTrayQuit = true
})
app.on('window-all-closed', () => {
if (isMac) return
@@ -206,35 +256,11 @@ const initTheme = () => {
let isInitialized = false
export const initAppSetting = async() => {
if (!global.lx) {
if (!global.lx.inited) {
const config = await initHotKey()
global.lx = {
isTrafficLightClose: false,
isSkipTrayQuit: false,
// mainWindowClosed: true,
event_app: createAppEvent(),
event_list: createListEvent(),
event_dislike: createDislikeEvent(),
appSetting: defaultSetting,
worker: createWorkers(),
hotKey: {
enable: true,
config: {
local: config.local,
global: config.global,
},
state: new Map(),
},
theme: {
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
theme: {
id: '',
name: '',
isDark: false,
colors: {},
},
},
}
global.lx.hotKey.config.local = config.local
global.lx.hotKey.config.global = config.global
global.lx.inited = true
}
if (!isInitialized) {
@@ -261,5 +287,5 @@ export const initAppSetting = async() => {
export const quitApp = () => {
global.lx.isSkipTrayQuit = true
closeWindow()
app.quit()
}
+17
View File
@@ -1,6 +1,7 @@
import { EventEmitter } from 'events'
import { saveAppHotKeyConfig, updateSetting } from '@main/utils'
import type { BrowserWindow } from 'electron'
export class Event extends EventEmitter {
// closeAll() {
@@ -52,6 +53,14 @@ export class Event extends EventEmitter {
this.emit('deeplink', link)
}
player_status(status: Partial<LX.Player.Status>) {
for (const [key, value] of Object.entries(status)) {
// @ts-expect-error
global.lx.player_status[key] = value
}
this.emit('player_status', status)
}
hot_key_down(keyInfo: LX.HotKeyDownInfo) {
this.emit('hot_key_down', keyInfo)
}
@@ -61,6 +70,10 @@ export class Event extends EventEmitter {
this.emit('hot_key_config_update', config)
}
main_window_created(win: BrowserWindow) {
this.emit('main_window_created', win)
}
main_window_ready_to_show() {
this.emit('main_window_ready_to_show')
}
@@ -92,6 +105,10 @@ export class Event extends EventEmitter {
main_window_fullscreen(isFullscreen: boolean) {
this.emit('main_window_fullscreen', isFullscreen)
}
desktop_lyric_window_created(win: BrowserWindow) {
this.emit('desktop_lyric_window_created', win)
}
}
+18 -4
View File
@@ -8,20 +8,34 @@
import { app } from 'electron'
import electronDebug from 'electron-debug'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import { openDevTools } from './utils'
// Install `electron-debug` with `devtron`
electronDebug({
showDevTools: true,
showDevTools: false,
devToolsMode: 'undocked',
})
// Install `vue-devtools`
app.on('ready', () => {
installExtension(VUEJS_DEVTOOLS)
global.lx.event_app.on('main_window_created', (win) => {
openDevTools(win.webContents)
installExtension(VUEJS_DEVTOOLS, { session: win.webContents.session })
.then((name: string) => {
console.log(`Added Extension: ${name}`)
console.log(`[main window] Added Extension: ${name}`)
})
.catch((err: Error) => {
console.log('An error occurred: ', err)
console.log('[main window] An error occurred: ', err)
})
})
global.lx.event_app.on('desktop_lyric_window_created', (win) => {
openDevTools(win.webContents)
installExtension(VUEJS_DEVTOOLS, { session: win.webContents.session })
.then((name: string) => {
console.log(`[lyric window] Added Extension: ${name}`)
})
.catch((err: Error) => {
console.log('[lyric window] An error occurred: ', err)
})
})
})
+253
View File
@@ -0,0 +1,253 @@
import http from 'node:http'
import querystring from 'node:querystring'
import type { Socket } from 'node:net'
import { getAddress } from '@common/utils/nodejs'
import { sendTaskbarButtonClick } from '@main/modules/winMain'
const sendResponse = (res: http.ServerResponse, code = 200, msg: string | Record<any, unknown> = 'OK', contentType = 'text/plain; charset=utf-8') => {
res.writeHead(code, {
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
})
if (typeof msg === 'object') {
res.end(JSON.stringify(msg))
} else {
res.end(msg)
}
}
let status: LX.OpenAPI.Status = {
status: false,
message: '',
address: '',
}
type SubscribeKeys = keyof LX.Player.Status
let httpServer: http.Server
let sockets = new Set<Socket>()
let responses = new Map<http.ServerResponse<http.IncomingMessage>, SubscribeKeys[]>()
let playerStatusKeys: SubscribeKeys[]
const defaultFilter = [
'status',
'name',
'singer',
'albumName',
'lyricLineText',
'duration',
'progress',
'playbackRate',
] satisfies SubscribeKeys[]
const parseFilter = (filter: any) => {
if (typeof filter != 'string') return defaultFilter
filter = filter.split(',')
const subKeys = playerStatusKeys.filter(k => filter.includes(k))
return subKeys.length ? subKeys : defaultFilter
}
const handleSendStatus = (res: http.ServerResponse<http.IncomingMessage>, query?: string) => {
const keys = parseFilter(querystring.parse(query ?? '').filter)
const resp: Partial<Record<SubscribeKeys, any>> = {}
for (const k of keys) resp[k] = global.lx.player_status[k]
sendResponse(res, 200, resp, 'application/json; charset=utf-8')
}
const handleSubscribePlayerStatus = (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>, query?: string) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
'Access-Control-Allow-Origin': '*',
})
req.socket.setTimeout(0)
req.on('close', () => {
res.end('OK')
responses.delete(res)
})
const keys = parseFilter(querystring.parse(query ?? '').filter)
responses.set(res, keys)
for (const [k, v] of Object.entries(global.lx.player_status)) {
if (!keys.includes(k as SubscribeKeys)) continue
res.write(`event: ${k}\n`)
res.write(`data: ${JSON.stringify(v)}\n\n`)
}
}
const handleStartServer = async(port: number, ip: string) => new Promise<void>((resolve, reject) => {
playerStatusKeys = Object.keys(global.lx.player_status) as SubscribeKeys[]
httpServer = http.createServer((req, res): void => {
const [endUrl, query] = `/${req.url?.split('/').at(-1) ?? ''}`.split('?')
let code = 200
let msg = 'OK'
switch (endUrl) {
case '/status':
handleSendStatus(res, query)
return
// case '/test':
// code = 200
// res.setHeader('Content-Type', 'text/html; charset=utf-8')
// msg = `<!DOCTYPE html>
// <html lang="en">
// <head>
// <meta charset="UTF-8" />
// <meta http-equiv="X-UA-Compatible" content="IE=edge" />
// <meta name="viewport" content="width=device-width, initial-scale=1.0" />
// <title>Nodejs Server-Sent Events</title>
// </head>
// <body>
// <h1>Hello SSE!</h1>
// <h2>List of Server-sent events</h2>
// <ul id="sse-list"></ul>
// <script>
// const subscription = new EventSource('/subscribe-player-status');
// // Default events
// subscription.addEventListener('open', () => {
// console.log('Connection opened')
// });
// subscription.addEventListener('error', (err) => {
// console.error(err)
// });
// subscription.addEventListener('lyricLineText', (event) => {
// console.log(event.data)
// });
// subscription.addEventListener('progress', (event) => {
// console.log(event.data)
// });
// subscription.addEventListener('name', (event) => {
// console.log(event.data)
// });
// subscription.addEventListener('singer', (event) => {
// console.log(event.data)
// });
// </script>
// </body>
// </html>`
// break
case '/lyric':
msg = global.lx.player_status.lyric
break
case '/play':
sendTaskbarButtonClick('play')
break
case '/pause':
sendTaskbarButtonClick('pause')
break
case '/skip-next':
sendTaskbarButtonClick('next')
break
case '/skip-prev':
sendTaskbarButtonClick('prev')
break
case '/collect':
sendTaskbarButtonClick('collect')
break
case '/uncollect':
sendTaskbarButtonClick('unCollect')
break
case '/subscribe-player-status':
try {
handleSubscribePlayerStatus(req, res, query)
return
} catch (err) {
console.log(err)
code = 500
msg = 'Error'
}
break
default:
code = 401
msg = 'Forbidden'
break
}
sendResponse(res, code, msg)
})
httpServer.on('error', error => {
console.log(error)
reject(error)
})
httpServer.on('connection', (socket) => {
sockets.add(socket)
socket.once('close', () => {
sockets.delete(socket)
})
socket.setTimeout(4000)
})
httpServer.on('listening', () => {
const addr = httpServer.address()
// console.log(addr)
if (!addr) {
reject(new Error('address is null'))
return
}
resolve()
})
httpServer.listen(port, ip)
})
const handleStopServer = async() => new Promise<void>((resolve, reject) => {
if (!httpServer) return
httpServer.close((err) => {
if (err) {
reject(err)
return
}
resolve()
})
for (const socket of sockets) socket.destroy()
sockets.clear()
responses.clear()
})
const sendStatus = (status: Partial<LX.Player.Status>) => {
if (!responses.size) return
for (const [resp, keys] of responses) {
for (const [k, v] of Object.entries(status)) {
if (!keys.includes(k as SubscribeKeys)) continue
resp.write(`event: ${k}\n`)
resp.write(`data: ${JSON.stringify(v)}\n\n`)
}
}
}
export const stopServer = async() => {
global.lx.event_app.off('player_status', sendStatus)
if (!status.status) {
status.status = false
status.message = ''
status.address = ''
return status
}
await handleStopServer().then(() => {
status.status = false
status.message = ''
status.address = ''
}).catch(err => {
console.log(err)
status.message = err.message
})
return status
}
export const startServer = async(port: number, bindLan: boolean) => {
if (status.status) await stopServer()
await handleStartServer(port, bindLan ? '0.0.0.0' : '127.0.0.1').then(() => {
status.status = true
status.message = ''
let address = ['127.0.0.1']
if (bindLan) address = [...address, ...getAddress()]
status.address = address.join(', ')
}).catch(err => {
console.log(err)
status.status = false
status.message = err.message
status.address = ''
})
global.lx.event_app.on('player_status', sendStatus)
return status
}
export const getStatus = (): LX.OpenAPI.Status => status
+2 -1
View File
@@ -6,10 +6,11 @@ import { callObj } from './sync'
// import registerSyncListHandler from './syncList'
import log from '../log'
import { dateFormat } from '@common/utils/common'
import { aesEncrypt, getAddress } from '../utils'
import { aesEncrypt } from '../utils'
import { sendClientStatus } from '@main/modules/winMain'
import { createMsg2call } from 'message2call'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants_sync'
import { getAddress } from '@common/utils/nodejs'
let status: LX.Sync.ClientStatus = {
status: false,
+1
View File
@@ -6,6 +6,7 @@ export const request = async(url: string, options: RequestOptions = { }) => {
return httpFetch(url, {
...options,
timeout: options.timeout ?? 10000,
follow_max: 5,
}).then(response => {
return {
text: response.body,
+12 -2
View File
@@ -2,7 +2,6 @@ import http, { type IncomingMessage } from 'node:http'
import { WebSocketServer } from 'ws'
import { registerLocalSyncEvent, callObj, sync, unregisterLocalSyncEvent } from './sync'
import { authCode, authConnect } from './auth'
import { getAddress } from '../../utils'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants_sync'
import { getUserSpace, releaseUserSpace, getServerId, initServerInfo } from '../user'
import { createMsg2call } from 'message2call'
@@ -10,6 +9,8 @@ import log from '../../log'
import { sendServerStatus } from '@main/modules/winMain'
import { decryptMsg, encryptMsg, generateCode as handleGenerateCode } from '../utils/tools'
import migrateData from '../../migrate'
import type { Socket } from 'node:net'
import { getAddress } from '@common/utils/nodejs'
let status: LX.Sync.ServerStatus = {
@@ -25,7 +26,7 @@ let stopingServer = false
let host = 'http://localhost'
const codeTools: {
timeout: NodeJS.Timer | null
timeout: NodeJS.Timeout | null
start: () => void
stop: () => void
} = {
@@ -114,6 +115,7 @@ const authConnection = (req: http.IncomingMessage, callback: (err: string | null
let wss: LX.Sync.Server.SocketServer | null
let httpServer: http.Server
let sockets = new Set<Socket>()
function noop() {}
function onSocketError(err: Error) {
@@ -290,6 +292,12 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
console.log(error)
reject(error)
})
httpServer.on('connection', (socket) => {
sockets.add(socket)
socket.once('close', () => {
sockets.delete(socket)
})
})
httpServer.on('listening', () => {
const addr = httpServer.address()
@@ -321,6 +329,8 @@ const handleStopServer = async() => new Promise<void>((resolve, reject) => {
}
resolve()
})
for (const socket of sockets) socket.destroy()
sockets.clear()
})
export const stopServer = async() => {
@@ -1,4 +1,3 @@
import { networkInterfaces } from 'node:os'
import { createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from 'node:crypto'
// import { join } from 'node:path'
import zlib from 'node:zlib'
@@ -8,23 +7,6 @@ import type http from 'node:http'
// import { getUserName } from '../user/data'
// import { saveClientKeyInfo } from './data'
export const getAddress = (): string[] => {
const nets = networkInterfaces()
const results: string[] = []
// console.log(nets)
for (const interfaceInfos of Object.values(nets)) {
if (!interfaceInfos) continue
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
for (const interfaceInfo of interfaceInfos) {
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
results.push(interfaceInfo.address)
}
}
}
return results
}
export const generateCode = (): string => {
return Math.random().toString().substring(2, 8)
}
+2 -19
View File
@@ -1,28 +1,10 @@
import { createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from 'node:crypto'
import os, { networkInterfaces } from 'node:os'
import os from 'node:os'
import fs from 'node:fs'
import zlib from 'node:zlib'
import cp from 'node:child_process'
export const getAddress = (): string[] => {
const nets = networkInterfaces()
const results: string[] = []
// console.log(nets)
for (const interfaceInfos of Object.values(nets)) {
if (!interfaceInfos) continue
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
for (const interfaceInfo of interfaceInfos) {
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
results.push(interfaceInfo.address)
}
}
}
return results
}
// https://stackoverflow.com/a/75309339
export const getComputerName = () => {
let name: string | undefined
@@ -40,6 +22,7 @@ export const getComputerName = () => {
try {
name = cp.execSync('hostnamectl --pretty').toString().trim()
} catch {}
break
}
if (!name) name = os.hostname()
return name
+221 -44
View File
@@ -1,17 +1,28 @@
import { Tray, Menu, nativeImage } from 'electron'
import { isWin } from '@common/utils'
import { isMac, isWin } from '@common/utils'
import path from 'node:path'
import {
hideWindow as hideMainWindow,
isExistWindow as isExistMainWindow,
isShowWindow as isShowMainWindow,
sendTaskbarButtonClick,
showWindow as showMainWindow,
} from './winMain'
import { quitApp } from '@main/app'
import { TRAY_AUTO_ID } from '@common/constants'
let tray: Electron.Tray | null
let isEnableTray: boolean = false
let themeId: number
let isShowStatusBarLyric: boolean = false
const playerState = {
empty: false,
collect: false,
play: false,
next: true,
prev: true,
}
const watchConfigKeys = [
'desktopLyric.enable',
@@ -19,6 +30,7 @@ const watchConfigKeys = [
'desktopLyric.isAlwaysOnTop',
'tray.themeId',
'tray.enable',
'player.isShowStatusBarLyric',
'common.langId',
] satisfies Array<keyof LX.AppSetting>
@@ -42,17 +54,33 @@ const themeList = [
const messages = {
'en-us': {
collect: 'Love',
uncollect: 'Unlove',
play: 'Play',
pause: 'Pause',
next: 'Next Song',
prev: 'Prev Song',
hide_win_main: 'Hide Main Window',
show_win_main: 'Show Main Window',
hide_win_lyric: 'Close desktop lyrics',
show_win_lyric: 'Open desktop lyrics',
lock_win_lyric: 'Lock desktop lyrics',
unlock_win_lyric: 'Unlock desktop lyrics',
top_win_lyric: 'Set top lyrics',
untop_win_lyric: 'Cancel top lyrics',
hide_win_lyric: 'Hide Lyric Window',
show_win_lyric: 'Show Lyric Window',
lock_win_lyric: 'Lock Lyric Window',
unlock_win_lyric: 'Unlock Lyric Window',
top_win_lyric: 'On-top Lyric Window',
untop_win_lyric: 'Un-top Lyric Window',
show_statusbar_lyric: 'Show Lyrics on Statusbar',
hide_statusbar_lyric: 'Hide Lyrics on Statusbar',
exit: 'Exit',
music_name: 'Title: ',
music_singer: 'Artist: ',
},
'zh-cn': {
collect: '收藏',
uncollect: '取消收藏',
play: '播放',
pause: '暂停',
next: '下一曲',
prev: '上一曲',
hide_win_main: '隐藏主界面',
show_win_main: '显示主界面',
hide_win_lyric: '关闭桌面歌词',
@@ -61,18 +89,32 @@ const messages = {
unlock_win_lyric: '解锁桌面歌词',
top_win_lyric: '置顶歌词',
untop_win_lyric: '取消置顶',
show_statusbar_lyric: '显示状态栏歌词',
hide_statusbar_lyric: '隐藏状态栏歌词',
exit: '退出',
music_name: '歌曲名: ',
music_singer: '艺术家: ',
},
'zh-tw': {
hide_win_main: '隱藏主界面',
show_win_main: '顯示主界面',
hide_win_lyric: '關閉桌面歌詞',
show_win_lyric: '開啟桌面歌詞',
lock_win_lyric: '鎖定桌面歌詞',
unlock_win_lyric: '解鎖桌面歌詞',
top_win_lyric: '置頂歌詞',
untop_win_lyric: '取消置頂',
collect: '收藏',
uncollect: '取消收藏',
play: '播放',
pause: '暫停',
next: '下一曲',
prev: '上一曲',
hide_win_main: '隱藏軟體視窗',
show_win_main: '顯示軟體視窗',
hide_win_lyric: '關閉歌詞視窗',
show_win_lyric: '開啟歌詞視窗',
lock_win_lyric: '鎖定歌詞視窗',
unlock_win_lyric: '解鎖歌詞視窗',
top_win_lyric: '置頂歌詞視窗',
untop_win_lyric: '取消置頂歌詞視窗',
show_statusbar_lyric: '顯示狀態列歌詞',
hide_statusbar_lyric: '隱藏狀態列歌詞',
exit: '退出',
music_name: '標題: ',
music_singer: '演出者: ',
},
} as const
type Messages = typeof messages
@@ -90,19 +132,23 @@ const i18n = {
},
}
const getIconPath = (id: number) => {
let theme = id == TRAY_AUTO_ID
? global.lx.theme.shouldUseDarkColors
? themeList[0] : themeList[2]
: themeList.find(item => item.id === id) ?? themeList[0]
return path.join(global.staticPath, 'images/tray', theme.fileName + (isWin ? '.ico' : '.png'))
}
export const createTray = () => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if ((tray && !tray.isDestroyed()) || !global.lx.appSetting['tray.enable']) return
themeId = global.lx.appSetting['tray.themeId']
let theme = themeList.find(item => item.id === themeId) ?? themeList[0]
const iconPath = path.join(global.staticPath, 'images/tray', theme.fileName + '.png')
// 托盘
tray = new Tray(nativeImage.createFromPath(iconPath))
tray = new Tray(nativeImage.createFromPath(getIconPath(global.lx.appSetting['tray.themeId'])))
tray.setToolTip('LX Music')
createMenu()
// tray.setToolTip('LX Music')
// createMenu()
tray.setIgnoreDoubleClickEvents(true)
tray.on('click', () => {
showMainWindow()
@@ -113,32 +159,58 @@ export const destroyTray = () => {
if (!tray) return
tray.destroy()
isEnableTray = false
isShowStatusBarLyric = false
tray = null
}
const handleUpdateConfig = (config: any) => {
global.lx.event_app.update_config(config)
const handleUpdateConfig = (setting: Partial<LX.AppSetting>) => {
global.lx.event_app.update_config(setting)
}
const createPlayerMenu = () => {
let menu: Electron.MenuItemConstructorOptions[] = []
menu.push(playerState.play ? {
label: i18n.getMessage('pause'),
click() {
sendTaskbarButtonClick('pause')
},
} : {
label: i18n.getMessage('play'),
click() {
sendTaskbarButtonClick('play')
},
})
menu.push({
label: i18n.getMessage('prev'),
click() {
sendTaskbarButtonClick('prev')
},
})
menu.push({
label: i18n.getMessage('next'),
click() {
sendTaskbarButtonClick('next')
},
})
menu.push(playerState.collect ? {
label: i18n.getMessage('uncollect'),
click() {
sendTaskbarButtonClick('unCollect')
},
} : {
label: i18n.getMessage('collect'),
click() {
sendTaskbarButtonClick('collect')
},
})
return menu
}
export const createMenu = () => {
if (!tray) return
let menu = []
if (isExistMainWindow()) {
const isShow = isShowMainWindow()
menu.push(isShow
? {
label: i18n.getMessage('hide_win_main'),
click() {
hideMainWindow()
},
}
: {
label: i18n.getMessage('show_win_main'),
click() {
showMainWindow()
},
})
}
let menu: Electron.MenuItemConstructorOptions[] = createPlayerMenu()
if (playerState.empty) for (const m of menu) m.enabled = false
menu.push({ type: 'separator' })
menu.push(global.lx.appSetting['desktopLyric.enable']
? {
label: i18n.getMessage('hide_win_lyric'),
@@ -178,6 +250,39 @@ export const createMenu = () => {
handleUpdateConfig({ 'desktopLyric.isAlwaysOnTop': true })
},
})
if (isMac) {
menu.push({ type: 'separator' })
menu.push(isShowStatusBarLyric
? {
label: i18n.getMessage('hide_statusbar_lyric'),
click() {
handleUpdateConfig({ 'player.isShowStatusBarLyric': false })
},
}
: {
label: i18n.getMessage('show_statusbar_lyric'),
click() {
handleUpdateConfig({ 'player.isShowStatusBarLyric': true })
},
})
}
menu.push({ type: 'separator' })
if (isExistMainWindow()) {
const isShow = isShowMainWindow()
menu.push(isShow
? {
label: i18n.getMessage('hide_win_main'),
click() {
hideMainWindow()
},
}
: {
label: i18n.getMessage('show_win_main'),
click() {
showMainWindow()
},
})
}
menu.push({
label: i18n.getMessage('exit'),
click() {
@@ -190,9 +295,29 @@ export const createMenu = () => {
export const setTrayImage = (themeId: number) => {
if (!tray) return
let theme = themeList.find(item => item.id === themeId) ?? themeList[0]
const iconPath = path.join(global.staticPath, 'images/tray', theme.fileName + '.png')
tray.setImage(nativeImage.createFromPath(iconPath))
tray.setImage(nativeImage.createFromPath(getIconPath(themeId)))
}
const setLyric = (lyricLineText?: string) => {
if (isShowStatusBarLyric && tray && lyricLineText != null) {
tray.setTitle(lyricLineText)
}
}
const defaultTip = 'LX Music'
const setTip = () => {
if (!tray) return
let name = global.lx.player_status.name
let tip: string
if (name) {
if (name.length > 20) name = name.substring(0, 20) + '...'
let singer = global.lx.player_status.singer
if (singer?.length > 20) singer = singer.substring(0, 20) + '...'
tip = `${defaultTip}\n${i18n.getMessage('music_name')}${name}${singer ? `\n${i18n.getMessage('music_singer')}${singer}` : ''}`
} else tip = defaultTip
tray.setToolTip(tip)
}
const init = () => {
@@ -204,6 +329,15 @@ const init = () => {
isEnableTray = global.lx.appSetting['tray.enable']
global.lx.appSetting['tray.enable'] ? createTray() : destroyTray()
}
if (isShowStatusBarLyric !== global.lx.appSetting['player.isShowStatusBarLyric']) {
isShowStatusBarLyric = global.lx.appSetting['player.isShowStatusBarLyric']
if (isShowStatusBarLyric) {
setLyric(global.lx.player_status.lyricLineText)
} else {
tray?.setTitle('')
}
}
setTip()
createMenu()
}
@@ -241,4 +375,47 @@ export default () => {
i18n.setLang(global.lx.appSetting['common.langId'])
init()
})
global.lx.event_app.on('system_theme_change', () => {
if (global.lx.appSetting['tray.themeId'] != TRAY_AUTO_ID) return
setTrayImage(global.lx.appSetting['tray.themeId'])
})
global.lx.event_app.on('player_status', (status) => {
let updated = false
if (status.status) {
switch (status.status) {
case 'paused':
playerState.play = false
playerState.empty &&= false
setLyric('')
break
case 'error':
playerState.play = false
playerState.empty &&= false
setLyric('')
break
case 'playing':
playerState.play = true
playerState.empty &&= false
setLyric(global.lx.player_status.lyricLineText)
break
case 'stoped':
playerState.play &&= false
playerState.empty = true
setLyric('')
break
}
updated = true
} else {
setLyric(status.lyricLineText)
}
if (status.name != null) setTip()
if (status.singer != null) setTip()
if (status.collect != null) {
playerState.collect = status.collect
updated = true
}
if (updated) init()
})
}
+4 -4
View File
@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron'
import fs from 'fs'
import path from 'node:path'
import { openDevTools as handleOpenDevTools } from '@main/utils'
import { encodePath } from '@common/utils/electron'
import USER_API_RENDERER_EVENT_NAME from './rendererEvent/name'
import { getScript } from './utils'
@@ -58,15 +57,15 @@ const winEvent = () => {
export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => {
await closeWindow()
dir ??= process.env.NODE_ENV !== 'production' ? webpackUserApiPath : path.join(encodePath(__dirname), 'userApi')
dir ??= process.env.NODE_ENV !== 'production' ? webpackUserApiPath : path.join(__dirname, 'userApi')
if (!html) {
// eslint-disable-next-line require-atomic-updates
html = await fs.promises.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8')
}
const preloadUrl = process.env.NODE_ENV !== 'production'
? `${path.join(encodePath(__dirname), '../dist/user-api-preload.js')}`
: `${path.join(encodePath(__dirname), 'user-api-preload.js')}`
? `${path.join(__dirname, '../dist/user-api-preload.js')}`
: `${path.join(__dirname, 'user-api-preload.js')}`
// console.log(preloadUrl)
/**
@@ -78,6 +77,7 @@ export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => {
minimizable: false,
maximizable: false,
fullscreenable: false,
hasShadow: false,
show: false,
webPreferences: {
contextIsolation: true,
+1 -1
View File
@@ -73,7 +73,7 @@ const matchInfo = (scriptInfo: string) => {
for (const [key, len] of Object.entries(INFO_NAMES) as Array<{ [K in keyof INFO_NAMES_Type]: [K, INFO_NAMES_Type[K]] }[keyof INFO_NAMES_Type]>) {
infos[key] ||= ''
if (infos[key] == null) infos[key] = ''
else if (infos[key]!.length > len) infos[key] = infos[key]!.substring(0, len) + '...'
else if (infos[key].length > len) infos[key] = infos[key].substring(0, len) + '...'
}
return infos as Record<keyof typeof INFO_NAMES, string>
+6 -3
View File
@@ -1,6 +1,6 @@
import path from 'node:path'
import { BrowserWindow } from 'electron'
import { debounce, isLinux, isWin } from '@common/utils'
import { debounce, getPlatform, isLinux, isWin } from '@common/utils'
import { initWindowSize } from './utils'
import { mainSend } from '@common/mainIpc'
import { encodePath } from '@common/utils/electron'
@@ -55,6 +55,7 @@ const winEvent = () => {
browserWindow.on('resize', () => {
// bounds = browserWindow.getBounds()
// console.log(bounds)
isWinBoundsUpdateing = true
const bounds = browserWindow!.getBounds()
saveBoundsConfig({
'desktopLyric.x': bounds.x,
@@ -119,9 +120,10 @@ export const createWindow = () => {
useContentSize: true,
frame: false,
transparent: true,
hasShadow: false,
// enableRemoteModule: false,
// icon: join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
resizable: isWin,
minimizable: false,
maximizable: false,
fullscreenable: false,
@@ -140,10 +142,11 @@ export const createWindow = () => {
})
const winURL = process.env.NODE_ENV !== 'production' ? 'http://localhost:9081/lyric.html' : `file://${path.join(encodePath(__dirname), 'lyric.html')}`
void browserWindow.loadURL(winURL + `?dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`)
void browserWindow.loadURL(winURL + `?os=${getPlatform()}&dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`)
winEvent()
// browserWindow.webContents.openDevTools()
global.lx.event_app.desktop_lyric_window_created(browserWindow)
}
export const isExistWindow = (): boolean => !!browserWindow
+75 -1
View File
@@ -1,7 +1,7 @@
import initRendererEvent, { handleKeyDown, hotKeyConfigUpdate } from './rendererEvent'
import { APP_EVENT_NAMES } from '@common/constants'
import { createWindow, minimize, toggleHide, toggleMinimize } from './main'
import { createWindow, minimize, setProgressBar, setProxy, setThumbarButtons, toggleHide, toggleMinimize } from './main'
import initUpdate from './autoUpdate'
import { HOTKEY_COMMON } from '@common/hotKey'
import { quitApp } from '@main/app'
@@ -38,6 +38,80 @@ export default () => {
global.lx.event_app.on('app_inited', () => {
createWindow()
})
const keys = (['status', 'collect'] as const) satisfies Array<keyof LX.Player.Status>
const taskBarButtonFlags: LX.TaskBarButtonFlags = {
empty: true,
collect: false,
play: false,
next: true,
prev: true,
}
const progressStatus = {
progress: -1,
status: 'none' as Electron.ProgressBarOptions['mode'],
}
let showProgress = global.lx.appSetting['player.isShowTaskProgess']
global.lx.event_app.on('player_status', (status) => {
if (status.status) {
switch (status.status) {
case 'paused':
taskBarButtonFlags.play = false
taskBarButtonFlags.empty &&= false
progressStatus.status = 'paused'
break
case 'error':
taskBarButtonFlags.play = false
taskBarButtonFlags.empty &&= false
progressStatus.status = 'error'
break
case 'playing':
taskBarButtonFlags.play = true
taskBarButtonFlags.empty &&= false
progressStatus.status = 'normal'
break
case 'stoped':
taskBarButtonFlags.play &&= false
taskBarButtonFlags.empty = true
progressStatus.status = 'none'
progressStatus.progress = 0
break
}
if (showProgress) {
setProgressBar(progressStatus.progress, {
mode: progressStatus.status,
})
}
}
if (keys.some(k => status[k] != null)) {
if (status.collect != null) taskBarButtonFlags.collect = status.collect
setThumbarButtons(taskBarButtonFlags)
}
if (showProgress && status.progress != null) {
const progress = global.lx.player_status.duration ? status.progress / global.lx.player_status.duration : 0
if (progress.toFixed(2) != progressStatus.progress.toFixed(2)) {
progressStatus.progress = progress < 0.01 ? 0.01 : progress
setProgressBar(progressStatus.progress, {
mode: progressStatus.status,
})
}
}
})
global.lx.event_app.on('updated_config', (keys, setting) => {
if (keys.includes('player.isShowTaskProgess')) {
showProgress = setting['player.isShowTaskProgess']!
if (showProgress) {
setProgressBar(progressStatus.progress, {
mode: progressStatus.status,
})
} else {
setProgressBar(-1, { mode: 'none' })
}
}
if (keys.includes('network.proxy.enable') || (global.lx.appSetting['network.proxy.enable'] && keys.some(k => k.includes('network.proxy.')))) {
setProxy()
}
})
}
export * from './main'
+27 -5
View File
@@ -1,8 +1,8 @@
import { BrowserWindow, dialog, session } from 'electron'
import path from 'node:path'
import { createTaskBarButtons, getWindowSizeInfo } from './utils'
import { isLinux, isWin } from '@common/utils'
import { openDevTools as handleOpenDevTools } from '@main/utils'
import { getPlatform, isLinux, isWin } from '@common/utils'
import { getProxy, openDevTools as handleOpenDevTools } from '@main/utils'
import { mainSend } from '@common/mainIpc'
import { sendFocus, sendTaskbarButtonClick } from './rendererEvent'
import { encodePath } from '@common/utils/electron'
@@ -13,14 +13,13 @@ const winEvent = () => {
if (!browserWindow) return
browserWindow.on('close', event => {
if (global.lx.isSkipTrayQuit || !global.lx.appSetting['tray.enable'] || (!isWin && !global.lx.isTrafficLightClose)) {
if (global.lx.isSkipTrayQuit || !global.lx.appSetting['tray.enable']) {
browserWindow!.setProgressBar(-1)
// global.lx.mainWindowClosed = true
global.lx.event_app.main_window_close()
return
}
global.lx.isTrafficLightClose &&= false
event.preventDefault()
browserWindow!.hide()
})
@@ -66,6 +65,12 @@ export const createWindow = () => {
const { shouldUseDarkColors, theme } = global.lx.theme
const ses = session.fromPartition('persist:win-main')
const proxy = getProxy()
if (proxy) {
void ses.setProxy({
proxyRules: `http://${proxy.host}:${proxy.port}`,
})
}
/**
* Initial window options
@@ -76,6 +81,7 @@ export const createWindow = () => {
width: windowSizeInfo.width,
frame: false,
transparent: !global.envParams.cmdParams.dt,
hasShadow: global.envParams.cmdParams.dt,
// enableRemoteModule: false,
// icon: join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
@@ -103,7 +109,7 @@ export const createWindow = () => {
browserWindow = new BrowserWindow(options)
const winURL = process.env.NODE_ENV !== 'production' ? 'http://localhost:9080' : `file://${path.join(encodePath(__dirname), 'index.html')}`
void browserWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`)
void browserWindow.loadURL(winURL + `?os=${getPlatform()}&dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`)
winEvent()
@@ -111,6 +117,7 @@ export const createWindow = () => {
// global.lx.mainWindowClosed = false
// browserWindow.webContents.openDevTools()
global.lx.event_app.main_window_created(browserWindow)
}
export const isExistWindow = (): boolean => !!browserWindow
@@ -124,6 +131,21 @@ export const closeWindow = () => {
browserWindow.close()
}
export const setProxy = () => {
if (!browserWindow) return
const proxy = getProxy()
if (proxy) {
void browserWindow.webContents.session.setProxy({
proxyRules: `http://${proxy.host}:${proxy.port}`,
})
} else {
void browserWindow.webContents.session.setProxy({
proxyRules: '',
})
}
}
export const sendEvent = <T = any>(name: string, params?: T) => {
if (!browserWindow) return
mainSend(browserWindow, name, params)
+3 -12
View File
@@ -14,10 +14,8 @@ import {
getCacheSize,
toggleDevTools,
setWindowBounds,
setProgressBar,
setIgnoreMouseEvents,
// setThumbnailClip,
setThumbarButtons,
toggleMinimize,
toggleHide,
showSelectDialog,
@@ -63,7 +61,6 @@ export default () => {
app.exit(0)
return
}
global.lx.isTrafficLightClose = true
closeWindow()
})
// 全屏
@@ -106,13 +103,6 @@ export default () => {
setWindowBounds(params)
})
mainOn<LX.Player.ProgressBarOptions>(WIN_MAIN_RENDERER_EVENT_NAME.progress, ({ params }) => {
// console.log(params)
setProgressBar(params.progress, {
mode: params.mode ?? 'normal',
})
})
mainOn<boolean>(WIN_MAIN_RENDERER_EVENT_NAME.set_ignore_mouse_events, ({ params: isIgnored }) => {
isIgnored
? setIgnoreMouseEvents(isIgnored, { forward: true })
@@ -123,8 +113,9 @@ export default () => {
// return setThumbnailClip(params)
// })
mainOn<LX.TaskBarButtonFlags>(WIN_MAIN_RENDERER_EVENT_NAME.player_action_set_buttons, ({ params }) => {
setThumbarButtons(params)
mainOn<LX.Player.Status>(WIN_MAIN_RENDERER_EVENT_NAME.player_status, ({ params }) => {
// setThumbarButtons(params)
global.lx.event_app.player_status(params)
})
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.inited, () => {
@@ -11,6 +11,7 @@ import data from './data'
import music from './music'
import download from './download'
import soundEffect from './soundEffect'
import openAPI from './openAPI'
import { sendEvent } from '../main'
export * from './app'
@@ -37,6 +38,7 @@ export default () => {
music()
download()
soundEffect()
openAPI()
global.lx.event_app.on('updated_config', (keys, setting) => {
sendConfigChange(setting)
@@ -24,13 +24,13 @@ const decodeLyric = async(buf: Buffer, isGetLyricx: boolean) => {
// const isLyric = info.includes('\r\nlrcx=0\r\n')
if (buf.toString('utf8', 0, 10) != 'tp=content') return ''
// const index = buf.indexOf('\r\n\r\n') + 4
const lrcData = await handleInflate(buf.slice(buf.indexOf('\r\n\r\n') + 4))
const lrcData = await handleInflate(buf.subarray(buf.indexOf('\r\n\r\n') + 4))
if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030')
const buf_str = Buffer.from(lrcData.toString(), 'base64')
const buf_str_len = buf_str.length
const output = new Uint16Array(buf_str_len)
const output = new Uint8Array(buf_str_len)
let i = 0
while (i < buf_str_len) {
let j = 0
@@ -0,0 +1,18 @@
import { mainHandle } from '@common/mainIpc'
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
import {
startServer,
stopServer,
getStatus,
} from '@main/modules/openApi'
export default () => {
mainHandle<LX.OpenAPI.Actions, any>(WIN_MAIN_RENDERER_EVENT_NAME.open_api_action, async({ params: data }) => {
switch (data.action) {
case 'enable':
return data.data.enable ? await startServer(parseInt(data.data.port), data.data.bindLan) : await stopServer()
case 'status': return getStatus()
}
})
}
+2 -4
View File
@@ -5,16 +5,13 @@ import { type DislikeType, type AppType, type ListType } from '@main/event'
import { type DBSeriveTypes } from '@main/worker/utils'
interface Lx {
inited: boolean
appSetting: LX.AppSetting
hotKey: {
enable: boolean
config: LX.HotKeyConfigAll
state: LX.HotKeyState
}
/**
* 是否红绿灯关闭
*/
isTrafficLightClose: boolean
/**
* 是否跳过托盘退出
*/
@@ -30,6 +27,7 @@ interface Lx {
dbService: DBSeriveTypes
}
theme: LX.ThemeSetting
player_status: LX.Player.Status
}
declare global {
+1
View File
@@ -14,3 +14,4 @@ import '@common/types/ipc_main'
import '@common/types/sound_effect'
import '@common/types/dislike_list'
import '@common/types/dislike_list_sync'
import '@common/types/open_api'
+30 -1
View File
@@ -290,6 +290,35 @@ export const setPowerSaveBlocker = (enabled: boolean) => {
powerSaveBlockerId = powerSaveBlocker.start('prevent-app-suspension')
} else {
if (!isEnabled) return
if (powerSaveBlocker.stop(powerSaveBlockerId!)) powerSaveBlockerId = null
powerSaveBlocker.stop(powerSaveBlockerId!)
powerSaveBlockerId = null
}
}
let envProxy: null | { host: string, port: number } = null
export const getProxy = () => {
if (global.lx.appSetting['network.proxy.enable'] && global.lx.appSetting['network.proxy.host']) {
return {
host: global.lx.appSetting['network.proxy.host'],
port: parseInt(global.lx.appSetting['network.proxy.port'] || '80'),
}
}
if (envProxy) {
return {
host: envProxy.host,
port: envProxy.port,
}
} else {
const envProxyStr = envParams.cmdParams['proxy-server']
if (envProxyStr && typeof envProxyStr == 'string') {
const [host, port = ''] = envProxyStr.split(':')
return envProxy = {
host,
port: parseInt(port || '80'),
}
}
}
return null
}
+3 -35
View File
@@ -2,6 +2,7 @@ import needle, { type NeedleHttpVerbs, type NeedleOptions, type BodyData, type N
// import progress from 'request-progress'
import { httpOverHttp, httpsOverHttp } from 'tunnel'
import { type ClientRequest } from 'node:http'
import { getProxy } from './index'
// import fs from 'fs'
export const requestMsg = {
@@ -15,42 +16,9 @@ export const requestMsg = {
const httpsRxp = /^https:/
let envProxy: null | { host: string, port: string } = null
const getRequestAgent = (url: string) => {
if (envProxy == null) {
if (global.envParams.cmdParams['proxy-server'] && typeof global.envParams.cmdParams['proxy-server'] == 'string') {
const [host, port = ''] = global.envParams.cmdParams['proxy-server'].split(':')
envProxy = {
host,
port,
}
}
}
const proxy = {
enable: global.lx.appSetting['network.proxy.enable'],
host: global.lx.appSetting['network.proxy.host'],
port: global.lx.appSetting['network.proxy.port'],
envProxy,
}
let options
if (global.lx.appSetting['network.proxy.enable'] && proxy.host) {
options = {
proxy: {
host: proxy.host,
port: parseInt(proxy.port || '80'),
},
}
} else if (proxy.envProxy) {
options = {
proxy: {
host: proxy.envProxy.host,
port: parseInt(proxy.envProxy.port || '80'),
},
}
}
return options ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)(options) : undefined
const proxy = getProxy()
return proxy ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)({ proxy }) : undefined
}
export interface RequestOptions extends NeedleOptions {
@@ -20,7 +20,7 @@ export const queryDislikeList = () => {
* 批量插入不喜欢歌曲并刷新顺序
* @param infos 列表
*/
export const inertDislikeList = async(infos: LX.DBService.DislikeInfo[]) => {
export const insertDislikeList = async(infos: LX.DBService.DislikeInfo[]) => {
const db = getDB()
const insertStatement = createInsertStatement()
db.transaction((infos: LX.DBService.DislikeInfo[]) => {
@@ -1,7 +1,7 @@
import { SPLIT_CHAR } from '@common/constants'
import {
queryDislikeList,
inertDislikeList,
insertDislikeList,
overwirteDislikeList,
// updateDislikeList,
// deleteDislikeList,
@@ -71,7 +71,7 @@ export const getDislikeListInfo = (): LX.Dislike.DislikeInfo => {
* @param lists 列表信息
*/
export const dislikeInfoAdd = async(lists: LX.Dislike.DislikeMusicInfo[]) => {
await inertDislikeList(lists.map(info => ({ content: `${info.name}${SPLIT_CHAR.DISLIKE_NAME}${info.singer}` })))
await insertDislikeList(lists.map(info => ({ content: `${info.name}${SPLIT_CHAR.DISLIKE_NAME}${info.singer}` })))
}
/**
@@ -20,7 +20,7 @@ export const queryDownloadList = () => {
* 批量插入下载歌曲并刷新顺序
* @param mInfos 列表
*/
export const inertDownloadList = (mInfos: LX.DBService.DownloadMusicInfo[], listPositions: Array<{ id: string, position: number }>) => {
export const insertDownloadList = (mInfos: LX.DBService.DownloadMusicInfo[], listPositions: Array<{ id: string, position: number }>) => {
const db = getDB()
const insertStatement = createInsertStatement()
const updatePositionStatement = createUpdatePositionStatement()

Some files were not shown because too many files have changed in this diff Show More