mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/lyswhut/lx-music-desktop.git
synced 2026-09-20 08:04:00 +08:00
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba2b1dac92 | ||
|
|
56ffeb3008 | ||
|
|
b3757fd6b5 | ||
|
|
e0de81230e | ||
|
|
2bc669c828 | ||
|
|
493f7809a4 | ||
|
|
ef52cd6991 | ||
|
|
480f15b6f6 | ||
|
|
fcfee88ef4 | ||
|
|
19f647e6ab | ||
|
|
27e5ec02f4 | ||
|
|
0808d77a2d | ||
|
|
90d9e9b911 | ||
|
|
ebfbc11c20 | ||
|
|
2764743062 | ||
|
|
c66d7d6187 | ||
|
|
f2b6c0a443 | ||
|
|
aef0ffa506 | ||
|
|
040fcfba4f | ||
|
|
9f72831e4a | ||
|
|
fa0d9ad4c3 | ||
|
|
b6107b7281 | ||
|
|
6ffe61562b | ||
|
|
b5f5e13f22 | ||
|
|
376b7545e1 | ||
|
|
ffaa3e591b | ||
|
|
24d5722ef7 | ||
|
|
9e1fe0a6c1 | ||
|
|
d128aeb6ef | ||
|
|
ef371dedb4 | ||
|
|
00d262f0e6 | ||
|
|
475b8924c3 | ||
|
|
0afb1417a3 | ||
|
|
b6b41660d6 | ||
|
|
c78811fe36 | ||
|
|
a0254eb4f2 | ||
|
|
e9c96ecde2 | ||
|
|
f9cbf9254c | ||
|
|
70d24ffdfd | ||
|
|
84530ca0a2 | ||
|
|
e848fda4e3 | ||
|
|
51964e51c6 | ||
|
|
0c63268df1 | ||
|
|
316f62c3c8 | ||
|
|
9e37800426 | ||
|
|
3e0ee374c9 | ||
|
|
bb411a365c | ||
|
|
da0878a7a7 | ||
|
|
d0328f17e1 | ||
|
|
c6f49984a7 | ||
|
|
f53fb071f0 | ||
|
|
2cc8af71d0 | ||
|
|
006da66028 | ||
|
|
2a74abdef4 | ||
|
|
269b14394d | ||
|
|
d6d6fae2ca | ||
|
|
331afc37df | ||
|
|
041bfdaf3c | ||
|
|
023cd15cd0 | ||
|
|
f2fb37df4c | ||
|
|
c4ae0dddb0 | ||
|
|
f66077f012 | ||
|
|
a31d6bc5ae | ||
|
|
fad13f517b | ||
|
|
ef36517a26 | ||
|
|
085e45a50f | ||
|
|
43b06a686b | ||
|
|
e8dba4d133 | ||
|
|
1af3cd1d77 | ||
|
|
4c44310fea | ||
|
|
3e343a80fd | ||
|
|
313fac9f18 | ||
|
|
2c242c66e5 | ||
|
|
4e2b525c99 | ||
|
|
93c76d4ba0 | ||
|
|
b69ecca252 | ||
|
|
947011103c | ||
|
|
2fd7e4503a | ||
|
|
a5fce15661 | ||
|
|
f1561ddc21 | ||
|
|
74ce094d79 | ||
|
|
f832d70701 | ||
|
|
24521bf50d | ||
|
|
cfe84fc2dd | ||
|
|
8efeb169af | ||
|
|
8b0779ac17 | ||
|
|
1e904e5ebb | ||
|
|
1d3b93d4a3 | ||
|
|
7756b62f4c | ||
|
|
7990e841e0 | ||
|
|
ae348a71e6 | ||
|
|
9a1a537bfd | ||
|
|
6b7d2ea657 | ||
|
|
98ef7c2385 | ||
|
|
093bfd26a3 | ||
|
|
9953c61d62 | ||
|
|
e61072ddc9 | ||
|
|
2269cd54be | ||
|
|
76d5e64097 | ||
|
|
06073f3982 | ||
|
|
0f71a93c5e | ||
|
|
fa41c32a3e | ||
|
|
0f323ce04f | ||
|
|
ad2acc2a16 | ||
|
|
162fea6e28 | ||
|
|
30e939c354 | ||
|
|
94c25b5dcb | ||
|
|
a58d65e8af | ||
|
|
06b453ebe7 | ||
|
|
f00c6b1cb6 | ||
|
|
027466dfc2 | ||
|
|
bc69a98df1 | ||
|
|
ba9fd8d1f1 | ||
|
|
0fcc3f257d | ||
|
|
382235c556 | ||
|
|
0a919d7076 |
3
.babelrc
3
.babelrc
@@ -19,6 +19,7 @@
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-modules-umd",
|
||||
"@babel/plugin-transform-runtime"
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/--bug.md
vendored
2
.github/ISSUE_TEMPLATE/--bug.md
vendored
@@ -34,7 +34,7 @@ assignees: ''
|
||||
|
||||
**环境:**
|
||||
-操作系统及版本:[例如:Windows 10 64位 18362.156]
|
||||
-软件安装包及版本:[例如:Windows 64位绿色版 1.0.0]
|
||||
-软件安装包及版本:[例如:1.0.0 安装版]
|
||||
|
||||
|
||||
**其他内容**
|
||||
|
||||
102
CHANGELOG.md
102
CHANGELOG.md
@@ -6,6 +6,108 @@ 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/).
|
||||
|
||||
## [1.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.1...v1.8.0) - 2021-03-07
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
|
||||
- 新增启动参数`-play`,可以在启动软件时播放指定歌单,使用方法看Readme.md的"启动参数"部分
|
||||
- 新增逐字歌词播放,默认开启,可到设置界面关闭,注:本功能目前仅对酷狗源的歌曲有效
|
||||
- 新增自定义源功能,源编写规则可以去常见问题查看
|
||||
|
||||
### 优化
|
||||
|
||||
- 允许播放除了搜索列表以外的所有歌曲,即原来没有播放按钮或者灰色的歌曲都可以去尝试点击播放。注:该功能的原理是尝试自动切换到其他源播放,所以不一定会播放成功,特别是对于那些独家的资源
|
||||
- 优化单首歌曲的“添加到列表”弹窗歌曲列表状态的显示;现在在收藏单首歌曲时,若列表存在本歌曲则列表名字将变成灰色不可点击状态。总的来说,在添加单首歌曲时若列表名是灰色,则证明当前歌曲已在那个列表中
|
||||
- 将歌词翻译放到原文的下方,同时新增当前播放翻译的高亮功能
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除虾米源。注:虽然已移除该源,但仍可尝试去播放之前添加的歌曲,虽然不一定会成功
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复音乐搜索列表的稍后播放功能无效的问题
|
||||
- 修复搜索列表双击不支持播放的源时会导致切歌的问题
|
||||
- 修复歌单列表加载失败时无法进入歌单打开界面的问题
|
||||
- 修复mg源歌单列表无法加载的问题
|
||||
- 修复kg跳转到官方歌曲详情页的歌曲无法播放的问题
|
||||
- 修复我的列表的歌曲添加到其他列表时不排除当前列表的问题
|
||||
- 修复在下载列表右击未下载完成的歌曲弹出的右击菜单中没有开始下载选项的问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 歌词翻译显示功能修改为默认关闭,注:此变更仅影响首次安装软件的用户
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v9.4.4
|
||||
|
||||
## [1.7.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.0...v1.7.1) - 2021-01-30
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复非透明模式下右侧滚动条无法拖动的问题
|
||||
- 修复MAC下xm音乐滑块验证问题
|
||||
|
||||
## [1.7.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.6.1...v1.7.0) - 2021-01-30
|
||||
|
||||
### 新增
|
||||
|
||||
- 搜索界面新增搜索状态的提示
|
||||
- 新增“稍后播放”功能,可在歌曲列表右键菜单使用
|
||||
- 新增“记住播放进度”功能的控制,该功能默认不再开启,可到播放设置-记住播放进度开启
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化播放歌曲换源匹配
|
||||
- 优化设置界面设置项的展示
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复快速切换歌曲时, 会出现播放的歌曲和界面展示的歌曲不一致的问题
|
||||
- 修复了一个由版本更新日志显示导致的潜在远程代码执行攻击漏洞,该漏洞影响v1.6.1及之前的所有版本,请务必更新到最新版本
|
||||
- 修复xm搜索源验证问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到9.4.2
|
||||
|
||||
## [1.6.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.6.0...v1.6.1) - 2021-01-13
|
||||
|
||||
### 优化
|
||||
|
||||
- 改进自动换源时的歌曲匹配
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复某些情况下自动换源的时间过长时会终止换源自动切歌的问题
|
||||
- 修复自动换源导致的搜索列表每页变成10条数据的问题
|
||||
- 降级electron到9.3.3修复部分系统没有声音的问题
|
||||
|
||||
## [1.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.5.0...v1.6.0) - 2021-01-10
|
||||
|
||||
### 新增
|
||||
|
||||
- 我的列表右键菜单新增列表排序功能,可调整单曲、多选后的歌曲的顺序。注意:多选排序还将会按照选中歌曲时的顺序排序
|
||||
- 添加鼠标提示的自动关闭功能,鼠标长时间(目前是10秒)不动时鼠标提示将会自动关闭
|
||||
- 添加鼠标指向歌曲封面的提示(对于进度条左边的歌曲封面,你可能不知道的操作->右击在“我的列表”定位当前播放的歌曲)
|
||||
- 隐藏播放详情页按钮添加快速隐藏详情页提示(你可能不知道的操作->在播放详情页内的任意非窗口可拖动区域右键双击可以快速隐藏详情页)
|
||||
- 添加桌面歌词字体、透明度调整按钮微调提示(你可能不知道的操作->对于字体、透明度可右击微调)
|
||||
- 我的列表右键菜单添加搜索当前歌曲功能
|
||||
- 新增`-dha`参数,添加此启动参数将禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动,Linux系统的界面显示有问题时可尝试添加此参数启动,若不行可尝试添加`-dt`参数启动
|
||||
- 新增播放自动换源功能~
|
||||
|
||||
### 变更
|
||||
|
||||
- `-nt`参数更名为`-dt`(Disable Transparent),目前原来的`-nt`参数仍然可用,但将在后续的版本中移除
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复恢复上次播放的歌曲时在随机播放模式下不把恢复播放的歌曲放入已播放队列的问题(该问题会导致随机模式下会导致未播放完整个列表前就会再次随机到该歌曲,以及无法通过上一曲切回该歌曲)
|
||||
- 修复音乐嵌入的封面在 Mac 系统无法显示的问题
|
||||
- 修复`-dt`(原来的`-nt`)启动参数不真正生效的问题
|
||||
|
||||
## [1.5.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.4.1...v1.5.0) - 2020-12-13
|
||||
|
||||
### 新增
|
||||
|
||||
204
FAQ.md
204
FAQ.md
@@ -13,7 +13,7 @@
|
||||
1. 尝试更新到最新版本
|
||||
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
|
||||
3. 尝试到 设置-音乐来源 切换到其他接口
|
||||
4. 尝试切换网络,比如用手机开热点(目前存在某些网络无法访问接口服务器的情况)
|
||||
4. 尝试切换网络,比如用手机开热点(所有歌曲都提示请求异常时可通过此方法解决,或等一两天后再试)
|
||||
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
|
||||
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明,则应该是你网络无法访问接口服务器的问题,如果接口有问题我会在那里说明。
|
||||
|
||||
@@ -45,15 +45,24 @@
|
||||
对于分享出来的歌单,若打开失败,可尝试先在浏览器中打开后,再从浏览器地址栏复制URL地址到软件打开;<br>
|
||||
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
|
||||
|
||||
## Windows 7 下界面异常(界面显示不完整)
|
||||
## 界面异常(界面显示不完整)
|
||||
|
||||
### Windows 7 下界面异常
|
||||
|
||||
由于软件默认使用了透明窗口,根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7):
|
||||
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
|
||||
|
||||
因此,当 win7 没有使用**AERO**主题时界面将会显示异常,开启AERO的方法请自行百度:`win7开启aero效果`(开启后可看到任务栏变透明)。<br>
|
||||
从`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-nt`来运行程序即可,例如:`.\lx-music-desktop.exe -nt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
|
||||
从`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-dt`来运行程序即可,例如:`.\lx-music-desktop.exe -dt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
|
||||
|
||||
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况,请阅读下面的 **软件启动后,界面无法显示** 解决
|
||||
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况,请阅读下面的 **软件启动后,界面无法显示** 解决。
|
||||
|
||||
### Linux 下界面异常
|
||||
|
||||
根据Electron里issue的[解决方案](https://github.com/electron/electron/issues/2170#issuecomment-736223269),<br>
|
||||
若你遇到透明问题可尝试添加启动参数 `-dha` 来禁用硬件加速,例如:`.\lx-music-desktop.exe -dha`。
|
||||
|
||||
注:v1.6.0及之后的版本才支持`-dha`参数
|
||||
|
||||
## 软件启动后,界面无法显示
|
||||
|
||||
@@ -66,6 +75,7 @@
|
||||
若还是不行可尝试以下操作:
|
||||
|
||||
- 更新显卡驱动
|
||||
- 添加启动参数`-dha`运行(添加的方法请自行百度“给快捷方式加参数”)
|
||||
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
|
||||
|
||||
## 安装版安装失败,提示安装程序并未成功地运行完成
|
||||
@@ -98,10 +108,24 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的
|
||||
|
||||
### Linux 系统下桌面歌词窗口异常
|
||||
|
||||
目前在 Ubuntu 18.10 下第一次开启桌面歌词时歌词窗口会变白,需要关闭后再开启,
|
||||
`v1.2.1`以前的版本在 Ubuntu 18.10 下第一次开启桌面歌词时歌词窗口会变白,需要关闭后再开启,
|
||||
`v1.2.1`及之后的版本已修复该问题。
|
||||
|
||||
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题。
|
||||
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题,但你可以尝试按照`Linux 下界面异常`的解决方案去解决。
|
||||
|
||||
## 歌曲下载失败
|
||||
|
||||
### 提示 `ENOENT: no such file or directory, mkdir`
|
||||
|
||||
更换下载歌曲目录即可解决(一般是设置的歌曲下载目录没有读写权限导致的)。
|
||||
|
||||
### 提示 `请求异常` 或 `Fail`
|
||||
|
||||
尝试更换网络,如切换到移动网络。
|
||||
|
||||
### 其他错误
|
||||
|
||||
按照前面的 "歌曲无法试听与下载" 方案解决。
|
||||
|
||||
## 软件安装包说明
|
||||
|
||||
@@ -125,6 +149,12 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的
|
||||
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
|
||||
注意:**Mac版**、**Linux deb**版不支持自动更新!
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“同步”使用该功能,
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
|
||||
## 缺少`xxx.dll`
|
||||
|
||||
这个是电脑缺少某些dll导致的,正常的系统是没有这个问题的,可以尝试如下几个解决办法:
|
||||
@@ -141,3 +171,165 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的
|
||||
从`v0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
|
||||
|
||||
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
|
||||
|
||||
## 自定义源脚本编写说明
|
||||
|
||||
文件请使用UTF-8编码格式编写,脚本所用编程语言为JavaScript,可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @name 测试音乐源
|
||||
* @description 我只是一个测试音乐源哦
|
||||
* @version 1.0.0
|
||||
* @author xxx
|
||||
* @homepage http://xxx
|
||||
*/
|
||||
|
||||
|
||||
const { EVENT_NAMES, request, on, send } = window.lx
|
||||
|
||||
const qualitys = {
|
||||
kw: {
|
||||
'128k': '128',
|
||||
'320k': '320',
|
||||
flac: 'flac',
|
||||
},
|
||||
}
|
||||
const httpRequest = (url, options) => new Promise((resolve, reject) => {
|
||||
request(url, options, (err, resp) => {
|
||||
if (err) return reject(err)
|
||||
resolve(resp.body)
|
||||
})
|
||||
})
|
||||
|
||||
const apis = {
|
||||
kw: {
|
||||
musicUrl({ songmid }, quality) {
|
||||
return httpRequest('http://xxx').then(data => {
|
||||
return data.url
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 注册应用API请求事件
|
||||
// source 音乐源,可能的值取决于初始化时传入的sources对象的源key值
|
||||
// info 请求附加信息,内容根据action变化
|
||||
// action 请求操作类型,目前只有musicUrl,即获取音乐URL链接,
|
||||
// 当action为musicUrl时info的结构:{type, musicInfo},
|
||||
// info.type:音乐质量,可能的值有128k / 320k / flac(取决于初始化时对应源传入的qualitys值中的一个),
|
||||
// info.musicInfo:音乐信息对象,里面有音乐ID、名字等信息
|
||||
on(EVENT_NAMES.request, ({ source, action, info }) => {
|
||||
// 回调必须返回 Promise 对象
|
||||
switch (action) {
|
||||
// action 为 musicUrl 时需要在 Promise 返回歌曲 url
|
||||
case 'musicUrl':
|
||||
return apis[source].musicUrl(info.musicInfo, qualitys[source][info.type]).catch(err => {
|
||||
console.log(err)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 脚本初始化完成后需要发送inited事件告知应用
|
||||
send(EVENT_NAMES.inited, {
|
||||
status: true, // 初始化成功 or 失败
|
||||
openDevTools: false, // 是否打开开发者工具,方便用于调试脚本
|
||||
sources: { // 当前脚本支持的源
|
||||
kw: { // 支持的源对象,可用key值:kw/kg/tx/wy/mg
|
||||
name: '酷我音乐',
|
||||
type: 'music', // 目前固定为 music
|
||||
actions: ['musicUrl'], // 目前固定为 ['musicUrl']
|
||||
qualitys: ['128k', '320k', 'flac'], // 当前脚本的该源所支持获取的Url音质,有效的值有:['128k', '320k', 'flac']
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 自定义源信息
|
||||
|
||||
文件的开头必须包含以下注释:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @name 测试脚本
|
||||
* @description 我只是一个测试脚本
|
||||
* @version 1.0.0
|
||||
* @author xxx
|
||||
* @homepage http://xxx
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
- `@name `:源的名字,建议不要过长,10个字符以内
|
||||
- `@description `:源的描述,建议不要过长,20个字符以内,可不填,不填时必须保留 @description
|
||||
- `@version`:源的版本号,可不填,不填时可以删除 @version
|
||||
- `@author `:脚本作者名字,可不填,不填时可以删除 @author
|
||||
- `@homepage `:脚本主页,可不填,不填时可以删除 @homepage
|
||||
|
||||
### `window.lx`
|
||||
|
||||
应用为脚本暴露的API对象。
|
||||
|
||||
#### `window.lx.EVENT_NAMES`
|
||||
|
||||
常量事件名称对象,发送、注册事件时传入事件名时使用,可用值:
|
||||
|
||||
| 事件名 | 描述
|
||||
| --- | ---
|
||||
| `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`<br>`status`:初始化结果(`true`成功,`false`失败)<br>`openDevTools`:是否打开DevTools,此选项可用于开发脚本时的调试<br>`sources`:支持的源信息对象,<br>`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)<br>`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`<br>`sources[kw/kg/tx/wy/mg].actions`:支持的actions,由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可<br>`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,支持的值`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型
|
||||
| `request` | 应用API请求事件名,回调入参:`handler({ source, action, info})`,回调必须返回`Promise`对象<br>`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值<br>`info`:请求附加信息,内容根据`action`变化<br>`action`:请求操作类型,目前只有`musicUrl`,即获取音乐URL链接,需要在 Promise 返回歌曲 url,`info`的结构:`{type, musicInfo}`,`info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息
|
||||
|
||||
|
||||
#### `window.lx.on`
|
||||
|
||||
事件注册方法,应用主动与脚本通信时使用:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param event_name 事件名
|
||||
* @param handler 事件处理回调 -- 注意:注册的回调必须返回 Promise 对象
|
||||
*/
|
||||
window.lx.on(event_name, handler)
|
||||
```
|
||||
|
||||
**注意:** 注册的回调必须返回 `Promise` 对象。
|
||||
|
||||
#### `window.lx.send`
|
||||
|
||||
事件发送方法,脚本主动与应用通信时使用:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param event_name 事件名
|
||||
* @param datas 要传给应用的数据
|
||||
*/
|
||||
window.lx.send(event_name, datas)
|
||||
```
|
||||
|
||||
#### `window.lx.request`
|
||||
|
||||
HTTP请求方法,用于发送HTTP请求,此HTTP请求方法不受跨域规则限制:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param url 请求的URL
|
||||
* @param options 请求选项,可用选项有 method / headers / body / form / formData / timeout
|
||||
* @param callback 请求结果的回调 入参:err, resp, body
|
||||
* @return 返回一个方法,调用此方法可以终止HTTP请求
|
||||
*/
|
||||
const cancelHttp = window.lx.request(url, options, callback)
|
||||
```
|
||||
|
||||
#### `window.lx.utils`
|
||||
|
||||
应用提供给脚本的工具方法:
|
||||
|
||||
- `window.lx.utils.buffer.from`:对应Node.js的 `Buffer.from`
|
||||
- `window.lx.utils.crypto.aesEncrypt`:AES加密 `aesEncrypt(buffer, mode, key, iv)`
|
||||
- `window.lx.utils.crypto.md5`:MD5加密 `md5(str)`
|
||||
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`
|
||||
- `window.lx.utils.crypto.rsaEncrypt`:RSA加密 `rsaEncrypt(buffer, key)`
|
||||
|
||||
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。
|
||||
|
||||
13
README.md
13
README.md
@@ -47,7 +47,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.lanzoux.com/b0bf2cfa/` 密码:`glqw`<br>
|
||||
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoux.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
|
||||
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
|
||||
|
||||
### 源码使用方法
|
||||
@@ -62,7 +62,7 @@ npm run dev
|
||||
npm run pack:dir
|
||||
|
||||
# 构建安装包(Windows版)
|
||||
npm run pack
|
||||
npm run pack:win
|
||||
|
||||
# 构建安装包(Mac版)
|
||||
npm run pack:mac
|
||||
@@ -81,7 +81,14 @@ npm run pack:linux
|
||||
目前软件已支持的启动参数如下:
|
||||
|
||||
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
||||
- `-nt` 以非透明模式启动,对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示
|
||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
||||
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示(注:该参数对桌面歌词无效),原来的`-nt`参数已重命名为`-dt`(v1.6.0起重命名)
|
||||
- `-play` 启动时播放指定列表的音乐,参数说明:
|
||||
- `type`:播放类型,目前固定为`songList`
|
||||
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
|
||||
- `link`:要播放的在线列表歌单链接、或ID,source为`kw/kg/tx/wy/mg`之一(在线列表)时必传,举例:`./lx-music-desktop -play="type=songList&source=kw&link=歌单URL or ID",注意:如果传入URL时必须对URL进行编码后再传入
|
||||
- `name`:要播放的本地列表歌单名字,source为`myList`时必传,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表"
|
||||
- `index`:从列表的哪个位置开始播放,选传,若不传默认播放第一首歌曲,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表&index=2"
|
||||
|
||||
### 常见问题
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ module.exports = merge(baseConfig, {
|
||||
NODE_ENV: '"development"',
|
||||
},
|
||||
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
__userApi: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin({
|
||||
onErrors(severity, errors) { // Silent warning from electron-debug
|
||||
if (severity != 'warning') return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path')
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
@@ -20,6 +21,18 @@ module.exports = merge(baseConfig, {
|
||||
__filename: false,
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/renderer'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/renderer'),
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/rendererEvent/name.js'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/rendererEvent/name.js'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
|
||||
@@ -11,7 +11,6 @@ module.exports = merge(baseConfig, {
|
||||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
||||
@@ -11,7 +11,6 @@ module.exports = merge(baseConfig, {
|
||||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
||||
@@ -179,6 +179,9 @@ function startElectron() {
|
||||
function electronLog(data, color) {
|
||||
let log = data.toString()
|
||||
if (/[0-9A-z]+/.test(log)) {
|
||||
// 抑制 user api 窗口使用 data url 加载页面时 vue扩展 的报错日志刷屏的问题
|
||||
if (color == 'red' && typeof log === 'string' && log.includes('"Extension server error: Operation failed: Permission denied", source: devtools://devtools/bundled/extensions/extensions.js')) return
|
||||
|
||||
console.log(chalk[color](log))
|
||||
}
|
||||
}
|
||||
|
||||
7138
package-lock.json
generated
7138
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
82
package.json
82
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "1.5.0",
|
||||
"description": "一个免费的音乐下载助手",
|
||||
"version": "1.8.0",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
"scripts": {
|
||||
"pack": "node build-config/pack.js && npm run pack:win",
|
||||
"pack:win": "npm run pack:win:setup:x86_64 && npm run pack:win:7z",
|
||||
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
|
||||
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
|
||||
"pack:win:setup:x86_64": "cross-env TARGET=win_安装版 ARCH=x86_64 electron-builder -w=nsis --x64 --ia32",
|
||||
"pack:win:setup:x64": "cross-env TARGET=win_安装版 ARCH=x64 electron-builder -w=nsis --x64",
|
||||
"pack:win:setup:x86": "cross-env TARGET=win_安装版 ARCH=x86 electron-builder -w=nsis --ia32",
|
||||
@@ -34,18 +34,20 @@
|
||||
"publish:gh:linux": "node build-config/pack.js && npm run publish:linux",
|
||||
"publish:linux": "npm run publish:linux:deb && npm run publish:linux:appImage && npm run publish:linux:rpm && npm run publish:linux:pacman",
|
||||
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
|
||||
"publish:linux:deb": "npm run publish:linux:deb:x64 && npm run publish:linux:deb:x86 && npm run publish:linux:deb:arm64",
|
||||
"publish:linux:deb": "npm run publish:linux:deb:x64 && npm run publish:linux:deb:x86 && npm run publish:linux:deb:arm64 && npm run publish:linux:deb:armv7l",
|
||||
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
|
||||
"publish:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -p onTagOrDraft",
|
||||
"publish:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p onTagOrDraft",
|
||||
"publish:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p onTagOrDraft",
|
||||
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
|
||||
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
|
||||
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
|
||||
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage",
|
||||
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:x86 && npm run pack:linux:deb:arm64",
|
||||
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:x86 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
|
||||
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64",
|
||||
"pack:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32",
|
||||
"pack:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64",
|
||||
"pack:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l",
|
||||
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64",
|
||||
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64",
|
||||
"pack:mac": "node build-config/pack.js && electron-builder -m=dmg",
|
||||
@@ -58,7 +60,8 @@
|
||||
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress --hide-modules",
|
||||
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric",
|
||||
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly src",
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src",
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm update"
|
||||
},
|
||||
"browserslist": [
|
||||
"Electron 9.3.4"
|
||||
@@ -160,82 +163,83 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/core": "^7.13.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.12.1",
|
||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
||||
"@babel/plugin-transform-modules-umd": "^7.13.0",
|
||||
"@babel/plugin-transform-runtime": "^7.13.9",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.10",
|
||||
"@babel/preset-env": "^7.13.9",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"cfonts": "^2.9.0",
|
||||
"cfonts": "^2.9.1",
|
||||
"chalk": "^4.1.0",
|
||||
"changelog-parser": "^2.8.0",
|
||||
"copy-webpack-plugin": "^6.4.0",
|
||||
"core-js": "^3.8.1",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"core-js": "^3.9.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^4.3.0",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^9.3.3",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-debug": "^3.1.0",
|
||||
"electron": "^9.4.4",
|
||||
"electron-builder": "^22.10.5",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"eslint": "^7.15.0",
|
||||
"eslint": "^7.21.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-html": "^6.1.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.1.0",
|
||||
"markdown-it": "^12.0.3",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"less": "^3.13.1",
|
||||
"less-loader": "^7.3.0",
|
||||
"markdown-it": "^12.0.4",
|
||||
"mini-css-extract-plugin": "^0.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||
"postcss-loader": "^4.1.0",
|
||||
"postcss-loader": "^4.2.0",
|
||||
"postcss-pxtorem": "^5.1.1",
|
||||
"pug": "^3.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"spinnies": "^0.5.1",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^4.3.1",
|
||||
"stylus-loader": "^4.3.3",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.5",
|
||||
"vue-loader": "^15.9.6",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.6.1"
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"dnscache": "^1.0.2",
|
||||
"electron-log": "^4.3.0",
|
||||
"electron-log": "^4.3.2",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-updater": "^4.3.5",
|
||||
"electron-updater": "^4.3.8",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"image-size": "^0.9.3",
|
||||
"image-size": "^0.9.4",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^1.0.5",
|
||||
"needle": "^2.5.2",
|
||||
"node-id3": "^0.1.19",
|
||||
"lrc-file-parser": "^1.0.7",
|
||||
"needle": "^2.6.0",
|
||||
"node-id3": "^0.2.2",
|
||||
"request": "^2.88.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-i18n": "^8.22.2",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.6.0",
|
||||
"vue-i18n": "^8.23.0",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
### 新增
|
||||
|
||||
- 直接从歌单详情收藏的列表新增同步功能。注意:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表
|
||||
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
|
||||
- 新增启动参数`-play`,可以在启动软件时播放指定歌单,使用方法看Readme.md的"启动参数"部分
|
||||
- 新增逐字歌词播放,默认开启,可到设置界面关闭,注:本功能目前仅对酷狗源的歌曲有效
|
||||
- 新增自定义源功能,源编写规则可以去常见问题查看
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化软件启动时恢复上一次播放的歌曲进度功能
|
||||
- 允许播放除了搜索列表以外的所有歌曲,即原来没有播放按钮或者灰色的歌曲都可以去尝试点击播放。注:该功能的原理是尝试自动切换到其他源播放,所以不一定会播放成功,特别是对于那些独家的资源
|
||||
- 优化单首歌曲的“添加到列表”弹窗歌曲列表状态的显示;现在在收藏单首歌曲时,若列表存在本歌曲则列表名字将变成灰色不可点击状态。总的来说,在添加单首歌曲时若列表名是灰色,则证明当前歌曲已在那个列表中
|
||||
- 将歌词翻译放到原文的下方,同时新增当前播放翻译的高亮功能
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除虾米源。注:虽然已移除该源,但仍可尝试去播放之前添加的歌曲,虽然不一定会成功
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复MAC平台上下载歌曲封面嵌入无法显示的问题
|
||||
- 修复MAC平台首次运行软件最小化、关闭控制按钮默认在右边的问题
|
||||
- 修复酷狗源的某些歌曲没有专辑字段导致的列表加载失败问题
|
||||
- 修复某些酷狗源歌单链接无法打开的问题
|
||||
- 修复音乐搜索列表的稍后播放功能无效的问题
|
||||
- 修复搜索列表双击不支持播放的源时会导致切歌的问题
|
||||
- 修复歌单列表加载失败时无法进入歌单打开界面的问题
|
||||
- 修复mg源歌单列表无法加载的问题
|
||||
- 修复kg跳转到官方歌曲详情页的歌曲无法播放的问题
|
||||
- 修复我的列表的歌曲添加到其他列表时不排除当前列表的问题
|
||||
- 修复在下载列表右击未下载完成的歌曲弹出的右击菜单中没有开始下载选项的问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 歌词翻译显示功能修改为默认关闭,注:此变更仅影响首次安装软件的用户
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v9.4.4
|
||||
|
||||
@@ -44,7 +44,7 @@ module.exports = async newVerNum => {
|
||||
desc: version.desc,
|
||||
})
|
||||
version.version = newVerNum
|
||||
version.desc = newChangeLog
|
||||
version.desc = newMDChangeLog.replace(/(?:^|(\n))#{1,6} (.+)\n/g, '$1$2').trim()
|
||||
pkg.version = newVerNum
|
||||
|
||||
console.log(chalk.blue('new version: ') + chalk.green(newVerNum))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -51,6 +51,6 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
navigationUrlWhiteList: [
|
||||
/^https:\/\/www\.xiami\.com/,
|
||||
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.38',
|
||||
version: '1.0.40',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
@@ -11,7 +11,9 @@ const defaultSetting = {
|
||||
isMute: false,
|
||||
mediaDeviceId: 'default',
|
||||
isMediaDeviceRemovedStopPlay: false,
|
||||
isShowLyricTransition: true,
|
||||
isShowLyricTransition: false,
|
||||
isPlayLxlrc: true,
|
||||
isSavePlayTime: false,
|
||||
},
|
||||
desktopLyric: {
|
||||
enable: false,
|
||||
|
||||
@@ -2,11 +2,17 @@ const { ipcMain, ipcRenderer } = require('electron')
|
||||
const names = require('./ipcNames')
|
||||
|
||||
|
||||
exports.mainOn = (event, callback) => {
|
||||
ipcMain.on(event, callback)
|
||||
exports.mainOn = (name, callback) => {
|
||||
ipcMain.on(name, callback)
|
||||
}
|
||||
exports.mainOnce = (event, callback) => {
|
||||
ipcMain.once(event, callback)
|
||||
exports.mainOnce = (name, callback) => {
|
||||
ipcMain.once(name, callback)
|
||||
}
|
||||
exports.mainOff = (name, callback) => {
|
||||
ipcMain.removeListener(name, callback)
|
||||
}
|
||||
exports.mainOffAll = name => {
|
||||
ipcMain.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.mainHandle = (name, callback) => {
|
||||
@@ -15,6 +21,9 @@ exports.mainHandle = (name, callback) => {
|
||||
exports.mainHandleOnce = (name, callback) => {
|
||||
ipcMain.handleOnce(name, callback)
|
||||
}
|
||||
exports.mainHandleRemove = name => {
|
||||
ipcMain.removeListener(name)
|
||||
}
|
||||
|
||||
exports.mainSend = (window, name, params) => {
|
||||
window.webContents.send(name, params)
|
||||
@@ -33,5 +42,11 @@ exports.rendererOn = (name, callback) => {
|
||||
exports.rendererOnce = (name, callback) => {
|
||||
ipcRenderer.once(name, callback)
|
||||
}
|
||||
exports.rendererOff = (name, callback) => {
|
||||
ipcRenderer.removeListener(name, callback)
|
||||
}
|
||||
exports.rendererOffAll = name => {
|
||||
ipcRenderer.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.NAMES = names
|
||||
|
||||
@@ -50,6 +50,15 @@ const names = {
|
||||
get_data: 'get_data',
|
||||
save_data: 'save_data',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
remove_user_api: 'remove_user_api',
|
||||
set_user_api: 'set_user_api',
|
||||
get_user_api_list: 'get_user_api_list',
|
||||
request_user_api: 'request_user_api',
|
||||
request_user_api_cancel: 'request_user_api_cancel',
|
||||
get_user_api_status: 'get_user_api_status',
|
||||
user_api_status: 'user_api_status',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const log = require('electron-log')
|
||||
const Store = require('electron-store')
|
||||
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
|
||||
const apiSource = require('../renderer/utils/music/api-source-info')
|
||||
// const apiSource = require('../renderer/utils/music/api-source-info')
|
||||
const defaultHotKey = require('./defaultHotKey')
|
||||
const { dialog, app } = require('electron')
|
||||
const path = require('path')
|
||||
@@ -115,10 +115,10 @@ exports.mergeSetting = (setting, version) => {
|
||||
setting = defaultSettingCopy
|
||||
}
|
||||
|
||||
if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||
let api = apiSource.find(api => !api.disabled)
|
||||
if (api) setting.apiSource = api.id
|
||||
}
|
||||
// if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||
// let api = apiSource.find(api => !api.disabled)
|
||||
// if (api) setting.apiSource = api.id
|
||||
// }
|
||||
|
||||
return { setting, version: defaultVersion }
|
||||
}
|
||||
@@ -150,6 +150,9 @@ exports.initSetting = () => {
|
||||
const electronStore_config = new Store({
|
||||
name: 'config',
|
||||
})
|
||||
const electronStore_downloadList = new Store({
|
||||
name: 'downloadList',
|
||||
})
|
||||
let setting = electronStore_config.get('setting')
|
||||
if (setting) {
|
||||
let version = electronStore_config.get('version')
|
||||
@@ -165,7 +168,7 @@ exports.initSetting = () => {
|
||||
}
|
||||
const downloadList = electronStore_config.get('download')
|
||||
if (downloadList) {
|
||||
if (downloadList.list) electronStore_list.set('downloadList', downloadList.list)
|
||||
if (downloadList.list) electronStore_downloadList.set('list', downloadList.list)
|
||||
electronStore_config.delete('download')
|
||||
}
|
||||
}
|
||||
@@ -181,6 +184,13 @@ exports.initSetting = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 从我的列表分离下载列表 v1.7.0 后
|
||||
let downloadList = electronStore_list.get('downloadList')
|
||||
if (downloadList) {
|
||||
electronStore_downloadList.set('list', downloadList)
|
||||
electronStore_list.delete('downloadList')
|
||||
}
|
||||
|
||||
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
|
||||
|
||||
// 重置 ^0.18.2 排行榜ID
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
global.lx_event = {}
|
||||
if (!global.lx_event) global.lx_event = {}
|
||||
|
||||
const Common = require('./Common')
|
||||
const MainWindow = require('./MainWindow')
|
||||
@@ -6,8 +6,12 @@ const Tray = require('./Tray')
|
||||
const WinLyric = require('./WinLyric')
|
||||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
|
||||
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
||||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
|
||||
@@ -24,14 +24,20 @@ app.on('second-instance', (event, argv, cwd) => {
|
||||
|
||||
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
|
||||
require('./env')
|
||||
// console.log(global.envParams.cmdParams)
|
||||
|
||||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
if (global.envParams.cmdParams.dt == null && global.envParams.cmdParams.nt != null) global.envParams.cmdParams.dt = global.envParams.cmdParams.nt
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
|
||||
const { navigationUrlWhiteList } = require('../common/config')
|
||||
const { getWindowSizeInfo } = require('./utils')
|
||||
const { isMac, isLinux, initSetting, initHotKey } = require('../common/utils')
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死
|
||||
// https://github.com/electron/electron/issues/22791
|
||||
@@ -48,6 +54,7 @@ app.on('web-contents-created', (event, contents) => {
|
||||
event.preventDefault()
|
||||
if (/^devtools/.test(navigationUrl)) return
|
||||
console.log(navigationUrl)
|
||||
if (!/^https?:\/\//.test(navigationUrl)) return
|
||||
await shell.openExternal(navigationUrl)
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
@@ -95,7 +102,7 @@ function createWindow() {
|
||||
useContentSize: true,
|
||||
width: windowSizeInfo.width,
|
||||
frame: false,
|
||||
transparent: !global.envParams.nt,
|
||||
transparent: !global.envParams.cmdParams.dt,
|
||||
enableRemoteModule: false,
|
||||
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
@@ -124,6 +131,7 @@ global.appHotKey = {
|
||||
}
|
||||
|
||||
function init() {
|
||||
console.log('init')
|
||||
const info = initSetting()
|
||||
global.appSetting = info.setting
|
||||
global.appSettingVersion = info.version
|
||||
|
||||
@@ -2,3 +2,4 @@ require('./appMenu')
|
||||
require('./winLyric')
|
||||
require('./tray')
|
||||
require('./hotKey')
|
||||
require('./userApi')
|
||||
|
||||
2
src/main/modules/userApi/config/index.js
Normal file
2
src/main/modules/userApi/config/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
exports.userApis = []
|
||||
11
src/main/modules/userApi/event/event.js
Normal file
11
src/main/modules/userApi/event/event.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const USER_API_EVENT_NAME = require('./name')
|
||||
|
||||
class UserApi extends EventEmitter {
|
||||
status(info) {
|
||||
this.emit(USER_API_EVENT_NAME.status, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserApi
|
||||
|
||||
3
src/main/modules/userApi/event/name.js
Normal file
3
src/main/modules/userApi/event/name.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
status: 'status',
|
||||
}
|
||||
44
src/main/modules/userApi/index.js
Normal file
44
src/main/modules/userApi/index.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const { closeWindow } = require('./main')
|
||||
const { getUserApis, importApi, removeApi } = require('./utils')
|
||||
const { request, cancelRequest, getStatus, loadApi } = require('./rendererEvent/rendererEvent')
|
||||
|
||||
// const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames }
|
||||
let userApiId
|
||||
|
||||
exports.Event = Event
|
||||
exports.eventNames = eventNames
|
||||
|
||||
exports.getApiList = getUserApis
|
||||
exports.importApi = script => {
|
||||
return {
|
||||
apiInfo: importApi(script),
|
||||
apiList: getUserApis(),
|
||||
}
|
||||
}
|
||||
exports.request = request
|
||||
exports.cancelRequest = cancelRequest
|
||||
exports.getStatus = getStatus
|
||||
|
||||
exports.removeApi = async ids => {
|
||||
if (userApiId && ids.includes(userApiId)) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
removeApi(ids)
|
||||
return getUserApis()
|
||||
}
|
||||
|
||||
exports.setApi = async id => {
|
||||
if (userApiId) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
const apiList = getUserApis()
|
||||
if (!apiList.some(a => a.id === id)) return
|
||||
userApiId = id
|
||||
await loadApi(id)
|
||||
}
|
||||
|
||||
|
||||
88
src/main/modules/userApi/main.js
Normal file
88
src/main/modules/userApi/main.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const { BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const dir = global.isDev ? __userApi : path.join(__dirname, 'userApi')
|
||||
|
||||
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||
|
||||
let html = ''
|
||||
|
||||
fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => {
|
||||
if (err) throw new Error('api html read failed, info: ' + err.message)
|
||||
html = data
|
||||
})
|
||||
|
||||
const denyEvents = [
|
||||
'new-window',
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'will-attach-webview',
|
||||
'will-prevent-unload',
|
||||
'media-started-playing',
|
||||
]
|
||||
|
||||
const winEvent = win => {
|
||||
win.on('closed', () => {
|
||||
win = global.modules.userApiWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
exports.createWindow = async userApi => {
|
||||
if (global.modules.userApiWindow) return
|
||||
while (true) {
|
||||
if (html) break
|
||||
await wait(100)
|
||||
}
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
global.modules.userApiWindow = new BrowserWindow({
|
||||
enableRemoteModule: false,
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
worldSafeExecuteJavaScript: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(dir, 'renderer/preload.js'),
|
||||
},
|
||||
})
|
||||
|
||||
for (const eventName of denyEvents) {
|
||||
global.modules.userApiWindow.webContents.on(eventName, event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
callback(false)
|
||||
})
|
||||
|
||||
winEvent(global.modules.userApiWindow)
|
||||
|
||||
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
|
||||
const randomNum = Math.random().toString().substring(2, 10)
|
||||
global.modules.userApiWindow.loadURL(
|
||||
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
|
||||
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
|
||||
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
|
||||
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
|
||||
|
||||
// global.modules.userApiWindow.loadFile(path.join(dir, 'renderer/user-api.html'))
|
||||
// global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
exports.closeWindow = async() => {
|
||||
if (!global.modules.userApiWindow) return
|
||||
await Promise.all([
|
||||
global.modules.userApiWindow.webContents.session.clearAuthCache(),
|
||||
global.modules.userApiWindow.webContents.session.clearStorageData(),
|
||||
global.modules.userApiWindow.webContents.session.clearCache(),
|
||||
])
|
||||
global.modules.userApiWindow.destroy()
|
||||
global.modules.userApiWindow = null
|
||||
}
|
||||
227
src/main/modules/userApi/renderer/preload.js
Normal file
227
src/main/modules/userApi/renderer/preload.js
Normal file
@@ -0,0 +1,227 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const needle = require('needle')
|
||||
const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto')
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
|
||||
const sendMessage = (action, status, data, message) => {
|
||||
ipcRenderer.send(action, { status, data, message })
|
||||
}
|
||||
|
||||
let isInitedApi = false
|
||||
const EVENT_NAMES = {
|
||||
request: 'request',
|
||||
inited: 'inited',
|
||||
}
|
||||
const eventNames = Object.values(EVENT_NAMES)
|
||||
const events = {
|
||||
request: null,
|
||||
}
|
||||
const allSources = ['kw', 'kg', 'tx', 'wy', 'mg']
|
||||
const supportQualitys = {
|
||||
kw: ['128k', '320k', 'flac'],
|
||||
kg: ['128k', '320k', 'flac'],
|
||||
tx: ['128k', '320k', 'flac'],
|
||||
wy: ['128k', '320k', 'flac'],
|
||||
mg: ['128k', '320k', 'flac'],
|
||||
}
|
||||
const supportActions = {
|
||||
kw: ['musicUrl'],
|
||||
kg: ['musicUrl'],
|
||||
tx: ['musicUrl'],
|
||||
wy: ['musicUrl'],
|
||||
mg: ['musicUrl'],
|
||||
xm: ['musicUrl'],
|
||||
}
|
||||
|
||||
const handleRequest = (context, { requestKey, data }) => {
|
||||
// console.log(data)
|
||||
if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, 'Request event is not defined')
|
||||
try {
|
||||
events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => {
|
||||
let sendData = {
|
||||
requestKey,
|
||||
}
|
||||
switch (data.action) {
|
||||
case 'musicUrl':
|
||||
sendData.result = {
|
||||
source: data.source,
|
||||
action: data.action,
|
||||
data: {
|
||||
type: data.info.type,
|
||||
url: response,
|
||||
},
|
||||
}
|
||||
break
|
||||
}
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, true, sendData)
|
||||
}).catch(err => {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
|
||||
})
|
||||
} catch (err) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} context
|
||||
* @param {*} info {
|
||||
* status: true,
|
||||
* message: 'xxx',
|
||||
* sources: {
|
||||
* kw: ['128k', '320k', 'flac'],
|
||||
* kg: ['128k', '320k', 'flac'],
|
||||
* tx: ['128k', '320k', 'flac'],
|
||||
* wy: ['128k', '320k', 'flac'],
|
||||
* mg: ['128k', '320k', 'flac'],
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
const handleInit = (context, info) => {
|
||||
if (!info) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
|
||||
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
|
||||
return
|
||||
}
|
||||
if (info.openDevTools === true) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools)
|
||||
}
|
||||
if (!info.status) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
|
||||
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
|
||||
return
|
||||
}
|
||||
const sourceInfo = {
|
||||
sources: {},
|
||||
}
|
||||
try {
|
||||
for (const source of allSources) {
|
||||
const userSource = info.sources[source]
|
||||
if (!userSource || userSource.type !== 'music') continue
|
||||
const qualitys = supportQualitys[source]
|
||||
const actions = supportActions[source]
|
||||
sourceInfo.sources[source] = {
|
||||
type: 'music',
|
||||
actions: actions.filter(a => userSource.actions.includes(a)),
|
||||
qualitys: qualitys.filter(q => userSource.qualitys.includes(q)),
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, error.message)
|
||||
return
|
||||
}
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, true, sourceInfo)
|
||||
|
||||
ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => {
|
||||
handleRequest(context, data)
|
||||
})
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('lx', {
|
||||
EVENT_NAMES,
|
||||
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
|
||||
let options = { headers }
|
||||
let data
|
||||
if (body) {
|
||||
data = body
|
||||
} else if (form) {
|
||||
data = form
|
||||
// data.content_type = 'application/x-www-form-urlencoded'
|
||||
options.json = false
|
||||
} else if (formData) {
|
||||
data = formData
|
||||
// data.content_type = 'multipart/form-data'
|
||||
options.json = false
|
||||
}
|
||||
options.response_timeout = timeout
|
||||
|
||||
let request = needle.request(method, url, data, options, (err, resp, body) => {
|
||||
if (!err) {
|
||||
body = resp.body = resp.raw.toString()
|
||||
try {
|
||||
resp.body = JSON.parse(resp.body)
|
||||
} catch (_) {}
|
||||
body = resp.body
|
||||
}
|
||||
callback(err, {
|
||||
statusCode: resp.statusCode,
|
||||
statusMessage: resp.statusMessage,
|
||||
headers: resp.headers,
|
||||
bytes: resp.bytes,
|
||||
raw: resp.raw,
|
||||
body: body,
|
||||
}, body)
|
||||
}).request
|
||||
|
||||
return () => {
|
||||
if (!request.aborted) request.abort()
|
||||
request = null
|
||||
}
|
||||
},
|
||||
send(eventName, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
|
||||
switch (eventName) {
|
||||
case EVENT_NAMES.inited:
|
||||
if (isInitedApi) return
|
||||
isInitedApi = true
|
||||
handleInit(this, data)
|
||||
break
|
||||
default:
|
||||
resolve(new Error('Unknown event name: ' + eventName))
|
||||
}
|
||||
})
|
||||
},
|
||||
on(eventName, handler) {
|
||||
if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
switch (eventName) {
|
||||
case EVENT_NAMES.request:
|
||||
events.request = handler
|
||||
break
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
crypto: {
|
||||
aesEncrypt(buffer, mode, key, iv) {
|
||||
const cipher = createCipheriv('aes-128-' + mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
},
|
||||
rsaEncrypt(buffer, key) {
|
||||
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
|
||||
return publicEncrypt({ key: key, padding: constants.RSA_NO_PADDING }, buffer)
|
||||
},
|
||||
randomBytes(size) {
|
||||
return randomBytes(size)
|
||||
},
|
||||
md5(str) {
|
||||
return createHash('md5').update(str).digest('hex')
|
||||
},
|
||||
},
|
||||
buffer: {
|
||||
from(...args) {
|
||||
return Buffer.from(...args)
|
||||
},
|
||||
},
|
||||
},
|
||||
// removeEvent(eventName, handler) {
|
||||
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
// let handlers
|
||||
// switch (eventName) {
|
||||
// case EVENT_NAMES.request:
|
||||
// handlers = events.request
|
||||
// break
|
||||
// }
|
||||
// for (let index = 0; index < handlers.length; index++) {
|
||||
// if (handlers[index] === handler) {
|
||||
// handlers.splice(index, 1)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// removeAllEvents() {
|
||||
// for (const handlers of Object.values(events)) {
|
||||
// handlers.splice(0, handlers.length)
|
||||
// }
|
||||
// },
|
||||
})
|
||||
12
src/main/modules/userApi/renderer/user-api.html
Normal file
12
src/main/modules/userApi/renderer/user-api.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
12
src/main/modules/userApi/rendererEvent/name.js
Normal file
12
src/main/modules/userApi/rendererEvent/name.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const names = {
|
||||
init: '',
|
||||
request: '',
|
||||
response: '',
|
||||
openDevTools: '',
|
||||
}
|
||||
|
||||
|
||||
for (const key of Object.keys(names)) {
|
||||
names[key] = `userApi_${key}`
|
||||
}
|
||||
module.exports = names
|
||||
82
src/main/modules/userApi/rendererEvent/rendererEvent.js
Normal file
82
src/main/modules/userApi/rendererEvent/rendererEvent.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const { mainOn, mainSend } = require('@common/ipc')
|
||||
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
const { createWindow } = require('../main')
|
||||
const { getUserApis } = require('../utils')
|
||||
|
||||
let userApi
|
||||
let status = { status: true }
|
||||
const requestQueue = new Map()
|
||||
const timeouts = {}
|
||||
|
||||
const handleInit = (event, { status, message, data: apiInfo }) => {
|
||||
// console.log('inited')
|
||||
if (!status) {
|
||||
console.log('init failed:', message)
|
||||
global.lx_event.userApi.status(status = { status: false, apiInfo: userApi, message })
|
||||
return
|
||||
}
|
||||
global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } })
|
||||
}
|
||||
const handleResponse = (event, { status, data: { requestKey, result }, message }) => {
|
||||
const request = requestQueue.get(requestKey)
|
||||
if (!request) return
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
if (status) {
|
||||
request[0](result)
|
||||
} else {
|
||||
request[1](new Error(message))
|
||||
}
|
||||
}
|
||||
const handleOpenDevTools = () => {
|
||||
if (global.modules.userApiWindow) {
|
||||
global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
}
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
|
||||
|
||||
exports.loadApi = async apiId => {
|
||||
if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' })
|
||||
userApi = getUserApis().find(api => api.id == apiId)
|
||||
console.log('load api', userApi.name)
|
||||
await createWindow(userApi)
|
||||
// if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' })
|
||||
// if (!global.modules.userApiWindow) {
|
||||
// global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' })
|
||||
// throw new Error('user api window is not defined')
|
||||
// }
|
||||
|
||||
// // const path = require('path')
|
||||
// // // eslint-disable-next-line no-undef
|
||||
// // userApi.script = require('fs').readFileSync(path.join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString()
|
||||
// console.log('load api', userApi.name)
|
||||
// mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi })
|
||||
}
|
||||
|
||||
exports.cancelRequest = requestKey => {
|
||||
if (!requestQueue.has(requestKey)) return
|
||||
const request = requestQueue.get(requestKey)
|
||||
request[1](new Error('Cancel request'))
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
}
|
||||
|
||||
exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => {
|
||||
if (!userApi) return reject(new Error('user api is not load'))
|
||||
|
||||
// const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
|
||||
timeouts[requestKey] = setTimeout(() => {
|
||||
exports.cancelRequest(requestKey)
|
||||
}, 20000)
|
||||
|
||||
requestQueue.set(requestKey, [resolve, reject, data])
|
||||
mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.request, { requestKey, data })
|
||||
})
|
||||
|
||||
exports.getStatus = () => status
|
||||
46
src/main/modules/userApi/utils/index.js
Normal file
46
src/main/modules/userApi/utils/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { userApis: defaultUserApis } = require('../config')
|
||||
const Store = require('electron-store')
|
||||
|
||||
let userApis
|
||||
const electronStore_userApi = new Store({
|
||||
name: 'userApi',
|
||||
})
|
||||
|
||||
exports.getUserApis = () => {
|
||||
if (userApis) return userApis
|
||||
userApis = electronStore_userApi.get('userApis')
|
||||
if (!userApis) {
|
||||
userApis = defaultUserApis
|
||||
electronStore_userApi.set('userApis', userApis)
|
||||
}
|
||||
return userApis
|
||||
}
|
||||
|
||||
exports.importApi = script => {
|
||||
let scriptInfo = script.split(/\r?\n/)
|
||||
let name = scriptInfo[1] || ''
|
||||
let description = scriptInfo[2] || ''
|
||||
name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}`
|
||||
if (name.length > 10) name = name.substring(0, 10) + '...'
|
||||
description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : ''
|
||||
if (description.length > 20) description = description.substring(0, 20) + '...'
|
||||
const apiInfo = {
|
||||
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
|
||||
name,
|
||||
description,
|
||||
script,
|
||||
}
|
||||
userApis.push(apiInfo)
|
||||
electronStore_userApi.set('userApis', userApis)
|
||||
return apiInfo
|
||||
}
|
||||
|
||||
exports.removeApi = ids => {
|
||||
for (let index = userApis.length - 1; index > -1; index--) {
|
||||
if (ids.includes(userApis[index].id)) {
|
||||
userApis.splice(index, 1)
|
||||
ids.splice(index, 1)
|
||||
}
|
||||
}
|
||||
electronStore_userApi.set('userApis', userApis)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ const setLrcConfig = () => {
|
||||
config: desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTransition: global.appSetting.player.isShowLyricTransition,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
})
|
||||
if (isLock != desktopLyric.isLock) {
|
||||
isLock = desktopLyric.isLock
|
||||
|
||||
@@ -24,7 +24,12 @@ mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
|
||||
})
|
||||
|
||||
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
|
||||
return { config: global.appSetting.desktopLyric, languageId: global.appSetting.langId, isShowLyricTransition: global.appSetting.player.isShowLyricTransition }
|
||||
return {
|
||||
config: global.appSetting.desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTransition: global.appSetting.player.isShowLyricTransition,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
|
||||
|
||||
@@ -18,6 +18,6 @@ require('./showDialog')
|
||||
require('./playList')
|
||||
require('./data')
|
||||
|
||||
require('./xm_verify')
|
||||
|
||||
require('./kw_decodeLyric')
|
||||
|
||||
require('./userApi')
|
||||
|
||||
@@ -3,6 +3,7 @@ const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = requir
|
||||
|
||||
|
||||
let electronStore_list
|
||||
let electronStore_downloadList
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false) => {
|
||||
if (!electronStore_list) {
|
||||
@@ -11,13 +12,35 @@ mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false)
|
||||
clearInvalidConfig: !isIgnoredError,
|
||||
})
|
||||
}
|
||||
if (!electronStore_downloadList) {
|
||||
electronStore_downloadList = new Store({
|
||||
name: 'downloadList',
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
defaultList: electronStore_list.get('defaultList'),
|
||||
loveList: electronStore_list.get('loveList'),
|
||||
userList: electronStore_list.get('userList'),
|
||||
downloadList: electronStore_list.get('downloadList'),
|
||||
downloadList: electronStore_downloadList.get('list'),
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => electronStore_list && electronStore_list.set(type, data))
|
||||
const handleSaveList = ({ defaultList, loveList, userList }) => {
|
||||
if (!electronStore_list) return
|
||||
let data = {}
|
||||
if (defaultList != null) data.defaultList = defaultList
|
||||
if (loveList != null) data.loveList = loveList
|
||||
if (userList != null) data.userList = userList
|
||||
electronStore_list.set(data)
|
||||
}
|
||||
mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => {
|
||||
switch (type) {
|
||||
case 'myList':
|
||||
handleSaveList(data)
|
||||
break
|
||||
case 'downloadList':
|
||||
electronStore_downloadList && electronStore_downloadList.set('list', data)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
35
src/main/rendererEvents/userApi.js
Normal file
35
src/main/rendererEvents/userApi.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { mainSend, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
|
||||
const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames } = require('../modules/userApi')
|
||||
|
||||
const handleStatusChange = status => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_status, status)
|
||||
}
|
||||
|
||||
global.lx_event.userApi.on(eventNames.status, handleStatusChange)
|
||||
|
||||
mainHandle(ipcMainWindowNames.import_user_api, async(event, script) => {
|
||||
return importApi(script)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.remove_user_api, (event, apiIds) => {
|
||||
return removeApi(apiIds)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.set_user_api, (event, apiId) => {
|
||||
return setApi(apiId)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_user_api_list, event => {
|
||||
return getApiList()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_user_api_status, event => {
|
||||
return getStatus()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.request_user_api, (event, musicInfo) => {
|
||||
return request(musicInfo)
|
||||
})
|
||||
mainHandle(ipcMainWindowNames.request_user_api_cancel, (event, requestKey) => {
|
||||
return cancelRequest(requestKey)
|
||||
})
|
||||
@@ -1,4 +0,0 @@
|
||||
const { isMac } = require('../../../common/utils')
|
||||
|
||||
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
|
||||
require(isMac ? './xm_verify_win' : './xm_verify_view')
|
||||
@@ -1,4 +0,0 @@
|
||||
const { isMac } = require('../../../common/utils')
|
||||
|
||||
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
|
||||
require(isMac ? './xm_verify_win' : './xm_verify_view')
|
||||
@@ -1,66 +0,0 @@
|
||||
const { BrowserView } = require('electron')
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
|
||||
const { getWindowSizeInfo } = require('../../utils')
|
||||
|
||||
let view
|
||||
let isActioned = false
|
||||
let rejectFn
|
||||
|
||||
const closeView = async() => {
|
||||
if (!view) return
|
||||
// await view.webContents.session.clearCache()
|
||||
if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(view)
|
||||
await view.webContents.session.clearStorageData()
|
||||
view.destroy()
|
||||
view = null
|
||||
}
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
|
||||
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
|
||||
if (view) {
|
||||
global.modules.mainWindow.removeBrowserView(view)
|
||||
view.destroy()
|
||||
}
|
||||
|
||||
rejectFn = reject
|
||||
|
||||
isActioned = false
|
||||
|
||||
view = new BrowserView({
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
disableHtmlFullscreenWindowResize: true,
|
||||
},
|
||||
})
|
||||
view.webContents.on('did-finish-load', () => {
|
||||
if (/punish\?/.test(view.webContents.getURL())) return
|
||||
let ses = view.webContents.session
|
||||
ses.cookies.get({ name: 'x5sec' })
|
||||
.then(async([x5sec]) => {
|
||||
isActioned = true
|
||||
await closeView()
|
||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
||||
resolve(x5sec.value)
|
||||
}).catch(async err => {
|
||||
isActioned = true
|
||||
await closeView()
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
|
||||
global.modules.mainWindow.setBrowserView(view)
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
view.setBounds({ x: (windowSizeInfo.width - 380) / 2, y: ((windowSizeInfo.height - 320 + 52) / 2), width: 380, height: 320 })
|
||||
view.webContents.loadURL(url, {
|
||||
httpReferrer: 'https://www.xiami.com/',
|
||||
})
|
||||
// view.webContents.openDevTools()
|
||||
}))
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
|
||||
await closeView()
|
||||
if (!rejectFn) return
|
||||
if (!isActioned) rejectFn(new Error('canceled verify'))
|
||||
rejectFn = null
|
||||
})
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
const { BrowserWindow } = require('electron')
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
|
||||
const { getWindowSizeInfo } = require('../../utils')
|
||||
|
||||
let win
|
||||
|
||||
const closeWin = async() => {
|
||||
if (!win) return
|
||||
// await win.webContents.session.clearCache()
|
||||
// if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(win)
|
||||
if (win.isDestroyed()) {
|
||||
win = null
|
||||
return
|
||||
}
|
||||
await win.webContents.session.clearStorageData()
|
||||
win.destroy()
|
||||
win = null
|
||||
}
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
|
||||
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
|
||||
if (win) win.destroy()
|
||||
|
||||
let isActioned = false
|
||||
|
||||
const mainWindowSizeInfo = global.modules.mainWindow.getBounds()
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
win = new BrowserWindow({
|
||||
parent: global.modules.mainWindow,
|
||||
width: 460,
|
||||
height: 370,
|
||||
resizable: false,
|
||||
// transparent: true,
|
||||
x: mainWindowSizeInfo.x + (windowSizeInfo.width - 460) / 2,
|
||||
y: mainWindowSizeInfo.y + (windowSizeInfo.height - 320 + 52) / 2,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
// movable: false,
|
||||
// frame: false,
|
||||
// modal: true,
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
disableHtmlFullscreenWindowResize: true,
|
||||
},
|
||||
})
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
if (/punish\?/.test(win.webContents.getURL())) return
|
||||
let ses = win.webContents.session
|
||||
ses.cookies.get({ name: 'x5sec' })
|
||||
.then(async([x5sec]) => {
|
||||
isActioned = true
|
||||
await closeWin()
|
||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
||||
resolve(x5sec.value)
|
||||
}).catch(async err => {
|
||||
isActioned = true
|
||||
await closeWin()
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
|
||||
win.webContents.loadURL(url, {
|
||||
httpReferrer: 'https://www.xiami.com/',
|
||||
})
|
||||
|
||||
win.on('closed', async() => {
|
||||
await closeWin()
|
||||
if (isActioned) return
|
||||
reject(new Error('canceled verify'))
|
||||
})
|
||||
|
||||
// win.webContents.openDevTools()
|
||||
}))
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
|
||||
await closeWin()
|
||||
})
|
||||
@@ -4,7 +4,7 @@
|
||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
.control-bar(v-show="!lrcConfig.isLock")
|
||||
core-control-bar(:lrcConfig="lrcConfig" :themes="themeList")
|
||||
core-lyric(:lrcConfig="lrcConfig" :isShowLyricTransition="isShowLyricTransition")
|
||||
core-lyric(:lrcConfig="lrcConfig" :isPlayLxlrc="isPlayLxlrc" :isShowLyricTransition="isShowLyricTransition")
|
||||
div.resize-left(@mousedown.self="handleMouseDown('left', $event)")
|
||||
div.resize-top(@mousedown.self="handleMouseDown('top', $event)")
|
||||
div.resize-right(@mousedown.self="handleMouseDown('right', $event)")
|
||||
@@ -45,6 +45,7 @@ export default {
|
||||
},
|
||||
},
|
||||
isShowLyricTransition: true,
|
||||
isPlayLxlrc: true,
|
||||
themeList: [
|
||||
{
|
||||
id: 0,
|
||||
@@ -118,9 +119,10 @@ export default {
|
||||
document.removeEventListener('mouseup', this.handleMouseUp)
|
||||
},
|
||||
methods: {
|
||||
handleUpdateConfig({ config, languageId, isShowLyricTransition }) {
|
||||
handleUpdateConfig({ config, languageId, isShowLyricTransition, isPlayLxlrc }) {
|
||||
this.lrcConfig = config
|
||||
this.isShowLyricTransition = isShowLyricTransition
|
||||
this.isPlayLxlrc = isPlayLxlrc
|
||||
if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId
|
||||
},
|
||||
handleMouseDown(origin, event) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template lang="pug">
|
||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }, { [$style.lrcActiveZoom]: lrcConfig.style.isZoomActiveLrc } ]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="$style.lyricSpace")
|
||||
div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]")
|
||||
div(:class="[$style.lyricText]" ref="dom_lyric_text")
|
||||
//- div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]")
|
||||
p(:class="$style.lrcLine") {{info.text}}
|
||||
div(:class="$style.lyricSpace")
|
||||
</template>
|
||||
@@ -9,7 +10,7 @@ div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" :style=
|
||||
<script>
|
||||
import { rendererOn, rendererSend, NAMES } from '../../../common/ipc'
|
||||
import { scrollTo } from '../../../renderer/utils'
|
||||
import Lyric from 'lrc-file-parser'
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
|
||||
let cancelScrollFn = null
|
||||
|
||||
@@ -27,6 +28,10 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
isPlayLxlrc: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isShowLyricTransition: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@@ -65,6 +70,7 @@ export default {
|
||||
lyrics: {
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
lxlyric: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -85,7 +91,7 @@ export default {
|
||||
if (n.length) {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
} else {
|
||||
@@ -97,7 +103,7 @@ export default {
|
||||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||
this.lyricLines = this._lyricLines
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}, 50)
|
||||
@@ -105,7 +111,7 @@ export default {
|
||||
} else {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}
|
||||
@@ -120,8 +126,11 @@ export default {
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
isShowLyricTransition(n) {
|
||||
console.log(n)
|
||||
isShowLyricTransition() {
|
||||
this.setLyric()
|
||||
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
||||
},
|
||||
isPlayLxlrc() {
|
||||
this.setLyric()
|
||||
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
||||
},
|
||||
@@ -129,6 +138,11 @@ export default {
|
||||
created() {
|
||||
rendererOn(NAMES.winLyric.set_lyric_info, (event, data) => this.handleSetInfo(data))
|
||||
window.lrc = new Lyric({
|
||||
lineClassName: 'lrc-content',
|
||||
fontClassName: 'font',
|
||||
shadowClassName: 'shadow',
|
||||
shadowContent: true,
|
||||
activeLineClassName: 'active',
|
||||
onPlay: (line, text) => {
|
||||
this.lyric.text = text
|
||||
this.lyric.line = line
|
||||
@@ -136,6 +150,12 @@ export default {
|
||||
},
|
||||
onSetLyric: lines => { // listening lyrics seting event
|
||||
// console.log(lines) // lines is array of all lyric text
|
||||
this.$refs.dom_lyric_text.textContent = ''
|
||||
const dom_lines = document.createDocumentFragment()
|
||||
for (const line of lines) {
|
||||
dom_lines.appendChild(line.dom_line)
|
||||
}
|
||||
this.$refs.dom_lyric_text.appendChild(dom_lines)
|
||||
this.lyric.lines = lines
|
||||
this.lyric.line = 0
|
||||
},
|
||||
@@ -159,6 +179,7 @@ export default {
|
||||
case 'lyric':
|
||||
this.lyrics.lyric = data.lrc
|
||||
this.lyrics.tlyric = data.tlrc
|
||||
this.lyrics.lxlyric = data.lxlrc
|
||||
this.setLyric()
|
||||
break
|
||||
case 'play':
|
||||
@@ -171,8 +192,9 @@ export default {
|
||||
break
|
||||
case 'info':
|
||||
// console.log('info', data)
|
||||
this.lyrics.lyric = data.lyric
|
||||
this.lyrics.tlyric = data.tlyric
|
||||
this.lyrics.lyric = data.lrc
|
||||
this.lyrics.tlyric = data.tlrc
|
||||
this.lyrics.lxlyric = data.lxlrc
|
||||
this.setLyric()
|
||||
this.$nextTick(() => {
|
||||
this.lyric.line = data.line
|
||||
@@ -211,7 +233,10 @@ export default {
|
||||
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0)
|
||||
},
|
||||
handleLyricMouseDown(e) {
|
||||
if (e.target.classList.contains(this.$style.lrcLine)) {
|
||||
if (e.target.classList.contains('font') ||
|
||||
e.target.parentNode.classList.contains('font') ||
|
||||
e.target.classList.contains('translation') ||
|
||||
e.target.parentNode.classList.contains('translation')) {
|
||||
this.lyricEvent.isMsDown = true
|
||||
this.lyricEvent.msDownY = e.clientY
|
||||
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
|
||||
@@ -279,7 +304,11 @@ export default {
|
||||
rendererSend(NAMES.winLyric.close)
|
||||
},
|
||||
setLyric() {
|
||||
window.lrc.setLyric((this.isShowLyricTransition && this.lyrics.tlyric ? (this.lyrics.tlyric + '\n') : '') + (this.lyrics.lyric || ''))
|
||||
window.lrc.setLyric(
|
||||
this.isPlayLxlrc && this.lyrics.lxlyric ? this.lyrics.lxlyric : this.lyrics.lyric,
|
||||
this.isShowLyricTransition && this.lyrics.tlyric ? this.lyrics.tlyric : '',
|
||||
// (this.isShowLyricTransition && this.lyrics.tlyric ? (this.lyrics.tlyric + '\n') : '') + (this.lyrics.lyric || ''),
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -292,34 +321,93 @@ export default {
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 68px;
|
||||
padding: 0 5px;
|
||||
opacity: .6;
|
||||
transition: opacity @transition-theme;
|
||||
&.draging {
|
||||
.lrc-line {
|
||||
cursor: grabbing;
|
||||
font-size: 16px;
|
||||
color: @color-theme-lyric;
|
||||
cursor: move;
|
||||
|
||||
:global {
|
||||
.lrc-content {
|
||||
line-height: 1.2;
|
||||
margin: 16px 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
.font {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.font, .translation {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.translation {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size, color;
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.line {
|
||||
transition-property: font-size, color !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: unset;
|
||||
// -webkit-text-fill-color: none !important;
|
||||
}
|
||||
&.active {
|
||||
.line {
|
||||
color: @color-theme;
|
||||
}
|
||||
.translation {
|
||||
color: @color-theme;
|
||||
}
|
||||
// span {
|
||||
// color: @color-theme;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size !important;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: @color-theme-lyric;
|
||||
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.shadow {
|
||||
text-shadow: 1px 1px 2px rgb(32, 32, 32);
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
}
|
||||
.lrc-line {
|
||||
display: inline-block;
|
||||
padding: 8px 0;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme;
|
||||
transition-property: color, font-size, text-shadow;
|
||||
cursor: grab;
|
||||
// font-weight: bold;
|
||||
// background-clip: text;
|
||||
color: @color-theme-lyric;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
// background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
|
||||
// background-clip: text;
|
||||
// -webkit-background-clip: text;
|
||||
// -webkit-text-fill-color: #fff;
|
||||
// -webkit-text-stroke: thin #124628;
|
||||
}
|
||||
// .lrc-line {
|
||||
// display: inline-block;
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme;
|
||||
// transition-property: color, font-size, text-shadow;
|
||||
// cursor: grab;
|
||||
// // font-weight: bold;
|
||||
// // background-clip: text;
|
||||
// color: @color-theme-lyric;
|
||||
// text-shadow: 1px 1px 2px #000;
|
||||
// // background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
|
||||
// // background-clip: text;
|
||||
// // -webkit-background-clip: text;
|
||||
// // -webkit-text-fill-color: #fff;
|
||||
// // -webkit-text-stroke: thin #124628;
|
||||
// }
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
}
|
||||
@@ -334,9 +422,28 @@ export default {
|
||||
// -webkit-text-stroke: thin #124628;
|
||||
}
|
||||
}
|
||||
.draging {
|
||||
:global {
|
||||
.lrc-content {
|
||||
.font, .translation {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.lrc-active-zoom {
|
||||
.lrc-active;
|
||||
font-size: 1.2em;
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.translation {
|
||||
font-size: 1em;
|
||||
}
|
||||
span {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
flex: 0 0 100px;
|
||||
@@ -348,14 +455,30 @@ export default {
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.lrc-line {
|
||||
color: ~'@{color-@{value}-theme-lyric}';
|
||||
}
|
||||
// .lrc-line {
|
||||
// color: ~'@{color-@{value}-theme-lyric}';
|
||||
// }
|
||||
.lrc-active, .lrc-active-zoom {
|
||||
.lrc-line {
|
||||
color: ~'@{color-@{value}-theme-lyric_2}';
|
||||
}
|
||||
}
|
||||
.lyric {
|
||||
color: ~'@{color-@{value}-theme-lyric}';
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.line {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
span {
|
||||
// background-color: ~'@{color-@{value}-theme_2-font}';
|
||||
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-theme}', ~'@{color-@{value}-theme}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template lang="pug">
|
||||
#container(v-if="isProd && !isNt && !isLinux" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
|
||||
#container(v-if="isProd && !isDT && !isLinux" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
|
||||
core-aside#left
|
||||
#right
|
||||
core-toolbar#toolbar
|
||||
core-view#view
|
||||
core-player#player
|
||||
core-icons
|
||||
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
#container(v-else :class="theme")
|
||||
@@ -16,7 +15,6 @@
|
||||
core-view#view
|
||||
core-player#player
|
||||
core-icons
|
||||
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
</template>
|
||||
@@ -27,8 +25,9 @@ import { mapMutations, mapGetters, mapActions } from 'vuex'
|
||||
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
||||
import { isLinux } from '../common/utils'
|
||||
import music from './utils/music'
|
||||
import { throttle, openUrl, compareVer, getPlayList } from './utils'
|
||||
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams } from './utils'
|
||||
import { base as eventBaseName } from './event/names'
|
||||
import apiSourceInfo from './utils/music/api-source-info'
|
||||
|
||||
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||
dnscache({
|
||||
@@ -37,19 +36,36 @@ dnscache({
|
||||
cachesize: 1000,
|
||||
})
|
||||
|
||||
const listThrottle = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _data = {}
|
||||
return function(data) {
|
||||
Object.assign(_data, data)
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.call(this, _data)
|
||||
_data = {}
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isProd: process.env.NODE_ENV === 'production',
|
||||
isNt: false,
|
||||
isDT: false,
|
||||
isLinux,
|
||||
globalObj: {
|
||||
apiSource: 'test',
|
||||
apiSource: null,
|
||||
proxy: {},
|
||||
isShowPact: false,
|
||||
qualityList: {},
|
||||
xm: {
|
||||
isShowVerify: false,
|
||||
userApi: {
|
||||
list: [],
|
||||
status: false,
|
||||
message: 'initing',
|
||||
apis: {},
|
||||
},
|
||||
},
|
||||
updateTimeout: null,
|
||||
@@ -70,24 +86,12 @@ export default {
|
||||
}),
|
||||
},
|
||||
created() {
|
||||
this.saveDefaultList = throttle(n => {
|
||||
this.saveMyList = listThrottle(data => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'defaultList',
|
||||
data: n,
|
||||
type: 'myList',
|
||||
data,
|
||||
})
|
||||
}, 500)
|
||||
this.saveLoveList = throttle(n => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'loveList',
|
||||
data: n,
|
||||
})
|
||||
}, 500)
|
||||
this.saveUserList = throttle(n => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'userList',
|
||||
data: n,
|
||||
})
|
||||
}, 500)
|
||||
}, 300)
|
||||
this.saveDownloadList = throttle(n => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'downloadList',
|
||||
@@ -102,7 +106,7 @@ export default {
|
||||
}, 500)
|
||||
},
|
||||
mounted() {
|
||||
document.body.classList.add(this.isNt ? 'noTransparent' : 'transparent')
|
||||
document.body.classList.add(this.isDT ? 'disableTransparent' : 'transparent')
|
||||
window.eventHub.$emit(eventBaseName.bindKey)
|
||||
this.init()
|
||||
},
|
||||
@@ -115,19 +119,19 @@ export default {
|
||||
},
|
||||
defaultList: {
|
||||
handler(n) {
|
||||
this.saveDefaultList(n)
|
||||
this.saveMyList({ defaultList: n })
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
loveList: {
|
||||
handler(n) {
|
||||
this.saveLoveList(n)
|
||||
this.saveMyList({ loveList: n })
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
userList: {
|
||||
handler(n) {
|
||||
this.saveUserList(n)
|
||||
this.saveMyList({ userList: n })
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
@@ -140,8 +144,20 @@ export default {
|
||||
searchHistoryList(n) {
|
||||
this.saveSearchHistoryList(n)
|
||||
},
|
||||
'globalObj.apiSource'(n) {
|
||||
this.globalObj.qualityList = music.supportQuality[n]
|
||||
'globalObj.apiSource'(n, o) {
|
||||
if (/^user_api/.test(n)) {
|
||||
this.globalObj.qualityList = {}
|
||||
this.globalObj.userApi.status = false
|
||||
this.globalObj.userApi.message = 'initing'
|
||||
} else {
|
||||
this.globalObj.qualityList = music.supportQuality[n]
|
||||
}
|
||||
if (o === null) return
|
||||
rendererInvoke(NAMES.mainWindow.set_user_api, n).catch(err => {
|
||||
console.log(err)
|
||||
let api = apiSourceInfo.find(api => !api.disabled)
|
||||
if (api) this.globalObj.apiSource = api.id
|
||||
})
|
||||
if (n != this.setting.apiSource) {
|
||||
this.setSetting(Object.assign({}, this.setting, {
|
||||
apiSource: n,
|
||||
@@ -177,10 +193,12 @@ export default {
|
||||
...mapMutations('player', {
|
||||
setPlayList: 'setList',
|
||||
}),
|
||||
...mapActions('songList', ['getListDetailAll']),
|
||||
init() {
|
||||
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
|
||||
|
||||
rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit)
|
||||
const asyncTask = []
|
||||
asyncTask.push(rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit))
|
||||
|
||||
document.body.addEventListener('click', this.handleBodyClick, true)
|
||||
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
|
||||
@@ -244,33 +262,44 @@ export default {
|
||||
}, 60 * 30 * 1000)
|
||||
|
||||
this.listenEvent()
|
||||
this.initData()
|
||||
asyncTask.push(this.initData())
|
||||
asyncTask.push(this.initUserApi())
|
||||
this.globalObj.apiSource = this.setting.apiSource
|
||||
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
|
||||
if (/^user_api/.test(this.setting.apiSource)) {
|
||||
rendererInvoke(NAMES.mainWindow.set_user_api, this.setting.apiSource)
|
||||
} else {
|
||||
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
|
||||
}
|
||||
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
|
||||
window.globalObj = this.globalObj
|
||||
|
||||
// 初始化音乐sdk
|
||||
music.init()
|
||||
asyncTask.push(music.init())
|
||||
Promise.all(asyncTask).then(() => {
|
||||
this.handleInitEnvParamSearch()
|
||||
this.handleInitEnvParamPlay()
|
||||
})
|
||||
},
|
||||
enableIgnoreMouseEvents() {
|
||||
if (this.isNt) return
|
||||
if (this.isDT) return
|
||||
rendererSend(NAMES.mainWindow.set_ignore_mouse_events, false)
|
||||
// console.log('content enable')
|
||||
},
|
||||
dieableIgnoreMouseEvents() {
|
||||
if (this.isNt) return
|
||||
if (this.isDT) return
|
||||
// console.log('content disable')
|
||||
rendererSend(NAMES.mainWindow.set_ignore_mouse_events, true)
|
||||
},
|
||||
|
||||
initData() { // 初始化数据
|
||||
this.initLocalList() // 初始化播放列表
|
||||
return Promise.all([
|
||||
this.initMyList(), // 初始化播放列表
|
||||
this.initSearchHistoryList(), // 初始化搜索历史列表
|
||||
])
|
||||
// this.initDownloadList() // 初始化下载列表
|
||||
this.initSearchHistoryList() // 初始化搜索历史列表
|
||||
},
|
||||
initLocalList() {
|
||||
getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
||||
initMyList() {
|
||||
return getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
||||
if (!defaultList) defaultList = this.defaultList
|
||||
if (!loveList) loveList = this.loveList
|
||||
if (userList) {
|
||||
@@ -340,6 +369,72 @@ export default {
|
||||
})
|
||||
})
|
||||
},
|
||||
initUserApi() {
|
||||
return Promise.all([
|
||||
rendererOn(NAMES.mainWindow.user_api_status, (event, { status, message, apiInfo }) => {
|
||||
// console.log(apiInfo)
|
||||
this.globalObj.userApi.status = status
|
||||
this.globalObj.userApi.message = message
|
||||
if (status) {
|
||||
if (apiInfo.id === this.setting.apiSource) {
|
||||
let apis = {}
|
||||
let qualitys = {}
|
||||
for (const [source, { actions, type, qualitys: sourceQualitys }] of Object.entries(apiInfo.sources)) {
|
||||
if (type != 'music') continue
|
||||
apis[source] = {}
|
||||
for (const action of actions) {
|
||||
switch (action) {
|
||||
case 'musicUrl':
|
||||
apis[source].getMusicUrl = (songInfo, type) => {
|
||||
const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
return {
|
||||
canceleFn() {
|
||||
rendererInvoke(NAMES.mainWindow.request_user_api_cancel, requestKey)
|
||||
},
|
||||
promise: rendererInvoke(NAMES.mainWindow.request_user_api, {
|
||||
requestKey,
|
||||
data: {
|
||||
source: source,
|
||||
action: 'musicUrl',
|
||||
info: {
|
||||
type,
|
||||
musicInfo: songInfo,
|
||||
},
|
||||
},
|
||||
}).then(res => {
|
||||
// console.log(res)
|
||||
if (!/^https?:/.test(res.data.url)) return Promise.reject(new Error('Get url failed'))
|
||||
return { type, url: res.data.url }
|
||||
}).catch(err => {
|
||||
console.log(err.message)
|
||||
return Promise.reject(err)
|
||||
}),
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
qualitys[source] = sourceQualitys
|
||||
}
|
||||
this.globalObj.qualityList = qualitys
|
||||
this.globalObj.userApi.apis = apis
|
||||
}
|
||||
}
|
||||
}),
|
||||
rendererInvoke(NAMES.mainWindow.get_user_api_list).then(res => {
|
||||
// console.log(res)
|
||||
if (![...apiSourceInfo.map(s => s.id), ...res.map(s => s.id)].includes(this.setting.apiSource)) {
|
||||
console.warn('reset api')
|
||||
let api = apiSourceInfo.find(api => !api.disabled)
|
||||
if (api) this.globalObj.apiSource = api.id
|
||||
}
|
||||
this.globalObj.userApi.list = res
|
||||
}),
|
||||
])
|
||||
},
|
||||
showUpdateModal() {
|
||||
(this.version.newVersion && this.version.newVersion.history
|
||||
? Promise.resolve(this.version.newVersion)
|
||||
@@ -380,27 +475,100 @@ export default {
|
||||
},
|
||||
handleEnvParamsInit(envParams) {
|
||||
this.envParams = envParams
|
||||
this.isNt = this.envParams.nt
|
||||
if (this.isNt) {
|
||||
this.isDT = this.envParams.dt
|
||||
if (this.isDT) {
|
||||
document.body.classList.remove('transparent')
|
||||
document.body.classList.add('noTransparent')
|
||||
document.body.classList.add('disableTransparent')
|
||||
}
|
||||
if (this.isProd && !this.isNt && !this.isLinux) {
|
||||
if (this.isProd && !this.isDT && !this.isLinux) {
|
||||
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
|
||||
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
|
||||
}
|
||||
this.handleInitEnvParamSearch()
|
||||
this.handleInitEnvParamPlay()
|
||||
},
|
||||
// 处理启动参数 search
|
||||
handleInitEnvParamSearch() {
|
||||
if (this.envParams.search == null) return
|
||||
this.$router.push({
|
||||
path: 'search',
|
||||
query: {
|
||||
text: this.envParams.search,
|
||||
},
|
||||
})
|
||||
},
|
||||
// 处理启动参数 play
|
||||
handleInitEnvParamPlay() {
|
||||
if (this.envParams.play == null || typeof this.envParams.play != 'string') return
|
||||
// -play="source=kw&link=链接、ID"
|
||||
// -play="source=myList&name=名字"
|
||||
// -play="source=myList&name=名字&index=位置"
|
||||
const params = parseUrlParams(this.envParams.play)
|
||||
if (params.type != 'songList') return
|
||||
this.handlePlaySongList(params)
|
||||
},
|
||||
handlePlaySongList(params) {
|
||||
switch (params.source) {
|
||||
case 'myList':
|
||||
if (params.name != null) {
|
||||
let targetList
|
||||
const lists = Object.values(window.allList)
|
||||
for (const list of lists) {
|
||||
if (list.name === params.name) {
|
||||
targetList = list
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!targetList) return
|
||||
|
||||
if (this.envParams.search != null) {
|
||||
this.$router.push({
|
||||
path: 'search',
|
||||
query: {
|
||||
text: this.envParams.search,
|
||||
},
|
||||
})
|
||||
|
||||
this.setPlayList({
|
||||
list: {
|
||||
list: targetList.list,
|
||||
id: targetList.id,
|
||||
},
|
||||
index: this.getListPlayIndex(targetList.list, params.index),
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'kw':
|
||||
case 'kg':
|
||||
case 'tx':
|
||||
case 'mg':
|
||||
case 'wy':
|
||||
this.playSongListDetail(params.source, params.link, params.index)
|
||||
break
|
||||
}
|
||||
},
|
||||
handleXMVerifyModalClose() {
|
||||
music.xm.closeVerifyModal()
|
||||
async playSongListDetail(source, link, playIndex) {
|
||||
if (link == null) return
|
||||
let list
|
||||
try {
|
||||
list = await this.getListDetailAll({ source, id: decodeURIComponent(link) })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
this.setPlayList({
|
||||
list: {
|
||||
list,
|
||||
id: null,
|
||||
},
|
||||
index: this.getListPlayIndex(list, playIndex),
|
||||
})
|
||||
},
|
||||
getListPlayIndex(list, index) {
|
||||
if (index == null) {
|
||||
index = 1
|
||||
} else {
|
||||
index = parseInt(index)
|
||||
if (Number.isNaN(index)) {
|
||||
index = 1
|
||||
} else {
|
||||
if (index < 1) index = 1
|
||||
else if (index > list.length) index = list.length
|
||||
}
|
||||
}
|
||||
return index - 1
|
||||
},
|
||||
listenEvent() {
|
||||
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
|
||||
@@ -455,8 +623,17 @@ body {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.noTransparent {
|
||||
.disableTransparent {
|
||||
background-color: #fff;
|
||||
|
||||
#right {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
#view { // 偏移5px距离解决非透明模式下右侧滚动条无法拖动的问题
|
||||
margin-right: 5Px;
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
|
||||
35
src/renderer/assets/styles/animate.less
vendored
35
src/renderer/assets/styles/animate.less
vendored
@@ -871,6 +871,41 @@
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes hinge {
|
||||
0% {
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
20%,
|
||||
60% {
|
||||
-webkit-transform: rotate3d(0, 0, 1, 80deg);
|
||||
transform: rotate3d(0, 0, 1, 80deg);
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
40%,
|
||||
80% {
|
||||
-webkit-transform: rotate3d(0, 0, 1, 60deg);
|
||||
transform: rotate3d(0, 0, 1, 60deg);
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 700px, 0);
|
||||
transform: translate3d(0, 700px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flipInX {
|
||||
backface-visibility: visible !important;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.player")
|
||||
div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail")
|
||||
div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail" :tips="$t('core.player.pic_tip')")
|
||||
img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError")
|
||||
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve')
|
||||
use(:xlink:href='`#${$style.iconPic}`')
|
||||
@@ -34,7 +34,7 @@ div(:class="$style.player")
|
||||
div(:class="$style.titleBtn" @click='addMusicTo' :tips="$t('core.player.add_music_to')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='80%' viewBox='0 0 512 512' space='preserve')
|
||||
use(xlink:href='#icon-add-2')
|
||||
//- div(:class="$style.playBtn" @click='handleNext' tips="音量")
|
||||
//- div(:class="$style.playBtn" @click='playNext' tips="音量")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 291.063 291.064' space='preserve')
|
||||
use(xlink:href='#icon-sound')
|
||||
|
||||
@@ -49,7 +49,7 @@ div(:class="$style.player")
|
||||
span(style="margin: 0 5px;") /
|
||||
span {{maxPlayTimeStr}}
|
||||
div(:class="$style.right")
|
||||
div(:class="$style.playBtn" @click='handlePrev' :tips="$t('core.player.prev')" style="transform: rotate(180deg);")
|
||||
div(:class="$style.playBtn" @click='playPrev' :tips="$t('core.player.prev')" style="transform: rotate(180deg);")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
div(:class="$style.playBtn" :tips="isPlay ? $t('core.player.pause') : $t('core.player.play')" @click='togglePlay')
|
||||
@@ -57,7 +57,7 @@ div(:class="$style.player")
|
||||
use(xlink:href='#icon-pause')
|
||||
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
|
||||
use(xlink:href='#icon-play')
|
||||
div(:class="$style.playBtn" @click='handleNext' :tips="$t('core.player.next')")
|
||||
div(:class="$style.playBtn" @click='playNext' :tips="$t('core.player.next')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
//- transition(enter-active-class="animated lightSpeedIn"
|
||||
@@ -85,12 +85,11 @@ div(:class="$style.player")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Lyric from 'lrc-file-parser'
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
||||
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
|
||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { requestMsg } from '../../utils/message'
|
||||
import { isMac } from '../../../common/utils'
|
||||
import { player as eventPlayerNames } from '../../../common/hotKey'
|
||||
import path from 'path'
|
||||
|
||||
@@ -106,7 +105,6 @@ const playNextModes = [
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
volume: 0,
|
||||
nowPlayTime: 0,
|
||||
maxPlayTime: 0,
|
||||
@@ -122,7 +120,6 @@ export default {
|
||||
singer: '',
|
||||
album: '',
|
||||
},
|
||||
targetSong: null,
|
||||
pregessWidth: 0,
|
||||
lyric: {
|
||||
lines: [],
|
||||
@@ -132,7 +129,6 @@ export default {
|
||||
delayNextTimeout: null,
|
||||
restorePlayTime: 0,
|
||||
retryNum: 0,
|
||||
isMac,
|
||||
volumeEvent: {
|
||||
isMsDown: false,
|
||||
msDownX: 0,
|
||||
@@ -148,10 +144,19 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
...mapGetters('player', ['list', 'playIndex', 'changePlay', 'listId', 'isShowPlayerDetail', 'playedList']),
|
||||
...mapGetters('player', ['list', 'changePlay', 'playMusicInfo', 'isShowPlayerDetail', 'playInfo', 'playedList']),
|
||||
// pic() {
|
||||
// return this.musicInfo.img ? this.musicInfo.img : ''
|
||||
// },
|
||||
listId() { // 当前播放歌曲的列表ID
|
||||
return this.playInfo.listId
|
||||
},
|
||||
playIndex() { // 当前播放歌曲所在列表的 播放列表的播放位置
|
||||
return this.playInfo.playIndex
|
||||
},
|
||||
targetSong() {
|
||||
return this.playInfo.musicInfo
|
||||
},
|
||||
title() {
|
||||
return this.musicInfo.name
|
||||
? this.setting.download.fileName.replace('歌名', this.musicInfo.name).replace('歌手', this.musicInfo.singer)
|
||||
@@ -210,8 +215,9 @@ export default {
|
||||
singer: this.musicInfo.singer,
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
lyric: this.musicInfo.lrc,
|
||||
tlyric: this.musicInfo.tlrc,
|
||||
lrc: this.musicInfo.lrc,
|
||||
tlrc: this.musicInfo.tlrc,
|
||||
lxlrc: this.musicInfo.lxlrc,
|
||||
isPlay: this.isPlay,
|
||||
line: this.lyric.line,
|
||||
played_time: audio.currentTime * 1000,
|
||||
@@ -246,37 +252,19 @@ export default {
|
||||
if (!n) return
|
||||
this.resetChangePlay()
|
||||
if (window.restorePlayInfo) {
|
||||
let musicInfo = this.targetSong = this.list[window.restorePlayInfo.index]
|
||||
this.musicInfo.songmid = musicInfo.songmid
|
||||
this.musicInfo.singer = musicInfo.singer
|
||||
this.musicInfo.name = musicInfo.name
|
||||
this.musicInfo.album = musicInfo.albumName
|
||||
this.setImg(musicInfo)
|
||||
this.setLrc(musicInfo)
|
||||
this.nowPlayTime = this.restorePlayTime = window.restorePlayInfo.time
|
||||
this.maxPlayTime = window.restorePlayInfo.maxTime || 0
|
||||
this.handleUpdateWinLyricInfo('music_info', {
|
||||
songmid: this.musicInfo.songmid,
|
||||
singer: this.musicInfo.singer,
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
})
|
||||
this.handleRestorePlay(window.restorePlayInfo)
|
||||
window.restorePlayInfo = null
|
||||
return
|
||||
}
|
||||
// console.log('changePlay')
|
||||
this.handleRemoveMusic()
|
||||
if (this.playIndex < 0) return
|
||||
this.stopPlay()
|
||||
if (!this.playInfo.musicInfo) return
|
||||
this.play()
|
||||
},
|
||||
'setting.player.togglePlayMethod'(n) {
|
||||
audio.loop = n === 'singleLoop'
|
||||
if (this.playedList.length) this.clearPlayedList()
|
||||
if (n == 'random' && this.playIndex > -1) this.setPlayedList(this.list[this.playIndex])
|
||||
if (n == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
'setting.player.isMute'(n) {
|
||||
audio.muted = n
|
||||
@@ -287,6 +275,9 @@ export default {
|
||||
'setting.player.isShowLyricTransition'() {
|
||||
this.setLyric()
|
||||
},
|
||||
'setting.player.isPlayLxlrc'() {
|
||||
this.setLyric()
|
||||
},
|
||||
async list(n, o) {
|
||||
if (n === o && this.musicInfo.songmid) {
|
||||
let index = this.listId == 'download'
|
||||
@@ -295,13 +286,13 @@ export default {
|
||||
if (index < 0) {
|
||||
// console.log(this.playIndex)
|
||||
if (n.length) {
|
||||
this.fixPlayIndex(this.playIndex - 1)
|
||||
this.handleNext()
|
||||
this.setPlayIndex(this.playInfo.listPlayIndex - 1)
|
||||
this.playNext()
|
||||
} else {
|
||||
this.setPlayIndex(-1)
|
||||
this.setPlayMusicInfo(null)
|
||||
}
|
||||
} else {
|
||||
this.fixPlayIndex(index)
|
||||
this.setPlayIndex(index)
|
||||
}
|
||||
// console.log(this.playIndex)
|
||||
}
|
||||
@@ -315,26 +306,38 @@ export default {
|
||||
},
|
||||
nowPlayTime(n, o) {
|
||||
if (Math.abs(n - o) > 2) this.isActiveTransition = true
|
||||
this.savePlayInfo({
|
||||
time: n,
|
||||
maxTime: this.maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
if (this.setting.player.isSavePlayTime && !this.playInfo.isTempPlay) {
|
||||
this.savePlayInfo({
|
||||
time: n,
|
||||
maxTime: this.maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
maxPlayTime(maxPlayTime) {
|
||||
if (!this.playInfo.isTempPlay) {
|
||||
this.savePlayInfo({
|
||||
time: this.nowPlayTime,
|
||||
maxTime: maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
|
||||
...mapActions('player', ['getUrl', 'getPic', 'getLrc', 'playPrev', 'playNext']),
|
||||
...mapActions('list', ['getOtherSource']),
|
||||
...mapMutations('player', [
|
||||
'setPlayMusicInfo',
|
||||
'setPlayIndex',
|
||||
'fixPlayIndex',
|
||||
'resetChangePlay',
|
||||
'visiblePlayerDetail',
|
||||
'clearPlayedList',
|
||||
'setPlayedList',
|
||||
'removePlayedList',
|
||||
'setList',
|
||||
]),
|
||||
...mapMutations(['setVolume', 'setPlayNextMode', 'setVisibleDesktopLyric', 'setLockDesktopLyric']),
|
||||
...mapMutations('list', ['updateMusicInfo']),
|
||||
@@ -343,8 +346,8 @@ export default {
|
||||
let eventHub = window.eventHub
|
||||
let name = action == 'on' ? '$on' : '$off'
|
||||
eventHub[name](eventPlayerNames.toggle_play.action, this.togglePlay)
|
||||
eventHub[name](eventPlayerNames.next.action, this.handleNext)
|
||||
eventHub[name](eventPlayerNames.prev.action, this.handlePrev)
|
||||
eventHub[name](eventPlayerNames.next.action, this.playNext)
|
||||
eventHub[name](eventPlayerNames.prev.action, this.playPrev)
|
||||
eventHub[name](eventPlayerNames.volume_up.action, this.handleSetVolumeUp)
|
||||
eventHub[name](eventPlayerNames.volume_down.action, this.handleSetVolumeDown)
|
||||
eventHub[name](eventPlayerNames.volume_mute.action, this.handleSetVolumeMute)
|
||||
@@ -375,18 +378,19 @@ export default {
|
||||
console.log('播放完毕')
|
||||
this.stopPlay()
|
||||
this.status = this.statusText = this.$t('core.player.end')
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
})
|
||||
audio.addEventListener('error', () => {
|
||||
// console.log('code', audio.error)
|
||||
if (!this.musicInfo.songmid) return
|
||||
console.log('出错')
|
||||
this.stopPlay()
|
||||
this.clearLoadingTimeout()
|
||||
if (this.listId != 'download' && audio.error.code !== 1 && this.retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
|
||||
// console.log(this.retryNum)
|
||||
if (!this.restorePlayTime) this.restorePlayTime = audio.currentTime // 记录出错的播放时间
|
||||
this.retryNum++
|
||||
this.setUrl(this.list[this.playIndex], true)
|
||||
this.setUrl(this.targetSong, true)
|
||||
this.status = this.statusText = this.$t('core.player.refresh_url')
|
||||
return
|
||||
}
|
||||
@@ -404,7 +408,9 @@ export default {
|
||||
audio.currentTime = this.restorePlayTime
|
||||
this.restorePlayTime = 0
|
||||
}
|
||||
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
|
||||
if (!this.targetSong.interval && this.listId != 'download') {
|
||||
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) }, musicInfo: this.targetSong })
|
||||
}
|
||||
})
|
||||
audio.addEventListener('loadstart', () => {
|
||||
console.log('loadstart')
|
||||
@@ -450,6 +456,10 @@ export default {
|
||||
})
|
||||
|
||||
window.lrc = new Lyric({
|
||||
lineClassName: 'lrc-content',
|
||||
fontClassName: 'font',
|
||||
shadowContent: false,
|
||||
activeLineClassName: 'active',
|
||||
onPlay: (line, text) => {
|
||||
this.lyric.text = text
|
||||
this.lyric.line = line
|
||||
@@ -461,16 +471,17 @@ export default {
|
||||
this.lyric.lines = lines
|
||||
this.lyric.line = 0
|
||||
},
|
||||
offset: 80,
|
||||
// offset: 80,
|
||||
})
|
||||
|
||||
this.handleRegisterEvent('on')
|
||||
},
|
||||
async play() {
|
||||
console.log('play', this.playIndex)
|
||||
this.checkDelayNextTimeout()
|
||||
let targetSong = this.targetSong = this.list[this.playIndex]
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(targetSong)
|
||||
this.clearDelayNextTimeout()
|
||||
|
||||
const targetSong = this.targetSong
|
||||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
this.retryNum = 0
|
||||
this.restorePlayTime = 0
|
||||
|
||||
@@ -478,7 +489,7 @@ export default {
|
||||
const filePath = path.join(this.setting.download.savePath, targetSong.fileName)
|
||||
// console.log(filePath)
|
||||
if (!await checkPath(filePath) || !targetSong.isComplate || /\.ape$/.test(filePath)) {
|
||||
return this.list.length == 1 ? null : this.handleNext()
|
||||
return this.list.length == 1 ? null : this.playNext()
|
||||
}
|
||||
this.musicInfo.songmid = targetSong.musicInfo.songmid
|
||||
this.musicInfo.singer = targetSong.musicInfo.singer
|
||||
@@ -489,7 +500,7 @@ export default {
|
||||
this.setImg(targetSong.musicInfo)
|
||||
this.setLrc(targetSong.musicInfo)
|
||||
} else {
|
||||
if (!this.assertApiSupport(targetSong.source)) return this.handleNext()
|
||||
// if (!this.assertApiSupport(targetSong.source)) return this.playNext()
|
||||
this.musicInfo.songmid = targetSong.songmid
|
||||
this.musicInfo.singer = targetSong.singer
|
||||
this.musicInfo.name = targetSong.name
|
||||
@@ -504,8 +515,17 @@ export default {
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
})
|
||||
if (!this.playInfo.isTempPlay) {
|
||||
this.savePlayInfo({
|
||||
time: this.nowPlayTime,
|
||||
maxTime: this.maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
checkDelayNextTimeout() {
|
||||
clearDelayNextTimeout() {
|
||||
// console.log(this.delayNextTimeout)
|
||||
if (this.delayNextTimeout) {
|
||||
clearTimeout(this.delayNextTimeout)
|
||||
@@ -513,123 +533,13 @@ export default {
|
||||
}
|
||||
},
|
||||
addDelayNextTimeout() {
|
||||
this.checkDelayNextTimeout()
|
||||
this.clearDelayNextTimeout()
|
||||
this.delayNextTimeout = setTimeout(() => {
|
||||
this.delayNextTimeout = null
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
}, 5000)
|
||||
},
|
||||
async filterList() {
|
||||
// if (this.list.listName === null) return
|
||||
let list
|
||||
let playedList = [...this.playedList]
|
||||
if (this.listId == 'download') {
|
||||
list = []
|
||||
for (const item of this.list) {
|
||||
const filePath = path.join(this.setting.download.savePath, item.fileName)
|
||||
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
|
||||
|
||||
let index = playedList.indexOf(item)
|
||||
if (index > -1) {
|
||||
playedList.splice(index, 1)
|
||||
continue
|
||||
}
|
||||
list.push(item)
|
||||
}
|
||||
} else {
|
||||
list = this.list.filter(s => {
|
||||
let index = playedList.indexOf(s)
|
||||
if (index > -1) {
|
||||
playedList.splice(index, 1)
|
||||
return false
|
||||
}
|
||||
return this.assertApiSupport(s.source)
|
||||
})
|
||||
}
|
||||
if (!list.length && this.playedList.length) {
|
||||
this.clearPlayedList()
|
||||
return this.filterList()
|
||||
}
|
||||
return list
|
||||
},
|
||||
async handlePrev() {
|
||||
// console.log(playIndex)
|
||||
if (this.setting.player.togglePlayMethod == 'random' && this.playedList.length) {
|
||||
let index = this.playedList.indexOf(this.targetSong)
|
||||
index -= 1
|
||||
while (true) {
|
||||
if (index > -1) {
|
||||
let listIndex = this.list.indexOf(this.playedList[index])
|
||||
if (listIndex < 0) {
|
||||
this.removePlayedList(index)
|
||||
continue
|
||||
}
|
||||
this.setPlayIndex(listIndex)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
let list = await this.filterList()
|
||||
if (!list.length) return this.setPlayIndex(-1)
|
||||
let playIndex = list.indexOf(this.list[this.playIndex])
|
||||
let index
|
||||
switch (this.setting.player.togglePlayMethod) {
|
||||
case 'random':
|
||||
index = this.hanldeListRandom(list, playIndex)
|
||||
break
|
||||
case 'listLoop':
|
||||
case 'list':
|
||||
index = playIndex === 0 ? list.length - 1 : playIndex - 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
if (index < 0) return
|
||||
index = this.list.indexOf(list[index])
|
||||
this.setPlayIndex(index)
|
||||
},
|
||||
async handleNext() {
|
||||
// if (this.list.listName === null) return
|
||||
// eslint-disable-next-line no-debugger
|
||||
if (this.setting.player.togglePlayMethod == 'random' && this.playedList.length) {
|
||||
let index = this.playedList.indexOf(this.targetSong)
|
||||
index += 1
|
||||
while (true) {
|
||||
if (index < this.playedList.length) {
|
||||
let listIndex = this.list.indexOf(this.playedList[index])
|
||||
if (listIndex < 0) {
|
||||
this.removePlayedList(index)
|
||||
continue
|
||||
}
|
||||
this.setPlayIndex(listIndex)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
let list = await this.filterList()
|
||||
if (!list.length) return this.setPlayIndex(-1)
|
||||
let playIndex = list.indexOf(this.list[this.playIndex])
|
||||
// console.log(playIndex)
|
||||
let index
|
||||
switch (this.setting.player.togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
index = playIndex === list.length - 1 ? 0 : playIndex + 1
|
||||
break
|
||||
case 'random':
|
||||
index = this.hanldeListRandom(list, playIndex)
|
||||
break
|
||||
case 'list':
|
||||
index = playIndex === list.length - 1 ? -1 : playIndex + 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
if (index < 0) return
|
||||
index = this.list.indexOf(list[index])
|
||||
this.setPlayIndex(index)
|
||||
},
|
||||
hanldeListRandom(list, index) {
|
||||
return getRandom(0, list.length)
|
||||
},
|
||||
@@ -671,7 +581,7 @@ export default {
|
||||
togglePlay() {
|
||||
if (!audio.src) {
|
||||
if (this.restorePlayTime != null) {
|
||||
if (!this.assertApiSupport(this.targetSong.source)) return this.handleNext()
|
||||
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
|
||||
this.setUrl(this.targetSong)
|
||||
}
|
||||
return
|
||||
@@ -693,20 +603,37 @@ export default {
|
||||
if (highQuality && songInfo._types['320k'] && list && list.includes('320k')) type = '320k'
|
||||
return type
|
||||
},
|
||||
setUrl(targetSong, isRefresh, isRetryed = false) {
|
||||
setUrl(targetSong, isRefresh, isRetryed = false, retryedSource = [], originMusic = null) {
|
||||
if (!retryedSource.includes(targetSong.source)) retryedSource.push(targetSong.source)
|
||||
|
||||
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
|
||||
this.musicInfo.url = targetSong.typeUrl[type]
|
||||
this.status = this.statusText = this.$t('core.player.geting_url')
|
||||
|
||||
return this.getUrl({ musicInfo: targetSong, type, isRefresh }).then(() => {
|
||||
audio.src = this.musicInfo.url = targetSong.typeUrl[type]
|
||||
return this.getUrl({ musicInfo: targetSong, originMusic, type, isRefresh }).then(url => {
|
||||
if ((targetSong !== this.targetSong && originMusic !== this.targetSong) || this.isPlay) return
|
||||
audio.src = this.musicInfo.url = url
|
||||
}).catch(err => {
|
||||
// console.log('err', err.message)
|
||||
if (err.message == requestMsg.cancelRequest) return
|
||||
if (!isRetryed) return this.setUrl(targetSong, isRefresh, true)
|
||||
this.status = this.statusText = err.message
|
||||
this.addDelayNextTimeout()
|
||||
return Promise.reject(err)
|
||||
if (!isRetryed) return this.setUrl(targetSong, isRefresh, true, retryedSource, originMusic)
|
||||
if (!originMusic) originMusic = targetSong
|
||||
|
||||
this.status = this.statusText = 'Try toggle source...'
|
||||
|
||||
return this.getOtherSource(originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source) || !this.assertApiSupport(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return this.setUrl(item, isRefresh, false, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
this.status = this.statusText = err.message
|
||||
this.addDelayNextTimeout()
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
setImg(targetSong) {
|
||||
@@ -722,10 +649,11 @@ export default {
|
||||
this.getLrc(targetSong).then(() => {
|
||||
this.musicInfo.lrc = targetSong.lrc
|
||||
this.musicInfo.tlrc = targetSong.tlrc
|
||||
this.musicInfo.lxlrc = targetSong.lxlrc
|
||||
}).catch(() => {
|
||||
this.status = this.statusText = this.$t('core.player.lyric_error')
|
||||
}).finally(() => {
|
||||
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc })
|
||||
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc, lxlrc: this.musicInfo.lxlrc })
|
||||
this.setLyric()
|
||||
})
|
||||
},
|
||||
@@ -739,6 +667,7 @@ export default {
|
||||
this.musicInfo.songmid = null
|
||||
this.musicInfo.lrc = null
|
||||
this.musicInfo.tlrc = null
|
||||
this.musicInfo.lxlrc = null
|
||||
this.musicInfo.url = null
|
||||
this.nowPlayTime = 0
|
||||
this.maxPlayTime = 0
|
||||
@@ -803,13 +732,13 @@ export default {
|
||||
this.setProgressWidth()
|
||||
},
|
||||
handleToMusicLocation() {
|
||||
if (!this.listId || this.listId == 'download') return
|
||||
if (!this.listId || this.listId == '__temp__' || this.listId == 'download') return
|
||||
if (this.playIndex == -1) return
|
||||
this.$router.push({
|
||||
path: 'list',
|
||||
query: {
|
||||
id: this.listId,
|
||||
scrollIndex: this.playIndex,
|
||||
scrollIndex: this.playInfo.playIndex,
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -824,7 +753,7 @@ export default {
|
||||
startLoadingTimeout() {
|
||||
// console.log('start load timeout')
|
||||
this.loadingTimeout = setTimeout(() => {
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
}, 20000)
|
||||
},
|
||||
clearLoadingTimeout() {
|
||||
@@ -843,7 +772,7 @@ export default {
|
||||
if (skipTime > this.maxPlayTime) skipTime = (this.maxPlayTime - audio.currentTime) / 2
|
||||
if (skipTime - this.mediaBuffer.playTime < 1 || this.maxPlayTime - skipTime < 1) {
|
||||
this.mediaBuffer.playTime = 0
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
return
|
||||
}
|
||||
this.startBuffering()
|
||||
@@ -903,13 +832,13 @@ export default {
|
||||
handlePlayDetailAction({ type, data }) {
|
||||
switch (type) {
|
||||
case 'prev':
|
||||
this.handlePrev()
|
||||
this.playPrev()
|
||||
break
|
||||
case 'togglePlay':
|
||||
this.togglePlay()
|
||||
break
|
||||
case 'next':
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
break
|
||||
case 'progress':
|
||||
this.setProgress(data)
|
||||
@@ -929,7 +858,15 @@ export default {
|
||||
})
|
||||
},
|
||||
setLyric() {
|
||||
window.lrc.setLyric((this.setting.player.isShowLyricTransition && this.musicInfo.tlrc ? (this.musicInfo.tlrc + '\n') : '') + (this.musicInfo.lrc || ''))
|
||||
window.lrc.setLyric(
|
||||
this.setting.player.isPlayLxlrc && this.musicInfo.lxlrc ? this.musicInfo.lxlrc : this.musicInfo.lrc,
|
||||
this.setting.player.isShowLyricTransition && this.musicInfo.tlrc ? this.musicInfo.tlrc : '',
|
||||
// (
|
||||
// this.setting.player.isShowLyricTransition && this.musicInfo.tlrc
|
||||
// ? (this.musicInfo.tlrc + '\n')
|
||||
// : ''
|
||||
// ) + (this.musicInfo.lrc || ''),
|
||||
)
|
||||
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) {
|
||||
window.lrc.play(audio.currentTime * 1000)
|
||||
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
||||
@@ -950,6 +887,28 @@ export default {
|
||||
if (!this.musicInfo.songmid) return
|
||||
this.isShowAddMusicTo = true
|
||||
},
|
||||
handleRestorePlay(restorePlayInfo) {
|
||||
let musicInfo = this.list[restorePlayInfo.index]
|
||||
this.musicInfo.songmid = musicInfo.songmid
|
||||
this.musicInfo.singer = musicInfo.singer
|
||||
this.musicInfo.name = musicInfo.name
|
||||
this.musicInfo.album = musicInfo.albumName
|
||||
this.setImg(musicInfo)
|
||||
this.setLrc(musicInfo)
|
||||
this.nowPlayTime = this.restorePlayTime = restorePlayInfo.time
|
||||
this.maxPlayTime = restorePlayInfo.maxTime || 0
|
||||
this.handleUpdateWinLyricInfo('music_info', {
|
||||
songmid: this.musicInfo.songmid,
|
||||
singer: this.musicInfo.singer,
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
})
|
||||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
div(:class="$style.right")
|
||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="$style.lyricSpace")
|
||||
p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="[$style.lyricText]" ref="dom_lyric_text")
|
||||
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="$style.lyricSpace")
|
||||
|
||||
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
|
||||
@@ -158,11 +159,14 @@ export default {
|
||||
handler(n, o) {
|
||||
this.isSetedLines = true
|
||||
if (o) {
|
||||
this.$refs.dom_lyric_text.textContent = ''
|
||||
this.setLyric(n)
|
||||
|
||||
this._lyricLines = n
|
||||
if (n.length) {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
} else {
|
||||
@@ -174,7 +178,7 @@ export default {
|
||||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||
this.lyricLines = this._lyricLines
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}, 50)
|
||||
@@ -182,7 +186,7 @@ export default {
|
||||
} else {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}
|
||||
@@ -235,6 +239,8 @@ export default {
|
||||
document.addEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.addEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
// console.log('object', this.$refs.dom_lyric_text)
|
||||
this.setLyric(this.lyricLines)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearLyricScrollTimeout()
|
||||
@@ -250,6 +256,13 @@ export default {
|
||||
...mapMutations('player', [
|
||||
'visiblePlayerDetail',
|
||||
]),
|
||||
setLyric(lines) {
|
||||
const dom_lines = document.createDocumentFragment()
|
||||
for (const line of lines) {
|
||||
dom_lines.appendChild(line.dom_line)
|
||||
}
|
||||
this.$refs.dom_lyric_text.appendChild(dom_lines)
|
||||
},
|
||||
handleResize() {
|
||||
this.setProgressWidth()
|
||||
},
|
||||
@@ -540,6 +553,7 @@ export default {
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
@@ -565,16 +579,62 @@ export default {
|
||||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
cursor: grab;
|
||||
color: @color-theme_2-font;
|
||||
&.draging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
p {
|
||||
padding: 8px 0;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme !important;
|
||||
transition-property: color, font-size;
|
||||
:global {
|
||||
.lrc-content {
|
||||
line-height: 1.2;
|
||||
margin: 16px 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
.translation {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size, color;
|
||||
font-size: 1em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.line {
|
||||
transition-property: font-size, color !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: unset;
|
||||
// -webkit-text-fill-color: none !important;
|
||||
}
|
||||
&.active {
|
||||
.line {
|
||||
color: @color-theme;
|
||||
}
|
||||
.translation {
|
||||
font-size: 1.2em;
|
||||
color: @color-theme;
|
||||
}
|
||||
span {
|
||||
// color: @color-theme;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size !important;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(77, 77, 77, 0.9);
|
||||
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
}
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
@@ -736,6 +796,7 @@ each(@themes, {
|
||||
.container {
|
||||
border-left-color: ~'@{color-@{value}-theme}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
// color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.right {
|
||||
&:before {
|
||||
@@ -769,9 +830,24 @@ each(@themes, {
|
||||
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
|
||||
// border-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
.lrc-active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
.lyric {
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.line {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
span {
|
||||
// background-color: ~'@{color-@{value}-theme_2-font}';
|
||||
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-theme}', ~'@{color-@{value}-theme}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .lrc-active {
|
||||
// color: ~'@{color-@{value}-theme}';
|
||||
// }
|
||||
.footerLeftControlBtns {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.lists")
|
||||
h2(:class="$style.listsTitle") {{$t('core.aside.my_list')}}
|
||||
ul.scroll(:class="$style.listsContent")
|
||||
li(:class="$style.listsItem" v-for="item in lists" :key="item.id") {{item.name}}
|
||||
|
||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
div(v-show="!list.length" :class="$style.noitem")
|
||||
p(v-html="noItem")
|
||||
material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { scrollTo, clipboardWriteText, assertApiSupport } from '../../utils'
|
||||
export default {
|
||||
name: 'MaterialSongList',
|
||||
model: {
|
||||
prop: 'selectdData',
|
||||
event: 'input',
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
selectdData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
},
|
||||
noItem: {
|
||||
type: String,
|
||||
default: '列表加载中...',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
},
|
||||
watch: {
|
||||
selectdList(n) {
|
||||
const len = n.length
|
||||
if (len) {
|
||||
this.isShowEditBtn = true
|
||||
} else {
|
||||
this.isShowEditBtn = false
|
||||
}
|
||||
},
|
||||
selectdData(n) {
|
||||
const len = n.length
|
||||
if (len) {
|
||||
this.isShowEditBtn = true
|
||||
this.selectdList = [...n]
|
||||
} else {
|
||||
this.isShowEditBtn = false
|
||||
this.removeAllSelect()
|
||||
}
|
||||
},
|
||||
list(n) {
|
||||
this.removeAllSelect()
|
||||
if (!this.list.length) return
|
||||
this.$nextTick(() => scrollTo(this.$refs.dom_scrollContent, 0))
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clickTime: 0,
|
||||
clickIndex: -1,
|
||||
isShowEditBtn: false,
|
||||
selectdList: [],
|
||||
keyEvent: {
|
||||
isShiftDown: false,
|
||||
isModDown: false,
|
||||
isADown: false,
|
||||
aDownTimeout: null,
|
||||
},
|
||||
lastSelectIndex: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listenEvent()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unlistenEvent()
|
||||
},
|
||||
methods: {
|
||||
listenEvent() {
|
||||
window.eventHub.$on('key_shift_down', this.handle_key_shift_down)
|
||||
window.eventHub.$on('key_shift_up', this.handle_key_shift_up)
|
||||
window.eventHub.$on('key_mod_down', this.handle_key_mod_down)
|
||||
window.eventHub.$on('key_mod_up', this.handle_key_mod_up)
|
||||
window.eventHub.$on('key_mod+a_down', this.handle_key_mod_a_down)
|
||||
window.eventHub.$on('key_mod+a_up', this.handle_key_mod_a_up)
|
||||
},
|
||||
unlistenEvent() {
|
||||
window.eventHub.$off('key_shift_down', this.handle_key_shift_down)
|
||||
window.eventHub.$off('key_shift_up', this.handle_key_shift_up)
|
||||
window.eventHub.$off('key_mod_down', this.handle_key_mod_down)
|
||||
window.eventHub.$off('key_mod_up', this.handle_key_mod_up)
|
||||
window.eventHub.$off('key_mod+a_down', this.handle_key_mod_a_down)
|
||||
window.eventHub.$off('key_mod+a_up', this.handle_key_mod_a_up)
|
||||
},
|
||||
handle_key_shift_down() {
|
||||
if (!this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = true
|
||||
},
|
||||
handle_key_shift_up() {
|
||||
if (this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = false
|
||||
},
|
||||
handle_key_mod_down() {
|
||||
if (!this.keyEvent.isModDown) this.keyEvent.isModDown = true
|
||||
},
|
||||
handle_key_mod_up() {
|
||||
if (this.keyEvent.isModDown) this.keyEvent.isModDown = false
|
||||
},
|
||||
handle_key_mod_a_down() {
|
||||
if (!this.keyEvent.isADown) {
|
||||
this.keyEvent.isModDown = false
|
||||
this.keyEvent.isADown = true
|
||||
this.handleSelectAllData()
|
||||
if (this.keyEvent.aDownTimeout) clearTimeout(this.keyEvent.aDownTimeout)
|
||||
this.keyEvent.aDownTimeout = setTimeout(() => {
|
||||
this.keyEvent.aDownTimeout = null
|
||||
this.keyEvent.isADown = false
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
handle_key_mod_a_up() {
|
||||
if (this.keyEvent.isADown) {
|
||||
if (this.keyEvent.aDownTimeout) {
|
||||
clearTimeout(this.keyEvent.aDownTimeout)
|
||||
this.keyEvent.aDownTimeout = null
|
||||
}
|
||||
this.keyEvent.isADown = false
|
||||
}
|
||||
},
|
||||
handleDoubleClick(event, index) {
|
||||
if (event.target.classList.contains('select')) return
|
||||
|
||||
this.handleSelectData(event, index)
|
||||
|
||||
if (
|
||||
window.performance.now() - this.clickTime > 400 ||
|
||||
this.clickIndex !== index
|
||||
) {
|
||||
this.clickTime = window.performance.now()
|
||||
this.clickIndex = index
|
||||
return
|
||||
}
|
||||
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
|
||||
this.clickTime = 0
|
||||
this.clickIndex = -1
|
||||
},
|
||||
handleSelectData(event, clickIndex) {
|
||||
if (this.keyEvent.isShiftDown) {
|
||||
if (this.selectdList.length) {
|
||||
let lastSelectIndex = this.lastSelectIndex
|
||||
this.removeAllSelect()
|
||||
if (lastSelectIndex != clickIndex) {
|
||||
let isNeedReverse = false
|
||||
if (clickIndex < lastSelectIndex) {
|
||||
let temp = lastSelectIndex
|
||||
lastSelectIndex = clickIndex
|
||||
clickIndex = temp
|
||||
isNeedReverse = true
|
||||
}
|
||||
this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) this.selectdList.reverse()
|
||||
let nodes = this.$refs.dom_tbody.childNodes
|
||||
do {
|
||||
nodes[lastSelectIndex].classList.add('active')
|
||||
lastSelectIndex++
|
||||
} while (lastSelectIndex <= clickIndex)
|
||||
}
|
||||
} else {
|
||||
event.currentTarget.classList.add('active')
|
||||
this.selectdList.push(this.list[clickIndex])
|
||||
this.lastSelectIndex = clickIndex
|
||||
}
|
||||
} else if (this.keyEvent.isModDown) {
|
||||
this.lastSelectIndex = clickIndex
|
||||
let item = this.list[clickIndex]
|
||||
let index = this.selectdList.indexOf(item)
|
||||
if (index < 0) {
|
||||
this.selectdList.push(item)
|
||||
event.currentTarget.classList.add('active')
|
||||
} else {
|
||||
this.selectdList.splice(index, 1)
|
||||
event.currentTarget.classList.remove('active')
|
||||
}
|
||||
} else if (this.selectdList.length) {
|
||||
this.removeAllSelect()
|
||||
} else return
|
||||
this.$emit('input', [...this.selectdList])
|
||||
},
|
||||
removeAllSelect() {
|
||||
this.selectdList = []
|
||||
let dom_tbody = this.$refs.dom_tbody
|
||||
if (!dom_tbody) return
|
||||
let nodes = dom_tbody.querySelectorAll('.active')
|
||||
for (const node of nodes) {
|
||||
if (node.parentNode == dom_tbody) node.classList.remove('active')
|
||||
}
|
||||
},
|
||||
handleListBtnClick(info) {
|
||||
this.emitEvent('listBtnClick', info)
|
||||
},
|
||||
handleSelectAllData() {
|
||||
this.removeAllSelect()
|
||||
this.selectdList = [...this.list]
|
||||
let nodes = this.$refs.dom_tbody.childNodes
|
||||
for (const node of nodes) {
|
||||
node.classList.add('active')
|
||||
}
|
||||
this.$emit('input', [...this.selectdList])
|
||||
},
|
||||
handleTogglePage(page) {
|
||||
this.emitEvent('togglePage', page)
|
||||
},
|
||||
handleFlowBtnClick(action) {
|
||||
this.emitEvent('flowBtnClick', action)
|
||||
},
|
||||
emitEvent(action, data) {
|
||||
this.$emit('action', { action, data })
|
||||
},
|
||||
handleChangeSelect() {
|
||||
this.$emit('input', [...this.selectdList])
|
||||
},
|
||||
handleContextMenu(event) {
|
||||
if (!event.target.classList.contains('select')) return
|
||||
let classList = this.$refs.dom_scrollContent.classList
|
||||
classList.add(this.$style.copying)
|
||||
window.requestAnimationFrame(() => {
|
||||
let str = window.getSelection().toString()
|
||||
classList.remove(this.$style.copying)
|
||||
str = str.trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
})
|
||||
},
|
||||
assertApiSupport(source) {
|
||||
return assertApiSupport(source)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
.lists {
|
||||
flex: none;
|
||||
width: 15%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.title {
|
||||
font-size: 12px;
|
||||
line-height: 28px;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
flex: none;
|
||||
}
|
||||
.list {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.item {
|
||||
|
||||
}
|
||||
.noitem {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.tbody {
|
||||
td {
|
||||
&:first-child {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
button(:class="[$style.btn, min ? $style.min : null]" :disabled="disabled" @click="$emit('click', $event)")
|
||||
button(:class="[$style.btn, min ? $style.min : null, outline ? $style.outline : null]" :disabled="disabled" @click="$emit('click', $event)")
|
||||
slot
|
||||
</template>
|
||||
|
||||
@@ -9,6 +9,10 @@ export default {
|
||||
min: {
|
||||
type: Boolean,
|
||||
},
|
||||
outline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -36,6 +40,10 @@ export default {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
@@ -54,6 +62,9 @@ each(@themes, {
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
input(:class="$style.input" :type="type" :placeholder="placeholder" :value="value" :disabled="disabled"
|
||||
@focus="$emit('focus', $event)" @blur="$emit('blur', $event)" @input="$emit('input', $event.target.value.trim())" @change="$emit('change', $event.target.value.trim())"
|
||||
input(:class="$style.input" ref="dom_input" :type="type" :placeholder="placeholder" :value="value" :disabled="disabled"
|
||||
@focus="$emit('focus', $event)" @blur="$emit('blur', $event)" @input="handleInput" @change="$emit('change', $event.target.value.trim())"
|
||||
@keyup.enter="$emit('submit', $event.target.value.trim())")
|
||||
</template>
|
||||
|
||||
@@ -24,6 +24,16 @@ export default {
|
||||
default: 'text',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleInput(event) {
|
||||
let value = event.target.value.trim()
|
||||
event.target.value = value
|
||||
this.$emit('input', value)
|
||||
},
|
||||
focus() {
|
||||
this.$refs.dom_input.focus()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -41,6 +51,13 @@ export default {
|
||||
transition: background-color 0.2s ease;
|
||||
background-color: @color-btn-background;
|
||||
font-size: 13.3px;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
||||
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
|
||||
| {{$t('material.list_add_modal.title_last')}}
|
||||
div.scroll(:class="$style.btnContent")
|
||||
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
|
||||
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
|
||||
material-btn(:class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('view.list.lists_new_list_btn')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 42 42' space='preserve')
|
||||
use(xlink:href='#icon-addTo')
|
||||
@@ -57,11 +57,12 @@ export default {
|
||||
computed: {
|
||||
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
|
||||
lists() {
|
||||
if (!this.musicInfo) return []
|
||||
return [
|
||||
this.defaultList,
|
||||
this.loveList,
|
||||
...this.userList,
|
||||
].filter(l => l.id != this.excludeListId.includes(l.id))
|
||||
].filter(l => !this.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: l.list.some(s => s.songmid == this.musicInfo.songmid) }))
|
||||
},
|
||||
spaceNum() {
|
||||
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
this.defaultList,
|
||||
this.loveList,
|
||||
...this.userList,
|
||||
].filter(l => l.id != this.excludeListId.includes(l.id))
|
||||
].filter(l => !this.excludeListId.includes(l.id))
|
||||
},
|
||||
spaceNum() {
|
||||
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
||||
121
src/renderer/components/material/ListSortModal.vue
Normal file
121
src/renderer/components/material/ListSortModal.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="show" @close="handleClose")
|
||||
main(:class="$style.main")
|
||||
h2 {{selectedNum > 0 ? $t('material.list_sort_modal.title_multiple', { num: selectedNum }) : $t('material.list_sort_modal.title', { name: musicInfo ? musicInfo.name : '' })}}
|
||||
material-input(:class="$style.input" type="number" v-model="sortNum" ref="input" @blur.native="verify" :placeholder="$t('material.list_sort_modal.input_tip')" @keydown.native.enter="handleSubmit")
|
||||
div(:class="$style.footer")
|
||||
material-btn(:class="$style.btn" @click="handleSubmit") {{$t('material.list_sort_modal.btn_confirm')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
musicInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
selectedNum: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sortNum: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if (n) {
|
||||
this.sortNum = ''
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
verify() {
|
||||
let num = /^[1-9]\d*/.exec(this.sortNum)
|
||||
num = num ? parseInt(num[0]) : ''
|
||||
this.sortNum = num.toString()
|
||||
return num
|
||||
},
|
||||
handleSubmit() {
|
||||
let num = this.verify()
|
||||
if (this.sortNum == '') return
|
||||
this.$emit('confirm', num)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 0 15px;
|
||||
max-width: 530px;
|
||||
min-width: 280px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
// text-align: center;
|
||||
padding: 15px 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
// width: 100%;
|
||||
// height: 26px;
|
||||
padding: 8px 8px;
|
||||
}
|
||||
.footer {
|
||||
margin: 20px 0 15px auto;
|
||||
}
|
||||
.btn {
|
||||
// box-sizing: border-box;
|
||||
// margin-left: 15px;
|
||||
// margin-bottom: 15px;
|
||||
// height: 36px;
|
||||
// line-height: 36px;
|
||||
// padding: 0 10px !important;
|
||||
min-width: 70px;
|
||||
// .mixin-ellipsis-1;
|
||||
|
||||
+.btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -138,10 +138,10 @@ export default {
|
||||
// will-change: transform;
|
||||
li {
|
||||
cursor: pointer;
|
||||
min-width: 80px;
|
||||
min-width: 96px;
|
||||
line-height: 34px;
|
||||
// color: @color-btn;
|
||||
padding: 0 5px;
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: @transition-theme;
|
||||
|
||||
@@ -74,9 +74,9 @@ export default {
|
||||
'slideOutLeft',
|
||||
'slideOutRight',
|
||||
'slideOutUp',
|
||||
'hinge',
|
||||
// 'hinge',
|
||||
],
|
||||
inClass: 'animated flipInX',
|
||||
inClass: 'animated jackInTheBox',
|
||||
outClass: 'animated flipOutX',
|
||||
unwatchFn: null,
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
singer: '',
|
||||
},
|
||||
page: 1,
|
||||
total: 10,
|
||||
total: 0,
|
||||
maxPage: 1,
|
||||
limit: 20,
|
||||
isHotLoading: true,
|
||||
|
||||
@@ -31,8 +31,6 @@ div(:class="$style.songList")
|
||||
td(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
||||
material-list-buttons(:index="index" :class="$style.btns"
|
||||
:remove-btn="false" @btn-click="handleListBtnClick"
|
||||
:listAdd-btn="assertApiSupport(item.source)"
|
||||
:play-btn="assertApiSupport(item.source)"
|
||||
:download-btn="assertApiSupport(item.source)")
|
||||
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
|
||||
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
|
||||
@@ -114,6 +112,11 @@ export default {
|
||||
action: 'download',
|
||||
disabled: !this.listMenu.itemMenuControl.download,
|
||||
},
|
||||
{
|
||||
name: this.$t('material.song_list.list_play_later'),
|
||||
action: 'playLater',
|
||||
disabled: !this.listMenu.itemMenuControl.playLater,
|
||||
},
|
||||
{
|
||||
name: this.$t('material.song_list.list_search'),
|
||||
action: 'search',
|
||||
@@ -173,6 +176,7 @@ export default {
|
||||
itemMenuControl: {
|
||||
play: true,
|
||||
addTo: true,
|
||||
playLater: true,
|
||||
download: true,
|
||||
search: true,
|
||||
sourceDetail: true,
|
||||
@@ -237,7 +241,7 @@ export default {
|
||||
this.clickIndex = index
|
||||
return
|
||||
}
|
||||
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
|
||||
this.emitEvent('testPlay', index)
|
||||
this.clickTime = 0
|
||||
this.clickIndex = -1
|
||||
},
|
||||
@@ -334,8 +338,9 @@ export default {
|
||||
},
|
||||
handleListItemRigthClick(event, index) {
|
||||
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
|
||||
this.listMenu.itemMenuControl.play =
|
||||
this.listMenu.itemMenuControl.download =
|
||||
// this.listMenu.itemMenuControl.play =
|
||||
// this.listMenu.itemMenuControl.playLater =
|
||||
this.listMenu.itemMenuControl.download =
|
||||
this.assertApiSupport(this.list[index].source)
|
||||
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
|
||||
if (dom_selected) dom_selected.classList.remove('selected')
|
||||
|
||||
240
src/renderer/components/material/UserApiModal.vue
Normal file
240
src/renderer/components/material/UserApiModal.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="visible" bg-close @close="handleClose")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('material.user_api_modal.title')}}
|
||||
ul.scroll(v-if="apiList.length" :class="$style.content")
|
||||
li(:class="[$style.listItem, setting.apiSource == api.id ? $style.active : null]" v-for="(api, index) in apiList" :key="api.id")
|
||||
div(:class="$style.listLeft")
|
||||
h3 {{api.name}}
|
||||
p {{api.description}}
|
||||
material-btn(:class="$style.listBtn" outline :tips="$t('material.user_api_modal.btn_remove')" @click.stop="handleRemove(index)")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once)
|
||||
use(xlink:href='#icon-delete')
|
||||
div(v-else :class="$style.content")
|
||||
div(:class="$style.noitem") {{$t('material.user_api_modal.noitem')}}
|
||||
div(:class="$style.note")
|
||||
p(:class="[$style.ruleLink]")
|
||||
| {{$t('material.user_api_modal.readme')}}
|
||||
span.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')" tips="https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md") FAQ.md
|
||||
p {{$t('material.user_api_modal.note')}}
|
||||
div(:class="$style.footer")
|
||||
material-btn(:class="$style.footerBtn" @click="handleImport") {{$t('material.user_api_modal.btn_import')}}
|
||||
//- material-btn(:class="$style.footerBtn" @click="handleExport") {{$t('material.user_api_modal.btn_export')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { rendererInvoke, NAMES } from '@common/ipc'
|
||||
import { promises as fsPromises } from 'fs'
|
||||
import {
|
||||
selectDir,
|
||||
openUrl,
|
||||
} from '../../utils'
|
||||
import apiSourceInfo from '../../utils/music/api-source-info'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'toggle',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
globalObj: window.globalObj,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
apiList() {
|
||||
return this.globalObj.userApi.list
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleImport() {
|
||||
selectDir({
|
||||
title: this.$t('material.user_api_modal.import_file'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'LX API File', extensions: ['js'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
return fsPromises.readFile(result.filePaths[0]).then(data => {
|
||||
return rendererInvoke(NAMES.mainWindow.import_user_api, data.toString()).then(({ apiList }) => {
|
||||
window.globalObj.userApi.list = apiList
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
handleExport() {
|
||||
|
||||
},
|
||||
async handleRemove(index) {
|
||||
const api = this.apiList[index]
|
||||
if (!api) return
|
||||
if (this.setting.apiSource == api.id) {
|
||||
let backApi = apiSourceInfo.find(api => !api.disabled)
|
||||
if (backApi) window.globalObj.apiSource = backApi.id
|
||||
}
|
||||
window.globalObj.userApi.list = await rendererInvoke(NAMES.mainWindow.remove_user_api, [api.id])
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit('toggle', false)
|
||||
},
|
||||
handleOpenUrl(url) {
|
||||
openUrl(url)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
color: @color-theme;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
min-height: 100px;
|
||||
max-height: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.listItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease;
|
||||
padding: 10px;
|
||||
border-radius: @radius-border;
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
color: @color-theme_2-font;
|
||||
word-break: break-all;
|
||||
}
|
||||
p {
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
color: @color-theme_2-font-label;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
height: 100px;
|
||||
font-size: 18px;
|
||||
color: @color-theme_2-font-label;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.listLeft {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.listBtn {
|
||||
flex: none;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
svg {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
.note {
|
||||
margin-top: 15px;
|
||||
font-size: 12px;
|
||||
line-height: 1.25;
|
||||
color: @color-theme_2-font;
|
||||
p {
|
||||
+ p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.footerBtn {
|
||||
flex: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding: 0 10px !important;
|
||||
width: 150px;
|
||||
.mixin-ellipsis-1;
|
||||
+ .footerBtn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
.ruleLink {
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.listItem {
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
h3 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -3,17 +3,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
||||
main(:class="$style.main" v-if="version.isDownloaded")
|
||||
h2 🚀程序更新🚀
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 最新版本:{{version.newVersion.version}}
|
||||
h3 当前版本:{{version.version}}
|
||||
h3 版本变化:
|
||||
p(:class="$style.desc" v-html="version.newVersion.desc")
|
||||
pre(:class="$style.desc" v-text="version.newVersion.desc")
|
||||
div(:class="[$style.history, $style.desc]" v-if="history.length")
|
||||
h3 历史版本:
|
||||
div(:class="$style.item" v-for="ver in history")
|
||||
h4 v{{ver.version}}
|
||||
p(v-html="ver.desc")
|
||||
pre(v-text="ver.desc")
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.desc")
|
||||
p 新版本已下载完毕,
|
||||
@@ -27,17 +27,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
||||
main(:class="$style.main" v-else-if="version.isError && !version.isUnknow && version.newVersion.version != version.version")
|
||||
h2 ❌ 版本更新出错 ❌
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 最新版本:{{version.newVersion.version}}
|
||||
h3 当前版本:{{version.version}}
|
||||
h3 版本变化:
|
||||
p(:class="$style.desc" v-html="version.newVersion.desc")
|
||||
pre(:class="$style.desc" v-text="version.newVersion.desc")
|
||||
div(:class="[$style.history, $style.desc]" v-if="history.length")
|
||||
h3 历史版本:
|
||||
div(:class="$style.item" v-for="ver in history")
|
||||
h4 v{{ver.version}}
|
||||
p(v-html="ver.desc")
|
||||
pre(v-text="ver.desc")
|
||||
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.desc")
|
||||
@@ -58,7 +58,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
||||
main(:class="$style.main" v-else-if="version.isDownloading && version.isTimeOut && !version.isUnknow")
|
||||
h2 ❗️ 新版本下载超时 ❗️
|
||||
div(:class="$style.desc")
|
||||
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),建议手动更新版本!
|
||||
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),你仍可选择继续等,但墙裂建议手动更新版本!
|
||||
p
|
||||
| 你可以去
|
||||
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
|
||||
@@ -75,7 +75,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
||||
main(:class="$style.main" v-else-if="version.isUnknow")
|
||||
h2 ❓ 获取最新版本信息失败 ❓
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 当前版本:{{version.version}}
|
||||
div(:class="$style.desc")
|
||||
@@ -94,17 +94,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
||||
main(:class="$style.main" v-else)
|
||||
h2 🌟发现新版本🌟
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 最新版本:{{version.newVersion.version}}
|
||||
h3 当前版本:{{version.version}}
|
||||
h3 版本变化:
|
||||
p(:class="$style.desc" v-html="version.newVersion.desc")
|
||||
pre(:class="$style.desc" v-text="version.newVersion.desc")
|
||||
div(:class="[$style.history, $style.desc]" v-if="history.length")
|
||||
h3 历史版本:
|
||||
div(:class="$style.item" v-for="ver in history")
|
||||
h4 v{{ver.version}}
|
||||
p(v-html="ver.desc")
|
||||
pre(v-text="ver.desc")
|
||||
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.desc")
|
||||
@@ -146,7 +146,7 @@ export default {
|
||||
progress() {
|
||||
return this.version.downloadProgress
|
||||
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
|
||||
: '初始化中...'
|
||||
: '处理更新中...'
|
||||
},
|
||||
isIgnored() {
|
||||
return this.setting.ignoreVersion == this.version.newVersion.version
|
||||
@@ -207,6 +207,11 @@ export default {
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
text-align: justify;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
||||
main.ignore-to-rem(:class="$style.main")
|
||||
h2 {{$t('material.xm_verify_modal.title')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
bgClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
background: #fff !important;
|
||||
&:global(.ignore-to-rem) {
|
||||
padding: 15px;
|
||||
width: 360px;
|
||||
height: 330px;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -14,7 +14,7 @@
|
||||
"buffering": "Buffering...",
|
||||
"geting_url": "Getting music link...",
|
||||
"lyric_error": "Failed to get lyrics",
|
||||
"hide_detail": "Hide detail page",
|
||||
"hide_detail": "Hide detail page (Right-click in the view to quickly hide the details page)",
|
||||
"name": "Name: ",
|
||||
"singer": "Artist: ",
|
||||
"album": "Album: ",
|
||||
@@ -28,6 +28,7 @@
|
||||
"play_toggle_mode_list": "Play in order",
|
||||
"play_toggle_mode_single_loop": "Single Loop",
|
||||
"play_toggle_mode_off": "Disable",
|
||||
"pic_tip": "Right click to locate the currently playing song in \"My List\"",
|
||||
|
||||
"comment_show": "Song comments",
|
||||
"comment_hot_loading": "Hot comments are loading",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"lock": "Lock Lyrics",
|
||||
"unlock": "Unlock Lyrics",
|
||||
"theme": "Theme Color",
|
||||
"font_increase": "Increase font size",
|
||||
"font_decrease": "Reduce font size",
|
||||
"opactiy_increase": "Decrease Transparency",
|
||||
"opactiy_decrease": "Increase Transparency",
|
||||
"font_increase": "Increase font size (Right click to fine-tune)",
|
||||
"font_decrease": "Reduce font size (Right click to fine-tune)",
|
||||
"opactiy_increase": "Decrease Transparency (Right click to fine-tune)",
|
||||
"opactiy_decrease": "Increase Transparency (Right click to fine-tune)",
|
||||
"lrc_active_zoom_on": "Zoom the currently playing lyrics",
|
||||
"lrc_active_zoom_off": "Unzoom the currently playing lyrics",
|
||||
"win_top_on": "Top lyrics interface",
|
||||
|
||||
6
src/renderer/lang/en-us/material/list_sort_modal.json
Normal file
6
src/renderer/lang/en-us/material/list_sort_modal.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"title": "Adjust the position of {name} to:",
|
||||
"title_multiple": "Adjust the position of the selected {num} songs to:",
|
||||
"input_tip": "Please input which position you want to adjust to",
|
||||
"btn_confirm": "Confirm"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_download": "Download",
|
||||
"list_search": "Search",
|
||||
|
||||
10
src/renderer/lang/en-us/material/user_api_modal.json
Normal file
10
src/renderer/lang/en-us/material/user_api_modal.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"title": "Custom Source Management",
|
||||
"readme": "Source writing instructions: ",
|
||||
"note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.",
|
||||
"btn_remove": "Remove",
|
||||
"btn_import": "Import",
|
||||
"btn_export": "Export",
|
||||
"import_file": "Select music API script file",
|
||||
"noitem": "There is nothing here...😲"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"menu_play": "Play",
|
||||
"menu_play_later": "Play later",
|
||||
"menu_start": "Start task",
|
||||
"menu_pause": "Pause Task",
|
||||
"menu_file": "Locate File",
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
"lists_sync": "Sync",
|
||||
"lists_remove": "Remove",
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_copy_name": "Copy name",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_move_to": "Move to ...",
|
||||
"list_sort": "Adjust position",
|
||||
"list_download": "Download",
|
||||
"list_search": "Search",
|
||||
"list_remove": "Remove",
|
||||
"list_source_detail": "Song Page",
|
||||
"default_list": "Recently Played",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_download": "Download",
|
||||
"list_source_detail": "Song Page",
|
||||
@@ -10,6 +11,7 @@
|
||||
"time": "Length",
|
||||
"lossless": "SQ",
|
||||
"high_quality": "HQ",
|
||||
"loding_list": "Loading...",
|
||||
"no_item": "Search what I want to 😉",
|
||||
"hot_search": "Top Searches",
|
||||
"history_search": "History Searches",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"basic": "General",
|
||||
"basic_theme": "Theme",
|
||||
"basic_show_animation": "Show switching animation",
|
||||
"basic_animation_title": "Animation effect of the pop-up layer",
|
||||
"basic_animation": "Random pop-up animation",
|
||||
"basic_source_title": "Choose a music source",
|
||||
"basic_source_test": "Test API (Available for most software features)",
|
||||
@@ -12,6 +11,10 @@
|
||||
"basic_sourcename_real": "Original",
|
||||
"basic_sourcename_alias": "Aliases",
|
||||
"basic_sourcename": "Source name",
|
||||
"basic_source_status_success": "Initialization successful",
|
||||
"basic_source_status_initing": "Initializing",
|
||||
"basic_source_status_failed": "Initialization failed",
|
||||
"basic_source_user_api_btn": "Custom Source Management",
|
||||
"basic_window_size_title": "Set the window size",
|
||||
"basic_window_size": "Window size",
|
||||
"basic_window_size_smaller": "Smaller",
|
||||
@@ -21,8 +24,7 @@
|
||||
"basic_window_size_larger": "Larger",
|
||||
"basic_window_size_oversized": "Oversized",
|
||||
"basic_window_size_huge": "Huge",
|
||||
"basic_to_tray_title": "Minimize it to the system tray without closing the software when closing",
|
||||
"basic_to_tray": "Minimize to system tray when closing",
|
||||
"basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
|
||||
"basic_lang_title": "The language displayed in the software",
|
||||
"basic_lang": "Language",
|
||||
"basic_control_btn_position": "Control Button Position",
|
||||
@@ -30,21 +32,14 @@
|
||||
"basic_control_btn_position_right": "Right",
|
||||
|
||||
"play": "Play",
|
||||
"play_toggle_title": "If none selected, it stopped when the music playing is done.",
|
||||
"play_toggle": "Playback mode",
|
||||
"play_toggle_list_loop": "Playlist repeat",
|
||||
"play_toggle_random": "Playlist shuffle",
|
||||
"play_toggle_list": "Play in order",
|
||||
"play_toggle_single_loop": "Single repeat",
|
||||
"play_save_play_time": "Remember playback progress",
|
||||
"play_lyric_transition": "Show lyrics translation",
|
||||
"play_quality_title": "The 320k quality is preferred for playing",
|
||||
"play_quality": "Prefer High Quality 320k",
|
||||
"play_task_bar_title": "Show playing progress on the taskbar",
|
||||
"play_task_bar": "Taskbar play progress bar",
|
||||
"play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
|
||||
"play_quality": "Play 320K quality songs first (if supported)",
|
||||
"play_task_bar": "Show playing progress on the taskbar",
|
||||
"play_mediaDevice_title": "Select a media device for audio output",
|
||||
"play_mediaDevice": "Audio output",
|
||||
"play_mediaDevice_remove_stop_play": "Whether to pause playback when the audio output device is changed",
|
||||
"play_mediaDevice_remove_stop_play_title": "Whether to pause the song when the current sound output device is changed",
|
||||
"play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
|
||||
|
||||
"desktop_lyric": "Desktop Lyric Settings",
|
||||
"desktop_lyric_enable": "Display lyrics",
|
||||
@@ -53,18 +48,13 @@
|
||||
"desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen",
|
||||
|
||||
"search": "Search",
|
||||
"search_hot_title": "Select whether to show popular searches",
|
||||
"search_hot": "Top Searches",
|
||||
"search_history_title": "Select whether to show search history",
|
||||
"search_history": "Search history",
|
||||
"search_focus_search_box_title": "Whether the search box is automatically focused on startup",
|
||||
"search_focus_search_box": "Whether the search box is focused on startup",
|
||||
"search_focus_search_box": "Automatically focus the search box on startup",
|
||||
|
||||
"list": "List",
|
||||
"list_source_title": "Select whether to show music source",
|
||||
"list_source": "Select whether to show music source (for Your Library only)",
|
||||
"list_scroll_title": "Select whether to remember the playlist scrollbar position",
|
||||
"list_scroll": "Remember playlist scrolling position (for Your library only)",
|
||||
"list_source": "Show song source (only valid for my music category)",
|
||||
"list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
|
||||
|
||||
"download": "Download",
|
||||
"download_enable": "Whether to enable download function",
|
||||
@@ -136,10 +126,11 @@
|
||||
"other_tray_theme": "Tray Icon Style",
|
||||
"other_tray_theme_native": "Solid Color",
|
||||
"other_tray_theme_origin": "Primary Color",
|
||||
"other_cache": "Cache size (Not recommended since resources such as pictures after the cache is cleaned need re-downloading. The software will dynamically manage the cache size based on disk space)",
|
||||
"other_cache_label": "Cache size used: ",
|
||||
"other_cache_label_title": "Currently used cache size",
|
||||
"other_cache_clear_btn": "Clear cache",
|
||||
"other_resource_cache": "Resource cache management (pictures, audios and other caches, pictures and other resources will need to be downloaded again after cleaning up, it is not recommended to clean up, the software will dynamically manage the cache size according to the disk space)",
|
||||
"other_resource_cache_label": "The software has used cache size: ",
|
||||
"other_resource_cache_clear_btn": "Clear resource cache",
|
||||
"other_play_list_cache": "List cache management (links to songs that have been cached in my list, alternative sources for playback, after cleaning up, you need to re-acquire them when you play and download songs, and do not clean up if there are no issues related to song playback)",
|
||||
"other_play_list_cache_clear_btn": "Clear list cache information",
|
||||
|
||||
"update": "Update",
|
||||
"update_latest_label": "Latest version: ",
|
||||
@@ -150,7 +141,7 @@
|
||||
"update_latest": "The software is up-to-date, enjoy yourself!🥂",
|
||||
"update_open_version_modal_btn": "Open the update window🚀",
|
||||
"update_checking": "Checking for updates...",
|
||||
"update_init": "Initializing update...",
|
||||
"update_init": "Processing update...",
|
||||
|
||||
"about": "About lx-music-desktop",
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"tip_2": "If you encounter a link to a playlist that cannot be opened, welcome feedback",
|
||||
"tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening",
|
||||
"play_all": "Play",
|
||||
"play_later": "Play later",
|
||||
"add_all": "Collect"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"buffering": "缓冲中...",
|
||||
"geting_url": "歌曲链接获取中...",
|
||||
"lyric_error": "歌词获取失败",
|
||||
"hide_detail": "隐藏详情页",
|
||||
"hide_detail": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
|
||||
"name": "歌曲名:",
|
||||
"singer": "艺术家:",
|
||||
"album": "专辑名:",
|
||||
@@ -28,6 +28,7 @@
|
||||
"play_toggle_mode_list": "顺序播放",
|
||||
"play_toggle_mode_single_loop": "单曲循环",
|
||||
"play_toggle_mode_off": "禁用",
|
||||
"pic_tip": "右击在“我的列表”定位当前播放的歌曲",
|
||||
|
||||
"comment_show": "歌曲评论",
|
||||
"comment_hot_loading": "热门评论加载中",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"lock": "锁定歌词",
|
||||
"unlock": "解锁歌词",
|
||||
"theme": "主题配色",
|
||||
"font_increase": "增加字体大小",
|
||||
"font_decrease": "减小字体大小",
|
||||
"opactiy_increase": "减小透明度",
|
||||
"opactiy_decrease": "增加透明度",
|
||||
"font_increase": "增加字体大小(右击可微调)",
|
||||
"font_decrease": "减小字体大小(右击可微调)",
|
||||
"opactiy_increase": "减小透明度(右击可微调)",
|
||||
"opactiy_decrease": "增加透明度(右击可微调)",
|
||||
"lrc_active_zoom_on": "缩放当前播放的歌词",
|
||||
"lrc_active_zoom_off": "取消缩放当前播放的歌词",
|
||||
"win_top_on": "置顶歌词界面",
|
||||
|
||||
6
src/renderer/lang/zh-cn/material/list_sort_modal.json
Normal file
6
src/renderer/lang/zh-cn/material/list_sort_modal.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"title": "将 {name} 的位置调整到:",
|
||||
"title_multiple": "将已选的 {num} 首歌曲的位置调整到:",
|
||||
"input_tip": "请输入要调整到第几个位置",
|
||||
"btn_confirm": "确定"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下载",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
|
||||
10
src/renderer/lang/zh-cn/material/user_api_modal.json
Normal file
10
src/renderer/lang/zh-cn/material/user_api_modal.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"title": "自定义源管理",
|
||||
"readme": "源编写说明:",
|
||||
"note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
|
||||
"btn_remove": "移除",
|
||||
"btn_import": "导入",
|
||||
"btn_export": "导出",
|
||||
"import_file": "选择音乐API脚本文件",
|
||||
"noitem": "这里竟然是空的 😲"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"menu_play": "播放",
|
||||
"menu_play_later": "稍后播放",
|
||||
"menu_start": "开始任务",
|
||||
"menu_pause": "暂停任务",
|
||||
"menu_file": "定位文件",
|
||||
|
||||
@@ -7,11 +7,14 @@
|
||||
"lists_sync": "同步",
|
||||
"lists_remove": "删除",
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_copy_name": "复制歌曲名",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
"list_add_to": "添加到...",
|
||||
"list_move_to": "移动到...",
|
||||
"list_sort": "调整位置",
|
||||
"list_download": "下载",
|
||||
"list_search": "搜索",
|
||||
"list_remove": "删除",
|
||||
"default_list": "试听列表",
|
||||
"love_list": "收藏",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下载",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
@@ -10,6 +11,7 @@
|
||||
"time": "时长",
|
||||
"lossless": "无损",
|
||||
"high_quality": "高品质",
|
||||
"loding_list": "加载中...",
|
||||
"no_item": "搜我所想~~😉",
|
||||
"hot_search": "热门搜索",
|
||||
"history_search": "历史搜索",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"basic": "基本设置",
|
||||
"basic_theme": "主题颜色",
|
||||
"basic_animation_title": "弹出层的动画效果",
|
||||
"basic_animation": "弹出层随机动画",
|
||||
"basic_show_animation": "显示切换动画",
|
||||
"basic_source_title": "选择音乐来源",
|
||||
@@ -12,6 +11,10 @@
|
||||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_alias": "别名",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_failed": "初始化失败",
|
||||
"basic_source_user_api_btn": "自定义源管理",
|
||||
"basic_window_size_title": "设置软件窗口尺寸",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_smaller": "较小",
|
||||
@@ -21,8 +24,7 @@
|
||||
"basic_window_size_larger": "较大",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_to_tray_title": "关闭时不退出软件将其最小化到系统托盘",
|
||||
"basic_to_tray": "关闭时最小化到系统托盘",
|
||||
"basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
|
||||
"basic_lang_title": "软件显示的语言",
|
||||
"basic_lang": "语言",
|
||||
"basic_control_btn_position": "控制按钮位置",
|
||||
@@ -30,21 +32,14 @@
|
||||
"basic_control_btn_position_right": "右边",
|
||||
|
||||
"play": "播放设置",
|
||||
"play_toggle_title": "都不选时播放完当前歌曲就停止播放",
|
||||
"play_toggle": "歌曲切换方式",
|
||||
"play_toggle_list_loop": "列表循环",
|
||||
"play_toggle_random": "列表随机",
|
||||
"play_toggle_list": "顺序播放",
|
||||
"play_toggle_single_loop": "单曲循环",
|
||||
"play_save_play_time": "记住播放进度",
|
||||
"play_lyric_transition": "显示歌词翻译",
|
||||
"play_quality_title": "启用时将优先播放320K品质的歌曲",
|
||||
"play_quality": "优先播放高品质音乐",
|
||||
"play_task_bar_title": "在任务栏上显示当前歌曲播放进度",
|
||||
"play_task_bar": "任务栏播放进度条",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
|
||||
"play_quality": "优先播放320K品质的歌曲(如果支持)",
|
||||
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
||||
"play_mediaDevice_title": "选择声音输出的媒体设备",
|
||||
"play_mediaDevice": "音频输出",
|
||||
"play_mediaDevice_remove_stop_play": "音频输出设备被改变时是否暂停播放",
|
||||
"play_mediaDevice_remove_stop_play_title": "当前的声音输出设备被改变时是否暂停播放歌曲",
|
||||
"play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
|
||||
|
||||
"desktop_lyric": "桌面歌词设置",
|
||||
"desktop_lyric_enable": "显示歌词",
|
||||
@@ -53,18 +48,13 @@
|
||||
"desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外",
|
||||
|
||||
"search": "搜索设置",
|
||||
"search_hot_title": "是否显示热门搜索",
|
||||
"search_hot": "热门搜索",
|
||||
"search_history_title": "是否显示历史搜索记录",
|
||||
"search_history": "搜索历史",
|
||||
"search_focus_search_box_title": "启动时是否自动聚焦搜索框",
|
||||
"search_focus_search_box": "启动时是否聚焦搜索框",
|
||||
"search_hot": "显示热门搜索",
|
||||
"search_history": "显示历史搜索记录",
|
||||
"search_focus_search_box": "启动时自动聚焦搜索框",
|
||||
|
||||
"list": "列表设置",
|
||||
"list_source_title": "是否显示歌曲源",
|
||||
"list_source": "是否显示歌曲源(仅对我的音乐分类有效)",
|
||||
"list_scroll_title": "是否记住播放列表滚动条位置",
|
||||
"list_scroll": "记住列表滚动位置(仅对我的音乐分类有效)",
|
||||
"list_source": "显示歌曲源(仅对我的音乐分类有效)",
|
||||
"list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
|
||||
|
||||
"download": "下载设置",
|
||||
"download_enable": "是否启用下载功能",
|
||||
@@ -136,10 +126,11 @@
|
||||
"other_tray_theme": "托盘图标样式",
|
||||
"other_tray_theme_native": "纯色",
|
||||
"other_tray_theme_origin": "原色",
|
||||
"other_cache": "缓存大小(清理缓存后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||
"other_cache_label": "软件已使用缓存大小:",
|
||||
"other_cache_label_title": "当前已用缓存",
|
||||
"other_cache_clear_btn": "清理缓存",
|
||||
"other_resource_cache": "资源缓存管理(图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||
"other_resource_cache_label": "软件已使用缓存大小:",
|
||||
"other_resource_cache_clear_btn": "清理资源缓存",
|
||||
"other_play_list_cache": "列表缓存管理(我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表缓存信息",
|
||||
|
||||
"update": "软件更新",
|
||||
"update_latest_label": "最新版本:",
|
||||
@@ -150,7 +141,7 @@
|
||||
"update_latest": "软件已是最新,尽情地体验吧~🥂",
|
||||
"update_open_version_modal_btn": "打开更新窗口 🚀",
|
||||
"update_checking": "检查更新中...",
|
||||
"update_init": "更新初始化中...",
|
||||
"update_init": "处理更新中...",
|
||||
|
||||
"about": "关于洛雪音乐",
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
|
||||
"tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
|
||||
"play_all": "播放",
|
||||
"play_later": "稍后播放",
|
||||
"add_all": "收藏"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"buffering": "緩衝中...",
|
||||
"geting_url": "歌曲鏈接獲取中...",
|
||||
"lyric_error": "歌詞獲取失敗",
|
||||
"hide_detail": "隱藏詳情頁",
|
||||
"hide_detail": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
|
||||
"name": "歌曲名:",
|
||||
"singer": "藝術家:",
|
||||
"album": "專輯名:",
|
||||
@@ -28,6 +28,7 @@
|
||||
"play_toggle_mode_list": "順序播放",
|
||||
"play_toggle_mode_single_loop": "單曲循環",
|
||||
"play_toggle_mode_off": "禁用",
|
||||
"pic_tip": "右擊在“我的列表”定位當前播放的歌曲",
|
||||
|
||||
"comment_show": "歌曲評論",
|
||||
"comment_hot_loading": "熱門評論加載中",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"lock": "鎖定歌詞",
|
||||
"unlock": "解鎖歌詞",
|
||||
"theme": "主題配色",
|
||||
"font_increase": "增加字體大小",
|
||||
"font_decrease": "減小字體大小",
|
||||
"opactiy_increase": "減小透明度",
|
||||
"opactiy_decrease": "增加透明度",
|
||||
"font_increase": "增加字體大小(右擊可微調)",
|
||||
"font_decrease": "減小字體大小(右擊可微調)",
|
||||
"opactiy_increase": "減小透明度(右擊可微調)",
|
||||
"opactiy_decrease": "增加透明度(右擊可微調)",
|
||||
"lrc_active_zoom_on": "縮放當前播放的歌詞",
|
||||
"lrc_active_zoom_off": "取消縮放當前播放的歌詞",
|
||||
"win_top_on": "置頂歌詞界面",
|
||||
|
||||
6
src/renderer/lang/zh-tw/material/list_sort_modal.json
Normal file
6
src/renderer/lang/zh-tw/material/list_sort_modal.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"title": "將 {name} 的位置調整到:",
|
||||
"title_multiple": "將已選的 {num} 首歌曲的位置調整到:",
|
||||
"input_tip": "請輸入要調整到第幾個位置",
|
||||
"btn_confirm": "確定"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍後播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下載",
|
||||
"list_search": "搜索",
|
||||
|
||||
10
src/renderer/lang/zh-tw/material/user_api_modal.json
Normal file
10
src/renderer/lang/zh-tw/material/user_api_modal.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"title": "自定義源管理",
|
||||
"readme": "源編寫說明:",
|
||||
"note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
|
||||
"btn_remove": "移除",
|
||||
"btn_import": "導入",
|
||||
"btn_export": "導出",
|
||||
"import_file": "選擇音樂API腳本文件",
|
||||
"noitem": "這裡竟然是空的 😲"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"menu_play": "播放",
|
||||
"menu_play_later": "稍後播放",
|
||||
"menu_start": "開始任務",
|
||||
"menu_pause": "暫停任務",
|
||||
"menu_file": "定位文件",
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
"lists_sync": "同步",
|
||||
"lists_remove": "刪除",
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍後播放",
|
||||
"list_copy_name": "複製歌曲名",
|
||||
"list_add_to": "添加到...",
|
||||
"list_move_to": "移動到...",
|
||||
"list_sort": "調整位置",
|
||||
"list_download": "下載",
|
||||
"list_search": "搜索",
|
||||
"list_remove": "刪除",
|
||||
"list_source_detail": "歌曲詳情頁",
|
||||
"default_list": "試聽列表",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍後播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下載",
|
||||
"list_source_detail": "歌曲詳情頁",
|
||||
@@ -10,6 +11,7 @@
|
||||
"time": "時長",
|
||||
"lossless": "無損",
|
||||
"high_quality": "高品質",
|
||||
"loding_list": "加載中...",
|
||||
"no_item": "搜我所想~~😉",
|
||||
"hot_search": "熱門搜索",
|
||||
"history_search": "歷史搜索",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"basic": "基本設置",
|
||||
"basic_theme": "主題顏色",
|
||||
"basic_animation_title": "彈出層的動畫效果",
|
||||
"basic_animation": "彈出層隨機動畫",
|
||||
"basic_show_animation": "顯示切換動畫",
|
||||
"basic_source_title": "選擇音樂來源",
|
||||
@@ -12,6 +11,10 @@
|
||||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_alias": "別名",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_failed": "初始化失敗",
|
||||
"basic_source_user_api_btn": "自定義源管理",
|
||||
"basic_window_size_title": "設置軟件窗口尺寸",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_smaller": "較小",
|
||||
@@ -21,46 +24,38 @@
|
||||
"basic_window_size_larger": "較大",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_to_tray_title": "關閉時不退出軟件將其最小化到系統托盤",
|
||||
"basic_to_tray": "關閉時最小化到系統托盤",
|
||||
"basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤",
|
||||
"basic_lang_title": "軟件顯示的語言",
|
||||
"basic_lang": "語言",
|
||||
"basic_control_btn_position": "控制按鈕位置",
|
||||
"basic_control_btn_position_left": "左邊",
|
||||
"basic_control_btn_position_right": "右邊",
|
||||
|
||||
"play": "播放設置",
|
||||
"play_toggle_title": "都不選時播放完當前歌曲就停止播放",
|
||||
"play_toggle": "歌曲切換方式",
|
||||
"play_toggle_list_loop": "列表循環",
|
||||
"play_toggle_random": "列表隨機",
|
||||
"play_toggle_list": "順序播放",
|
||||
"play_toggle_single_loop": "單曲循環",
|
||||
"play_save_play_time": "記住播放進度",
|
||||
"play_lyric_transition": "顯示歌詞翻譯",
|
||||
"play_quality_title": "啟用時將優先播放320K品質的歌曲",
|
||||
"play_quality": "優先播放高品質音樂",
|
||||
"play_task_bar_title": "在任務欄上顯示當前歌曲播放進度",
|
||||
"play_task_bar": "任務欄播放進度條",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
|
||||
"play_quality": "優先播放320K品質的歌曲(如果支持)",
|
||||
"play_task_bar": "在任務欄上顯示當前歌曲播放進度",
|
||||
"play_mediaDevice_title": "選擇聲音輸出的媒體設備",
|
||||
"play_mediaDevice": "音頻輸出",
|
||||
"play_mediaDevice_remove_stop_play": "音頻輸出設備被改變時是否暫停播放",
|
||||
"play_mediaDevice_remove_stop_play_title": "當前的聲音輸出設備被改變時是否暫停播放歌曲",
|
||||
"play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
|
||||
|
||||
"desktop_lyric": "桌面歌詞設置",
|
||||
"desktop_lyric_enable": "顯示歌詞",
|
||||
"desktop_lyric_lock": "鎖定歌詞",
|
||||
"desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
|
||||
"desktop_lyric_lock_screen": "不允許歌詞窗口拖出主屏幕之外",
|
||||
|
||||
"search": "搜索設置",
|
||||
"search_hot_title": "是否顯示熱門搜索",
|
||||
"search_hot": "熱門搜索",
|
||||
"search_history_title": "是否顯示歷史搜索記錄",
|
||||
"search_history": "搜索歷史",
|
||||
"search_focus_search_box_title": "啟動時是否自動聚焦搜索框",
|
||||
"search_focus_search_box": "啟動時是否聚焦搜索框",
|
||||
"search_hot": "顯示熱門搜索",
|
||||
"search_history": "顯示歷史搜索記錄",
|
||||
"search_focus_search_box": "啟動時自動聚焦搜索框",
|
||||
|
||||
"list": "列表設置",
|
||||
"list_source_title": "是否顯示歌曲源",
|
||||
"list_source": "是否顯示歌曲源(僅對我的音樂分類有效)",
|
||||
"list_scroll_title": "是否記住播放列表滾動條位置",
|
||||
"list_scroll": "記住列表滾動位置(僅對我的音樂分類有效)",
|
||||
"list_source": "顯示歌曲源(僅對我的音樂分類有效)",
|
||||
"list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
|
||||
|
||||
"download": "下載設置",
|
||||
"download_enable": "是否啟用下載功能",
|
||||
"download_path_title": "下載歌曲保存的路徑",
|
||||
@@ -79,6 +74,7 @@
|
||||
"download_name2": "歌手 - 歌名",
|
||||
"download_name3": "歌名",
|
||||
"download_select_save_path": "選擇歌曲保存路徑",
|
||||
|
||||
"hot_key": "快捷鍵設置",
|
||||
"hot_key_local_title": "軟件內快捷鍵",
|
||||
"hot_key_global_title": "全局快捷鍵",
|
||||
@@ -98,15 +94,18 @@
|
||||
"hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
|
||||
"hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
|
||||
|
||||
"network": "網絡設置",
|
||||
"network_proxy_title": "HTTP代理設置(亂設置軟件將無法聯網)",
|
||||
"network_proxy_host": "主機",
|
||||
"network_proxy_port": "端口",
|
||||
"network_proxy_username": "用戶名",
|
||||
"network_proxy_password": "密碼",
|
||||
|
||||
"odc": "強迫症設置",
|
||||
"odc_clear_search_input": "離開搜索界面時清空搜索框",
|
||||
"odc_clear_search_list": "離開搜索界面時清空搜索列表",
|
||||
|
||||
"backup": "備份與恢復",
|
||||
"backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自定義列表,設置數據不包括快捷鍵設置)",
|
||||
"backup_part_import_list": "導入列表",
|
||||
@@ -121,15 +120,18 @@
|
||||
"backup_part_import_setting_desc": "選擇配置文件",
|
||||
"backup_part_export_setting_desc": "選擇設置保存位置",
|
||||
"backup_part_import_list_desc": "選擇列表文件",
|
||||
"backup_part_export_list_desc": "選擇列表保存位置",
|
||||
"backup_part_export_list_desc": "選擇歌單保存位置",
|
||||
|
||||
"other": "其他",
|
||||
"other_tray_theme": "托盤圖標樣式",
|
||||
"other_tray_theme_native": "純色",
|
||||
"other_tray_theme_origin": "原色",
|
||||
"other_cache": "緩存大小(清理緩存後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
|
||||
"other_cache_label": "軟件已使用緩存大小:",
|
||||
"other_cache_label_title": "當前已用緩存",
|
||||
"other_cache_clear_btn": "清理緩存",
|
||||
"other_resource_cache": "資源緩存管理(圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
|
||||
"other_resource_cache_label": "軟件已使用緩存大小:",
|
||||
"other_resource_cache_clear_btn": "清理資源緩存",
|
||||
"other_play_list_cache": "列表緩存管理(我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表緩存信息",
|
||||
|
||||
"update": "軟件更新",
|
||||
"update_latest_label": "最新版本:",
|
||||
"update_unknown": "未知",
|
||||
@@ -139,8 +141,11 @@
|
||||
"update_latest": "軟件已是最新,盡情地體驗吧~🥂",
|
||||
"update_open_version_modal_btn": "打開更新窗口 🚀",
|
||||
"update_checking": "檢查更新中...",
|
||||
"update_init": "更新初始化中...",
|
||||
"update_init": "處理更新中...",
|
||||
|
||||
"about": "關於洛雪音樂",
|
||||
|
||||
|
||||
"is_enable": "是否啟用",
|
||||
"is_show": "是否顯示",
|
||||
"click_open": "點擊打開",
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"tip_2": "若遇到無法打開的歌單鏈接,歡迎反饋",
|
||||
"tip_3": "酷狗源不支持用歌單ID打開,但支持酷狗碼打開",
|
||||
"play_all": "播放",
|
||||
"play_later": "稍後播放",
|
||||
"add_all": "收藏"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,21 @@ import Tips from './Tips.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
const TipsConstructor = Vue.extend(Tips)
|
||||
export default ({ position, message } = {}) => {
|
||||
|
||||
const addAutoCloseTimer = (instance, time) => {
|
||||
if (!time) return
|
||||
if (instance.autoCloseTimer) clearTimeout(instance.autoCloseTimer)
|
||||
instance.autoCloseTimer = setTimeout(() => {
|
||||
instance.cancel()
|
||||
}, time)
|
||||
}
|
||||
const clearAutoCloseTimer = instance => {
|
||||
if (!instance.autoCloseTimer) return
|
||||
clearTimeout(instance.autoCloseTimer)
|
||||
instance.autoCloseTimer = null
|
||||
}
|
||||
|
||||
export default ({ position, message, autoCloseTime } = {}) => {
|
||||
if (!position) return
|
||||
let instance = new TipsConstructor().$mount(document.createElement('div'))
|
||||
|
||||
@@ -17,14 +31,19 @@ export default ({ position, message } = {}) => {
|
||||
document.body.appendChild(instance.$el)
|
||||
|
||||
instance.cancel = () => {
|
||||
instance.$emit('beforeClose', instance)
|
||||
clearAutoCloseTimer(instance)
|
||||
instance.visible = false
|
||||
instance = null
|
||||
}
|
||||
|
||||
instance.setTips = tips => {
|
||||
addAutoCloseTimer(instance, autoCloseTime)
|
||||
instance.message = tips
|
||||
}
|
||||
|
||||
addAutoCloseTimer(instance, autoCloseTime)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export default {
|
||||
transform: 'translate(0, 0)',
|
||||
cancel: null,
|
||||
setTips: null,
|
||||
aotoCloseTimer: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -26,9 +27,13 @@ export default {
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
const el = this.$el
|
||||
el.parentNode.removeChild(el)
|
||||
},
|
||||
methods: {
|
||||
afterLeave(el, done) {
|
||||
el.parentNode.removeChild(el)
|
||||
this.$destroy()
|
||||
},
|
||||
handleGetOffsetXY(left, top) {
|
||||
const tipsWidth = this.$refs.dom_tips.clientWidth
|
||||
|
||||
@@ -4,11 +4,14 @@ import { debounce } from '../../utils'
|
||||
let instance
|
||||
let prevTips
|
||||
|
||||
const getTips = el => el.getAttribute('tips')
|
||||
? el.getAttribute('tips')
|
||||
: el.parentNode === document.documentElement
|
||||
? null
|
||||
: getTips(el.parentNode)
|
||||
const getTips = el =>
|
||||
el
|
||||
? el.getAttribute('tips')
|
||||
? el.getAttribute('tips')
|
||||
: el.parentNode === document.documentElement
|
||||
? null
|
||||
: getTips(el.parentNode)
|
||||
: null
|
||||
|
||||
const showTips = debounce(event => {
|
||||
let msg = getTips(event.target)
|
||||
@@ -16,18 +19,22 @@ const showTips = debounce(event => {
|
||||
prevTips = msg
|
||||
instance = tips({
|
||||
message: msg,
|
||||
autoCloseTime: 10000,
|
||||
position: {
|
||||
top: event.y + 12,
|
||||
left: event.x + 8,
|
||||
},
|
||||
})
|
||||
instance.$on('beforeClose', closeInstance => {
|
||||
if (instance !== closeInstance) return
|
||||
prevTips = null
|
||||
instance = null
|
||||
})
|
||||
}, 400)
|
||||
|
||||
const hideTips = () => {
|
||||
if (!instance) return
|
||||
instance.cancel()
|
||||
prevTips = null
|
||||
instance = null
|
||||
}
|
||||
|
||||
const setTips = tips => {
|
||||
@@ -36,7 +43,7 @@ const setTips = tips => {
|
||||
}
|
||||
|
||||
const updateTips = event => {
|
||||
if (!instance) return
|
||||
if (!instance) return showTips(event)
|
||||
setTimeout(() => {
|
||||
let msg = getTips(event.target)
|
||||
if (!msg || prevTips === msg) return
|
||||
|
||||
@@ -19,16 +19,31 @@ export default {
|
||||
},
|
||||
getVersionInfo2(state, retryNum = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpGet('https://cdn.stsky.cn/lx-music/desktop/version.json', {
|
||||
httpGet('https://gitee.com/lyswhut/lx-music-desktop-versions/raw/master/version.json', {
|
||||
timeout: 20000,
|
||||
}, (err, resp, body) => {
|
||||
if (!err && !body.version) err = new Error(JSON.stringify(body))
|
||||
if (err) {
|
||||
return ++retryNum > 3
|
||||
? reject(err)
|
||||
? this.dispatch('getVersionInfo3').then(resolve).catch(reject)
|
||||
: this.dispatch('getVersionInfo2', retryNum).then(resolve).catch(reject)
|
||||
}
|
||||
resolve(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
getVersionInfo3(state, retryNum = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpGet('https://cdn.stsky.cn/lx-music/desktop/version.json', {
|
||||
timeout: 20000,
|
||||
}, (err, resp, body) => {
|
||||
if (err) {
|
||||
return ++retryNum > 3
|
||||
? reject(err)
|
||||
: this.dispatch('getVersionInfo3', retryNum).then(resolve).catch(reject)
|
||||
}
|
||||
resolve(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import musicSdk from '../../utils/music'
|
||||
|
||||
let allList = {}
|
||||
window.allList = allList
|
||||
|
||||
@@ -48,7 +50,12 @@ const getters = {
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
|
||||
getOtherSource({ state, commit }, musicInfo) {
|
||||
return (musicInfo.otherSource && musicInfo.otherSource.length ? Promise.resolve(musicInfo.otherSource) : musicSdk.findMusic(musicInfo)).then(otherSource => {
|
||||
commit('setOtherSource', { musicInfo, otherSource })
|
||||
return otherSource
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// mitations
|
||||
@@ -142,9 +149,9 @@ const mutations = {
|
||||
if (!targetList) return
|
||||
targetList.list.splice(0, targetList.list.length)
|
||||
},
|
||||
updateMusicInfo(state, { id, index, data }) {
|
||||
updateMusicInfo(state, { id, index, data, musicInfo = {} }) {
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
if (!targetList) return Object.assign(musicInfo, data)
|
||||
Object.assign(targetList.list[index], data)
|
||||
},
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId }) {
|
||||
@@ -185,6 +192,28 @@ const mutations = {
|
||||
setListScroll(state, { id, location }) {
|
||||
if (allList[id]) allList[id].location = location
|
||||
},
|
||||
sortList(state, { id, sortNum, musicInfos }) {
|
||||
let targetList = allList[id]
|
||||
this.commit('list/listRemoveMultiple', { id, list: musicInfos })
|
||||
|
||||
targetList.list.splice(sortNum - 1, 0, ...musicInfos)
|
||||
},
|
||||
clearCache() {
|
||||
const lists = Object.values(allList)
|
||||
for (const { list } of lists) {
|
||||
for (const item of list) {
|
||||
if (item.otherSource) item.otherSource = null
|
||||
if (item.typeUrl['128k']) delete item.typeUrl['128k']
|
||||
if (item.typeUrl['320k']) delete item.typeUrl['320k']
|
||||
if (item.typeUrl.flac) delete item.typeUrl.flac
|
||||
if (item.typeUrl.wav) delete item.typeUrl.wav
|
||||
// if (item.lxlrc == '') item.lxlrc = null
|
||||
}
|
||||
}
|
||||
},
|
||||
setOtherSource(state, { musicInfo, otherSource }) {
|
||||
musicInfo.otherSource = otherSource
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import path from 'path'
|
||||
import music from '../../utils/music'
|
||||
import { getRandom, checkPath } from '../../utils'
|
||||
|
||||
// state
|
||||
const state = {
|
||||
@@ -10,25 +12,142 @@ const state = {
|
||||
changePlay: false,
|
||||
isShowPlayerDetail: false,
|
||||
playedList: [],
|
||||
|
||||
playMusicInfo: null,
|
||||
tempPlayList: [],
|
||||
}
|
||||
|
||||
let urlRequest
|
||||
let picRequest
|
||||
let lrcRequest
|
||||
// let picRequest
|
||||
// let lrcRequest
|
||||
|
||||
const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
||||
// if (this.list.listName === null) return
|
||||
let list
|
||||
let canPlayList = []
|
||||
const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo)
|
||||
if (listInfo.id == 'download') {
|
||||
list = []
|
||||
for (const item of listInfo.list) {
|
||||
const filePath = path.join(savePath, item.fileName)
|
||||
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
|
||||
|
||||
canPlayList.push(item)
|
||||
|
||||
// 排除已播放音乐
|
||||
let index = filteredPlayedList.indexOf(item)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
continue
|
||||
}
|
||||
list.push(item)
|
||||
}
|
||||
} else {
|
||||
list = listInfo.list.filter(s => {
|
||||
// if (!assertApiSupport(s.source)) return false
|
||||
canPlayList.push(s)
|
||||
|
||||
let index = filteredPlayedList.indexOf(s)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (!list.length && playedList.length) {
|
||||
commit('clearPlayedList')
|
||||
return canPlayList
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
||||
// console.log(musicInfo.source)
|
||||
return music[musicInfo.source].getPic(musicInfo).promise.catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', musicInfo).then(otherSource => {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return getPic.call(this, item, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
return music[musicInfo.source].getLyric(musicInfo).promise.catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', musicInfo).then(otherSource => {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return getLyric.call(this, item, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
list: state => state.listInfo.list,
|
||||
listId: state => state.listInfo.id,
|
||||
changePlay: satte => satte.changePlay,
|
||||
playIndex: state => state.playIndex,
|
||||
playInfo(state) {
|
||||
if (state.playMusicInfo == null) return { listId: null, playIndex: -1, playListId: null, listPlayIndex: -1, isPlayList: false, musicInfo: null }
|
||||
const playListId = state.listInfo.id
|
||||
let listId = state.playMusicInfo.listId
|
||||
const isTempPlay = !!state.playMusicInfo.isTempPlay
|
||||
const isPlayList = listId === playListId
|
||||
let playIndex = -1
|
||||
let listPlayIndex = state.playIndex
|
||||
|
||||
if (listId != '__temp__') {
|
||||
if (isPlayList) {
|
||||
playIndex = state.listInfo.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
if (!isTempPlay) listPlayIndex = playIndex
|
||||
} else {
|
||||
let list = window.allList[listId]
|
||||
if (list) playIndex = list.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
}
|
||||
}
|
||||
// console.log({
|
||||
// listId,
|
||||
// playIndex,
|
||||
// playListId,
|
||||
// listPlayIndex,
|
||||
// isPlayList,
|
||||
// isTempPlay,
|
||||
// musicInfo: state.playMusicInfo.musicInfo,
|
||||
// })
|
||||
return {
|
||||
listId,
|
||||
playIndex,
|
||||
playListId,
|
||||
listPlayIndex,
|
||||
isPlayList,
|
||||
isTempPlay,
|
||||
musicInfo: state.playMusicInfo.musicInfo,
|
||||
}
|
||||
},
|
||||
isShowPlayerDetail: state => state.isShowPlayerDetail,
|
||||
playMusicInfo: state => state.playMusicInfo,
|
||||
playedList: state => state.playedList,
|
||||
tempPlayList: state => state.tempPlayList,
|
||||
}
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
getUrl({ commit, state }, { musicInfo, type, isRefresh }) {
|
||||
getUrl({ commit, state }, { musicInfo, originMusic, type, isRefresh }) {
|
||||
if (!musicInfo._types[type]) {
|
||||
// 兼容旧版酷我源搜索列表过滤128k音质的bug
|
||||
if (!(musicInfo.source == 'kw' && type == '128k')) return Promise.reject(new Error('该歌曲没有可播放的音频'))
|
||||
@@ -36,47 +155,174 @@ const actions = {
|
||||
// return Promise.reject(new Error('该歌曲没有可播放的音频'))
|
||||
}
|
||||
if (urlRequest && urlRequest.cancelHttp) urlRequest.cancelHttp()
|
||||
if (musicInfo.typeUrl[type] && !isRefresh) return Promise.resolve()
|
||||
urlRequest = music[musicInfo.source].getMusicUrl(musicInfo, type)
|
||||
return urlRequest.promise.then(result => {
|
||||
commit('setUrl', { musicInfo, url: result.url, type })
|
||||
if (musicInfo.typeUrl[type] && !isRefresh) return Promise.resolve(musicInfo.typeUrl[type])
|
||||
try {
|
||||
urlRequest = music[musicInfo.source].getMusicUrl(musicInfo, type)
|
||||
} catch (err) {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
return urlRequest.promise.then(({ url }) => {
|
||||
if (originMusic) commit('setUrl', { musicInfo: originMusic, url, type })
|
||||
commit('setUrl', { musicInfo, url, type })
|
||||
urlRequest = null
|
||||
return url
|
||||
}).catch(err => {
|
||||
urlRequest = null
|
||||
return Promise.reject(err)
|
||||
})
|
||||
},
|
||||
getPic({ commit, state }, musicInfo) {
|
||||
if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
|
||||
picRequest = music[musicInfo.source].getPic(musicInfo)
|
||||
return picRequest.promise.then(url => {
|
||||
picRequest = null
|
||||
// if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
|
||||
// picRequest = music[musicInfo.source].getPic(musicInfo)
|
||||
return getPic.call(this, musicInfo).then(url => {
|
||||
// picRequest = null
|
||||
commit('getPic', { musicInfo, url })
|
||||
}).catch(err => {
|
||||
picRequest = null
|
||||
// picRequest = null
|
||||
return Promise.reject(err)
|
||||
})
|
||||
},
|
||||
getLrc({ commit, state }, musicInfo) {
|
||||
if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
|
||||
// if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
|
||||
if (musicInfo.lrc && musicInfo.tlrc != null) {
|
||||
if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
||||
let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
|
||||
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc })
|
||||
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
} else if (musicInfo.lrc.startsWith('[id:$00000000]')) {
|
||||
let str = musicInfo.lrc.replace('[id:$00000000]\n', '')
|
||||
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
}
|
||||
|
||||
if ((musicInfo.lxlrc == null && musicInfo.source != 'kg') || musicInfo.lxlrc != null) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
|
||||
lrcRequest = music[musicInfo.source].getLyric(musicInfo)
|
||||
return lrcRequest.promise.then(({ lyric, tlyric }) => {
|
||||
lrcRequest = null
|
||||
commit('setLrc', { musicInfo, lyric, tlyric })
|
||||
// lrcRequest = music[musicInfo.source].getLyric(musicInfo)
|
||||
return getLyric.call(this, musicInfo).then(({ lyric, tlyric, lxlyric }) => {
|
||||
// lrcRequest = null
|
||||
commit('setLrc', { musicInfo, lyric, tlyric, lxlyric })
|
||||
}).catch(err => {
|
||||
lrcRequest = null
|
||||
// lrcRequest = null
|
||||
return Promise.reject(err)
|
||||
})
|
||||
},
|
||||
|
||||
async playPrev({ state, rootState, commit, getters }) {
|
||||
const currentListId = state.listInfo.id
|
||||
const currentList = state.listInfo.list
|
||||
if (state.playedList.length) {
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = state.playedList.indexOf(state.playMusicInfo) - 1; index > -1; index--) {
|
||||
const playMusicInfo = state.playedList[index]
|
||||
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (index > -1) {
|
||||
commit('setPlayMusicInfo', state.playedList[index])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let filteredList = await filterList({
|
||||
listInfo: state.listInfo,
|
||||
playedList: state.playedList,
|
||||
savePath: rootState.setting.download.savePath,
|
||||
commit,
|
||||
})
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', null)
|
||||
const playInfo = getters.playInfo
|
||||
let currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
if (currentIndex == -1) currentIndex = 0
|
||||
let nextIndex = currentIndex
|
||||
if (!playInfo.isTempPlay) {
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
case 'random':
|
||||
nextIndex = getRandom(0, filteredList.length)
|
||||
break
|
||||
case 'listLoop':
|
||||
case 'list':
|
||||
nextIndex = currentIndex === 0 ? filteredList.length - 1 : currentIndex - 1
|
||||
break
|
||||
case 'singleLoop':
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
}
|
||||
|
||||
commit('setPlayMusicInfo', {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
})
|
||||
},
|
||||
async playNext({ state, rootState, commit, getters }) {
|
||||
if (state.tempPlayList.length) {
|
||||
const playMusicInfo = state.tempPlayList[0]
|
||||
commit('removeTempPlayList', 0)
|
||||
commit('setPlayMusicInfo', playMusicInfo)
|
||||
return
|
||||
}
|
||||
const currentListId = state.listInfo.id
|
||||
const currentList = state.listInfo.list
|
||||
if (state.playedList.length) {
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = state.playedList.indexOf(state.playMusicInfo) + 1; index < state.playedList.length; index++) {
|
||||
const playMusicInfo = state.playedList[index]
|
||||
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (index < state.playedList.length) {
|
||||
commit('setPlayMusicInfo', state.playedList[index])
|
||||
return
|
||||
}
|
||||
}
|
||||
let filteredList = await filterList({
|
||||
listInfo: state.listInfo,
|
||||
playedList: state.playedList,
|
||||
savePath: rootState.setting.download.savePath,
|
||||
commit,
|
||||
})
|
||||
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', null)
|
||||
const playInfo = getters.playInfo
|
||||
const currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
let nextIndex = currentIndex
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
nextIndex = currentIndex === filteredList.length - 1 ? 0 : currentIndex + 1
|
||||
break
|
||||
case 'random':
|
||||
nextIndex = getRandom(0, filteredList.length)
|
||||
break
|
||||
case 'list':
|
||||
nextIndex = currentIndex === filteredList.length - 1 ? -1 : currentIndex + 1
|
||||
break
|
||||
case 'singleLoop':
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
|
||||
commit('setPlayMusicInfo', {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -91,25 +337,31 @@ const mutations = {
|
||||
setLrc(state, datas) {
|
||||
datas.musicInfo.lrc = datas.lyric
|
||||
datas.musicInfo.tlrc = datas.tlyric
|
||||
datas.musicInfo.lxlrc = datas.lxlyric
|
||||
},
|
||||
setList(state, { list, index }) {
|
||||
state.playMusicInfo = {
|
||||
musicInfo: list.list[index],
|
||||
listId: list.id,
|
||||
}
|
||||
state.listInfo = list
|
||||
state.playIndex = index
|
||||
state.changePlay = true
|
||||
// console.log(state.playMusicInfo)
|
||||
if (state.playedList.length) this.commit('player/clearPlayedList')
|
||||
if (state.tempPlayList.length) this.commit('player/clearTempPlayeList')
|
||||
},
|
||||
setPlayIndex(state, index) {
|
||||
state.playIndex = index
|
||||
state.changePlay = true
|
||||
// console.log(state.changePlay)
|
||||
},
|
||||
fixPlayIndex(state, index) {
|
||||
state.playIndex = index
|
||||
setChangePlay(state) {
|
||||
state.changePlay = true
|
||||
},
|
||||
resetChangePlay(state) {
|
||||
state.changePlay = false
|
||||
},
|
||||
setPlayedList(state, item) {
|
||||
// console.log(item)
|
||||
if (state.playedList.includes(item)) return
|
||||
state.playedList.push(item)
|
||||
},
|
||||
@@ -117,11 +369,35 @@ const mutations = {
|
||||
state.playedList.splice(index, 1)
|
||||
},
|
||||
clearPlayedList(state) {
|
||||
state.playedList = []
|
||||
state.playedList.splice(0, state.playedList.length)
|
||||
},
|
||||
visiblePlayerDetail(state, visible) {
|
||||
state.isShowPlayerDetail = visible
|
||||
},
|
||||
setTempPlayList(state, list) {
|
||||
state.tempPlayList.push(...list.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
|
||||
if (!state.playMusicInfo) this.commit('player/playNext')
|
||||
},
|
||||
removeTempPlayList(state, index) {
|
||||
state.tempPlayList.splice(index, 1)
|
||||
},
|
||||
clearTempPlayeList(state) {
|
||||
state.tempPlayList.splice(0, state.tempPlayList.length)
|
||||
},
|
||||
|
||||
setPlayMusicInfo(state, playMusicInfo) {
|
||||
let playIndex = state.playIndex
|
||||
if (playMusicInfo == null) {
|
||||
playIndex = -1
|
||||
} else {
|
||||
let listId = playMusicInfo.listId
|
||||
if (listId != '__temp__' && listId === state.listInfo.id) playIndex = state.listInfo.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
}
|
||||
|
||||
state.playMusicInfo = playMusicInfo
|
||||
state.playIndex = playIndex
|
||||
state.changePlay = true
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -127,7 +127,7 @@ const actions = {
|
||||
}
|
||||
}))
|
||||
}
|
||||
Promise.all(task).then(results => commit('setLists', { results, page }))
|
||||
return Promise.all(task).then(results => commit('setLists', { results, page }))
|
||||
} else {
|
||||
return music[rootState.setting.search.searchSource].musicSearch.search(text, page, limit).catch(error => {
|
||||
console.log(error)
|
||||
|
||||
@@ -232,6 +232,7 @@ export const objectDeepMerge = (target, source, mergedObj) => {
|
||||
* @param {*} url
|
||||
*/
|
||||
export const openUrl = url => {
|
||||
if (!/^https?:\/\//.test(url)) return
|
||||
shell.openExternal(url)
|
||||
}
|
||||
|
||||
@@ -395,3 +396,14 @@ export const getPlayList = () => rendererInvoke(NAMES.mainWindow.get_playlist).c
|
||||
return rendererInvoke(NAMES.mainWindow.get_playlist, true)
|
||||
})
|
||||
|
||||
// 解析URL参数为对象
|
||||
export const parseUrlParams = str => {
|
||||
const params = {}
|
||||
if (typeof str !== 'string') return params
|
||||
const paramsArr = str.split('&')
|
||||
for (const param of paramsArr) {
|
||||
let [key, value] = param.split('=')
|
||||
params[key] = value
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
291
src/renderer/utils/lyric-font-player/font-player.js
Normal file
291
src/renderer/utils/lyric-font-player/font-player.js
Normal file
@@ -0,0 +1,291 @@
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
|
||||
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
|
||||
const timeRxp = /<(\d+),(\d+)>/
|
||||
|
||||
|
||||
// Create animation
|
||||
const createAnimation = (dom, duration) => new window.Animation(new window.KeyframeEffect(dom, [
|
||||
{ backgroundSize: '0 100%' },
|
||||
{ backgroundSize: '100% 100%' },
|
||||
], {
|
||||
duration,
|
||||
easing: 'linear',
|
||||
},
|
||||
), document.timeline)
|
||||
|
||||
|
||||
// https://jsfiddle.net/ceqpnbky/
|
||||
// https://jsfiddle.net/ceqpnbky/1/
|
||||
|
||||
module.exports = class FontPlayer {
|
||||
constructor({ lyric = '', translationLyric = '', lineClassName = '', fontClassName = '', translationClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) {
|
||||
this.lyric = lyric
|
||||
this.translationLyric = translationLyric
|
||||
|
||||
this.lineClassName = lineClassName
|
||||
this.fontClassName = fontClassName
|
||||
this.translationClassName = translationClassName
|
||||
this.lineModeClassName = lineModeClassName
|
||||
this.shadowContent = shadowContent
|
||||
this.shadowClassName = shadowClassName
|
||||
|
||||
this.isPlay = false
|
||||
this.curFontNum = 0
|
||||
this.maxFontNum = 0
|
||||
this._performanceTime = 0
|
||||
this._performanceOffsetTime = 0
|
||||
|
||||
this.fontContent = null
|
||||
|
||||
this.timeoutTools = new TimeoutTools()
|
||||
this.waitPlayTimeout = new TimeoutTools()
|
||||
|
||||
this._init()
|
||||
}
|
||||
|
||||
_init() {
|
||||
if (this.lyric == null) this.lyric = ''
|
||||
|
||||
this.isLineMode = false
|
||||
|
||||
this.lineContent = document.createElement('div')
|
||||
if (this.lineClassName) this.lineContent.classList.add(this.lineClassName)
|
||||
this.fontContent = document.createElement('div')
|
||||
this.fontContent.style = 'position:relative;display:inline-block;'
|
||||
if (this.fontClassName) this.fontContent.classList.add(this.fontClassName)
|
||||
if (this.shadowContent) {
|
||||
this.fontShadowContent = document.createElement('div')
|
||||
this.fontShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
this.fontShadowContent.className = this.shadowClassName
|
||||
this.fontContent.appendChild(this.fontShadowContent)
|
||||
}
|
||||
this.lineContent.appendChild(this.fontContent)
|
||||
if (this.translationLyric) {
|
||||
this.translationContent = document.createElement('div')
|
||||
this.translationContent.style = 'position:relative;display:inline-block;'
|
||||
this.translationContent.className = this.translationClassName
|
||||
this.translationContent.textContent = this.translationLyric
|
||||
this.lineContent.appendChild(document.createElement('br'))
|
||||
this.lineContent.appendChild(this.translationContent)
|
||||
|
||||
if (this.shadowContent) {
|
||||
this.translationShadowContent = document.createElement('div')
|
||||
this.translationShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
this.translationShadowContent.className = this.shadowClassName
|
||||
this.translationShadowContent.textContent = this.translationLyric
|
||||
this.translationContent.appendChild(this.translationShadowContent)
|
||||
}
|
||||
}
|
||||
this._parseLyric()
|
||||
}
|
||||
|
||||
_parseLyric() {
|
||||
const fonts = this.lyric.split(fontSplitRxp)
|
||||
// console.log(fonts)
|
||||
|
||||
this.maxFontNum = fonts.length - 1
|
||||
this.fonts = []
|
||||
let text
|
||||
for (const font of fonts) {
|
||||
text = font.replace(timeRxp, '')
|
||||
if (RegExp.$2 == '') return this._handleLineParse()
|
||||
const time = parseInt(RegExp.$2)
|
||||
|
||||
const dom = document.createElement('span')
|
||||
let shadowDom
|
||||
dom.textContent = text
|
||||
const animation = createAnimation(dom, time)
|
||||
this.fontContent.appendChild(dom)
|
||||
|
||||
if (this.shadowContent) {
|
||||
shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = text
|
||||
this.fontShadowContent.appendChild(shadowDom)
|
||||
}
|
||||
// dom.style = shadowDom.style = this.fontStyle
|
||||
// dom.className = shadowDom.className = this.fontClassName
|
||||
|
||||
this.fonts.push({
|
||||
text,
|
||||
startTime: parseInt(RegExp.$1),
|
||||
time,
|
||||
dom,
|
||||
shadowDom,
|
||||
animation,
|
||||
})
|
||||
}
|
||||
// console.log(this.fonts)
|
||||
}
|
||||
|
||||
_handleLineParse() {
|
||||
this.isLineMode = true
|
||||
const dom = document.createElement('span')
|
||||
let shadowDom
|
||||
dom.classList.add(this.lineModeClassName)
|
||||
dom.textContent = this.lyric
|
||||
if (this.shadowContent) {
|
||||
shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = this.lyric
|
||||
this.fontShadowContent.appendChild(shadowDom)
|
||||
}
|
||||
this.fontContent.appendChild(dom)
|
||||
this.fonts.push({
|
||||
text: this.lyric,
|
||||
dom,
|
||||
shadowDom,
|
||||
})
|
||||
}
|
||||
|
||||
_currentTime() {
|
||||
return getNow() - this._performanceTime + this._performanceOffsetTime
|
||||
}
|
||||
|
||||
_findcurFontNum(curTime) {
|
||||
const length = this.fonts.length
|
||||
for (let index = 0; index < length; index++) if (curTime <= this.fonts[index].startTime) return index === 0 ? 0 : index - 1
|
||||
return length - 1
|
||||
}
|
||||
|
||||
_handlePlayMaxFontNum() {
|
||||
let curFont = this.fonts[this.curFontNum]
|
||||
// console.log(curFont.text)
|
||||
const currentTime = this._currentTime()
|
||||
const driftTime = currentTime - curFont.startTime
|
||||
if (currentTime > curFont.startTime + curFont.time) {
|
||||
this._handlePlayFont(curFont, driftTime, true)
|
||||
this.pause()
|
||||
} else {
|
||||
this._handlePlayFont(curFont, driftTime)
|
||||
this.isPlay = false
|
||||
}
|
||||
}
|
||||
|
||||
_handlePlayFont(font, currentTime, toFinishe) {
|
||||
switch (font.animation.playState) {
|
||||
case 'finished':
|
||||
break
|
||||
case 'idle':
|
||||
font.dom.style.backgroundSize = '100% 100%'
|
||||
if (!toFinishe) font.animation.play()
|
||||
break
|
||||
default:
|
||||
if (toFinishe) {
|
||||
font.animation.cancel()
|
||||
} else {
|
||||
font.animation.currentTime = currentTime
|
||||
font.animation.play()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_handlePlayLine(isPlayed) {
|
||||
this.isPlay = false
|
||||
this.fonts[0].dom.style.backgroundSize = isPlayed ? '100% 100%' : '100% 0'
|
||||
}
|
||||
|
||||
_handlePauseFont(font) {
|
||||
if (font.animation.playState == 'running') font.animation.pause()
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
this.curFontNum++
|
||||
// console.log('curFontNum time', this.fonts[this.curFontNum].time)
|
||||
if (this.curFontNum === this.maxFontNum) return this._handlePlayMaxFontNum()
|
||||
let curFont = this.fonts[this.curFontNum]
|
||||
let nextFont = this.fonts[this.curFontNum + 1]
|
||||
// console.log(curFont, nextFont, this.curFontNum, this.maxFontNum)
|
||||
const currentTime = this._currentTime()
|
||||
// console.log(curFont.text)
|
||||
const driftTime = currentTime - curFont.startTime
|
||||
|
||||
// console.log(currentTime, driftTime)
|
||||
|
||||
if (driftTime >= 0 || this.curFontNum == 0) {
|
||||
this.delay = nextFont.startTime - curFont.startTime - driftTime
|
||||
if (this.delay > 0) {
|
||||
this._handlePlayFont(curFont, driftTime)
|
||||
this.timeoutTools.start(() => {
|
||||
this._refresh()
|
||||
}, this.delay)
|
||||
return
|
||||
}
|
||||
} else if (this.curFontNum == 0) {
|
||||
this.curFontNum--
|
||||
this.waitPlayTimeout.start(() => {
|
||||
this._refresh()
|
||||
}, -driftTime)
|
||||
return
|
||||
}
|
||||
|
||||
this.curFontNum = this._findcurFontNum(currentTime)
|
||||
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
|
||||
this.curFontNum--
|
||||
this._refresh()
|
||||
}
|
||||
|
||||
play(curTime = 0) {
|
||||
// console.log('play', curTime)
|
||||
if (!this.fonts.length) return
|
||||
this.pause()
|
||||
|
||||
if (this.isLineMode) return this._handlePlayLine(true)
|
||||
this.isPlay = true
|
||||
this._performanceTime = getNow() - curTime
|
||||
this._performanceOffsetTime = 0
|
||||
if (this._performanceTime < 0) {
|
||||
this._performanceOffsetTime = -this._performanceTime
|
||||
this._performanceTime = 0
|
||||
}
|
||||
|
||||
this.curFontNum = this._findcurFontNum(curTime)
|
||||
for (let i = this.curFontNum; i > -1; i--) {
|
||||
this._handlePlayFont(this.fonts[i], 0, true)
|
||||
}
|
||||
for (let i = this.curFontNum, len = this.fonts.length; i < len; i++) {
|
||||
let font = this.fonts[i]
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '0 100%'
|
||||
}
|
||||
|
||||
this.curFontNum--
|
||||
|
||||
this._refresh()
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (!this.isPlay) return
|
||||
this.isPlay = false
|
||||
this.timeoutTools.clear()
|
||||
this.waitPlayTimeout.clear()
|
||||
this._handlePauseFont(this.fonts[this.curFontNum])
|
||||
if (this.curFontNum === this.maxLine) return
|
||||
const curFontNum = this._findcurFontNum(this._currentTime())
|
||||
if (this.curFontNum === curFontNum) return
|
||||
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
|
||||
}
|
||||
|
||||
finish() {
|
||||
this.pause()
|
||||
if (this.isLineMode) return this._handlePlayLine(true)
|
||||
|
||||
for (const font of this.fonts) {
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '100% 100%'
|
||||
}
|
||||
this.curFontNum = this.maxFontNum
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.pause()
|
||||
if (this.isLineMode) return this._handlePlayLine(false)
|
||||
for (const font of this.fonts) {
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '0 100%'
|
||||
}
|
||||
this.curFontNum = 0
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user