commit
0164936038
|
@ -16,7 +16,8 @@
|
|||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off"
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off"
|
||||
},
|
||||
"settings": {
|
||||
"html/html-extensions": [".html", ".vue"]
|
||||
|
|
|
@ -124,8 +124,8 @@ jobs:
|
|||
|
||||
- name: Build Package dmg
|
||||
run: |
|
||||
npm run publish:mac:dmg
|
||||
npm run publish:mac:dmg:arm64
|
||||
npm run pack:mac:dmg
|
||||
npm run pack:mac:dmg:arm64
|
||||
env:
|
||||
ELECTRON_CACHE: $HOME/.cache/electron
|
||||
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
|
||||
|
|
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -6,6 +6,21 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
|||
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
||||
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||
|
||||
## [1.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.11.0...v1.12.0) - 2021-08-08
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增局域网同步功能(实验性,首次使用前建议先备份一次列表),此功能需要配合PC端使用,移动端与PC端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用问题请看"常见问题"。
|
||||
|
||||
### 优化
|
||||
|
||||
- 添加播放器对系统媒体控制与显示的兼容处理,现在在windows下的锁屏界面可以正确显示当前播放的音乐信息及切换歌曲了
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复导入kg歌单最多只能加载100、500首歌曲的问题。注:现在可以加载1000+首歌曲的歌单,但出于未知原因会导致部分歌曲无法加载(可能是无版权导致的),目前酷狗码仍然最多只能加载500首歌
|
||||
- 修复某些情况下所显示的歌词、封面图片与当前正在播放的歌曲不一致的问题
|
||||
|
||||
## [1.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.2...v1.11.0) - 2021-07-18
|
||||
|
||||
### 新增
|
||||
|
|
93
FAQ.md
93
FAQ.md
|
@ -78,6 +78,99 @@
|
|||
|
||||
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况,请阅读下面的 **软件启动后,界面无法显示** 解决。
|
||||
|
||||
|
||||
# lx-music-mobile 常见问题
|
||||
|
||||
在阅读本常见问题后,仍然无法解决你的问题,请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
|
||||
|
||||
## 歌曲无法试听与下载
|
||||
|
||||
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`
|
||||
|
||||
尝试更换网络,如切换到移动网络,若移动网络还是不行则尝试开关下手机的飞行模式后再试,<br>
|
||||
若使用家庭网络的话,可尝试将光猫断电5分钟左右再通电联网后播放。
|
||||
|
||||
### 其他情况
|
||||
|
||||
尝试在在浏览器打开这个地址`http://ts.tempmusic.tk`,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器,对于此类情况请尝试切换其他网络。
|
||||
|
||||
### 通用解决方法
|
||||
|
||||
尝试按以下顺序解决:
|
||||
|
||||
1. 尝试更新到最新版本
|
||||
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
|
||||
3. 尝试到 设置-音乐来源 切换到其他接口
|
||||
4. 尝试切换网络,比如用手机开热点(所有歌曲都提示请求异常时可通过此方法解决,或等一两天后再试)
|
||||
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
|
||||
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明,则应该是你网络无法访问接口服务器的问题,如果接口有问题我会在那里说明。
|
||||
|
||||
想要知道是不是自己网络的问题可以看看`http://ts.tempmusic.tk`能不能在浏览器打开,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
若网页无法打开或打来不是404,则应该是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`
|
||||
|
||||
## 列表多选
|
||||
|
||||
长按列表将会进入多选模式。
|
||||
|
||||
- 例子一:想要选中1-5项,进入多选模式后,取消所有选中的内容,切换到区间,点击第一项,再点击第五项即可完成选择;
|
||||
- 例子二:想要选中1项与第3项,进入多选模式后,点击第一项,再点击第三项即可完成选择;
|
||||
- 例子三:想要选中当前列表的全部内容,进入多选模式后,点击全选即可完成选择(注:由于**在线列表**使用分页加载,全选只会选择目前已加载的内容,若要完整选择整个在线列表的内容则需要往下滑动将列表加载完毕再进行全选)。
|
||||
|
||||
注:选完后可用歌曲列表三个点的菜单操作已选的内容
|
||||
|
||||
## 无法打开外部歌单
|
||||
|
||||
不支持垮源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
对于分享出来的歌单,若打开失败,可尝试先在浏览器中打开后,再从浏览器地址栏复制URL地址到软件打开;<br>
|
||||
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
|
||||
|
||||
注:网易源的“我喜欢”歌单无法在未登录的情况下打开,所以你需要手动创建一个歌单后将“我喜欢”里的歌曲移动到该歌单打开
|
||||
|
||||
## 播放整个歌单或排行榜
|
||||
|
||||
播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。
|
||||
|
||||
## 无法打开外部歌单
|
||||
|
||||
不支持垮源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
对于分享出来的歌单,若打开失败,可尝试先在浏览器中打开后,再从浏览器地址栏复制URL地址到软件打开;<br>
|
||||
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
|
||||
|
||||
|
||||
## 同步功能的使用(实验性,首次使用前建议先备份一次列表)
|
||||
|
||||
**注意:由于同步传输时的数据是明文传输,请在受信任的网络下使用此功能!**<br>
|
||||
此功能需要配合PC端使用,移动端与PC端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用方法:
|
||||
|
||||
1. 在PC端的设置-数据同步开启同步功能(这时如果出现安全软件、防火墙等提示网络连接弹窗时需要点击允许)
|
||||
2. 在移动端的设置-同步-同步服务器地址输入PC端显示的同步服务器地址(如果显示可以多个,则输入与**移动端上显示的本机地址**最相似的那个),端口号与PC端的同步端口一致
|
||||
3. 输入完这两项后点击“启动同步”
|
||||
4. 若连接成功,对于首次同步时,若两边的设备的列表不为空,则PC端会弹出选择列表同步方式的弹窗,同步方式的说明弹窗下面有介绍
|
||||
|
||||
对于连接同步失败的可能原因:
|
||||
|
||||
- 此功能需要PC端与移动端都连接在同一个路由器下的网络才能使用
|
||||
- 路由器若开启了AP隔离,则此功能无法使用
|
||||
- 检查防火墙是否拦截了PC端的服务端口
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“更新”使用该功能,
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
|
||||
## 杀毒软件提示有病毒或恶意行为
|
||||
|
||||
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[GitHub Actions](https://github.com/lyswhut/lx-music-mobile/actions)<br>
|
||||
尽管如此,但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
|
||||
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。
|
||||
|
||||
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
|
||||
|
||||
|
||||
### Linux 下界面异常
|
||||
|
||||
根据Electron里issue的[解决方案](https://github.com/electron/electron/issues/2170#issuecomment-736223269),<br>
|
||||
|
|
|
@ -97,26 +97,38 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'media/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -97,26 +97,38 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'media/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.0",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
|
@ -163,33 +163,32 @@
|
|||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"browserslist": "^4.16.6",
|
||||
"browserslist": "^4.16.7",
|
||||
"cfonts": "^2.9.3",
|
||||
"chalk": "^4.1.1",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^2.8.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"core-js": "^3.15.2",
|
||||
"core-js": "^3.16.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.7",
|
||||
"css-loader": "^6.2.0",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^13.1.7",
|
||||
"electron": "^13.1.8",
|
||||
"electron-builder": "^22.11.7",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.3.779",
|
||||
"eslint": "^7.30.0",
|
||||
"electron-to-chromium": "^1.3.796",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
|
@ -204,9 +203,9 @@
|
|||
"less": "^4.1.1",
|
||||
"less-loader": "^10.0.1",
|
||||
"less-plugin-clean-css": "^1.5.1",
|
||||
"markdown-it": "^12.1.0",
|
||||
"mini-css-extract-plugin": "^2.1.0",
|
||||
"postcss": "^8.3.5",
|
||||
"markdown-it": "^12.2.0",
|
||||
"mini-css-extract-plugin": "^2.2.0",
|
||||
"postcss": "^8.3.6",
|
||||
"postcss-loader": "^6.1.1",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
|
@ -219,25 +218,32 @@
|
|||
"stylus-loader": "^6.1.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^5.45.1",
|
||||
"webpack": "^5.49.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"electron-log": "^4.3.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"electron-log": "^4.4.1",
|
||||
"bufferutil": "^4.0.3",
|
||||
"eiows": "^3.6.1",
|
||||
"electron-store": "^8.0.0",
|
||||
"electron-updater": "^4.3.9",
|
||||
"http-terminator": "^3.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.0",
|
||||
"lrc-file-parser": "^1.1.0",
|
||||
"koa": "^2.13.1",
|
||||
"long": "^4.0.0",
|
||||
"lrc-file-parser": "^1.1.2",
|
||||
"needle": "^2.8.0",
|
||||
"node-id3": "^0.2.3",
|
||||
"request": "^2.88.2",
|
||||
"socket.io": "^4.1.3",
|
||||
"utf-8-validate": "^5.0.5",
|
||||
"vue": "^2.6.14",
|
||||
"vue-i18n": "^8.25.0",
|
||||
"vue-router": "^3.5.2",
|
||||
|
|
|
@ -1,26 +1,12 @@
|
|||
### 新增
|
||||
|
||||
- 添加 win arm64 架构的安装包构建
|
||||
- 新增“添加歌曲到列表时的位置”设置,可选项为列表的“顶部”与“底部”
|
||||
- 新增局域网同步功能(实验性,首次使用前建议先备份一次列表),此功能需要配合PC端使用,移动端与PC端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用问题请看"常见问题"。
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化网络请求,尝试去解决无法连接服务器的问题
|
||||
- 优化mg源打开歌单的链接兼容
|
||||
- 添加播放器对系统媒体控制与显示的兼容处理,现在在windows下的锁屏界面可以正确显示当前播放的音乐信息及切换歌曲了
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复mg源搜索失效的问题
|
||||
|
||||
### 移除
|
||||
|
||||
- 因wy源的歌单列表已没有“最新”排序的选项,所以现跟随移除wy源歌单列表按“最新”排序的按钮
|
||||
|
||||
### 变更
|
||||
|
||||
- 添加歌曲到列表时从原来的底部改为顶部,若你想要将你的列表歌曲顺序反转以适应这一变更,可先按住`shift`键的情况下点击列表的最后一首歌,然后再点击列表的第一首歌,完成倒序选中,最后随便右击列表的任意一首歌,在弹出的菜单中选择调整顺序,在弹出框输入1后确定即可反转列表。
|
||||
若你想要恢复原来的行为则可以去更改“添加歌曲到列表时的位置”设置项。
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v13.1.7
|
||||
- 修复导入kg歌单最多只能加载100、500首歌曲的问题。注:现在可以加载1000+首歌曲的歌单,但出于未知原因会导致部分歌曲无法加载(可能是无版权导致的),目前酷狗码仍然最多只能加载500首歌
|
||||
- 修复某些情况下所显示的歌词、封面图片与当前正在播放的歌曲不一致的问题
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,7 @@ const path = require('path')
|
|||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.42',
|
||||
version: '1.0.43',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
|
@ -85,6 +85,10 @@ const defaultSetting = {
|
|||
isToTray: false,
|
||||
themeId: 0,
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
port: '23332',
|
||||
},
|
||||
windowSizeId: 2,
|
||||
themeId: 0,
|
||||
langId: null,
|
||||
|
|
|
@ -66,6 +66,13 @@ const names = {
|
|||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
|
||||
sync_enable: 'sync_enable',
|
||||
sync_status: 'sync_status',
|
||||
sync_get_status: 'sync_get_status',
|
||||
sync_generate_code: 'sync_generate_code',
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
|
|
|
@ -10,6 +10,10 @@ class Common extends EventEmitter {
|
|||
configStatus(name) {
|
||||
this.emit(COMMON_EVENT_NAME.configStatus, name)
|
||||
}
|
||||
|
||||
saveMyList(data) {
|
||||
this.emit(COMMON_EVENT_NAME.saveMyList, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Common
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
exports.common = {
|
||||
initConfig: 'initConfig',
|
||||
configStatus: 'config',
|
||||
saveMyList: 'saveMyList',
|
||||
}
|
||||
|
||||
exports.mainWindow = {
|
||||
|
|
|
@ -7,6 +7,7 @@ const WinLyric = require('./WinLyric')
|
|||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
const { Event: Sync } = require('../modules/sync')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
|
@ -15,3 +16,4 @@ if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
|||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
if (!global.lx_event.sync) global.lx_event.sync = new Sync()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
const { EventEmitter } = require('events')
|
||||
const SYNC_EVENT_NAME = require('./name')
|
||||
|
||||
class Sync extends EventEmitter {
|
||||
status(status) {
|
||||
this.emit(SYNC_EVENT_NAME.status, status)
|
||||
}
|
||||
|
||||
sync_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_list, data)
|
||||
}
|
||||
|
||||
sync_handle_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_handle_list, data)
|
||||
}
|
||||
|
||||
action_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_action_list, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sync
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
sync_handle_list: 'sync_handle_list',
|
||||
status: 'status',
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const modules = require('./modules')
|
||||
const { startServer, stopServer, getStatus, generateCode } = require('./server/server')
|
||||
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
Event,
|
||||
eventNames,
|
||||
modules,
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
exports.list = require('./list')
|
|
@ -0,0 +1,41 @@
|
|||
const { encryptMsg, decryptMsg } = require('../server/utils')
|
||||
let io
|
||||
|
||||
const handleListAction = ({ action, data }) => {
|
||||
// console.log(action, data)
|
||||
global.lx_event.sync.action_list({ action, data })
|
||||
}
|
||||
|
||||
// const addMusic = (orderId, callback) => {
|
||||
// // ...
|
||||
// }
|
||||
|
||||
const broadcast = async(action, data, excludeIds = []) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
|
||||
}
|
||||
}
|
||||
|
||||
exports.sendListAction = (action, data) => {
|
||||
// io.sockets
|
||||
return broadcast('list:action', JSON.stringify({ action, data }))
|
||||
}
|
||||
|
||||
exports.registerListHandler = (_io, socket) => {
|
||||
io = _io
|
||||
socket.on('list:action', msg => {
|
||||
// console.log(msg)
|
||||
msg = decryptMsg(socket.data.keyInfo, msg)
|
||||
if (!msg) return
|
||||
handleListAction(JSON.parse(msg))
|
||||
broadcast('list:action', msg, [socket.data.keyInfo.clientId])
|
||||
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
|
||||
})
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
exports.unregisterListHandler = () => {
|
||||
io = null
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
const { aesEncrypt, aesDecrypt, createClientKeyInfo, getClientKeyInfo, setClientKeyInfo } = require('./utils')
|
||||
|
||||
const authMsg = 'lx-music auth::'
|
||||
const helloMsg = 'Hello~::^-^::'
|
||||
|
||||
exports.authCode = async(req, res, authCode) => {
|
||||
let code = 401
|
||||
let msg = 'Forbidden'
|
||||
// console.log(req.headers)
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) {
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
} else {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(authCode, key, iv)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key, iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
}
|
||||
|
||||
exports.authConnect = async req => {
|
||||
const { i, t } = req._query
|
||||
label:
|
||||
if (i && t) {
|
||||
const keyInfo = getClientKeyInfo(i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
if (text == 'lx-music connect') return
|
||||
}
|
||||
throw new Error('failed')
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
const { startServer, stopServer, getStatus } = require('./server')
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
const http = require('http')
|
||||
const sio = require('socket.io')
|
||||
const { createHttpTerminator } = require('http-terminator')
|
||||
const modules = require('../modules')
|
||||
const { authCode, authConnect } = require('./auth')
|
||||
const { getAddress, getServerId, generateCode, getClientKeyInfo } = require('./utils')
|
||||
const syncList = require('./syncList')
|
||||
|
||||
|
||||
let status = {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
code: '',
|
||||
devices: [],
|
||||
}
|
||||
|
||||
const handleConnection = (io, socket) => {
|
||||
console.log('connection')
|
||||
// console.log(socket.handshake.query)
|
||||
for (const module of Object.values(modules)) {
|
||||
module.registerListHandler(io, socket)
|
||||
}
|
||||
}
|
||||
|
||||
const authConnection = (req, callback) => {
|
||||
// console.log(req.headers)
|
||||
// // console.log(req.auth)
|
||||
// console.log(req._query.authCode)
|
||||
authConnect(req).then(() => {
|
||||
callback(null, true)
|
||||
}).catch(err => {
|
||||
callback(err, false)
|
||||
})
|
||||
}
|
||||
|
||||
let httpTerminator = null
|
||||
let io = null
|
||||
|
||||
const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
// console.log(req.url)
|
||||
let code
|
||||
let msg
|
||||
switch (req.url) {
|
||||
case '/hello':
|
||||
code = 200
|
||||
msg = 'Hello~::^-^::'
|
||||
break
|
||||
case '/id':
|
||||
code = 200
|
||||
msg = 'OjppZDo6' + getServerId()
|
||||
break
|
||||
case '/ah':
|
||||
authCode(req, res, status.code)
|
||||
break
|
||||
default:
|
||||
code = 401
|
||||
msg = 'Forbidden'
|
||||
break
|
||||
}
|
||||
if (!code) return
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
})
|
||||
httpTerminator = createHttpTerminator({
|
||||
server: httpServer,
|
||||
})
|
||||
io = sio(httpServer, {
|
||||
path: '/sync',
|
||||
serveClient: false,
|
||||
connectTimeout: 10000,
|
||||
pingTimeout: 30000,
|
||||
maxHttpBufferSize: 3e6,
|
||||
allowRequest: authConnection,
|
||||
transports: ['websocket'],
|
||||
})
|
||||
|
||||
io.on('connection', async socket => {
|
||||
socket.on('disconnect', reason => {
|
||||
console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
|
||||
// socket.lx_keyInfo = keyInfo
|
||||
socket.data.keyInfo = keyInfo
|
||||
try {
|
||||
await syncList(io, socket)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
handleConnection(io, socket, keyInfo)
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
|
||||
httpServer.on('error', error => {
|
||||
console.log(error)
|
||||
reject(error)
|
||||
})
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const addr = httpServer.address()
|
||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
||||
console.info(`Listening on ${bind}`)
|
||||
resolve()
|
||||
})
|
||||
|
||||
httpServer.listen(port)
|
||||
})
|
||||
|
||||
const handleStopServer = async() => {
|
||||
if (!httpTerminator) return
|
||||
await io.close()
|
||||
await httpTerminator.terminate().catch(() => {})
|
||||
io = null
|
||||
httpTerminator = null
|
||||
}
|
||||
|
||||
exports.stopServer = async() => {
|
||||
if (!status.status) return
|
||||
console.log('stoping sync server...')
|
||||
return handleStopServer().then(() => {
|
||||
console.log('sync server stoped')
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.message = err.message
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
}
|
||||
exports.startServer = async port => {
|
||||
if (status.status) await handleStopServer()
|
||||
|
||||
console.log('starting sync server...')
|
||||
return handleStartServer(port).then(() => {
|
||||
console.log('sync server started')
|
||||
status.status = true
|
||||
status.message = ''
|
||||
status.address = getAddress()
|
||||
status.code = generateCode()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.status = false
|
||||
status.message = err.message
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
}
|
||||
|
||||
exports.getStatus = () => status
|
||||
|
||||
exports.generateCode = async() => {
|
||||
status.code = generateCode()
|
||||
global.lx_event.sync.status(status)
|
||||
return status.code
|
||||
}
|
|
@ -0,0 +1,394 @@
|
|||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
const { app } = require('electron')
|
||||
const { encryptMsg, decryptMsg } = require('./utils')
|
||||
const SYNC_EVENT_NAMES = require('../event/name')
|
||||
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
|
||||
const { throttle } = require('@common/utils')
|
||||
|
||||
let io
|
||||
let syncingId = null
|
||||
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
|
||||
|
||||
const getRemoteListData = socket => new Promise((resolve, reject) => {
|
||||
console.log('getRemoteListData')
|
||||
const handleError = reason => {
|
||||
reject(new Error(reason))
|
||||
}
|
||||
const handleSuccess = enData => {
|
||||
socket.removeListener('disconnect', handleError)
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) return reject(new Error('Get remote list data failed'))
|
||||
if (data.action != 'getData') return
|
||||
resolve(data.data)
|
||||
}
|
||||
|
||||
socket.on('disconnect', handleError)
|
||||
socket.on('list:sync', handleSuccess)
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
|
||||
})
|
||||
|
||||
const getLocalListData = () => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'getData') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'getData',
|
||||
})
|
||||
})
|
||||
const getSyncMode = keyInfo => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'selectMode') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'selectMode',
|
||||
data: keyInfo,
|
||||
})
|
||||
})
|
||||
|
||||
const finishedSync = socket => {
|
||||
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
|
||||
action: 'finished',
|
||||
})))
|
||||
}
|
||||
|
||||
const setLocalList = listData => {
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'setData',
|
||||
data: listData,
|
||||
})
|
||||
}
|
||||
const setRemotelList = async(socket, listData) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
|
||||
}
|
||||
}
|
||||
|
||||
let writeFilePromises = {}
|
||||
const updateSnapshot = (path, data) => {
|
||||
console.log('updateSnapshot', path)
|
||||
let writeFilePromise = writeFilePromises[path] || Promise.resolve()
|
||||
return writeFilePromise.then(() => {
|
||||
writeFilePromise = writeFilePromises[path] = fsPromises.writeFile(path, data)
|
||||
return writeFilePromise
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const createListDataObj = listData => {
|
||||
const listDataObj = {}
|
||||
for (const list of listData.userList) listDataObj[list.id] = list
|
||||
return listDataObj
|
||||
}
|
||||
|
||||
const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
|
||||
let newList
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
break
|
||||
}
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const mergeList = (sourceListData, targetListData) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const newListData = {}
|
||||
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
|
||||
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
|
||||
|
||||
const listDataObj = createListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
for (const list of targetListData.userList) {
|
||||
const targetList = listDataObj[list.id]
|
||||
if (targetList) {
|
||||
targetList.list = handleMergeList(targetList, list, addMusicLocationType).list
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
}
|
||||
|
||||
return newListData
|
||||
}
|
||||
const overwriteList = (sourceListData, targetListData) => {
|
||||
const newListData = {}
|
||||
newListData.defaultList = sourceListData.defaultList
|
||||
newListData.loveList = sourceListData.loveList
|
||||
|
||||
const listDataObj = createListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
for (const list of targetListData.userList) {
|
||||
const targetList = listDataObj[list.id]
|
||||
if (targetList) continue
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
|
||||
return newListData
|
||||
}
|
||||
|
||||
const handleMergeListData = async socket => {
|
||||
let isSelectingMode = false
|
||||
const handleDisconnect = () => {
|
||||
if (!isSelectingMode) return
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'closeSelectMode',
|
||||
})
|
||||
}
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
isSelectingMode = true
|
||||
const mode = await getSyncMode(socket.data.keyInfo)
|
||||
isSelectingMode = false
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListData', 'remoteListData, localListData')
|
||||
let listData
|
||||
switch (mode) {
|
||||
case 'merge_local_remote':
|
||||
listData = mergeList(localListData, remoteListData)
|
||||
break
|
||||
case 'merge_remote_local':
|
||||
listData = mergeList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote':
|
||||
listData = overwriteList(localListData, remoteListData)
|
||||
break
|
||||
case 'overwrite_remote_local':
|
||||
listData = overwriteList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote_full':
|
||||
listData = localListData
|
||||
break
|
||||
case 'overwrite_remote_local_full':
|
||||
listData = remoteListData
|
||||
break
|
||||
case 'none': return
|
||||
case 'cancel':
|
||||
socket.disconnect(true)
|
||||
throw new Error('cancel')
|
||||
}
|
||||
return listData
|
||||
}
|
||||
|
||||
const handleSyncList = async socket => {
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleSyncList', 'remoteListData, localListData')
|
||||
const listData = {}
|
||||
if (localListData.defaultList.list.length || localListData.loveList.list.length || localListData.userList.length) {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
const mergedList = await handleMergeListData(socket)
|
||||
console.log('handleMergeListData', 'mergedList')
|
||||
console.log(mergedList)
|
||||
if (!mergedList) return
|
||||
listData.defaultList = mergedList.defaultList
|
||||
listData.loveList = mergedList.loveList
|
||||
listData.userList = mergedList.userList
|
||||
setLocalList(mergedList)
|
||||
setRemotelList(socket, mergedList)
|
||||
} else {
|
||||
setRemotelList(socket, localListData)
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
} else {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
setLocalList(remoteListData)
|
||||
listData.defaultList = remoteListData.defaultList
|
||||
listData.loveList = remoteListData.loveList
|
||||
listData.userList = remoteListData.userList
|
||||
} else {
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
}
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: listData.defaultList,
|
||||
loveList: listData.loveList,
|
||||
userList: listData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return listData
|
||||
})
|
||||
}
|
||||
|
||||
const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusicLocationType) => {
|
||||
const removedListIds = new Set()
|
||||
const sourceListItemIds = new Set()
|
||||
const targetListItemIds = new Set()
|
||||
for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
|
||||
for (const m of targetList.list) targetListItemIds.add(m.songmid)
|
||||
for (const m of snapshotList.list) {
|
||||
if (!sourceListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
for (const m of snapshotList.list) {
|
||||
if (!targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListDataFromSnapshot', 'remoteListData, localListData')
|
||||
const newListData = {}
|
||||
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
|
||||
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
|
||||
const localUserListData = createListDataObj(localListData)
|
||||
const remoteUserListData = createListDataObj(remoteListData)
|
||||
const snapshotUserListData = createListDataObj(snapshot)
|
||||
const removedListIds = new Set()
|
||||
const localUserListIds = new Set()
|
||||
const remoteUserListIds = new Set()
|
||||
for (const l of localListData.userList) localUserListIds.add(l.id)
|
||||
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
|
||||
|
||||
for (const l of snapshot.userList) {
|
||||
if (!localUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
for (const l of snapshot.userList) {
|
||||
if (!remoteUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
|
||||
let newUserList = []
|
||||
for (const list of localListData.userList) {
|
||||
if (removedListIds.has(list.id)) continue
|
||||
const remoteList = remoteUserListData[list.id]
|
||||
let newList
|
||||
if (remoteList) {
|
||||
newList = mergeListDataFromSnapshot(list, remoteList, snapshotUserListData[list.id], addMusicLocationType)
|
||||
} else {
|
||||
newList = { ...list }
|
||||
}
|
||||
newUserList.push(newList)
|
||||
}
|
||||
for (const list of remoteListData.userList) {
|
||||
if (removedListIds.has(list.id) || localUserListData[list.id]) continue
|
||||
newUserList.push({ ...list })
|
||||
}
|
||||
newListData.userList = newUserList
|
||||
setLocalList(newListData)
|
||||
setRemotelList(socket, newListData)
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: newListData.defaultList,
|
||||
loveList: newListData.loveList,
|
||||
userList: newListData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return newListData
|
||||
})
|
||||
}
|
||||
|
||||
const registerUpdateSnapshotTask = (socket, snapshot) => {
|
||||
if (!socket.data.isCreatedSnapshot) return
|
||||
const handleUpdateSnapshot = throttle(({ defaultList, loveList, userList }) => {
|
||||
if (defaultList != null) snapshot.defaultList = defaultList
|
||||
if (loveList != null) snapshot.loveList = loveList
|
||||
if (userList != null) snapshot.userList = userList
|
||||
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
|
||||
}, 10000)
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
socket.on('disconnect', () => {
|
||||
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
})
|
||||
}
|
||||
|
||||
const syncList = async socket => {
|
||||
socket.data.snapshotFilePath = path.join(app.getPath('userData'), `snapshot-${Buffer.from(socket.data.keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
let fileData
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
fileData = await fsPromises.readFile(socket.data.snapshotFilePath)
|
||||
fileData = JSON.parse(fileData)
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err
|
||||
isSyncRequired = true
|
||||
}
|
||||
console.log('isSyncRequired', isSyncRequired)
|
||||
if (isSyncRequired) return handleSyncList(socket)
|
||||
return handleMergeListDataFromSnapshot(socket, fileData)
|
||||
}
|
||||
|
||||
const checkSyncQueue = async() => {
|
||||
if (!syncingId) return
|
||||
await wait()
|
||||
return checkSyncQueue()
|
||||
}
|
||||
module.exports = async(_io, socket) => {
|
||||
io = _io
|
||||
await checkSyncQueue()
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
return syncList(socket).then(newListData => {
|
||||
registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
return finishedSync(socket)
|
||||
}).finally(() => {
|
||||
syncingId = null
|
||||
})
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
const { networkInterfaces } = require('os')
|
||||
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
|
||||
const getStore = require('@common/store')
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
exports.getAddress = () => {
|
||||
const nets = networkInterfaces()
|
||||
const results = []
|
||||
// console.log(nets)
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
results.push(net.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let serverId
|
||||
exports.getServerId = () => {
|
||||
if (serverId) return serverId
|
||||
const store = getStore(STORE_NAME)
|
||||
serverId = store.get('serverId')
|
||||
if (!serverId) {
|
||||
serverId = randomBytes(4 * 4).toString('base64')
|
||||
store.set('serverId', serverId)
|
||||
}
|
||||
return serverId
|
||||
}
|
||||
|
||||
let keyInfos
|
||||
exports.createClientKeyInfo = deviceName => {
|
||||
const keyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
iv: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
if (!keyInfos) keyInfos = store.get('keys') || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
store.set('keys', keyInfos)
|
||||
return keyInfo
|
||||
}
|
||||
exports.setClientKeyInfo = keyInfo => {
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
const store = getStore(STORE_NAME)
|
||||
store.set('keys', keyInfos)
|
||||
}
|
||||
exports.getClientKeyInfo = clientId => {
|
||||
if (!keyInfos) {
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos = store.get('keys') || {}
|
||||
}
|
||||
return keyInfos[clientId] || null
|
||||
}
|
||||
|
||||
exports.generateCode = () => {
|
||||
return Math.random().toString().substring(2, 8)
|
||||
}
|
||||
|
||||
exports.aesEncrypt = (buffer, key, iv) => {
|
||||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
|
||||
}
|
||||
|
||||
exports.aesDecrypt = (text, key, iv) => {
|
||||
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
exports.encryptMsg = (keyInfo, msg) => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return exports.aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
exports.decryptMsg = (keyInfo, enMsg) => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = exports.aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
|
@ -23,3 +23,4 @@ require('./musicUrl')
|
|||
require('./kw_decodeLyric')
|
||||
|
||||
require('./userApi')
|
||||
require('./sync')
|
||||
|
|
|
@ -24,6 +24,7 @@ mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => {
|
|||
switch (type) {
|
||||
case 'myList':
|
||||
handleSaveList(data)
|
||||
global.lx_event.common.saveMyList(data)
|
||||
break
|
||||
case 'downloadList':
|
||||
getStore('downloadList').set('list', data)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('@common/ipc')
|
||||
const { eventNames, modules, startServer, stopServer, getStatus, generateCode } = require('../modules/sync')
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.sync_action_list, (event, { action, data }) => {
|
||||
modules.list.sendListAction(action, data)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.sync_enable, (event, { enable, port }) => {
|
||||
return enable ? startServer(port) : stopServer()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.sync_get_status, () => {
|
||||
return getStatus()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.sync_generate_code, () => {
|
||||
return generateCode()
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.sync_list, (event, { action, data }) => {
|
||||
global.lx_event.sync.sync_handle_list({ action, data })
|
||||
})
|
||||
|
||||
global.lx_event.sync.on(eventNames.sync_action_list, ({ action, data }) => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_action_list, { action, data })
|
||||
})
|
||||
global.lx_event.sync.on(eventNames.status, status => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_status, status)
|
||||
})
|
||||
global.lx_event.sync.on(eventNames.sync_list, ({ action, data }) => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_list, { action, data })
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
const { log } = require('../../common/utils')
|
||||
const { log, isWin } = require('../../common/utils')
|
||||
const { autoUpdater } = require('electron-updater')
|
||||
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
|
||||
|
@ -120,6 +120,11 @@ module.exports = () => {
|
|||
}, 1000)
|
||||
})
|
||||
|
||||
// 由于集合安装包中不包含win arm版,这将会导致arm版更新失败
|
||||
if (isWin && process.arch.includes('arm')) {
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_error, info: 'failed' })
|
||||
} else {
|
||||
autoUpdater.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
core-icons
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
material-sync-mode-modal(v-show="globalObj.sync.isShowSyncMode")
|
||||
#container(v-else :class="theme")
|
||||
core-aside#left
|
||||
#right
|
||||
|
@ -17,6 +18,7 @@
|
|||
core-icons
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
material-sync-mode-modal(v-show="globalObj.sync.isShowSyncMode")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -25,7 +27,7 @@ import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
|||
import { isLinux } from '../common/utils'
|
||||
import music from './utils/music'
|
||||
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils'
|
||||
import { base as eventBaseName } from './event/names'
|
||||
import { base as eventBaseName, sync as eventSyncName } from './event/names'
|
||||
import apiSourceInfo from './utils/music/api-source-info'
|
||||
|
||||
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||
|
@ -61,6 +63,11 @@ export default {
|
|||
message: 'initing',
|
||||
apis: {},
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
isShowSyncMode: false,
|
||||
deviceName: '',
|
||||
},
|
||||
},
|
||||
updateTimeout: null,
|
||||
envParams: {
|
||||
|
@ -103,6 +110,14 @@ export default {
|
|||
document.body.classList.add(this.isDT ? 'disableTransparent' : 'transparent')
|
||||
window.eventHub.$emit(eventBaseName.bindKey)
|
||||
this.init()
|
||||
window.eventHub.$on(eventSyncName.handle_action_list, this.handleSyncAction)
|
||||
window.eventHub.$on(eventSyncName.handle_sync_list, this.handleSyncList)
|
||||
if (this.setting.sync.enable && this.setting.sync.port) {
|
||||
rendererInvoke(NAMES.mainWindow.sync_enable, {
|
||||
enable: this.setting.sync.enable,
|
||||
port: this.setting.sync.port,
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
setting: {
|
||||
|
@ -193,7 +208,25 @@ export default {
|
|||
methods: {
|
||||
...mapActions(['getVersionInfo']),
|
||||
...mapMutations(['setNewVersion', 'setVersionModalVisible', 'setDownloadProgress', 'setSetting', 'setDesktopLyricConfig']),
|
||||
...mapMutations('list', ['initList']),
|
||||
...mapMutations('list', {
|
||||
list_initList: 'initList',
|
||||
list_setList: 'setList',
|
||||
list_listAdd: 'listAdd',
|
||||
list_listMove: 'listMove',
|
||||
list_listAddMultiple: 'listAddMultiple',
|
||||
list_listMoveMultiple: 'listMoveMultiple',
|
||||
list_listRemove: 'listRemove',
|
||||
list_listRemoveMultiple: 'listRemoveMultiple',
|
||||
list_listClear: 'listClear',
|
||||
list_updateMusicInfo: 'updateMusicInfo',
|
||||
list_createUserList: 'createUserList',
|
||||
list_removeUserList: 'removeUserList',
|
||||
list_setUserListName: 'setUserListName',
|
||||
list_moveupUserList: 'moveupUserList',
|
||||
list_movedownUserList: 'movedownUserList',
|
||||
list_setMusicPosition: 'setMusicPosition',
|
||||
list_setSyncListData: 'setSyncListData',
|
||||
}),
|
||||
...mapMutations('download', ['updateDownloadList']),
|
||||
...mapMutations('search', {
|
||||
setSearchHistoryList: 'setHistory',
|
||||
|
@ -272,6 +305,7 @@ export default {
|
|||
this.listenEvent()
|
||||
asyncTask.push(this.initData())
|
||||
asyncTask.push(this.initUserApi())
|
||||
this.globalObj.sync.enable = this.setting.sync.enable
|
||||
this.globalObj.apiSource = this.setting.apiSource
|
||||
if (/^user_api/.test(this.setting.apiSource)) {
|
||||
rendererInvoke(NAMES.mainWindow.set_user_api, this.setting.apiSource)
|
||||
|
@ -328,7 +362,7 @@ export default {
|
|||
|
||||
if (!defaultList.list) defaultList.list = []
|
||||
if (!loveList.list) loveList.list = []
|
||||
this.initList({ defaultList, loveList, userList })
|
||||
this.list_initList({ defaultList, loveList, userList })
|
||||
this.initDownloadList(downloadList) // 初始化下载列表
|
||||
this.initPlayInfo()
|
||||
})
|
||||
|
@ -590,6 +624,84 @@ export default {
|
|||
event.target.value = ''
|
||||
event.target.blur()
|
||||
},
|
||||
handleSyncAction({ action, data }) {
|
||||
if (typeof data == 'object') data.isSync = true
|
||||
// console.log(action, data)
|
||||
|
||||
switch (action) {
|
||||
case 'set_list':
|
||||
this.list_setList(data)
|
||||
break
|
||||
case 'list_add':
|
||||
this.list_listAdd(data)
|
||||
break
|
||||
case 'list_move':
|
||||
this.list_listMove(data)
|
||||
break
|
||||
case 'list_add_multiple':
|
||||
this.list_listAddMultiple(data)
|
||||
break
|
||||
case 'list_move_multiple':
|
||||
this.list_listMoveMultiple(data)
|
||||
break
|
||||
case 'list_remove':
|
||||
this.list_listRemove(data)
|
||||
break
|
||||
case 'list_remove_multiple':
|
||||
this.list_listRemoveMultiple(data)
|
||||
break
|
||||
case 'list_clear':
|
||||
this.list_listClear(data)
|
||||
break
|
||||
case 'update_music_info':
|
||||
this.list_updateMusicInfo(data)
|
||||
break
|
||||
case 'create_user_list':
|
||||
this.list_createUserList(data)
|
||||
break
|
||||
case 'remove_user_list':
|
||||
this.list_removeUserList(data)
|
||||
break
|
||||
case 'set_user_list_name':
|
||||
this.list_setUserListName(data)
|
||||
break
|
||||
case 'moveup_user_list':
|
||||
this.list_moveupUserList(data)
|
||||
break
|
||||
case 'movedown_user_list':
|
||||
this.list_movedownUserList(data)
|
||||
break
|
||||
case 'set_music_position':
|
||||
this.list_setMusicPosition(data)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
handleSyncList({ action, data }) {
|
||||
switch (action) {
|
||||
case 'getData':
|
||||
global.eventHub.$emit(eventSyncName.send_sync_list, {
|
||||
action: 'getData',
|
||||
data: {
|
||||
defaultList: this.defaultList,
|
||||
loveList: this.loveList,
|
||||
userList: this.userList,
|
||||
},
|
||||
})
|
||||
break
|
||||
case 'setData':
|
||||
this.list_setSyncListData(data)
|
||||
break
|
||||
case 'selectMode':
|
||||
this.globalObj.sync.deviceName = data.deviceName
|
||||
this.globalObj.sync.isShowSyncMode = true
|
||||
break
|
||||
case 'closeSelectMode':
|
||||
this.globalObj.sync.isShowSyncMode = false
|
||||
break
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearUpdateTimeout()
|
||||
|
|
|
@ -123,6 +123,9 @@ small {
|
|||
.small {
|
||||
font-size: .9em;
|
||||
}
|
||||
.tip {
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -192,6 +195,9 @@ each(@themes, {
|
|||
button, input, textarea, a {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.tip {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
|
||||
.hover, a {
|
||||
&:hover {
|
||||
|
|
|
@ -236,6 +236,7 @@ export default {
|
|||
break
|
||||
}
|
||||
})
|
||||
this.registerMediaSessionHandler()
|
||||
navigator.mediaDevices.addEventListener('devicechange', this.handleMediaListChange)
|
||||
document.addEventListener('mousemove', this.handleVolumeMsMove)
|
||||
document.addEventListener('mouseup', this.handleVolumeMsUp)
|
||||
|
@ -255,6 +256,8 @@ export default {
|
|||
if (window.restorePlayInfo) {
|
||||
this.handleRestorePlay(window.restorePlayInfo)
|
||||
window.restorePlayInfo = null
|
||||
navigator.mediaSession.playbackState = 'paused'
|
||||
this.updateMediaSessionInfo()
|
||||
return
|
||||
}
|
||||
// console.log('changePlay')
|
||||
|
@ -411,7 +414,7 @@ export default {
|
|||
this.restorePlayTime = 0
|
||||
}
|
||||
if (!this.targetSong.interval && this.listId != 'download') {
|
||||
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) }, musicInfo: this.targetSong })
|
||||
this.updateMusicInfo({ listId: this.listId, id: this.targetSong.songmid, musicInfo: this.targetSong, data: { interval: formatPlayTime2(this.maxPlayTime) } })
|
||||
}
|
||||
})
|
||||
audio.addEventListener('loadstart', () => {
|
||||
|
@ -480,6 +483,7 @@ export default {
|
|||
},
|
||||
async play() {
|
||||
this.clearDelayNextTimeout()
|
||||
this.updateMediaSessionInfo()
|
||||
|
||||
const targetSong = this.targetSong
|
||||
|
||||
|
@ -551,20 +555,28 @@ export default {
|
|||
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
||||
this.setAppTitle()
|
||||
this.sendProgressEvent(this.progress, 'normal')
|
||||
navigator.mediaSession.playbackState = 'playing'
|
||||
},
|
||||
stopPlay() {
|
||||
this.isPlay = false
|
||||
window.lrc.pause()
|
||||
this.handleUpdateWinLyricInfo('pause')
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
this.clearAppTitle()
|
||||
this.$nextTick(() => {
|
||||
if (this.playMusicInfo) {
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
navigator.mediaSession.playbackState = 'paused'
|
||||
} else {
|
||||
this.sendProgressEvent(this.progress, 'none')
|
||||
navigator.mediaSession.playbackState = 'none'
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSetProgress(event) {
|
||||
this.setProgress(event.offsetX / this.pregessWidth)
|
||||
this.setProgress(event.offsetX / this.pregessWidth * this.maxPlayTime)
|
||||
},
|
||||
setProgress(pregress) {
|
||||
setProgress(time) {
|
||||
if (!audio.src) return
|
||||
const time = pregress * this.maxPlayTime
|
||||
if (this.restorePlayTime) this.restorePlayTime = time
|
||||
if (this.mediaBuffer.playTime) {
|
||||
this.clearBufferTimeout()
|
||||
|
@ -634,12 +646,15 @@ export default {
|
|||
|
||||
if (!this.musicInfo.img) {
|
||||
this.getPic(targetSong).then(() => {
|
||||
if (targetSong !== this.targetSong) return
|
||||
this.musicInfo.img = targetSong.img
|
||||
this.updateMediaSessionInfo()
|
||||
})
|
||||
}
|
||||
},
|
||||
setLrc(targetSong) {
|
||||
this.getLrc(targetSong).then(({ lyric, tlyric, lxlyric }) => {
|
||||
if (targetSong !== this.targetSong) return
|
||||
this.musicInfo.lrc = lyric
|
||||
this.musicInfo.tlrc = tlyric
|
||||
this.musicInfo.lxlrc = lxlyric
|
||||
|
@ -834,7 +849,7 @@ export default {
|
|||
this.playNext()
|
||||
break
|
||||
case 'progress':
|
||||
this.setProgress(data)
|
||||
this.handleSetProgress(data)
|
||||
break
|
||||
case 'volume':
|
||||
break
|
||||
|
@ -902,6 +917,59 @@ export default {
|
|||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
updateMediaSessionInfo() {
|
||||
const mediaMetadata = {
|
||||
title: this.targetSong.name,
|
||||
artist: this.targetSong.singer,
|
||||
album: this.targetSong.albumName,
|
||||
}
|
||||
if (this.targetSong.img) mediaMetadata.artwork = [{ src: this.targetSong.img }]
|
||||
navigator.mediaSession.metadata = new window.MediaMetadata(mediaMetadata)
|
||||
},
|
||||
registerMediaSessionHandler() {
|
||||
// navigator.mediaSession.setActionHandler('play', () => {
|
||||
// if (this.isPlay || !this.playMusicInfo) return
|
||||
// console.log('play')
|
||||
// this.startPlay()
|
||||
// })
|
||||
// navigator.mediaSession.setActionHandler('pause', () => {
|
||||
// if (!this.isPlay || !this.playMusicInfo) return
|
||||
// console.log('pause')
|
||||
// this.stopPlay()
|
||||
// })
|
||||
navigator.mediaSession.setActionHandler('stop', () => {
|
||||
if (!this.isPlay || !this.playMusicInfo) return
|
||||
console.log('stop')
|
||||
this.stopPlay()
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('seekbackward', details => {
|
||||
if (!this.isPlay || !this.playMusicInfo) return
|
||||
console.log('seekbackward')
|
||||
this.setProgress(Math.max(audio.currentTime - details.seekOffset, 0))
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('seekforward', details => {
|
||||
if (!this.isPlay || !this.playMusicInfo) return
|
||||
console.log('seekforward')
|
||||
this.setProgress(Math.min(audio.currentTime + details.seekOffset, audio.duration))
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('seekto', details => {
|
||||
console.log('seekto', details.seekTime)
|
||||
let time = Math.min(details.seekTime, audio.duration)
|
||||
time = Math.max(time, 0)
|
||||
this.setProgress(time)
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
console.log('previoustrack')
|
||||
this.playPrev()
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
console.log('nexttrack')
|
||||
this.playNext()
|
||||
})
|
||||
// navigator.mediaSession.setActionHandler('skipad', () => {
|
||||
// console.log('')
|
||||
// })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -282,7 +282,7 @@ export default {
|
|||
setProgress(event) {
|
||||
this.$emit('action', {
|
||||
type: 'progress',
|
||||
data: event.offsetX / this.pregessWidth,
|
||||
data: event,
|
||||
})
|
||||
},
|
||||
setProgressWidth() {
|
||||
|
|
|
@ -140,7 +140,7 @@ export default {
|
|||
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
|
||||
overflow: hidden;
|
||||
max-height: 80%;
|
||||
max-width: 70%;
|
||||
max-width: 76%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
<template lang="pug">
|
||||
material-modal(:show="globalObj.sync.isShowSyncMode" @close="handleClose(false)" :bgClose="false" :close-btn="false")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('material.sync_mode_modal.title', { name: globalObj.sync.deviceName })}}
|
||||
div.scroll(:class="$style.content")
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('material.sync_mode_modal.merge_label')}}
|
||||
dd(:class="$style.btns")
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('merge_local_remote')") {{$t('material.sync_mode_modal.merge_btn_local_remote')}}
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('merge_remote_local')") {{$t('material.sync_mode_modal.merge_btn_remote_local')}}
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('material.sync_mode_modal.overwrite_label')}}
|
||||
dd(:class="$style.btns")
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('overwrite_local_remote')") {{$t('material.sync_mode_modal.overwrite_btn_local_remote')}}
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('overwrite_remote_local')") {{$t('material.sync_mode_modal.overwrite_btn_remote_local')}}
|
||||
dd(style="font-size: 14px; margin-top: 5px;")
|
||||
material-checkbox(id="sync_mode_modal_isOverwrite" v-model="isOverwrite" :label="$t('material.sync_mode_modal.overwrite')")
|
||||
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('material.sync_mode_modal.other_label')}}
|
||||
dd(:class="$style.btns")
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('none')") {{$t('material.sync_mode_modal.overwrite_btn_none')}}
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('cancel')") {{$t('material.sync_mode_modal.overwrite_btn_cancel')}}
|
||||
dl(:class="$style.btnGroup")
|
||||
dd
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('material.sync_mode_modal.merge_tip')}}
|
||||
p(:class="$style.tip") {{$t('material.sync_mode_modal.merge_tip_desc')}}
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('material.sync_mode_modal.overwrite_tip')}}
|
||||
p(:class="$style.tip") {{$t('material.sync_mode_modal.overwrite_tip_desc')}}
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('material.sync_mode_modal.other_tip')}}
|
||||
p(:class="$style.tip") {{$t('material.sync_mode_modal.other_tip_desc')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { sync as eventSyncName } from '@renderer/event/names'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isOverwrite: false,
|
||||
globalObj: {
|
||||
sync: {
|
||||
isShowSyncMode: false,
|
||||
deviceName: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.globalObj = window.globalObj
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleSelectMode(mode) {
|
||||
if (mode.startsWith('overwrite') && this.isOverwrite) mode += '_full'
|
||||
window.eventHub.$emit(eventSyncName.send_sync_list, {
|
||||
action: 'selectMode',
|
||||
data: mode,
|
||||
})
|
||||
this.handleClose()
|
||||
},
|
||||
handleClose() {
|
||||
this.globalObj.sync.isShowSyncMode = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 700px;
|
||||
min-width: 200px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
padding: 15px 0 5px;
|
||||
padding-right: 5px;
|
||||
.btnGroup + .btnGroup {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.label {
|
||||
color: @color-theme_2-font-label;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
.desc {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.tipGroup {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 12px;
|
||||
|
||||
+ .tipGroup {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
+.btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.name {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import keyBind from '../utils/keyBind'
|
||||
import { rendererOn, rendererSend, NAMES, rendererInvoke } from '../../common/ipc'
|
||||
import { base as baseName } from './names'
|
||||
import { base as baseName, sync as syncName } from './names'
|
||||
import { common as hotKeyNamesCommon } from '../../common/hotKey'
|
||||
|
||||
const eventHub = window.eventHub = new Vue()
|
||||
|
@ -77,3 +77,18 @@ rendererOn(NAMES.mainWindow.set_hot_key_config, (event, config) => {
|
|||
}
|
||||
window.eventHub.$emit(baseName.set_hot_key_config, config)
|
||||
})
|
||||
|
||||
rendererOn(NAMES.mainWindow.sync_action_list, (event, { action, data }) => {
|
||||
window.eventHub.$emit(syncName.handle_action_list, { action, data })
|
||||
})
|
||||
eventHub.$on(syncName.send_action_list, ({ action, data }) => {
|
||||
if (!window.globalObj.sync.enable) return
|
||||
rendererSend(NAMES.mainWindow.sync_action_list, { action, data })
|
||||
})
|
||||
rendererOn(NAMES.mainWindow.sync_list, (event, { action, data }) => {
|
||||
window.eventHub.$emit(syncName.handle_sync_list, { action, data })
|
||||
})
|
||||
eventHub.$on(syncName.send_sync_list, ({ action, data }) => {
|
||||
if (!window.globalObj.sync.enable) return
|
||||
rendererSend(NAMES.mainWindow.sync_list, { action, data })
|
||||
})
|
||||
|
|
|
@ -10,6 +10,12 @@ const names = {
|
|||
set_config: 'set_config',
|
||||
set_hot_key_config: 'set_hot_key_config',
|
||||
},
|
||||
sync: {
|
||||
send_action_list: 'send_action_list',
|
||||
handle_action_list: 'handle_action_list',
|
||||
send_sync_list: 'send_sync_list',
|
||||
handle_sync_list: 'handle_sync_list',
|
||||
},
|
||||
}
|
||||
|
||||
for (const item of Object.keys(names)) {
|
||||
|
@ -20,3 +26,4 @@ for (const item of Object.keys(names)) {
|
|||
}
|
||||
|
||||
export const base = names.base
|
||||
export const sync = names.sync
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"merge_btn_local_remote": "Local list merge remote list",
|
||||
"merge_btn_remote_local": "Remote list merge local list",
|
||||
"merge_label": "Merge",
|
||||
"merge_tip": "Merge:",
|
||||
"merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
|
||||
"other_label": "Other",
|
||||
"other_tip": "Other: ",
|
||||
"other_tip_desc": "\"Only use real-time synchronization function\" will not modify the lists of both parties, only real-time synchronization operations; \"Cancel synchronization\" will directly disconnect the two parties.",
|
||||
"overwrite": "Full coverage",
|
||||
"overwrite_btn_cancel": "Cancel sync",
|
||||
"overwrite_btn_local_remote": "Local list Overwrite remote list",
|
||||
"overwrite_btn_none": "Only use real-time synchronization",
|
||||
"overwrite_btn_remote_local": "Remote list Overwrite local list",
|
||||
"overwrite_label": "Cover",
|
||||
"overwrite_tip": "Cover: ",
|
||||
"overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
|
||||
"title": "Choose how to synchronize the list with {name}"
|
||||
}
|
|
@ -130,6 +130,14 @@
|
|||
"search_focus_search_box": "Automatically focus the search box on startup",
|
||||
"search_history": "Search history",
|
||||
"search_hot": "Top Searches",
|
||||
"sync": "Data synchronization [This is a test function, it is recommended to back up the playlist before using it for the first time]",
|
||||
"sync_address": "Synchronization service address: {address}",
|
||||
"sync_auth_code": "Connection code: {code}",
|
||||
"sync_device": "Connected devices: {devices}",
|
||||
"sync_enable": "Enable the synchronization function (because the data is transmitted in clear text, please use it under a trusted network)",
|
||||
"sync_port": "Sync port settings",
|
||||
"sync_port_tip": "Please enter the synchronization service port number",
|
||||
"sync_refresh_code": "Refresh the connection code",
|
||||
"update": "Update",
|
||||
"update_checking": "Checking for updates...",
|
||||
"update_current_label": "Current version: ",
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"merge_btn_local_remote": "本机列表 合并 远程列表",
|
||||
"merge_btn_remote_local": "远程列表 合并 本机列表",
|
||||
"merge_label": "合并",
|
||||
"merge_tip": "合并:",
|
||||
"merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
|
||||
"other_label": "其他",
|
||||
"other_tip": "其他:",
|
||||
"other_tip_desc": "“仅使用实时同步功能”将不修改双方的列表,仅实时同步操作;“取消同步”将直接断开双方的连接。",
|
||||
"overwrite": "完全覆盖",
|
||||
"overwrite_btn_cancel": "取消同步",
|
||||
"overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
|
||||
"overwrite_btn_none": "仅使用实时同步功能",
|
||||
"overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
|
||||
"overwrite_label": "覆盖",
|
||||
"overwrite_tip": "覆盖:",
|
||||
"overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表(列表ID不同的列表将被合并到一起),若勾选完全覆盖,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
|
||||
"title": "选择与 {name} 的列表同步方式"
|
||||
}
|
|
@ -130,6 +130,14 @@
|
|||
"search_focus_search_box": "启动时自动聚焦搜索框",
|
||||
"search_history": "显示历史搜索记录",
|
||||
"search_hot": "显示热门搜索",
|
||||
"sync": "数据同步 [此为测试功能,首次使用前建议先备份一次歌单]",
|
||||
"sync_address": "同步服务地址:{address}",
|
||||
"sync_auth_code": "连接码:{code}",
|
||||
"sync_device": "已连接的设备:{devices}",
|
||||
"sync_enable": "启用同步功能(由于数据是明文传输,请在受信任的网络下使用)",
|
||||
"sync_port": "同步端口设置",
|
||||
"sync_port_tip": "请输入同步服务端口号",
|
||||
"sync_refresh_code": "刷新连接码",
|
||||
"update": "软件更新",
|
||||
"update_checking": "检查更新中...",
|
||||
"update_current_label": "当前版本:",
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"merge_btn_local_remote": "本機列表 合併 遠程列表",
|
||||
"merge_btn_remote_local": "遠程列表 合併 本機列表",
|
||||
"merge_label": "合併",
|
||||
"merge_tip": "合併:",
|
||||
"merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
|
||||
"other_label": "其他",
|
||||
"other_tip": "其他:",
|
||||
"other_tip_desc": "“僅使用實時同步功能”將不修改雙方的列表,僅實時同步操作;“取消同步”將直接斷開雙方的連接。",
|
||||
"overwrite": "完全覆蓋",
|
||||
"overwrite_btn_cancel": "取消同步",
|
||||
"overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表",
|
||||
"overwrite_btn_none": "僅使用實時同步功能",
|
||||
"overwrite_btn_remote_local": "遠程列表 覆蓋 本機列表",
|
||||
"overwrite_label": "覆蓋",
|
||||
"overwrite_tip": "覆蓋:",
|
||||
"overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表(列表ID不同的列表將被合併到一起),若勾選完全覆蓋,則被覆蓋者的所有列表將被移除,然後替換成覆蓋者的列表。",
|
||||
"title": "選擇與 {name} 的列表同步方式"
|
||||
}
|
|
@ -130,6 +130,14 @@
|
|||
"search_focus_search_box": "啟動時自動聚焦搜索框",
|
||||
"search_history": "顯示歷史搜索記錄",
|
||||
"search_hot": "顯示熱門搜索",
|
||||
"sync": "數據同步 [此為測試功能,首次使用前建議先備份一次歌單]",
|
||||
"sync_address": "同步服務地址:{address}",
|
||||
"sync_auth_code": "連接碼:{code}",
|
||||
"sync_device": "已連接的設備:{devices}",
|
||||
"sync_enable": "啟用同步功能(由於數據是明文傳輸,請在受信任的網絡下使用)",
|
||||
"sync_port": "同步端口設置",
|
||||
"sync_port_tip": "請輸入同步服務端口號",
|
||||
"sync_refresh_code": "刷新連接碼",
|
||||
"update": "軟件更新",
|
||||
"update_checking": "檢查更新中...",
|
||||
"update_current_label": "當前版本:",
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import musicSdk from '../../utils/music'
|
||||
import { clearLyric, clearMusicUrl } from '../../utils'
|
||||
import { sync as eventSyncName } from '@renderer/event/names'
|
||||
|
||||
let allList = {}
|
||||
window.allList = allList
|
||||
|
||||
const allListInit = (defaultList, loveList, userList) => {
|
||||
for (const id of Object.keys(allList)) {
|
||||
delete allList[id]
|
||||
}
|
||||
allList[defaultList.id] = defaultList
|
||||
allList[loveList.id] = loveList
|
||||
for (const list of userList) allList[list.id] = list
|
||||
|
@ -67,8 +71,28 @@ const mutations = {
|
|||
if (userList != null) state.userList = userList
|
||||
allListInit(state.defaultList, state.loveList, state.userList)
|
||||
state.isInitedList = true
|
||||
|
||||
// if (!isSync) {
|
||||
// window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
// action: 'init_list',
|
||||
// data: { defaultList, loveList, userList },
|
||||
// })
|
||||
// }
|
||||
},
|
||||
setList(state, { id, list, name, location, source, sourceListId }) {
|
||||
setSyncListData(state, { defaultList, loveList, userList }) {
|
||||
state.defaultList.list.splice(0, state.defaultList.list.length, ...defaultList.list)
|
||||
state.loveList.list.splice(0, state.loveList.list.length, ...loveList.list)
|
||||
state.userList = userList
|
||||
allListInit(state.defaultList, state.loveList, state.userList)
|
||||
},
|
||||
setList(state, { id, list, name, location, source, sourceListId, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_list',
|
||||
data: { id, list, name, location, source, sourceListId },
|
||||
})
|
||||
}
|
||||
|
||||
const targetList = allList[id]
|
||||
if (targetList) {
|
||||
if (name && targetList.name === name) {
|
||||
|
@ -90,11 +114,20 @@ const mutations = {
|
|||
state.userList.push(newList)
|
||||
allListUpdate(newList)
|
||||
},
|
||||
listAdd(state, { id, musicInfo }) {
|
||||
listAdd(state, { id, musicInfo, addMusicLocationType, isSync }) {
|
||||
if (!addMusicLocationType) addMusicLocationType = this.state.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_add',
|
||||
data: { id, musicInfo, addMusicLocationType },
|
||||
})
|
||||
}
|
||||
|
||||
const targetList = allList[id]
|
||||
if (!targetList) return
|
||||
if (targetList.list.some(s => s.songmid === musicInfo.songmid)) return
|
||||
switch (this.state.setting.list.addMusicLocationType) {
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
targetList.list.unshift(musicInfo)
|
||||
break
|
||||
|
@ -104,11 +137,18 @@ const mutations = {
|
|||
break
|
||||
}
|
||||
},
|
||||
listMove(state, { fromId, musicInfo, toId }) {
|
||||
listMove(state, { fromId, musicInfo, toId, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_move',
|
||||
data: { fromId, musicInfo, toId },
|
||||
})
|
||||
}
|
||||
|
||||
const fromList = allList[fromId]
|
||||
const toList = allList[toId]
|
||||
if (!fromList || !toList) return
|
||||
fromList.list.splice(fromList.list.indexOf(musicInfo), 1)
|
||||
fromList.list.splice(fromList.list.findIndex(s => s.songmid === musicInfo.songmid), 1)
|
||||
let index = toList.list.findIndex(s => s.songmid === musicInfo.songmid)
|
||||
if (index < 0) {
|
||||
switch (this.state.setting.list.addMusicLocationType) {
|
||||
|
@ -122,41 +162,79 @@ const mutations = {
|
|||
}
|
||||
}
|
||||
},
|
||||
listAddMultiple(state, { id, list }) {
|
||||
listAddMultiple(state, { id, list, addMusicLocationType, isSync }) {
|
||||
if (!addMusicLocationType) addMusicLocationType = this.state.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_add_multiple',
|
||||
data: { id, list, addMusicLocationType },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
let newList
|
||||
switch (this.state.setting.list.addMusicLocationType) {
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...list, ...targetList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...targetList.list, ...list]
|
||||
break
|
||||
}
|
||||
let map = {}
|
||||
let ids = []
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
targetList.list.splice(0, targetList.list.length, ...ids.map(id => map[id]))
|
||||
},
|
||||
// { fromId, toId, list }
|
||||
listMoveMultiple(state, { fromId, toId, list }) {
|
||||
listMoveMultiple(state, { fromId, toId, list, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_move_multiple',
|
||||
data: { fromId, toId, list },
|
||||
})
|
||||
}
|
||||
|
||||
// console.log(state.commit)
|
||||
this.commit('list/listRemoveMultiple', { id: fromId, list })
|
||||
this.commit('list/listAddMultiple', { id: toId, list })
|
||||
this.commit('list/listRemoveMultiple', { listId: fromId, ids: list.map(s => s.songmid), isSync: true })
|
||||
this.commit('list/listAddMultiple', { id: toId, list, isSync: true })
|
||||
},
|
||||
listRemove(state, { id, index }) {
|
||||
let targetList = allList[id]
|
||||
listRemove(state, { listId, id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_remove',
|
||||
data: { listId, id },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) return
|
||||
const index = targetList.list.findIndex(item => item.songmid == id)
|
||||
if (index < 0) return
|
||||
targetList.list.splice(index, 1)
|
||||
},
|
||||
listRemoveMultiple(state, { id, list }) {
|
||||
let targetList = allList[id]
|
||||
listRemoveMultiple(state, { listId, ids: musicIds, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_remove_multiple',
|
||||
data: { listId, ids: musicIds },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) return
|
||||
let map = {}
|
||||
let ids = []
|
||||
|
@ -164,25 +242,50 @@ const mutations = {
|
|||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
for (const item of list) {
|
||||
if (map[item.songmid]) delete map[item.songmid]
|
||||
for (const songmid of musicIds) {
|
||||
if (map[songmid]) delete map[songmid]
|
||||
}
|
||||
let newList = []
|
||||
for (const id of ids) if (map[id]) newList.push(map[id])
|
||||
|
||||
targetList.list.splice(0, targetList.list.length, ...newList)
|
||||
},
|
||||
listClear(state, id) {
|
||||
listClear(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_clear',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
targetList.list.splice(0, targetList.list.length)
|
||||
},
|
||||
updateMusicInfo(state, { id, index, data, musicInfo = {} }) {
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return Object.assign(musicInfo, data)
|
||||
Object.assign(targetList.list[index], data)
|
||||
updateMusicInfo(state, { listId, id, data, musicInfo, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'update_music_info',
|
||||
data: { listId, id, data, musicInfo },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) {
|
||||
if (musicInfo) Object.assign(musicInfo, data)
|
||||
return
|
||||
}
|
||||
const targetMusicInfo = targetList.list.find(item => item.songmid == id)
|
||||
if (targetMusicInfo) Object.assign(targetMusicInfo, data)
|
||||
},
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId }) {
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'create_user_list',
|
||||
data: { name, id, list, source, sourceListId },
|
||||
})
|
||||
}
|
||||
|
||||
let newList = state.userList.find(item => item.id === id)
|
||||
if (!newList) {
|
||||
newList = {
|
||||
|
@ -196,35 +299,75 @@ const mutations = {
|
|||
state.userList.push(newList)
|
||||
allListUpdate(newList)
|
||||
}
|
||||
this.commit('list/listAddMultiple', { id, list })
|
||||
this.commit('list/listAddMultiple', { id, list, isSync: true })
|
||||
},
|
||||
removeUserList(state, index) {
|
||||
removeUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'remove_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
const index = state.userList.findIndex(l => l.id === id)
|
||||
if (index < 0) return
|
||||
let list = state.userList.splice(index, 1)[0]
|
||||
allListRemove(list)
|
||||
},
|
||||
setUserListName(state, { index, name }) {
|
||||
let list = state.userList[index]
|
||||
setUserListName(state, { id, name, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_user_list_name',
|
||||
data: { id, name },
|
||||
})
|
||||
}
|
||||
|
||||
let list = allList[id]
|
||||
if (!list) return
|
||||
list.name = name
|
||||
},
|
||||
moveupUserList(state, index) {
|
||||
let targetList = state.userList[index]
|
||||
moveupUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'moveup_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
const index = state.userList.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
let targetList = allList[id]
|
||||
state.userList.splice(index, 1)
|
||||
state.userList.splice(index - 1, 0, targetList)
|
||||
},
|
||||
movedownUserList(state, index) {
|
||||
let targetList = state.userList[index]
|
||||
movedownUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'movedown_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
const index = state.userList.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
let targetList = allList[id]
|
||||
state.userList.splice(index, 1)
|
||||
state.userList.splice(index + 1, 0, targetList)
|
||||
},
|
||||
setListScroll(state, { id, location }) {
|
||||
if (allList[id]) allList[id].location = location
|
||||
},
|
||||
sortList(state, { id, sortNum, musicInfos }) {
|
||||
let targetList = allList[id]
|
||||
this.commit('list/listRemoveMultiple', { id, list: musicInfos })
|
||||
setMusicPosition(state, { id, position, list, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_music_position',
|
||||
data: { id, position, list },
|
||||
})
|
||||
}
|
||||
|
||||
targetList.list.splice(sortNum - 1, 0, ...musicInfos)
|
||||
let targetList = allList[id]
|
||||
this.commit('list/listRemoveMultiple', { listId: id, ids: list.map(m => m.songmid), isSync: true })
|
||||
|
||||
targetList.list.splice(position - 1, 0, ...list)
|
||||
},
|
||||
clearCache() {
|
||||
const lists = Object.values(allList)
|
||||
|
|
|
@ -141,6 +141,7 @@ const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
|||
})
|
||||
}
|
||||
|
||||
let prevPlayIndex
|
||||
// getters
|
||||
const getters = {
|
||||
list: state => state.listInfo.list,
|
||||
|
@ -156,13 +157,14 @@ const getters = {
|
|||
|
||||
if (listId != '__temp__') {
|
||||
if (isPlayList) {
|
||||
playIndex = state.listInfo.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
playIndex = state.listInfo.list.findIndex(m => m.songmid == state.playMusicInfo.musicInfo.songmid)
|
||||
if (!isTempPlay) listPlayIndex = playIndex
|
||||
} else {
|
||||
let list = window.allList[listId]
|
||||
if (list) playIndex = list.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
if (list) playIndex = list.list.findIndex(m => m.songmid == state.playMusicInfo.musicInfo.songmid)
|
||||
}
|
||||
}
|
||||
if (listPlayIndex > -1) prevPlayIndex = listPlayIndex
|
||||
// console.log({
|
||||
// listId,
|
||||
// playIndex,
|
||||
|
@ -170,7 +172,7 @@ const getters = {
|
|||
// listPlayIndex,
|
||||
// isPlayList,
|
||||
// isTempPlay,
|
||||
// musicInfo: state.playMusicInfo.musicInfo,
|
||||
// // musicInfo: state.playMusicInfo.musicInfo,
|
||||
// })
|
||||
return {
|
||||
listId,
|
||||
|
@ -250,9 +252,9 @@ const actions = {
|
|||
if (state.playedList.length) {
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = state.playedList.indexOf(state.playMusicInfo) - 1; index > -1; index--) {
|
||||
for (index = state.playedList.findIndex(m => m.songmid === state.playMusicInfo.musicInfo.songmid) - 1; index > -1; index--) {
|
||||
const playMusicInfo = state.playedList[index]
|
||||
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => m.songmid === playMusicInfo.musicInfo.songmid)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
|
@ -273,7 +275,19 @@ const actions = {
|
|||
})
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', null)
|
||||
const playInfo = getters.playInfo
|
||||
let currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
let currentMusic
|
||||
if (playInfo.listPlayIndex < 0) {
|
||||
let index = prevPlayIndex
|
||||
if (index > currentList.length - 1) index = 0
|
||||
while (index > -1) {
|
||||
currentMusic = currentList[index]
|
||||
if (currentMusic) break
|
||||
index--
|
||||
}
|
||||
} else {
|
||||
currentMusic = currentList[playInfo.listPlayIndex]
|
||||
}
|
||||
let currentIndex = filteredList.findIndex(m => m.songmid == currentMusic.songmid)
|
||||
if (currentIndex == -1) currentIndex = 0
|
||||
let nextIndex = currentIndex
|
||||
if (!playInfo.isTempPlay) {
|
||||
|
@ -311,9 +325,9 @@ const actions = {
|
|||
if (state.playedList.length) {
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = state.playedList.indexOf(state.playMusicInfo) + 1; index < state.playedList.length; index++) {
|
||||
for (index = state.playedList.findIndex(m => m.songmid === state.playMusicInfo.musicInfo.songmid) + 1; index < state.playedList.length; index++) {
|
||||
const playMusicInfo = state.playedList[index]
|
||||
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => m.songmid === playMusicInfo.musicInfo.songmid)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
|
@ -334,7 +348,19 @@ const actions = {
|
|||
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', null)
|
||||
const playInfo = getters.playInfo
|
||||
const currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
let currentMusic
|
||||
if (playInfo.listPlayIndex < 0) {
|
||||
let index = prevPlayIndex - 1
|
||||
if (index < 0) index = currentList.length - 1
|
||||
while (index > -1) {
|
||||
currentMusic = currentList[index]
|
||||
if (currentMusic) break
|
||||
index--
|
||||
}
|
||||
} else {
|
||||
currentMusic = currentList[playInfo.listPlayIndex]
|
||||
}
|
||||
let currentIndex = filteredList.findIndex(m => m.songmid == currentMusic.songmid)
|
||||
let nextIndex = currentIndex
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
|
@ -433,7 +459,7 @@ const mutations = {
|
|||
playIndex = -1
|
||||
} else {
|
||||
let listId = playMusicInfo.listId
|
||||
if (listId != '__temp__' && !playMusicInfo.isTempPlay && listId === state.listInfo.id) playIndex = state.listInfo.list.indexOf(playMusicInfo.musicInfo)
|
||||
if (listId != '__temp__' && !playMusicInfo.isTempPlay && listId === state.listInfo.id) playIndex = state.listInfo.list.findIndex(m => m.songmid == playMusicInfo.musicInfo.songmid)
|
||||
}
|
||||
|
||||
state.playMusicInfo = playMusicInfo
|
||||
|
|
|
@ -165,7 +165,9 @@ export default {
|
|||
: result.body.err_code
|
||||
) !== 0)
|
||||
) return this.createHttp(url, options, ++retryNum)
|
||||
return result.body.data || result.body.info
|
||||
if (result.body.data) return result.body.data
|
||||
if (Array.isArray(result.body.info)) return result.body
|
||||
return result.body.info
|
||||
},
|
||||
|
||||
createTask(hashs) {
|
||||
|
@ -183,9 +185,9 @@ export default {
|
|||
let list = hashs
|
||||
let tasks = []
|
||||
while (list.length) {
|
||||
tasks.push(Object.assign({ data: list.slice(0, 20) }, data))
|
||||
if (list.length < 20) break
|
||||
list = list.slice(20)
|
||||
tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
|
||||
if (list.length < 100) break
|
||||
list = list.slice(100)
|
||||
}
|
||||
let url = 'http://kmr.service.kugou.com/v2/album_audio/audio'
|
||||
return tasks.map(task => this.createHttp(url, {
|
||||
|
@ -267,6 +269,68 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
deDuplication(datas) {
|
||||
let ids = new Set()
|
||||
return datas.filter(({ hash }) => {
|
||||
if (ids.has(hash)) return false
|
||||
ids.add(hash)
|
||||
return true
|
||||
})
|
||||
},
|
||||
|
||||
async getUserListDetailByLink({ info }, link) {
|
||||
let listInfo = info['0']
|
||||
let total = listInfo.count
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 90 ? 90 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp(link.replace(/pagesize=\d+/, 'pagesize=' + limit).replace(/page=\d+/, 'page=' + page), {
|
||||
headers: {
|
||||
'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',
|
||||
Referer: link,
|
||||
},
|
||||
}).then(data => data.list.info))
|
||||
}
|
||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
result = await Promise.all(this.createTask(this.deDuplication(result).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(result)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: this.formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
createGetListDetail2Task(id, total) {
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp('https://mobiles.kugou.com/api/v5/special/song_v2?appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&page=' + page + '&pagesize=' + limit + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163263991clientver=20000dfid=-global_specialid=' + id + 'mid=1586163263991page=' + page + 'pagesize=' + limit + 'plat=0specialid=0srcappid=2919uuid=1586163263991version=8000NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991',
|
||||
},
|
||||
}).then(data => data.info))
|
||||
}
|
||||
return Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
},
|
||||
async getUserListDetail2(global_collection_id) {
|
||||
let id = global_collection_id
|
||||
if (id.length > 1000) throw new Error('get list error')
|
||||
|
@ -279,22 +343,14 @@ export default {
|
|||
clienttime: '1586163242519',
|
||||
},
|
||||
})
|
||||
let songInfo = await this.createHttp('https://mobiles.kugou.com/api/v5/special/song_v2?appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&pagesize=' + info.songcount + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163263991clientver=20000dfid=-global_specialid=' + id + 'mid=1586163263991pagesize=' + info.songcount + 'plat=0specialid=0srcappid=2919uuid=1586163263991version=8000NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991',
|
||||
},
|
||||
})
|
||||
let result = await Promise.all(this.createTask(songInfo.info.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||
let result = await Promise.all(this.createTask(this.deDuplication(songInfo).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page: 1,
|
||||
limit: songInfo.total,
|
||||
total: songInfo.total,
|
||||
limit: this.listDetailLimit,
|
||||
total: info.songcount,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
|
@ -333,10 +389,9 @@ export default {
|
|||
// console.log(body, location)
|
||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||
if (location) {
|
||||
// console.log(location)
|
||||
if (location.includes('global_collection_id')) return this.getUserListDetail2(location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
||||
if (location.includes('chain=')) return this.getUserListDetail3(location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
|
||||
// console.log('location', location)
|
||||
if (location.includes('.html')) {
|
||||
if (location.includes('zlist.html')) {
|
||||
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
|
@ -348,27 +403,12 @@ export default {
|
|||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
} else return this.getUserListDetail3(location.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
// console.log('location', location)
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
}
|
||||
if (typeof body == 'string') return this.getUserListDetail2(body.replace(/^[\s\S]+?"global_collection_id":"(\w+)"[\s\S]+?$/, '$1'))
|
||||
if (body.errcode !== 0) return this.getUserListDetail(link, page, ++retryNum)
|
||||
let listInfo = body.info['0']
|
||||
let result = body.list.info.map(item => ({ hash: item.hash }))
|
||||
result = await Promise.all(this.createTask(result)).then(([...datas]) => datas.flat())
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: this.formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
|
||||
|
|
|
@ -355,7 +355,7 @@ export default {
|
|||
'removeUserList',
|
||||
'setListScroll',
|
||||
'setList',
|
||||
'sortList',
|
||||
'setMusicPosition',
|
||||
]),
|
||||
...mapActions('songList', ['getListDetailAll']),
|
||||
...mapActions('leaderboard', {
|
||||
|
@ -576,7 +576,7 @@ export default {
|
|||
this.setPlayList({ list: this.listData, index })
|
||||
},
|
||||
handleRemove(index) {
|
||||
this.listRemove({ id: this.listId, index })
|
||||
this.listRemove({ listId: this.listId, id: this.list[index].songmid })
|
||||
},
|
||||
handleListBtnClick(info) {
|
||||
switch (info.action) {
|
||||
|
@ -677,8 +677,9 @@ export default {
|
|||
let dom_target = this.$refs.dom_lists_list.querySelector('.' + this.$style.editing)
|
||||
if (dom_target) dom_target.classList.remove(this.$style.editing)
|
||||
let name = event.target.value.trim()
|
||||
if (name.length) return this.setUserListName({ index, name })
|
||||
event.target.value = this.userList[index].name
|
||||
const targetList = this.userList[index]
|
||||
if (name.length) return this.setUserListName({ id: targetList.id, name })
|
||||
event.target.value = targetList.name
|
||||
},
|
||||
handleListsCreate(event) {
|
||||
if (event.target.readonly) return
|
||||
|
@ -774,13 +775,13 @@ export default {
|
|||
this.handleSyncSourceList(index)
|
||||
break
|
||||
case 'moveup':
|
||||
this.moveupUserList(index)
|
||||
this.moveupUserList({ id: this.userList[index].id })
|
||||
break
|
||||
case 'movedown':
|
||||
this.movedownUserList(index)
|
||||
this.movedownUserList({ id: this.userList[index].id })
|
||||
break
|
||||
case 'remove':
|
||||
this.removeUserList(index)
|
||||
this.removeUserList({ id: this.userList[index].id })
|
||||
break
|
||||
}
|
||||
},
|
||||
|
@ -870,7 +871,7 @@ export default {
|
|||
break
|
||||
case 'remove':
|
||||
if (this.selectdListDetailData.length) {
|
||||
this.listRemoveMultiple({ id: this.listId, list: this.selectdListDetailData })
|
||||
this.listRemoveMultiple({ listId: this.listId, ids: this.selectdListDetailData.map(m => m.songmid) })
|
||||
this.removeAllSelectListDetail()
|
||||
} else {
|
||||
this.handleRemove(index)
|
||||
|
@ -929,10 +930,10 @@ export default {
|
|||
},
|
||||
handleSortMusicInfo(num) {
|
||||
num = Math.min(num, this.list.length)
|
||||
this.sortList({
|
||||
this.setMusicPosition({
|
||||
id: this.listId,
|
||||
sortNum: num,
|
||||
musicInfos: this.selectdListDetailData.length ? [...this.selectdListDetailData] : [this.musicInfo],
|
||||
position: num,
|
||||
list: this.selectdListDetailData.length ? [...this.selectdListDetailData] : [this.musicInfo],
|
||||
})
|
||||
this.removeAllSelectListDetail()
|
||||
this.isShowListSortModal = false
|
||||
|
|
|
@ -150,6 +150,21 @@ div(:class="$style.main")
|
|||
div
|
||||
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" :label="$t('view.setting.is_enable')")
|
||||
|
||||
dt#sync {{$t('view.setting.sync')}}
|
||||
dd
|
||||
material-checkbox(id="setting_sync_enable" v-model="current_setting.sync.enable" @change="handleSyncChange('enable')" :label="syncEnableTitle")
|
||||
div
|
||||
p.small {{$t('view.setting.sync_auth_code', { code: sync.status.code || '' })}}
|
||||
p.small {{$t('view.setting.sync_address', { address: sync.status.address.join(', ') || '' })}}
|
||||
p.small {{$t('view.setting.sync_device', { devices: syncDevices })}}
|
||||
p
|
||||
material-btn(:class="$style.btn" min :disabled="!sync.status.status" @click="handleRefreshSyncCode") {{$t('view.setting.sync_refresh_code')}}
|
||||
dd
|
||||
h3#sync_port {{$t('view.setting.sync_port')}}
|
||||
div
|
||||
p
|
||||
material-input(:class="$style.gapLeft" v-model.trim="current_setting.sync.port" @change="handleSyncChange('port')" :placeholder="$t('view.setting.sync_port_tip')")
|
||||
|
||||
dt#hot_key {{$t('view.setting.hot_key')}}
|
||||
dd
|
||||
h3#hot_key_local_title {{$t('view.setting.hot_key_local_title')}}
|
||||
|
@ -295,7 +310,7 @@ import {
|
|||
getSetting,
|
||||
saveSetting,
|
||||
} from '../utils'
|
||||
import { rendererSend, rendererInvoke, NAMES } from '@common/ipc'
|
||||
import { rendererSend, rendererInvoke, rendererOn, NAMES, rendererOff } from '@common/ipc'
|
||||
import { mergeSetting, isMac } from '../../common/utils'
|
||||
import apiSourceInfo from '../utils/music/api-source-info'
|
||||
import fs from 'fs'
|
||||
|
@ -410,6 +425,21 @@ export default {
|
|||
},
|
||||
]
|
||||
},
|
||||
syncEnableTitle() {
|
||||
let title = this.$t('view.setting.sync_enable')
|
||||
if (this.sync.status.message) {
|
||||
title += ` [${this.sync.status.message}]`
|
||||
}
|
||||
// else if (this.sync.status.address.length) {
|
||||
// // title += ` [${this.sync.status.address.join(', ')}]`
|
||||
// }
|
||||
return title
|
||||
},
|
||||
syncDevices() {
|
||||
return this.sync.status.devices.length
|
||||
? this.sync.status.devices.map(d => `${d.deviceName} (${d.clientId.substring(0, 5)})`).join(', ')
|
||||
: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -474,6 +504,10 @@ export default {
|
|||
isToTray: false,
|
||||
themeId: 0,
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
port: '23332',
|
||||
},
|
||||
windowSizeId: 1,
|
||||
langId: 'cns',
|
||||
themeId: 0,
|
||||
|
@ -608,6 +642,15 @@ export default {
|
|||
},
|
||||
isDisabledResourceCacheClear: false,
|
||||
isDisabledListCacheClear: false,
|
||||
sync: {
|
||||
status: {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
code: '',
|
||||
devices: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -665,6 +708,7 @@ export default {
|
|||
window.eventHub.$off(eventBaseName.set_config, this.handleUpdateSetting)
|
||||
window.eventHub.$off(eventBaseName.key_down, this.handleKeyDown)
|
||||
window.eventHub.$off(eventBaseName.set_hot_key_config, this.handleUpdateHotKeyConfig)
|
||||
this.syncUnInit()
|
||||
|
||||
if (this.current_setting.network.proxy.enable && !this.current_setting.network.proxy.host) window.globalObj.proxy.enable = false
|
||||
},
|
||||
|
@ -684,6 +728,7 @@ export default {
|
|||
this.current_hot_key = window.appHotKeyConfig
|
||||
this.initHotKeyConfig()
|
||||
this.getHotKeyStatus()
|
||||
this.syncInit()
|
||||
},
|
||||
// initTOC() {
|
||||
// const list = this.$refs.dom_setting_list.children
|
||||
|
@ -1151,6 +1196,42 @@ export default {
|
|||
|
||||
return status
|
||||
},
|
||||
setStatus(e, status) {
|
||||
this.sync.status.status = status.status
|
||||
this.sync.status.message = status.message
|
||||
this.sync.status.address = status.address
|
||||
this.sync.status.code = status.code
|
||||
this.sync.status.devices = status.devices
|
||||
},
|
||||
syncInit() {
|
||||
rendererInvoke(NAMES.mainWindow.sync_get_status).then(status => {
|
||||
this.sync.status.status = status.status
|
||||
this.sync.status.message = status.message
|
||||
this.sync.status.address = status.address
|
||||
this.sync.status.code = status.code
|
||||
this.sync.status.devices = status.devices
|
||||
})
|
||||
rendererOn(NAMES.mainWindow.sync_status, this.setStatus)
|
||||
},
|
||||
syncUnInit() {
|
||||
rendererOff(NAMES.mainWindow.sync_status, this.setStatus)
|
||||
},
|
||||
handleSyncChange(action) {
|
||||
switch (action) {
|
||||
case 'port':
|
||||
if (!this.current_setting.sync.enable) return
|
||||
case 'enable':
|
||||
rendererInvoke(NAMES.mainWindow.sync_enable, {
|
||||
enable: this.current_setting.sync.enable,
|
||||
port: this.current_setting.sync.port,
|
||||
})
|
||||
window.globalObj.sync.enable = this.current_setting.sync.enable
|
||||
break
|
||||
}
|
||||
},
|
||||
handleRefreshSyncCode() {
|
||||
rendererInvoke(NAMES.mainWindow.sync_generate_code)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue