Merge branch 'dev'

pull/930/merge v1.17.0
lyswhut 2022-01-22 13:02:05 +08:00
commit 0580b059fc
57 changed files with 2235 additions and 2697 deletions

View File

@ -6,15 +6,15 @@
"corejs": "3", "corejs": "3",
"useBuiltIns": "usage" "useBuiltIns": "usage"
} }
],
[
"minify",
{
"builtIns": false,
"evaluate": false,
"mangle": false
}
] ]
// [
// "minify",
// {
// "builtIns": false,
// "evaluate": false,
// "mangle": false
// }
// ]
], ],
"plugins": [ "plugins": [
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",

View File

@ -6,7 +6,10 @@
"plugins": [ "plugins": [
"html" "html"
], ],
"parser": "babel-eslint", "parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false
},
"rules": { "rules": {
"no-new": "off", "no-new": "off",
"camelcase": "off", "camelcase": "off",

View File

@ -1,12 +1,9 @@
module.exports = { module.exports = {
// upgrade: true, upgrade: true,
// target: 'newest', // target: 'newest',
reject: [ reject: [
'webpack-dev-server',
'eslint',
'electron', 'electron',
'electron-builder', 'electron-builder',
'chalk', 'chalk',
// 'eslint-config-standard'
] ]
} }

View File

@ -6,6 +6,32 @@ 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.17.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.16.0...v1.17.0) - 2022-01-22
### 新增
- 新增“便携”功能在Windows平台下若程序目录下存在 portable 目录,则自动使用此目录作为数据存储目录
- 新增 Scheme URL 支持同时发布lx-music-script项目配合使用一个油猴脚本可以在浏览器中的官方平台网页直接调用LX MusicScheme URL的调用说明看Readme.md文档的Scheme URL支持部分
- 新增启动参数`-proxy-server`与`-proxy-bypass-list`详细介绍看Readme.md文档的启动参数部分
- 新增桌面歌词是否延迟滚动设置,默认开启,若你不想要桌面歌词延迟滚动可以去设置-桌面歌词设置关掉
### 优化
- 为可视化音频的频谱整体添加频谱均值加成,使频谱显示更有节奏感
- 优化程序初始化逻辑,修复无网络的情况下的初始化问题
- 我的列表-列表名的右击菜单更新已收藏的在线列表时将始终重新加载不再使用缓存解决在原平台更新歌单后在LX点击更新可能看到的还是在原平台更新前的歌单的问题
### 修复
- 修复代理不生效的问题
- 修复`openDevTools`选项无效的问题
- 修复播放状态的提示问题
- 修复tx源无搜索结果的问题
### 其他
- 更新 Electron 到 v13.6.7
## [1.16.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.3...v1.16.0) - 2022-01-01 ## [1.16.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.3...v1.16.0) - 2022-01-01
这算是一个大版本,对主窗口部分的代码逻辑做了较大改动,但由于界面的改动不大,所以没有更新大版本号。 这算是一个大版本,对主窗口部分的代码逻辑做了较大改动,但由于界面的改动不大,所以没有更新大版本号。

45
FAQ.md
View File

@ -260,6 +260,16 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
- <http://www.pc6.com/edu/168719.html> - <http://www.pc6.com/edu/168719.html>
- <https://blog.csdn.net/for641/article/details/104811538> - <https://blog.csdn.net/for641/article/details/104811538>
## 数据存储路径
默认情况下,软件的数据存储在:
- Windows`%APPDATA%/lx-music-desktop`
- Linux`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
- macOS`~/Library/Application/lx-music-desktop`
在Windows平台下若程序目录下存在`portable`目录则自动使用此目录作为数据存储目录v1.17.0新增)。
## 杀毒软件提示有病毒或恶意行为 ## 杀毒软件提示有病毒或恶意行为
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br> 本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br>
@ -270,6 +280,41 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件! 最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
## 启动参数
目前软件已支持的启动参数如下:
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
- `-dha` 禁用硬件加速启动Disable Hardware Acceleration窗口显示有问题时可以尝试添加此参数启动v1.6.0起新增)
- `-dt` 以非透明模式启动Disable Transparent对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示该参数对桌面歌词无效原来的`-nt`参数已重命名为`-dt`v1.6.0起重命名)
- `-dhmkh` 禁用硬件媒体密钥处理Disable Hardware Media Key Handling此选项将禁用Chromium的Hardware Media Key Handling特性v1.9.0起新增)
- `-proxy-server` 设置代理服务器,代理应用的所有流量,例:`-proxy-server="127.0.0.1:1081"`不支持设置账号密码v1.17.0起新增)。注:应用内“设置-网络-代理设置”仅代理接口请求的流量,优先级更高
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器,例:`-proxy-bypass-list="<local>;*.google.com;*foo.com;1.2.3.4:5678"`(与`-proxy-server`一起使用才有效v1.17.0起新增)。注:此设置对应用内接口请求无效
- `-play` 启动时播放指定列表的音乐,参数说明:
- `type`:播放类型,目前固定为`songList`
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
- `link`要播放的在线列表歌单链接、或IDsource为`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"`
## Scheme URL支持
从v1.17.0起支持 Scheme URL可以使用此功能从浏览器等场景下调用LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用<br>
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148-lx-msuic-%E8%BE%85%E5%8A%A9%E8%84%9A%E6%9C%AC><br>
以下是目前可用的Scheme URL调用方式
- URL统一以`lxmusic://`开头
- 此技术目前只支持 Windows、Mac系统
- URL传参以经过URL编码的JSON数据传参`lxmusic://music/play?data=xxxx`,其中`xxxx`为经过URL编码后的JSON数据
- 若无特别说明,源的可用值为:`kw/kg/tx/wy/mg`
- 若无特别说明,音质的可用值为:`128k/320k/flac/flac32bit`
| 描述 | URL | 参数
| --- | --- | ---
| 打开歌单 | `songlist/open` | `source<String>`(源,必须)<br>`id<String/Number>`歌单ID可选<br>`url<String>`歌单URL可选其中ID与URL必需传一个
| 播放歌单 | `songlist/play` | `source<String>`(源,必须)<br>`id<String/Number>`歌单ID可选<br>`url<String>`歌单URL可选其中`id`与`url`必需传一个<br>`index<Number>`播放第几首歌可选从0开始
| 播放歌曲 | `music/play` | `name<String>`(歌曲名,必传)<br>`singer<String>`(艺术家名,必传)<br>`source<String>`(源,必传)<br>`songmid<String/Number>`歌曲ID必传<br>`img<String>`(歌曲图片链接,选传)<br>`albumId<String/Number>`歌曲专辑ID选传<br>`interval<String>`(格式化后的歌曲时长,选传,例:`03:55`<br>`albumName<String>`(歌曲专辑名称,选传)<br>`types<Object>`(歌曲可用音质数组,必传,<br>数组格式:`[{"type": "<音质>", size: "<格式化后的文件大小,选传>", hash: "<kg源必传>"}]`<br>例:`[{"type": "128k", size: "3.56M"}, {"type": "320k", size: null}]`<br><br>以下为平台特定参数:<br>`hash<String>`歌曲hashkg源必传<br>`strMediaMid<String>`歌曲strMediaMidtx源必传<br>`albumMid<String>`歌曲albumMidtx源专用选传<br>`copyrightId<String>`歌曲copyrightIdmg源必传<br>`lrcUrl<String>`歌曲lrcUrlmg源专用选传
## 自定义源脚本编写说明 ## 自定义源脚本编写说明
文件请使用UTF-8编码格式编写脚本所用编程语言为JavaScript可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子: 文件请使用UTF-8编码格式编写脚本所用编程语言为JavaScript可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子:

View File

@ -50,6 +50,35 @@
或者到网盘下载网盘内有MAC、windows版`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br> 或者到网盘下载网盘内有MAC、windows版`https://www.lanzoui.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)
#### Scheme URL支持
从v1.17.0起支持 Scheme URL可以使用此功能从浏览器等场景下调用LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148-lx-msuic-%E8%BE%85%E5%8A%A9%E8%84%9A%E6%9C%AC><br>
#### 启动参数
目前软件已支持的启动参数如下:
- `-proxy-server` 设置代理服务器,代理应用的所有流量
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器
- `-play` 启动时播放指定列表的音乐
- `-search` 启动软件时自动在搜索框搜索指定的内容
- `-dha` 禁用硬件加速启动Disable Hardware Acceleration
- `-dt` 以非透明模式启动Disable Transparent
- `-dhmkh` 禁用硬件媒体密钥处理Disable Hardware Media Key Handling
启动参数的详细说明请看[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0)
#### 数据存储路径
默认情况下,软件的数据存储在:
- Windows`%APPDATA%/lx-music-desktop`
- Linux`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
- macOS`~/Library/Application/lx-music-desktop`
在Windows平台下若程序目录下存在`portable`目录则自动使用此目录作为数据存储目录v1.17.0新增)。
### 源码使用方法 ### 源码使用方法
环境要求Node.js 14+ 环境要求Node.js 14+
@ -76,22 +105,6 @@ npm run pack:linux
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p> <p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
### 启动参数
目前软件已支持的启动参数如下:
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
- `-dha` 禁用硬件加速启动Disable Hardware Acceleration窗口显示有问题时可以尝试添加此参数启动v1.6.0起新增)
- `-dt` 以非透明模式启动Disable Transparent对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示该参数对桌面歌词无效原来的`-nt`参数已重命名为`-dt`v1.6.0起重命名)
- `-dhmkh` 禁用硬件媒体密钥处理Disable Hardware Media Key Handling此选项将禁用Chromium的Hardware Media Key Handling特性v1.9.0起新增)
- `-play` 启动时播放指定列表的音乐,参数说明:
- `type`:播放类型,目前固定为`songList`
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
- `link`要播放的在线列表歌单链接、或IDsource为`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"`
### 常见问题 ### 常见问题
常见问题已移至:<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md> 常见问题已移至:<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>

View File

@ -40,4 +40,7 @@ module.exports = merge(baseConfig, {
}, },
}), }),
], ],
optimization: {
minimize: false,
},
}) })

