commit
ba2b1dac92
3
.babelrc
3
.babelrc
|
@ -19,6 +19,7 @@
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"@babel/plugin-transform-modules-umd",
|
"@babel/plugin-transform-modules-umd",
|
||||||
"@babel/plugin-transform-runtime"
|
"@babel/plugin-transform-runtime",
|
||||||
|
"@babel/plugin-proposal-class-properties"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -6,6 +6,43 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
||||||
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
||||||
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
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
|
## [1.7.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.0...v1.7.1) - 2021-01-30
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
162
FAQ.md
162
FAQ.md
|
@ -171,3 +171,165 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的
|
||||||
从`v0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
|
从`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讨论。
|
||||||
|
|
10
README.md
10
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/blob/master/CHANGELOG.md)<br>
|
||||||
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<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)
|
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
|
||||||
|
|
||||||
### 源码使用方法
|
### 源码使用方法
|
||||||
|
@ -82,7 +82,13 @@ npm run pack:linux
|
||||||
|
|
||||||
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
||||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
||||||
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示,原来的`-nt`参数已重命名为`-dt`(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"',
|
NODE_ENV: '"development"',
|
||||||
},
|
},
|
||||||
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||||
|
__userApi: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
|
||||||
}),
|
}),
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
|
||||||
new FriendlyErrorsPlugin({
|
new FriendlyErrorsPlugin({
|
||||||
onErrors(severity, errors) { // Silent warning from electron-debug
|
onErrors(severity, errors) { // Silent warning from electron-debug
|
||||||
if (severity != 'warning') return
|
if (severity != 'warning') return
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { merge } = require('webpack-merge')
|
const { merge } = require('webpack-merge')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||||
|
|
||||||
const baseConfig = require('./webpack.config.base')
|
const baseConfig = require('./webpack.config.base')
|
||||||
|
|
||||||
|
@ -20,6 +21,18 @@ module.exports = merge(baseConfig, {
|
||||||
__filename: false,
|
__filename: false,
|
||||||
},
|
},
|
||||||
plugins: [
|
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({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
NODE_ENV: '"production"',
|
NODE_ENV: '"production"',
|
||||||
|
|
|
@ -11,7 +11,6 @@ module.exports = merge(baseConfig, {
|
||||||
devtool: 'eval-source-map',
|
devtool: 'eval-source-map',
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
|
||||||
new FriendlyErrorsPlugin(),
|
new FriendlyErrorsPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
|
|
|
@ -11,7 +11,6 @@ module.exports = merge(baseConfig, {
|
||||||
devtool: 'eval-source-map',
|
devtool: 'eval-source-map',
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
|
||||||
new FriendlyErrorsPlugin(),
|
new FriendlyErrorsPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
|
|
|
@ -179,6 +179,9 @@ function startElectron() {
|
||||||
function electronLog(data, color) {
|
function electronLog(data, color) {
|
||||||
let log = data.toString()
|
let log = data.toString()
|
||||||
if (/[0-9A-z]+/.test(log)) {
|
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))
|
console.log(chalk[color](log))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "lx-music-desktop",
|
"name": "lx-music-desktop",
|
||||||
"version": "1.7.1",
|
"version": "1.8.0",
|
||||||
"description": "一个免费的音乐查找助手",
|
"description": "一个免费的音乐查找助手",
|
||||||
"main": "./dist/electron/main.js",
|
"main": "./dist/electron/main.js",
|
||||||
"productName": "lx-music-desktop",
|
"productName": "lx-music-desktop",
|
||||||
|
@ -34,18 +34,20 @@
|
||||||
"publish:gh:linux": "node build-config/pack.js && npm run publish:linux",
|
"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": "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: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: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: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: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: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",
|
"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": "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: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: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: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: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:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64",
|
||||||
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64",
|
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64",
|
||||||
"pack:mac": "node build-config/pack.js && electron-builder -m=dmg",
|
"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: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",
|
"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": "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": [
|
"browserslist": [
|
||||||
"Electron 9.3.4"
|
"Electron 9.3.4"
|
||||||
|
@ -160,12 +163,13 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||||
"devDependencies": {
|
"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-syntax-dynamic-import": "^7.8.3",
|
||||||
"@babel/plugin-transform-modules-umd": "^7.12.1",
|
"@babel/plugin-transform-modules-umd": "^7.13.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
"@babel/plugin-transform-runtime": "^7.13.9",
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@babel/polyfill": "^7.12.1",
|
||||||
"@babel/preset-env": "^7.12.11",
|
"@babel/preset-env": "^7.13.9",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"babel-minify-webpack-plugin": "^0.3.1",
|
"babel-minify-webpack-plugin": "^0.3.1",
|
||||||
|
@ -174,26 +178,26 @@
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"changelog-parser": "^2.8.0",
|
"changelog-parser": "^2.8.0",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.9.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^4.3.0",
|
"css-loader": "^4.3.0",
|
||||||
"del": "^6.0.0",
|
"del": "^6.0.0",
|
||||||
"electron": "^9.4.2",
|
"electron": "^9.4.4",
|
||||||
"electron-builder": "^22.9.1",
|
"electron-builder": "^22.10.5",
|
||||||
"electron-debug": "^3.2.0",
|
"electron-debug": "^3.2.0",
|
||||||
"electron-devtools-installer": "^3.1.1",
|
"electron-devtools-installer": "^3.1.1",
|
||||||
"eslint": "^7.18.0",
|
"eslint": "^7.21.0",
|
||||||
"eslint-config-standard": "^14.1.1",
|
"eslint-config-standard": "^14.1.1",
|
||||||
"eslint-formatter-friendly": "^7.0.0",
|
"eslint-formatter-friendly": "^7.0.0",
|
||||||
"eslint-loader": "^4.0.2",
|
"eslint-loader": "^4.0.2",
|
||||||
"eslint-plugin-html": "^6.1.1",
|
"eslint-plugin-html": "^6.1.1",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.3.1",
|
||||||
"eslint-plugin-standard": "^4.1.0",
|
"eslint-plugin-standard": "^4.1.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||||
"html-webpack-plugin": "^4.5.1",
|
"html-webpack-plugin": "^4.5.2",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
"less-loader": "^7.3.0",
|
"less-loader": "^7.3.0",
|
||||||
"markdown-it": "^12.0.4",
|
"markdown-it": "^12.0.4",
|
||||||
|
@ -201,7 +205,7 @@
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||||
"postcss-loader": "^4.2.0",
|
"postcss-loader": "^4.2.0",
|
||||||
"postcss-pxtorem": "^5.1.1",
|
"postcss-pxtorem": "^5.1.1",
|
||||||
"pug": "^3.0.0",
|
"pug": "^3.0.2",
|
||||||
"pug-loader": "^2.4.0",
|
"pug-loader": "^2.4.0",
|
||||||
"pug-plain-loader": "^1.1.0",
|
"pug-plain-loader": "^1.1.0",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
|
@ -222,18 +226,18 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-js": "^4.0.0",
|
"crypto-js": "^4.0.0",
|
||||||
"dnscache": "^1.0.2",
|
"dnscache": "^1.0.2",
|
||||||
"electron-log": "^4.3.1",
|
"electron-log": "^4.3.2",
|
||||||
"electron-store": "^6.0.1",
|
"electron-store": "^6.0.1",
|
||||||
"electron-updater": "^4.3.5",
|
"electron-updater": "^4.3.8",
|
||||||
"iconv-lite": "^0.6.2",
|
"iconv-lite": "^0.6.2",
|
||||||
"image-size": "^0.9.3",
|
"image-size": "^0.9.4",
|
||||||
"js-htmlencode": "^0.3.0",
|
"js-htmlencode": "^0.3.0",
|
||||||
"lrc-file-parser": "^1.0.5",
|
"lrc-file-parser": "^1.0.7",
|
||||||
"needle": "^2.6.0",
|
"needle": "^2.6.0",
|
||||||
"node-id3": "^0.2.2",
|
"node-id3": "^0.2.2",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue-i18n": "^8.22.4",
|
"vue-i18n": "^8.23.0",
|
||||||
"vue-router": "^3.5.1",
|
"vue-router": "^3.5.1",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"vuex-router-sync": "^5.0.0"
|
"vuex-router-sync": "^5.0.0"
|
||||||
|
|
|
@ -1,4 +1,34 @@
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
|
||||||
|
- 新增启动参数`-play`,可以在启动软件时播放指定歌单,使用方法看Readme.md的"启动参数"部分
|
||||||
|
- 新增逐字歌词播放,默认开启,可到设置界面关闭,注:本功能目前仅对酷狗源的歌曲有效
|
||||||
|
- 新增自定义源功能,源编写规则可以去常见问题查看
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
|
||||||
|
- 允许播放除了搜索列表以外的所有歌曲,即原来没有播放按钮或者灰色的歌曲都可以去尝试点击播放。注:该功能的原理是尝试自动切换到其他源播放,所以不一定会播放成功,特别是对于那些独家的资源
|
||||||
|
- 优化单首歌曲的“添加到列表”弹窗歌曲列表状态的显示;现在在收藏单首歌曲时,若列表存在本歌曲则列表名字将变成灰色不可点击状态。总的来说,在添加单首歌曲时若列表名是灰色,则证明当前歌曲已在那个列表中
|
||||||
|
- 将歌词翻译放到原文的下方,同时新增当前播放翻译的高亮功能
|
||||||
|
|
||||||
|
### 移除
|
||||||
|
|
||||||
|
- 移除虾米源。注:虽然已移除该源,但仍可尝试去播放之前添加的歌曲,虽然不一定会成功
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
- 修复非透明模式下右侧滚动条无法拖动的问题
|
- 修复音乐搜索列表的稍后播放功能无效的问题
|
||||||
- 修复MAC下xm音乐滑块验证问题
|
- 修复搜索列表双击不支持播放的源时会导致切歌的问题
|
||||||
|
- 修复歌单列表加载失败时无法进入歌单打开界面的问题
|
||||||
|
- 修复mg源歌单列表无法加载的问题
|
||||||
|
- 修复kg跳转到官方歌曲详情页的歌曲无法播放的问题
|
||||||
|
- 修复我的列表的歌曲添加到其他列表时不排除当前列表的问题
|
||||||
|
- 修复在下载列表右击未下载完成的歌曲弹出的右击菜单中没有开始下载选项的问题
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
- 歌词翻译显示功能修改为默认关闭,注:此变更仅影响首次安装软件的用户
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
|
||||||
|
- 更新electron到v9.4.4
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -51,6 +51,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
navigationUrlWhiteList: [
|
navigationUrlWhiteList: [
|
||||||
/^https:\/\/www\.xiami\.com/,
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ const path = require('path')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
|
|
||||||
const defaultSetting = {
|
const defaultSetting = {
|
||||||
version: '1.0.39',
|
version: '1.0.40',
|
||||||
player: {
|
player: {
|
||||||
togglePlayMethod: 'listLoop',
|
togglePlayMethod: 'listLoop',
|
||||||
highQuality: false,
|
highQuality: false,
|
||||||
|
@ -11,7 +11,8 @@ const defaultSetting = {
|
||||||
isMute: false,
|
isMute: false,
|
||||||
mediaDeviceId: 'default',
|
mediaDeviceId: 'default',
|
||||||
isMediaDeviceRemovedStopPlay: false,
|
isMediaDeviceRemovedStopPlay: false,
|
||||||
isShowLyricTransition: true,
|
isShowLyricTransition: false,
|
||||||
|
isPlayLxlrc: true,
|
||||||
isSavePlayTime: false,
|
isSavePlayTime: false,
|
||||||
},
|
},
|
||||||
desktopLyric: {
|
desktopLyric: {
|
||||||
|
|
|
@ -2,11 +2,17 @@ const { ipcMain, ipcRenderer } = require('electron')
|
||||||
const names = require('./ipcNames')
|
const names = require('./ipcNames')
|
||||||
|
|
||||||
|
|
||||||
exports.mainOn = (event, callback) => {
|
exports.mainOn = (name, callback) => {
|
||||||
ipcMain.on(event, callback)
|
ipcMain.on(name, callback)
|
||||||
}
|
}
|
||||||
exports.mainOnce = (event, callback) => {
|
exports.mainOnce = (name, callback) => {
|
||||||
ipcMain.once(event, callback)
|
ipcMain.once(name, callback)
|
||||||
|
}
|
||||||
|
exports.mainOff = (name, callback) => {
|
||||||
|
ipcMain.removeListener(name, callback)
|
||||||
|
}
|
||||||
|
exports.mainOffAll = name => {
|
||||||
|
ipcMain.removeAllListeners(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.mainHandle = (name, callback) => {
|
exports.mainHandle = (name, callback) => {
|
||||||
|
@ -15,6 +21,9 @@ exports.mainHandle = (name, callback) => {
|
||||||
exports.mainHandleOnce = (name, callback) => {
|
exports.mainHandleOnce = (name, callback) => {
|
||||||
ipcMain.handleOnce(name, callback)
|
ipcMain.handleOnce(name, callback)
|
||||||
}
|
}
|
||||||
|
exports.mainHandleRemove = name => {
|
||||||
|
ipcMain.removeListener(name)
|
||||||
|
}
|
||||||
|
|
||||||
exports.mainSend = (window, name, params) => {
|
exports.mainSend = (window, name, params) => {
|
||||||
window.webContents.send(name, params)
|
window.webContents.send(name, params)
|
||||||
|
@ -33,5 +42,11 @@ exports.rendererOn = (name, callback) => {
|
||||||
exports.rendererOnce = (name, callback) => {
|
exports.rendererOnce = (name, callback) => {
|
||||||
ipcRenderer.once(name, callback)
|
ipcRenderer.once(name, callback)
|
||||||
}
|
}
|
||||||
|
exports.rendererOff = (name, callback) => {
|
||||||
|
ipcRenderer.removeListener(name, callback)
|
||||||
|
}
|
||||||
|
exports.rendererOffAll = name => {
|
||||||
|
ipcRenderer.removeAllListeners(name)
|
||||||
|
}
|
||||||
|
|
||||||
exports.NAMES = names
|
exports.NAMES = names
|
||||||
|
|
|
@ -50,6 +50,15 @@ const names = {
|
||||||
get_data: 'get_data',
|
get_data: 'get_data',
|
||||||
save_data: 'save_data',
|
save_data: 'save_data',
|
||||||
get_hot_key: 'get_hot_key',
|
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: {
|
winLyric: {
|
||||||
close: 'close',
|
close: 'close',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const log = require('electron-log')
|
const log = require('electron-log')
|
||||||
const Store = require('electron-store')
|
const Store = require('electron-store')
|
||||||
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
|
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 defaultHotKey = require('./defaultHotKey')
|
||||||
const { dialog, app } = require('electron')
|
const { dialog, app } = require('electron')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
@ -115,10 +115,10 @@ exports.mergeSetting = (setting, version) => {
|
||||||
setting = defaultSettingCopy
|
setting = defaultSettingCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
// if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||||
let api = apiSource.find(api => !api.disabled)
|
// let api = apiSource.find(api => !api.disabled)
|
||||||
if (api) setting.apiSource = api.id
|
// if (api) setting.apiSource = api.id
|
||||||
}
|
// }
|
||||||
|
|
||||||
return { setting, version: defaultVersion }
|
return { setting, version: defaultVersion }
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,9 @@ exports.initSetting = () => {
|
||||||
const electronStore_config = new Store({
|
const electronStore_config = new Store({
|
||||||
name: 'config',
|
name: 'config',
|
||||||
})
|
})
|
||||||
|
const electronStore_downloadList = new Store({
|
||||||
|
name: 'downloadList',
|
||||||
|
})
|
||||||
let setting = electronStore_config.get('setting')
|
let setting = electronStore_config.get('setting')
|
||||||
if (setting) {
|
if (setting) {
|
||||||
let version = electronStore_config.get('version')
|
let version = electronStore_config.get('version')
|
||||||
|
@ -165,7 +168,7 @@ exports.initSetting = () => {
|
||||||
}
|
}
|
||||||
const downloadList = electronStore_config.get('download')
|
const downloadList = electronStore_config.get('download')
|
||||||
if (downloadList) {
|
if (downloadList) {
|
||||||
if (downloadList.list) electronStore_list.set('downloadList', downloadList.list)
|
if (downloadList.list) electronStore_downloadList.set('list', downloadList.list)
|
||||||
electronStore_config.delete('download')
|
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'))
|
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
|
||||||
|
|
||||||
// 重置 ^0.18.2 排行榜ID
|
// 重置 ^0.18.2 排行榜ID
|
||||||
|
|
|
@ -6,8 +6,12 @@ const Tray = require('./Tray')
|
||||||
const WinLyric = require('./WinLyric')
|
const WinLyric = require('./WinLyric')
|
||||||
const HotKey = require('./HotKey')
|
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.common) global.lx_event.common = new Common()
|
||||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
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.tray) global.lx_event.tray = new Tray()
|
||||||
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
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.hotKey) global.lx_event.hotKey = new HotKey()
|
||||||
|
|
||||||
|
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||||
|
|
|
@ -2,3 +2,4 @@ require('./appMenu')
|
||||||
require('./winLyric')
|
require('./winLyric')
|
||||||
require('./tray')
|
require('./tray')
|
||||||
require('./hotKey')
|
require('./hotKey')
|
||||||
|
require('./userApi')
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
exports.userApis = []
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
status: 'status',
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
})
|
|
@ -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>
|
|
@ -0,0 +1,12 @@
|
||||||
|
const names = {
|
||||||
|
init: '',
|
||||||
|
request: '',
|
||||||
|
response: '',
|
||||||
|
openDevTools: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (const key of Object.keys(names)) {
|
||||||
|
names[key] = `userApi_${key}`
|
||||||
|
}
|
||||||
|
module.exports = names
|
|
@ -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
|
|
@ -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,
|
config: desktopLyric,
|
||||||
languageId: global.appSetting.langId,
|
languageId: global.appSetting.langId,
|
||||||
isShowLyricTransition: global.appSetting.player.isShowLyricTransition,
|
isShowLyricTransition: global.appSetting.player.isShowLyricTransition,
|
||||||
|
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||||
})
|
})
|
||||||
if (isLock != desktopLyric.isLock) {
|
if (isLock != desktopLyric.isLock) {
|
||||||
isLock = desktopLyric.isLock
|
isLock = desktopLyric.isLock
|
||||||
|
|
|
@ -24,7 +24,12 @@ mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
|
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) => {
|
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
|
||||||
|
|
|
@ -18,6 +18,6 @@ require('./showDialog')
|
||||||
require('./playList')
|
require('./playList')
|
||||||
require('./data')
|
require('./data')
|
||||||
|
|
||||||
require('./xm_verify')
|
|
||||||
|
|
||||||
require('./kw_decodeLyric')
|
require('./kw_decodeLyric')
|
||||||
|
|
||||||
|
require('./userApi')
|
||||||
|
|
|
@ -3,6 +3,7 @@ const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = requir
|
||||||
|
|
||||||
|
|
||||||
let electronStore_list
|
let electronStore_list
|
||||||
|
let electronStore_downloadList
|
||||||
|
|
||||||
mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false) => {
|
mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false) => {
|
||||||
if (!electronStore_list) {
|
if (!electronStore_list) {
|
||||||
|
@ -11,13 +12,35 @@ mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false)
|
||||||
clearInvalidConfig: !isIgnoredError,
|
clearInvalidConfig: !isIgnoredError,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (!electronStore_downloadList) {
|
||||||
|
electronStore_downloadList = new Store({
|
||||||
|
name: 'downloadList',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultList: electronStore_list.get('defaultList'),
|
defaultList: electronStore_list.get('defaultList'),
|
||||||
loveList: electronStore_list.get('loveList'),
|
loveList: electronStore_list.get('loveList'),
|
||||||
userList: electronStore_list.get('userList'),
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -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,79 +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)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
view.webContents.session.webRequest.onCompleted({ urls: ['*://www.xiami.com/*'] }, details => {
|
|
||||||
if (/\/_____tmd_____\/slide\?/.test(details.url)) {
|
|
||||||
for (const item of details.responseHeaders['set-cookie']) {
|
|
||||||
if (!/^x5sec=/.test(item)) continue
|
|
||||||
const x5sec = /x5sec=(\w+);.+$/.exec(item)
|
|
||||||
isActioned = true
|
|
||||||
closeView().finally(() => {
|
|
||||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
|
||||||
resolve(x5sec[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// console.log(url)
|
|
||||||
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,91 +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: 1000,
|
|
||||||
height: 800,
|
|
||||||
resizable: false,
|
|
||||||
// transparent: true,
|
|
||||||
x: mainWindowSizeInfo.x + (windowSizeInfo.width - 1000) / 2,
|
|
||||||
y: mainWindowSizeInfo.y + (windowSizeInfo.height - 800 + 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.session.webRequest.onCompleted({ urls: ['*://www.xiami.com/*'] }, details => {
|
|
||||||
if (/\/_____tmd_____\/slide\?/.test(details.url)) {
|
|
||||||
for (const item of details.responseHeaders['set-cookie']) {
|
|
||||||
if (!/^x5sec=/.test(item)) continue
|
|
||||||
const x5sec = /x5sec=(\w+);.+$/.exec(item)
|
|
||||||
isActioned = true
|
|
||||||
closeWin().finally(() => {
|
|
||||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
|
||||||
resolve(x5sec[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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")
|
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||||
.control-bar(v-show="!lrcConfig.isLock")
|
.control-bar(v-show="!lrcConfig.isLock")
|
||||||
core-control-bar(:lrcConfig="lrcConfig" :themes="themeList")
|
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-left(@mousedown.self="handleMouseDown('left', $event)")
|
||||||
div.resize-top(@mousedown.self="handleMouseDown('top', $event)")
|
div.resize-top(@mousedown.self="handleMouseDown('top', $event)")
|
||||||
div.resize-right(@mousedown.self="handleMouseDown('right', $event)")
|
div.resize-right(@mousedown.self="handleMouseDown('right', $event)")
|
||||||
|
@ -45,6 +45,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isShowLyricTransition: true,
|
isShowLyricTransition: true,
|
||||||
|
isPlayLxlrc: true,
|
||||||
themeList: [
|
themeList: [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
|
@ -118,9 +119,10 @@ export default {
|
||||||
document.removeEventListener('mouseup', this.handleMouseUp)
|
document.removeEventListener('mouseup', this.handleMouseUp)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleUpdateConfig({ config, languageId, isShowLyricTransition }) {
|
handleUpdateConfig({ config, languageId, isShowLyricTransition, isPlayLxlrc }) {
|
||||||
this.lrcConfig = config
|
this.lrcConfig = config
|
||||||
this.isShowLyricTransition = isShowLyricTransition
|
this.isShowLyricTransition = isShowLyricTransition
|
||||||
|
this.isPlayLxlrc = isPlayLxlrc
|
||||||
if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId
|
if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId
|
||||||
},
|
},
|
||||||
handleMouseDown(origin, event) {
|
handleMouseDown(origin, event) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<template lang="pug">
|
<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(: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}}
|
p(:class="$style.lrcLine") {{info.text}}
|
||||||
div(:class="$style.lyricSpace")
|
div(:class="$style.lyricSpace")
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,7 +10,7 @@ div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" :style=
|
||||||
<script>
|
<script>
|
||||||
import { rendererOn, rendererSend, NAMES } from '../../../common/ipc'
|
import { rendererOn, rendererSend, NAMES } from '../../../common/ipc'
|
||||||
import { scrollTo } from '../../../renderer/utils'
|
import { scrollTo } from '../../../renderer/utils'
|
||||||
import Lyric from 'lrc-file-parser'
|
import Lyric from '@renderer/utils/lyric-font-player'
|
||||||
|
|
||||||
let cancelScrollFn = null
|
let cancelScrollFn = null
|
||||||
|
|
||||||
|
@ -27,6 +28,10 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
isPlayLxlrc: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
isShowLyricTransition: {
|
isShowLyricTransition: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -65,6 +70,7 @@ export default {
|
||||||
lyrics: {
|
lyrics: {
|
||||||
lyric: '',
|
lyric: '',
|
||||||
tlyric: '',
|
tlyric: '',
|
||||||
|
lxlyric: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -85,7 +91,7 @@ export default {
|
||||||
if (n.length) {
|
if (n.length) {
|
||||||
this.lyricLines = n
|
this.lyricLines = n
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||||
this.handleScrollLrc()
|
this.handleScrollLrc()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,7 +103,7 @@ export default {
|
||||||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||||
this.lyricLines = this._lyricLines
|
this.lyricLines = this._lyricLines
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||||
this.handleScrollLrc()
|
this.handleScrollLrc()
|
||||||
})
|
})
|
||||||
}, 50)
|
}, 50)
|
||||||
|
@ -105,7 +111,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.lyricLines = n
|
this.lyricLines = n
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||||
this.handleScrollLrc()
|
this.handleScrollLrc()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -120,8 +126,11 @@ export default {
|
||||||
},
|
},
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
isShowLyricTransition(n) {
|
isShowLyricTransition() {
|
||||||
console.log(n)
|
this.setLyric()
|
||||||
|
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
||||||
|
},
|
||||||
|
isPlayLxlrc() {
|
||||||
this.setLyric()
|
this.setLyric()
|
||||||
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
||||||
},
|
},
|
||||||
|
@ -129,6 +138,11 @@ export default {
|
||||||
created() {
|
created() {
|
||||||
rendererOn(NAMES.winLyric.set_lyric_info, (event, data) => this.handleSetInfo(data))
|
rendererOn(NAMES.winLyric.set_lyric_info, (event, data) => this.handleSetInfo(data))
|
||||||
window.lrc = new Lyric({
|
window.lrc = new Lyric({
|
||||||
|
lineClassName: 'lrc-content',
|
||||||
|
fontClassName: 'font',
|
||||||
|
shadowClassName: 'shadow',
|
||||||
|
shadowContent: true,
|
||||||
|
activeLineClassName: 'active',
|
||||||
onPlay: (line, text) => {
|
onPlay: (line, text) => {
|
||||||
this.lyric.text = text
|
this.lyric.text = text
|
||||||
this.lyric.line = line
|
this.lyric.line = line
|
||||||
|
@ -136,6 +150,12 @@ export default {
|
||||||
},
|
},
|
||||||
onSetLyric: lines => { // listening lyrics seting event
|
onSetLyric: lines => { // listening lyrics seting event
|
||||||
// console.log(lines) // lines is array of all lyric text
|
// 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.lines = lines
|
||||||
this.lyric.line = 0
|
this.lyric.line = 0
|
||||||
},
|
},
|
||||||
|
@ -159,6 +179,7 @@ export default {
|
||||||
case 'lyric':
|
case 'lyric':
|
||||||
this.lyrics.lyric = data.lrc
|
this.lyrics.lyric = data.lrc
|
||||||
this.lyrics.tlyric = data.tlrc
|
this.lyrics.tlyric = data.tlrc
|
||||||
|
this.lyrics.lxlyric = data.lxlrc
|
||||||
this.setLyric()
|
this.setLyric()
|
||||||
break
|
break
|
||||||
case 'play':
|
case 'play':
|
||||||
|
@ -171,8 +192,9 @@ export default {
|
||||||
break
|
break
|
||||||
case 'info':
|
case 'info':
|
||||||
// console.log('info', data)
|
// console.log('info', data)
|
||||||
this.lyrics.lyric = data.lyric
|
this.lyrics.lyric = data.lrc
|
||||||
this.lyrics.tlyric = data.tlyric
|
this.lyrics.tlyric = data.tlrc
|
||||||
|
this.lyrics.lxlyric = data.lxlrc
|
||||||
this.setLyric()
|
this.setLyric()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.lyric.line = data.line
|
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)
|
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) {
|
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.isMsDown = true
|
||||||
this.lyricEvent.msDownY = e.clientY
|
this.lyricEvent.msDownY = e.clientY
|
||||||
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
|
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
|
||||||
|
@ -279,7 +304,11 @@ export default {
|
||||||
rendererSend(NAMES.winLyric.close)
|
rendererSend(NAMES.winLyric.close)
|
||||||
},
|
},
|
||||||
setLyric() {
|
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;
|
text-align: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 68px;
|
font-size: 16px;
|
||||||
padding: 0 5px;
|
color: @color-theme-lyric;
|
||||||
opacity: .6;
|
cursor: move;
|
||||||
transition: opacity @transition-theme;
|
|
||||||
&.draging {
|
:global {
|
||||||
.lrc-line {
|
.lrc-content {
|
||||||
cursor: grabbing;
|
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 {
|
// .lrc-line {
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
padding: 8px 0;
|
// padding: 8px 0;
|
||||||
line-height: 1.2;
|
// line-height: 1.2;
|
||||||
overflow-wrap: break-word;
|
// overflow-wrap: break-word;
|
||||||
transition: @transition-theme;
|
// transition: @transition-theme;
|
||||||
transition-property: color, font-size, text-shadow;
|
// transition-property: color, font-size, text-shadow;
|
||||||
cursor: grab;
|
// cursor: grab;
|
||||||
// font-weight: bold;
|
// // font-weight: bold;
|
||||||
// background-clip: text;
|
// // background-clip: text;
|
||||||
color: @color-theme-lyric;
|
// color: @color-theme-lyric;
|
||||||
text-shadow: 1px 1px 2px #000;
|
// text-shadow: 1px 1px 2px #000;
|
||||||
// background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
|
// // background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
|
||||||
// background-clip: text;
|
// // background-clip: text;
|
||||||
// -webkit-background-clip: text;
|
// // -webkit-background-clip: text;
|
||||||
// -webkit-text-fill-color: #fff;
|
// // -webkit-text-fill-color: #fff;
|
||||||
// -webkit-text-stroke: thin #124628;
|
// // -webkit-text-stroke: thin #124628;
|
||||||
}
|
// }
|
||||||
.lyric-space {
|
.lyric-space {
|
||||||
height: 70%;
|
height: 70%;
|
||||||
}
|
}
|
||||||
|
@ -334,9 +422,28 @@ export default {
|
||||||
// -webkit-text-stroke: thin #124628;
|
// -webkit-text-stroke: thin #124628;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.draging {
|
||||||
|
:global {
|
||||||
|
.lrc-content {
|
||||||
|
.font, .translation {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.lrc-active-zoom {
|
.lrc-active-zoom {
|
||||||
.lrc-active;
|
:global {
|
||||||
font-size: 1.2em;
|
.lrc-content {
|
||||||
|
&.active {
|
||||||
|
.translation {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.footer {
|
.footer {
|
||||||
flex: 0 0 100px;
|
flex: 0 0 100px;
|
||||||
|
@ -348,14 +455,30 @@ export default {
|
||||||
|
|
||||||
each(@themes, {
|
each(@themes, {
|
||||||
:global(#container.@{value}) {
|
:global(#container.@{value}) {
|
||||||
.lrc-line {
|
// .lrc-line {
|
||||||
color: ~'@{color-@{value}-theme-lyric}';
|
// color: ~'@{color-@{value}-theme-lyric}';
|
||||||
}
|
// }
|
||||||
.lrc-active, .lrc-active-zoom {
|
.lrc-active, .lrc-active-zoom {
|
||||||
.lrc-line {
|
.lrc-line {
|
||||||
color: ~'@{color-@{value}-theme-lyric_2}';
|
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}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
core-view#view
|
core-view#view
|
||||||
core-player#player
|
core-player#player
|
||||||
core-icons
|
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-version-modal(v-show="version.showModal")
|
||||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||||
#container(v-else :class="theme")
|
#container(v-else :class="theme")
|
||||||
|
@ -16,7 +15,6 @@
|
||||||
core-view#view
|
core-view#view
|
||||||
core-player#player
|
core-player#player
|
||||||
core-icons
|
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-version-modal(v-show="version.showModal")
|
||||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||||
</template>
|
</template>
|
||||||
|
@ -27,8 +25,9 @@ import { mapMutations, mapGetters, mapActions } from 'vuex'
|
||||||
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
||||||
import { isLinux } from '../common/utils'
|
import { isLinux } from '../common/utils'
|
||||||
import music from './utils/music'
|
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 { 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
|
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||||
dnscache({
|
dnscache({
|
||||||
|
@ -37,6 +36,20 @@ dnscache({
|
||||||
cachesize: 1000,
|
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 {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -44,12 +57,15 @@ export default {
|
||||||
isDT: false,
|
isDT: false,
|
||||||
isLinux,
|
isLinux,
|
||||||
globalObj: {
|
globalObj: {
|
||||||
apiSource: 'test',
|
apiSource: null,
|
||||||
proxy: {},
|
proxy: {},
|
||||||
isShowPact: false,
|
isShowPact: false,
|
||||||
qualityList: {},
|
qualityList: {},
|
||||||
xm: {
|
userApi: {
|
||||||
isShowVerify: false,
|
list: [],
|
||||||
|
status: false,
|
||||||
|
message: 'initing',
|
||||||
|
apis: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
updateTimeout: null,
|
updateTimeout: null,
|
||||||
|
@ -70,24 +86,12 @@ export default {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.saveDefaultList = throttle(n => {
|
this.saveMyList = listThrottle(data => {
|
||||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||||
type: 'defaultList',
|
type: 'myList',
|
||||||
data: n,
|
data,
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 300)
|
||||||
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)
|
|
||||||
this.saveDownloadList = throttle(n => {
|
this.saveDownloadList = throttle(n => {
|
||||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||||
type: 'downloadList',
|
type: 'downloadList',
|
||||||
|
@ -115,19 +119,19 @@ export default {
|
||||||
},
|
},
|
||||||
defaultList: {
|
defaultList: {
|
||||||
handler(n) {
|
handler(n) {
|
||||||
this.saveDefaultList(n)
|
this.saveMyList({ defaultList: n })
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
loveList: {
|
loveList: {
|
||||||
handler(n) {
|
handler(n) {
|
||||||
this.saveLoveList(n)
|
this.saveMyList({ loveList: n })
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
userList: {
|
userList: {
|
||||||
handler(n) {
|
handler(n) {
|
||||||
this.saveUserList(n)
|
this.saveMyList({ userList: n })
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
@ -140,8 +144,20 @@ export default {
|
||||||
searchHistoryList(n) {
|
searchHistoryList(n) {
|
||||||
this.saveSearchHistoryList(n)
|
this.saveSearchHistoryList(n)
|
||||||
},
|
},
|
||||||
'globalObj.apiSource'(n) {
|
'globalObj.apiSource'(n, o) {
|
||||||
this.globalObj.qualityList = music.supportQuality[n]
|
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) {
|
if (n != this.setting.apiSource) {
|
||||||
this.setSetting(Object.assign({}, this.setting, {
|
this.setSetting(Object.assign({}, this.setting, {
|
||||||
apiSource: n,
|
apiSource: n,
|
||||||
|
@ -177,10 +193,12 @@ export default {
|
||||||
...mapMutations('player', {
|
...mapMutations('player', {
|
||||||
setPlayList: 'setList',
|
setPlayList: 'setList',
|
||||||
}),
|
}),
|
||||||
|
...mapActions('songList', ['getListDetailAll']),
|
||||||
init() {
|
init() {
|
||||||
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
|
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)
|
document.body.addEventListener('click', this.handleBodyClick, true)
|
||||||
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
|
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
|
||||||
|
@ -244,14 +262,23 @@ export default {
|
||||||
}, 60 * 30 * 1000)
|
}, 60 * 30 * 1000)
|
||||||
|
|
||||||
this.listenEvent()
|
this.listenEvent()
|
||||||
this.initData()
|
asyncTask.push(this.initData())
|
||||||
|
asyncTask.push(this.initUserApi())
|
||||||
this.globalObj.apiSource = this.setting.apiSource
|
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)
|
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
|
||||||
window.globalObj = this.globalObj
|
window.globalObj = this.globalObj
|
||||||
|
|
||||||
// 初始化音乐sdk
|
// 初始化音乐sdk
|
||||||
music.init()
|
asyncTask.push(music.init())
|
||||||
|
Promise.all(asyncTask).then(() => {
|
||||||
|
this.handleInitEnvParamSearch()
|
||||||
|
this.handleInitEnvParamPlay()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
enableIgnoreMouseEvents() {
|
enableIgnoreMouseEvents() {
|
||||||
if (this.isDT) return
|
if (this.isDT) return
|
||||||
|
@ -265,12 +292,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
initData() { // 初始化数据
|
initData() { // 初始化数据
|
||||||
this.initLocalList() // 初始化播放列表
|
return Promise.all([
|
||||||
|
this.initMyList(), // 初始化播放列表
|
||||||
|
this.initSearchHistoryList(), // 初始化搜索历史列表
|
||||||
|
])
|
||||||
// this.initDownloadList() // 初始化下载列表
|
// this.initDownloadList() // 初始化下载列表
|
||||||
this.initSearchHistoryList() // 初始化搜索历史列表
|
|
||||||
},
|
},
|
||||||
initLocalList() {
|
initMyList() {
|
||||||
getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
return getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
||||||
if (!defaultList) defaultList = this.defaultList
|
if (!defaultList) defaultList = this.defaultList
|
||||||
if (!loveList) loveList = this.loveList
|
if (!loveList) loveList = this.loveList
|
||||||
if (userList) {
|
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() {
|
showUpdateModal() {
|
||||||
(this.version.newVersion && this.version.newVersion.history
|
(this.version.newVersion && this.version.newVersion.history
|
||||||
? Promise.resolve(this.version.newVersion)
|
? Promise.resolve(this.version.newVersion)
|
||||||
|
@ -389,18 +484,91 @@ export default {
|
||||||
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
|
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
|
||||||
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
|
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({
|
this.setPlayList({
|
||||||
path: 'search',
|
list: {
|
||||||
query: {
|
list: targetList.list,
|
||||||
text: this.envParams.search,
|
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() {
|
async playSongListDetail(source, link, playIndex) {
|
||||||
music.xm.closeVerifyModal()
|
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() {
|
listenEvent() {
|
||||||
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
|
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
|
||||||
|
|
|
@ -85,13 +85,12 @@ div(:class="$style.player")
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Lyric from 'lrc-file-parser'
|
import Lyric from '@renderer/utils/lyric-font-player'
|
||||||
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
||||||
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
|
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
|
||||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||||
import { requestMsg } from '../../utils/message'
|
import { requestMsg } from '../../utils/message'
|
||||||
import { player as eventPlayerNames } from '../../../common/hotKey'
|
import { player as eventPlayerNames } from '../../../common/hotKey'
|
||||||
import musicSdk from '@renderer/utils/music'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
let audio
|
let audio
|
||||||
|
@ -216,8 +215,9 @@ export default {
|
||||||
singer: this.musicInfo.singer,
|
singer: this.musicInfo.singer,
|
||||||
name: this.musicInfo.name,
|
name: this.musicInfo.name,
|
||||||
album: this.musicInfo.album,
|
album: this.musicInfo.album,
|
||||||
lyric: this.musicInfo.lrc,
|
lrc: this.musicInfo.lrc,
|
||||||
tlyric: this.musicInfo.tlrc,
|
tlrc: this.musicInfo.tlrc,
|
||||||
|
lxlrc: this.musicInfo.lxlrc,
|
||||||
isPlay: this.isPlay,
|
isPlay: this.isPlay,
|
||||||
line: this.lyric.line,
|
line: this.lyric.line,
|
||||||
played_time: audio.currentTime * 1000,
|
played_time: audio.currentTime * 1000,
|
||||||
|
@ -275,6 +275,9 @@ export default {
|
||||||
'setting.player.isShowLyricTransition'() {
|
'setting.player.isShowLyricTransition'() {
|
||||||
this.setLyric()
|
this.setLyric()
|
||||||
},
|
},
|
||||||
|
'setting.player.isPlayLxlrc'() {
|
||||||
|
this.setLyric()
|
||||||
|
},
|
||||||
async list(n, o) {
|
async list(n, o) {
|
||||||
if (n === o && this.musicInfo.songmid) {
|
if (n === o && this.musicInfo.songmid) {
|
||||||
let index = this.listId == 'download'
|
let index = this.listId == 'download'
|
||||||
|
@ -327,6 +330,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('player', ['getUrl', 'getPic', 'getLrc', 'playPrev', 'playNext']),
|
...mapActions('player', ['getUrl', 'getPic', 'getLrc', 'playPrev', 'playNext']),
|
||||||
|
...mapActions('list', ['getOtherSource']),
|
||||||
...mapMutations('player', [
|
...mapMutations('player', [
|
||||||
'setPlayMusicInfo',
|
'setPlayMusicInfo',
|
||||||
'setPlayIndex',
|
'setPlayIndex',
|
||||||
|
@ -452,6 +456,10 @@ export default {
|
||||||
})
|
})
|
||||||
|
|
||||||
window.lrc = new Lyric({
|
window.lrc = new Lyric({
|
||||||
|
lineClassName: 'lrc-content',
|
||||||
|
fontClassName: 'font',
|
||||||
|
shadowContent: false,
|
||||||
|
activeLineClassName: 'active',
|
||||||
onPlay: (line, text) => {
|
onPlay: (line, text) => {
|
||||||
this.lyric.text = text
|
this.lyric.text = text
|
||||||
this.lyric.line = line
|
this.lyric.line = line
|
||||||
|
@ -463,7 +471,7 @@ export default {
|
||||||
this.lyric.lines = lines
|
this.lyric.lines = lines
|
||||||
this.lyric.line = 0
|
this.lyric.line = 0
|
||||||
},
|
},
|
||||||
offset: 80,
|
// offset: 80,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.handleRegisterEvent('on')
|
this.handleRegisterEvent('on')
|
||||||
|
@ -492,7 +500,7 @@ export default {
|
||||||
this.setImg(targetSong.musicInfo)
|
this.setImg(targetSong.musicInfo)
|
||||||
this.setLrc(targetSong.musicInfo)
|
this.setLrc(targetSong.musicInfo)
|
||||||
} else {
|
} else {
|
||||||
if (!this.assertApiSupport(targetSong.source)) return this.playNext()
|
// if (!this.assertApiSupport(targetSong.source)) return this.playNext()
|
||||||
this.musicInfo.songmid = targetSong.songmid
|
this.musicInfo.songmid = targetSong.songmid
|
||||||
this.musicInfo.singer = targetSong.singer
|
this.musicInfo.singer = targetSong.singer
|
||||||
this.musicInfo.name = targetSong.name
|
this.musicInfo.name = targetSong.name
|
||||||
|
@ -573,7 +581,7 @@ export default {
|
||||||
togglePlay() {
|
togglePlay() {
|
||||||
if (!audio.src) {
|
if (!audio.src) {
|
||||||
if (this.restorePlayTime != null) {
|
if (this.restorePlayTime != null) {
|
||||||
if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
|
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
|
||||||
this.setUrl(this.targetSong)
|
this.setUrl(this.targetSong)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -613,10 +621,7 @@ export default {
|
||||||
|
|
||||||
this.status = this.statusText = 'Try toggle source...'
|
this.status = this.statusText = 'Try toggle source...'
|
||||||
|
|
||||||
return (originMusic.otherSource && originMusic.otherSource.length ? Promise.resolve(originMusic.otherSource) : musicSdk.findMusic(originMusic)).then(res => {
|
return this.getOtherSource(originMusic).then(otherSource => {
|
||||||
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { otherSource: res }, musicInfo: originMusic })
|
|
||||||
return res
|
|
||||||
}).then(otherSource => {
|
|
||||||
console.log('find otherSource', otherSource)
|
console.log('find otherSource', otherSource)
|
||||||
if (otherSource.length) {
|
if (otherSource.length) {
|
||||||
for (const item of otherSource) {
|
for (const item of otherSource) {
|
||||||
|
@ -644,10 +649,11 @@ export default {
|
||||||
this.getLrc(targetSong).then(() => {
|
this.getLrc(targetSong).then(() => {
|
||||||
this.musicInfo.lrc = targetSong.lrc
|
this.musicInfo.lrc = targetSong.lrc
|
||||||
this.musicInfo.tlrc = targetSong.tlrc
|
this.musicInfo.tlrc = targetSong.tlrc
|
||||||
|
this.musicInfo.lxlrc = targetSong.lxlrc
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.status = this.statusText = this.$t('core.player.lyric_error')
|
this.status = this.statusText = this.$t('core.player.lyric_error')
|
||||||
}).finally(() => {
|
}).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()
|
this.setLyric()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -661,6 +667,7 @@ export default {
|
||||||
this.musicInfo.songmid = null
|
this.musicInfo.songmid = null
|
||||||
this.musicInfo.lrc = null
|
this.musicInfo.lrc = null
|
||||||
this.musicInfo.tlrc = null
|
this.musicInfo.tlrc = null
|
||||||
|
this.musicInfo.lxlrc = null
|
||||||
this.musicInfo.url = null
|
this.musicInfo.url = null
|
||||||
this.nowPlayTime = 0
|
this.nowPlayTime = 0
|
||||||
this.maxPlayTime = 0
|
this.maxPlayTime = 0
|
||||||
|
@ -851,7 +858,15 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setLyric() {
|
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')) {
|
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) {
|
||||||
window.lrc.play(audio.currentTime * 1000)
|
window.lrc.play(audio.currentTime * 1000)
|
||||||
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
div(:class="$style.right")
|
div(:class="$style.right")
|
||||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||||
div(:class="$style.lyricSpace")
|
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")
|
div(:class="$style.lyricSpace")
|
||||||
|
|
||||||
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
|
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) {
|
handler(n, o) {
|
||||||
this.isSetedLines = true
|
this.isSetedLines = true
|
||||||
if (o) {
|
if (o) {
|
||||||
|
this.$refs.dom_lyric_text.textContent = ''
|
||||||
|
this.setLyric(n)
|
||||||
|
|
||||||
this._lyricLines = n
|
this._lyricLines = n
|
||||||
if (n.length) {
|
if (n.length) {
|
||||||
this.lyricLines = n
|
this.lyricLines = n
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||||
this.handleScrollLrc()
|
this.handleScrollLrc()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -174,7 +178,7 @@ export default {
|
||||||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||||
this.lyricLines = this._lyricLines
|
this.lyricLines = this._lyricLines
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||||
this.handleScrollLrc()
|
this.handleScrollLrc()
|
||||||
})
|
})
|
||||||
}, 50)
|
}, 50)
|
||||||
|
@ -182,7 +186,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.lyricLines = n
|
this.lyricLines = n
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||||
this.handleScrollLrc()
|
this.handleScrollLrc()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -235,6 +239,8 @@ export default {
|
||||||
document.addEventListener('mousemove', this.handleMouseMsMove)
|
document.addEventListener('mousemove', this.handleMouseMsMove)
|
||||||
document.addEventListener('mouseup', this.handleMouseMsUp)
|
document.addEventListener('mouseup', this.handleMouseMsUp)
|
||||||
window.addEventListener('resize', this.handleResize)
|
window.addEventListener('resize', this.handleResize)
|
||||||
|
// console.log('object', this.$refs.dom_lyric_text)
|
||||||
|
this.setLyric(this.lyricLines)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.clearLyricScrollTimeout()
|
this.clearLyricScrollTimeout()
|
||||||
|
@ -250,6 +256,13 @@ export default {
|
||||||
...mapMutations('player', [
|
...mapMutations('player', [
|
||||||
'visiblePlayerDetail',
|
'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() {
|
handleResize() {
|
||||||
this.setProgressWidth()
|
this.setProgressWidth()
|
||||||
},
|
},
|
||||||
|
@ -540,6 +553,7 @@ export default {
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
|
@ -565,16 +579,62 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
|
color: @color-theme_2-font;
|
||||||
&.draging {
|
&.draging {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
p {
|
:global {
|
||||||
padding: 8px 0;
|
.lrc-content {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
overflow-wrap: break-word;
|
margin: 16px 0;
|
||||||
transition: @transition-theme !important;
|
overflow-wrap: break-word;
|
||||||
transition-property: color, font-size;
|
|
||||||
|
.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 {
|
.lyric-space {
|
||||||
height: 70%;
|
height: 70%;
|
||||||
|
@ -736,6 +796,7 @@ each(@themes, {
|
||||||
.container {
|
.container {
|
||||||
border-left-color: ~'@{color-@{value}-theme}';
|
border-left-color: ~'@{color-@{value}-theme}';
|
||||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||||
|
// color: ~'@{color-@{value}-theme_2-font}';
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
&:before {
|
&:before {
|
||||||
|
@ -769,9 +830,24 @@ each(@themes, {
|
||||||
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
|
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
|
||||||
// border-color: ~'@{color-@{value}-theme-hover}';
|
// border-color: ~'@{color-@{value}-theme-hover}';
|
||||||
}
|
}
|
||||||
.lrc-active {
|
.lyric {
|
||||||
color: ~'@{color-@{value}-theme}';
|
: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 {
|
.footerLeftControlBtns {
|
||||||
color: ~'@{color-@{value}-theme_2-font}';
|
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">
|
<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
|
slot
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ export default {
|
||||||
min: {
|
min: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
outline: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -36,6 +40,10 @@ export default {
|
||||||
opacity: .4;
|
opacity: .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.outline {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @color-btn-hover;
|
background-color: @color-btn-hover;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +62,9 @@ each(@themes, {
|
||||||
.btn {
|
.btn {
|
||||||
color: ~'@{color-@{value}-btn}';
|
color: ~'@{color-@{value}-btn}';
|
||||||
background-color: ~'@{color-@{value}-btn-background}';
|
background-color: ~'@{color-@{value}-btn-background}';
|
||||||
|
&.outline {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ~'@{color-@{value}-btn-hover}';
|
background-color: ~'@{color-@{value}-btn-hover}';
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
||||||
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
|
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
|
||||||
| {{$t('material.list_add_modal.title_last')}}
|
| {{$t('material.list_add_modal.title_last')}}
|
||||||
div.scroll(:class="$style.btnContent")
|
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')")
|
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')
|
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')
|
use(xlink:href='#icon-addTo')
|
||||||
|
@ -57,11 +57,12 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
|
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
|
||||||
lists() {
|
lists() {
|
||||||
|
if (!this.musicInfo) return []
|
||||||
return [
|
return [
|
||||||
this.defaultList,
|
this.defaultList,
|
||||||
this.loveList,
|
this.loveList,
|
||||||
...this.userList,
|
...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() {
|
spaceNum() {
|
||||||
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default {
|
||||||
this.defaultList,
|
this.defaultList,
|
||||||
this.loveList,
|
this.loveList,
|
||||||
...this.userList,
|
...this.userList,
|
||||||
].filter(l => l.id != this.excludeListId.includes(l.id))
|
].filter(l => !this.excludeListId.includes(l.id))
|
||||||
},
|
},
|
||||||
spaceNum() {
|
spaceNum() {
|
||||||
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default {
|
||||||
singer: '',
|
singer: '',
|
||||||
},
|
},
|
||||||
page: 1,
|
page: 1,
|
||||||
total: 10,
|
total: 0,
|
||||||
maxPage: 1,
|
maxPage: 1,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
isHotLoading: true,
|
isHotLoading: true,
|
||||||
|
|
|
@ -31,8 +31,6 @@ div(:class="$style.songList")
|
||||||
td(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
td(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
||||||
material-list-buttons(:index="index" :class="$style.btns"
|
material-list-buttons(:index="index" :class="$style.btns"
|
||||||
:remove-btn="false" @btn-click="handleListBtnClick"
|
:remove-btn="false" @btn-click="handleListBtnClick"
|
||||||
:listAdd-btn="assertApiSupport(item.source)"
|
|
||||||
:play-btn="assertApiSupport(item.source)"
|
|
||||||
:download-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-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)') 试听
|
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
|
||||||
|
@ -243,7 +241,7 @@ export default {
|
||||||
this.clickIndex = index
|
this.clickIndex = index
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
|
this.emitEvent('testPlay', index)
|
||||||
this.clickTime = 0
|
this.clickTime = 0
|
||||||
this.clickIndex = -1
|
this.clickIndex = -1
|
||||||
},
|
},
|
||||||
|
@ -340,9 +338,9 @@ export default {
|
||||||
},
|
},
|
||||||
handleListItemRigthClick(event, index) {
|
handleListItemRigthClick(event, index) {
|
||||||
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
|
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
|
||||||
this.listMenu.itemMenuControl.play =
|
// this.listMenu.itemMenuControl.play =
|
||||||
this.listMenu.itemMenuControl.playLater =
|
// this.listMenu.itemMenuControl.playLater =
|
||||||
this.listMenu.itemMenuControl.download =
|
this.listMenu.itemMenuControl.download =
|
||||||
this.assertApiSupport(this.list[index].source)
|
this.assertApiSupport(this.list[index].source)
|
||||||
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
|
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
|
||||||
if (dom_selected) dom_selected.classList.remove('selected')
|
if (dom_selected) dom_selected.classList.remove('selected')
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
|
@ -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...😲"
|
||||||
|
}
|
|
@ -11,6 +11,10 @@
|
||||||
"basic_sourcename_real": "Original",
|
"basic_sourcename_real": "Original",
|
||||||
"basic_sourcename_alias": "Aliases",
|
"basic_sourcename_alias": "Aliases",
|
||||||
"basic_sourcename": "Source name",
|
"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_title": "Set the window size",
|
||||||
"basic_window_size": "Window size",
|
"basic_window_size": "Window size",
|
||||||
"basic_window_size_smaller": "Smaller",
|
"basic_window_size_smaller": "Smaller",
|
||||||
|
@ -30,6 +34,7 @@
|
||||||
"play": "Play",
|
"play": "Play",
|
||||||
"play_save_play_time": "Remember playback progress",
|
"play_save_play_time": "Remember playback progress",
|
||||||
"play_lyric_transition": "Show lyrics translation",
|
"play_lyric_transition": "Show lyrics translation",
|
||||||
|
"play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
|
||||||
"play_quality": "Play 320K quality songs first (if supported)",
|
"play_quality": "Play 320K quality songs first (if supported)",
|
||||||
"play_task_bar": "Show playing progress on the taskbar",
|
"play_task_bar": "Show playing progress on the taskbar",
|
||||||
"play_mediaDevice_title": "Select a media device for audio output",
|
"play_mediaDevice_title": "Select a media device for audio output",
|
||||||
|
@ -121,10 +126,11 @@
|
||||||
"other_tray_theme": "Tray Icon Style",
|
"other_tray_theme": "Tray Icon Style",
|
||||||
"other_tray_theme_native": "Solid Color",
|
"other_tray_theme_native": "Solid Color",
|
||||||
"other_tray_theme_origin": "Primary 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_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_cache_label": "Cache size used: ",
|
"other_resource_cache_label": "The software has used cache size: ",
|
||||||
"other_cache_label_title": "Currently used cache size",
|
"other_resource_cache_clear_btn": "Clear resource cache",
|
||||||
"other_cache_clear_btn": "Clear 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": "Update",
|
||||||
"update_latest_label": "Latest version: ",
|
"update_latest_label": "Latest version: ",
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"title": "自定义源管理",
|
||||||
|
"readme": "源编写说明:",
|
||||||
|
"note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
|
||||||
|
"btn_remove": "移除",
|
||||||
|
"btn_import": "导入",
|
||||||
|
"btn_export": "导出",
|
||||||
|
"import_file": "选择音乐API脚本文件",
|
||||||
|
"noitem": "这里竟然是空的 😲"
|
||||||
|
}
|
|
@ -11,6 +11,10 @@
|
||||||
"basic_sourcename_real": "原名",
|
"basic_sourcename_real": "原名",
|
||||||
"basic_sourcename_alias": "别名",
|
"basic_sourcename_alias": "别名",
|
||||||
"basic_sourcename": "音源名字",
|
"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_title": "设置软件窗口尺寸",
|
||||||
"basic_window_size": "窗口尺寸",
|
"basic_window_size": "窗口尺寸",
|
||||||
"basic_window_size_smaller": "较小",
|
"basic_window_size_smaller": "较小",
|
||||||
|
@ -30,6 +34,7 @@
|
||||||
"play": "播放设置",
|
"play": "播放设置",
|
||||||
"play_save_play_time": "记住播放进度",
|
"play_save_play_time": "记住播放进度",
|
||||||
"play_lyric_transition": "显示歌词翻译",
|
"play_lyric_transition": "显示歌词翻译",
|
||||||
|
"play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
|
||||||
"play_quality": "优先播放320K品质的歌曲(如果支持)",
|
"play_quality": "优先播放320K品质的歌曲(如果支持)",
|
||||||
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
||||||
"play_mediaDevice_title": "选择声音输出的媒体设备",
|
"play_mediaDevice_title": "选择声音输出的媒体设备",
|
||||||
|
@ -121,10 +126,11 @@
|
||||||
"other_tray_theme": "托盘图标样式",
|
"other_tray_theme": "托盘图标样式",
|
||||||
"other_tray_theme_native": "纯色",
|
"other_tray_theme_native": "纯色",
|
||||||
"other_tray_theme_origin": "原色",
|
"other_tray_theme_origin": "原色",
|
||||||
"other_cache": "缓存大小(清理缓存后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
"other_resource_cache": "资源缓存管理(图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||||
"other_cache_label": "软件已使用缓存大小:",
|
"other_resource_cache_label": "软件已使用缓存大小:",
|
||||||
"other_cache_label_title": "当前已用缓存",
|
"other_resource_cache_clear_btn": "清理资源缓存",
|
||||||
"other_cache_clear_btn": "清理缓存",
|
"other_play_list_cache": "列表缓存管理(我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理)",
|
||||||
|
"other_play_list_cache_clear_btn": "清理列表缓存信息",
|
||||||
|
|
||||||
"update": "软件更新",
|
"update": "软件更新",
|
||||||
"update_latest_label": "最新版本:",
|
"update_latest_label": "最新版本:",
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"title": "自定義源管理",
|
||||||
|
"readme": "源編寫說明:",
|
||||||
|
"note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
|
||||||
|
"btn_remove": "移除",
|
||||||
|
"btn_import": "導入",
|
||||||
|
"btn_export": "導出",
|
||||||
|
"import_file": "選擇音樂API腳本文件",
|
||||||
|
"noitem": "這裡竟然是空的 😲"
|
||||||
|
}
|
|
@ -11,6 +11,10 @@
|
||||||
"basic_sourcename_real": "原名",
|
"basic_sourcename_real": "原名",
|
||||||
"basic_sourcename_alias": "別名",
|
"basic_sourcename_alias": "別名",
|
||||||
"basic_sourcename": "音源名字",
|
"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_title": "設置軟件窗口尺寸",
|
||||||
"basic_window_size": "窗口尺寸",
|
"basic_window_size": "窗口尺寸",
|
||||||
"basic_window_size_smaller": "較小",
|
"basic_window_size_smaller": "較小",
|
||||||
|
@ -30,6 +34,7 @@
|
||||||
"play": "播放設置",
|
"play": "播放設置",
|
||||||
"play_save_play_time": "記住播放進度",
|
"play_save_play_time": "記住播放進度",
|
||||||
"play_lyric_transition": "顯示歌詞翻譯",
|
"play_lyric_transition": "顯示歌詞翻譯",
|
||||||
|
"play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
|
||||||
"play_quality": "優先播放320K品質的歌曲(如果支持)",
|
"play_quality": "優先播放320K品質的歌曲(如果支持)",
|
||||||
"play_task_bar": "在任務欄上顯示當前歌曲播放進度",
|
"play_task_bar": "在任務欄上顯示當前歌曲播放進度",
|
||||||
"play_mediaDevice_title": "選擇聲音輸出的媒體設備",
|
"play_mediaDevice_title": "選擇聲音輸出的媒體設備",
|
||||||
|
@ -121,10 +126,11 @@
|
||||||
"other_tray_theme": "托盤圖標樣式",
|
"other_tray_theme": "托盤圖標樣式",
|
||||||
"other_tray_theme_native": "純色",
|
"other_tray_theme_native": "純色",
|
||||||
"other_tray_theme_origin": "原色",
|
"other_tray_theme_origin": "原色",
|
||||||
"other_cache": "緩存大小(清理緩存後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
|
"other_resource_cache": "資源緩存管理(圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
|
||||||
"other_cache_label": "軟件已使用緩存大小:",
|
"other_resource_cache_label": "軟件已使用緩存大小:",
|
||||||
"other_cache_label_title": "當前已用緩存",
|
"other_resource_cache_clear_btn": "清理資源緩存",
|
||||||
"other_cache_clear_btn": "清理緩存",
|
"other_play_list_cache": "列表緩存管理(我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理)",
|
||||||
|
"other_play_list_cache_clear_btn": "清理列表緩存信息",
|
||||||
|
|
||||||
"update": "軟件更新",
|
"update": "軟件更新",
|
||||||
"update_latest_label": "最新版本:",
|
"update_latest_label": "最新版本:",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import musicSdk from '../../utils/music'
|
||||||
|
|
||||||
let allList = {}
|
let allList = {}
|
||||||
window.allList = allList
|
window.allList = allList
|
||||||
|
|
||||||
|
@ -48,7 +50,12 @@ const getters = {
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
const 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
|
// mitations
|
||||||
|
@ -57,20 +64,6 @@ const mutations = {
|
||||||
if (defaultList != null) Object.assign(state.defaultList, { list: defaultList.list, location: defaultList.location })
|
if (defaultList != null) Object.assign(state.defaultList, { list: defaultList.list, location: defaultList.location })
|
||||||
if (loveList != null) Object.assign(state.loveList, { list: loveList.list, location: loveList.location })
|
if (loveList != null) Object.assign(state.loveList, { list: loveList.list, location: loveList.location })
|
||||||
if (userList != null) state.userList = userList
|
if (userList != null) state.userList = userList
|
||||||
if (window.localStorage.getItem('isResetOtherSource') != '1') {
|
|
||||||
for (const item of defaultList.list) {
|
|
||||||
if (item.otherSource) item.otherSource = null
|
|
||||||
}
|
|
||||||
for (const item of loveList.list) {
|
|
||||||
if (item.otherSource) item.otherSource = null
|
|
||||||
}
|
|
||||||
for (const list of userList) {
|
|
||||||
for (const item of list.list) {
|
|
||||||
if (item.otherSource) item.otherSource = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.localStorage.setItem('isResetOtherSource', '1')
|
|
||||||
}
|
|
||||||
allListInit(state.defaultList, state.loveList, state.userList)
|
allListInit(state.defaultList, state.loveList, state.userList)
|
||||||
state.isInitedList = true
|
state.isInitedList = true
|
||||||
},
|
},
|
||||||
|
@ -205,6 +198,22 @@ const mutations = {
|
||||||
|
|
||||||
targetList.list.splice(sortNum - 1, 0, ...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 {
|
export default {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import music from '../../utils/music'
|
import music from '../../utils/music'
|
||||||
import { getRandom, checkPath, assertApiSupport } from '../../utils'
|
import { getRandom, checkPath } from '../../utils'
|
||||||
|
|
||||||
// state
|
// state
|
||||||
const state = {
|
const state = {
|
||||||
|
@ -18,8 +18,8 @@ const state = {
|
||||||
}
|
}
|
||||||
|
|
||||||
let urlRequest
|
let urlRequest
|
||||||
let picRequest
|
// let picRequest
|
||||||
let lrcRequest
|
// let lrcRequest
|
||||||
|
|
||||||
const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
||||||
// if (this.list.listName === null) return
|
// if (this.list.listName === null) return
|
||||||
|
@ -44,7 +44,7 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
list = listInfo.list.filter(s => {
|
list = listInfo.list.filter(s => {
|
||||||
if (!assertApiSupport(s.source)) return false
|
// if (!assertApiSupport(s.source)) return false
|
||||||
canPlayList.push(s)
|
canPlayList.push(s)
|
||||||
|
|
||||||
let index = filteredPlayedList.indexOf(s)
|
let index = filteredPlayedList.indexOf(s)
|
||||||
|
@ -62,6 +62,42 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
||||||
return list
|
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
|
// getters
|
||||||
const getters = {
|
const getters = {
|
||||||
list: state => state.listInfo.list,
|
list: state => state.listInfo.list,
|
||||||
|
@ -120,7 +156,11 @@ const actions = {
|
||||||
}
|
}
|
||||||
if (urlRequest && urlRequest.cancelHttp) urlRequest.cancelHttp()
|
if (urlRequest && urlRequest.cancelHttp) urlRequest.cancelHttp()
|
||||||
if (musicInfo.typeUrl[type] && !isRefresh) return Promise.resolve(musicInfo.typeUrl[type])
|
if (musicInfo.typeUrl[type] && !isRefresh) return Promise.resolve(musicInfo.typeUrl[type])
|
||||||
urlRequest = music[musicInfo.source].getMusicUrl(musicInfo, type)
|
try {
|
||||||
|
urlRequest = music[musicInfo.source].getMusicUrl(musicInfo, type)
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
return urlRequest.promise.then(({ url }) => {
|
return urlRequest.promise.then(({ url }) => {
|
||||||
if (originMusic) commit('setUrl', { musicInfo: originMusic, url, type })
|
if (originMusic) commit('setUrl', { musicInfo: originMusic, url, type })
|
||||||
commit('setUrl', { musicInfo, url, type })
|
commit('setUrl', { musicInfo, url, type })
|
||||||
|
@ -132,33 +172,38 @@ const actions = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getPic({ commit, state }, musicInfo) {
|
getPic({ commit, state }, musicInfo) {
|
||||||
if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
|
// if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
|
||||||
picRequest = music[musicInfo.source].getPic(musicInfo)
|
// picRequest = music[musicInfo.source].getPic(musicInfo)
|
||||||
return picRequest.promise.then(url => {
|
return getPic.call(this, musicInfo).then(url => {
|
||||||
picRequest = null
|
// picRequest = null
|
||||||
commit('getPic', { musicInfo, url })
|
commit('getPic', { musicInfo, url })
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
picRequest = null
|
// picRequest = null
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getLrc({ commit, state }, musicInfo) {
|
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 && musicInfo.tlrc != null) {
|
||||||
if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
||||||
let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
|
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)
|
||||||
lrcRequest = music[musicInfo.source].getLyric(musicInfo)
|
return getLyric.call(this, musicInfo).then(({ lyric, tlyric, lxlyric }) => {
|
||||||
return lrcRequest.promise.then(({ lyric, tlyric }) => {
|
// lrcRequest = null
|
||||||
lrcRequest = null
|
commit('setLrc', { musicInfo, lyric, tlyric, lxlyric })
|
||||||
commit('setLrc', { musicInfo, lyric, tlyric })
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
lrcRequest = null
|
// lrcRequest = null
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -292,6 +337,7 @@ const mutations = {
|
||||||
setLrc(state, datas) {
|
setLrc(state, datas) {
|
||||||
datas.musicInfo.lrc = datas.lyric
|
datas.musicInfo.lrc = datas.lyric
|
||||||
datas.musicInfo.tlrc = datas.tlyric
|
datas.musicInfo.tlrc = datas.tlyric
|
||||||
|
datas.musicInfo.lxlrc = datas.lxlyric
|
||||||
},
|
},
|
||||||
setList(state, { list, index }) {
|
setList(state, { list, index }) {
|
||||||
state.playMusicInfo = {
|
state.playMusicInfo = {
|
||||||
|
|
|
@ -396,3 +396,14 @@ export const getPlayList = () => rendererInvoke(NAMES.mainWindow.get_playlist).c
|
||||||
return rendererInvoke(NAMES.mainWindow.get_playlist, true)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
const LinePlayer = require('./line-player')
|
||||||
|
const FontPlayer = require('./font-player')
|
||||||
|
|
||||||
|
const fontTimeExp = /<(\d+),(\d+)>/g
|
||||||
|
|
||||||
|
module.exports = class Lyric {
|
||||||
|
constructor({
|
||||||
|
lyric = '',
|
||||||
|
translationLyric = '',
|
||||||
|
offset = 150,
|
||||||
|
lineClassName = '',
|
||||||
|
fontClassName = 'font',
|
||||||
|
translationClassName = 'translation',
|
||||||
|
activeLineClassName = 'active',
|
||||||
|
lineModeClassName = 'line',
|
||||||
|
shadowClassName = '',
|
||||||
|
shadowContent = false,
|
||||||
|
onPlay = function() { },
|
||||||
|
onSetLyric = function() { },
|
||||||
|
}) {
|
||||||
|
this.lyric = lyric
|
||||||
|
this.translationLyric = translationLyric
|
||||||
|
this.offset = offset
|
||||||
|
this.onPlay = onPlay
|
||||||
|
this.onSetLyric = onSetLyric
|
||||||
|
|
||||||
|
this.lineClassName = lineClassName
|
||||||
|
this.fontClassName = fontClassName
|
||||||
|
this.translationClassName = translationClassName
|
||||||
|
this.activeLineClassName = activeLineClassName
|
||||||
|
this.lineModeClassName = lineModeClassName
|
||||||
|
this.shadowClassName = shadowClassName
|
||||||
|
this.shadowContent = shadowContent
|
||||||
|
|
||||||
|
this.playingLineNum = -1
|
||||||
|
this.isLineMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
_init() {
|
||||||
|
this.playingLineNum = -1
|
||||||
|
this.isLineMode = false
|
||||||
|
|
||||||
|
if (this.linePlayer) {
|
||||||
|
this.linePlayer.setLyric(this.lyric, this.translationLyric)
|
||||||
|
} else {
|
||||||
|
this.linePlayer = new LinePlayer({
|
||||||
|
lyric: this.lyric,
|
||||||
|
translationLyric: this.translationLyric,
|
||||||
|
offset: this.offset,
|
||||||
|
onPlay: this._handleLinePlayerOnPlay,
|
||||||
|
onSetLyric: this._handleLinePlayerOnSetLyric,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleLinePlayerOnPlay = (num, text, curTime) => {
|
||||||
|
if (this.isLineMode) {
|
||||||
|
if (num < this.playingLineNum + 1) {
|
||||||
|
for (let i = this.playingLineNum; i > num - 1; i--) {
|
||||||
|
const font = this._lineFonts[i]
|
||||||
|
font.reset()
|
||||||
|
font.lineContent.classList.remove(this.activeLineClassName)
|
||||||
|
}
|
||||||
|
} else if (num > this.playingLineNum + 1) {
|
||||||
|
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
|
||||||
|
const font = this._lineFonts[i]
|
||||||
|
font.reset()
|
||||||
|
font.lineContent.classList.remove(this.activeLineClassName)
|
||||||
|
}
|
||||||
|
} else if (this.playingLineNum > -1) {
|
||||||
|
const font = this._lineFonts[this.playingLineNum]
|
||||||
|
font.reset()
|
||||||
|
font.lineContent.classList.remove(this.activeLineClassName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (num < this.playingLineNum + 1) {
|
||||||
|
for (let i = this.playingLineNum; i > num - 1; i--) {
|
||||||
|
const font = this._lineFonts[i]
|
||||||
|
font.lineContent.classList.remove(this.activeLineClassName)
|
||||||
|
font.reset()
|
||||||
|
}
|
||||||
|
} else if (num > this.playingLineNum + 1) {
|
||||||
|
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
|
||||||
|
const font = this._lineFonts[i]
|
||||||
|
font.lineContent.classList.remove(this.activeLineClassName)
|
||||||
|
font.finish()
|
||||||
|
}
|
||||||
|
} else if (this.playingLineNum > -1) {
|
||||||
|
const font = this._lineFonts[this.playingLineNum]
|
||||||
|
font.lineContent.classList.remove(this.activeLineClassName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.playingLineNum = num
|
||||||
|
const font = this._lineFonts[num]
|
||||||
|
font.lineContent.classList.add(this.activeLineClassName)
|
||||||
|
font.play(curTime - this._lines[num].time)
|
||||||
|
this.onPlay(num, this._lines[num].text)
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleLinePlayerOnSetLyric = lyricLines => {
|
||||||
|
// console.log(lyricLines)
|
||||||
|
// this._lines = lyricsLines
|
||||||
|
this.isLineMode = lyricLines.length && !/^<\d+,\d+>/.test(lyricLines[0].text)
|
||||||
|
|
||||||
|
this._lineFonts = []
|
||||||
|
if (this.isLineMode) {
|
||||||
|
this._lines = lyricLines.map(line => {
|
||||||
|
const fontPlayer = new FontPlayer({
|
||||||
|
lyric: line.text,
|
||||||
|
translationLyric: line.translation,
|
||||||
|
lineClassName: this.lineClassName,
|
||||||
|
fontClassName: this.fontClassName,
|
||||||
|
translationClassName: this.translationClassName,
|
||||||
|
lineModeClassName: this.lineModeClassName,
|
||||||
|
shadowClassName: this.shadowClassName,
|
||||||
|
shadowContent: this.shadowContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
this._lineFonts.push(fontPlayer)
|
||||||
|
return {
|
||||||
|
text: line.text,
|
||||||
|
time: line.time,
|
||||||
|
dom_line: fontPlayer.lineContent,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this._lines = lyricLines.map(line => {
|
||||||
|
const fontPlayer = new FontPlayer({
|
||||||
|
lyric: line.text,
|
||||||
|
translationLyric: line.translation,
|
||||||
|
lineClassName: this.lineClassName,
|
||||||
|
fontClassName: this.fontClassName,
|
||||||
|
translationClassName: this.translationClassName,
|
||||||
|
shadowClassName: this.shadowClassName,
|
||||||
|
shadowContent: this.shadowContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
this._lineFonts.push(fontPlayer)
|
||||||
|
return {
|
||||||
|
text: line.text.replace(fontTimeExp, ''),
|
||||||
|
time: line.time,
|
||||||
|
dom_line: fontPlayer.lineContent,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onSetLyric(this._lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
play(curTime) {
|
||||||
|
if (!this.linePlayer) return
|
||||||
|
this.linePlayer.play(curTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
if (!this.linePlayer) return
|
||||||
|
this.linePlayer.pause()
|
||||||
|
if (this.playingLineNum > -1) this._lineFonts[this.playingLineNum].pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
setLyric(lyric, translationLyric) {
|
||||||
|
this.lyric = lyric
|
||||||
|
this.translationLyric = translationLyric
|
||||||
|
this._init()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
const { getNow, TimeoutTools } = require('./utils')
|
||||||
|
|
||||||
|
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||||
|
const tagRegMap = {
|
||||||
|
title: 'ti',
|
||||||
|
artist: 'ar',
|
||||||
|
album: 'al',
|
||||||
|
offset: 'offset',
|
||||||
|
by: 'by',
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutTools = new TimeoutTools()
|
||||||
|
|
||||||
|
module.exports = class LinePlayer {
|
||||||
|
constructor({ lyric = '', translationLyric = '', offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
|
||||||
|
this.lyric = lyric
|
||||||
|
this.translationLyric = translationLyric
|
||||||
|
this.tags = {}
|
||||||
|
this.lines = null
|
||||||
|
this.translationLines = null
|
||||||
|
this.onPlay = onPlay
|
||||||
|
this.onSetLyric = onSetLyric
|
||||||
|
this.isPlay = false
|
||||||
|
this.curLineNum = 0
|
||||||
|
this.maxLine = 0
|
||||||
|
this.offset = offset
|
||||||
|
this.isOffseted = false
|
||||||
|
this._performanceTime = 0
|
||||||
|
this._performanceOffsetTime = 0
|
||||||
|
this._init()
|
||||||
|
}
|
||||||
|
|
||||||
|
_init() {
|
||||||
|
if (this.lyric == null) this.lyric = ''
|
||||||
|
if (this.translationLyric == null) this.translationLyric = ''
|
||||||
|
this._initTag()
|
||||||
|
this._initLines()
|
||||||
|
this.onSetLyric(this.lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
_initTag() {
|
||||||
|
for (let tag in tagRegMap) {
|
||||||
|
const matches = this.lyric.match(new RegExp(`\\[${tagRegMap[tag]}:([^\\]]*)]`, 'i'))
|
||||||
|
this.tags[tag] = (matches && matches[1]) || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_initLines() {
|
||||||
|
this.lines = []
|
||||||
|
this.translationLines = []
|
||||||
|
const lines = this.lyric.split('\n')
|
||||||
|
const linesMap = {}
|
||||||
|
// const translationLines = this.translationLyric.split('\n')
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim()
|
||||||
|
let result = timeExp.exec(line)
|
||||||
|
if (result) {
|
||||||
|
const text = line.replace(timeExp, '').trim()
|
||||||
|
if (text) {
|
||||||
|
const timeStr = RegExp.$1
|
||||||
|
const timeArr = timeStr.split(':')
|
||||||
|
if (timeArr.length < 3) timeArr.unshift(0)
|
||||||
|
if (timeArr[2].indexOf('.') > -1) {
|
||||||
|
timeArr.push(...timeArr[2].split('.'))
|
||||||
|
timeArr.splice(2, 1)
|
||||||
|
}
|
||||||
|
linesMap[timeStr] = {
|
||||||
|
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const translationLines = this.translationLyric.split('\n')
|
||||||
|
for (let i = 0; i < translationLines.length; i++) {
|
||||||
|
const line = translationLines[i].trim()
|
||||||
|
let result = timeExp.exec(line)
|
||||||
|
if (result) {
|
||||||
|
const text = line.replace(timeExp, '').trim()
|
||||||
|
if (text) {
|
||||||
|
const timeStr = RegExp.$1
|
||||||
|
const targetLine = linesMap[timeStr]
|
||||||
|
if (targetLine) targetLine.translation = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lines = Object.values(linesMap)
|
||||||
|
this.lines.sort((a, b) => {
|
||||||
|
return a.time - b.time
|
||||||
|
})
|
||||||
|
this.maxLine = this.lines.length - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentTime() {
|
||||||
|
return getNow() - this._performanceTime + this._performanceOffsetTime
|
||||||
|
}
|
||||||
|
|
||||||
|
_findCurLineNum(curTime) {
|
||||||
|
const length = this.lines.length
|
||||||
|
for (let index = 0; index < length; index++) if (curTime <= this.lines[index].time) return index === 0 ? 0 : index - 1
|
||||||
|
return length - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleMaxLine() {
|
||||||
|
this.onPlay(this.curLineNum, this.lines[this.curLineNum].text, this._currentTime())
|
||||||
|
this.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
_refresh() {
|
||||||
|
this.curLineNum++
|
||||||
|
// console.log('curLineNum time', this.lines[this.curLineNum].time)
|
||||||
|
let curLine = this.lines[this.curLineNum]
|
||||||
|
let nextLine = this.lines[this.curLineNum + 1]
|
||||||
|
const currentTime = this._currentTime()
|
||||||
|
const driftTime = currentTime - curLine.time
|
||||||
|
|
||||||
|
if (driftTime >= 0 || this.curLineNum === 0) {
|
||||||
|
if (this.curLineNum === this.maxLine) return this._handleMaxLine()
|
||||||
|
this.delay = nextLine.time - curLine.time - driftTime
|
||||||
|
if (this.delay > 0) {
|
||||||
|
if (!this.isOffseted && this.delay >= this.offset) {
|
||||||
|
this._performanceOffsetTime += this.offset
|
||||||
|
this.delay -= this.offset
|
||||||
|
this.isOffseted = true
|
||||||
|
}
|
||||||
|
timeoutTools.start(() => {
|
||||||
|
this._refresh()
|
||||||
|
}, this.delay)
|
||||||
|
this.onPlay(this.curLineNum, curLine.text, currentTime)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.curLineNum = this._findCurLineNum(currentTime) - 1
|
||||||
|
this._refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
play(curTime = 0) {
|
||||||
|
if (!this.lines.length) return
|
||||||
|
this.pause()
|
||||||
|
this.isPlay = true
|
||||||
|
|
||||||
|
this._performanceOffsetTime = 0
|
||||||
|
this._performanceTime = getNow() - curTime
|
||||||
|
if (this._performanceTime < 0) {
|
||||||
|
this._performanceOffsetTime = -this._performanceTime
|
||||||
|
this._performanceTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
this.curLineNum = this._findCurLineNum(curTime) - 1
|
||||||
|
|
||||||
|
this._refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
if (!this.isPlay) return
|
||||||
|
this.isPlay = false
|
||||||
|
this.isOffseted = false
|
||||||
|
timeoutTools.clear()
|
||||||
|
if (this.curLineNum === this.maxLine) return
|
||||||
|
const currentTime = this._currentTime()
|
||||||
|
const curLineNum = this._findCurLineNum(currentTime)
|
||||||
|
if (this.curLineNum !== curLineNum) {
|
||||||
|
this.curLineNum = curLineNum
|
||||||
|
this.onPlay(curLineNum, this.lines[curLineNum].text, currentTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLyric(lyric, translationLyric) {
|
||||||
|
// console.log(translationLyric)
|
||||||
|
if (this.isPlay) this.pause()
|
||||||
|
this.lyric = lyric
|
||||||
|
this.translationLyric = translationLyric
|
||||||
|
this._init()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
const getNow = exports.getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
|
||||||
|
|
||||||
|
exports.TimeoutTools = class TimeoutTools {
|
||||||
|
constructor() {
|
||||||
|
this.invokeTime = 0
|
||||||
|
this.animationFrameId = null
|
||||||
|
this.timeoutId = null
|
||||||
|
this.callback = null
|
||||||
|
this.thresholdTime = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
this.animationFrameId = window.requestAnimationFrame(() => {
|
||||||
|
this.animationFrameId = null
|
||||||
|
let diff = this.invokeTime - getNow()
|
||||||
|
// console.log('diff', diff)
|
||||||
|
if (diff > 0) {
|
||||||
|
if (diff < this.thresholdTime) return this.run()
|
||||||
|
return this.timeoutId = setTimeout(() => {
|
||||||
|
this.timeoutId = null
|
||||||
|
this.run()
|
||||||
|
}, diff - this.thresholdTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('diff', diff)
|
||||||
|
this.callback(diff)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start(callback = () => {}, timeout = 0) {
|
||||||
|
// console.log(timeout)
|
||||||
|
this.callback = callback
|
||||||
|
this.invokeTime = getNow() + timeout
|
||||||
|
|
||||||
|
this.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
if (this.animationFrameId) {
|
||||||
|
window.cancelAnimationFrame(this.animationFrameId)
|
||||||
|
this.animationFrameId = null
|
||||||
|
}
|
||||||
|
if (this.timeoutId) {
|
||||||
|
window.clearTimeout(this.timeoutId)
|
||||||
|
this.timeoutId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ module.exports = [
|
||||||
tx: ['128k'],
|
tx: ['128k'],
|
||||||
wy: ['128k'],
|
wy: ['128k'],
|
||||||
mg: ['128k'],
|
mg: ['128k'],
|
||||||
xm: ['128k'],
|
|
||||||
// bd: ['128k'],
|
// bd: ['128k'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,6 +13,7 @@ for (const api of apiSourceInfo) {
|
||||||
const getAPI = source => apiList[`${window.globalObj.apiSource}_api_${source}`]
|
const getAPI = source => apiList[`${window.globalObj.apiSource}_api_${source}`]
|
||||||
|
|
||||||
const apis = source => {
|
const apis = source => {
|
||||||
|
if (/^user_api/.test(window.globalObj.apiSource)) return window.globalObj.userApi.apis[source]
|
||||||
let api = getAPI(source)
|
let api = getAPI(source)
|
||||||
if (api) return api
|
if (api) return api
|
||||||
throw new Error('Api is not found')
|
throw new Error('Api is not found')
|
||||||
|
|
|
@ -50,10 +50,12 @@ const sources = {
|
||||||
export default {
|
export default {
|
||||||
...sources,
|
...sources,
|
||||||
init() {
|
init() {
|
||||||
|
const tasks = []
|
||||||
for (let source of sources.sources) {
|
for (let source of sources.sources) {
|
||||||
let sm = sources[source.id]
|
let sm = sources[source.id]
|
||||||
sm && sm.init && sm.init()
|
sm && sm.init && tasks.push(sm.init())
|
||||||
}
|
}
|
||||||
|
return Promise.all(tasks)
|
||||||
},
|
},
|
||||||
supportQuality,
|
supportQuality,
|
||||||
|
|
||||||
|
@ -74,26 +76,31 @@ export default {
|
||||||
return tempResult
|
return tempResult
|
||||||
}
|
}
|
||||||
const trimStr = str => typeof str == 'string' ? str.trim() : str
|
const trimStr = str => typeof str == 'string' ? str.trim() : str
|
||||||
const sortedSinger = sortSingle(musicInfo.singer)
|
const sortedSinger = String(sortSingle(musicInfo.singer)).toLowerCase()
|
||||||
const musicName = trimStr(musicInfo.name)
|
const musicName = trimStr(musicInfo.name)
|
||||||
|
const lowerCaseName = String(musicName).toLowerCase()
|
||||||
|
const lowerCaseAlbumName = String(musicInfo.albumName).toLowerCase()
|
||||||
for (const source of sources.sources) {
|
for (const source of sources.sources) {
|
||||||
if (!sources[source.id].musicSearch || source.id === musicInfo.source || source.id === 'xm') continue
|
if (!sources[source.id].musicSearch || source.id === musicInfo.source || source.id === 'xm') continue
|
||||||
|
|
||||||
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${musicInfo.singer || ''}`.trim(), 1, { limit: 10 }).then(res => {
|
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${musicInfo.singer || ''}`.trim(), 1, { limit: 10 }).then(res => {
|
||||||
for (const item of res.list) {
|
for (const item of res.list) {
|
||||||
item.sortedSinger = sortSingle(item.singer)
|
item.sortedSinger = String(sortSingle(item.singer)).toLowerCase()
|
||||||
item.name = trimStr(item.name)
|
item.name = trimStr(item.name)
|
||||||
|
item.lowerCaseName = String(item.name).toLowerCase()
|
||||||
|
item.lowerCaseAlbumName = String(item.albumName).toLowerCase()
|
||||||
|
// console.log(lowerCaseName, item.lowerCaseName)
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
item.sortedSinger === sortedSinger &&
|
item.sortedSinger === sortedSinger &&
|
||||||
(item.name === musicName || item.interval === musicInfo.interval)
|
(item.lowerCaseName === lowerCaseName || item.interval === musicInfo.interval)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
item.interval === musicInfo.interval && item.name === musicName &&
|
item.interval === musicInfo.interval && item.lowerCaseName === lowerCaseName &&
|
||||||
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
item.name === musicName && item.albumName === musicInfo.albumName &&
|
item.lowerCaseName === lowerCaseName && item.lowerCaseAlbumName === lowerCaseAlbumName &&
|
||||||
item.interval === musicInfo.interval
|
item.interval === musicInfo.interval
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -106,12 +113,13 @@ export default {
|
||||||
const result = (await Promise.all(tasks)).filter(s => s)
|
const result = (await Promise.all(tasks)).filter(s => s)
|
||||||
const newResult = []
|
const newResult = []
|
||||||
if (result.length) {
|
if (result.length) {
|
||||||
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.name === musicName && item.interval === musicInfo.interval))
|
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.lowerCaseName === lowerCaseName && item.interval === musicInfo.interval))
|
||||||
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.interval === musicInfo.interval))
|
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.interval === musicInfo.interval))
|
||||||
newResult.push(...sortMusic(result, item => item.name === musicName && item.sortedSinger === sortedSinger && item.albumName === musicInfo.albumName))
|
newResult.push(...sortMusic(result, item => item.lowerCaseName === lowerCaseName && item.sortedSinger === sortedSinger && item.lowerCaseAlbumName === lowerCaseAlbumName))
|
||||||
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.name === musicName))
|
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.lowerCaseName === lowerCaseName))
|
||||||
for (const item of result) {
|
for (const item of result) {
|
||||||
delete item.sortedSinger
|
delete item.sortedSinger
|
||||||
|
delete item.lowerCaseName
|
||||||
}
|
}
|
||||||
newResult.push(...result)
|
newResult.push(...result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ const kg = {
|
||||||
return pic.getPic(songInfo)
|
return pic.getPic(songInfo)
|
||||||
},
|
},
|
||||||
getMusicDetailPageUrl(songInfo) {
|
getMusicDetailPageUrl(songInfo) {
|
||||||
return `https://www.kugou.com/song/#hash=${songInfo.hash}`
|
return `https://www.kugou.com/song/#hash=${songInfo.hash}&album_id=${songInfo.albumId}`
|
||||||
},
|
},
|
||||||
// getPic(songInfo) {
|
// getPic(songInfo) {
|
||||||
// return apis('kg').getPic(songInfo)
|
// return apis('kg').getPic(songInfo)
|
||||||
|
|
|
@ -2,10 +2,13 @@ import { httpFetch } from '../../request'
|
||||||
import { decodeLyric } from './util'
|
import { decodeLyric } from './util'
|
||||||
import { decodeName } from '../..'
|
import { decodeName } from '../..'
|
||||||
|
|
||||||
|
const headExp = /^.*\[id:\$\w+\]\n/
|
||||||
|
|
||||||
const parseLyric = str => {
|
const parseLyric = str => {
|
||||||
str = str.replace(/(?:<\d+,\d+,\d+>|\r)/g, '')
|
str = str.replace(/\r/g, '')
|
||||||
if (str.startsWith('\ufeff[id:$00000000]')) str = str.replace('\ufeff[id:$00000000]\n', '')
|
if (headExp.test(str)) str = str.replace(headExp, '')
|
||||||
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
|
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
|
||||||
|
let lyric
|
||||||
let tlyric
|
let tlyric
|
||||||
if (trans) {
|
if (trans) {
|
||||||
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
|
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
|
||||||
|
@ -18,7 +21,7 @@ const parseLyric = str => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let i = 0
|
let i = 0
|
||||||
let lyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
|
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
|
||||||
let result = str.match(/\[((\d+),\d+)\].*/)
|
let result = str.match(/\[((\d+),\d+)\].*/)
|
||||||
let time = parseInt(result[2])
|
let time = parseInt(result[2])
|
||||||
let ms = time % 1000
|
let ms = time % 1000
|
||||||
|
@ -31,11 +34,14 @@ const parseLyric = str => {
|
||||||
return str.replace(result[1], time)
|
return str.replace(result[1], time)
|
||||||
})
|
})
|
||||||
tlyric = tlyric ? tlyric.join('\n') : ''
|
tlyric = tlyric ? tlyric.join('\n') : ''
|
||||||
lyric = decodeName(lyric)
|
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
|
||||||
|
lxlyric = decodeName(lxlyric)
|
||||||
|
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
|
||||||
tlyric = decodeName(tlyric)
|
tlyric = decodeName(tlyric)
|
||||||
return {
|
return {
|
||||||
lyric,
|
lyric,
|
||||||
tlyric,
|
tlyric,
|
||||||
|
lxlyric,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +125,7 @@ export default {
|
||||||
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
|
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
|
||||||
|
|
||||||
requestObj.promise = requestObj.promise.then(result => {
|
requestObj.promise = requestObj.promise.then(result => {
|
||||||
if (!result) return { lyric: '', tlyric: '' }
|
if (!result) return { lyric: null, tlyric: null, lxlyric: null }
|
||||||
|
|
||||||
let requestObj2 = this.getLyricDownload(result.id, result.accessKey)
|
let requestObj2 = this.getLyricDownload(result.id, result.accessKey)
|
||||||
|
|
||||||
|
|
|
@ -16,56 +16,67 @@ export default {
|
||||||
searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
|
searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
|
||||||
return searchRequest.promise.then(({ body }) => body)
|
return searchRequest.promise.then(({ body }) => body)
|
||||||
},
|
},
|
||||||
|
filterData(rawData) {
|
||||||
|
const types = []
|
||||||
|
const _types = {}
|
||||||
|
if (rawData.filesize !== 0) {
|
||||||
|
let size = sizeFormate(rawData.filesize)
|
||||||
|
types.push({ type: '128k', size, hash: rawData.hash })
|
||||||
|
_types['128k'] = {
|
||||||
|
size,
|
||||||
|
hash: rawData.hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rawData['320filesize'] !== 0) {
|
||||||
|
let size = sizeFormate(rawData['320filesize'])
|
||||||
|
types.push({ type: '320k', size, hash: rawData['320hash'] })
|
||||||
|
_types['320k'] = {
|
||||||
|
size,
|
||||||
|
hash: rawData['320hash'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rawData.sqfilesize !== 0) {
|
||||||
|
let size = sizeFormate(rawData.sqfilesize)
|
||||||
|
types.push({ type: 'flac', size, hash: rawData.sqhash })
|
||||||
|
_types.flac = {
|
||||||
|
size,
|
||||||
|
hash: rawData.sqhash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
singer: decodeName(rawData.singername),
|
||||||
|
name: decodeName(rawData.songname),
|
||||||
|
albumName: decodeName(rawData.album_name),
|
||||||
|
albumId: rawData.album_id,
|
||||||
|
songmid: rawData.hash,
|
||||||
|
source: 'kg',
|
||||||
|
interval: formatPlayTime(rawData.duration),
|
||||||
|
_interval: rawData.duration,
|
||||||
|
img: null,
|
||||||
|
lrc: null,
|
||||||
|
otherSource: null,
|
||||||
|
hash: rawData.hash,
|
||||||
|
types,
|
||||||
|
_types,
|
||||||
|
audioId: rawData.audio_id + '_' + rawData.hash,
|
||||||
|
typeUrl: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
handleResult(rawData) {
|
handleResult(rawData) {
|
||||||
// console.log(rawData)
|
// console.log(rawData)
|
||||||
let ids = new Set()
|
let ids = new Set()
|
||||||
const list = []
|
const list = []
|
||||||
rawData.forEach(item => {
|
rawData.forEach(item => {
|
||||||
if (ids.has(item.audio_id)) return
|
const key = item.audio_id + item.hash
|
||||||
ids.add(item.audio_id)
|
if (ids.has(key)) return
|
||||||
const types = []
|
ids.add(key)
|
||||||
const _types = {}
|
list.push(this.filterData(item))
|
||||||
if (item.filesize !== 0) {
|
for (const childItem of item.group) {
|
||||||
let size = sizeFormate(item.filesize)
|
const key = item.audio_id + item.hash
|
||||||
types.push({ type: '128k', size, hash: item.hash })
|
if (ids.has(key)) return
|
||||||
_types['128k'] = {
|
ids.add(key)
|
||||||
size,
|
list.push(this.filterData(childItem))
|
||||||
hash: item.hash,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (item['320filesize'] !== 0) {
|
|
||||||
let size = sizeFormate(item['320filesize'])
|
|
||||||
types.push({ type: '320k', size, hash: item['320hash'] })
|
|
||||||
_types['320k'] = {
|
|
||||||
size,
|
|
||||||
hash: item['320hash'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item.sqfilesize !== 0) {
|
|
||||||
let size = sizeFormate(item.sqfilesize)
|
|
||||||
types.push({ type: 'flac', size, hash: item.sqhash })
|
|
||||||
_types.flac = {
|
|
||||||
size,
|
|
||||||
hash: item.sqhash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.push({
|
|
||||||
singer: decodeName(item.singername),
|
|
||||||
name: decodeName(item.songname),
|
|
||||||
albumName: decodeName(item.album_name),
|
|
||||||
albumId: item.album_id,
|
|
||||||
songmid: item.audio_id,
|
|
||||||
source: 'kg',
|
|
||||||
interval: formatPlayTime(item.duration),
|
|
||||||
_interval: item.duration,
|
|
||||||
img: null,
|
|
||||||
lrc: null,
|
|
||||||
otherSource: null,
|
|
||||||
hash: item.hash,
|
|
||||||
types,
|
|
||||||
_types,
|
|
||||||
typeUrl: {},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,7 +37,9 @@ export default {
|
||||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||||
if (body.error_code !== 0) return Promise.reject('图片获取失败')
|
if (body.error_code !== 0) return Promise.reject('图片获取失败')
|
||||||
let info = body.data[0].info
|
let info = body.data[0].info
|
||||||
return info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
|
const img = info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
|
||||||
|
if (!img) return Promise.reject('Pic get failed')
|
||||||
|
return img
|
||||||
})
|
})
|
||||||
return requestObj
|
return requestObj
|
||||||
},
|
},
|
||||||
|
|
|
@ -106,7 +106,7 @@ const kw = {
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
getToken()
|
return getToken()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,10 +48,12 @@ export default {
|
||||||
return `https://app.c.nf.migu.cn/MIGUM2.0/v1.0/user/queryMusicListSongs.do?musicListId=${id}&pageNo=${page}&pageSize=${this.limit_song}`
|
return `https://app.c.nf.migu.cn/MIGUM2.0/v1.0/user/queryMusicListSongs.do?musicListId=${id}&pageNo=${page}&pageSize=${this.limit_song}`
|
||||||
},
|
},
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
language: 'Chinese',
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
|
||||||
ua: 'Android_migu',
|
Referer: 'https://m.music.migu.cn/',
|
||||||
mode: 'android',
|
// language: 'Chinese',
|
||||||
version: '6.8.5',
|
// ua: 'Android_migu',
|
||||||
|
// mode: 'android',
|
||||||
|
// version: '6.8.5',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,6 +157,7 @@ export default {
|
||||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
this._requestObj_list = httpFetch(this.getSongListUrl(sortId, tagId, page), {
|
this._requestObj_list = httpFetch(this.getSongListUrl(sortId, tagId, page), {
|
||||||
|
headers: this.defaultHeaders,
|
||||||
// headers: {
|
// headers: {
|
||||||
// sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
|
// sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
|
||||||
// timestamp: 1578225871982,
|
// timestamp: 1578225871982,
|
||||||
|
@ -186,6 +189,7 @@ export default {
|
||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
return this._requestObj_list.promise.then(({ body }) => {
|
return this._requestObj_list.promise.then(({ body }) => {
|
||||||
|
// console.log(body)
|
||||||
if (body.retCode !== '100000' || body.retMsg.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
if (body.retCode !== '100000' || body.retMsg.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||||
return {
|
return {
|
||||||
list: this.filterList(body.retMsg.playlist),
|
list: this.filterList(body.retMsg.playlist),
|
||||||
|
|
|
@ -20,7 +20,9 @@ const wy = {
|
||||||
return getLyric(songInfo.songmid)
|
return getLyric(songInfo.songmid)
|
||||||
},
|
},
|
||||||
getPic(songInfo) {
|
getPic(songInfo) {
|
||||||
return getMusicInfo(songInfo.songmid).then(info => info.al.picUrl)
|
const requestObj = getMusicInfo(songInfo.songmid)
|
||||||
|
requestObj.promise = requestObj.promise.then(info => info.al.picUrl)
|
||||||
|
return requestObj
|
||||||
},
|
},
|
||||||
getMusicDetailPageUrl(songInfo) {
|
getMusicDetailPageUrl(songInfo) {
|
||||||
return `https://music.163.com/#/song?id=${songInfo.songmid}`
|
return `https://music.163.com/#/song?id=${songInfo.songmid}`
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
// import { apis } from '../api-source'
|
||||||
|
// import leaderboard from './leaderboard'
|
||||||
|
// import songList from './songList'
|
||||||
|
// import musicSearch from './musicSearch'
|
||||||
|
// import pic from './pic'
|
||||||
|
// import lyric from './lyric'
|
||||||
|
// import hotSearch from './hotSearch'
|
||||||
|
// import comment from './comment'
|
||||||
|
// import musicInfo from './musicInfo'
|
||||||
|
// import { closeVerifyModal } from './util'
|
||||||
|
|
||||||
|
const xm = {
|
||||||
|
// songList,
|
||||||
|
// musicSearch,
|
||||||
|
// leaderboard,
|
||||||
|
// hotSearch,
|
||||||
|
// closeVerifyModal,
|
||||||
|
comment: {
|
||||||
|
getComment() {
|
||||||
|
return Promise.reject(new Error('fail'))
|
||||||
|
},
|
||||||
|
getHotComment() {
|
||||||
|
return Promise.reject(new Error('fail'))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getMusicUrl(songInfo, type) {
|
||||||
|
return {
|
||||||
|
promise: Promise.reject(new Error('fail')),
|
||||||
|
}
|
||||||
|
// return apis('xm').getMusicUrl(songInfo, type)
|
||||||
|
},
|
||||||
|
getLyric(songInfo) {
|
||||||
|
return {
|
||||||
|
promise: Promise.reject(new Error('fail')),
|
||||||
|
}
|
||||||
|
// return lyric.getLyric(songInfo)
|
||||||
|
},
|
||||||
|
getPic(songInfo) {
|
||||||
|
return {
|
||||||
|
promise: Promise.reject(new Error('fail')),
|
||||||
|
}
|
||||||
|
// return pic.getPic(songInfo)
|
||||||
|
},
|
||||||
|
// getMusicDetailPageUrl(songInfo) {
|
||||||
|
// if (songInfo.songStringId) return `https://www.xiami.com/song/${songInfo.songStringId}`
|
||||||
|
|
||||||
|
// musicInfo.getMusicInfo(songInfo).then(({ data }) => {
|
||||||
|
// songInfo.songStringId = data.songStringId
|
||||||
|
// })
|
||||||
|
// return `https://www.xiami.com/song/${songInfo.songmid}`
|
||||||
|
// },
|
||||||
|
// init() {
|
||||||
|
// getToken()
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default xm
|
|
@ -1,20 +0,0 @@
|
||||||
import { httpFetch } from '../../request'
|
|
||||||
import { requestMsg } from '../../message'
|
|
||||||
import { headers, timeout } from '../options'
|
|
||||||
|
|
||||||
const api_test = {
|
|
||||||
getMusicUrl(songInfo, type) {
|
|
||||||
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/xm/${songInfo.songmid}/${type}`, {
|
|
||||||
method: 'get',
|
|
||||||
timeout,
|
|
||||||
headers,
|
|
||||||
family: 4,
|
|
||||||
})
|
|
||||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
|
||||||
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default api_test
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
import { dateFormat2 } from '../../'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
_requestObj: null,
|
|
||||||
_requestObj2: null,
|
|
||||||
async getComment({ songmid }, page = 1, limit = 20) {
|
|
||||||
if (this._requestObj) this._requestObj.cancelHttp()
|
|
||||||
|
|
||||||
const _requestObj = xmRequest('/api/comment/getCommentList', { objectId: songmid, objectType: 'song', pagingVO: { page, pageSize: limit } })
|
|
||||||
const { body, statusCode } = await _requestObj.promise
|
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取评论失败')
|
|
||||||
return { source: 'xm', comments: this.filterComment(body.result.data.commentList), total: body.result.data.pagingVO.count, page, limit, maxPage: Math.ceil(body.result.data.pagingVO.count / limit) || 1 }
|
|
||||||
},
|
|
||||||
async getHotComment({ songmid }, page = 1, limit = 100) {
|
|
||||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
|
||||||
if (!songmid) throw new Error('获取失败')
|
|
||||||
const _requestObj2 = xmRequest('/api/comment/getHotCommentList', { objectId: songmid, objectType: 'song', pagingVO: { page, pageSize: limit } })
|
|
||||||
const { body, statusCode } = await _requestObj2.promise
|
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取热门评论失败')
|
|
||||||
return { source: 'xm', comments: this.filterComment(body.result.data.hotList) }
|
|
||||||
},
|
|
||||||
filterComment(rawList) {
|
|
||||||
return rawList.map(item => ({
|
|
||||||
id: item.commentId,
|
|
||||||
text: item.message.split('\n').filter(t => !!t),
|
|
||||||
time: item.gmtCreate,
|
|
||||||
timeStr: dateFormat2(item.gmtCreate),
|
|
||||||
userName: item.nickName,
|
|
||||||
avatar: item.avatar,
|
|
||||||
userId: item.userId,
|
|
||||||
likedCount: item.likes,
|
|
||||||
reply: item.replyData ? item.replyData.map(c => ({
|
|
||||||
id: c.commentId,
|
|
||||||
text: c.message.split('\n').filter(t => !!t),
|
|
||||||
time: c.gmtCreate,
|
|
||||||
timeStr: dateFormat2(c.gmtCreate),
|
|
||||||
userName: c.nickName,
|
|
||||||
avatar: c.avatar,
|
|
||||||
userId: c.userId,
|
|
||||||
likedCount: c.likes,
|
|
||||||
})) : [],
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
// import { xmRequest } from './util'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
_requestObj: null,
|
|
||||||
async getList(retryNum = 0) {
|
|
||||||
// if (this._requestObj) this._requestObj.cancelHttp()
|
|
||||||
// if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
|
||||||
|
|
||||||
// const _requestObj = xmRequest('/api/search/getHotSearchWords')
|
|
||||||
// const { body, statusCode } = await _requestObj.promise
|
|
||||||
// // console.log(body)
|
|
||||||
// if (statusCode != 200 || body.code !== 'SUCCESS') return this.getList(++retryNum)
|
|
||||||
// // console.log(body, statusCode)
|
|
||||||
// return { source: 'xm', list: this.filterList(body.result.data.hotWords) }
|
|
||||||
return { source: 'xm', list: [] }
|
|
||||||
},
|
|
||||||
filterList(rawList) {
|
|
||||||
return rawList.map(item => item.word)
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import { apis } from '../api-source'
|
|
||||||
import leaderboard from './leaderboard'
|
|
||||||
import songList from './songList'
|
|
||||||
import musicSearch from './musicSearch'
|
|
||||||
// import pic from './pic'
|
|
||||||
import lyric from './lyric'
|
|
||||||
import hotSearch from './hotSearch'
|
|
||||||
import comment from './comment'
|
|
||||||
import musicInfo from './musicInfo'
|
|
||||||
import { closeVerifyModal } from './util'
|
|
||||||
|
|
||||||
const xm = {
|
|
||||||
songList,
|
|
||||||
musicSearch,
|
|
||||||
leaderboard,
|
|
||||||
hotSearch,
|
|
||||||
closeVerifyModal,
|
|
||||||
comment,
|
|
||||||
getMusicUrl(songInfo, type) {
|
|
||||||
return apis('xm').getMusicUrl(songInfo, type)
|
|
||||||
},
|
|
||||||
getLyric(songInfo) {
|
|
||||||
return lyric.getLyric(songInfo)
|
|
||||||
},
|
|
||||||
getPic(songInfo) {
|
|
||||||
return Promise.reject(new Error('fail'))
|
|
||||||
// return pic.getPic(songInfo)
|
|
||||||
},
|
|
||||||
getMusicDetailPageUrl(songInfo) {
|
|
||||||
if (songInfo.songStringId) return `https://www.xiami.com/song/${songInfo.songStringId}`
|
|
||||||
|
|
||||||
musicInfo.getMusicInfo(songInfo).then(({ data }) => {
|
|
||||||
songInfo.songStringId = data.songStringId
|
|
||||||
})
|
|
||||||
return `https://www.xiami.com/song/${songInfo.songmid}`
|
|
||||||
},
|
|
||||||
// init() {
|
|
||||||
// getToken()
|
|
||||||
// },
|
|
||||||
}
|
|
||||||
|
|
||||||
export default xm
|
|
|
@ -1,211 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
import { formatPlayTime, sizeFormate } from '../../index'
|
|
||||||
// import jshtmlencode from 'js-htmlencode'
|
|
||||||
|
|
||||||
let boardList = [{ id: 'xm__102', name: '新歌榜', bangid: '102' }, { id: 'xm__103', name: '热歌榜', bangid: '103' }, { id: 'xm__104', name: '原创榜', bangid: '104' }, { id: 'xm__306', name: 'K歌榜', bangid: '306' }, { id: 'xm__332', name: '抖音热歌榜', bangid: '332' }, { id: 'xm__305', name: '歌单收录榜', bangid: '305' }, { id: 'xm__327', name: '趴间热歌榜', bangid: '327' }, { id: 'xm__324', name: '影视原声榜', bangid: '324' }, { id: 'xm__204', name: '美国Billboard单曲榜', bangid: '204' }, { id: 'xm__206', name: '韩国MNET音乐排行榜', bangid: '206' }, { id: 'xm__201', name: 'Hito 中文排行榜', bangid: '201' }, { id: 'xm__203', name: '英国UK单曲榜', bangid: '203' }, { id: 'xm__205', name: 'oricon公信单曲榜', bangid: '205' }, { id: 'xm__328', name: '美国iTunes榜', bangid: '328' }, { id: 'xm__329', name: 'Beatport电音榜', bangid: '329' }, { id: 'xm__330', name: '香港商业电台榜', bangid: '330' }]
|
|
||||||
|
|
||||||
export default {
|
|
||||||
limit: 200,
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
id: 'xmrgb',
|
|
||||||
name: '热歌榜',
|
|
||||||
bangid: '103',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmxgb',
|
|
||||||
name: '新歌榜',
|
|
||||||
bangid: '102',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmrcb',
|
|
||||||
name: '原创榜',
|
|
||||||
bangid: '104',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmdyb',
|
|
||||||
name: '抖音榜',
|
|
||||||
bangid: '332',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmkgb',
|
|
||||||
name: 'K歌榜',
|
|
||||||
bangid: '306',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmfxb',
|
|
||||||
name: '分享榜',
|
|
||||||
bangid: '307',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmrdtlb',
|
|
||||||
name: '讨论榜',
|
|
||||||
bangid: '331',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmgdslb',
|
|
||||||
name: '歌单榜',
|
|
||||||
bangid: '305',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmpjrgb',
|
|
||||||
name: '趴间榜',
|
|
||||||
bangid: '327',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xmysysb',
|
|
||||||
name: '影视榜',
|
|
||||||
bangid: '324',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requestBoardsObj: null,
|
|
||||||
requestObj: null,
|
|
||||||
getBoardsData() {
|
|
||||||
if (this.requestBoardsObj) this.requestBoardsObj.cancelHttp()
|
|
||||||
this.requestBoardsObj = xmRequest('/api/billboard/getBillboards')
|
|
||||||
return this.requestBoardsObj.promise
|
|
||||||
},
|
|
||||||
getData(id) {
|
|
||||||
if (this.requestObj) this.requestObj.cancelHttp()
|
|
||||||
this.requestObj = xmRequest('/api/billboard/getBillboardDetail', { billboardId: id })
|
|
||||||
return this.requestObj.promise
|
|
||||||
},
|
|
||||||
getSinger(singers) {
|
|
||||||
let arr = []
|
|
||||||
singers.forEach(singer => {
|
|
||||||
arr.push(singer.artistName)
|
|
||||||
})
|
|
||||||
return arr.join('、')
|
|
||||||
},
|
|
||||||
filterData(rawList) {
|
|
||||||
// console.log(rawList)
|
|
||||||
let ids = new Set()
|
|
||||||
const list = []
|
|
||||||
rawList.forEach(songData => {
|
|
||||||
if (!songData) return
|
|
||||||
if (ids.has(songData.songId)) return
|
|
||||||
ids.add(songData.songId)
|
|
||||||
|
|
||||||
const types = []
|
|
||||||
const _types = {}
|
|
||||||
let size = null
|
|
||||||
for (const item of songData.purviewRoleVOs) {
|
|
||||||
if (!item.filesize) continue
|
|
||||||
size = sizeFormate(item.filesize)
|
|
||||||
switch (item.quality) {
|
|
||||||
case 's':
|
|
||||||
types.push({ type: 'wav', size })
|
|
||||||
_types.wav = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'h':
|
|
||||||
types.push({ type: '320k', size })
|
|
||||||
_types['320k'] = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'l':
|
|
||||||
types.push({ type: '128k', size })
|
|
||||||
_types['128k'] = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
types.reverse()
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
singer: this.getSinger(songData.singerVOs),
|
|
||||||
name: songData.songName,
|
|
||||||
albumName: songData.albumName,
|
|
||||||
albumId: songData.albumId,
|
|
||||||
source: 'xm',
|
|
||||||
interval: formatPlayTime(parseInt(songData.length / 1000)),
|
|
||||||
songmid: songData.songId,
|
|
||||||
img: songData.albumLogo || songData.albumLogoS,
|
|
||||||
songStringId: songData.songStringId,
|
|
||||||
lrc: null,
|
|
||||||
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
|
||||||
otherSource: null,
|
|
||||||
types,
|
|
||||||
_types,
|
|
||||||
typeUrl: {},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
filterBoardsData(rawList) {
|
|
||||||
// console.log(rawList)
|
|
||||||
let list = []
|
|
||||||
if (rawList.xiamiBillboards) {
|
|
||||||
for (const board of rawList.xiamiBillboards) {
|
|
||||||
if (board.itemType != 1) continue
|
|
||||||
list.push({
|
|
||||||
id: 'xm__' + board.billboardId,
|
|
||||||
name: board.name,
|
|
||||||
bangid: String(board.billboardId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rawList.spBillboards) {
|
|
||||||
for (const board of rawList.spBillboards) {
|
|
||||||
if (board.itemType != 1) continue
|
|
||||||
list.push({
|
|
||||||
id: 'xm__' + board.billboardId,
|
|
||||||
name: board.name,
|
|
||||||
bangid: String(board.billboardId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rawList.globalBillboards) {
|
|
||||||
for (const board of rawList.globalBillboards) {
|
|
||||||
if (board.itemType != 1) continue
|
|
||||||
list.push({
|
|
||||||
id: 'xm__' + board.billboardId,
|
|
||||||
name: board.name,
|
|
||||||
bangid: String(board.billboardId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
async getBoards(retryNum = 0) {
|
|
||||||
// if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
|
||||||
// let response
|
|
||||||
// try {
|
|
||||||
// response = await this.getBoardsData()
|
|
||||||
// } catch (error) {
|
|
||||||
// return this.getBoards(retryNum)
|
|
||||||
// }
|
|
||||||
// if (response.statusCode !== 200 || response.body.code !== 'SUCCESS') return this.getBoards(retryNum)
|
|
||||||
// const list = this.filterBoardsData(response.body.result.data)
|
|
||||||
// this.list = list
|
|
||||||
// return {
|
|
||||||
// list,
|
|
||||||
// source: 'xm',
|
|
||||||
// }
|
|
||||||
this.list = boardList
|
|
||||||
return {
|
|
||||||
list: boardList,
|
|
||||||
source: 'xm',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getList(bangid, page, retryNum = 0) {
|
|
||||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
|
||||||
return this.getData(bangid).then(({ statusCode, body }) => {
|
|
||||||
if (statusCode !== 200 || body.code !== 'SUCCESS') return this.getList(bangid, page, retryNum)
|
|
||||||
// console.log(body)
|
|
||||||
const list = this.filterData(body.result.data.billboard.songs)
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: parseInt(body.result.data.billboard.attributeMap.item_size),
|
|
||||||
list,
|
|
||||||
limit: this.limit,
|
|
||||||
page,
|
|
||||||
source: 'xm',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
import { httpGet, httpFetch } from '../../request'
|
|
||||||
import { xmRequest } from './util'
|
|
||||||
|
|
||||||
const parseLyric = str => {
|
|
||||||
str = str.replace(/(?:<\d+>|\r)/g, '')
|
|
||||||
let tlyric = []
|
|
||||||
let lyric = str.replace(/\[[\d:.]+\].*?\n\[x-trans\].*/g, s => {
|
|
||||||
// console.log(s)
|
|
||||||
let [lrc, tlrc] = s.split('\n')
|
|
||||||
tlrc = tlrc.replace('[x-trans]', lrc.replace(/^(\[[\d:.]+\]).*$/, '$1'))
|
|
||||||
tlyric.push(tlrc)
|
|
||||||
return lrc
|
|
||||||
})
|
|
||||||
tlyric = tlyric.join('\n')
|
|
||||||
return {
|
|
||||||
lyric,
|
|
||||||
tlyric,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
failTime: 0,
|
|
||||||
expireTime: 60 * 1000 * 1000,
|
|
||||||
getLyricFile_1(url, retryNum = 0) {
|
|
||||||
if (retryNum > 5) return Promise.reject('歌词获取失败')
|
|
||||||
let requestObj = httpFetch(url)
|
|
||||||
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
|
|
||||||
if (statusCode !== 200) {
|
|
||||||
let tryRequestObj = this.getLyric(url, ++retryNum)
|
|
||||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
|
||||||
return tryRequestObj.promise
|
|
||||||
}
|
|
||||||
return url.endsWith('.xtrc') ? parseLyric(body) : {
|
|
||||||
lyric: body,
|
|
||||||
tlyric: '',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
},
|
|
||||||
getLyricFile_2(url, retryNum = 0) {
|
|
||||||
if (retryNum > 5) return Promise.reject('歌词获取失败')
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
httpGet(url, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
|
||||||
referer: 'https://www.xiami.com',
|
|
||||||
},
|
|
||||||
}, function(err, resp, body) {
|
|
||||||
if (err || resp.statusCode !== 200) return this.getLyricFile(url, ++retryNum).then(resolve).catch(reject)
|
|
||||||
return resolve(url.endsWith('.xtrc') ? parseLyric(body) : {
|
|
||||||
lyric: body,
|
|
||||||
tlyric: '',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getLyricUrl_1(songInfo, retryNum = 0) {
|
|
||||||
if (retryNum > 2) return Promise.reject('歌词获取失败')
|
|
||||||
let requestObj = xmRequest('/api/lyric/getSongLyrics', { songId: songInfo.songmid })
|
|
||||||
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
|
|
||||||
if (statusCode !== 200) {
|
|
||||||
let tryRequestObj = this.getLyricUrl_1(songInfo, ++retryNum)
|
|
||||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
|
||||||
return tryRequestObj.promise
|
|
||||||
}
|
|
||||||
if (body.code !== 'SUCCESS') {
|
|
||||||
this.failTime = Date.now()
|
|
||||||
let tryRequestObj = this.getLyricUrl_2(songInfo)
|
|
||||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
|
||||||
return tryRequestObj.promise
|
|
||||||
}
|
|
||||||
if (!body.result.data.lyrics.length) return Promise.reject(new Error('未找到歌词'))
|
|
||||||
let lrc = body.result.data.lyrics.find(lyric => /\.(trc|lrc)$/.test(lyric.lyricUrl))
|
|
||||||
return lrc
|
|
||||||
? lrc.lyricUrl.endsWith('.trc')
|
|
||||||
? parseLyric(lrc.content)
|
|
||||||
: { lyric: lrc.content, tlyric: '' }
|
|
||||||
: Promise.reject(new Error('未找到歌词'))
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
},
|
|
||||||
getLyricUrl_2(songInfo, retryNum = 0) {
|
|
||||||
if (retryNum > 2) return Promise.reject('歌词获取失败')
|
|
||||||
// https://github.com/listen1/listen1_chrome_extension/blob/2587e627d23a85e490628acc0b3c9b534bc8323d/js/provider/xiami.js#L149
|
|
||||||
let requestObj = httpFetch(`https://emumo.xiami.com/song/playlist/id/${songInfo.songmid}/object_name/default/object_id/0/cat/json`, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
|
||||||
referer: 'https://www.xiami.com',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
|
|
||||||
if (statusCode !== 200 || !body.status) {
|
|
||||||
let tryRequestObj = this.getLyricUrl_2(songInfo, ++retryNum)
|
|
||||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
|
||||||
return tryRequestObj.promise
|
|
||||||
}
|
|
||||||
let url = body.data.trackList[0].lyric_url
|
|
||||||
if (!url) return Promise.reject(new Error('未找到歌词'))
|
|
||||||
return this.getLyricFile_2(/^http:/.test(url) ? url : ('http:' + url))
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
},
|
|
||||||
getLyric(songInfo) {
|
|
||||||
if (songInfo.lrcUrl && /\.(xtrc|lrc)$/.test(songInfo.lrcUrl)) return this.getLyricFile_1(songInfo.lrcUrl)
|
|
||||||
return Date.now() - this.failTime > this.expireTime ? this.getLyricUrl_1(songInfo) : this.getLyricUrl_2(songInfo)
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
_requestObj: null,
|
|
||||||
async getMusicInfo({ songmid }, page = 1, limit = 20) {
|
|
||||||
if (this._requestObj) this._requestObj.cancelHttp()
|
|
||||||
|
|
||||||
const _requestObj = xmRequest('/api/song/initialize', { songId: songmid })
|
|
||||||
const { body, statusCode } = await _requestObj.promise
|
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取歌曲信息失败')
|
|
||||||
return { source: 'xm', data: body.result.data.songDetail }
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
// import '../../polyfill/array.find'
|
|
||||||
// import jshtmlencode from 'js-htmlencode'
|
|
||||||
import { xmRequest } from './util'
|
|
||||||
import { formatPlayTime, sizeFormate } from '../../index'
|
|
||||||
// import { debug } from '../../utils/env'
|
|
||||||
// import { formatSinger } from './util'
|
|
||||||
// "cdcb72dc3eba41cb5bc4267f09183119_xmMain_/api/list/collect_{"pagingVO":{"page":1,"pageSize":60},"dataType":"system"}"
|
|
||||||
let searchRequest
|
|
||||||
export default {
|
|
||||||
limit: 30,
|
|
||||||
total: 0,
|
|
||||||
page: 0,
|
|
||||||
allPage: 1,
|
|
||||||
musicSearch(str, page, limit) {
|
|
||||||
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
|
|
||||||
searchRequest = xmRequest('/api/search/searchSongs', {
|
|
||||||
key: str,
|
|
||||||
pagingVO: {
|
|
||||||
page: page,
|
|
||||||
pageSize: limit,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return searchRequest.promise.then(({ body }) => body)
|
|
||||||
},
|
|
||||||
getSinger(singers) {
|
|
||||||
let arr = []
|
|
||||||
singers.forEach(singer => {
|
|
||||||
arr.push(singer.artistName)
|
|
||||||
})
|
|
||||||
return arr.join('、')
|
|
||||||
},
|
|
||||||
handleResult(rawData) {
|
|
||||||
// console.log(rawData)
|
|
||||||
let ids = new Set()
|
|
||||||
const list = []
|
|
||||||
rawData.forEach(songData => {
|
|
||||||
if (!songData) return
|
|
||||||
if (ids.has(songData.songId)) return
|
|
||||||
ids.add(songData.songId)
|
|
||||||
|
|
||||||
const types = []
|
|
||||||
const _types = {}
|
|
||||||
let size = null
|
|
||||||
for (const item of songData.purviewRoleVOs) {
|
|
||||||
if (!item.filesize) continue
|
|
||||||
size = sizeFormate(item.filesize)
|
|
||||||
switch (item.quality) {
|
|
||||||
case 's':
|
|
||||||
types.push({ type: 'wav', size })
|
|
||||||
_types.wav = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'h':
|
|
||||||
types.push({ type: '320k', size })
|
|
||||||
_types['320k'] = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'l':
|
|
||||||
types.push({ type: '128k', size })
|
|
||||||
_types['128k'] = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
types.reverse()
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
singer: this.getSinger(songData.singerVOs),
|
|
||||||
name: songData.songName,
|
|
||||||
albumName: songData.albumName,
|
|
||||||
albumId: songData.albumId,
|
|
||||||
source: 'xm',
|
|
||||||
interval: formatPlayTime(parseInt(songData.length / 1000)),
|
|
||||||
songmid: songData.songId,
|
|
||||||
img: songData.albumLogo || songData.albumLogoS,
|
|
||||||
songStringId: songData.songStringId,
|
|
||||||
lrc: null,
|
|
||||||
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
|
||||||
otherSource: null,
|
|
||||||
types,
|
|
||||||
_types,
|
|
||||||
typeUrl: {},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
search(str, page = 1, { limit } = {}, retryNum = 0) {
|
|
||||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
|
||||||
if (limit == null) limit = this.limit
|
|
||||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
|
||||||
return this.musicSearch(str, page, limit).then(result => {
|
|
||||||
if (!result) return this.search(str, page, { limit }, retryNum)
|
|
||||||
if (result.code !== 'SUCCESS') return this.search(str, page, { limit }, retryNum)
|
|
||||||
// const songResultData = result.data || { songs: [], total: 0 }
|
|
||||||
|
|
||||||
let list = this.handleResult(result.result.data.songs)
|
|
||||||
if (list == null) return this.search(str, page, { limit }, retryNum)
|
|
||||||
|
|
||||||
this.total = parseInt(result.result.data.pagingVO.count)
|
|
||||||
this.page = page
|
|
||||||
this.allPage = Math.ceil(this.total / limit)
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
list,
|
|
||||||
allPage: this.allPage,
|
|
||||||
limit,
|
|
||||||
total: this.total,
|
|
||||||
source: 'xm',
|
|
||||||
})
|
|
||||||
}).catch(err => err.message.includes('canceled verify') ? Promise.reject(err) : this.search(str, page, { limit }, retryNum))
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,221 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
import { sizeFormate, formatPlayTime } from '../../index'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
_requestObj_tags: null,
|
|
||||||
_requestObj_list: null,
|
|
||||||
_requestObj_listDetail: null,
|
|
||||||
limit_list: 36,
|
|
||||||
limit_song: 100000,
|
|
||||||
successCode: 'SUCCESS',
|
|
||||||
sortList: [
|
|
||||||
{
|
|
||||||
name: '推荐',
|
|
||||||
id: 'system',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '精选',
|
|
||||||
id: 'recommend',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '最热',
|
|
||||||
id: 'hot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '最新',
|
|
||||||
id: 'new',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
regExps: {
|
|
||||||
// https://www.xiami.com/collect/1138092824?action=play
|
|
||||||
listDetailLink: /^.+\/collect\/(\d+)(?:\s\(.*|\?.*|&.*$|#.*$|$)/,
|
|
||||||
},
|
|
||||||
tagsUrl: '/api/collect/getRecommendTags',
|
|
||||||
songListUrl: '/api/list/collect',
|
|
||||||
songListDetailUrl: '/api/collect/initialize',
|
|
||||||
getSongListData(sortId, tagId, page) {
|
|
||||||
if (tagId == null) {
|
|
||||||
return { pagingVO: { page, pageSize: this.limit_list }, dataType: sortId }
|
|
||||||
}
|
|
||||||
switch (sortId) {
|
|
||||||
case 'system':
|
|
||||||
case 'recommend':
|
|
||||||
sortId = 'hot'
|
|
||||||
}
|
|
||||||
return { pagingVO: { page, pageSize: this.limit_list }, dataType: sortId, key: tagId }
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化播放数量
|
|
||||||
* @param {*} num
|
|
||||||
*/
|
|
||||||
formatPlayCount(num) {
|
|
||||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
|
||||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
|
||||||
return num
|
|
||||||
},
|
|
||||||
getSinger(singers) {
|
|
||||||
let arr = []
|
|
||||||
singers.forEach(singer => {
|
|
||||||
arr.push(singer.artistName)
|
|
||||||
})
|
|
||||||
return arr.join('、')
|
|
||||||
},
|
|
||||||
|
|
||||||
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
|
|
||||||
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
|
|
||||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
|
||||||
|
|
||||||
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
|
||||||
|
|
||||||
this._requestObj_listDetail = xmRequest('/api/collect/getCollectStaticUrl', { listId: id })
|
|
||||||
return this._requestObj_listDetail.promise.then(({ body }) => {
|
|
||||||
if (body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
|
|
||||||
this._requestObj_listDetail = xmRequest(body.result.data.data.data.url)
|
|
||||||
return this._requestObj_listDetail.promise.then(({ body }) => {
|
|
||||||
if (!body.status) return this.getListDetail(id, page, ++tryNum)
|
|
||||||
// console.log(JSON.stringify(body))
|
|
||||||
return {
|
|
||||||
list: this.filterListDetail(body.resultObj.songs),
|
|
||||||
page,
|
|
||||||
limit: this.limit_song,
|
|
||||||
total: body.resultObj.songCount,
|
|
||||||
source: 'xm',
|
|
||||||
info: {
|
|
||||||
name: body.resultObj.collectName,
|
|
||||||
img: body.resultObj.collectLogo,
|
|
||||||
desc: body.resultObj.description,
|
|
||||||
author: body.resultObj.userName,
|
|
||||||
play_count: this.formatPlayCount(body.resultObj.playCount),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
filterListDetail(rawList) {
|
|
||||||
// console.log(rawList)
|
|
||||||
let ids = new Set()
|
|
||||||
const list = []
|
|
||||||
rawList.forEach(songData => {
|
|
||||||
if (!songData) return
|
|
||||||
if (ids.has(songData.songId)) return
|
|
||||||
ids.add(songData.songId)
|
|
||||||
|
|
||||||
const types = []
|
|
||||||
const _types = {}
|
|
||||||
let size = null
|
|
||||||
for (const item of songData.purviewRoleVOs) {
|
|
||||||
if (!item.filesize) continue
|
|
||||||
size = sizeFormate(item.filesize)
|
|
||||||
switch (item.quality) {
|
|
||||||
case 's':
|
|
||||||
types.push({ type: 'wav', size })
|
|
||||||
_types.wav = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'h':
|
|
||||||
types.push({ type: '320k', size })
|
|
||||||
_types['320k'] = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'l':
|
|
||||||
types.push({ type: '128k', size })
|
|
||||||
_types['128k'] = {
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
types.reverse()
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
singer: this.getSinger(songData.singerVOs),
|
|
||||||
name: songData.songName,
|
|
||||||
albumName: songData.albumName,
|
|
||||||
albumId: songData.albumId,
|
|
||||||
source: 'xm',
|
|
||||||
interval: formatPlayTime(parseInt(songData.length / 1000)),
|
|
||||||
songmid: songData.songId,
|
|
||||||
songStringId: songData.songStringId,
|
|
||||||
img: songData.albumLogo || songData.albumLogoS,
|
|
||||||
lrc: null,
|
|
||||||
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
|
||||||
otherSource: null,
|
|
||||||
types,
|
|
||||||
_types,
|
|
||||||
typeUrl: {},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取列表数据
|
|
||||||
getList(sortId, tagId, page, tryNum = 0) {
|
|
||||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
|
||||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
|
||||||
this._requestObj_list = xmRequest(this.songListUrl, this.getSongListData(sortId, tagId, page))
|
|
||||||
return this._requestObj_list.promise.then(({ body }) => {
|
|
||||||
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
|
||||||
return {
|
|
||||||
list: this.filterList(body.result.data.collects),
|
|
||||||
total: body.result.data.pagingVO.count,
|
|
||||||
page,
|
|
||||||
limit: body.result.data.pagingVO.pageSize,
|
|
||||||
source: 'xm',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
filterList(rawData) {
|
|
||||||
return rawData.map(item => ({
|
|
||||||
play_count: this.formatPlayCount(item.playCount),
|
|
||||||
id: item.listId,
|
|
||||||
author: item.userName,
|
|
||||||
name: item.collectName,
|
|
||||||
time: null,
|
|
||||||
img: item.collectLogo,
|
|
||||||
grade: null,
|
|
||||||
desc: null,
|
|
||||||
source: 'xm',
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取标签
|
|
||||||
getTag(tryNum = 0) {
|
|
||||||
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
|
|
||||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
|
||||||
this._requestObj_tags = xmRequest(this.tagsUrl, { recommend: 1 })
|
|
||||||
return this._requestObj_tags.promise.then(({ body }) => {
|
|
||||||
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
|
||||||
return this.filterTagInfo(body.result.data.recommendTags)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
filterTagInfo(rawList) {
|
|
||||||
return {
|
|
||||||
hotTag: rawList[0].items.map(item => ({
|
|
||||||
id: item.name,
|
|
||||||
name: item.name,
|
|
||||||
source: 'xm',
|
|
||||||
})),
|
|
||||||
tags: rawList.slice(1).map(item => ({
|
|
||||||
name: item.title,
|
|
||||||
list: item.items.map(tag => ({
|
|
||||||
parent_id: item.title,
|
|
||||||
parent_name: item.title,
|
|
||||||
id: tag.name,
|
|
||||||
name: tag.name,
|
|
||||||
source: 'xm',
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
source: 'xm',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getTags() {
|
|
||||||
return this.getTag()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// getList
|
|
||||||
// getTags
|
|
||||||
// getListDetail
|
|
|
@ -1,122 +0,0 @@
|
||||||
import { httpGet, httpFetch } from '../../request'
|
|
||||||
import { toMD5 } from '../../index'
|
|
||||||
// import crateIsg from './isg'
|
|
||||||
import { rendererInvoke, NAMES } from '../../../../common/ipc'
|
|
||||||
|
|
||||||
if (!window.xm_token) {
|
|
||||||
let data = window.localStorage.getItem('xm_token')
|
|
||||||
window.xm_token = data ? JSON.parse(data) : {
|
|
||||||
cookies: {},
|
|
||||||
cookie: null,
|
|
||||||
token: null,
|
|
||||||
isGetingToken: false,
|
|
||||||
}
|
|
||||||
window.xm_token.isGetingToken = false
|
|
||||||
}
|
|
||||||
|
|
||||||
export const formatSinger = rawData => rawData.replace(/&/g, '、')
|
|
||||||
|
|
||||||
const matchToken = headers => {
|
|
||||||
let cookies = {}
|
|
||||||
let token
|
|
||||||
for (const item of headers['set-cookie']) {
|
|
||||||
const [key, value] = item.substring(0, item.indexOf(';')).split('=')
|
|
||||||
cookies[key] = value
|
|
||||||
if (key == 'xm_sg_tk') token = value.substring(0, value.indexOf('_'))
|
|
||||||
}
|
|
||||||
// console.log(cookies)
|
|
||||||
return { token, cookies }
|
|
||||||
}
|
|
||||||
|
|
||||||
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
|
||||||
|
|
||||||
const createToken = (token, path, params) => toMD5(`${token}_xmMain_${path}_${params}`)
|
|
||||||
|
|
||||||
const handleSaveToken = ({ token, cookies }) => {
|
|
||||||
Object.assign(window.xm_token.cookies, cookies)
|
|
||||||
// window.xm_token.cookies.isg = crateIsg()
|
|
||||||
window.xm_token.cookie = Object.keys(window.xm_token.cookies).map(k => `${k}=${window.xm_token.cookies[k]};`).join(' ')
|
|
||||||
if (token) window.xm_token.token = token
|
|
||||||
|
|
||||||
window.localStorage.setItem('xm_token', JSON.stringify(window.xm_token))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getToken = (path, params) => new Promise((resolve, reject) => {
|
|
||||||
if (window.xm_token.isGetingToken) return wait(1000).then(() => getToken(path, params).then(data => resolve(data)))
|
|
||||||
if (window.xm_token.token) return resolve({ token: createToken(window.xm_token.token, path, params), cookie: window.xm_token.cookie })
|
|
||||||
window.xm_token.isGetingToken = true
|
|
||||||
httpGet('https://www.xiami.com/', (err, resp) => {
|
|
||||||
window.xm_token.isGetingToken = false
|
|
||||||
if (err) return reject(err)
|
|
||||||
if (resp.statusCode != 200) return reject(new Error('获取失败'))
|
|
||||||
|
|
||||||
handleSaveToken(matchToken(resp.headers))
|
|
||||||
|
|
||||||
resolve({ token: createToken(window.xm_token.token, path, params), cookie: window.xm_token.cookie })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const baseUrl = 'https://www.xiami.com'
|
|
||||||
export const xmRequest = (path, params = '') => {
|
|
||||||
let query = params
|
|
||||||
if (params != '') {
|
|
||||||
params = JSON.stringify(params)
|
|
||||||
query = '&_q=' + encodeURIComponent(params)
|
|
||||||
}
|
|
||||||
let requestObj = {
|
|
||||||
isInited: false,
|
|
||||||
isCancelled: false,
|
|
||||||
cancelHttp() {
|
|
||||||
if (!this.isInited) this.isCancelled = true
|
|
||||||
this.requestObj.cancelHttp()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
requestObj.promise = getToken(path, params).then(data => {
|
|
||||||
// console.log(data)
|
|
||||||
if (requestObj.isCancelled) return Promise.reject('取消请求')
|
|
||||||
let url = path
|
|
||||||
if (!/^http/.test(path)) url = baseUrl + path
|
|
||||||
let s = `_s=${data.token}${query}`
|
|
||||||
url += (url.includes('?') ? '&' : '?') + s
|
|
||||||
requestObj.requestObj = httpFetch(url, {
|
|
||||||
headers: {
|
|
||||||
Referer: 'https://www.xiami.com/',
|
|
||||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
|
||||||
cookie: data.cookie,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return requestObj.requestObj.promise.then(resp => {
|
|
||||||
// console.log(resp.body)
|
|
||||||
if (resp.statusCode != 200) {
|
|
||||||
// console.log(resp.headers)
|
|
||||||
window.xm_token.token = null
|
|
||||||
return Promise.reject(new Error('获取失败'))
|
|
||||||
}
|
|
||||||
if (resp.body.code !== 'SUCCESS' && resp.body.rgv587_flag == 'sm') {
|
|
||||||
window.globalObj.xm.isShowVerify = true
|
|
||||||
return wait(300).then(() => {
|
|
||||||
return rendererInvoke(NAMES.mainWindow.handle_xm_verify_open, /^https:/.test(resp.body.url) ? resp.body.url : 'https:' + resp.body.url).then(x5sec => {
|
|
||||||
handleSaveToken({ cookies: { x5sec } })
|
|
||||||
// console.log(x5sec)
|
|
||||||
window.globalObj.xm.isShowVerify = false
|
|
||||||
return Promise.reject(new Error('获取成功'))
|
|
||||||
}).catch(err => {
|
|
||||||
window.globalObj.xm.isShowVerify = false
|
|
||||||
return Promise.reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (resp.headers['set-cookie']) handleSaveToken(matchToken(resp.headers))
|
|
||||||
|
|
||||||
return Promise.resolve(resp)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
}
|
|
||||||
|
|
||||||
export const closeVerifyModal = async() => {
|
|
||||||
if (!window.globalObj.xm.isShowVerify) return
|
|
||||||
await rendererInvoke(NAMES.mainWindow.handle_xm_verify_close)
|
|
||||||
window.globalObj.xm.isShowVerify = false
|
|
||||||
}
|
|
|
@ -386,12 +386,12 @@ export default {
|
||||||
this.listMenu.itemMenuControl.file = true
|
this.listMenu.itemMenuControl.file = true
|
||||||
this.listMenu.itemMenuControl.start =
|
this.listMenu.itemMenuControl.start =
|
||||||
this.listMenu.itemMenuControl.pause = false
|
this.listMenu.itemMenuControl.pause = false
|
||||||
// } else if (item.status === this.downloadStatus.ERROR || item.status === this.downloadStatus.PAUSE) {
|
} else if (item.status === this.downloadStatus.ERROR || item.status === this.downloadStatus.PAUSE) {
|
||||||
// this.listMenu.itemMenuControl.play =
|
this.listMenu.itemMenuControl.play =
|
||||||
// this.listMenu.itemMenuControl.playLater =
|
this.listMenu.itemMenuControl.playLater =
|
||||||
// this.listMenu.itemMenuControl.pause =
|
this.listMenu.itemMenuControl.pause =
|
||||||
// this.listMenu.itemMenuControl.file = false
|
this.listMenu.itemMenuControl.file = false
|
||||||
// this.listMenu.itemMenuControl.start = true
|
this.listMenu.itemMenuControl.start = true
|
||||||
} else {
|
} else {
|
||||||
this.listMenu.itemMenuControl.play =
|
this.listMenu.itemMenuControl.play =
|
||||||
this.listMenu.itemMenuControl.playLater =
|
this.listMenu.itemMenuControl.playLater =
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
td(style="width: 9%;")
|
td(style="width: 9%;")
|
||||||
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
|
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
|
||||||
td(style="width: 15%; padding-left: 0; padding-right: 0;")
|
td(style="width: 15%; padding-left: 0; padding-right: 0;")
|
||||||
material-list-buttons(:index="index" @btn-click="handleListBtnClick")
|
material-list-buttons(:index="index" @btn-click="handleListBtnClick" :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-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)') 试听
|
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
|
||||||
//- button.btn-secondary(type='button' @click.stop='handleRemove(index)') 删除
|
//- button.btn-secondary(type='button' @click.stop='handleRemove(index)') 删除
|
||||||
|
@ -569,7 +569,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
testPlay(index) {
|
testPlay(index) {
|
||||||
if (!this.assertApiSupport(this.list[index].source)) return
|
// if (!this.assertApiSupport(this.list[index].source)) return
|
||||||
this.setPlayList({ list: this.listData, index })
|
this.setPlayList({ list: this.listData, index })
|
||||||
},
|
},
|
||||||
handleRemove(index) {
|
handleRemove(index) {
|
||||||
|
@ -579,7 +579,7 @@ export default {
|
||||||
switch (info.action) {
|
switch (info.action) {
|
||||||
case 'download': {
|
case 'download': {
|
||||||
const minfo = this.list[info.index]
|
const minfo = this.list[info.index]
|
||||||
if (!this.assertApiSupport(minfo.source)) return
|
// if (!this.assertApiSupport(minfo.source)) return
|
||||||
this.musicInfo = minfo
|
this.musicInfo = minfo
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.isShowDownload = true
|
this.isShowDownload = true
|
||||||
|
@ -732,9 +732,9 @@ export default {
|
||||||
},
|
},
|
||||||
handleListItemRigthClick(event, index) {
|
handleListItemRigthClick(event, index) {
|
||||||
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
|
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
|
||||||
this.listMenu.itemMenuControl.play =
|
// this.listMenu.itemMenuControl.play =
|
||||||
this.listMenu.itemMenuControl.playLater =
|
// this.listMenu.itemMenuControl.playLater =
|
||||||
this.listMenu.itemMenuControl.download =
|
this.listMenu.itemMenuControl.download =
|
||||||
this.assertApiSupport(this.list[index].source)
|
this.assertApiSupport(this.list[index].source)
|
||||||
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
|
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
|
||||||
if (dom_selected) dom_selected.classList.remove('selected')
|
if (dom_selected) dom_selected.classList.remove('selected')
|
||||||
|
|
|
@ -274,7 +274,7 @@ export default {
|
||||||
this.clickIndex = index
|
this.clickIndex = index
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.testPlay(index)
|
if (this.assertApiSupport(this.listInfo.list[index].source)) this.testPlay(index)
|
||||||
this.clickTime = 0
|
this.clickTime = 0
|
||||||
this.clickIndex = -1
|
this.clickIndex = -1
|
||||||
},
|
},
|
||||||
|
@ -289,14 +289,6 @@ export default {
|
||||||
case 'play':
|
case 'play':
|
||||||
this.testPlay(info.index)
|
this.testPlay(info.index)
|
||||||
break
|
break
|
||||||
case 'playLater':
|
|
||||||
if (this.selectedData.length) {
|
|
||||||
this.setTempPlayList(this.selectedData.map(s => ({ listId: '__temp__', musicInfo: s })))
|
|
||||||
this.resetSelect()
|
|
||||||
} else {
|
|
||||||
this.setTempPlayList([{ listId: '__temp__', musicInfo: this.list[info.index] }])
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'listAdd':
|
case 'listAdd':
|
||||||
this.musicInfo = this.listInfo.list[info.index]
|
this.musicInfo = this.listInfo.list[info.index]
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
@ -480,6 +472,14 @@ export default {
|
||||||
}
|
}
|
||||||
this.testPlay(index)
|
this.testPlay(index)
|
||||||
break
|
break
|
||||||
|
case 'playLater':
|
||||||
|
if (this.selectedData.length) {
|
||||||
|
this.setTempPlayList(this.selectedData.map(s => ({ listId: '__temp__', musicInfo: s })))
|
||||||
|
this.resetSelect()
|
||||||
|
} else {
|
||||||
|
this.setTempPlayList([{ listId: '__temp__', musicInfo: this.listInfo.list[index] }])
|
||||||
|
}
|
||||||
|
break
|
||||||
case 'addTo':
|
case 'addTo':
|
||||||
if (this.selectedData.length) {
|
if (this.selectedData.length) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
//- div(:class="$style.main")
|
div(:class="$style.main")
|
||||||
//- div.scroll(:class="$style.toc")
|
//- div.scroll(:class="$style.toc")
|
||||||
//- ul(:class="$style.tocList")
|
//- ul(:class="$style.tocList")
|
||||||
//- li(:class="$style.tocListItem" v-for="h2 in toc.list" :key="h2.id")
|
//- li(:class="$style.tocListItem" v-for="h2 in toc.list" :key="h2.id")
|
||||||
|
@ -9,57 +9,59 @@
|
||||||
//- li(:class="$style.tocSubListItem" v-for="h3 in h2.children" :key="h3.id")
|
//- li(:class="$style.tocSubListItem" v-for="h3 in h2.children" :key="h3.id")
|
||||||
//- h3(:class="[$style.tocH3, toc.activeId == h3.id ? $style.active : null]" :tips="h3.title")
|
//- h3(:class="[$style.tocH3, toc.activeId == h3.id ? $style.active : null]" :tips="h3.title")
|
||||||
//- a(:href="'#' + h3.id" @click="toc.activeId = h3.id") {{h3.title}}
|
//- a(:href="'#' + h3.id" @click="toc.activeId = h3.id") {{h3.title}}
|
||||||
div.scroll(:class="$style.setting" ref="dom_setting")
|
div.scroll(:class="$style.setting" ref="dom_setting")
|
||||||
dl(ref="dom_setting_list")
|
dl(ref="dom_setting_list")
|
||||||
dt#basic {{$t('view.setting.basic')}}
|
dt#basic {{$t('view.setting.basic')}}
|
||||||
dd
|
dd
|
||||||
h3#basic_theme {{$t('view.setting.basic_theme')}}
|
h3#basic_theme {{$t('view.setting.basic_theme')}}
|
||||||
div
|
div
|
||||||
ul(:class="$style.theme")
|
ul(:class="$style.theme")
|
||||||
li(v-for="theme in themes.list" :key="theme.id" :tips="$t('store.state.theme_' + theme.class)" @click="current_setting.themeId = theme.id" :class="[theme.class, themes.active == theme.id ? $style.active : '']")
|
li(v-for="theme in themes.list" :key="theme.id" :tips="$t('store.state.theme_' + theme.class)" @click="current_setting.themeId = theme.id" :class="[theme.class, themes.active == theme.id ? $style.active : '']")
|
||||||
span
|
span
|
||||||
label {{$t('store.state.theme_' + theme.class)}}
|
label {{$t('store.state.theme_' + theme.class)}}
|
||||||
|
|
||||||
dd
|
dd
|
||||||
div(:class="[$style.gapTop, $style.top]")
|
div(:class="[$style.gapTop, $style.top]")
|
||||||
material-checkbox(id="setting_show_animate" v-model="current_setting.isShowAnimation" :label="$t('view.setting.basic_show_animation')")
|
material-checkbox(id="setting_show_animate" v-model="current_setting.isShowAnimation" :label="$t('view.setting.basic_show_animation')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_animate" v-model="current_setting.randomAnimate" :label="$t('view.setting.basic_animation')")
|
material-checkbox(id="setting_animate" v-model="current_setting.randomAnimate" :label="$t('view.setting.basic_animation')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_to_tray" v-model="current_setting.tray.isShow" @change="handleTrayShowChange" :label="$t('view.setting.basic_to_tray')")
|
material-checkbox(id="setting_to_tray" v-model="current_setting.tray.isShow" @change="handleTrayShowChange" :label="$t('view.setting.basic_to_tray')")
|
||||||
|
|
||||||
|
|
||||||
dd(:tips="$t('view.setting.basic_source_title')")
|
dd(:tips="$t('view.setting.basic_source_title')")
|
||||||
h3#basic_source {{$t('view.setting.basic_source')}}
|
h3#basic_source {{$t('view.setting.basic_source')}}
|
||||||
div
|
div
|
||||||
div(v-for="item in apiSources" :key="item.id" :class="$style.gapTop")
|
div(v-for="item in apiSources" :key="item.id" :class="$style.gapTop")
|
||||||
material-checkbox(:id="`setting_api_source_${item.id}`" name="setting_api_source" @change="handleAPISourceChange(item.id)"
|
material-checkbox(:id="`setting_api_source_${item.id}`" name="setting_api_source" @change="handleAPISourceChange(item.id)"
|
||||||
need v-model="current_setting.apiSource" :disabled="item.disabled" :value="item.id" :label="item.label")
|
need v-model="current_setting.apiSource" :disabled="item.disabled" :value="item.id" :label="item.label")
|
||||||
|
p(:class="$style.gapTop")
|
||||||
|
material-btn(:class="$style.btn" min @click="isShowUserApiModal = true") {{$t('view.setting.basic_source_user_api_btn')}}
|
||||||
|
|
||||||
dd(:tips="$t('view.setting.basic_window_size_title')")
|
dd(:tips="$t('view.setting.basic_window_size_title')")
|
||||||
h3#basic_window_size {{$t('view.setting.basic_window_size')}}
|
h3#basic_window_size {{$t('view.setting.basic_window_size')}}
|
||||||
div
|
div
|
||||||
material-checkbox(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size" @change="handleWindowSizeChange" :class="$style.gapLeft"
|
material-checkbox(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size" @change="handleWindowSizeChange" :class="$style.gapLeft"
|
||||||
need v-model="current_setting.windowSizeId" :value="item.id" :label="$t('view.setting.basic_window_size_' + item.name)" :key="item.id")
|
need v-model="current_setting.windowSizeId" :value="item.id" :label="$t('view.setting.basic_window_size_' + item.name)" :key="item.id")
|
||||||
|
|
||||||
dd(:tips="$t('view.setting.basic_lang_title')")
|
dd(:tips="$t('view.setting.basic_lang_title')")
|
||||||
h3#basic_lang {{$t('view.setting.basic_lang')}}
|
h3#basic_lang {{$t('view.setting.basic_lang')}}
|
||||||
div
|
div
|
||||||
material-checkbox(v-for="item in languageList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang"
|
material-checkbox(v-for="item in languageList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang"
|
||||||
@change="handleLangChange(item.locale)" :class="$style.gapLeft"
|
@change="handleLangChange(item.locale)" :class="$style.gapLeft"
|
||||||
need v-model="current_setting.langId" :value="item.locale" :label="item.name")
|
need v-model="current_setting.langId" :value="item.locale" :label="item.name")
|
||||||
|
|
||||||
dd(:tips="$t('view.setting.basic_sourcename_title')")
|
dd(:tips="$t('view.setting.basic_sourcename_title')")
|
||||||
h3#basic_sourcename {{$t('view.setting.basic_sourcename')}}
|
h3#basic_sourcename {{$t('view.setting.basic_sourcename')}}
|
||||||
div
|
div
|
||||||
material-checkbox(v-for="item in sourceNameTypes" :key="item.id" :class="$style.gapLeft" :id="`setting_abasic_sourcename_${item.id}`"
|
material-checkbox(v-for="item in sourceNameTypes" :key="item.id" :class="$style.gapLeft" :id="`setting_abasic_sourcename_${item.id}`"
|
||||||
name="setting_basic_sourcename" need v-model="current_setting.sourceNameType" :value="item.id" :label="item.label")
|
name="setting_basic_sourcename" need v-model="current_setting.sourceNameType" :value="item.id" :label="item.label")
|
||||||
|
|
||||||
dd
|
dd
|
||||||
h3#basic_control_btn_position {{$t('view.setting.basic_control_btn_position')}}
|
h3#basic_control_btn_position {{$t('view.setting.basic_control_btn_position')}}
|
||||||
div
|
div
|
||||||
material-checkbox(v-for="item in controlBtnPositionList" :key="item.id" :class="$style.gapLeft" :id="`setting_basic_control_btn_position_${item.id}`"
|
material-checkbox(v-for="item in controlBtnPositionList" :key="item.id" :class="$style.gapLeft" :id="`setting_basic_control_btn_position_${item.id}`"
|
||||||
name="setting_basic_control_btn_position" need v-model="current_setting.controlBtnPosition" :value="item.id" :label="item.name")
|
name="setting_basic_control_btn_position" need v-model="current_setting.controlBtnPosition" :value="item.id" :label="item.name")
|
||||||
|
|
||||||
dt#play {{$t('view.setting.play')}}
|
dt#play {{$t('view.setting.play')}}
|
||||||
dd
|
dd
|
||||||
|
@ -67,6 +69,8 @@ div.scroll(:class="$style.setting" ref="dom_setting")
|
||||||
material-checkbox(id="setting_player_save_play_time" v-model="current_setting.player.isSavePlayTime" :label="$t('view.setting.play_save_play_time')")
|
material-checkbox(id="setting_player_save_play_time" v-model="current_setting.player.isSavePlayTime" :label="$t('view.setting.play_save_play_time')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_player_lyric_transition" v-model="current_setting.player.isShowLyricTransition" :label="$t('view.setting.play_lyric_transition')")
|
material-checkbox(id="setting_player_lyric_transition" v-model="current_setting.player.isShowLyricTransition" :label="$t('view.setting.play_lyric_transition')")
|
||||||
|
div(:class="$style.gapTop")
|
||||||
|
material-checkbox(id="setting_player_lyric_play_lxlrc" v-model="current_setting.player.isPlayLxlrc" :label="$t('view.setting.play_lyric_lxlrc')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_player_highQuality" v-model="current_setting.player.highQuality" :label="$t('view.setting.play_quality')")
|
material-checkbox(id="setting_player_highQuality" v-model="current_setting.player.highQuality" :label="$t('view.setting.play_quality')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
|
@ -88,173 +92,179 @@ div.scroll(:class="$style.setting" ref="dom_setting")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_desktop_lyric_lockScreen" v-model="current_setting.desktopLyric.isLockScreen" :label="$t('view.setting.desktop_lyric_lock_screen')")
|
material-checkbox(id="setting_desktop_lyric_lockScreen" v-model="current_setting.desktopLyric.isLockScreen" :label="$t('view.setting.desktop_lyric_lock_screen')")
|
||||||
|
|
||||||
dt#search {{$t('view.setting.search')}}
|
dt#search {{$t('view.setting.search')}}
|
||||||
dd
|
dd
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_search_showHot_enable" v-model="current_setting.search.isShowHotSearch" :label="$t('view.setting.search_hot')")
|
material-checkbox(id="setting_search_showHot_enable" v-model="current_setting.search.isShowHotSearch" :label="$t('view.setting.search_hot')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_search_showHistory_enable" v-model="current_setting.search.isShowHistorySearch" :label="$t('view.setting.search_history')")
|
material-checkbox(id="setting_search_showHistory_enable" v-model="current_setting.search.isShowHistorySearch" :label="$t('view.setting.search_history')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_search_focusSearchBox_enable" v-model="current_setting.search.isFocusSearchBox" :label="$t('view.setting.search_focus_search_box')")
|
material-checkbox(id="setting_search_focusSearchBox_enable" v-model="current_setting.search.isFocusSearchBox" :label="$t('view.setting.search_focus_search_box')")
|
||||||
|
|
||||||
dt#list {{$t('view.setting.list')}}
|
dt#list {{$t('view.setting.list')}}
|
||||||
dd
|
dd
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_list_showSource_enable" v-model="current_setting.list.isShowSource" :label="$t('view.setting.list_source')")
|
material-checkbox(id="setting_list_showSource_enable" v-model="current_setting.list.isShowSource" :label="$t('view.setting.list_source')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_list_scroll_enable" v-model="current_setting.list.isSaveScrollLocation" :label="$t('view.setting.list_scroll')")
|
material-checkbox(id="setting_list_scroll_enable" v-model="current_setting.list.isSaveScrollLocation" :label="$t('view.setting.list_scroll')")
|
||||||
//- dd(:tips="播放列表是否显示专辑栏")
|
//- dd(:tips="播放列表是否显示专辑栏")
|
||||||
h3 专辑栏
|
h3 专辑栏
|
||||||
div
|
div
|
||||||
material-checkbox(id="setting_list_showalbum" v-model="current_setting.list.isShowAlbumName" label="是否显示专辑栏")
|
material-checkbox(id="setting_list_showalbum" v-model="current_setting.list.isShowAlbumName" label="是否显示专辑栏")
|
||||||
dt#download {{$t('view.setting.download')}}
|
dt#download {{$t('view.setting.download')}}
|
||||||
dd
|
dd
|
||||||
material-checkbox(id="setting_download_enable" v-model="current_setting.download.enable" :label="$t('view.setting.download_enable')")
|
material-checkbox(id="setting_download_enable" v-model="current_setting.download.enable" :label="$t('view.setting.download_enable')")
|
||||||
dd(:tips="$t('view.setting.download_path_title')")
|
dd(:tips="$t('view.setting.download_path_title')")
|
||||||
h3#download_path {{$t('view.setting.download_path')}}
|
h3#download_path {{$t('view.setting.download_path')}}
|
||||||
div
|
div
|
||||||
p
|
p
|
||||||
| {{$t('view.setting.download_path_label')}}
|
| {{$t('view.setting.download_path_label')}}
|
||||||
span.auto-hidden.hover(:tips="$t('view.setting.download_path_open_label')" :class="$style.savePath" @click="handleOpenDir(current_setting.download.savePath)") {{current_setting.download.savePath}}
|
span.auto-hidden.hover(:tips="$t('view.setting.download_path_open_label')" :class="$style.savePath" @click="handleOpenDir(current_setting.download.savePath)") {{current_setting.download.savePath}}
|
||||||
p
|
p
|
||||||
material-btn(:class="$style.btn" min @click="handleChangeSavePath") {{$t('view.setting.download_path_change_btn')}}
|
material-btn(:class="$style.btn" min @click="handleChangeSavePath") {{$t('view.setting.download_path_change_btn')}}
|
||||||
dd(:tips="$t('view.setting.download_name_title')")
|
dd(:tips="$t('view.setting.download_name_title')")
|
||||||
h3#download_name {{$t('view.setting.download_name')}}
|
h3#download_name {{$t('view.setting.download_name')}}
|
||||||
div
|
div
|
||||||
material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gapLeft" name="setting_download_musicName" :value="item.value" :key="item.value" need
|
material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gapLeft" name="setting_download_musicName" :value="item.value" :key="item.value" need
|
||||||
v-model="current_setting.download.fileName" v-for="item in musicNames" :label="item.name")
|
v-model="current_setting.download.fileName" v-for="item in musicNames" :label="item.name")
|
||||||
dd
|
dd
|
||||||
h3#download_data_embed {{$t('view.setting.download_data_embed')}}
|
h3#download_data_embed {{$t('view.setting.download_data_embed')}}
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_download_isEmbedPic" v-model="current_setting.download.isEmbedPic" :label="$t('view.setting.download_embed_pic')")
|
material-checkbox(id="setting_download_isEmbedPic" v-model="current_setting.download.isEmbedPic" :label="$t('view.setting.download_embed_pic')")
|
||||||
div(:class="$style.gapTop")
|
div(:class="$style.gapTop")
|
||||||
material-checkbox(id="setting_download_isEmbedLyric" v-model="current_setting.download.isEmbedLyric" :label="$t('view.setting.download_embed_lyric')")
|
material-checkbox(id="setting_download_isEmbedLyric" v-model="current_setting.download.isEmbedLyric" :label="$t('view.setting.download_embed_lyric')")
|
||||||
dd(:tips="$t('view.setting.download_lyric_title')")
|
dd(:tips="$t('view.setting.download_lyric_title')")
|
||||||
h3#download_lyric {{$t('view.setting.download_lyric')}}
|
h3#download_lyric {{$t('view.setting.download_lyric')}}
|
||||||
div
|
div
|
||||||
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" :label="$t('view.setting.is_enable')")
|
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" :label="$t('view.setting.is_enable')")
|
||||||
|
|
||||||
dt#hot_key {{$t('view.setting.hot_key')}}
|
dt#hot_key {{$t('view.setting.hot_key')}}
|
||||||
dd
|
dd
|
||||||
h3#hot_key_local_title {{$t('view.setting.hot_key_local_title')}}
|
h3#hot_key_local_title {{$t('view.setting.hot_key_local_title')}}
|
||||||
div
|
div
|
||||||
material-checkbox(id="setting_download_hotKeyLocal" v-model="current_hot_key.local.enable" :label="$t('view.setting.is_enable')" @change="handleHotKeySaveConfig")
|
material-checkbox(id="setting_download_hotKeyLocal" v-model="current_hot_key.local.enable" :label="$t('view.setting.is_enable')" @change="handleHotKeySaveConfig")
|
||||||
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.local.enable ? 1 : .6 }")
|
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.local.enable ? 1 : .6 }")
|
||||||
div(:class="$style.hotKeyItem" v-for="item in hotKeys.local")
|
div(:class="$style.hotKeyItem" v-for="item in hotKeys.local")
|
||||||
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
|
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
|
||||||
material-input.key-bind(:class="$style.hotKeyItemInput" readonly @keyup.prevent :placeholder="$t('view.setting.hot_key_unset_input')"
|
material-input.key-bind(:class="$style.hotKeyItemInput" readonly @keyup.prevent :placeholder="$t('view.setting.hot_key_unset_input')"
|
||||||
:value="hotKeyConfig.local[item.name] && formatHotKeyName(hotKeyConfig.local[item.name].key)"
|
:value="hotKeyConfig.local[item.name] && formatHotKeyName(hotKeyConfig.local[item.name].key)"
|
||||||
@focus="handleHotKeyFocus($event, item, 'local')"
|
@focus="handleHotKeyFocus($event, item, 'local')"
|
||||||
@blur="handleHotKeyBlur($event, item, 'local')")
|
@blur="handleHotKeyBlur($event, item, 'local')")
|
||||||
|
|
||||||
h3#hot_key_global_title {{$t('view.setting.hot_key_global_title')}}
|
h3#hot_key_global_title {{$t('view.setting.hot_key_global_title')}}
|
||||||
div
|
div
|
||||||
material-checkbox(id="setting_download_hotKeyGlobal" v-model="current_hot_key.global.enable" :label="$t('view.setting.is_enable')" @change="handleEnableHotKey")
|
material-checkbox(id="setting_download_hotKeyGlobal" v-model="current_hot_key.global.enable" :label="$t('view.setting.is_enable')" @change="handleEnableHotKey")
|
||||||
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.global.enable ? 1 : .6 }")
|
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.global.enable ? 1 : .6 }")
|
||||||
div(:class="$style.hotKeyItem" v-for="item in hotKeys.global")
|
div(:class="$style.hotKeyItem" v-for="item in hotKeys.global")
|
||||||
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
|
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
|
||||||
material-input.key-bind(:class="[$style.hotKeyItemInput, hotKeyConfig.global[item.name] && hotKeyStatus[hotKeyConfig.global[item.name].key] && hotKeyStatus[hotKeyConfig.global[item.name].key].status === false ? $style.hotKeyFailed : null]"
|
material-input.key-bind(:class="[$style.hotKeyItemInput, hotKeyConfig.global[item.name] && hotKeyStatus[hotKeyConfig.global[item.name].key] && hotKeyStatus[hotKeyConfig.global[item.name].key].status === false ? $style.hotKeyFailed : null]"
|
||||||
:value="hotKeyConfig.global[item.name] && formatHotKeyName(hotKeyConfig.global[item.name].key)" @input.prevent readonly :placeholder="$t('view.setting.hot_key_unset_input')"
|
:value="hotKeyConfig.global[item.name] && formatHotKeyName(hotKeyConfig.global[item.name].key)" @input.prevent readonly :placeholder="$t('view.setting.hot_key_unset_input')"
|
||||||
@focus="handleHotKeyFocus($event, item, 'global')"
|
@focus="handleHotKeyFocus($event, item, 'global')"
|
||||||
@blur="handleHotKeyBlur($event, item, 'global')")
|
@blur="handleHotKeyBlur($event, item, 'global')")
|
||||||
|
|
||||||
|
dt#network {{$t('view.setting.network')}}
|
||||||
|
dd
|
||||||
|
h3#network_proxy_title {{$t('view.setting.network_proxy_title')}}
|
||||||
|
div
|
||||||
|
p
|
||||||
|
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" :label="$t('view.setting.is_enable')")
|
||||||
|
p
|
||||||
|
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.host" @change="handleProxyChange('host')" :placeholder="$t('view.setting.network_proxy_host')")
|
||||||
|
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.port" @change="handleProxyChange('port')" :placeholder="$t('view.setting.network_proxy_port')")
|
||||||
|
p
|
||||||
|
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.username" @change="handleProxyChange('username')" :placeholder="$t('view.setting.network_proxy_username')")
|
||||||
|
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.password" @change="handleProxyChange('password')" type="password" :placeholder="$t('view.setting.network_proxy_password')")
|
||||||
|
dt#odc {{$t('view.setting.odc')}}
|
||||||
|
dd
|
||||||
|
div(:class="$style.gapTop")
|
||||||
|
material-checkbox(id="setting_odc_isAutoClearSearchInput" v-model="current_setting.odc.isAutoClearSearchInput" :label="$t('view.setting.odc_clear_search_input')")
|
||||||
|
div(:class="$style.gapTop")
|
||||||
|
material-checkbox(id="setting_odc_isAutoClearSearchList" v-model="current_setting.odc.isAutoClearSearchList" :label="$t('view.setting.odc_clear_search_list')")
|
||||||
|
dt#backup {{$t('view.setting.backup')}}
|
||||||
|
dd
|
||||||
|
h3#backup_part {{$t('view.setting.backup_part')}}
|
||||||
|
div
|
||||||
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportPlayList") {{$t('view.setting.backup_part_import_list')}}
|
||||||
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportPlayList") {{$t('view.setting.backup_part_export_list')}}
|
||||||
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportSetting") {{$t('view.setting.backup_part_import_setting')}}
|
||||||
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportSetting") {{$t('view.setting.backup_part_export_setting')}}
|
||||||
|
dd
|
||||||
|
h3#backup_all {{$t('view.setting.backup_all')}}
|
||||||
|
div
|
||||||
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportAllData") {{$t('view.setting.backup_all_import')}}
|
||||||
|
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") {{$t('view.setting.backup_all_export')}}
|
||||||
|
dt#other {{$t('view.setting.other')}}
|
||||||
|
dd
|
||||||
|
h3#other_tray_theme {{$t('view.setting.other_tray_theme')}}
|
||||||
|
div
|
||||||
|
material-checkbox(:id="'setting_tray_theme_' + item.id" v-model="current_setting.tray.themeId" name="setting_tray_theme" need :class="$style.gapLeft"
|
||||||
|
:label="$t('view.setting.other_tray_theme_' + item.name)" :key="item.id" :value="item.id" v-for="item in trayThemeList")
|
||||||
|
dd
|
||||||
|
h3#other_resource_cache {{$t('view.setting.other_resource_cache')}}
|
||||||
|
div
|
||||||
|
p
|
||||||
|
| {{$t('view.setting.other_resource_cache_label')}}
|
||||||
|
span.auto-hidden {{cacheSize}}
|
||||||
|
p
|
||||||
|
material-btn(:class="$style.btn" min :disabled="isDisabledResourceCacheClear" @click="clearResourceCache") {{$t('view.setting.other_resource_cache_clear_btn')}}
|
||||||
|
dd
|
||||||
|
h3#other_play_list_cache {{$t('view.setting.other_play_list_cache')}}
|
||||||
|
div
|
||||||
|
material-btn(:class="$style.btn" min :disabled="isDisabledListCacheClear" @click="clearListCache") {{$t('view.setting.other_play_list_cache_clear_btn')}}
|
||||||
|
|
||||||
|
dt#update {{$t('view.setting.update')}}
|
||||||
|
dd
|
||||||
|
p.small
|
||||||
|
| {{$t('view.setting.update_latest_label')}}{{version.newVersion ? version.newVersion.version : $t('view.setting.update_unknown')}}
|
||||||
|
p.small {{$t('view.setting.update_current_label')}}{{version.version}}
|
||||||
|
p.small(v-if="this.version.downloadProgress" style="line-height: 1.5;")
|
||||||
|
| {{$t('view.setting.update_downloading')}}
|
||||||
|
br
|
||||||
|
| {{$t('view.setting.update_progress')}}{{downloadProgress}}
|
||||||
|
p(v-if="version.newVersion")
|
||||||
|
span(v-if="version.isLatestVer") {{$t('view.setting.update_latest')}}
|
||||||
|
material-btn(v-else :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") {{$t('view.setting.update_open_version_modal_btn')}}
|
||||||
|
p.small(v-else) {{$t('view.setting.update_checking')}}
|
||||||
|
dt#about {{$t('view.setting.about')}}
|
||||||
|
dd
|
||||||
|
p.small
|
||||||
|
| 本软件完全免费,代码已开源,开源地址:
|
||||||
|
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop
|
||||||
|
p.small
|
||||||
|
| 最新版网盘下载地址(网盘内有Windows、MAC版):
|
||||||
|
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')") 网盘地址
|
||||||
|
| 密码:
|
||||||
|
span.hover(:tips="$t('view.setting.click_copy')" @click="clipboardWriteText('glqw')") glqw
|
||||||
|
p.small
|
||||||
|
| 软件的常见问题可转至:
|
||||||
|
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题
|
||||||
|
p.small
|
||||||
|
strong 仔细 仔细 仔细
|
||||||
|
| 地阅读常见问题后,
|
||||||
|
p.small
|
||||||
|
| 仍有问题可加企鹅群
|
||||||
|
span.hover(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506
|
||||||
|
| 反馈
|
||||||
|
strong (为免满人,无事勿加,入群先看群公告)
|
||||||
|
| ,或到 GitHub 提交
|
||||||
|
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue
|
||||||
|
|
||||||
dt#network {{$t('view.setting.network')}}
|
|
||||||
dd
|
|
||||||
h3#network_proxy_title {{$t('view.setting.network_proxy_title')}}
|
|
||||||
div
|
|
||||||
p
|
|
||||||
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" :label="$t('view.setting.is_enable')")
|
|
||||||
p
|
|
||||||
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.host" @change="handleProxyChange('host')" :placeholder="$t('view.setting.network_proxy_host')")
|
|
||||||
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.port" @change="handleProxyChange('port')" :placeholder="$t('view.setting.network_proxy_port')")
|
|
||||||
p
|
|
||||||
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.username" @change="handleProxyChange('username')" :placeholder="$t('view.setting.network_proxy_username')")
|
|
||||||
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.password" @change="handleProxyChange('password')" type="password" :placeholder="$t('view.setting.network_proxy_password')")
|
|
||||||
dt#odc {{$t('view.setting.odc')}}
|
|
||||||
dd
|
|
||||||
div(:class="$style.gapTop")
|
|
||||||
material-checkbox(id="setting_odc_isAutoClearSearchInput" v-model="current_setting.odc.isAutoClearSearchInput" :label="$t('view.setting.odc_clear_search_input')")
|
|
||||||
div(:class="$style.gapTop")
|
|
||||||
material-checkbox(id="setting_odc_isAutoClearSearchList" v-model="current_setting.odc.isAutoClearSearchList" :label="$t('view.setting.odc_clear_search_list')")
|
|
||||||
dt#backup {{$t('view.setting.backup')}}
|
|
||||||
dd
|
|
||||||
h3#backup_part {{$t('view.setting.backup_part')}}
|
|
||||||
div
|
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportPlayList") {{$t('view.setting.backup_part_import_list')}}
|
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportPlayList") {{$t('view.setting.backup_part_export_list')}}
|
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportSetting") {{$t('view.setting.backup_part_import_setting')}}
|
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportSetting") {{$t('view.setting.backup_part_export_setting')}}
|
|
||||||
dd
|
|
||||||
h3#backup_all {{$t('view.setting.backup_all')}}
|
|
||||||
div
|
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportAllData") {{$t('view.setting.backup_all_import')}}
|
|
||||||
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") {{$t('view.setting.backup_all_export')}}
|
|
||||||
dt#other {{$t('view.setting.other')}}
|
|
||||||
dd
|
|
||||||
h3#other_tray_theme {{$t('view.setting.other_tray_theme')}}
|
|
||||||
div
|
|
||||||
material-checkbox(:id="'setting_tray_theme_' + item.id" v-model="current_setting.tray.themeId" name="setting_tray_theme" need :class="$style.gapLeft"
|
|
||||||
:label="$t('view.setting.other_tray_theme_' + item.name)" :key="item.id" :value="item.id" v-for="item in trayThemeList")
|
|
||||||
dd
|
|
||||||
h3#other_cache {{$t('view.setting.other_cache')}}
|
|
||||||
div
|
|
||||||
p
|
|
||||||
| {{$t('view.setting.other_cache_label')}}
|
|
||||||
span.auto-hidden(:tips="$t('view.setting.other_cache_label_title')") {{cacheSize}}
|
|
||||||
p
|
|
||||||
material-btn(:class="$style.btn" min @click="clearCache") {{$t('view.setting.other_cache_clear_btn')}}
|
|
||||||
dt#update {{$t('view.setting.update')}}
|
|
||||||
dd
|
|
||||||
p.small
|
|
||||||
| {{$t('view.setting.update_latest_label')}}{{version.newVersion ? version.newVersion.version : $t('view.setting.update_unknown')}}
|
|
||||||
p.small {{$t('view.setting.update_current_label')}}{{version.version}}
|
|
||||||
p.small(v-if="this.version.downloadProgress" style="line-height: 1.5;")
|
|
||||||
| {{$t('view.setting.update_downloading')}}
|
|
||||||
br
|
br
|
||||||
| {{$t('view.setting.update_progress')}}{{downloadProgress}}
|
p.small 感谢以前捐赠过的人❤️,现在软件不再接受捐赠,建议把你们的爱心用来支持正版音乐,
|
||||||
p(v-if="version.newVersion")
|
p.small 由于软件开发的初衷仅是为了对新技术的学习与研究,因此软件直至停止维护都将会一直保持纯净。
|
||||||
span(v-if="version.isLatestVer") {{$t('view.setting.update_latest')}}
|
|
||||||
material-btn(v-else :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") {{$t('view.setting.update_open_version_modal_btn')}}
|
|
||||||
p.small(v-else) {{$t('view.setting.update_checking')}}
|
|
||||||
dt#about {{$t('view.setting.about')}}
|
|
||||||
dd
|
|
||||||
p.small
|
|
||||||
| 本软件完全免费,代码已开源,开源地址:
|
|
||||||
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop
|
|
||||||
p.small
|
|
||||||
| 最新版网盘下载地址(网盘内有Windows、MAC版):
|
|
||||||
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')") 网盘地址
|
|
||||||
| 密码:
|
|
||||||
span.hover(:tips="$t('view.setting.click_copy')" @click="clipboardWriteText('glqw')") glqw
|
|
||||||
p.small
|
|
||||||
| 软件的常见问题可转至:
|
|
||||||
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题
|
|
||||||
p.small
|
|
||||||
strong 仔细 仔细 仔细
|
|
||||||
| 地阅读常见问题后,
|
|
||||||
p.small
|
|
||||||
| 仍有问题可加企鹅群
|
|
||||||
span.hover(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506
|
|
||||||
| 反馈
|
|
||||||
strong (为免满人,无事勿加,入群先看群公告)
|
|
||||||
| ,或到 GitHub 提交
|
|
||||||
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue
|
|
||||||
|
|
||||||
br
|
p.small
|
||||||
p.small 感谢以前捐赠过的人❤️,现在软件不再接受捐赠,建议把你们的爱心用来支持正版音乐,
|
| 你已签署本软件的
|
||||||
p.small 由于软件开发的初衷仅是为了对新技术的学习与研究,因此软件直至停止维护都将会一直保持纯净。
|
material-btn(min @click="handleShowPact") 许可协议
|
||||||
|
| ,协议的在线版本在
|
||||||
|
strong.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里
|
||||||
|
| 。
|
||||||
|
br
|
||||||
|
|
||||||
p.small
|
p
|
||||||
| 你已签署本软件的
|
small By:
|
||||||
material-btn(min @click="handleShowPact") 许可协议
|
| 落雪无痕
|
||||||
| ,协议的在线版本在
|
material-user-api-modal(v-model="isShowUserApiModal")
|
||||||
strong.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里
|
|
||||||
| 。
|
|
||||||
br
|
|
||||||
|
|
||||||
p
|
|
||||||
small By:
|
|
||||||
| 落雪无痕
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -296,32 +306,41 @@ export default {
|
||||||
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
|
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
|
||||||
: this.$t('view.setting.update_init')
|
: this.$t('view.setting.update_init')
|
||||||
},
|
},
|
||||||
togglePlayMethods() {
|
// togglePlayMethods() {
|
||||||
return [
|
// return [
|
||||||
{
|
// {
|
||||||
name: this.$t('view.setting.play_toggle_list_loop'),
|
// name: this.$t('view.setting.play_toggle_list_loop'),
|
||||||
value: 'listLoop',
|
// value: 'listLoop',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: this.$t('view.setting.play_toggle_random'),
|
// name: this.$t('view.setting.play_toggle_random'),
|
||||||
value: 'random',
|
// value: 'random',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: this.$t('view.setting.play_toggle_list'),
|
// name: this.$t('view.setting.play_toggle_list'),
|
||||||
value: 'list',
|
// value: 'list',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: this.$t('view.setting.play_toggle_single_loop'),
|
// name: this.$t('view.setting.play_toggle_single_loop'),
|
||||||
value: 'singleLoop',
|
// value: 'singleLoop',
|
||||||
},
|
// },
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
apiSources() {
|
apiSources() {
|
||||||
return apiSourceInfo.map(api => ({
|
return [
|
||||||
id: api.id,
|
...apiSourceInfo.map(api => ({
|
||||||
label: this.$t('view.setting.basic_source_' + api.id) || api.name,
|
id: api.id,
|
||||||
disabled: api.disabled,
|
label: this.$t('view.setting.basic_source_' + api.id) || api.name,
|
||||||
}))
|
disabled: api.disabled,
|
||||||
|
})),
|
||||||
|
...window.globalObj.userApi.list.map(api => ({
|
||||||
|
id: api.id,
|
||||||
|
label: `${api.name}${api.description ? `(${api.description})` : ''}${api.id == this.setting.apiSource ? `[${this.getApiStatus()}]` : ''}`,
|
||||||
|
status: api.status,
|
||||||
|
message: api.message,
|
||||||
|
disabled: false,
|
||||||
|
})),
|
||||||
|
]
|
||||||
},
|
},
|
||||||
sourceNameTypes() {
|
sourceNameTypes() {
|
||||||
return [
|
return [
|
||||||
|
@ -566,10 +585,13 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
isEditHotKey: false,
|
isEditHotKey: false,
|
||||||
|
isShowUserApiModal: false,
|
||||||
toc: {
|
toc: {
|
||||||
list: [],
|
list: [],
|
||||||
activeId: '',
|
activeId: '',
|
||||||
},
|
},
|
||||||
|
isDisabledResourceCacheClear: false,
|
||||||
|
isDisabledListCacheClear: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -590,6 +612,9 @@ export default {
|
||||||
'setting.player.isMute'(n) {
|
'setting.player.isMute'(n) {
|
||||||
this.current_setting.player.isMute = n
|
this.current_setting.player.isMute = n
|
||||||
},
|
},
|
||||||
|
'setting.apiSource'(n) {
|
||||||
|
this.current_setting.apiSource = n
|
||||||
|
},
|
||||||
'setting.desktopLyric.enable'(n) {
|
'setting.desktopLyric.enable'(n) {
|
||||||
this.current_setting.desktopLyric.enable = n
|
this.current_setting.desktopLyric.enable = n
|
||||||
},
|
},
|
||||||
|
@ -599,6 +624,9 @@ export default {
|
||||||
'setting.player.togglePlayMethod'(n) {
|
'setting.player.togglePlayMethod'(n) {
|
||||||
this.current_setting.player.togglePlayMethod = n
|
this.current_setting.player.togglePlayMethod = n
|
||||||
},
|
},
|
||||||
|
// 'setting.player.isPlayLxlrc'(n) {
|
||||||
|
// this.current_setting.player.isPlayLxlrc = n
|
||||||
|
// },
|
||||||
'current_setting.player.isShowTaskProgess'(n) {
|
'current_setting.player.isShowTaskProgess'(n) {
|
||||||
if (n) return
|
if (n) return
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
@ -624,7 +652,10 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['setSetting', 'setSettingVersion', 'setVersionModalVisible']),
|
...mapMutations(['setSetting', 'setSettingVersion', 'setVersionModalVisible']),
|
||||||
...mapMutations('list', ['setList']),
|
...mapMutations('list', {
|
||||||
|
setList: 'setList',
|
||||||
|
clearMyListCache: 'clearCache',
|
||||||
|
}),
|
||||||
...mapMutations(['setMediaDeviceId']),
|
...mapMutations(['setMediaDeviceId']),
|
||||||
init() {
|
init() {
|
||||||
this.current_setting = JSON.parse(JSON.stringify(this.setting))
|
this.current_setting = JSON.parse(JSON.stringify(this.setting))
|
||||||
|
@ -859,11 +890,18 @@ export default {
|
||||||
this.cacheSize = sizeFormate(size)
|
this.cacheSize = sizeFormate(size)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clearCache() {
|
clearResourceCache() {
|
||||||
|
this.isDisabledResourceCacheClear = true
|
||||||
clearCache().then(() => {
|
clearCache().then(() => {
|
||||||
this.getCacheSize()
|
this.getCacheSize()
|
||||||
|
this.isDisabledResourceCacheClear = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
clearListCache() {
|
||||||
|
this.isDisabledListCacheClear = true
|
||||||
|
this.clearMyListCache()
|
||||||
|
this.isDisabledListCacheClear = false
|
||||||
|
},
|
||||||
handleWindowSizeChange(index) {
|
handleWindowSizeChange(index) {
|
||||||
let info = index == null ? this.windowSizeList[2] : this.windowSizeList[index]
|
let info = index == null ? this.windowSizeList[2] : this.windowSizeList[index]
|
||||||
setWindowSize(info.width, info.height)
|
setWindowSize(info.width, info.height)
|
||||||
|
@ -1070,6 +1108,14 @@ export default {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getApiStatus() {
|
||||||
|
let status
|
||||||
|
if (window.globalObj.userApi.status) status = this.$t('view.setting.basic_source_status_success')
|
||||||
|
else if (window.globalObj.userApi.message == 'initing') status = this.$t('view.setting.basic_source_status_initing')
|
||||||
|
else status = `${this.$t('view.setting.basic_source_status_failed')} - ${window.globalObj.userApi.message}`
|
||||||
|
|
||||||
|
return status
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1077,12 +1123,12 @@ export default {
|
||||||
<style lang="less" module>
|
<style lang="less" module>
|
||||||
@import '../assets/styles/layout.less';
|
@import '../assets/styles/layout.less';
|
||||||
|
|
||||||
// .main {
|
.main {
|
||||||
// display: flex;
|
display: flex;
|
||||||
// flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
// height: 100%;
|
height: 100%;
|
||||||
// border-top: 1px solid rgba(0, 0, 0, 0.12);
|
// border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// .toc {
|
// .toc {
|
||||||
// flex: 0 0 15%;
|
// flex: 0 0 15%;
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
material-select(:class="$style.select" :list="sourceInfo.sources" item-key="id" item-name="name" v-model="source")
|
material-select(:class="$style.select" :list="sourceInfo.sources" item-key="id" item-name="name" v-model="source")
|
||||||
div(:class="$style.songListContent")
|
div(:class="$style.songListContent")
|
||||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||||
div(:class="$style.songListContent" v-show="listData.list.length")
|
div(:class="$style.songListContent")
|
||||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||||
div.scroll(:class="$style.songList" v-if="sortId !== 'importSongList'" ref="dom_scrollContent")
|
div.scroll(:class="$style.songList" v-if="sortId !== 'importSongList'" ref="dom_scrollContent")
|
||||||
ul
|
ul
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
li {{$t('view.song_list.tip_2')}}
|
li {{$t('view.song_list.tip_2')}}
|
||||||
li {{$t('view.song_list.tip_3')}}
|
li {{$t('view.song_list.tip_3')}}
|
||||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||||
div(v-show="!listData.list.length" :class="$style.noitem")
|
div(v-show="!listData.list.length && sortId !== 'importSongList'" :class="$style.noitem")
|
||||||
p {{$t('view.song_list.loding_list')}}
|
p {{$t('view.song_list.loding_list')}}
|
||||||
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
|
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
|
||||||
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectedData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
|
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectedData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
|
||||||
|
@ -239,7 +239,7 @@ export default {
|
||||||
break
|
break
|
||||||
case 'play':
|
case 'play':
|
||||||
if (this.selectedData.length) {
|
if (this.selectedData.length) {
|
||||||
this.listAddMultiple({ id: 'default', list: this.filterList(this.selectedData) })
|
this.listAddMultiple({ id: 'default', list: [...this.selectedData] })
|
||||||
this.resetSelect()
|
this.resetSelect()
|
||||||
}
|
}
|
||||||
this.testPlay(info.index)
|
this.testPlay(info.index)
|
||||||
|
|
Loading…
Reference in New Issue