View File

@ -147,6 +147,7 @@ module.exports = {
}), }),
new ESLintPlugin({ new ESLintPlugin({
extensions: ['js', 'vue'], extensions: ['js', 'vue'],
formatter: require('eslint-formatter-friendly'),
}), }),
], ],
} }

View File

@ -9,7 +9,6 @@ module.exports = merge(baseConfig, {
mode: 'development', mode: 'development',
devtool: 'eval-source-map', devtool: 'eval-source-map',
plugins: [ plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: '"development"', NODE_ENV: '"development"',

View File

@ -37,6 +37,7 @@ module.exports = merge(baseConfig, {
}), }),
], ],
optimization: { optimization: {
minimize: false,
minimizer: [ minimizer: [
new TerserPlugin(), new TerserPlugin(),
new CssMinimizerPlugin(), new CssMinimizerPlugin(),

View File

@ -147,6 +147,7 @@ module.exports = {
}), }),
new ESLintPlugin({ new ESLintPlugin({
extensions: ['js', 'vue'], extensions: ['js', 'vue'],
formatter: require('eslint-formatter-friendly'),
}), }),
], ],
} }

View File

@ -9,7 +9,6 @@ module.exports = merge(baseConfig, {
mode: 'development', mode: 'development',
devtool: 'eval-source-map', devtool: 'eval-source-map',
plugins: [ plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: '"development"', NODE_ENV: '"development"',

View File

@ -43,27 +43,26 @@ function startRenderer() {
// // logStats('Renderer', stats) // // logStats('Renderer', stats)
// }) // })
const server = new WebpackDevServer( const server = new WebpackDevServer({
compiler, port: 9080,
{ hot: true,
contentBase: path.join(__dirname, '../'), historyApiFallback: true,
quiet: true, // static: {
hot: true, // directory: path.join(__dirname, '../'),
historyApiFallback: true, // },
clientLogLevel: 'warning', client: {
overlay: { logging: 'warn',
errors: true, overlay: true,
},
before(app, ctx) {
app.use(hotMiddlewareRenderer)
ctx.middleware.waitUntilValid(() => {
resolve()
})
},
}, },
) setupMiddlewares(middlewares, devServer) {
devServer.app.use(hotMiddlewareRenderer)
devServer.middleware.waitUntilValid(resolve)
server.listen(9080) return middlewares
},
}, compiler)
server.start()
}) })
} }
@ -90,27 +89,25 @@ function startRendererLyric() {
// // logStats('Renderer', stats) // // logStats('Renderer', stats)
// }) // })
const server = new WebpackDevServer( const server = new WebpackDevServer({
compiler, port: 9081,
{ hot: true,
contentBase: path.join(__dirname, '../'), historyApiFallback: true,
quiet: true, // static: {
hot: true, // directory: path.join(__dirname, '../'),
historyApiFallback: true, // },
clientLogLevel: 'warning', client: {
overlay: { logging: 'warn',
errors: true, overlay: true,
},
before(app, ctx) {
app.use(hotMiddlewareRendererLyric)
ctx.middleware.waitUntilValid(() => {
resolve()
})
},
}, },
) setupMiddlewares(middlewares, devServer) {
devServer.app.use(hotMiddlewareRenderer)
devServer.middleware.waitUntilValid(resolve)
return middlewares
},
}, compiler)
server.listen(9081) server.start()
}) })
} }
@ -202,8 +199,14 @@ function init() {
} }
Promise.all([ Promise.all([
startRenderer().then(() => handleSuccess('renderer')).catch(() => handleFail('renderer')), startRenderer().then(() => handleSuccess('renderer')).catch((err) => {
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch(() => handleFail('renderer-lyric')), console.error(err.message)
return handleFail('renderer')
}),
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch((err) => {
console.error(err.message)
return handleFail('renderer-lyric')
}),
startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')), startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')),
]).then(startElectron).catch(err => { ]).then(startElectron).catch(err => {
console.error(err) console.error(err)

3855
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "lx-music-desktop", "name": "lx-music-desktop",
"version": "1.16.0", "version": "1.17.0",
"description": "一个免费的音乐查找助手", "description": "一个免费的音乐查找助手",
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
"productName": "lx-music-desktop", "productName": "lx-music-desktop",
@ -64,19 +64,25 @@
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress", "build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress",
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress", "build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress",
"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",
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm run pack", "dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i" "up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i"
}, },
"browserslist": [ "browserslist": [
"Electron 13.4.0" "Electron 13.6.7"
], ],
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
}, },
"build": { "build": {
"appId": "cn.toside.music.desktop", "appId": "cn.toside.music.desktop",
"protocols": {
"name": "lx-music-protocol",
"schemes": [
"lxmusic"
]
},
"directories": { "directories": {
"buildResources": "./resources", "buildResources": "./resources",
"output": "./build" "output": "./build"
@ -167,47 +173,47 @@
}, },
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme", "homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.7", "@babel/core": "^7.16.10",
"@babel/eslint-parser": "^7.16.5",
"@babel/plugin-proposal-class-properties": "^7.16.7", "@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.16.7", "@babel/plugin-transform-modules-umd": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.16.7", "@babel/plugin-transform-runtime": "^7.16.10",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.16.7", "@babel/preset-env": "^7.16.11",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"babel-preset-minify": "^0.5.1", "babel-preset-minify": "^0.5.1",
"browserslist": "^4.19.1", "browserslist": "^4.19.1",
"cfonts": "^2.10.0", "cfonts": "^2.10.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"changelog-parser": "^2.8.0", "changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^10.2.0", "copy-webpack-plugin": "^10.2.1",
"core-js": "^3.20.1", "core-js": "^3.20.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.3.1", "css-minimizer-webpack-plugin": "^3.4.1",
"del": "^6.0.0", "del": "^6.0.0",
"electron": "^13.4.0", "electron": "^13.6.7",
"electron-builder": "^22.11.7", "electron-builder": "^22.11.7",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.31", "electron-to-chromium": "^1.4.51",
"eslint": "^7.32.0", "eslint": "^8.7.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
"eslint-formatter-friendly": "^7.0.0", "eslint-formatter-friendly": "^7.0.0",
"eslint-plugin-html": "^6.2.0", "eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.3", "eslint-plugin-import": "^2.25.4",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^4.1.0", "eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^8.2.0", "eslint-plugin-vue": "^8.3.0",
"eslint-webpack-plugin": "^3.1.1", "eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"less": "^4.1.2", "less": "^4.1.2",
"less-loader": "^10.2.0", "less-loader": "^10.2.0",
"markdown-it": "^12.3.0", "markdown-it": "^12.3.2",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.5.2",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"postcss-loader": "^6.2.1", "postcss-loader": "^6.2.1",
@ -225,31 +231,31 @@
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"webpack": "^5.65.0", "webpack": "^5.67.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^4.7.3",
"webpack-hot-middleware": "^2.25.1", "webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },
"dependencies": { "dependencies": {
"bufferutil": "^4.0.5", "bufferutil": "^4.0.6",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"electron-log": "^4.4.4", "electron-log": "^4.4.5",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"electron-updater": "^4.6.1", "electron-updater": "^4.6.1",
"font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d", "font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
"http-terminator": "^3.0.4", "http-terminator": "^3.0.4",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"image-size": "^1.0.0", "image-size": "^1.0.1",
"koa": "^2.13.4", "koa": "^2.13.4",
"long": "^5.2.0", "long": "^5.2.0",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"needle": "^3.0.0", "needle": "^3.0.0",
"node-id3": "^0.2.3", "node-id3": "^0.2.3",
"request": "^2.88.2", "request": "^2.88.2",
"socket.io": "^4.4.0", "socket.io": "^4.4.1",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"utf-8-validate": "^5.0.7", "utf-8-validate": "^5.0.8",
"vue": "^3.2.26", "vue": "^3.2.26",
"vue-i18n": "^9.2.0-beta.26", "vue-i18n": "^9.2.0-beta.26",
"vue-router": "^4.0.12", "vue-router": "^4.0.12",

View File

@ -1,40 +1,23 @@
这算是一个大版本,对主窗口部分的代码逻辑做了较大改动,但由于界面的改动不大,所以没有更新大版本号。
虽然经过一个月的测试与问题修复,但可能仍然存在未发现的问题,若你发现某些界面异常、某些行为与旧版本存在差异等问题,欢迎反馈!
另外祝大家元旦快乐~
### 新增 ### 新增
- 播放详情页新增音量控制条 - 新增“便携”功能在Windows平台下若程序目录下存在 portable 目录,则自动使用此目录作为数据存储目录
- 播放详情页新增桌面歌词切换按钮 - 新增 Scheme URL 支持同时发布lx-music-script项目配合使用一个油猴脚本可以在浏览器中的官方平台网页直接调用LX MusicScheme URL的调用说明看Readme.md文档的Scheme URL支持部分
- 新增将我的列表保存为TXT、CSV格式可以去设置-备份与恢复中使用注意此类格式的备份目前不支持恢复到LX Music中 - 新增启动参数`-proxy-server`与`-proxy-bypass-list`详细介绍看Readme.md文档的启动参数部分
- 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用 - 新增桌面歌词是否延迟滚动设置,默认开启,若你不想要桌面歌词延迟滚动可以去设置-桌面歌词设置关掉
- 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启
- 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表
- 播放详情页新增音频可视化功能(实验性)
- 我的列表新增拖动调整位置功能按住Ctrl键Mac上对应Command键的时候将进入“拖动模式”此时可以拖动列表的位置来调整顺序
### 优化 ### 优化
- 优化列表性能,软件整体性能 - 为可视化音频的频谱整体添加频谱均值加成,使频谱显示更有节奏感
- 调整Mac平台下的图标大小 - 优化程序初始化逻辑,修复无网络的情况下的初始化问题
- 同步功能添加对列表顺序调整的控制,确保手动调整位置后的列表与不同的电脑同步时,列表位置不会被还原 - 我的列表-列表名的右击菜单更新已收藏的在线列表时将始终重新加载不再使用缓存解决在原平台更新歌单后在LX点击更新可能看到的还是在原平台更新前的歌单的问题
- 优化歌单详情、排行榜名右键的播放按钮的播放机制,现在不用等待整个列表(多页时)加载完成才能播放了
- 为播放详情页、桌面歌词添加延迟滚动,播放详情页略微减小已激活歌词的缩放大小及桌面歌词翻译大小
- 修改右边控制按钮为windows风格
- 更新了新年皮肤的背景与配色,欢迎体验~
### 修复 ### 修复
- 修复kw源某些歌曲的歌词提取异常的问题 - 修复代理不生效的问题
- 修复`openDevTools`选项无效的问题
### 变更 - 修复播放状态的提示问题
- 修复tx源无搜索结果的问题
- 现在使用繁体中文语言时将不再自动转换歌词,转换行为将由上面新增的转换开关控制
### 移除
- 移除我的列表右键菜单的“上移、下移列表”功能,调整改用新增的拖动功能去调整位置
### 其他 ### 其他
- 升级vue到 3.x - 更新 Electron 到 v13.6.7

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@ const path = require('path')
const os = require('os') const os = require('os')
const defaultSetting = { const defaultSetting = {
version: '1.0.47', version: '1.0.48',
player: { player: {
togglePlayMethod: 'listLoop', togglePlayMethod: 'listLoop',
highQuality: false, highQuality: false,
@ -27,6 +27,7 @@ const defaultSetting = {
y: null, y: null,
theme: 0, theme: 0,
isLockScreen: true, isLockScreen: true,
isDelayScroll: true,
style: { style: {
font: '', font: '',
fontSize: 120, fontSize: 120,

View File

@ -8,8 +8,10 @@ const names = {
clear_cache: 'clear_cache', clear_cache: 'clear_cache',
get_cache_size: 'get_cache_size', get_cache_size: 'get_cache_size',
get_env_params: 'get_env_params', get_env_params: 'get_env_params',
clear_env_params_deeplink: 'clear_env_params_deeplink',
wait: 'wait', wait: 'wait',
wait_cancel: 'wait_cancel', wait_cancel: 'wait_cancel',
open_dev_tools: 'open_dev_tools',
set_music_meta: 'set_music_meta', set_music_meta: 'set_music_meta',
progress: 'progress', progress: 'progress',

View File

@ -20,6 +20,7 @@
"date_format_hour": "{num} hours ago", "date_format_hour": "{num} hours ago",
"date_format_minute": "{num} minutes ago", "date_format_minute": "{num} minutes ago",
"date_format_second": "{num} seconds ago", "date_format_second": "{num} seconds ago",
"deep_link__handle_error_tip": "Call failed: {message}",
"default": "Default", "default": "Default",
"default_list": "Recently Played", "default_list": "Recently Played",
"desktop_lyric__back": "Back", "desktop_lyric__back": "Back",
@ -229,6 +230,7 @@
"setting__click_open": "Click to open", "setting__click_open": "Click to open",
"setting__desktop_lyric": "Desktop Lyric Settings", "setting__desktop_lyric": "Desktop Lyric Settings",
"setting__desktop_lyric_always_on_top": "Make the lyrics always above other windows", "setting__desktop_lyric_always_on_top": "Make the lyrics always above other windows",
"setting__desktop_lyric_delay_scroll": "Delayed lyrics scroll",
"setting__desktop_lyric_enable": "Display lyrics", "setting__desktop_lyric_enable": "Display lyrics",
"setting__desktop_lyric_font": "Lyric font", "setting__desktop_lyric_font": "Lyric font",
"setting__desktop_lyric_font_default": "Default", "setting__desktop_lyric_font_default": "Default",

View File

@ -20,6 +20,7 @@
"date_format_hour": "{num}小时前", "date_format_hour": "{num}小时前",
"date_format_minute": "{num}分钟前", "date_format_minute": "{num}分钟前",
"date_format_second": "{num}秒前", "date_format_second": "{num}秒前",
"deep_link__handle_error_tip": "调用失败:{message}",
"default": "默认", "default": "默认",
"default_list": "试听列表", "default_list": "试听列表",
"desktop_lyric__back": "返回", "desktop_lyric__back": "返回",
@ -229,6 +230,7 @@
"setting__click_open": "点击打开", "setting__click_open": "点击打开",
"setting__desktop_lyric": "桌面歌词设置", "setting__desktop_lyric": "桌面歌词设置",
"setting__desktop_lyric_always_on_top": "使歌词总是在其他窗口之上", "setting__desktop_lyric_always_on_top": "使歌词总是在其他窗口之上",
"setting__desktop_lyric_delay_scroll": "延迟歌词滚动",
"setting__desktop_lyric_enable": "显示歌词", "setting__desktop_lyric_enable": "显示歌词",
"setting__desktop_lyric_font": "歌词字体", "setting__desktop_lyric_font": "歌词字体",
"setting__desktop_lyric_font_default": "默认", "setting__desktop_lyric_font_default": "默认",

View File

@ -20,6 +20,7 @@
"date_format_hour": "{num}小時前", "date_format_hour": "{num}小時前",
"date_format_minute": "{num}分鐘前", "date_format_minute": "{num}分鐘前",
"date_format_second": "{num}秒前", "date_format_second": "{num}秒前",
"deep_link__handle_error_tip": "調用失敗:{message}",
"default": "默認", "default": "默認",
"default_list": "試聽列表", "default_list": "試聽列表",
"desktop_lyric__back": "返回", "desktop_lyric__back": "返回",
@ -229,6 +230,7 @@
"setting__click_open": "點擊打開", "setting__click_open": "點擊打開",
"setting__desktop_lyric": "桌面歌詞設置", "setting__desktop_lyric": "桌面歌詞設置",
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上", "setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
"setting__desktop_lyric_delay_scroll": "延遲歌詞滾動",
"setting__desktop_lyric_enable": "顯示歌詞", "setting__desktop_lyric_enable": "顯示歌詞",
"setting__desktop_lyric_font": "歌詞字體", "setting__desktop_lyric_font": "歌詞字體",
"setting__desktop_lyric_font_default": "默認", "setting__desktop_lyric_font_default": "默認",

View File

@ -1,7 +1,13 @@
const urlSchemeRxp = /^lxmusic:\/\//
const parseEnv = () => { const parseEnv = () => {
const params = {} const params = {}
const rx = /^-\w+/ const rx = /^-\w+/
for (let param of process.argv) { for (let param of process.argv) {
if (urlSchemeRxp.test(param)) {
global.envParams.deeplink = param
}
if (!rx.test(param)) continue if (!rx.test(param)) continue
param = param.substring(1) param = param.substring(1)
let index = param.indexOf('=') let index = param.indexOf('=')

View File

@ -1,6 +1,8 @@
const { app, BrowserWindow, shell } = require('electron') const { app, BrowserWindow, shell } = require('electron')
const path = require('path') const path = require('path')
const urlSchemeRxp = /^lxmusic:\/\//
// 单例应用程序 // 单例应用程序
if (!app.requestSingleInstanceLock()) { if (!app.requestSingleInstanceLock()) {
app.quit() app.quit()
@ -8,6 +10,13 @@ if (!app.requestSingleInstanceLock()) {
} }
if (!global.modules) global.modules = {} if (!global.modules) global.modules = {}
app.on('second-instance', (event, argv, cwd) => { app.on('second-instance', (event, argv, cwd) => {
for (const param of argv) {
if (urlSchemeRxp.test(param)) {
global.envParams.deeplink = param
break
}
}
if (global.modules.mainWindow) { if (global.modules.mainWindow) {
if (global.modules.mainWindow.isMinimized()) { if (global.modules.mainWindow.isMinimized()) {
global.modules.mainWindow.restore() global.modules.mainWindow.restore()
@ -22,6 +31,18 @@ app.on('second-instance', (event, argv, cwd) => {
} }
}) })
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
if (process.platform === 'win32') {
const fs = require('fs')
const portablePath = path.join(path.dirname(app.getPath('exe')), '/portable')
if (fs.existsSync(portablePath)) {
app.setPath('appData', portablePath)
const appDataPath = path.join(portablePath, '/userData')
if (!fs.existsSync(appDataPath)) fs.mkdirSync(appDataPath)
app.setPath('userData', appDataPath)
}
}
const isDev = global.isDev = process.env.NODE_ENV !== 'production' const isDev = global.isDev = process.env.NODE_ENV !== 'production'
require('./env') require('./env')
// console.log(global.envParams.cmdParams) // console.log(global.envParams.cmdParams)
@ -37,6 +58,39 @@ if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop
// https://github.com/electron/electron/issues/22691 // https://github.com/electron/electron/issues/22691
app.commandLine.appendSwitch('wm-window-animations-disabled') app.commandLine.appendSwitch('wm-window-animations-disabled')
// proxy
if (global.envParams.cmdParams['proxy-server']) {
app.commandLine.appendSwitch('proxy-server', global.envParams.cmdParams['proxy-server'])
app.commandLine.appendSwitch('proxy-bypass-list', global.envParams.cmdParams['proxy-bypass-list'] ?? '<local>')
}
// if (global.envParams.cmdParams['proxy-pac-url']) app.commandLine.appendSwitch('proxy-pac-url', global.envParams.cmdParams['proxy-pac-url'])
// deep link
app.on('open-url', (event, url) => {
if (!urlSchemeRxp.test(url)) return
event.preventDefault()
global.envParams.deeplink = url
if (global.modules.mainWindow) {
if (global.modules.mainWindow.isMinimized()) {
global.modules.mainWindow.restore()
}
if (global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.focus()
} else {
global.modules.mainWindow.show()
}
} else if (global.modules.mainWindow === null) {
init()
}
})
if (isDev && process.platform === 'win32') {
// Set the path of electron.exe and your app.
// These two additional parameters are only available on windows.
// console.log(process.execPath, process.argv)
app.setAsDefaultProtocolClient('lxmusic', process.execPath, process.argv.slice(1))
} else {
app.setAsDefaultProtocolClient('lxmusic')
}
const { navigationUrlWhiteList, themes } = require('../common/config') const { navigationUrlWhiteList, themes } = require('../common/config')
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils') const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')

View File

@ -32,7 +32,9 @@ const handleResponse = (event, { status, data: { requestKey, result }, message }
} }
const handleOpenDevTools = () => { const handleOpenDevTools = () => {
if (global.modules.userApiWindow) { if (global.modules.userApiWindow) {
global.modules.userApiWindow.webContents.openDevTools() global.modules.userApiWindow.webContents.openDevTools({
mode: 'undocked',
})
} }
} }
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit) mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)

View File

@ -1,6 +1,9 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') const { mainHandle, mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainHandle(ipcMainWindowNames.get_env_params, async(event, options) => { mainHandle(ipcMainWindowNames.get_env_params, async(event, options) => {
return global.envParams.cmdParams return global.envParams
}) })
mainOn(ipcMainWindowNames.clear_env_params_deeplink, () => {
global.envParams.deeplink = null
})

View File

@ -21,6 +21,7 @@ require('./lyric')
require('./musicUrl') require('./musicUrl')
require('./systemFonts') require('./systemFonts')
require('./wait') require('./wait')
require('./openDevtools')
// require('./kw_decodeLyric') // require('./kw_decodeLyric')

View File

@ -0,0 +1,13 @@
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn(ipcMainWindowNames.open_dev_tools, event => {
if (global.modules.mainWindow) {
if (global.modules.mainWindow.isDevToolsOpened()) {
global.modules.mainWindow.webContents.closeDevTools()
} else {
global.modules.mainWindow.webContents.openDevTools({
mode: 'undocked',
})
}
}
})

View File

@ -33,6 +33,7 @@ export default {
enable: false, enable: false,
isLock: true, isLock: true,
isAlwaysOnTop: false, isAlwaysOnTop: false,
isDelayScroll: true,
width: 600, width: 600,
height: 700, height: 700,
x: -1, x: -1,

View File

@ -22,6 +22,7 @@ export default {
type: Object, type: Object,
default() { default() {
return { return {
isDelayScroll: true,
style: { style: {
font: '', font: '',
fontSize: 125, fontSize: 125,
@ -127,10 +128,14 @@ export default {
if (n < 0) return if (n < 0) return
if (n == 0 && this.isSetedLines) return this.isSetedLines = false if (n == 0 && this.isSetedLines) return this.isSetedLines = false
if (o == null || n - o != 1) return this.handleScrollLrc() if (o == null || n - o != 1) return this.handleScrollLrc()
delayScrollTimeout = setTimeout(() => { if (this.lrcConfig.isDelayScroll) {
delayScrollTimeout = null delayScrollTimeout = setTimeout(() => {
this.handleScrollLrc(600) delayScrollTimeout = null
}, 600) this.handleScrollLrc(600)
}, 600)
} else {
this.handleScrollLrc()
}
}, },
immediate: true, immediate: true,
}, },

View File

@ -941,8 +941,8 @@
@color-happy_new_year-pagination-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 60%); @color-happy_new_year-pagination-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 60%);
@color-happy_new_year-pagination-active: fadeout(darken(@color-happy_new_year-theme, 10%), 70%); @color-happy_new_year-pagination-active: fadeout(darken(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-pagination-select: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%); @color-happy_new_year-pagination-select: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%);
@color-happy_new_year-search-form-background: fadeout(lighten(@color-happy_new_year-theme, 30%), 20%); @color-happy_new_year-search-form-background: fadeout(lighten(@color-happy_new_year-theme, 32%), 20%);
@color-happy_new_year-search-list-hover: fadeout(darken(@color-happy_new_year-theme, 10%), 65%); @color-happy_new_year-search-list-hover: fadeout(darken(@color-happy_new_year-theme, 1%), 70%);
@color-happy_new_year-scrollbar-track: fadeout(lighten(@color-happy_new_year-theme, 10%), 70%); @color-happy_new_year-scrollbar-track: fadeout(lighten(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-scrollbar-thumb: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%); @color-happy_new_year-scrollbar-thumb: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%);
@color-happy_new_year-scrollbar-thumb-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 35%); @color-happy_new_year-scrollbar-thumb-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 35%);

View File

@ -11,19 +11,19 @@ import { isPlay } from '@renderer/core/share/player'
import { player as eventPlayerNames } from '@renderer/event/names' import { player as eventPlayerNames } from '@renderer/event/names'
const themes = { const themes = {
green: 'rgba(77,175,124,.1)', green: 'rgba(77,175,124,.16)',
blue: 'rgba(52,152,219,.1)', blue: 'rgba(52,152,219,.16)',
yellow: 'rgba(233,212,96,.16)', yellow: 'rgba(233,212,96,.22)',
orange: 'rgba(245,171,53,.1)', orange: 'rgba(245,171,53,.16)',
red: 'rgba(214,69,65,.08)', red: 'rgba(214,69,65,.12)',
pink: 'rgba(241,130,141,.1)', pink: 'rgba(241,130,141,.16)',
purple: 'rgba(155,89,182,.1)', purple: 'rgba(155,89,182,.14)',
grey: 'rgba(108,122,137,.1)', grey: 'rgba(108,122,137,.16)',
ming: 'rgba(51,110,123,.1)', ming: 'rgba(51,110,123,.14)',
blue2: 'rgba(79,98,208,.1)', blue2: 'rgba(79,98,208,.14)',
black: 'rgba(39,39,39,.26)', black: 'rgba(39,39,39,.4)',
mid_autumn: 'rgba(74,55,82,.05)', mid_autumn: 'rgba(74,55,82,.1)',
naruto: 'rgba(87,144,167,.1)', naruto: 'rgba(87,144,167,.14)',
happy_new_year: 'rgba(192,57,43,.1)', happy_new_year: 'rgba(192,57,43,.1)',
} }
export default { export default {
@ -42,6 +42,11 @@ export default {
let isPlaying = false let isPlaying = false
let animationFrameId let animationFrameId
let num
let mult
const maxNum = 255
let frequencyAvg = 0
const theme = useRefGetter('theme') const theme = useRefGetter('theme')
// const setting = useRefGetter('setting') // const setting = useRefGetter('setting')
let themeColor = themes[theme.value || 'green'] let themeColor = themes[theme.value || 'green']
@ -49,7 +54,7 @@ export default {
themeColor = themes[theme || 'green'] themeColor = themes[theme || 'green']
}) })
// https://codepen.io/nfj525/pen/rVBaab // https://developer.mozilla.org/zh-CN/docs/Web/API/AnalyserNode/smoothingTimeConstant
const renderFrame = () => { const renderFrame = () => {
animationFrameId = null animationFrameId = null
if (isPlaying) animationFrameId = window.requestAnimationFrame(renderFrame) if (isPlaying) animationFrameId = window.requestAnimationFrame(renderFrame)
@ -60,8 +65,23 @@ export default {
ctx.clearRect(0, 0, WIDTH, HEIGHT) ctx.clearRect(0, 0, WIDTH, HEIGHT)
// ctx.fillRect(0, 0, WIDTH, HEIGHT) // ctx.fillRect(0, 0, WIDTH, HEIGHT)
ctx.fillStyle = themeColor
for (let i = 0; i < bufferLength; i++) { for (let i = 0; i < bufferLength; i++) {
mult = Math.floor(i / maxNum)
num = mult % 2 === 0 ? (i - maxNum * mult) : (maxNum - (i - maxNum * mult))
let spectrum = num > 90 ? 0 : dataArray[num + 20]
frequencyAvg += spectrum * 1.2
}
frequencyAvg /= bufferLength
frequencyAvg *= 1.4
frequencyAvg = frequencyAvg / maxNum
// ctx.scale(1, 1 + frequencyAvg)
for (let i = 0; i < bufferLength; i++) {
if (x > WIDTH) break
barHeight = dataArray[i] barHeight = dataArray[i]
// let r = barHeight + (25 * (i / bufferLength)) // let r = barHeight + (25 * (i / bufferLength))
@ -69,7 +89,7 @@ export default {
// let b = 50 // let b = 50
// ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')' // ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')'
ctx.fillStyle = themeColor barHeight = barHeight * frequencyAvg + barHeight * 0.42
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight) ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight)
x += barWidth x += barWidth

View File

@ -162,7 +162,16 @@ export const clearPlayedList = () => {
export const tempPlayList = reactive([]) export const tempPlayList = reactive([])
export const addTempPlayList = (list) => { export const addTempPlayList = (list) => {
tempPlayList.push(...list.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true }))) const topList = []
const bottomList = list.filter(({ isTop, ...musicInfo }) => {
if (isTop) {
topList.push(musicInfo)
return false
}
return true
})
if (topList.length) tempPlayList.unshift(...topList.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
if (bottomList.length) tempPlayList.push(...bottomList.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
} }
export const removeTempPlayList = (index) => { export const removeTempPlayList = (index) => {
tempPlayList.splice(index, 1) tempPlayList.splice(index, 1)

View File

@ -0,0 +1,66 @@
import { useAction, useCommit } from '@renderer/utils/vueTools'
import { tempList } from '@renderer/core/share/list'
const 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
}
export default () => {
const getListDetail = useAction('songList', 'getListDetail')
const getListDetailAll = useAction('songList', 'getListDetailAll')
const setTempList = useCommit('player', 'setTempList')
const updateTempList = useCommit('player', 'updateTempList')
const playSongListDetail = async(source, link, playIndex) => {
console.log(source, link, playIndex)
if (link == null) return
let isPlayingList = false
const id = decodeURIComponent(link)
const playListId = `${source}__${decodeURIComponent(link)}`
let list
try {
list = await getListDetail({ source, id, page: 1 })
} catch (err) {
console.log(err)
}
if (list.length > playIndex) {
isPlayingList = true
setTempList({
list,
index: getListPlayIndex(list, playIndex),
id: playListId,
})
}
getListDetailAll({ source, id }).then(list => {
if (isPlayingList) {
if (tempList.meta.id == id) {
updateTempList({
list,
id: playListId,
})
}
} else {
setTempList({
list,
index: getListPlayIndex(list, playIndex),
id: playListId,
})
}
})
}
return (source, link, playIndex) => {
playSongListDetail(source, link, playIndex)
}
}

View File

@ -9,6 +9,7 @@ import useUpdate from './useUpdate'
import useDataInit from './useDataInit' import useDataInit from './useDataInit'
import useHandleEnvParams from './useHandleEnvParams' import useHandleEnvParams from './useHandleEnvParams'
import useEventListener from './useEventListener' import useEventListener from './useEventListener'
import useDeepLink from './useDeepLink'
import usePlayer from './usePlayer' import usePlayer from './usePlayer'
@ -19,7 +20,7 @@ export default () => {
sync.enable = setting.value.sync.enable sync.enable = setting.value.sync.enable
apiSource.value = setting.value.apiSource apiSource.value = setting.value.apiSource
proxy.value = Object.assign({}, setting.value.network.proxy) Object.assign(proxy, setting.value.network.proxy)
const dieableIgnoreMouseEvents = () => { const dieableIgnoreMouseEvents = () => {
if (window.dt) return if (window.dt) return
@ -44,12 +45,23 @@ export default () => {
const initData = useDataInit({ const initData = useDataInit({
setting, setting,
}) })
const initDeepLink = useDeepLink()
getEnvParams().then(envParams => { getEnvParams().then(envParams => {
const envProxy = envParams.cmdParams['proxy-server']
if (envProxy && typeof envProxy == 'string') {
const [host, port = ''] = envProxy.split(':')
proxy.envProxy = {
host,
port,
}
}
// 初始化我的列表、下载列表等数据 // 初始化我的列表、下载列表等数据
initData().then(() => { initData().then(() => {
handleEnvParams(envParams) // 处理传入的启动参数 handleEnvParams(envParams) // 处理传入的启动参数
initDeepLink(envParams)
}) })
}) })
} }

View File

@ -153,8 +153,8 @@ export default ({
initListPosition(), // 列表位置记录 initListPosition(), // 列表位置记录
initListPrevSelectId(), // 上次选中的列表记录 initListPrevSelectId(), // 上次选中的列表记录
initUserApi(), // 自定义API initUserApi(), // 自定义API
music.init(), // 初始化音乐sdk
]).catch(err => log.error(err)) ]).catch(err => log.error(err))
music.init() // 初始化音乐sdk
await initList().catch(err => log.error(err)) // 初始化列表 await initList().catch(err => log.error(err)) // 初始化列表
await initPlayInfo(downloadList.value).catch(err => log.error(err)) // 初始化上次的歌曲播放信息 await initPlayInfo(downloadList.value).catch(err => log.error(err)) // 初始化上次的歌曲播放信息
await initSearchHistory(saveSearchHistoryListThrottle).catch(err => log.error(err)) // 初始化搜索历史记录 await initSearchHistory(saveSearchHistoryListThrottle).catch(err => log.error(err)) // 初始化搜索历史记录

View File

@ -0,0 +1,268 @@
import { useCommit, useAction, onBeforeUnmount, useRouter, useI18n, markRaw } from '@renderer/utils/vueTools'
import { base as eventBaseName } from '@renderer/event/names'
import { getEnvParams, clearEnvParamsDeeplink } from '@renderer/utils/tools'
import { decodeName } from '@renderer/utils'
// import { allList, defaultList, loveList, userLists } from '@renderer/core/share/list'
import { isShowPlayerDetail, setShowPlayerDetail, playMusicInfo } from '@renderer/core/share/player'
import usePlaySonglist from './compositions/usePlaySonglist'
import { dialog } from '@renderer/plugins/Dialog'
const sources = ['kw', 'kg', 'tx', 'wy', 'mg']
const sourceVerify = source => {
if (!sources.includes(source)) throw new Error('Source no match')
}
const qualitys = ['128k', '320k', 'flac', 'flac32bit']
const qualityFilter = (source, types) => {
types = types.filter(({ type }) => qualitys.includes(type)).map(({ type, size, hash }) => {
if (size != null && typeof size != 'string') throw new Error(type + ' size type no match')
if (source == 'kg' && typeof hash != 'string') throw new Error(type + ' hash type no match')
return hash == null ? { type, size } : { type, size, hash }
})
if (!types.length) throw new Error('quality no match')
return types
}
const dataVerify = (rules, data) => {
const newData = {}
for (const rule of rules) {
const val = data[rule.key]
if (rule.required && val == null) throw new Error(rule.key + ' missing')
if (val != null) {
if (rule.types && !rule.types.includes(typeof val)) throw new Error(rule.key + ' type no match')
if (rule.max && String(val).length > rule.max) throw new Error(rule.key + ' max length no match')
if (rule.min && String(val).length > rule.min) throw new Error(rule.key + ' min length no match')
}
newData[rule.key] = val
}
return newData
}
export default () => {
// const setList = useCommit('list', 'setList')
// const listAdd = useCommit('list', 'listAdd')
// const listMove = useCommit('list', 'listMove')
// const listAddMultiple = useCommit('list', 'listAddMultiple')
// const listMoveMultiple = useCommit('list', 'listMoveMultiple')
// const listRemove = useCommit('list', 'listRemove')
// const listRemoveMultiple = useCommit('list', 'listRemoveMultiple')
// const listClear = useCommit('list', 'listClear')
// const updateMusicInfo = useCommit('list', 'updateMusicInfo')
// const createUserList = useCommit('list', 'createUserList')
// const removeUserList = useCommit('list', 'removeUserList')
// const setUserListName = useCommit('list', 'setUserListName')
// const setMusicPosition = useCommit('list', 'setMusicPosition')
// // const setSyncListData = useCommit('list', 'setSyncListData')
// const setUserListPosition = useCommit('list', 'setUserListPosition')
const router = useRouter()
const setTempPlayList = useCommit('player', 'setTempPlayList')
const playNext = useAction('player', 'playNext')
const playSongListDetail = usePlaySonglist()
const { t } = useI18n()
let isInited = false
const handleOpenSonglist = params => {
if (params.id) {
router.replace({
path: '/songList',
query: {
source: params.source,
id: params.id,
},
})
} else if (params.url) {
router.replace({
path: '/songList',
query: {
source: params.source,
url: params.url,
},
})
}
}
const handlePlayMusic = _musicInfo => {
const musicInfo = {
..._musicInfo,
singer: decodeName(_musicInfo.singer),
name: decodeName(_musicInfo.name),
albumName: decodeName(_musicInfo.albumName),
otherSource: null,
_types: {},
typeUrl: {},
}
for (const type of musicInfo.types) {
musicInfo._types[type.type] = { size: type.size }
}
markRaw(musicInfo)
const isPlaying = !!playMusicInfo.musicInfo
setTempPlayList([{ listId: '__temp__', musicInfo, isTop: true }])
if (isPlaying) playNext()
}
const handleSonglist = (action, songlistInfo) => {
sourceVerify(songlistInfo.source)
switch (action) {
case 'open':
songlistInfo = dataVerify([
{ key: 'source', types: ['string'] },
{ key: 'id', types: ['string', 'number'], max: 64 },
{ key: 'url', types: ['string'], max: 500 },
], songlistInfo)
if (isShowPlayerDetail.value) setShowPlayerDetail(false)
handleOpenSonglist(songlistInfo)
break
case 'play':
songlistInfo = dataVerify([
{ key: 'source', types: ['string'] },
{ key: 'id', types: ['string', 'number'], max: 64 },
{ key: 'url', types: ['string'], max: 500 },
{ key: 'index', types: ['number'], max: 1000000 },
], songlistInfo)
playSongListDetail(songlistInfo.source, songlistInfo.id ?? songlistInfo.url, songlistInfo.index ?? 0)
break
default: throw new Error('Unknown action: ' + action)
}
}
const handleMusic = (action, musicInfo) => {
switch (musicInfo.source) {
case 'kw':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
], musicInfo)
break
case 'kg':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: '_interval', types: ['number'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'hash', types: ['string'], required: true, max: 64 },
], musicInfo)
break
case 'tx':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'strMediaMid', types: ['string'], required: true, max: 64 },
{ key: 'albumMid', types: ['string'], max: 64 },
], musicInfo)
break
case 'wy':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
], musicInfo)
break
case 'mg':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'copyrightId', types: ['string', 'number'], required: true, max: 64 },
{ key: 'lrcUrl', types: ['string'], max: 1024 },
], musicInfo)
break
default: throw new Error('Unknown action: ' + action)
}
musicInfo.types = qualityFilter(musicInfo.source, musicInfo.types)
switch (action) {
case 'play':
handlePlayMusic(musicInfo)
break
default: throw new Error('Unknown action: ' + action)
}
}
const handleLinkAction = link => {
// console.log(link)
const [url, search] = link.split('?')
const [type, action] = url.replace('lxmusic://', '').split('/')
const params = {}
for (const param of search.split('&')) {
const [key, value] = param.split('=')
params[key] = value
}
if (params.data) params.data = JSON.parse(decodeURIComponent(params.data))
console.log(params.data)
switch (type) {
case 'music':
handleMusic(action, params.data)
break
case 'songlist':
handleSonglist(action, params.data)
break
default: throw new Error('Unknown type: ' + type)
}
}
const handleFocus = () => {
if (!isInited) return
getEnvParams().then(envParams => {
if (!envParams.deeplink) return
clearEnvParamsDeeplink()
try {
handleLinkAction(envParams.deeplink)
} catch (err) {
dialog(`${t('deep_link__handle_error_tip', { message: err.message })}`)
}
})
}
window.eventHub.on(eventBaseName.focus, handleFocus)
onBeforeUnmount(() => {
window.eventHub.off(eventBaseName.focus, handleFocus)
})
return envParams => {
if (envParams.deeplink) {
clearEnvParamsDeeplink()
try {
handleLinkAction(envParams.deeplink)
} catch (err) {
dialog(`${t('deep_link__handle_error_tip', { message: err.message })}`)
}
}
isInited = true
}
}

View File

@ -1,7 +1,8 @@
import { useAction, useCommit, useRouter } from '@renderer/utils/vueTools' import { useCommit, useRouter } from '@renderer/utils/vueTools'
import { parseUrlParams } from '@renderer/utils' import { parseUrlParams } from '@renderer/utils'
import { defaultList, loveList, userLists } from '@renderer/core/share/list' import { defaultList, loveList, userLists } from '@renderer/core/share/list'
import { getList } from '@renderer/core/share/utils' import { getList } from '@renderer/core/share/utils'
import usePlaySonglist from './compositions/usePlaySonglist'
const getListPlayIndex = (list, index) => { const getListPlayIndex = (list, index) => {
if (index == null) { if (index == null) {
@ -32,26 +33,9 @@ const useInitEnvParamSearch = () => {
} }
} }
const useInitEnvParamPlay = () => { const useInitEnvParamPlay = () => {
const getListDetailAll = useAction('songList', 'getListDetailAll')
const setPlayList = useCommit('player', 'setList') const setPlayList = useCommit('player', 'setList')
const setTempList = useCommit('player', 'setTempList')
const playSongListDetail = async(source, link, playIndex) => { const playSongListDetail = usePlaySonglist()
if (link == null) return
let list
let id
try {
id = decodeURIComponent(link)
list = await getListDetailAll({ source, id })
} catch (err) {
console.log(err)
}
setTempList({
list,
index: getListPlayIndex(list, playIndex),
id: `${source}__${id}`,
})
}
return (playStr) => { return (playStr) => {
if (playStr == null || typeof playStr != 'string') return if (playStr == null || typeof playStr != 'string') return
@ -98,7 +82,7 @@ export default () => {
const initEnvParamPlay = useInitEnvParamPlay() const initEnvParamPlay = useInitEnvParamPlay()
return envParams => { return envParams => {
initEnvParamSearch(envParams.search) initEnvParamSearch(envParams.cmdParams.search)
initEnvParamPlay(envParams.play) initEnvParamPlay(envParams.cmdParams.play)
} }
} }

View File

@ -5,7 +5,7 @@ import { musicInfo, musicInfoItem, playMusicInfo } from '@renderer/core/share/pl
export default ({ export default ({
playNext, playNext,
setStatus, setAllStatus,
setUrl, setUrl,
}) => { }) => {
const { t } = useI18n() const { t } = useI18n()
@ -50,18 +50,15 @@ export default ({
const handleLoadstart = () => { const handleLoadstart = () => {
startLoadingTimeout() startLoadingTimeout()
const status = t('player__loading') setAllStatus(t('player__loading'))
setStatus(status, status)
} }
const handleLoadeddata = () => { const handleLoadeddata = () => {
const status = t('player__loading') setAllStatus(t('player__loading'))
setStatus(status, status)
} }
const handleCanplay = () => { const handleCanplay = () => {
const status = '' setAllStatus('')
setStatus(status, status)
} }
const handlePlaying = () => { const handlePlaying = () => {
@ -74,8 +71,7 @@ export default ({
} }
const handleWating = () => { const handleWating = () => {
const status = t('player__buffering') setAllStatus(t('player__buffering'))
setStatus(status, status)
} }
const handleError = errCode => { const handleError = errCode => {
@ -85,13 +81,11 @@ export default ({
// console.log(this.retryNum) // console.log(this.retryNum)
retryNum++ retryNum++
setUrl(musicInfoItem.value, true) setUrl(musicInfoItem.value, true)
const status = t('player__refresh_url') setAllStatus(t('player__refresh_url'))
setStatus(status, status)
return return
} }
const status = t('player__error') setAllStatus(t('player__error'))
setStatus(status, status)
addDelayNextTimeout() addDelayNextTimeout()
} }

View File

@ -22,8 +22,6 @@ import { requestMsg } from '@renderer/utils/message'
import { import {
isPlay, isPlay,
setPlay, setPlay,
setStatus,
setStatusText,
setAllStatus, setAllStatus,
musicInfo, musicInfo,
setMusicInfo, setMusicInfo,
@ -164,7 +162,7 @@ export default ({ setting }) => {
useMediaSessionInfo({ playPrev, playNext }) useMediaSessionInfo({ playPrev, playNext })
usePlayEvent({ usePlayEvent({
playNext, playNext,
setStatus, setAllStatus,
setUrl, setUrl,
}) })
useLyric({ useLyric({
@ -204,8 +202,7 @@ export default ({ setting }) => {
const setStopStatus = () => { const setStopStatus = () => {
setPlay(false) setPlay(false)
setTitle() setTitle()
setStatus('') setAllStatus('')
setStatusText('')
setMusicInfo({ setMusicInfo({
songmid: null, songmid: null,
img: null, img: null,
@ -296,6 +293,7 @@ export default ({ setting }) => {
// 播放、暂停播放切换 // 播放、暂停播放切换
const handleTogglePlay = async() => { const handleTogglePlay = async() => {
if (playMusicInfo.musicInfo == null) return
if (isPlayerEmpty()) { if (isPlayerEmpty()) {
if (playMusicInfo.listId == 'download') { if (playMusicInfo.listId == 'download') {
const musicInfo = playMusicInfo.musicInfo const musicInfo = playMusicInfo.musicInfo

View File

@ -105,3 +105,7 @@ eventHub.on(syncName.send_sync_list, ({ action, data }) => {
if (!sync.enable) return if (!sync.enable) return
rendererSend(NAMES.mainWindow.sync_list, { action, data }) rendererSend(NAMES.mainWindow.sync_list, { action, data })
}) })
eventHub.on('key_mod+f12_down', ({ action, data }) => {
if (!sync.enable) return
rendererSend(NAMES.mainWindow.open_dev_tools)
})

View File

@ -66,11 +66,12 @@ const actions = {
return listInfo return listInfo
}) })
}, },
getListAll({ state, rootState }, id) { getListAll({ state, rootState }, { id, isRefresh = false }) {
// console.log(source, id) // console.log(source, id)
let [source, bangId] = id.split('__') let [source, bangId] = id.split('__')
const loadData = (id, page) => { const loadData = (id, page) => {
let key = `${source}${id}${page}` let key = `${source}${id}${page}`
if (isRefresh && cache.has(key)) cache.delete(key)
return cache.has(key) return cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source].leaderboard.getList(bangId, page).then(result => { : music[source].leaderboard.getList(bangId, page).then(result => {

View File

@ -415,7 +415,7 @@ const mutations = {
}, },
setTempPlayList(state, list) { setTempPlayList(state, list) {
addTempPlayList(list) addTempPlayList(list)
if (!playMusicInfo.musicInfo) this.commit('player/playNext') if (!playMusicInfo.musicInfo) this.dispatch('player/playNext')
}, },
removeTempPlayList(state, index) { removeTempPlayList(state, index) {
removeTempPlayList(index) removeTempPlayList(index)

View File

@ -81,21 +81,25 @@ const actions = {
commit('clearList') commit('clearList')
return music[source].songList.getList(sortId, tabId, page).then(result => commit('setList', { result, key, page })) return music[source].songList.getList(sortId, tabId, page).then(result => commit('setList', { result, key, page }))
}, },
getListDetail({ state, rootState, commit }, { id, page }) { getListDetail({ state, commit }, { id, source, page, isRefresh = false }) {
let source = rootState.setting.songList.source
let key = `sdetail__${source}__${id}__${page}` let key = `sdetail__${source}__${id}__${page}`
if (state.listDetail.list.length && state.listDetail.key == key) return Promise.resolve() if (state.listDetail.list.length && state.listDetail.key == key) return Promise.resolve(state.listDetail.list)
commit('clearListDetail') commit('clearListDetail')
if (isRefresh && cache.has(key)) cache.delete(key)
return ( return (
cache.has(key) cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source].songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) })) : music[source].songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) }))
).then(result => commit('setListDetail', { result, key, source, id, page })) ).then(result => {
commit('setListDetail', { result, key, source, id, page })
return result.list
})
}, },
getListDetailAll({ state, rootState }, { source, id }) { getListDetailAll({ state, rootState }, { source, id, isRefresh = false }) {
// console.log(source, id) // console.log(source, id)
const loadData = (id, page) => { const loadData = (id, page) => {
let key = `sdetail__${source}__${id}__${page}` let key = `sdetail__${source}__${id}__${page}`
if (isRefresh && cache.has(key)) cache.delete(key)
return cache.has(key) return cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source].songList.getListDetail(id, page).then(result => { : music[source].songList.getListDetail(id, page).then(result => {

View File

@ -387,9 +387,13 @@ export const clearCache = () => rendererInvoke(NAMES.mainWindow.clear_cache)
export const setWindowSize = (width, height) => rendererSend(NAMES.mainWindow.set_window_size, { width, height }) export const setWindowSize = (width, height) => rendererSend(NAMES.mainWindow.set_window_size, { width, height })
export const getProxyInfo = () => proxy.enable && proxy.host export const getProxyInfo = () => {
? `http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port};` return proxy.enable && proxy.host
: undefined ? `http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port};`
: proxy.envProxy
? `http://${proxy.envProxy.host}:${proxy.envProxy.port};`
: undefined
}
export const assertApiSupport = source => qualityList.value[source] != undefined export const assertApiSupport = source => qualityList.value[source] != undefined

View File

@ -126,6 +126,13 @@ export default {
size, size,
} }
break break
case 'ZQ':
size = sizeFormate(type.size)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
size,
}
break
} }
}) })

View File

@ -69,6 +69,13 @@ export default {
size, size,
} }
break break
case 'ZQ':
size = sizeFormate(type.size)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
size,
}
break
} }
}) })

View File

@ -194,6 +194,13 @@ export default {
size, size,
} }
break break
case 'ZQ':
size = sizeFormate(type.size)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
size,
}
break
} }
}) })

View File

@ -16,7 +16,7 @@ export default {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp() if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
if (retryNum > 5) return Promise.reject(new Error('搜索失败')) if (retryNum > 5) return Promise.reject(new Error('搜索失败'))
// searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=sizer.yqq.song_next&searchid=49252838123499591&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`) // searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=sizer.yqq.song_next&searchid=49252838123499591&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`)
searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.top&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&cv=4747474&ct=24&format=json&inCharset=utf-8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&uin=0&hostUin=0&loginUin=0`) searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&remoteplace=txt.yqq.top&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&cv=4747474&ct=24&format=json&inCharset=utf-8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&uin=0&hostUin=0&loginUin=0`)
// searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.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=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
return searchRequest.promise.then(({ body }) => { return searchRequest.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.musicSearch(str, page, limit, ++retryNum) if (body.code !== this.successCode) return this.musicSearch(str, page, limit, ++retryNum)
@ -31,33 +31,33 @@ export default {
return arr.join('、') return arr.join('、')
}, },
handleResult(rawList) { handleResult(rawList) {
// console.log(rawData) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
let types = [] let types = []
let _types = {} let _types = {}
if (item.file.size_128mp3 !== 0) { if (item.size128 !== 0) {
let size = sizeFormate(item.file.size_128mp3) let size = sizeFormate(item.size128)
types.push({ type: '128k', size }) types.push({ type: '128k', size })
_types['128k'] = { _types['128k'] = {
size, size,
} }
} }
if (item.file.size_320mp3 !== 0) { if (item.size320 !== 0) {
let size = sizeFormate(item.file.size_320mp3) let size = sizeFormate(item.size320)
types.push({ type: '320k', size }) types.push({ type: '320k', size })
_types['320k'] = { _types['320k'] = {
size, size,
} }
} }
if (item.file.size_ape !== 0) { if (item.sizeape !== 0) {
let size = sizeFormate(item.file.size_ape) let size = sizeFormate(item.sizeape)
types.push({ type: 'ape', size }) types.push({ type: 'ape', size })
_types.ape = { _types.ape = {
size, size,
} }
} }
if (item.file.size_flac !== 0) { if (item.sizeflac !== 0) {
let size = sizeFormate(item.file.size_flac) let size = sizeFormate(item.sizeflac)
types.push({ type: 'flac', size }) types.push({ type: 'flac', size })
_types.flac = { _types.flac = {
size, size,
@ -66,18 +66,18 @@ export default {
// types.reverse() // types.reverse()
return { return {
singer: this.getSinger(item.singer), singer: this.getSinger(item.singer),
name: item.title, name: item.songname,
albumName: item.album.title, albumName: item.albumname,
albumId: item.album.mid, albumId: item.albummid,
source: 'tx', source: 'tx',
interval: formatPlayTime(item.interval), interval: formatPlayTime(item.interval),
songId: item.id, songId: item.songid,
albumMid: item.album.mid, albumMid: item.albummid,
strMediaMid: item.file.strMediaMid, strMediaMid: item.strMediaMid,
songmid: item.mid, songmid: item.songmid,
img: (item.album.name === '' || item.album.name === '空') img: (item.albummid === '' || item.albummid === '空')
? `https://y.gtimg.cn/music/photo_new/T001R500x500M000${item.singer[0].mid}.jpg` ? `https://y.gtimg.cn/music/photo_new/T001R500x500M000${item.singer[0]?.mid}.jpg`
: `https://y.gtimg.cn/music/photo_new/T002R500x500M000${item.album.mid}.jpg`, : `https://y.gtimg.cn/music/photo_new/T002R500x500M000${item.albummid}.jpg`,
lrc: null, lrc: null,
otherSource: null, otherSource: null,
types, types,

View File

@ -48,6 +48,10 @@ export const getEnvParams = () => {
return rendererInvoke(NAMES.mainWindow.get_env_params) return rendererInvoke(NAMES.mainWindow.get_env_params)
} }
export const clearEnvParamsDeeplink = () => {
return rendererSend(NAMES.mainWindow.clear_env_params_deeplink)
}
export const onUpdateAvailable = callback => { export const onUpdateAvailable = callback => {
rendererOn(NAMES.mainWindow.update_available, callback) rendererOn(NAMES.mainWindow.update_available, callback)
return () => { return () => {

View File

@ -211,7 +211,7 @@ export default {
async addSongListDetail({ boardId, boardName, source, id }) { async addSongListDetail({ boardId, boardName, source, id }) {
// console.log(this.listDetail.info) // console.log(this.listDetail.info)
// if (!this.listDetail.info.name) return // if (!this.listDetail.info.name) return
const list = await this.getListAll(boardId) const list = await this.getListAll({ id: boardId })
this.createUserList({ this.createUserList({
name: boardName, name: boardName,
id, id,
@ -230,7 +230,7 @@ export default {
}) })
isPlayingList = true isPlayingList = true
} }
const fullList = await this.getListAll(boardId) const fullList = await this.getListAll({ id: boardId })
if (!fullList.length) return if (!fullList.length) return
if (isPlayingList) { if (isPlayingList) {
if (tempList.meta.id == id) { if (tempList.meta.id == id) {

View File

@ -45,14 +45,15 @@ export default {
// console.log(to, from) // console.log(to, from)
if (to.query.updated) return if (to.query.updated) return
let id = to.query.id let id = to.query.id
if (id == null || !getList(id)) { if (id == null) return
if (!getList(id)) {
id = defaultList.id id = defaultList.id
} }
this.listId = id this.listId = id
const scrollIndex = to.query.scrollIndex const scrollIndex = to.query.scrollIndex
const isAnimation = from.query.id == to.query.id const isAnimation = from.query.id == to.query.id
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.musicList.restoreScroll(scrollIndex, isAnimation) this.$refs.musicList?.restoreScroll(scrollIndex, isAnimation)
}) })
return { return {
path: '/list', path: '/list',
@ -60,7 +61,7 @@ export default {
} }
}, },
beforeRouteLeave(to, from) { beforeRouteLeave(to, from) {
this.$refs.musicList.saveListPosition() this.$refs.musicList?.saveListPosition()
}, },
created() { created() {
this.listId = this.$route.query.id this.listId = this.$route.query.id

View File

@ -339,9 +339,9 @@ export default {
let promise let promise
if (/board__/.test(sourceListId)) { if (/board__/.test(sourceListId)) {
const id = sourceListId.replace(/board__/, '') const id = sourceListId.replace(/board__/, '')
promise = this.getBoardListAll(id) promise = this.getBoardListAll({ id, isRefresh: true })
} else { } else {
promise = this.getListDetailAll({ source, id: sourceListId }) promise = this.getListDetailAll({ source, id: sourceListId, isRefresh: true })
} }
return promise.finally(() => { return promise.finally(() => {
this.fetchingListStatus[id] = false this.fetchingListStatus[id] = false

View File

@ -5,6 +5,8 @@ dd
base-checkbox(id="setting_desktop_lyric_enable" v-model="currentStting.desktopLyric.enable" :label="$t('setting__desktop_lyric_enable')") base-checkbox(id="setting_desktop_lyric_enable" v-model="currentStting.desktopLyric.enable" :label="$t('setting__desktop_lyric_enable')")
.gap-top .gap-top
base-checkbox(id="setting_desktop_lyric_lock" v-model="currentStting.desktopLyric.isLock" :label="$t('setting__desktop_lyric_lock')") base-checkbox(id="setting_desktop_lyric_lock" v-model="currentStting.desktopLyric.isLock" :label="$t('setting__desktop_lyric_lock')")
.gap-top
base-checkbox(id="setting_desktop_lyric_delayScroll" v-model="currentStting.desktopLyric.isDelayScroll" :label="$t('setting__desktop_lyric_delay_scroll')")
.gap-top .gap-top
base-checkbox(id="setting_desktop_lyric_alwaysOnTop" v-model="currentStting.desktopLyric.isAlwaysOnTop" :label="$t('setting__desktop_lyric_always_on_top')") base-checkbox(id="setting_desktop_lyric_alwaysOnTop" v-model="currentStting.desktopLyric.isAlwaysOnTop" :label="$t('setting__desktop_lyric_always_on_top')")
.gap-top .gap-top

View File

@ -6,8 +6,8 @@ dd
p p
base-checkbox(id="setting_network_proxy_enable" v-model="currentStting.network.proxy.enable" :label="$t('setting__is_enable')") base-checkbox(id="setting_network_proxy_enable" v-model="currentStting.network.proxy.enable" :label="$t('setting__is_enable')")
p p
base-input.gap-left(v-model.trim="currentStting.network.proxy.host" :placeholder="$t('setting__network_proxy_host')") base-input.gap-left(v-model.trim="currentStting.network.proxy.host" :placeholder="proxy.envProxy ? proxy.envProxy.host : $t('setting__network_proxy_host')")
base-input.gap-left(v-model.trim="currentStting.network.proxy.port" :placeholder="$t('setting__network_proxy_port')") base-input.gap-left(v-model.trim="currentStting.network.proxy.port" :placeholder="proxy.envProxy ? proxy.envProxy.port : $t('setting__network_proxy_port')")
p p
base-input.gap-left(v-model.trim="currentStting.network.proxy.username" :placeholder="$t('setting__network_proxy_username')") base-input.gap-left(v-model.trim="currentStting.network.proxy.username" :placeholder="$t('setting__network_proxy_username')")
base-input.gap-left(v-model.trim="currentStting.network.proxy.password" type="password" :placeholder="$t('setting__network_proxy_password')") base-input.gap-left(v-model.trim="currentStting.network.proxy.password" type="password" :placeholder="$t('setting__network_proxy_password')")
@ -51,6 +51,7 @@ export default {
return { return {
currentStting, currentStting,
proxy,
} }
}, },
} }

View File

@ -17,6 +17,8 @@ export const currentStting = ref({
x: -1, x: -1,
y: -1, y: -1,
theme: '', theme: '',
isLockScreen: true,
isDelayScroll: true,
style: { style: {
font: '', font: '',
fontSize: 125, fontSize: 125,

View File

@ -170,10 +170,25 @@ export default {
this.sortId = this.setting.songList.sortId this.sortId = this.setting.songList.sortId
if (!this.isVisibleListDetail) this.setTagListWidth() if (!this.isVisibleListDetail) this.setTagListWidth()
this.listenEvent() this.listenEvent()
if (this.$route.query.source && (this.$route.query.id || this.$route.query.url)) {
this.handleRouteParams(this.$route.query.id, this.$route.query.url, this.$route.query.source)
this.$router.replace({
path: '/songList',
})
}
}, },
beforeUnmount() { beforeUnmount() {
this.unlistenEvent() this.unlistenEvent()
}, },
beforeRouteUpdate(to) {
if (to.query.source && (to.query.id || to.query.url)) {
this.handleRouteParams(to.query.id, to.query.url, to.query.source)
return {
path: '/songList',
}
}
},
methods: { methods: {
...mapMutations(['setSongList']), ...mapMutations(['setSongList']),
...mapActions('songList', ['getTags', 'getList', 'getListDetail', 'getListDetailAll']), ...mapActions('songList', ['getTags', 'getList', 'getListDetail', 'getListDetailAll']),
@ -201,6 +216,10 @@ export default {
event.target.classList.contains('key-bind')) return event.target.classList.contains('key-bind')) return
this.hideListDetail() this.hideListDetail()
}, },
handleRouteParams(id, url, source) {
if (!id) id = decodeURIComponent(url)
this.handleGetSongListDetail(id, source)
},
handleToggleListPage(page) { handleToggleListPage(page) {
this.getList(page).then(() => { this.getList(page).then(() => {
this.$nextTick(() => { this.$nextTick(() => {
@ -209,7 +228,7 @@ export default {
}) })
}, },
handleToggleListDetailPage(page) { handleToggleListDetailPage(page) {
this.handleGetListDetail(this.selectListInfo.id, page).then(() => { this.handleGetListDetail(this.selectListInfo.id, this.selectListInfo.source, page).then(() => {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.songList.scrollToTop() this.$refs.songList.scrollToTop()
}) })
@ -220,7 +239,7 @@ export default {
this.setSelectListInfo(this.listData.list[index]) this.setSelectListInfo(this.listData.list[index])
this.setVisibleListDetail(true) this.setVisibleListDetail(true)
this.$nextTick(() => { this.$nextTick(() => {
this.handleGetListDetail(this.selectListInfo.id, 1) this.handleGetListDetail(this.selectListInfo.id, this.source, 1)
}) })
}, },
// handleFlowBtnClick(action) { // handleFlowBtnClick(action) {
@ -246,40 +265,41 @@ export default {
handleImportSongListEvent({ action }) { handleImportSongListEvent({ action }) {
switch (action) { switch (action) {
case 'submit': case 'submit':
this.handleGetSongListDetail() this.handleGetSongListDetail(this.importSongListText, this.source)
break break
// case 'blur': // case 'blur':
// break // break
} }
}, },
handleGetSongListDetail() { handleGetSongListDetail(id, source) {
if (!this.importSongListText.length) return if (!id.length) return
console.log(id, source)
this.setSelectListInfo({ this.setSelectListInfo({
play_count: null, play_count: null,
id: this.importSongListText, id,
author: '', author: '',
name: '', name: '',
img: null, img: null,
desc: '', desc: '',
source: this.source, source,
}) })
this.setVisibleListDetail(true) this.setVisibleListDetail(true)
this.handleGetListDetail(this.importSongListText, 1) this.handleGetListDetail(id, source, 1)
}, },
setTagListWidth() { setTagListWidth() {
this.isInitedTagListWidth = true this.isInitedTagListWidth = true
this.listWidth = this.$refs.tagList.$el.clientWidth + this.$refs.tab.$el.clientWidth + 2 this.listWidth = this.$refs.tagList.$el.clientWidth + this.$refs.tab.$el.clientWidth + 2
}, },
handleGetListDetail(id, page) { handleGetListDetail(id, source, page) {
this.isGetDetailFailed = false this.isGetDetailFailed = false
return this.getListDetail({ id, page }).catch(err => { return this.getListDetail({ id, source, page }).catch(err => {
this.isGetDetailFailed = true this.isGetDetailFailed = true
return Promise.reject(err) return Promise.reject(err)
}) })
}, },
async fetchList() { async fetchList() {
this.detailLoading = true this.detailLoading = true
return this.getListDetailAll({ source: this.source, id: this.selectListInfo.id }).finally(() => { return this.getListDetailAll({ source: this.listDetail.source, id: this.listDetail.id }).finally(() => {
this.detailLoading = false this.detailLoading = false
}) })
}, },