Merge branch 'dev'
commit
1b4c910264
|
@ -16,7 +16,7 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
|
@ -107,7 +107,7 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
|
@ -158,7 +158,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
@ -166,7 +166,7 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
|
@ -104,7 +104,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
@ -112,7 +112,7 @@ jobs:
|
|||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
|
|
39
CHANGELOG.md
39
CHANGELOG.md
|
@ -6,6 +6,45 @@ 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.20.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.19.0...v1.20.0) - 2022-04-17
|
||||
|
||||
特别说明:Scheme URL其实是支持Linux系统的,但好像需要deb之类的安装包创建出`.desktop`文件才行。
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增播放详情页歌词右键菜单,原来设置-播放详情页设置的字体重置已迁移到此菜单内
|
||||
- 新增歌词偏移设置,可以在播放详情页歌词右键菜单中使用
|
||||
- 新增设置-播放设置-播放错误时自动切换歌曲设置,默认开启(原来的行为),若你不想在遇到音频加载失败、url获取失败等错误时自动切歌可以关闭此设置
|
||||
- 新增设置-桌面歌词设置-自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)
|
||||
- 新增列表更新管理,可以在鼠标移入“我的列表”标题时出现的按钮中进入,这可以用来设置启动软件时需要自动从原平台更新的列表
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化播放详情页背景显示,现在有背景图片的主题可以在播放详情页显示它的图片了
|
||||
- 播放详情页在全屏状态下仍会显示退出播放详情页按钮,同时在其旁边添加退出全屏按钮
|
||||
- 播放详情页在全屏状态下鼠标在空白处静止不动3秒后自动将其隐藏
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复Linux无法全屏的问题
|
||||
- 修复播放下载列表的歌曲时,使用Windows任务栏缩略图工具栏控制按钮的收藏按钮收藏歌曲时的异常问题
|
||||
- 修复启用搜索历史但不启用热门搜索时,搜索历史不显示的问题
|
||||
- 修复窗口尺寸设置对应的字体大小在启动后不生效的问题
|
||||
- 修复wy源搜索某些歌曲时第一页之后的歌曲无法加载的问题
|
||||
- 修复使用Scheme URL搜索歌曲时,不会自动关闭播放详情页(若处于打开状态)的问题
|
||||
- 修复换源失败时的处理问题
|
||||
- 修复启用代理时,https请求可能被挂起或被转为http的问题
|
||||
- 修复正在下载的歌曲暂停任务后,再开始会导致程序卡死的问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 播放详情页的任意地方右键双击隐藏详情页的行为,“任意区域”改为在“非歌词区域”
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除设置-播放详情页设置-歌词字体重置,此设置项已迁移到播放详情页的歌词菜单中
|
||||
- 移除播放详情页使用+-快捷键调整字体大小的功能,改用歌词右键菜单的字体大小调整功能
|
||||
|
||||
## [1.19.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.18.0...v1.19.0) - 2022-03-20
|
||||
|
||||
### 新增
|
||||
|
|
7
FAQ.md
7
FAQ.md
|
@ -8,8 +8,8 @@
|
|||
|
||||
## 音乐播放列表机制
|
||||
|
||||
1. 默认情况下,播放搜索列表、歌单列表、排行榜列表的歌曲时会自动将该歌曲添加到“我的列表”的试听列表后再播放,手动将歌曲添加到试听列表,再去试听列表找到这首歌点播放是等价的
|
||||
2. 如果你想要播放多首歌曲,需要使用多选功能(若不知道如何多选请看常见问题)多选后,将歌曲这些歌曲添加到“我的列表”播放,或使用稍后播放功能播放
|
||||
1. 默认情况下,播放搜索列表、歌单列表、排行榜列表的歌曲时会自动将该歌曲添加到“我的列表”的试听列表后再播放,这与手动将歌曲添加到试听列表,再去试听列表找到这首歌点播放是等价的
|
||||
2. 如果你想要播放多首歌曲,需要使用多选功能(若不知道如何多选请看常见问题)多选后,将这些歌曲添加到“我的列表”播放,或使用稍后播放功能播放
|
||||
3. 第2条适用于搜索列表、歌单列表、排行榜列表、我的列表中的歌曲
|
||||
4. 对于歌单详情列表,除了可以使用第2条的方式播放外,你可以点击详情页上面的播放按钮临时播放当前歌单,或点击收藏将当前歌单收藏到“我的列表”后再去播放
|
||||
5. 对于排行榜详情列表,除了可以使用第2条的方式播放外,你可以在右击排行榜名字后弹出的菜单中,播放或收藏整个排行榜,这与第四条的歌单中的播放、与收藏按钮功能一致
|
||||
|
@ -33,6 +33,8 @@
|
|||
- 编辑列表名时按`Esc`键可以取消编辑
|
||||
- 按`F11`可以进入、退出全屏状态(v1.19.0新增)
|
||||
|
||||
注:在macOS上`Ctrl`键对应`Command`键
|
||||
|
||||
## 歌曲无法试听与下载
|
||||
|
||||
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`
|
||||
|
@ -334,7 +336,6 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
|||
以下是目前可用的Scheme URL调用方式:
|
||||
|
||||
- URL统一以`lxmusic://`开头
|
||||
- 此技术目前只支持 Windows、Mac系统
|
||||
- 若无特别说明,源的可用值为:`kw/kg/tx/wy/mg`
|
||||
- 若无特别说明,音质的可用值为:`128k/320k/flac/flac32bit`
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
从v1.17.0起支持 Scheme URL,可以使用此功能从浏览器等场景下调用LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
|
||||
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br>
|
||||
|
||||
若你想自己调用LX Music,可以看常见问题[Scheme URL支持](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#scheme-url%E6%94%AF%E6%8C%81)部分说明
|
||||
若你想自己调用LX Music,可以看[Scheme URL支持](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#scheme-url%E6%94%AF%E6%8C%81)
|
||||
|
||||
#### 启动参数
|
||||
|
||||
|
@ -70,7 +70,7 @@
|
|||
- `-dt` 以非透明模式启动(Disable Transparent)
|
||||
- `-dhmkh` 禁用硬件媒体密钥处理(Disable Hardware Media Key Handling)
|
||||
|
||||
启动参数的详细说明请看[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0)
|
||||
启动参数的详细说明请看[启动参数说明](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0)
|
||||
|
||||
#### 数据存储路径
|
||||
|
||||
|
@ -84,7 +84,7 @@
|
|||
|
||||
### 源码使用方法
|
||||
|
||||
环境要求:Node.js 14+
|
||||
环境要求:Node.js 16+
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
|
|
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "1.19.0",
|
||||
"version": "1.20.0",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
|
@ -73,7 +73,8 @@
|
|||
"Electron 13.6.8"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 16",
|
||||
"npm": ">=8.3.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "cn.toside.music.desktop",
|
||||
|
@ -103,7 +104,11 @@
|
|||
},
|
||||
"mac": {
|
||||
"icon": "./resources/icons/icon.icns",
|
||||
"category": "public.app-category.music"
|
||||
"category": "public.app-category.music",
|
||||
"extendInfo": {
|
||||
"CFBundleName": "lx-music-desktop",
|
||||
"CFBundleDisplayName": "LX Music"
|
||||
}
|
||||
},
|
||||
"linux": {
|
||||
"maintainer": "lyswhut <lyswhut@qq.com>",
|
||||
|
@ -173,7 +178,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/core": "^7.17.9",
|
||||
"@babel/eslint-parser": "^7.17.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
|
@ -181,33 +186,31 @@
|
|||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-loader": "^8.2.4",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"browserslist": "^4.20.2",
|
||||
"cfonts": "^2.10.0",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^2.8.1",
|
||||
"copy-webpack-plugin": "^10.2.4",
|
||||
"core-js": "^3.21.1",
|
||||
"core-js": "^3.22.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^13.6.9",
|
||||
"electron-builder": "^23.0.2",
|
||||
"electron-builder": "^23.0.6",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.4.88",
|
||||
"electron-updater": "^5.0.0",
|
||||
"eslint": "^8.11.0",
|
||||
"electron-to-chromium": "^1.4.111",
|
||||
"electron-updater": "^5.0.2",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-formatter-friendly": "git+https://github.com/lyswhut/eslint-friendly-formatter.git#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||
"eslint-plugin-html": "^6.2.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"eslint-webpack-plugin": "^3.1.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
|
@ -232,10 +235,10 @@
|
|||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^17.0.0",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4",
|
||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-dev-server": "^4.8.1",
|
||||
"webpack-hot-middleware": "git+https://github.com/lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -243,22 +246,29 @@
|
|||
"crypto-js": "^4.1.1",
|
||||
"electron-log": "^4.4.6",
|
||||
"electron-store": "^8.0.1",
|
||||
"font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
|
||||
"font-list": "git+https://github.com/lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
|
||||
"http-terminator": "^3.2.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.1",
|
||||
"koa": "^2.13.4",
|
||||
"long": "^5.2.0",
|
||||
"mitt": "^3.0.0",
|
||||
"needle": "^3.0.0",
|
||||
"needle": "^3.1.0",
|
||||
"node-id3": "^0.2.3",
|
||||
"request": "^2.88.2",
|
||||
"socket.io": "^4.4.1",
|
||||
"sortablejs": "^1.14.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"utf-8-validate": "^5.0.9",
|
||||
"vue": "^3.2.31",
|
||||
"vue-i18n": "^9.2.0-beta.32",
|
||||
"vue": "^3.2.33",
|
||||
"vue-i18n": "^9.2.0-beta.35",
|
||||
"vue-router": "^4.0.14",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"async": "^2.3.0",
|
||||
"svg-sprite-loader": {
|
||||
"postcss": "8.2.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,36 @@
|
|||
特别说明:Scheme URL其实是支持Linux系统的,但好像需要deb之类的安装包创建出`.desktop`文件才行。
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增对播放详情页歌词大小、是否缩放、对齐方式的设置,可以去设置-播放详情页设置查看
|
||||
- 新增播放详情页通过歌词调整播放进度,默认关闭,需要到设置-播放详情页设置开启,开启后在播放详情页拖动歌词时将会出现跳转当前行歌词播放的按钮
|
||||
- 新增全屏状态,按F11可以进入、退出全屏状态,由于全屏时会隐藏控制栏按钮,所以需要使用鼠标右键双击(详情页的任意地方都可以)来关闭播放详情页
|
||||
- 新增动态主题“道法自然”,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。注:鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。
|
||||
- 新增对kw源卡拉OK歌词的支持
|
||||
- 新增播放详情页歌词右键菜单,原来设置-播放详情页设置的字体重置已迁移到此菜单内
|
||||
- 新增歌词偏移设置,可以在播放详情页歌词右键菜单中使用
|
||||
- 新增设置-播放设置-播放错误时自动切换歌曲设置,默认开启(原来的行为),若你不想在遇到音频加载失败、url获取失败等错误时自动切歌可以关闭此设置
|
||||
- 新增设置-桌面歌词设置-自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)
|
||||
- 新增列表更新管理,可以在鼠标移入“我的列表”标题时出现的按钮中进入,这可以用来设置启动软件时需要自动从原平台更新的列表
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化Windows任务栏缩略图工具栏控制按钮在浅色任务栏下的显示效果
|
||||
- 添加音频可视化与音频输出设备冲突的提示
|
||||
- 优化歌词的播放偏移
|
||||
- 优化托盘菜单操作(#686)
|
||||
- 优化播放下载列表时的切歌性能
|
||||
- 优化播放详情页背景显示,现在有背景图片的主题可以在播放详情页显示它的图片了
|
||||
- 播放详情页在全屏状态下仍会显示退出播放详情页按钮,同时在其旁边添加退出全屏按钮
|
||||
- 播放详情页在全屏状态下鼠标在空白处静止不动3秒后自动将其隐藏
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复“当前的声音输出设备被改变时暂停播放歌曲”设置无效的问题
|
||||
- 修复桌面歌词没有处理停止播放状态的问题
|
||||
- 修复AppImage包无法运行的问题
|
||||
- 修复Windows任务栏缩略图工具栏控制按钮的歌曲收藏按钮状态更新问题
|
||||
- 修复使用链接导入的歌单无法在我的列表打开原歌单详情页的问题
|
||||
- 修复播放下载列表的歌曲时增删下载任务导致正在的歌曲序号改变时,不会更新到新增序号的问题
|
||||
- 修复Linux无法全屏的问题
|
||||
- 修复播放下载列表的歌曲时,使用Windows任务栏缩略图工具栏控制按钮的收藏按钮收藏歌曲时的异常问题
|
||||
- 修复启用搜索历史但不启用热门搜索时,搜索历史不显示的问题
|
||||
- 修复窗口尺寸设置对应的字体大小在启动后不生效的问题
|
||||
- 修复wy源搜索某些歌曲时第一页之后的歌曲无法加载的问题
|
||||
- 修复使用Scheme URL搜索歌曲时,不会自动关闭播放详情页(若处于打开状态)的问题
|
||||
- 修复换源失败时的处理问题
|
||||
- 修复启用代理时,https请求可能被挂起或被转为http的问题
|
||||
- 修复正在下载的歌曲暂停任务后,再开始会导致程序卡死的问题
|
||||
|
||||
### 文档
|
||||
### 变更
|
||||
|
||||
添加LX中定义的快捷操作汇总说明到常见问题中,这是目前可用的鼠标、键盘快捷操作,它们都可以在更新日志中找到
|
||||
- 播放详情页的任意地方右键双击隐藏详情页的行为,“任意区域”改为在“非歌词区域”
|
||||
|
||||
- 鼠标右击播放栏的歌曲图片封面可以定位当前播放的歌曲
|
||||
- 鼠标右击播放栏进度条上的LRC按钮可以锁定/解锁桌面歌词
|
||||
- 歌曲搜索框、歌单链接输入框内鼠标右击可以将当前剪贴板上的文字粘贴到输入框内
|
||||
- 鼠标右击搜索界面中的单条搜索历史可以将其移除
|
||||
- 歌曲列表内的文字在选中后,鼠标右击可以复制已选中的文字,此功能只对搜索、歌单、排行榜、我的列表中的列表有效
|
||||
- 鼠标在播放详情页内右键双击可以关闭播放详情页
|
||||
- 鼠标左击播放栏上的歌曲名字可以将它复制
|
||||
- 鼠标右击“道法自然(英文Auto)”主题可以打开亮、暗主题设置窗口
|
||||
- 歌曲搜索框的候选内容可以用键盘上下方向键选择,按回车键搜索已选内容
|
||||
- 在歌单详情页按退格键可以返回歌单列表
|
||||
- 歌曲列表中可以使用Ctrl、Shift键进行多选,这类似Windows下的文件选择,详情看常见问题列表多选部分
|
||||
- 在我的列表内可以使用Ctrl+f键打开搜索框进行列表内歌曲搜索,搜索框按Esc键可以关闭搜索框,搜索框内按上下方向键可以选择歌曲,按回车键跳转到已选歌曲,按Ctrl+回车可以跳转并播放已选歌曲
|
||||
- 在我的列表按住Ctrl键可以进入列表拖动模式,此时可以用鼠标拖动列表调整列表的位置
|
||||
- 编辑列表名时按Esc键可以取消编辑
|
||||
- 按F11可以进入、退出全屏状态
|
||||
### 移除
|
||||
|
||||
- 移除设置-播放详情页设置-歌词字体重置,此设置项已迁移到播放详情页的歌词菜单中
|
||||
- 移除播放详情页使用+-快捷键调整字体大小的功能,改用歌词右键菜单的字体大小调整功能
|
||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 27 KiB |
|
@ -2,7 +2,7 @@ const path = require('path')
|
|||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.54',
|
||||
version: '1.0.56',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
|
@ -18,6 +18,7 @@ const defaultSetting = {
|
|||
audioVisualization: false,
|
||||
waitPlayEndStop: true,
|
||||
waitPlayEndStopTime: '',
|
||||
autoSkipOnError: true,
|
||||
},
|
||||
playDetail: {
|
||||
isZoomActiveLrc: true,
|
||||
|
@ -31,6 +32,7 @@ const defaultSetting = {
|
|||
enable: false,
|
||||
isLock: false,
|
||||
isAlwaysOnTop: false,
|
||||
isAlwaysOnTopLoop: false,
|
||||
width: 380,
|
||||
height: 420,
|
||||
x: null,
|
||||
|
|
|
@ -77,6 +77,12 @@ const names = {
|
|||
get_lyric: 'get_lyric',
|
||||
save_lyric: 'save_lyric',
|
||||
clear_lyric: 'clear_lyric',
|
||||
get_lyric_raw: 'get_lyric_raw',
|
||||
save_lyric_raw: 'save_lyric_raw',
|
||||
clear_lyric_raw: 'clear_lyric_raw',
|
||||
get_lyric_edited: 'get_lyric_edited',
|
||||
save_lyric_edited: 'save_lyric_edited',
|
||||
remove_lyric_edited: 'remove_lyric_edited',
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
"download__runing": "Downloading",
|
||||
"download__status": "Status",
|
||||
"export": "Export",
|
||||
"fullscreen_exit": "Exit Full Screen",
|
||||
"history_clear": "Clear History",
|
||||
"history_remove": "Right click to remove this entry",
|
||||
"history_search": "History Searches",
|
||||
|
@ -104,6 +105,10 @@
|
|||
"list_sort_modal_by_type": "Sort categories",
|
||||
"list_sort_modal_by_up": "Ascending",
|
||||
"list_sort_modal_tip_confirm": "Are you sure you want to do this?",
|
||||
"list_update_modal__auto_update": "auto update",
|
||||
"list_update_modal__tips": "💡 The list with \"Automatic Updates\" checked will be automatically updated each time the software is launched",
|
||||
"list_update_modal__title": "List update management",
|
||||
"list_update_modal__update": "Sync",
|
||||
"lists__duplicate": "Duplicate song",
|
||||
"lists__export": "Export",
|
||||
"lists__export_part_desc": "Choose where to save the list file",
|
||||
|
@ -127,6 +132,19 @@
|
|||
"love_list": "Favorites",
|
||||
"lyric__load_error": "Failed to get lyrics",
|
||||
"lyric__select": "Lyric text selection",
|
||||
"lyric_menu__align": "Lyric Alignment",
|
||||
"lyric_menu__align_center": "Centered",
|
||||
"lyric_menu__align_left": "Left",
|
||||
"lyric_menu__lrc_size": "Font size [{size}]",
|
||||
"lyric_menu__offset": "Offset [ {offset}ms ]",
|
||||
"lyric_menu__offset_add_10": "10ms faster",
|
||||
"lyric_menu__offset_add_100": "100ms faster",
|
||||
"lyric_menu__offset_dec_10": "10ms slow down (right click slows down 5ms)",
|
||||
"lyric_menu__offset_dec_100": "slow down by 100ms",
|
||||
"lyric_menu__offset_reset": "Reset",
|
||||
"lyric_menu__size_add": "Increase font size (right click to fine-tune)",
|
||||
"lyric_menu__size_dec": "Decrease font (right click to fine tune)",
|
||||
"lyric_menu__size_reset": "Reset",
|
||||
"min": "Minimize",
|
||||
"music_album": "Album",
|
||||
"music_duplicate": "Duplicate song",
|
||||
|
@ -243,6 +261,7 @@
|
|||
"setting__click_open": "Click to open",
|
||||
"setting__desktop_lyric": "Desktop Lyric Settings",
|
||||
"setting__desktop_lyric_always_on_top": "Make the lyrics always above other windows",
|
||||
"setting__desktop_lyric_always_on_top_loop": "Automatically refresh the top of the lyrics (try to enable this setting when the lyrics are still blocked by some programs)",
|
||||
"setting__desktop_lyric_delay_scroll": "Delayed lyrics scroll",
|
||||
"setting__desktop_lyric_enable": "Display lyrics",
|
||||
"setting__desktop_lyric_font": "Lyric font",
|
||||
|
@ -325,16 +344,17 @@
|
|||
"setting__other_tray_theme_native": "White",
|
||||
"setting__other_tray_theme_origin": "Primary Color",
|
||||
"setting__play": "Play",
|
||||
"setting__play_auto_skip_on_error": "Automatically switch songs on playback error",
|
||||
"setting__play_detail": "Play details page settings",
|
||||
"setting__play_detail_align": "Lyric Alignment",
|
||||
"setting__play_detail_align_center": "Centered",
|
||||
"setting__play_detail_align_left": "Left",
|
||||
"setting__play_detail_align_right": "Right",
|
||||
"setting__play_detail_detail_lyric_progress": "Allows to adjust playback progress by lyrics",
|
||||
"setting__play_detail_font_size": "Lyrics font size (you can use the keyboard + - adjust the font size on the playback details page)",
|
||||
"setting__play_detail_font_size_current": "Current font size: {size}",
|
||||
"setting__play_detail_font_size_reset": "Reset",
|
||||
"setting__play_detail_font_zoom": "Zoom the currently playing lyrics",
|
||||
"setting__play_detail_lyric_progress": "Allows to adjust playback progress by lyrics",
|
||||
"setting__play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
|
||||
"setting__play_lyric_s2t": "Convert the playing and downloading lyrics to Traditional Chinese",
|
||||
"setting__play_lyric_transition": "Show lyrics translation",
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
"download__runing": "正在下载",
|
||||
"download__status": "状态",
|
||||
"export": "导出",
|
||||
"fullscreen_exit": "退出全屏",
|
||||
"history_clear": "清空搜索历史",
|
||||
"history_remove": "右击移除该历史",
|
||||
"history_search": "历史搜索",
|
||||
|
@ -104,6 +105,10 @@
|
|||
"list_sort_modal_by_type": "排序类别",
|
||||
"list_sort_modal_by_up": "升序",
|
||||
"list_sort_modal_tip_confirm": "你确定要这么做吗?",
|
||||
"list_update_modal__auto_update": "自动更新",
|
||||
"list_update_modal__tips": "💡 每次启动软件时将会自动更新已勾选“自动更新”的列表",
|
||||
"list_update_modal__title": "列表更新管理",
|
||||
"list_update_modal__update": "立即更新",
|
||||
"lists__duplicate": "重复歌曲",
|
||||
"lists__export": "导出",
|
||||
"lists__export_part_desc": "选择列表文件保存位置",
|
||||
|
@ -127,6 +132,19 @@
|
|||
"love_list": "收藏",
|
||||
"lyric__load_error": "歌词获取失败",
|
||||
"lyric__select": "歌词文本选择",
|
||||
"lyric_menu__align": "歌词对齐方式",
|
||||
"lyric_menu__align_center": "居中",
|
||||
"lyric_menu__align_left": "居左",
|
||||
"lyric_menu__lrc_size": "字体大小 [ {size} ]",
|
||||
"lyric_menu__offset": "歌词偏移 [ {offset}ms ]",
|
||||
"lyric_menu__offset_add_10": "加快10毫秒",
|
||||
"lyric_menu__offset_add_100": "加快100毫秒",
|
||||
"lyric_menu__offset_dec_10": "减慢10毫秒",
|
||||
"lyric_menu__offset_dec_100": "减慢100毫秒",
|
||||
"lyric_menu__offset_reset": "重置",
|
||||
"lyric_menu__size_add": "加大字体(右击可微调)",
|
||||
"lyric_menu__size_dec": "减小字体(右击可微调)",
|
||||
"lyric_menu__size_reset": "重置",
|
||||
"min": "最小化",
|
||||
"music_album": "专辑名",
|
||||
"music_duplicate": "重复歌曲",
|
||||
|
@ -243,6 +261,7 @@
|
|||
"setting__click_open": "点击打开",
|
||||
"setting__desktop_lyric": "桌面歌词设置",
|
||||
"setting__desktop_lyric_always_on_top": "使歌词总是在其他窗口之上",
|
||||
"setting__desktop_lyric_always_on_top_loop": "自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)",
|
||||
"setting__desktop_lyric_delay_scroll": "延迟歌词滚动",
|
||||
"setting__desktop_lyric_enable": "显示歌词",
|
||||
"setting__desktop_lyric_font": "歌词字体",
|
||||
|
@ -325,16 +344,17 @@
|
|||
"setting__other_tray_theme_native": "白色",
|
||||
"setting__other_tray_theme_origin": "原色",
|
||||
"setting__play": "播放设置",
|
||||
"setting__play_auto_skip_on_error": "播放错误时自动切换歌曲",
|
||||
"setting__play_detail": "播放详情页设置",
|
||||
"setting__play_detail_align": "歌词对齐方式",
|
||||
"setting__play_detail_align_center": "居中",
|
||||
"setting__play_detail_align_left": "居左",
|
||||
"setting__play_detail_align_right": "居右",
|
||||
"setting__play_detail_detail_lyric_progress": "允许通过歌词调整播放进度",
|
||||
"setting__play_detail_font_size": "歌词字体大小(可以在播放详情页使用键盘的 + - 调整字体大小)",
|
||||
"setting__play_detail_font_size_current": "当前字体大小:{size}",
|
||||
"setting__play_detail_font_size_reset": "重置",
|
||||
"setting__play_detail_font_zoom": "缩放当前正在播放的歌词",
|
||||
"setting__play_detail_lyric_progress": "允许通过歌词调整播放进度",
|
||||
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
|
||||
"setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文",
|
||||
"setting__play_lyric_transition": "显示歌词翻译",
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
"download__runing": "正在下載",
|
||||
"download__status": "狀態",
|
||||
"export": "導出",
|
||||
"fullscreen_exit": "退出全屏",
|
||||
"history_clear": "清空搜索歷史",
|
||||
"history_remove": "右擊移除該歷史",
|
||||
"history_search": "歷史搜索",
|
||||
|
@ -104,6 +105,10 @@
|
|||
"list_sort_modal_by_type": "排序類別",
|
||||
"list_sort_modal_by_up": "升序",
|
||||
"list_sort_modal_tip_confirm": "你確定要這麼做嗎?",
|
||||
"list_update_modal__auto_update": "自動更新",
|
||||
"list_update_modal__tips": "💡 每次啟動軟件時將會自動更新已勾選“自動更新”的列表",
|
||||
"list_update_modal__title": "列表更新管理",
|
||||
"list_update_modal__update": "立即更新",
|
||||
"lists__duplicate": "重複歌曲",
|
||||
"lists__export": "導出",
|
||||
"lists__export_part_desc": "選擇列表文件保存位置",
|
||||
|
@ -127,6 +132,19 @@
|
|||
"love_list": "收藏列表",
|
||||
"lyric__load_error": "歌詞獲取失敗",
|
||||
"lyric__select": "歌詞文本選擇",
|
||||
"lyric_menu__align": "歌詞對齊方式",
|
||||
"lyric_menu__align_center": "居中",
|
||||
"lyric_menu__align_left": "居左",
|
||||
"lyric_menu__lrc_size": "字體大小 [ {size} ]",
|
||||
"lyric_menu__offset": "歌詞偏移 [ {offset}ms ]",
|
||||
"lyric_menu__offset_add_10": "加快10毫秒(右擊加快5毫秒)",
|
||||
"lyric_menu__offset_add_100": "加快100毫秒",
|
||||
"lyric_menu__offset_dec_10": "減慢10毫秒",
|
||||
"lyric_menu__offset_dec_100": "減慢100毫秒",
|
||||
"lyric_menu__offset_reset": "重置偏移",
|
||||
"lyric_menu__size_add": "加大字體(右擊可微調)",
|
||||
"lyric_menu__size_dec": "減小字體(右擊可微調)",
|
||||
"lyric_menu__size_reset": "重置",
|
||||
"min": "最小化",
|
||||
"music_album": "專輯名",
|
||||
"music_duplicate": "重複歌曲",
|
||||
|
@ -243,6 +261,7 @@
|
|||
"setting__click_open": "點擊打開",
|
||||
"setting__desktop_lyric": "桌面歌詞設置",
|
||||
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
|
||||
"setting__desktop_lyric_always_on_top_loop": "自動刷新歌詞置頂(當歌詞置頂後仍被某些程序遮擋時可嘗試啟用此設置)",
|
||||
"setting__desktop_lyric_delay_scroll": "延遲歌詞滾動",
|
||||
"setting__desktop_lyric_enable": "顯示歌詞",
|
||||
"setting__desktop_lyric_font": "歌詞字體",
|
||||
|
@ -325,16 +344,17 @@
|
|||
"setting__other_tray_theme_native": "白色",
|
||||
"setting__other_tray_theme_origin": "原色",
|
||||
"setting__play": "播放設置",
|
||||
"setting__play_auto_skip_on_error": "播放錯誤時自動切換歌曲",
|
||||
"setting__play_detail": "播放詳情頁設置",
|
||||
"setting__play_detail_align": "歌詞對齊方式",
|
||||
"setting__play_detail_align_center": "居中",
|
||||
"setting__play_detail_align_left": "居左",
|
||||
"setting__play_detail_align_right": "居右",
|
||||
"setting__play_detail_detail_lyric_progress": "允許通過歌詞調整播放進度",
|
||||
"setting__play_detail_font_size": "歌詞字體大小(可以在播放詳情頁使用鍵盤的 + - 調整字體大小)",
|
||||
"setting__play_detail_font_size_current": "當前字體大小:{size}",
|
||||
"setting__play_detail_font_size_reset": "重置",
|
||||
"setting__play_detail_font_zoom": "縮放當前正在播放的歌詞",
|
||||
"setting__play_detail_lyric_progress": "允許通過歌詞調整播放進度",
|
||||
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
|
||||
"setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文",
|
||||
"setting__play_lyric_transition": "顯示歌詞翻譯",
|
||||
|
|
|
@ -3,8 +3,8 @@ 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')
|
||||
const { getAddress, getServerId, generateCode, getClientKeyInfo, setClientKeyInfo } = require('./utils')
|
||||
const { syncList, removeSnapshot } = require('./syncList')
|
||||
const { log } = require('@common/utils')
|
||||
|
||||
|
||||
|
@ -84,6 +84,8 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
|||
global.lx_event.sync.status(status)
|
||||
})
|
||||
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
|
||||
keyInfo.connectionTime = Date.now()
|
||||
setClientKeyInfo(keyInfo)
|
||||
// socket.lx_keyInfo = keyInfo
|
||||
socket.data.keyInfo = keyInfo
|
||||
try {
|
||||
|
@ -172,3 +174,5 @@ exports.generateCode = async() => {
|
|||
global.lx_event.sync.status(status)
|
||||
return status.code
|
||||
}
|
||||
|
||||
exports.removeSnapshot = removeSnapshot
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
const { app } = require('electron')
|
||||
const { encryptMsg, decryptMsg } = require('./utils')
|
||||
const { encryptMsg, decryptMsg, getSnapshotFilePath } = require('./utils')
|
||||
const SYNC_EVENT_NAMES = require('../event/name')
|
||||
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
|
||||
const { throttle } = require('@common/utils')
|
||||
|
@ -403,7 +401,7 @@ const registerUpdateSnapshotTask = (socket, snapshot) => {
|
|||
}
|
||||
|
||||
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`)
|
||||
socket.data.snapshotFilePath = getSnapshotFilePath(socket.data.keyInfo)
|
||||
let fileData
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
|
@ -423,7 +421,7 @@ const checkSyncQueue = async() => {
|
|||
await wait()
|
||||
return checkSyncQueue()
|
||||
}
|
||||
module.exports = async(_io, socket) => {
|
||||
exports.syncList = async(_io, socket) => {
|
||||
io = _io
|
||||
await checkSyncQueue()
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
|
@ -434,3 +432,7 @@ module.exports = async(_io, socket) => {
|
|||
syncingId = null
|
||||
})
|
||||
}
|
||||
exports.removeSnapshot = keyInfo => {
|
||||
const filePath = getSnapshotFilePath(keyInfo)
|
||||
return fsPromises.unlink(filePath)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const { app } = require('electron')
|
||||
const { networkInterfaces } = require('os')
|
||||
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
|
||||
const path = require('path')
|
||||
const getStore = require('@common/store')
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
|
@ -91,3 +93,7 @@ exports.decryptMsg = (keyInfo, enMsg) => {
|
|||
// }
|
||||
// return msg
|
||||
}
|
||||
|
||||
exports.getSnapshotFilePath = keyInfo => {
|
||||
return path.join(app.getPath('userData'), `snapshot-${Buffer.from(keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
}
|
||||
|
|
|
@ -68,6 +68,8 @@ exports.createWindow = async userApi => {
|
|||
})
|
||||
}
|
||||
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
if (webContents === global.modules.mainWindow.webContents) return callback(true)
|
||||
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
callback(false)
|
||||
})
|
||||
|
|
|
@ -6,7 +6,34 @@ const { getLyricWindowBounds } = require('./utils')
|
|||
let isLock = null
|
||||
let isEnable = null
|
||||
let isAlwaysOnTop = null
|
||||
let isAlwaysOnTopLoop = null
|
||||
let isLockScreen = null
|
||||
|
||||
const alwaysOnTopTools = {
|
||||
timeout: null,
|
||||
alwaysOnTop: false,
|
||||
setAlwaysOnTop(flag, isLoop) {
|
||||
this.alwaysOnTop = flag
|
||||
this.clearLoop()
|
||||
global.modules.lyricWindow.setAlwaysOnTop(flag, 'screen-saver')
|
||||
console.log(isLoop)
|
||||
if (flag && isLoop) this.startLoop()
|
||||
},
|
||||
startLoop() {
|
||||
if (!this.alwaysOnTop) return
|
||||
this.timeout = setInterval(() => {
|
||||
if (!global.modules.lyricWindow) return this.clearLoop()
|
||||
global.modules.lyricWindow.setAlwaysOnTop(true, 'screen-saver')
|
||||
}, 1000)
|
||||
},
|
||||
clearLoop() {
|
||||
if (!this.timeout) return
|
||||
clearInterval(this.timeout)
|
||||
this.timeout = null
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const setLrcConfig = () => {
|
||||
let desktopLyric = global.appSetting.desktopLyric
|
||||
if (global.modules.lyricWindow) {
|
||||
|
@ -26,7 +53,15 @@ const setLrcConfig = () => {
|
|||
}
|
||||
if (isAlwaysOnTop != desktopLyric.isAlwaysOnTop) {
|
||||
isAlwaysOnTop = desktopLyric.isAlwaysOnTop
|
||||
global.modules.lyricWindow.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, 'screen-saver')
|
||||
alwaysOnTopTools.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, desktopLyric.isAlwaysOnTopLoop)
|
||||
}
|
||||
if (isAlwaysOnTopLoop != desktopLyric.isAlwaysOnTopLoop) {
|
||||
isAlwaysOnTopLoop = desktopLyric.isAlwaysOnTopLoop
|
||||
if (isAlwaysOnTopLoop) {
|
||||
alwaysOnTopTools.startLoop()
|
||||
} else {
|
||||
alwaysOnTopTools.clearLoop()
|
||||
}
|
||||
}
|
||||
if (isLockScreen != desktopLyric.isLockScreen) {
|
||||
isLockScreen = desktopLyric.isLockScreen
|
||||
|
@ -45,6 +80,7 @@ const setLrcConfig = () => {
|
|||
if (desktopLyric.enable) {
|
||||
global.lx_event.winLyric.create()
|
||||
} else {
|
||||
alwaysOnTopTools.clearLoop()
|
||||
global.lx_event.winLyric.close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
const LRC_RAW = 'lyrics'
|
||||
const LRC_EDITED = 'lyrics_edited'
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_lyric, async(event, id) => getStore('lyrics', true, false).get(id) || {})
|
||||
mainHandle(ipcMainWindowNames.get_lyric, async(event, id) => {
|
||||
return getStore(LRC_EDITED, true, false).get(id) || getStore(LRC_RAW, true, false).get(id) || {}
|
||||
})
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.save_lyric, (event, { id, lyrics }) => getStore('lyrics', true, false).set(id, lyrics))
|
||||
// 原始歌词
|
||||
mainHandle(ipcMainWindowNames.get_lyric_raw, async(event, id) => getStore(LRC_RAW, true, false).get(id) || {})
|
||||
mainOn(ipcMainWindowNames.save_lyric_raw, (event, { id, lyrics }) => getStore(LRC_RAW, true, false).set(id, lyrics))
|
||||
mainOn(ipcMainWindowNames.clear_lyric_raw, () => getStore(LRC_RAW, true, false).clear())
|
||||
|
||||
mainOn(ipcMainWindowNames.clear_lyric, () => getStore('lyrics', true, false).clear())
|
||||
// 已编辑的歌词
|
||||
mainHandle(ipcMainWindowNames.get_lyric_edited, async(event, id) => getStore(LRC_EDITED, true, false).get(id) || {})
|
||||
mainOn(ipcMainWindowNames.save_lyric_edited, (event, { id, lyrics }) => getStore(LRC_EDITED, true, false).set(id, lyrics))
|
||||
mainOn(ipcMainWindowNames.remove_lyric_edited, async(event, id) => getStore(LRC_EDITED, true, false).delete(id))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const { app } = require('electron')
|
||||
const { mainOn, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { isLinux } = require('@common/utils')
|
||||
|
||||
mainOn(ipcMainWindowNames.min, event => {
|
||||
if (global.modules.mainWindow) {
|
||||
|
@ -18,6 +19,16 @@ mainOn(ipcMainWindowNames.close, (event, isForce) => {
|
|||
})
|
||||
mainHandle(ipcMainWindowNames.fullscreen, async(event, isFullscreen) => {
|
||||
if (!global.modules.mainWindow) return false
|
||||
await global.modules.mainWindow.setFullScreen(isFullscreen)
|
||||
if (isLinux) { // linux 需要先设置为可调整窗口大小才能全屏
|
||||
if (isFullscreen) {
|
||||
await global.modules.mainWindow.setResizable(isFullscreen)
|
||||
await global.modules.mainWindow.setFullScreen(isFullscreen)
|
||||
} else {
|
||||
await global.modules.mainWindow.setFullScreen(isFullscreen)
|
||||
await global.modules.mainWindow.setResizable(isFullscreen)
|
||||
}
|
||||
} else {
|
||||
await global.modules.mainWindow.setFullScreen(isFullscreen)
|
||||
}
|
||||
return isFullscreen
|
||||
})
|
||||
|
|
|
@ -51,7 +51,7 @@ div(:class="$style.container")
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
||||
import { rendererSend, NAMES } from '../../../common/ipc'
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
export default {
|
||||
|
@ -108,9 +108,6 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.config = JSON.parse(JSON.stringify(this.lrcConfig))
|
||||
rendererOn(NAMES.winLyric.key_down, (event, key) => {
|
||||
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
sendEvent() {
|
||||
|
|
|
@ -45,6 +45,8 @@ export default {
|
|||
document.body.classList.add(window.dt ? 'disableTransparent' : 'transparent')
|
||||
document.documentElement.style.fontSize = windowSizeActive.value.fontSize
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
useApp()
|
||||
|
|
|
@ -683,7 +683,7 @@
|
|||
@color-black-theme-active: fadeout(lighten(@color-black-theme, 8%), 30%);
|
||||
@color-black-theme-font: lighten(@color-black-theme, 55%);
|
||||
@color-black-theme-font-label: lighten(@color-black-theme, 35%);
|
||||
@color-black-theme_2: rgba(19, 19, 19, 0.82);
|
||||
@color-black-theme_2: rgba(19, 19, 19, 0.93);
|
||||
@color-black-theme_2-background_1: #080808;
|
||||
@color-black-theme_2-background_2: #1f1f1f;
|
||||
@color-black-theme_2-hover: fadeout(lighten(@color-black-theme, 10%), 80%);
|
||||
|
@ -710,7 +710,7 @@
|
|||
@color-black-scrollbar-thumb-hover: fadeout(lighten(@color-black-theme, 10%), 35%);
|
||||
@color-black-player-pic-c1: fadeout(@color-black-theme_2, 50%);
|
||||
@color-black-player-pic-c2: lighten(@color-black-theme_2, 30%);
|
||||
@color-black-player-progress: lighten(@color-black-theme_2, 6%);
|
||||
@color-black-player-progress: fadeout(lighten(@color-black-theme, 20%), 80%);
|
||||
@color-black-player-progress-bar1: lighten(@color-black-theme_2, 12%);
|
||||
@color-black-player-progress-bar2: fadeout(lighten(@color-black-theme, 12%), 20%);
|
||||
@color-black-player-status-text: darken(@color-black-theme_2-font, 20%);
|
||||
|
@ -770,7 +770,7 @@
|
|||
@color-mid_autumn-scrollbar-thumb-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 40%);
|
||||
@color-mid_autumn-player-pic-c1: fadeout(@color-mid_autumn-theme_2, 50%);
|
||||
@color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%);
|
||||
@color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%);
|
||||
@color-mid_autumn-player-progress: fadeout(lighten(@color-mid_autumn-theme, 20%), 82%);
|
||||
@color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%);
|
||||
@color-mid_autumn-player-progress-bar2: fadeout(lighten(@color-mid_autumn-theme, 12%), 20%);
|
||||
@color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%);
|
||||
|
@ -889,7 +889,7 @@
|
|||
@color-naruto-scrollbar-thumb-hover: fadeout(lighten(@color-naruto-theme, 10%), 35%);
|
||||
@color-naruto-player-pic-c1: fadeout(@color-naruto-theme_2, 50%);
|
||||
@color-naruto-player-pic-c2: darken(@color-naruto-theme_2, 30%);
|
||||
@color-naruto-player-progress: darken(@color-naruto-theme_2, 10%);
|
||||
@color-naruto-player-progress: fadeout(lighten(@color-naruto-theme, 20%), 70%);
|
||||
@color-naruto-player-progress-bar1: darken(@color-naruto-theme_2, 12%);
|
||||
@color-naruto-player-progress-bar2: fadeout(lighten(@color-naruto-theme, 12%), 20%);
|
||||
@color-naruto-player-status-text: lighten(@color-naruto-theme_2-font, 32%);
|
||||
|
@ -948,7 +948,7 @@
|
|||
@color-happy_new_year-scrollbar-thumb-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 35%);
|
||||
@color-happy_new_year-player-pic-c1: fadeout(@color-happy_new_year-theme_2, 50%);
|
||||
@color-happy_new_year-player-pic-c2: darken(@color-happy_new_year-theme_2, 30%);
|
||||
@color-happy_new_year-player-progress: darken(@color-happy_new_year-theme_2, 10%);
|
||||
@color-happy_new_year-player-progress: fadeout(lighten(@color-happy_new_year-theme, 20%), 82%);
|
||||
@color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 5%);
|
||||
@color-happy_new_year-player-progress-bar2: fadeout(lighten(@color-happy_new_year-theme, 5%), 20%);
|
||||
@color-happy_new_year-player-status-text: lighten(@color-happy_new_year-theme_2-font, 32%);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M14,14H19V16H16V19H14V14M5,14H10V19H8V16H5V14M8,5H10V10H5V8H8V5M19,8V10H14V5H16V8H19Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 187 B |
|
@ -23,7 +23,7 @@ const themes = {
|
|||
blue2: 'rgba(79,98,208,.14)',
|
||||
black: 'rgba(39,39,39,.4)',
|
||||
mid_autumn: 'rgba(74,55,82,.1)',
|
||||
naruto: 'rgba(87,144,167,.14)',
|
||||
naruto: 'rgba(87,144,167,.15)',
|
||||
happy_new_year: 'rgba(192,57,43,.1)',
|
||||
}
|
||||
|
||||
|
|
|
@ -234,5 +234,13 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
|
|||
// 0 0 24 24
|
||||
path(fill='currentColor', d='M22 12L20 13L19 14L18 13L17 16L16 13L15 21L14 13L13 15L12 13L11 17L10 13L9 22L8 13L7 19L6 13L5 14L4 13L2 12L4 11L5 10L6 11L7 5L8 11L9 2L10 11L11 7L12 11L13 9L14 11L15 3L16 11L17 8L18 11L19 10L20 11L22 12Z')
|
||||
|
||||
g#icon-font-decrease(fill='currentColor')
|
||||
// 0 0 24 24
|
||||
path(d='M5.12,14L7.5,7.67L9.87,14M6.5,5L1,19H3.25L4.37,16H10.62L11.75,19H14L8.5,5H6.5M18,17L23,11.93L21.59,10.5L19,13.1V7H17V13.1L14.41,10.5L13,11.93L18,17Z')
|
||||
|
||||
g#icon-font-increase(fill='currentColor')
|
||||
// 0 0 24 24
|
||||
path(d='M5.12,14L7.5,7.67L9.87,14M6.5,5L1,19H3.25L4.37,16H10.62L11.75,19H14L8.5,5H6.5M18,7L13,12.07L14.41,13.5L17,10.9V17H19V10.9L21.59,13.5L23,12.07L18,7Z')
|
||||
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
<template>
|
||||
<div :class="['right', $style.right]" :style="lrcFontSize">
|
||||
<div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric">
|
||||
<div :class="['pre', $style.lyricSpace]"></div>
|
||||
<div ref="dom_lyric_text"></div>
|
||||
<div :class="$style.lyricSpace"></div>
|
||||
</div>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div :class="$style.skip" v-if="isShowLyricProgressSetting" v-show="isStopScroll">
|
||||
<div v-show="!isShowLrcSelectContent"
|
||||
:class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]"
|
||||
:style="lrcStyles" @wheel="handleWheel"
|
||||
@mousedown="handleLyricMouseDown" ref="dom_lyric"
|
||||
@contextmenu.stop="handleShowLyricMenu"
|
||||
>
|
||||
<div :class="['pre', $style.lyricSpace]"></div>
|
||||
<div ref="dom_lyric_text"></div>
|
||||
<div :class="$style.lyricSpace"></div>
|
||||
</div>
|
||||
</transition>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div :class="$style.skip" v-if="isShowLyricProgressSetting" v-show="isStopScroll && !isShowLrcSelectContent">
|
||||
<div :class="$style.line" ref="dom_skip_line"></div>
|
||||
<span :class="$style.label">{{timeStr}}</span>
|
||||
<base-btn :class="$style.skipBtn" @mouseenter="handleSkipMouseEnter" @mouseleave="handleSkipMouseLeave" @click="handleSkipPlay">
|
||||
|
@ -25,6 +32,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<LyricMenu v-model="lyricMenuVisible" :xy="lyricMenuXY" :lyricInfo="lyricInfo" @updateLyric="handleUpdateLyric" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -32,14 +40,21 @@
|
|||
import { clipboardWriteText } from '@renderer/utils'
|
||||
import { lyric } from '@renderer/core/share/lyric'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
import { isPlay, isShowLrcSelectContent, isShowPlayComment } from '@renderer/core/share/player'
|
||||
import { onMounted, onBeforeUnmount, useCommit, useRefGetter, computed } from '@renderer/utils/vueTools'
|
||||
import { isPlay, isShowLrcSelectContent, isShowPlayComment, musicInfo as playerMusicInfo, musicInfoItem, setMusicInfo } from '@renderer/core/share/player'
|
||||
import { onMounted, onBeforeUnmount, useRefGetter, computed, reactive, ref } from '@renderer/utils/vueTools'
|
||||
import useLyric from '@renderer/utils/compositions/useLyric'
|
||||
import LyricMenu from './components/LyricMenu'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LyricMenu,
|
||||
},
|
||||
setup() {
|
||||
const setting = useRefGetter('setting')
|
||||
const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
|
||||
const isZoomActiveLrc = computed(() => setting.value.playDetail.isZoomActiveLrc)
|
||||
const isShowLyricProgressSetting = computed(() => setting.value.playDetail.isShowLyricProgressSetting)
|
||||
|
||||
const {
|
||||
dom_lyric,
|
||||
dom_lyric_text,
|
||||
|
@ -52,15 +67,39 @@ export default {
|
|||
handleSkipPlay,
|
||||
handleSkipMouseEnter,
|
||||
handleSkipMouseLeave,
|
||||
} = useLyric({ isPlay, lyric })
|
||||
} = useLyric({ isPlay, lyric, isShowLyricProgressSetting })
|
||||
|
||||
const fontSizeUp = () => {
|
||||
if (setting.value.playDetail.style.fontSize >= 200) return
|
||||
setPlayDetailLyricFont(setting.value.playDetail.style.fontSize + 1)
|
||||
const lyricMenuVisible = ref(false)
|
||||
const lyricMenuXY = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
const lyricInfo = reactive({
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
lxlyric: '',
|
||||
musicInfo: null,
|
||||
})
|
||||
const updateMusicInfo = () => {
|
||||
lyricInfo.lyric = playerMusicInfo.lrc
|
||||
lyricInfo.tlyric = playerMusicInfo.tlrc
|
||||
lyricInfo.lxlyric = playerMusicInfo.lxlrc
|
||||
lyricInfo.musicInfo = musicInfoItem.value
|
||||
}
|
||||
const fontSizeDown = () => {
|
||||
if (setting.value.playDetail.style.fontSize <= 70) return
|
||||
setPlayDetailLyricFont(setting.value.playDetail.style.fontSize - 1)
|
||||
const handleShowLyricMenu = event => {
|
||||
updateMusicInfo()
|
||||
lyricMenuXY.x = event.pageX
|
||||
lyricMenuXY.y = event.pageY
|
||||
lyricMenuVisible.value = true
|
||||
}
|
||||
const handleUpdateLyric = ({ lyric, tlyric, lxlyric, offset }) => {
|
||||
setMusicInfo({
|
||||
lrc: lyric,
|
||||
tlrc: tlyric,
|
||||
lxlrc: lxlyric,
|
||||
})
|
||||
console.log(offset)
|
||||
window.eventHub.emit(eventPlayerNames.updateLyricOffset, offset)
|
||||
}
|
||||
|
||||
const lrcStyles = computed(() => {
|
||||
|
@ -75,20 +114,12 @@ export default {
|
|||
'--playDetail-lrc-font-size': (isShowPlayComment.value ? size * 0.82 : size) + 'rem',
|
||||
}
|
||||
})
|
||||
const isZoomActiveLrc = computed(() => setting.value.playDetail.isZoomActiveLrc)
|
||||
const isShowLyricProgressSetting = computed(() => setting.value.playDetail.isShowLyricProgressSetting)
|
||||
|
||||
onMounted(() => {
|
||||
window.eventHub.on('key_shift++_down', fontSizeUp)
|
||||
window.eventHub.on('key_numadd_down', fontSizeUp)
|
||||
window.eventHub.on('key_-_down', fontSizeDown)
|
||||
window.eventHub.on('key_numsub_down', fontSizeDown)
|
||||
window.eventHub.on(eventPlayerNames.updateLyric, updateMusicInfo)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.eventHub.off('key_shift++_down', fontSizeUp)
|
||||
window.eventHub.off('key_numadd_down', fontSizeUp)
|
||||
window.eventHub.off('key_-_down', fontSizeDown)
|
||||
window.eventHub.off('key_numsub_down', fontSizeDown)
|
||||
window.eventHub.off(eventPlayerNames.updateLyric, updateMusicInfo)
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -109,6 +140,11 @@ export default {
|
|||
isShowLyricProgressSetting,
|
||||
isZoomActiveLrc,
|
||||
isStopScroll,
|
||||
lyricMenuVisible,
|
||||
lyricMenuXY,
|
||||
handleShowLyricMenu,
|
||||
handleUpdateLyric,
|
||||
lyricInfo,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -131,34 +167,13 @@ export default {
|
|||
// padding: 0 30px;
|
||||
position: relative;
|
||||
transition: flex-basis @transition-theme;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
|
||||
pointer-events: none;
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.lyric {
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: var(--playDetail-lrc-font-size, 16px);
|
||||
-webkit-mask-image: linear-gradient(transparent 0%, #fff 20%, #fff 80%, transparent 100%);
|
||||
cursor: grab;
|
||||
&.draging {
|
||||
cursor: grabbing;
|
||||
|
@ -246,9 +261,10 @@ export default {
|
|||
pointer-events: none;
|
||||
// opacity: .5;
|
||||
.line {
|
||||
border-top: 1px dashed @color-player-detail-lyric-active;
|
||||
border-top: 2px dotted @color-player-detail-lyric-active;
|
||||
opacity: .15;
|
||||
margin-right: 30px;
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 15%, #fff 100%);
|
||||
}
|
||||
.label {
|
||||
position: absolute;
|
||||
|
@ -288,7 +304,6 @@ export default {
|
|||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
background-color: @color-theme_2-background_1;
|
||||
z-index: 10;
|
||||
color: @color-player-detail-lyric;
|
||||
|
||||
|
@ -313,14 +328,6 @@ export default {
|
|||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.right {
|
||||
&:before {
|
||||
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
|
||||
}
|
||||
&:after {
|
||||
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
|
||||
}
|
||||
}
|
||||
.lyric {
|
||||
:global {
|
||||
.lrc-content {
|
||||
|
@ -353,7 +360,6 @@ each(@themes, {
|
|||
}
|
||||
}
|
||||
.lyricSelectContent {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
color: ~'@{color-@{value}-player-detail-lyric}';
|
||||
.lrc-active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { debounce } from '@renderer/utils/index'
|
||||
let isAutoHide = false
|
||||
let isLockedPointer = false
|
||||
let dom = null
|
||||
let event = null
|
||||
let isMouseDown = false
|
||||
|
||||
const isControl = dom => {
|
||||
if (!dom || dom === document.body) return false
|
||||
// console.log(dom)
|
||||
if (dom.getAttribute('aria-label') || dom.tagName == 'BUTTON') return true
|
||||
return isControl(dom.parentNode)
|
||||
}
|
||||
|
||||
const lockPointer = () => {
|
||||
if (!isAutoHide || isMouseDown) return
|
||||
if (event && isControl(document.elementFromPoint(event.clientX, event.clientY))) return
|
||||
|
||||
dom.requestPointerLock()
|
||||
isLockedPointer = true
|
||||
}
|
||||
const unLockPointer = () => {
|
||||
if (!isLockedPointer) return
|
||||
document.exitPointerLock()
|
||||
isLockedPointer = false
|
||||
}
|
||||
|
||||
const startTimeout = debounce(lockPointer, 3000)
|
||||
|
||||
const handleMouseMove = (_event) => {
|
||||
event = _event
|
||||
startTimeout()
|
||||
unLockPointer()
|
||||
}
|
||||
|
||||
const handleMouseDown = () => {
|
||||
isMouseDown = true
|
||||
}
|
||||
const handleMouseUp = () => {
|
||||
isMouseDown = false
|
||||
startTimeout()
|
||||
}
|
||||
|
||||
export const registerAutoHideMounse = () => {
|
||||
if (isAutoHide) return
|
||||
if (!dom) dom = document.getElementById('root')
|
||||
isAutoHide = true
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mousedown', handleMouseDown)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
startTimeout()
|
||||
}
|
||||
|
||||
export const unregisterAutoHideMounse = () => {
|
||||
if (!isAutoHide) return
|
||||
isAutoHide = false
|
||||
// console.log(dom)
|
||||
dom.removeEventListener('mousemove', handleMouseMove)
|
||||
dom.removeEventListener('mousedown', handleMouseDown)
|
||||
dom.removeEventListener('mouseup', handleMouseUp)
|
||||
unLockPointer()
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
<template>
|
||||
<teleport to="#root">
|
||||
<div :class="$style.container" :style="menuStyles" ref="dom_menu" :aria-hidden="!modelValue">
|
||||
<!-- <div :class="$style.group">
|
||||
<div :class="$style.title">{{$t('lyric_menu__align')}}</div>
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="[$style.btn, { [$style.active]: playDetailSetting.style.align == 'left' }]" role="button" @click="setFontAlign('left')" ignore-tip :aria-label="$t('lyric_menu__align_left')">{{$t('lyric_menu__align_left')}}</div>
|
||||
<div :class="[$style.btn, { [$style.active]: playDetailSetting.style.align == 'center' }]" role="button" @click="setFontAlign('center')" ignore-tip :aria-label="$t('lyric_menu__align_center')">{{$t('lyric_menu__align_center')}}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="$style.title">{{$t('lyric_menu__lrc_size', { size: playDetailSetting.style.fontSize })}}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="playDetailSetting.style.fontSize == 100" @click="fontSizeReset" ignore-tip :aria-label="$t('lyric_menu__size_reset')">{{$t('lyric_menu__size_reset')}}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" @click="fontSizeUp(5)" @contextmenu="fontSizeUp(1)" :aria-label="$t('lyric_menu__size_add')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="18px" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-font-increase"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="$style.btn" @click="fontSizeDown(5)" @contextmenu="fontSizeDown(1)" :aria-label="$t('lyric_menu__size_dec')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="18px" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-font-decrease"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="$style.title">{{$t('lyric_menu__offset', { offset })}}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="offsetDisabled || !offset" @click="offsetReset">{{$t('lyric_menu__offset_reset')}}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(10)" ignore-tip :aria-label="$t('lyric_menu__offset_add_10')">+ 10ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(-10)" ignore-tip :aria-label="$t('lyric_menu__offset_dec_10')">- 10ms</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(100)" ignore-tip :aria-label="$t('lyric_menu__offset_add_100')">+ 100ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(-100)" ignore-tip :aria-label="$t('lyric_menu__offset_dec_100')">- 100ms</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, useRefGetter, ref, useCommit, watch } from '@renderer/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
import { setLyricEdited, removeLyricEdited, debounce } from '@renderer/utils'
|
||||
|
||||
const offsetTagRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/
|
||||
const offsetTagAllRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/g
|
||||
|
||||
const saveLyric = debounce((musicInfo, lyricInfo) => {
|
||||
setLyricEdited(musicInfo, lyricInfo)
|
||||
})
|
||||
const removeLyric = debounce(musicInfo => {
|
||||
removeLyricEdited(musicInfo)
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'LyricMenu',
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
xy: Object,
|
||||
lyricInfo: Object,
|
||||
},
|
||||
emits: ['updateLyric', 'update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const setting = useRefGetter('setting')
|
||||
const playDetailSetting = useRefGetter('playDetailSetting')
|
||||
const setPlayDetailLyricAlign = useCommit('setPlayDetailLyricAlign')
|
||||
const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
|
||||
|
||||
const offset = ref(0)
|
||||
const offsetDisabled = ref(true)
|
||||
|
||||
const visible = computed(() => props.modelValue)
|
||||
const musicInfo = computed(() => props.lyricInfo.musicInfo)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
const setFontAlign = val => {
|
||||
if (playDetailSetting.value.style.align == val) return
|
||||
setPlayDetailLyricAlign(val)
|
||||
}
|
||||
|
||||
const fontSizeUp = step => {
|
||||
if (setting.value.playDetail.style.fontSize >= 200) return
|
||||
setPlayDetailLyricFont(Math.min(setting.value.playDetail.style.fontSize + step, 200))
|
||||
}
|
||||
const fontSizeDown = step => {
|
||||
if (setting.value.playDetail.style.fontSize <= 70) return
|
||||
setPlayDetailLyricFont(Math.max(setting.value.playDetail.style.fontSize - step, 70))
|
||||
}
|
||||
const fontSizeReset = () => {
|
||||
setPlayDetailLyricFont(100)
|
||||
}
|
||||
|
||||
const updateLyric = offset => {
|
||||
let lyric = props.lyricInfo.lyric
|
||||
let tlyric = props.lyricInfo.tlyric
|
||||
let lxlyric = props.lyricInfo.lxlyric
|
||||
if (offsetTagRxp.test(lyric)) {
|
||||
lyric = lyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (tlyric) tlyric = tlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (lxlyric) lxlyric = lxlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
} else {
|
||||
lyric = `[offset:${offset}]\n` + lyric
|
||||
if (tlyric) tlyric = `[offset:${offset}]\n` + tlyric
|
||||
if (lxlyric) lxlyric = `[offset:${offset}]\n` + lxlyric
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
saveLyric(props.lyricInfo.musicInfo, {
|
||||
lyric,
|
||||
tlyric,
|
||||
lxlyric,
|
||||
})
|
||||
} else removeLyric(props.lyricInfo.musicInfo)
|
||||
|
||||
emit('updateLyric', {
|
||||
lyric,
|
||||
tlyric,
|
||||
lxlyric,
|
||||
offset,
|
||||
})
|
||||
}
|
||||
const setOffset = step => {
|
||||
offset.value += step
|
||||
updateLyric(offset.value)
|
||||
}
|
||||
const offsetReset = () => {
|
||||
if (!offset.value) return
|
||||
offset.value = 0
|
||||
updateLyric(0)
|
||||
}
|
||||
|
||||
const parseLrcOffset = () => {
|
||||
let lrcOffset
|
||||
if (props.lyricInfo.lyric) {
|
||||
lrcOffset = offsetTagRxp.exec(props.lyricInfo.lyric)
|
||||
if (lrcOffset) {
|
||||
lrcOffset = parseInt(lrcOffset[1])
|
||||
if (Number.isNaN(lrcOffset)) lrcOffset = 0
|
||||
} else lrcOffset = 0
|
||||
offsetDisabled.value = false
|
||||
} else {
|
||||
offsetDisabled.value = true
|
||||
lrcOffset = 0
|
||||
}
|
||||
offset.value = lrcOffset
|
||||
}
|
||||
|
||||
|
||||
const { dom_menu, menuStyles } = useMenuLocation({
|
||||
visible,
|
||||
location,
|
||||
onHide,
|
||||
})
|
||||
|
||||
watch(musicInfo, () => {
|
||||
if (!props.modelValue) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
watch(visible, val => {
|
||||
if (!val) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
playDetailSetting,
|
||||
offset,
|
||||
fontSizeUp,
|
||||
fontSizeDown,
|
||||
fontSizeReset,
|
||||
setOffset,
|
||||
offsetReset,
|
||||
setFontAlign,
|
||||
offsetDisabled,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.title {
|
||||
flex: auto;
|
||||
padding: 10px 0 10px 10px;
|
||||
color: @color-theme_2-font-label;
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.subGroup {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: auto;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
min-width: 60px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// color: @color-btn;
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
transition: @transition-theme;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
background-color: @color-theme_2-background_2;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-background_2;
|
||||
color: @color-btn-active;
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.titleBtn {
|
||||
flex: none;
|
||||
padding: 0 10;
|
||||
min-width: 40px;
|
||||
opacity: .7;
|
||||
|
||||
&[disabled] {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.container {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
}
|
||||
.btn {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
|
@ -35,7 +35,6 @@ div.comment(:class="$style.comment" ref="dom_container")
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { scrollTo } from '@renderer/utils'
|
||||
import music from '@renderer/utils/music'
|
||||
import CommentFloor from './CommentFloor'
|
||||
|
||||
|
@ -170,7 +169,7 @@ export default {
|
|||
this.newComment.page = page
|
||||
this.newComment.list = comment.comments
|
||||
this.$nextTick(() => {
|
||||
scrollTo(this.$refs.dom_commentNew, 0, 300)
|
||||
this.$refs.dom_commentNew.scrollTo(0, 0)
|
||||
})
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
|
@ -189,7 +188,7 @@ export default {
|
|||
this.hotComment.page = page
|
||||
this.hotComment.list = hotComment.comments
|
||||
this.$nextTick(() => {
|
||||
scrollTo(this.$refs.dom_commentHot, 0, 300)
|
||||
this.$refs.dom_commentHot.scrollTo(0, 0)
|
||||
})
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
|
@ -321,6 +320,7 @@ export default {
|
|||
height: 100%;
|
||||
padding-left: 15px;
|
||||
padding-right: 10px;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
.commentLabel {
|
||||
padding: 15px;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template lang="pug">
|
||||
transition(enter-active-class="animated lightSpeedIn" leave-active-class="animated slideOutDown" @after-enter="handleAfterEnter" @after-leave="handleAfterLeave")
|
||||
div(:class="[$style.container, , { [$style.fullscreen]: isFullscreen }]" @contextmenu="handleContextMenu" v-if="isShowPlayerDetail")
|
||||
div(:class="[$style.container, { [$style.fullscreen]: isFullscreen }]" @contextmenu="handleContextMenu" v-if="isShowPlayerDetail")
|
||||
div(:class="$style.bg")
|
||||
//- div(:class="$style.bg" :style="bgStyle")
|
||||
//- div(:class="$style.bg2")
|
||||
div(:class="[$style.header, $style.controlBtnLeft]" v-if="setting.controlBtnPosition == 'left'")
|
||||
|
@ -8,6 +9,9 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
|||
button(type="button" :class="$style.hide" :aria-label="$t('player__hide_detail_tip')" @click="hide")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve')
|
||||
use(xlink:href='#icon-window-hide')
|
||||
button(type="button" :class="$style.fullscreenExit" :aria-label="$t('fullscreen_exit')" @click="fullscreenExit")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%')
|
||||
use(xlink:href='#icon-fullscreen-exit')
|
||||
button(type="button" :class="$style.min" :aria-label="$t('min')" @click="min")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-window-minimize')
|
||||
|
@ -21,6 +25,9 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
|||
button(type="button" :class="$style.hide" :aria-label="$t('player__hide_detail_tip')" @click="hide")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='35%' viewBox='0 0 30.727 30.727' space='preserve')
|
||||
use(xlink:href='#icon-window-hide')
|
||||
button(type="button" :class="$style.fullscreenExit" :aria-label="$t('fullscreen_exit')" @click="fullscreenExit")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%')
|
||||
use(xlink:href='#icon-fullscreen-exit')
|
||||
button(type="button" :class="$style.min" :aria-label="$t('min')" @click="min")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-window-minimize-2')
|
||||
|
@ -29,7 +36,6 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
|||
button(type="button" :class="$style.close" :aria-label="$t('close')" @click="close")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-window-close-2')
|
||||
|
||||
div(:class="[$style.main, {[$style.showComment]: isShowPlayComment}]")
|
||||
div.left(:class="$style.left")
|
||||
//- div(:class="$style.info")
|
||||
|
@ -51,7 +57,7 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
|||
|
||||
|
||||
<script>
|
||||
import { useRefGetter, ref } from '@renderer/utils/vueTools'
|
||||
import { useRefGetter, ref, watch } from '@renderer/utils/vueTools'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
import { base as eventBaseName } from '@renderer/event/names'
|
||||
import {
|
||||
|
@ -66,6 +72,7 @@ import {
|
|||
import LyricPlayer from './LyricPlayer'
|
||||
import PlayBar from './PlayBar'
|
||||
import MusicComment from './components/MusicComment'
|
||||
import { registerAutoHideMounse, unregisterAutoHideMounse } from './autoHideMounse'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayDetail',
|
||||
|
@ -97,6 +104,8 @@ export default {
|
|||
}
|
||||
|
||||
const handleAfterEnter = () => {
|
||||
if (isFullscreen.value) registerAutoHideMounse()
|
||||
|
||||
visibled.value = true
|
||||
}
|
||||
|
||||
|
@ -104,8 +113,14 @@ export default {
|
|||
setShowPlayLrcSelectContentLrc(false)
|
||||
hideComment(false)
|
||||
visibled.value = false
|
||||
|
||||
unregisterAutoHideMounse()
|
||||
}
|
||||
|
||||
watch(isFullscreen, isFullscreen => {
|
||||
(isFullscreen ? registerAutoHideMounse : unregisterAutoHideMounse)()
|
||||
})
|
||||
|
||||
return {
|
||||
setting,
|
||||
isShowPlayerDetail,
|
||||
|
@ -119,6 +134,9 @@ export default {
|
|||
handleAfterLeave,
|
||||
visibled,
|
||||
isFullscreen,
|
||||
fullscreenExit() {
|
||||
window.eventHub.emit(eventBaseName.fullscreenToggle, false)
|
||||
},
|
||||
min() {
|
||||
window.eventHub.emit(eventBaseName.min)
|
||||
},
|
||||
|
@ -166,23 +184,38 @@ export default {
|
|||
&.fullscreen {
|
||||
.header {
|
||||
-webkit-app-region: no-drag;
|
||||
> * {
|
||||
display: none;
|
||||
align-self: flex-start;
|
||||
.controBtn {
|
||||
.close, .min {
|
||||
display: none;
|
||||
}
|
||||
.fullscreenExit {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// .bg {
|
||||
// position: absolute;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// background-size: 110% 110%;
|
||||
// filter: blur(60px);
|
||||
// z-index: -1;
|
||||
// }
|
||||
.bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-image: @color-theme-bgimg;
|
||||
// background-size: 110% 110%;
|
||||
// filter: blur(60px);
|
||||
opacity: .7;
|
||||
z-index: -1;
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: @color-theme_2;
|
||||
}
|
||||
}
|
||||
// .bg2 {
|
||||
// position: absolute;
|
||||
// width: 100%;
|
||||
|
@ -204,6 +237,23 @@ export default {
|
|||
top: 0;
|
||||
display: flex;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fullscreenExit {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.controlBtnLeft {
|
||||
|
@ -220,19 +270,10 @@ export default {
|
|||
}
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
width: @control-btn-width;
|
||||
height: @control-btn-width;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
color: @color-theme_2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
+ button {
|
||||
margin-right: (@control-btn-width / 2);
|
||||
}
|
||||
|
@ -240,7 +281,7 @@ export default {
|
|||
&.hide {
|
||||
background-color: @color-hideBtn;
|
||||
}
|
||||
&.min {
|
||||
&.min, &.fullscreenExit {
|
||||
background-color: @color-minBtn;
|
||||
}
|
||||
&.max {
|
||||
|
@ -262,24 +303,14 @@ export default {
|
|||
.controBtn {
|
||||
right: 0;
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 46px;
|
||||
height: 30px;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
color: @color-theme;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
&.hide, &.min, &.max {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
background-color: @color-btn-hover;
|
||||
|
||||
&.close {
|
||||
background-color: @color-closeBtn;
|
||||
}
|
||||
|
@ -377,6 +408,15 @@ each(@themes, {
|
|||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
// color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.bg {
|
||||
// background-color: ~'@{color-@{value}-theme}';
|
||||
background-image: ~'@{color-@{value}-theme-bgimg}';
|
||||
background-size: ~'@{color-@{value}-theme-bgsize}';
|
||||
background-position: ~'@{color-@{value}-theme-bgposition}';
|
||||
&:after {
|
||||
background-color: ~'@{color-@{value}-theme_2}';
|
||||
}
|
||||
}
|
||||
.header {
|
||||
&.controlBtnLeft {
|
||||
.controBtn {
|
||||
|
@ -388,7 +428,7 @@ each(@themes, {
|
|||
&.hide {
|
||||
background-color: ~'@{color-@{value}-hideBtn}';
|
||||
}
|
||||
&.min {
|
||||
&.min, &.fullscreenExit {
|
||||
background-color: ~'@{color-@{value}-minBtn}';
|
||||
}
|
||||
&.max {
|
||||
|
@ -405,9 +445,8 @@ each(@themes, {
|
|||
button {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
&:hover {
|
||||
&.hide, &.min, &.max {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
|
||||
&.close {
|
||||
background-color: ~'@{color-@{value}-closeBtn}';
|
||||
}
|
||||
|
|
|
@ -121,3 +121,8 @@ export const removeUserList = id => {
|
|||
export const getList = id => {
|
||||
return allList[id] ?? []
|
||||
}
|
||||
|
||||
export const fetchingListStatus = reactive({})
|
||||
export const setFetchingListStatus = (id, status) => {
|
||||
fetchingListStatus[id] = status
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ export const lyric = reactive({
|
|||
lines: [],
|
||||
text: '',
|
||||
line: 0,
|
||||
offset: 0, // 歌词延迟
|
||||
tempOffset: 0, // 歌词临时延迟
|
||||
})
|
||||
|
||||
export const setLines = lines => {
|
||||
|
@ -13,3 +15,9 @@ export const setText = (text, line) => {
|
|||
lyric.text = text
|
||||
lyric.line = line
|
||||
}
|
||||
export const setOffset = offset => {
|
||||
lyric.offset = offset
|
||||
}
|
||||
export const setTempOffset = offset => {
|
||||
lyric.tempOffset = offset
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import useHandleEnvParams from './useHandleEnvParams'
|
|||
import useEventListener from './useEventListener'
|
||||
import useDeepLink from './useDeepLink'
|
||||
import usePlayer from './usePlayer'
|
||||
import useListAutoUpdate from './useListAutoUpdate'
|
||||
|
||||
|
||||
export default () => {
|
||||
|
@ -46,6 +47,7 @@ export default () => {
|
|||
setting,
|
||||
})
|
||||
const initDeepLink = useDeepLink()
|
||||
const handleListAutoUpdate = useListAutoUpdate()
|
||||
|
||||
|
||||
getEnvParams().then(envParams => {
|
||||
|
@ -63,6 +65,7 @@ export default () => {
|
|||
initPlayer()
|
||||
handleEnvParams(envParams) // 处理传入的启动参数
|
||||
initDeepLink(envParams)
|
||||
handleListAutoUpdate()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useCommit, useRefGetter } from '@renderer/utils/vueTools'
|
||||
import { getPlayList } from '@renderer/utils'
|
||||
import { getPlayInfo, getSearchHistoryList } from '@renderer/utils/tools'
|
||||
import { initListPosition, initListPrevSelectId } from '@renderer/utils/data'
|
||||
import { initListPosition, initListPrevSelectId, initListUpdateInfo } from '@renderer/utils/data'
|
||||
import music from '@renderer/utils/music'
|
||||
import { log } from '@common/utils'
|
||||
import {
|
||||
|
@ -152,6 +152,7 @@ export default ({
|
|||
await Promise.all([
|
||||
initListPosition(), // 列表位置记录
|
||||
initListPrevSelectId(), // 上次选中的列表记录
|
||||
initListUpdateInfo(), // 列表更新设置
|
||||
initUserApi(), // 自定义API
|
||||
]).catch(err => log.error(err))
|
||||
music.init() // 初始化音乐sdk
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useCommit, useAction, useRouter, markRaw } from '@renderer/utils/vueTools'
|
||||
import { decodeName } from '@renderer/utils'
|
||||
// import { allList, defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
import { playMusicInfo } from '@renderer/core/share/player'
|
||||
import { playMusicInfo, isShowPlayerDetail, setShowPlayerDetail } from '@renderer/core/share/player'
|
||||
|
||||
import { dataVerify, qualityFilter, sources } from './utils'
|
||||
|
||||
|
@ -29,6 +29,7 @@ const useSearchMusic = () => {
|
|||
if (text.length > 128) text = text.substring(0, 128)
|
||||
}
|
||||
|
||||
if (isShowPlayerDetail.value) setShowPlayerDetail(false)
|
||||
const sourceList = [...sources, 'all']
|
||||
source = sourceList.includes(source) ? source : null
|
||||
router.replace({
|
||||
|
|
|
@ -28,8 +28,11 @@ const handle_open_devtools = event => {
|
|||
rendererSend(NAMES.mainWindow.open_dev_tools)
|
||||
}
|
||||
const handle_fullscreen = event => {
|
||||
if (event.event.repeat) return
|
||||
rendererInvoke(NAMES.mainWindow.fullscreen, !isFullscreen.value).then(fullscreen => {
|
||||
let fullscreen = !isFullscreen.value
|
||||
if (typeof event == 'boolean') {
|
||||
fullscreen = event
|
||||
} else if (event.event.repeat) return
|
||||
rendererInvoke(NAMES.mainWindow.fullscreen, fullscreen).then(fullscreen => {
|
||||
isFullscreen.value = fullscreen
|
||||
})
|
||||
}
|
||||
|
@ -75,6 +78,7 @@ export default ({
|
|||
window.eventHub.on('key_escape_down', handle_key_esc_down)
|
||||
window.eventHub.on('key_mod+f12_down', handle_open_devtools)
|
||||
window.eventHub.on('key_f11_down', handle_fullscreen)
|
||||
window.eventHub.on(eventBaseName.fullscreenToggle, handle_fullscreen)
|
||||
document.body.addEventListener('click', handleBodyClick, true)
|
||||
|
||||
if (isProd && !window.dt && !isLinux) {
|
||||
|
@ -89,6 +93,7 @@ export default ({
|
|||
window.eventHub.off('key_escape_down', handle_key_esc_down)
|
||||
window.eventHub.off('key_mod+f12_down', handle_open_devtools)
|
||||
window.eventHub.off('key_f11_down', handle_fullscreen)
|
||||
window.eventHub.off(eventBaseName.fullscreenToggle, handle_fullscreen)
|
||||
document.body.removeEventListener('click', handleBodyClick)
|
||||
window.eventHub.emit(eventBaseName.unbindKey)
|
||||
rSetConfig()
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { getListUpdateInfo } from '@renderer/utils/data'
|
||||
import useSyncSourceList from '@renderer/utils/compositions/useSyncSourceList'
|
||||
import { userLists } from '@renderer/core/share/list'
|
||||
|
||||
export default () => {
|
||||
const syncSourceList = useSyncSourceList()
|
||||
|
||||
|
||||
const handleSyncSourceList = async(waitUpdateLists) => {
|
||||
if (!waitUpdateLists.length) return
|
||||
const targetListInfo = waitUpdateLists.shift()
|
||||
// console.log(targetListInfo)
|
||||
try {
|
||||
await syncSourceList(targetListInfo)
|
||||
} catch {}
|
||||
handleSyncSourceList(waitUpdateLists)
|
||||
}
|
||||
|
||||
return () => {
|
||||
const waitUpdateLists = Object.entries(getListUpdateInfo())
|
||||
.filter(([id, info]) => info.isAutoUpdate)
|
||||
.map(([id]) => userLists.find(l => l.id === id))
|
||||
.filter(_ => _)
|
||||
for (let i = 2; i > 0; i--) {
|
||||
handleSyncSourceList(waitUpdateLists)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { onBeforeUnmount, watch, markRawList } from '@renderer/utils/vueTools'
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
import { getCurrentTime } from '@renderer/plugins/player'
|
||||
import { getCurrentTime as getPlayerCurrentTime } from '@renderer/plugins/player'
|
||||
import { setDesktopLyricInfo, onGetDesktopLyricInfo } from '@renderer/utils/tools'
|
||||
import { player } from '@renderer/event/names'
|
||||
import { lyric, setText, setLines } from '@renderer/core/share/lyric'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
import { lyric, setText, setLines, setOffset, setTempOffset } from '@renderer/core/share/lyric'
|
||||
import { musicInfo, setStatusText, isPlay, playMusicInfo } from '@renderer/core/share/player'
|
||||
|
||||
export default ({ setting }) => {
|
||||
|
@ -12,19 +12,25 @@ export default ({ setting }) => {
|
|||
fontClassName: 'font',
|
||||
shadowContent: false,
|
||||
activeLineClassName: 'active',
|
||||
onPlay: (line, text) => {
|
||||
onPlay(line, text) {
|
||||
setText(text, line)
|
||||
setStatusText(text)
|
||||
// console.log(line, text)
|
||||
},
|
||||
onSetLyric: lines => { // listening lyrics seting event
|
||||
onSetLyric(lines, offset) { // listening lyrics seting event
|
||||
// console.log(lines) // lines is array of all lyric text
|
||||
setLines(markRawList([...lines]))
|
||||
setText(lines[0] ?? '', 0)
|
||||
setOffset(offset) // 歌词延迟
|
||||
setTempOffset(0) // 重置临时延迟
|
||||
},
|
||||
// offset: 80,
|
||||
})
|
||||
|
||||
const getCurrentTime = () => {
|
||||
return getPlayerCurrentTime() * 1000 + lyric.tempOffset
|
||||
}
|
||||
|
||||
|
||||
const setPlayInfo = ({ musicInfo }) => {
|
||||
setDesktopLyricInfo('music_info', {
|
||||
|
@ -45,7 +51,18 @@ export default ({ setting }) => {
|
|||
|
||||
if (isPlay.value && (musicInfo.url || playMusicInfo.listId == 'download')) {
|
||||
setTimeout(() => {
|
||||
const time = getCurrentTime() * 1000
|
||||
const time = getCurrentTime()
|
||||
setDesktopLyricInfo('play', time)
|
||||
lrc.play(time)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const setLyricOffset = offset => {
|
||||
setTempOffset(offset)
|
||||
if (isPlay.value && (musicInfo.url || playMusicInfo.listId == 'download')) {
|
||||
setTimeout(() => {
|
||||
const time = getCurrentTime()
|
||||
setDesktopLyricInfo('play', time)
|
||||
lrc.play(time)
|
||||
})
|
||||
|
@ -54,7 +71,7 @@ export default ({ setting }) => {
|
|||
|
||||
const handlePlay = () => {
|
||||
if (!musicInfo.lrc) return
|
||||
const currentTime = getCurrentTime() * 1000
|
||||
const currentTime = getCurrentTime()
|
||||
lrc.play(currentTime)
|
||||
setDesktopLyricInfo('play', currentTime)
|
||||
}
|
||||
|
@ -85,14 +102,14 @@ export default ({ setting }) => {
|
|||
lxlrc: musicInfo.lxlrc,
|
||||
isPlay: isPlay.value,
|
||||
line: lyric.line,
|
||||
played_time: getCurrentTime() * 1000,
|
||||
played_time: getCurrentTime(),
|
||||
}, info)
|
||||
break
|
||||
case 'status':
|
||||
setDesktopLyricInfo('status', {
|
||||
isPlay: isPlay.value,
|
||||
line: lyric.line,
|
||||
played_time: getCurrentTime() * 1000,
|
||||
played_time: getCurrentTime(),
|
||||
}, info)
|
||||
break
|
||||
|
||||
|
@ -102,20 +119,22 @@ export default ({ setting }) => {
|
|||
})
|
||||
|
||||
|
||||
window.eventHub.on(player.play, handlePlay)
|
||||
window.eventHub.on(player.pause, handlePause)
|
||||
window.eventHub.on(player.stop, handleStop)
|
||||
window.eventHub.on(player.error, handlePause)
|
||||
window.eventHub.on(player.setPlayInfo, setPlayInfo)
|
||||
window.eventHub.on(player.updateLyric, setLyric)
|
||||
window.eventHub.on(eventPlayerNames.play, handlePlay)
|
||||
window.eventHub.on(eventPlayerNames.pause, handlePause)
|
||||
window.eventHub.on(eventPlayerNames.stop, handleStop)
|
||||
window.eventHub.on(eventPlayerNames.error, handlePause)
|
||||
window.eventHub.on(eventPlayerNames.setPlayInfo, setPlayInfo)
|
||||
window.eventHub.on(eventPlayerNames.updateLyric, setLyric)
|
||||
window.eventHub.on(eventPlayerNames.updateLyricOffset, setLyricOffset)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
rGetDesktopLyricInfo()
|
||||
window.eventHub.off(player.play, handlePlay)
|
||||
window.eventHub.off(player.pause, handlePause)
|
||||
window.eventHub.off(player.stop, handleStop)
|
||||
window.eventHub.off(player.error, handlePause)
|
||||
window.eventHub.off(player.setPlayInfo, setPlayInfo)
|
||||
window.eventHub.off(player.updateLyric, setLyric)
|
||||
window.eventHub.off(eventPlayerNames.play, handlePlay)
|
||||
window.eventHub.off(eventPlayerNames.pause, handlePause)
|
||||
window.eventHub.off(eventPlayerNames.stop, handleStop)
|
||||
window.eventHub.off(eventPlayerNames.error, handlePause)
|
||||
window.eventHub.off(eventPlayerNames.setPlayInfo, setPlayInfo)
|
||||
window.eventHub.off(eventPlayerNames.updateLyric, setLyric)
|
||||
window.eventHub.off(eventPlayerNames.updateLyricOffset, setLyricOffset)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ import { onBeforeUnmount, useI18n } from '@renderer/utils/vueTools'
|
|||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
import { wait, waitCancel } from '@renderer/utils/tools'
|
||||
import { musicInfo, musicInfoItem, playMusicInfo } from '@renderer/core/share/player'
|
||||
import { setStop, isEmpty } from '@renderer/plugins/player'
|
||||
|
||||
export default ({
|
||||
setting,
|
||||
playNext,
|
||||
setAllStatus,
|
||||
setUrl,
|
||||
|
@ -50,7 +52,7 @@ export default ({
|
|||
|
||||
const handleLoadstart = () => {
|
||||
if (global.isPlayedStop) return
|
||||
startLoadingTimeout()
|
||||
if (setting.value.player.autoSkipOnError) startLoadingTimeout()
|
||||
setAllStatus(t('player__loading'))
|
||||
}
|
||||
|
||||
|
@ -79,6 +81,7 @@ export default ({
|
|||
if (!musicInfo.songmid) return
|
||||
clearLoadingTimeout()
|
||||
if (global.isPlayedStop) return
|
||||
if (!isEmpty()) setStop()
|
||||
if (playMusicInfo.listId != 'download' && errCode !== 1 && retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
|
||||
// console.log(this.retryNum)
|
||||
retryNum++
|
||||
|
@ -87,8 +90,10 @@ export default ({
|
|||
return
|
||||
}
|
||||
|
||||
setAllStatus(t('player__error'))
|
||||
addDelayNextTimeout()
|
||||
if (setting.value.player.autoSkipOnError) {
|
||||
setAllStatus(t('player__error'))
|
||||
addDelayNextTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSetPlayInfo = () => {
|
||||
|
|
|
@ -31,7 +31,7 @@ export default ({ setting, playNext }) => {
|
|||
if (skipTime > playProgress.maxPlayTime) skipTime = (playProgress.maxPlayTime - currentTime) / 2
|
||||
if (skipTime - mediaBuffer.playTime < 1 || playProgress.maxPlayTime - skipTime < 1) {
|
||||
mediaBuffer.playTime = 0
|
||||
playNext()
|
||||
if (setting.value.player.autoSkipOnError) playNext()
|
||||
return
|
||||
}
|
||||
startBuffering()
|
||||
|
|
|
@ -87,12 +87,14 @@ export default ({ setting }) => {
|
|||
const { addDelayNextTimeout, clearDelayNextTimeout } = useDelayNextTimeout({ playNext, timeout: 5000 })
|
||||
const { addDelayNextTimeout: addLoadTimeout, clearDelayNextTimeout: clearLoadTimeout } = useDelayNextTimeout({ playNext, timeout: 123000 })
|
||||
|
||||
let isGettingUrl = false
|
||||
const setUrl = (targetSong, isRefresh, isRetryed = false) => {
|
||||
let type = getPlayType(setting.value.player.highQuality, targetSong)
|
||||
// this.musicInfo.url = await getMusicUrl(targetSong, type)
|
||||
setAllStatus(t('player__geting_url'))
|
||||
addLoadTimeout()
|
||||
if (setting.value.player.autoSkipOnError) addLoadTimeout()
|
||||
|
||||
isGettingUrl = true
|
||||
return getUrl({
|
||||
musicInfo: targetSong,
|
||||
type,
|
||||
|
@ -113,10 +115,11 @@ export default ({ setting }) => {
|
|||
if (err.message == requestMsg.cancelRequest) return
|
||||
if (!isRetryed) return setUrl(targetSong, isRefresh, true)
|
||||
setAllStatus(err.message)
|
||||
addDelayNextTimeout()
|
||||
if (setting.value.player.autoSkipOnError) addDelayNextTimeout()
|
||||
return Promise.reject(err)
|
||||
}).finally(() => {
|
||||
clearLoadTimeout()
|
||||
if (targetSong === musicInfoItem.value) isGettingUrl = false
|
||||
})
|
||||
}
|
||||
const setImg = ({ listId, musicInfo: targetSong }) => {
|
||||
|
@ -162,6 +165,7 @@ export default ({ setting }) => {
|
|||
usePlayProgress({ setting, playNext })
|
||||
useMediaSessionInfo({ playPrev, playNext })
|
||||
usePlayEvent({
|
||||
setting,
|
||||
playNext,
|
||||
setAllStatus,
|
||||
setUrl,
|
||||
|
@ -313,10 +317,10 @@ export default ({ setting }) => {
|
|||
}
|
||||
return
|
||||
}
|
||||
setResource(filePath)
|
||||
if (!isGettingUrl) setResource(filePath)
|
||||
} else {
|
||||
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
|
||||
setUrl(musicInfoItem.value)
|
||||
if (!isGettingUrl) setUrl(musicInfoItem.value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { onTaskbarThumbarClick, setTaskbarThumbnailClip, setTaskbarThumbarButton
|
|||
// import store from '@renderer/store'
|
||||
|
||||
import { loveList, getList } from '@renderer/core/share/list'
|
||||
import { playMusicInfo } from '@renderer/core/share/player'
|
||||
import { playMusicInfo, musicInfoItem } from '@renderer/core/share/player'
|
||||
import { throttle } from '@renderer/utils'
|
||||
|
||||
export default () => {
|
||||
|
@ -82,12 +82,12 @@ export default () => {
|
|||
break
|
||||
case 'collect':
|
||||
if (!playMusicInfo.musicInfo) return
|
||||
listAdd({ id: loveList.id, musicInfo: playMusicInfo.musicInfo })
|
||||
listAdd({ id: loveList.id, musicInfo: musicInfoItem.value })
|
||||
if (updateCollectStatus()) setButtons()
|
||||
break
|
||||
case 'unCollect':
|
||||
if (!playMusicInfo.musicInfo) return
|
||||
listRemove({ listId: loveList.id, id: playMusicInfo.musicInfo.songmid })
|
||||
listRemove({ listId: loveList.id, id: musicInfoItem.value.songmid })
|
||||
if (updateCollectStatus()) setButtons()
|
||||
break
|
||||
// case 'lrc':
|
||||
|
|
|
@ -20,7 +20,7 @@ rendererInvoke(NAMES.mainWindow.get_hot_key).then(({ local, global }) => {
|
|||
})
|
||||
|
||||
eventHub.on(baseName.bindKey, () => {
|
||||
keyBind.bindKey((key, type, event, keys) => {
|
||||
keyBind.bindKey((key, eventKey, type, event, keys) => {
|
||||
// console.log(`key_${key}_${type}`)
|
||||
eventHub.emit(baseName.key_down, { event, keys, key, type })
|
||||
// console.log(event, key)
|
||||
|
@ -38,7 +38,8 @@ eventHub.on(baseName.bindKey, () => {
|
|||
eventHub.emit(appHotKeyConfig.local.keys[key].action)
|
||||
return
|
||||
}
|
||||
eventHub.emit(`key_${key}_${type}`, { event, keys, key, type })
|
||||
eventHub.emit(`key_${key}_${type}`, { event, keys, key, eventKey, type })
|
||||
if (key != eventKey) eventHub.emit(`key_${eventKey}_${type}`, { event, keys, key, eventKey, type })
|
||||
})
|
||||
registerCommonEvents()
|
||||
})
|
||||
|
|
|
@ -10,6 +10,7 @@ const names = {
|
|||
min: 'min',
|
||||
max: 'max',
|
||||
close: 'close',
|
||||
fullscreenToggle: 'fullscreenToggle',
|
||||
set_config: 'set_config',
|
||||
set_hot_key_config: 'set_hot_key_config',
|
||||
},
|
||||
|
@ -30,6 +31,7 @@ const names = {
|
|||
setPlayInfo: 'setPlayInfo', // 设置播放信息
|
||||
updatePic: 'updatePic', // 更新图片事件
|
||||
updateLyric: 'updateLyric', // 更新歌词事件
|
||||
updateLyricOffset: 'updateLyricOffset', // 更新歌词偏移
|
||||
|
||||
activeTransition: 'activeTransition', // 激活进度条动画事件
|
||||
playedStop: 'playedStop', // 定时停止事件
|
||||
|
|
|
@ -69,4 +69,7 @@ export default {
|
|||
isShowAnimation(state) {
|
||||
return state.setting.isShowAnimation
|
||||
},
|
||||
playDetailSetting(state) {
|
||||
return state.setting.playDetail
|
||||
},
|
||||
}
|
||||
|
|
|
@ -153,6 +153,7 @@ const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
|||
})
|
||||
}
|
||||
|
||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
|
@ -161,7 +162,9 @@ const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
|
|||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.catch(err => {
|
||||
return reqPromise.then(lyricInfo => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? lyricInfo : Promise.reject(new Error('failed'))
|
||||
}).catch(err => {
|
||||
// console.log(err)
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
|
@ -181,7 +184,7 @@ const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
|
|||
const getLyric = function(musicInfo, isUseOtherSource, isS2t) {
|
||||
return getLyricFromStorage(musicInfo).then(lrcInfo => {
|
||||
return (
|
||||
lrcInfo.lyric
|
||||
existTimeExp.test(lrcInfo.lyric)
|
||||
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
|
||||
: (
|
||||
isUseOtherSource
|
||||
|
@ -233,7 +236,7 @@ const saveMeta = function({ downloadInfo, filePath, isUseOtherSource, isEmbedPic
|
|||
: Promise.resolve(),
|
||||
]
|
||||
Promise.all(tasks).then(([imgUrl, lyrics = {}]) => {
|
||||
if (lyrics.lyric) lyrics.lyric = fixKgLyric(lyrics.lyric)
|
||||
if (lyrics?.lyric) lyrics.lyric = fixKgLyric(lyrics.lyric)
|
||||
setMeta(filePath, {
|
||||
title: downloadInfo.metadata.musicInfo.name,
|
||||
artist: downloadInfo.metadata.musicInfo.singer,
|
||||
|
@ -562,6 +565,7 @@ const actions = {
|
|||
if (!result) return
|
||||
downloadInfo = result
|
||||
}
|
||||
commit('setStatus', { downloadInfo, status: downloadStatus.RUN })
|
||||
|
||||
let dl = dls[downloadInfo.key]
|
||||
if (dl) {
|
||||
|
|
|
@ -30,6 +30,9 @@ const getters = {
|
|||
sources(state, getters, rootState, { sourceNames }) {
|
||||
return sources.map(item => ({ id: item.id, name: sourceNames[item.id] }))
|
||||
},
|
||||
sourceIds() {
|
||||
return sources.map(item => item.id)
|
||||
},
|
||||
boards(state) {
|
||||
return state.boards
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import musicSdk from '../../utils/music'
|
||||
import { clearLyric, clearMusicUrl } from '../../utils'
|
||||
import { sync as eventSyncName, list as eventListNames } from '@renderer/event/names'
|
||||
import { removeListPosition, setListPrevSelectId } from '@renderer/utils/data'
|
||||
import { removeListPosition, setListPrevSelectId, removeListUpdateInfo, getListPositionAll, setListPositionAll, getListUpdateInfo, setListUpdateInfo } from '@renderer/utils/data'
|
||||
import { markRawList, toRaw, markRaw } from '@renderer/utils/vueTools'
|
||||
import { allList, allListInit, setInited, removeUserList, addUserList, updateList, defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
|
||||
|
@ -31,14 +31,35 @@ const actions = {
|
|||
if (getOtherSourcePromises.has(key)) return getOtherSourcePromises.get(key)
|
||||
const promise = musicSdk.findMusic(musicInfo).then(otherSource => {
|
||||
commit('setOtherSource', { musicInfo, otherSource })
|
||||
if (getOtherSourcePromises.has(key)) getOtherSourcePromises.delete(key)
|
||||
return otherSource
|
||||
}).finally(() => {
|
||||
if (getOtherSourcePromises.has(key)) getOtherSourcePromises.delete(key)
|
||||
})
|
||||
getOtherSourcePromises.set(key, promise)
|
||||
return promise
|
||||
},
|
||||
}
|
||||
|
||||
const updateListMetaData = () => {
|
||||
const listUpdateInfos = getListUpdateInfo()
|
||||
const newListUpdateInfos = {}
|
||||
|
||||
const listPositions = getListPositionAll()
|
||||
const newListPositions = {}
|
||||
|
||||
for (const list of [defaultList, loveList, ...userLists]) {
|
||||
if (listPositions[list.id] != null) {
|
||||
newListPositions[list.id] = listPositions[list.id]
|
||||
}
|
||||
if (listUpdateInfos[list.id] != null) {
|
||||
newListUpdateInfos[list.id] = listUpdateInfos[list.id]
|
||||
}
|
||||
}
|
||||
setListPositionAll(newListPositions)
|
||||
setListUpdateInfo(newListUpdateInfos)
|
||||
}
|
||||
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
initList(state, { defaultList, loveList, userList, tempList }) {
|
||||
|
@ -59,6 +80,8 @@ const mutations = {
|
|||
// state.isInitedList = true
|
||||
setInited()
|
||||
|
||||
updateListMetaData()
|
||||
|
||||
// if (!isSync) {
|
||||
// window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
// action: 'init_list',
|
||||
|
@ -287,19 +310,11 @@ const mutations = {
|
|||
return
|
||||
}
|
||||
const targetMusicInfo = targetList.find(item => item.songmid == id)
|
||||
if (targetMusicInfo) Object.assign(targetMusicInfo, data)
|
||||
|
||||
switch (listId) {
|
||||
case defaultList.id:
|
||||
window.eventHub.emit(eventListNames.musicInfoChange, { list: targetList, ...defaultList })
|
||||
break
|
||||
case loveList.id:
|
||||
window.eventHub.emit(eventListNames.musicInfoChange, { list: targetList, ...loveList })
|
||||
break
|
||||
default:
|
||||
window.eventHub.emit(eventListNames.musicInfoChange, userLists.map(l => ({ list: allList[l.id], ...l })))
|
||||
break
|
||||
}
|
||||
if (!targetMusicInfo) return
|
||||
Object.assign(targetMusicInfo, data)
|
||||
const targetListInfo = [defaultList, loveList, ...userLists].find(l => l.id == listId)
|
||||
if (!targetListInfo) return
|
||||
window.eventHub.emit(eventListNames.musicInfoChange, targetListInfo)
|
||||
},
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position, isSync }) {
|
||||
if (!isSync) {
|
||||
|
@ -337,6 +352,7 @@ const mutations = {
|
|||
if (index < 0) return
|
||||
removeUserList(id)
|
||||
removeListPosition(id)
|
||||
removeListUpdateInfo(id)
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
setUserListName(state, { id, name, isSync }) {
|
||||
|
|
|
@ -150,6 +150,7 @@ const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
|||
})
|
||||
})
|
||||
}
|
||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
|
@ -158,7 +159,9 @@ const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
|||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.catch(err => {
|
||||
return reqPromise.then(lyricInfo => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? lyricInfo : Promise.reject(new Error('failed'))
|
||||
}).catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
|
@ -213,7 +216,7 @@ const actions = {
|
|||
const lrcInfo = await getStoreLyric(musicInfo)
|
||||
// lrcInfo = {}
|
||||
// if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
|
||||
if (lrcInfo.lyric && lrcInfo.tlyric != null) {
|
||||
if (existTimeExp.test(lrcInfo.lyric) && lrcInfo.tlyric != null) {
|
||||
// if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
||||
// let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
|
||||
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
|
|
|
@ -52,7 +52,7 @@ sources.forEach(source => {
|
|||
// getters
|
||||
const getters = {
|
||||
sourceInfo(state, getters, rootState, { sourceNames }) {
|
||||
return { sources: sources.map(item => ({ id: item.id, name: sourceNames[item.id] })), sortList }
|
||||
return { sourceIds: sources.map(item => item.id), sources: sources.map(item => ({ id: item.id, name: sourceNames[item.id] })), sortList }
|
||||
},
|
||||
tags: state => state.tags,
|
||||
isVisibleListDetail: state => state.isVisibleListDetail,
|
||||
|
|
|
@ -35,6 +35,9 @@ export default {
|
|||
state.setting.player.volume = val
|
||||
}
|
||||
},
|
||||
setPlayDetailLyricAlign(state, val) {
|
||||
state.setting.playDetail.style.align = val
|
||||
},
|
||||
setPlayDetailLyricFont(state, val) {
|
||||
state.setting.playDetail.style.fontSize = val
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@renderer/util
|
|||
import { scrollTo, throttle, formatPlayTime2 } from '@renderer/utils'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
|
||||
export default ({ isPlay, lyric }) => {
|
||||
export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
|
||||
const dom_lyric = ref(null)
|
||||
const dom_lyric_text = ref(null)
|
||||
const dom_skip_line = ref(null)
|
||||
|
@ -28,7 +28,9 @@ export default ({ isPlay, lyric }) => {
|
|||
if (time == -1) return
|
||||
handleSkipMouseLeave()
|
||||
isStopScroll.value = false
|
||||
window.eventHub.emit(eventPlayerNames.setProgress, time)
|
||||
let offset = lyric.offset + lyric.tempOffset
|
||||
if (offset) offset = offset / 1000
|
||||
window.eventHub.emit(eventPlayerNames.setProgress, Math.max(time + offset, 0))
|
||||
if (!isPlay.value) window.eventHub.emit(eventPlayerNames.setPlay)
|
||||
}
|
||||
const handleSkipMouseEnter = () => {
|
||||
|
@ -40,13 +42,11 @@ export default ({ isPlay, lyric }) => {
|
|||
startLyricScrollTimeout()
|
||||
}
|
||||
|
||||
const setTime = throttle(() => {
|
||||
if (point.x == null) {
|
||||
if (!dom_skip_line.value) return
|
||||
const rect = dom_skip_line.value.getBoundingClientRect()
|
||||
point.x = rect.x
|
||||
point.y = rect.y
|
||||
}
|
||||
const throttleSetTime = throttle(() => {
|
||||
if (!dom_skip_line.value) return
|
||||
const rect = dom_skip_line.value.getBoundingClientRect()
|
||||
point.x = rect.x
|
||||
point.y = rect.y
|
||||
let dom = document.elementFromPoint(point.x, point.y)
|
||||
if (dom_pre_line === dom) return
|
||||
if (dom.tagName == 'SPAN') {
|
||||
|
@ -70,6 +70,9 @@ export default ({ isPlay, lyric }) => {
|
|||
}
|
||||
dom_pre_line = dom
|
||||
})
|
||||
const setTime = () => {
|
||||
if (isShowLyricProgressSetting.value) throttleSetTime()
|
||||
}
|
||||
|
||||
const handleScrollLrc = (duration = 300) => {
|
||||
if (!dom_lines?.length || !dom_lyric.value) return
|
||||
|
@ -184,14 +187,15 @@ export default ({ isPlay, lyric }) => {
|
|||
watch(() => lyric.line, scrollLine)
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousemove', handleMouseMsMove)
|
||||
document.addEventListener('mouseup', handleMouseMsUp)
|
||||
|
||||
initLrc(lyric.lines, null)
|
||||
nextTick(() => {
|
||||
scrollLine(lyric.line)
|
||||
})
|
||||
})
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMsMove)
|
||||
document.addEventListener('mouseup', handleMouseMsUp)
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('mousemove', handleMouseMsMove)
|
||||
document.removeEventListener('mouseup', handleMouseMsUp)
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import { onMounted, onBeforeUnmount, watch, reactive, ref, nextTick } from '@renderer/utils/vueTools'
|
||||
|
||||
export default ({ visible, location, onHide }) => {
|
||||
const transition1 = 'transform, opacity'
|
||||
const transition2 = 'transform, opacity, top, left'
|
||||
let show = false
|
||||
const dom_menu = ref(null)
|
||||
const menuStyles = reactive({
|
||||
left: 0,
|
||||
top: 0,
|
||||
opacity: 0,
|
||||
transitionProperty: 'transform, opacity',
|
||||
transform: 'scale(0) translate(0,0)',
|
||||
})
|
||||
|
||||
const handleShow = () => {
|
||||
show = true
|
||||
menuStyles.opacity = 1
|
||||
menuStyles.transform = `scaleY(1) translate(${handleGetOffsetXY(location.value.x, location.value.y)})`
|
||||
}
|
||||
const handleHide = () => {
|
||||
menuStyles.opacity = 0
|
||||
menuStyles.transform = 'scale(0) translate(0, 0)'
|
||||
show = false
|
||||
}
|
||||
const handleGetOffsetXY = (left, top) => {
|
||||
const listWidth = dom_menu.value.clientWidth
|
||||
const listHeight = dom_menu.value.clientHeight
|
||||
const dom_container_parant = dom_menu.value.offsetParent
|
||||
const containerWidth = dom_container_parant.clientWidth
|
||||
const containerHeight = dom_container_parant.clientHeight
|
||||
const offsetWidth = containerWidth - left - listWidth
|
||||
const offsetHeight = containerHeight - top - listHeight
|
||||
let x = 0
|
||||
let y = 0
|
||||
if (containerWidth > listWidth && offsetWidth < 17) {
|
||||
x = offsetWidth - 17
|
||||
}
|
||||
if (containerHeight > listHeight && offsetHeight < 5) {
|
||||
y = offsetHeight - 5
|
||||
}
|
||||
return `${x}px, ${y}px`
|
||||
}
|
||||
const handleDocumentClick = (event) => {
|
||||
if (!show) return
|
||||
|
||||
if (event.target == dom_menu.value || dom_menu.value.contains(event.target)) return
|
||||
|
||||
if (show && menuStyles.transitionProperty != transition1) menuStyles.transitionProperty = transition1
|
||||
|
||||
onHide()
|
||||
}
|
||||
|
||||
watch(visible, visible => {
|
||||
visible ? handleShow() : handleHide()
|
||||
}, { immediate: true })
|
||||
|
||||
watch(location, location => {
|
||||
menuStyles.left = location.x + 2 + 'px'
|
||||
menuStyles.top = location.y + 'px'
|
||||
nextTick(() => {
|
||||
if (show) {
|
||||
if (menuStyles.transitionProperty != transition2) menuStyles.transitionProperty = transition2
|
||||
menuStyles.transform = `scaleY(1) translate(${handleGetOffsetXY(location.x, location.y)})`
|
||||
}
|
||||
})
|
||||
}, { deep: true })
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleDocumentClick)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleDocumentClick)
|
||||
})
|
||||
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { setListUpdateTime } from '@renderer/utils/data'
|
||||
import { setFetchingListStatus } from '@renderer/core/share/list'
|
||||
import { useAction, useCommit } from '@renderer/utils/vueTools'
|
||||
|
||||
export default () => {
|
||||
const getBoardListAll = useAction('leaderboard', 'getListAll')
|
||||
const getListDetailAll = useAction('songList', 'getListDetailAll')
|
||||
const setList = useCommit('list', 'setList')
|
||||
|
||||
const fetchList = (id, source, sourceListId) => {
|
||||
setFetchingListStatus(id, true)
|
||||
|
||||
let promise
|
||||
if (/board__/.test(sourceListId)) {
|
||||
const id = sourceListId.replace(/board__/, '')
|
||||
promise = getBoardListAll({ id, isRefresh: true })
|
||||
} else {
|
||||
promise = getListDetailAll({ source, id: sourceListId, isRefresh: true })
|
||||
}
|
||||
return promise.finally(() => {
|
||||
setFetchingListStatus(id, false)
|
||||
})
|
||||
}
|
||||
|
||||
return async targetListInfo => {
|
||||
const list = await fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
|
||||
// console.log(targetListInfo.list.length, list.length)
|
||||
setList({
|
||||
...targetListInfo,
|
||||
list,
|
||||
})
|
||||
setListUpdateTime(targetListInfo.id, Date.now())
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import { throttle } from './index'
|
|||
|
||||
let listPosition = {}
|
||||
let listPrevSelectId
|
||||
let listUpdateInfo = {}
|
||||
|
||||
const saveListPosition = throttle(() => {
|
||||
rendererSend(NAMES.mainWindow.save_data, {
|
||||
|
@ -18,6 +19,11 @@ export const initListPosition = () => {
|
|||
})
|
||||
}
|
||||
export const getListPosition = id => listPosition[id] || 0
|
||||
export const getListPositionAll = () => listPosition
|
||||
export const setListPositionAll = positions => {
|
||||
listPosition = positions
|
||||
saveListPosition()
|
||||
}
|
||||
export const setListPosition = (id, position) => {
|
||||
listPosition[id] = position || 0
|
||||
saveListPosition()
|
||||
|
@ -43,3 +49,43 @@ export const setListPrevSelectId = id => {
|
|||
listPrevSelectId = id
|
||||
saveListPrevSelectId()
|
||||
}
|
||||
|
||||
export const initListUpdateInfo = () => {
|
||||
return rendererInvoke(NAMES.mainWindow.get_data, 'listUpdateInfo').then(data => {
|
||||
if (!data) data = {}
|
||||
// console.log(data)
|
||||
listUpdateInfo = data
|
||||
})
|
||||
}
|
||||
const saveListUpdateInfo = throttle(() => {
|
||||
rendererSend(NAMES.mainWindow.save_data, {
|
||||
path: 'listUpdateInfo',
|
||||
data: listUpdateInfo,
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
export const getListUpdateInfo = () => listUpdateInfo
|
||||
export const setListUpdateInfo = info => {
|
||||
listUpdateInfo = info
|
||||
saveListUpdateInfo()
|
||||
}
|
||||
export const setListAutoUpdate = (id, enable) => {
|
||||
const targetInfo = listUpdateInfo[id] ?? { updateTime: '', isAutoUpdate: false }
|
||||
targetInfo.isAutoUpdate = enable
|
||||
listUpdateInfo[id] = targetInfo
|
||||
saveListUpdateInfo()
|
||||
}
|
||||
export const setListUpdateTime = (id, time) => {
|
||||
const targetInfo = listUpdateInfo[id] ?? { updateTime: '', isAutoUpdate: false }
|
||||
targetInfo.updateTime = time
|
||||
listUpdateInfo[id] = targetInfo
|
||||
saveListUpdateInfo()
|
||||
}
|
||||
// export const setListUpdateInfo = (id, { updateTime, isAutoUpdate }) => {
|
||||
// listUpdateInfo[id] = { updateTime, isAutoUpdate }
|
||||
// saveListUpdateInfo()
|
||||
// }
|
||||
export const removeListUpdateInfo = id => {
|
||||
delete listUpdateInfo[id]
|
||||
saveListUpdateInfo()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { shell, clipboard } from 'electron'
|
||||
import { httpOverHttp, httpsOverHttp } from 'tunnel'
|
||||
import crypto from 'crypto'
|
||||
import { rendererSend, rendererInvoke, NAMES } from '@common/ipc'
|
||||
import { log } from '@common/utils'
|
||||
|
@ -451,12 +452,33 @@ export const setWindowSize = (width, height) => rendererSend(NAMES.mainWindow.se
|
|||
|
||||
export const getProxyInfo = () => {
|
||||
return proxy.enable && proxy.host
|
||||
? `http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port};`
|
||||
? `http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port}`
|
||||
: proxy.envProxy
|
||||
? `http://${proxy.envProxy.host}:${proxy.envProxy.port};`
|
||||
? `http://${proxy.envProxy.host}:${proxy.envProxy.port}`
|
||||
: undefined
|
||||
}
|
||||
|
||||
const httpsRxp = /^https:/
|
||||
export const getRequestAgent = url => {
|
||||
let options
|
||||
if (proxy.enable && proxy.host) {
|
||||
options = {
|
||||
proxy: {
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
},
|
||||
}
|
||||
} else if (proxy.envProxy) {
|
||||
options = {
|
||||
proxy: {
|
||||
host: proxy.envProxy.host,
|
||||
port: proxy.envProxy.port,
|
||||
},
|
||||
}
|
||||
}
|
||||
return options ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)(options) : undefined
|
||||
}
|
||||
|
||||
|
||||
export const assertApiSupport = source => qualityList.value[source] != undefined
|
||||
|
||||
|
@ -491,11 +513,17 @@ export const parseUrlParams = str => {
|
|||
}
|
||||
|
||||
export const getLyric = musicInfo => rendererInvoke(NAMES.mainWindow.get_lyric, `${musicInfo.source}_${musicInfo.songmid}`)
|
||||
export const setLyric = (musicInfo, { lyric, tlyric, lxlyric }) => rendererSend(NAMES.mainWindow.save_lyric, {
|
||||
export const setLyric = (musicInfo, { lyric, tlyric, lxlyric }) => rendererSend(NAMES.mainWindow.save_lyric_raw, {
|
||||
id: `${musicInfo.source}_${musicInfo.songmid}`,
|
||||
lyrics: { lyric, tlyric, lxlyric },
|
||||
})
|
||||
export const clearLyric = () => rendererSend(NAMES.mainWindow.clear_lyric)
|
||||
export const setLyricEdited = (musicInfo, { lyric, tlyric, lxlyric }) => rendererSend(NAMES.mainWindow.save_lyric_edited, {
|
||||
id: `${musicInfo.source}_${musicInfo.songmid}`,
|
||||
lyrics: { lyric, tlyric, lxlyric },
|
||||
})
|
||||
export const removeLyricEdited = musicInfo => rendererSend(NAMES.mainWindow.remove_lyric_edited, `${musicInfo.source}_${musicInfo.songmid}`)
|
||||
|
||||
export const clearLyric = () => rendererSend(NAMES.mainWindow.clear_lyric_raw)
|
||||
|
||||
export const getMusicUrl = (musicInfo, type) => rendererInvoke(NAMES.mainWindow.get_music_url, `${musicInfo.source}_${musicInfo.songmid}_${type}`)
|
||||
export const setMusicUrl = (musicInfo, type, url) => rendererSend(NAMES.mainWindow.save_music_url, {
|
||||
|
|
|
@ -3,12 +3,15 @@ import { isMac } from '../../common/utils'
|
|||
const downKeys = new Set()
|
||||
|
||||
const handleEvent = (type, event, keys) => {
|
||||
let eventKey = event.key
|
||||
if (isMac) {
|
||||
let index = keys.indexOf('meta')
|
||||
if (index > -1) keys.splice(index, 1, 'mod')
|
||||
if (eventKey == 'Meta') eventKey = 'mod'
|
||||
} else {
|
||||
let index = keys.indexOf('ctrl')
|
||||
if (index > -1) keys.splice(index, 1, 'mod')
|
||||
if (eventKey == 'Control') eventKey = 'mod'
|
||||
}
|
||||
let key = keys.join('+')
|
||||
|
||||
|
@ -20,7 +23,7 @@ const handleEvent = (type, event, keys) => {
|
|||
downKeys.delete(key)
|
||||
break
|
||||
}
|
||||
handleSendEvent(key, type, event, keys)
|
||||
handleSendEvent(key, eventKey, type, event, keys)
|
||||
}
|
||||
|
||||
// 修饰键处理
|
||||
|
|
|
@ -34,23 +34,19 @@ module.exports = class Lyric {
|
|||
|
||||
this.playingLineNum = -1
|
||||
this.isLineMode = false
|
||||
|
||||
this.linePlayer = new LinePlayer({
|
||||
offset: this.offset,
|
||||
onPlay: this._handleLinePlayerOnPlay,
|
||||
onSetLyric: this._handleLinePlayerOnSetLyric,
|
||||
})
|
||||
}
|
||||
|
||||
_init() {
|
||||
this.playingLineNum = -1
|
||||
this.isLineMode = false
|
||||
|
||||
if (this.linePlayer) {
|
||||
this.linePlayer.setLyric(this.lyric, this.translationLyric)
|
||||
} else {
|
||||
this.linePlayer = new LinePlayer({
|
||||
lyric: this.lyric,
|
||||
translationLyric: this.translationLyric,
|
||||
offset: this.offset,
|
||||
onPlay: this._handleLinePlayerOnPlay,
|
||||
onSetLyric: this._handleLinePlayerOnSetLyric,
|
||||
})
|
||||
}
|
||||
this.linePlayer.setLyric(this.lyric, this.translationLyric)
|
||||
}
|
||||
|
||||
_handleLinePlayerOnPlay = (num, text, curTime) => {
|
||||
|
@ -97,7 +93,7 @@ module.exports = class Lyric {
|
|||
this.onPlay(num, this._lines[num].text)
|
||||
}
|
||||
|
||||
_handleLinePlayerOnSetLyric = lyricLines => {
|
||||
_handleLinePlayerOnSetLyric = (lyricLines, offset) => {
|
||||
// console.log(lyricLines)
|
||||
// this._lines = lyricsLines
|
||||
this.isLineMode = lyricLines.length && !/^<\d+,\d+>/.test(lyricLines[0].text)
|
||||
|
@ -148,7 +144,11 @@ module.exports = class Lyric {
|
|||
})
|
||||
}
|
||||
|
||||
this.onSetLyric(this._lines)
|
||||
// 如果是逐行歌词,则添加 60ms 的偏移
|
||||
let newOffset = this.isLineMode ? this.offset + 60 : this.offset
|
||||
offset = offset - this.linePlayer.offset + newOffset
|
||||
this.linePlayer.offset = newOffset
|
||||
this.onSetLyric(this._lines, offset)
|
||||
}
|
||||
|
||||
play(curTime) {
|
||||
|
@ -166,6 +166,5 @@ module.exports = class Lyric {
|
|||
this.lyric = lyric
|
||||
this.translationLyric = translationLyric
|
||||
this._init()
|
||||
this.linePlayer.offset = this.isLineMode ? this.offset + 90 : this.offset
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,7 @@ const tagRegMap = {
|
|||
const timeoutTools = new TimeoutTools()
|
||||
|
||||
module.exports = class LinePlayer {
|
||||
constructor({ lyric = '', translationLyric = '', offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
|
||||
this.lyric = lyric
|
||||
this.translationLyric = translationLyric
|
||||
constructor({ offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
|
||||
this.tags = {}
|
||||
this.lines = null
|
||||
this.translationLines = null
|
||||
|
@ -26,7 +24,6 @@ module.exports = class LinePlayer {
|
|||
this.offset = offset
|
||||
this._performanceTime = 0
|
||||
this._startTime = 0
|
||||
this._init()
|
||||
}
|
||||
|
||||
_init() {
|
||||
|
@ -34,7 +31,7 @@ module.exports = class LinePlayer {
|
|||
if (this.translationLyric == null) this.translationLyric = ''
|
||||
this._initTag()
|
||||
this._initLines()
|
||||
this.onSetLyric(this.lines)
|
||||
this.onSetLyric(this.lines, this.tags.offset + this.offset)
|
||||
}
|
||||
|
||||
_initTag() {
|
||||
|
|
|
@ -101,6 +101,10 @@ export default {
|
|||
(
|
||||
item.lowerCaseName === lowerCaseName && item.lowerCaseAlbumName === lowerCaseAlbumName &&
|
||||
item.interval === musicInfo.interval
|
||||
) ||
|
||||
(
|
||||
item.lowerCaseName === lowerCaseName && item.lowerCaseAlbumName === lowerCaseAlbumName &&
|
||||
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
||||
)
|
||||
) {
|
||||
return item
|
||||
|
|
|
@ -91,6 +91,7 @@ const buildParams = (id, isGetLyricx) => {
|
|||
// console.log(buildParams('207527604', true))
|
||||
|
||||
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
export default {
|
||||
sortLrcArr(arr) {
|
||||
const lrcSet = new Set()
|
||||
|
@ -201,6 +202,7 @@ export default {
|
|||
lrcInfo.lxlyric = ''
|
||||
}
|
||||
lrcInfo.lyric = lrcInfo.lyric.replace(lrcTools.rxps.wordTimeAll, '')
|
||||
if (!existTimeExp.test(lrcInfo.lyric)) return Promise.reject(new Error('Get lyric failed'))
|
||||
// console.log(lrcInfo)
|
||||
return lrcInfo
|
||||
})
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { httpFetch } from '../../request'
|
||||
import { weapi } from './utils/crypto'
|
||||
// import { sizeFormate, formatPlayTime } from '../../index'
|
||||
import musicDetailApi from './musicDetail'
|
||||
// import { httpFetch } from '../../request'
|
||||
// import { weapi } from './utils/crypto'
|
||||
import { sizeFormate, formatPlayTime } from '../../index'
|
||||
// import musicDetailApi from './musicDetail'
|
||||
import { eapiRequest } from './utils'
|
||||
|
||||
let searchRequest
|
||||
export default {
|
||||
|
@ -11,39 +12,14 @@ export default {
|
|||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
|
||||
searchRequest = httpFetch('https://music.163.com/weapi/search/get', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
|
||||
origin: 'https://music.163.com',
|
||||
},
|
||||
form: weapi({
|
||||
s: str,
|
||||
type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
|
||||
limit,
|
||||
offset: limit * (page - 1),
|
||||
}),
|
||||
})
|
||||
return searchRequest.promise.then(({ body }) => {
|
||||
// console.log(body)
|
||||
return body && body.code === 200
|
||||
? musicDetailApi.getList(body.result.songs.map(s => s.id)).then(({ list }) => {
|
||||
this.total = body.result.songCount || 0
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
return {
|
||||
code: 200,
|
||||
data: {
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'wy',
|
||||
},
|
||||
}
|
||||
})
|
||||
: body
|
||||
searchRequest = eapiRequest('/api/cloudsearch/pc', {
|
||||
s: str,
|
||||
type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
|
||||
limit,
|
||||
total: page == 1,
|
||||
offset: limit * (page - 1),
|
||||
})
|
||||
return searchRequest.promise.then(({ body }) => body)
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
|
@ -52,7 +28,7 @@ export default {
|
|||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
/* handleResult(rawList) {
|
||||
handleResult(rawList) {
|
||||
// console.log(rawList)
|
||||
if (!rawList) return []
|
||||
return rawList.map(item => {
|
||||
|
@ -102,29 +78,29 @@ export default {
|
|||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
}, */
|
||||
},
|
||||
search(str, page = 1, { limit } = {}, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
if (limit == null) limit = this.limit
|
||||
return this.musicSearch(str, page, limit).then(result => {
|
||||
// console.log(result)
|
||||
if (!result || result.code !== 200) return this.search(str, page, { limit }, retryNum)
|
||||
// let list = this.handleResult(result.result.songs || [])
|
||||
let list = this.handleResult(result.result.songs || [])
|
||||
|
||||
// if (list == null) return this.search(str, page, { limit }, retryNum)
|
||||
if (list == null) return this.search(str, page, { limit }, retryNum)
|
||||
|
||||
// this.total = result.result.songCount || 0
|
||||
// this.page = page
|
||||
// this.allPage = Math.ceil(this.total / this.limit)
|
||||
this.total = result.result.songCount || 0
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / this.limit)
|
||||
|
||||
// return Promise.resolve({
|
||||
// list,
|
||||
// allPage: this.allPage,
|
||||
// limit: this.limit,
|
||||
// total: this.total,
|
||||
// source: 'wy',
|
||||
// })
|
||||
return result.data
|
||||
return {
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit: this.limit,
|
||||
total: this.total,
|
||||
source: 'wy',
|
||||
}
|
||||
// return result.data
|
||||
})
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/util/crypto.js
|
||||
import { createCipheriv, publicEncrypt, constants, randomBytes } from 'crypto'
|
||||
import { createCipheriv, createDecipheriv, publicEncrypt, randomBytes, createHash, constants } from 'crypto'
|
||||
const iv = Buffer.from('0102030405060708')
|
||||
const presetKey = Buffer.from('0CoJUm6Qyw8W8jud')
|
||||
const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q')
|
||||
const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----'
|
||||
const eapiKey = 'e82ckenh8dichen8'
|
||||
|
||||
const aesEncrypt = (buffer, mode, key, iv) => {
|
||||
const cipher = createCipheriv('aes-128-' + mode, key, iv)
|
||||
const cipher = createCipheriv(mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
}
|
||||
|
||||
const aesDecrypt = function(cipherBuffer, mode, key, iv) {
|
||||
let decipher = createDecipheriv(mode, key, iv)
|
||||
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()])
|
||||
}
|
||||
|
||||
const rsaEncrypt = (buffer, key) => {
|
||||
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
|
||||
return publicEncrypt({ key: key, padding: constants.RSA_NO_PADDING }, buffer)
|
||||
|
@ -20,7 +26,7 @@ export const weapi = object => {
|
|||
const text = JSON.stringify(object)
|
||||
const secretKey = randomBytes(16).map(n => (base62.charAt(n % 62).charCodeAt()))
|
||||
return {
|
||||
params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64')), 'cbc', secretKey, iv).toString('base64'),
|
||||
params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'aes-128-cbc', presetKey, iv).toString('base64')), 'aes-128-cbc', secretKey, iv).toString('base64'),
|
||||
encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'),
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +34,21 @@ export const weapi = object => {
|
|||
export const linuxapi = object => {
|
||||
const text = JSON.stringify(object)
|
||||
return {
|
||||
eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '').toString('hex').toUpperCase(),
|
||||
eparams: aesEncrypt(Buffer.from(text), 'aes-128-ecb', linuxapiKey, '').toString('hex').toUpperCase(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const eapi = (url, object) => {
|
||||
const text = typeof object === 'object' ? JSON.stringify(object) : object
|
||||
const message = `nobody${url}use${text}md5forencrypt`
|
||||
const digest = createHash('md5').update(message).digest('hex')
|
||||
const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`
|
||||
return {
|
||||
params: aesEncrypt(Buffer.from(data), 'aes-128-ecb', eapiKey, '').toString('hex').toUpperCase(),
|
||||
}
|
||||
}
|
||||
|
||||
export const eapiDecrypt = cipherBuffer => {
|
||||
return aesDecrypt(cipherBuffer, 'aes-128-ecb', eapiKey, '').toString()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { httpFetch } from '../../../request'
|
||||
import { eapi } from './crypto'
|
||||
|
||||
export const eapiRequest = (url, data) => {
|
||||
return httpFetch('http://interface.music.163.com/eapi/batch', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
|
||||
origin: 'https://music.163.com',
|
||||
// cookie: 'os=pc; deviceId=A9C064BB4584D038B1565B58CB05F95290998EE8B025AA2D07AE; osver=Microsoft-Windows-10-Home-China-build-19043-64bit; appver=2.5.2.197409; channel=netease; MUSIC_A=37a11f2eb9de9930cad479b2ad495b0e4c982367fb6f909d9a3f18f876c6b49faddb3081250c4980dd7e19d4bd9bf384e004602712cf2b2b8efaafaab164268a00b47359f85f22705cc95cb6180f3aee40f5be1ebf3148d888aa2d90636647d0c3061cd18d77b7a0; __csrf=05b50d54082694f945d7de75c210ef94; mode=Z7M-KP5(7)GZ; NMTID=00OZLp2VVgq9QdwokUgq3XNfOddQyIAAAF_6i8eJg; ntes_kaola_ad=1',
|
||||
},
|
||||
form: eapi(url, data),
|
||||
})
|
||||
// requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
// // console.log(raw)
|
||||
// console.log(body)
|
||||
// // console.log(eapiDecrypt(raw))
|
||||
// // return eapiDecrypt(raw)
|
||||
// return body
|
||||
// })
|
||||
// return requestObj
|
||||
}
|
|
@ -4,7 +4,7 @@ import { debugRequest } from './env'
|
|||
import { requestMsg } from './message'
|
||||
import { bHh } from './music/options'
|
||||
import { deflateRaw } from 'zlib'
|
||||
import { getProxyInfo } from './index'
|
||||
import { getRequestAgent } from './index'
|
||||
// import fs from 'fs'
|
||||
|
||||
const request = (url, options, callback) => {
|
||||
|
@ -277,7 +277,7 @@ const fetchData = async(url, method, {
|
|||
method,
|
||||
headers: Object.assign({}, defaultHeaders, headers),
|
||||
timeout,
|
||||
proxy: getProxyInfo(),
|
||||
agent: getRequestAgent(url),
|
||||
json: format === 'json',
|
||||
}, (err, resp, body) => {
|
||||
if (err) return callback(err, null)
|
||||
|
|
|
@ -78,7 +78,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
...mapGetters('leaderboard', ['sources', 'boards', 'info']),
|
||||
...mapGetters('leaderboard', ['sources', 'sourceIds', 'boards', 'info']),
|
||||
boardList() {
|
||||
return this.source && this.boards[this.source] ? this.boards[this.source] : []
|
||||
},
|
||||
|
@ -129,6 +129,7 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.source = this.setting.leaderboard.source
|
||||
if (!this.sourceIds.includes(this.source)) this.source = this.sourceIds[0]
|
||||
this.tabId = this.setting.leaderboard.tabId
|
||||
this.page = this.listInfo.page
|
||||
},
|
||||
|
|
|
@ -43,7 +43,7 @@ div(:class="$style.search")
|
|||
p {{$t('list__loading')}}
|
||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
div(v-show="!isLoading && !listInfo.list.length" :class="$style.noitem")
|
||||
div.scroll(:class="$style.noitemListContainer" v-if="setting.search.isShowHotSearch || (setting.search.isShowHistorySearch && setting.search.isShowHistorySearch.length)")
|
||||
div.scroll(:class="$style.noitemListContainer" v-if="setting.search.isShowHotSearch || (setting.search.isShowHistorySearch && historyList.length)")
|
||||
dl(:class="[$style.noitemList, $style.noitemHotSearchList]" v-if="setting.search.isShowHotSearch")
|
||||
dt(:class="$style.noitemListTitle") {{$t('search__hot_search')}}
|
||||
dd(:class="$style.noitemListItem" @click="handleNoitemSearch(item)" v-for="item in hotSearchList") {{item}}
|
||||
|
@ -66,7 +66,7 @@ div(:class="$style.search")
|
|||
|
||||
<script>
|
||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { scrollTo, clipboardWriteText, assertApiSupport, openUrl } from '@renderer/utils'
|
||||
import { clipboardWriteText, assertApiSupport, openUrl } from '@renderer/utils'
|
||||
import musicSdk from '@renderer/utils/music'
|
||||
import { defaultList } from '@renderer/core/share/list'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
|
@ -264,7 +264,7 @@ export default {
|
|||
this.search({ text, page, limit: this.listInfo.limit }).then(data => {
|
||||
this.page = page
|
||||
this.$nextTick(() => {
|
||||
scrollTo(this.$refs.dom_scrollContent, 0)
|
||||
this.$refs.dom_scrollContent.scrollTo(0, 0)
|
||||
})
|
||||
}).finally(() => {
|
||||
this.isLoading = false
|
||||
|
@ -569,6 +569,8 @@ export default {
|
|||
.tbody {
|
||||
flex: auto;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
td {
|
||||
font-size: 12px;
|
||||
:global(.badge) {
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
<template>
|
||||
<material-modal :show="visible" @close="$emit('update:visible', false)" bg-close teleport="#view">
|
||||
<div :class="$style.header">
|
||||
<h2>{{$t('list_update_modal__title')}}</h2>
|
||||
</div>
|
||||
<main class="scroll" :class="$style.main">
|
||||
<ul ref="dom_list" v-if="lists.length" :class="$style.list">
|
||||
<li v-for="(list, index) in lists" :key="list.id" :class="[$style.listItem, {[$style.fetching]: fetchingListStatus[list.id]}]">
|
||||
<div :class="$style.listLeft">
|
||||
<h3 :class="$style.text">{{list.name}} <span :class="$style.label">{{list.source}}</span></h3>
|
||||
<div>
|
||||
<base-checkbox :class="$style.checkbox" :id="`list_auto_update_${list.id}`" :modelValue="autoUpdate[list.id] == true"
|
||||
@change="handleChangeAutoUpdate(list, $event)" :label="$t('list_update_modal__auto_update')" />
|
||||
<span :class="$style.label" style="vertical-align: text-top;">{{listUpdateTimes[list.id]}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.btns">
|
||||
<button :class="$style.btn" :disabled="fetchingListStatus[list.id]" outline="outline" :aria-label="$t('list_update_modal__update')" @click.stop="handleUpdate(index)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" style="transform: rotate(45deg);" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-refresh" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div :class="$style.noItem" v-else>
|
||||
<p v-text="$t('no_item')"></p>
|
||||
</div>
|
||||
</main>
|
||||
<div :class="$style.footer">
|
||||
<div :class="$style.tips">{{$t('list_update_modal__tips')}}</div>
|
||||
</div>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, reactive } from '@renderer/utils/vueTools'
|
||||
import { userLists, fetchingListStatus } from '@renderer/core/share/list'
|
||||
import musicSdk from '@renderer/utils/music'
|
||||
import { getListUpdateInfo, setListAutoUpdate } from '@renderer/utils/data'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
listUpdateTimes: Object,
|
||||
},
|
||||
emits: ['update-list', 'update:visible'],
|
||||
setup(props, { emit }) {
|
||||
const lists = computed(() => userLists.filter(l => !!l.source && !!musicSdk[l.source]?.songList))
|
||||
const autoUpdate = reactive({})
|
||||
for (const [id, value] of Object.entries(getListUpdateInfo())) {
|
||||
autoUpdate[id] = value.isAutoUpdate == true
|
||||
}
|
||||
|
||||
const handleChangeAutoUpdate = (list, enable) => {
|
||||
setListAutoUpdate(list.id, enable)
|
||||
autoUpdate[list.id] = enable
|
||||
}
|
||||
const handleUpdate = index => {
|
||||
emit('update-list', lists.value[index])
|
||||
}
|
||||
return {
|
||||
lists,
|
||||
autoUpdate,
|
||||
fetchingListStatus,
|
||||
handleUpdate,
|
||||
handleChangeAutoUpdate,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
@width: 460px;
|
||||
|
||||
.header {
|
||||
flex: none;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
h2 {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.main {
|
||||
min-height: 200px;
|
||||
width: @width;
|
||||
}
|
||||
|
||||
.list {
|
||||
// background-color: @color-search-form-background;
|
||||
font-size: 13px;
|
||||
transition-property: height;
|
||||
position: relative;
|
||||
.listItem {
|
||||
position: relative;
|
||||
padding: 15px 10px 15px 15px;
|
||||
transition: .3s ease;
|
||||
transition-property: background-color, opacity;
|
||||
line-height: 1.3;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
// border-radius: 4px;
|
||||
// &:last-child {
|
||||
// border-bottom-left-radius: 4px;
|
||||
// border-bottom-right-radius: 4px;
|
||||
// }
|
||||
&.fetching {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.listLeft {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: auto;
|
||||
margin-bottom: 2px;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.checkbox {
|
||||
margin-top: 3px;
|
||||
font-size: 14px;
|
||||
opacity: .86;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
padding: 0 10px;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// transform: rotate(45deg);
|
||||
// background-color:
|
||||
}
|
||||
.btns {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: @form-radius;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
padding: 4px 7px;
|
||||
color: @color-btn;
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
line-height: 0;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: @width;
|
||||
}
|
||||
.tips {
|
||||
padding: 8px 15px;
|
||||
font-size: 13px;
|
||||
line-height: 1.25;
|
||||
color: @color-theme_2-font;
|
||||
}
|
||||
|
||||
.no-item {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.listItem {
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
.note {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.no-item {
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
|
@ -2,11 +2,18 @@
|
|||
<div :class="$style.lists" ref="dom_lists">
|
||||
<div :class="$style.listHeader">
|
||||
<h2 :class="$style.listsTitle">{{$t('my_list')}}</h2>
|
||||
<button :class="$style.listsAdd" @click="handleShowNewList" :aria-label="$t('lists__new_list_btn')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="70%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-add"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<div :class="$style.headerBtns">
|
||||
<button :class="$style.listsAdd" @click="handleShowNewList" :aria-label="$t('lists__new_list_btn')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="70%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-add"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="$style.listsAdd" @click="isShowListUpdateModal = true" :aria-label="$t('list_update_modal__title')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" style="transform: rotate(45deg);" height="70%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-refresh"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="scroll" :class="[$style.listsContent, { [$style.sortable]: keyEvent.isModDown }]" ref="dom_lists_list">
|
||||
<li class="default-list" :class="[$style.listsItem, {[$style.active]: defaultList.id == listId}]" :aria-label="defaultList.name" :aria-selected="defaultList.id == listId"
|
||||
|
@ -36,19 +43,23 @@
|
|||
<base-menu :menus="listsItemMenu" :location="listsData.menuLocation" item-name="name" :isShow="listsData.isShowItemMenu" @menu-click="handleListsItemMenuClick" />
|
||||
<DuplicateMusicModal v-model:visible="isShowDuplicateMusicModal" :list-info="selectedDuplicateListInfo" />
|
||||
<ListSortModal v-model:visible="isShowListSortModal" :list-info="selectedSortListInfo" />
|
||||
<ListUpdateModal v-model:visible="isShowListUpdateModal" :list-update-times="listUpdateTimes" @update-list="handleSyncSourceList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapActions } from 'vuex'
|
||||
import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName, openUrl } from '@renderer/utils'
|
||||
import { mapMutations } from 'vuex'
|
||||
import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName, openUrl, dateFormat } from '@renderer/utils'
|
||||
import musicSdk from '@renderer/utils/music'
|
||||
import DuplicateMusicModal from './DuplicateMusicModal'
|
||||
import ListSortModal from './ListSortModal'
|
||||
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
import ListUpdateModal from './ListUpdateModal'
|
||||
import { defaultList, loveList, userLists, fetchingListStatus } from '@renderer/core/share/list'
|
||||
import { ref, computed, useCommit, useCssModule } from '@renderer/utils/vueTools'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
import useDarg from '@renderer/utils/compositions/useDrag'
|
||||
import { getListUpdateInfo } from '@renderer/utils/data'
|
||||
import useSyncSourceList from '@renderer/utils/compositions/useSyncSourceList'
|
||||
|
||||
export default {
|
||||
name: 'MyLists',
|
||||
|
@ -61,12 +72,15 @@ export default {
|
|||
components: {
|
||||
DuplicateMusicModal,
|
||||
ListSortModal,
|
||||
ListUpdateModal,
|
||||
},
|
||||
setup() {
|
||||
const dom_lists_list = ref(null)
|
||||
const lists = computed(() => [defaultList, loveList, ...userLists])
|
||||
const setUserListPosition = useCommit('list', 'setUserListPosition')
|
||||
|
||||
const syncSourceList = useSyncSourceList()
|
||||
|
||||
const styles = useCssModule()
|
||||
const { setDisabled } = useDarg({
|
||||
dom_list: dom_lists_list,
|
||||
|
@ -82,8 +96,10 @@ export default {
|
|||
loveList,
|
||||
userLists,
|
||||
lists,
|
||||
fetchingListStatus,
|
||||
dom_lists_list,
|
||||
setDisabledSort: setDisabled,
|
||||
syncSourceList,
|
||||
}
|
||||
},
|
||||
emits: ['show-menu'],
|
||||
|
@ -91,6 +107,7 @@ export default {
|
|||
return {
|
||||
isShowDuplicateMusicModal: false,
|
||||
isShowListSortModal: false,
|
||||
isShowListUpdateModal: false,
|
||||
listsData: {
|
||||
isShowItemMenu: false,
|
||||
itemMenuControl: {
|
||||
|
@ -111,9 +128,9 @@ export default {
|
|||
isShowNewList: false,
|
||||
isNewLeave: false,
|
||||
},
|
||||
fetchingListStatus: {},
|
||||
selectedDuplicateListInfo: {},
|
||||
selectedSortListInfo: {},
|
||||
listUpdateTimes: {},
|
||||
keyEvent: {
|
||||
isModDown: false,
|
||||
},
|
||||
|
@ -186,6 +203,9 @@ export default {
|
|||
this.setListsScroll()
|
||||
window.eventHub.on('key_mod_down', this.handle_key_mod_down)
|
||||
window.eventHub.on('key_mod_up', this.handle_key_mod_up)
|
||||
for (const [id, value] of Object.entries(getListUpdateInfo())) {
|
||||
this.listUpdateTimes[id] = value.updateTime ? dateFormat(value.updateTime) : ''
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.eventHub.off('key_mod_down', this.handle_key_mod_down)
|
||||
|
@ -199,10 +219,6 @@ export default {
|
|||
'setPrevSelectListId',
|
||||
'setList',
|
||||
]),
|
||||
...mapActions('songList', ['getListDetailAll']),
|
||||
...mapActions('leaderboard', {
|
||||
getBoardListAll: 'getListAll',
|
||||
}),
|
||||
handle_key_mod_down(event) {
|
||||
if (!this.keyEvent.isModDown) {
|
||||
// console.log(event)
|
||||
|
@ -336,7 +352,7 @@ export default {
|
|||
this.handleExportList(index)
|
||||
break
|
||||
case 'sync':
|
||||
this.handleSyncSourceList(index)
|
||||
this.handleSyncSourceList(userLists[index])
|
||||
break
|
||||
case 'remove':
|
||||
this.$dialog.confirm({
|
||||
|
@ -349,28 +365,10 @@ export default {
|
|||
break
|
||||
}
|
||||
},
|
||||
fetchList(id, source, sourceListId) {
|
||||
this.fetchingListStatus[id] = true
|
||||
|
||||
let promise
|
||||
if (/board__/.test(sourceListId)) {
|
||||
const id = sourceListId.replace(/board__/, '')
|
||||
promise = this.getBoardListAll({ id, isRefresh: true })
|
||||
} else {
|
||||
promise = this.getListDetailAll({ source, id: sourceListId, isRefresh: true })
|
||||
}
|
||||
return promise.finally(() => {
|
||||
this.fetchingListStatus[id] = false
|
||||
})
|
||||
},
|
||||
async handleSyncSourceList(index) {
|
||||
const targetListInfo = userLists[index]
|
||||
const list = await this.fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
|
||||
async handleSyncSourceList(targetListInfo) {
|
||||
await this.syncSourceList(targetListInfo)
|
||||
// console.log(targetListInfo.list.length, list.length)
|
||||
this.setList({
|
||||
...targetListInfo,
|
||||
list,
|
||||
})
|
||||
this.listUpdateTimes[targetListInfo.id] = dateFormat(Date.now())
|
||||
},
|
||||
getTargetListInfo(index) {
|
||||
let list
|
||||
|
@ -498,6 +496,9 @@ export default {
|
|||
}
|
||||
.listHeader {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
&:hover {
|
||||
.listsAdd {
|
||||
opacity: 1;
|
||||
|
@ -505,23 +506,27 @@ export default {
|
|||
}
|
||||
}
|
||||
.listsTitle {
|
||||
flex: auto;
|
||||
font-size: 12px;
|
||||
line-height: 38px;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.headerBtns {
|
||||
flex: none;
|
||||
display: flex;
|
||||
}
|
||||
.listsAdd {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 8px;
|
||||
// position: absolute;
|
||||
// right: 0;
|
||||
margin-top: 6px;
|
||||
background: none;
|
||||
height: 30px;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: @radius-border;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
opacity: .1;
|
||||
transition: opacity @transition-theme;
|
||||
color: @color-btn;
|
||||
svg {
|
||||
|
@ -530,6 +535,9 @@ export default {
|
|||
&:active {
|
||||
opacity: .7 !important;
|
||||
}
|
||||
&:hover {
|
||||
opacity: .6 !important;
|
||||
}
|
||||
}
|
||||
.listsContent {
|
||||
flex: auto;
|
||||
|
|
|
@ -26,8 +26,7 @@ teleport(to="#view")
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { clipboardReadText, debounce, scrollTo } from '@renderer/utils'
|
||||
let canceleFn
|
||||
import { clipboardReadText, debounce } from '@renderer/utils'
|
||||
|
||||
|
||||
// https://blog.csdn.net/xcxy2015/article/details/77164126#comments
|
||||
|
@ -178,12 +177,10 @@ export default {
|
|||
}
|
||||
},
|
||||
handle_key_mod_down() {
|
||||
this.isModDown = true
|
||||
if (!this.isModDown) this.isModDown = true
|
||||
},
|
||||
handle_key_mod_up() {
|
||||
setTimeout(() => {
|
||||
this.isModDown = false
|
||||
}, 100)
|
||||
if (this.isModDown) this.isModDown = false
|
||||
},
|
||||
handle_key_mod_f_down() {
|
||||
if (this.visible) this.$refs.dom_input.focus()
|
||||
|
@ -223,13 +220,13 @@ export default {
|
|||
let dom = this.$refs.dom_list.children[this.selectIndex]
|
||||
let offsetTop = dom.offsetTop
|
||||
let scrollTop = this.$refs.dom_scrollContainer.scrollTop
|
||||
let top
|
||||
if (offsetTop < scrollTop) {
|
||||
if (canceleFn) canceleFn()
|
||||
canceleFn = scrollTo(this.$refs.dom_scrollContainer, offsetTop, 200, () => canceleFn = null)
|
||||
top = offsetTop
|
||||
} else if (offsetTop + dom.clientHeight > this.$refs.dom_scrollContainer.clientHeight + scrollTop) {
|
||||
if (canceleFn) canceleFn()
|
||||
canceleFn = scrollTo(this.$refs.dom_scrollContainer, offsetTop + dom.clientHeight - this.$refs.dom_scrollContainer.clientHeight, 200, () => canceleFn = null)
|
||||
}
|
||||
top = offsetTop + dom.clientHeight - this.$refs.dom_scrollContainer.clientHeight
|
||||
} else return
|
||||
this.$refs.dom_scrollContainer.scrollTo(0, top)
|
||||
},
|
||||
handleContextMenu() {
|
||||
let str = clipboardReadText()
|
||||
|
@ -345,6 +342,8 @@ export default {
|
|||
height: 0;
|
||||
transition-property: height;
|
||||
position: relative;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -97,9 +97,9 @@ export default {
|
|||
watch(() => setting.value.player.mediaDeviceId, val => {
|
||||
currentStting.value.player.mediaDeviceId = val
|
||||
})
|
||||
watch(() => setting.value.playDetail.style.fontSize, val => {
|
||||
currentStting.value.playDetail.style.fontSize = val
|
||||
})
|
||||
watch(() => setting.value.playDetail, val => {
|
||||
currentStting.value.playDetail = JSON.parse(JSON.stringify(val))
|
||||
}, { deep: true })
|
||||
watch(() => setting.value.player.isMute, val => {
|
||||
currentStting.value.player.isMute = val
|
||||
})
|
||||
|
|
|
@ -9,6 +9,8 @@ dd
|
|||
base-checkbox(id="setting_desktop_lyric_delayScroll" v-model="currentStting.desktopLyric.isDelayScroll" :label="$t('setting__desktop_lyric_delay_scroll')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_alwaysOnTop" v-model="currentStting.desktopLyric.isAlwaysOnTop" :label="$t('setting__desktop_lyric_always_on_top')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_alwaysOnTopLoop" v-model="currentStting.desktopLyric.isAlwaysOnTopLoop" :label="$t('setting__desktop_lyric_always_on_top_loop')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_lockScreen" v-model="currentStting.desktopLyric.isLockScreen" :label="$t('setting__desktop_lyric_lock_screen')")
|
||||
dd
|
||||
|
|
|
@ -5,6 +5,8 @@ dd
|
|||
base-checkbox(id="setting_player_save_play_time" v-model="currentStting.player.isSavePlayTime" :label="$t('setting__play_save_play_time')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_lyric_transition" v-model="currentStting.player.isShowLyricTranslation" :label="$t('setting__play_lyric_transition')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_auto_skip_on_error" v-model="currentStting.player.autoSkipOnError" :label="$t('setting__play_auto_skip_on_error')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_lyric_s2t" v-model="currentStting.player.isS2t" :label="$t('setting__play_lyric_s2t')")
|
||||
.gap-top
|
||||
|
|
|
@ -4,7 +4,7 @@ dd
|
|||
.gap-top
|
||||
base-checkbox(id="setting_play_detail_font_zoom_enable" v-model="currentStting.playDetail.isZoomActiveLrc" :label="$t('setting__play_detail_font_zoom')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_play_detail_lyric_progress_enable" v-model="currentStting.playDetail.isShowLyricProgressSetting" :label="$t('setting__play_detail_detail_lyric_progress')")
|
||||
base-checkbox(id="setting_play_detail_lyric_progress_enable" v-model="currentStting.playDetail.isShowLyricProgressSetting" :label="$t('setting__play_detail_lyric_progress')")
|
||||
|
||||
dd
|
||||
h3#play_detail_align {{$t('setting__play_detail_align')}}
|
||||
|
@ -13,12 +13,6 @@ dd
|
|||
base-checkbox.gap-left(id="setting_play_detail_align_center" v-model="currentStting.playDetail.style.align" value="center" :label="$t('setting__play_detail_align_center')")
|
||||
//- base-checkbox.gap-left(id="setting_play_detail_align_right" v-model="currentStting.playDetail.style.align" value="right" :label="$t('setting__play_detail_align_right')")
|
||||
|
||||
dd
|
||||
h3#play_detail_align {{$t('setting__play_detail_font_size')}}
|
||||
div
|
||||
p.gap-top {{$t('setting__play_detail_font_size_current', { size: currentStting.playDetail.style.fontSize })}}
|
||||
base-btn.gap-top.btn(min @click="handleResetFont") {{$t('setting__play_detail_font_size_reset')}}
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -28,13 +22,8 @@ import { currentStting } from '../setting'
|
|||
export default {
|
||||
name: 'SettingPlayDetail',
|
||||
setup() {
|
||||
const handleResetFont = () => {
|
||||
currentStting.value.playDetail.style.fontSize = 100
|
||||
}
|
||||
|
||||
return {
|
||||
currentStting,
|
||||
handleResetFont,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const currentStting = ref({
|
|||
audioVisualization: false,
|
||||
waitPlayEndStop: true,
|
||||
waitPlayEndStopTime: '',
|
||||
autoSkipOnError: true,
|
||||
},
|
||||
playDetail: {
|
||||
isZoomActiveLrc: true,
|
||||
|
|
|
@ -56,7 +56,6 @@ div(:class="$style.container")
|
|||
|
||||
<script>
|
||||
import { mapGetters, mapMutations, mapActions } from 'vuex'
|
||||
import { scrollTo } from '@renderer/utils'
|
||||
import TagList from './components/TagList'
|
||||
import { tempList } from '@renderer/core/share/list'
|
||||
export default {
|
||||
|
@ -97,7 +96,7 @@ export default {
|
|||
case 'tx':
|
||||
case 'mg':
|
||||
case 'kg':
|
||||
case 'xm':
|
||||
// case 'xm':
|
||||
list.push({
|
||||
name: this.$t('songlist__open_list', { name: this.sourceInfo.sources.find(s => s.id == this.source).name }),
|
||||
id: 'importSongList',
|
||||
|
@ -122,7 +121,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
this.getList(1).then(() => {
|
||||
this.$nextTick(() => {
|
||||
scrollTo(this.$refs.dom_scrollContent, 0)
|
||||
this.$refs.dom_scrollContent.scrollTo(0, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -139,7 +138,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
this.getList(1).then(() => {
|
||||
this.$nextTick(() => {
|
||||
scrollTo(this.$refs.dom_scrollContent, 0)
|
||||
this.$refs.dom_scrollContent.scrollTo(0, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -164,6 +163,7 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.source = this.setting.songList.source
|
||||
if (!this.sourceInfo.sourceIds.includes(this.source)) this.source = this.sourceInfo.sourceIds[0]
|
||||
this.isToggleSource = true
|
||||
this.tagInfo = this.setting.songList.tagInfo
|
||||
this.sortId = this.setting.songList.sortId
|
||||
|
@ -230,7 +230,7 @@ export default {
|
|||
handleToggleListPage(page) {
|
||||
this.getList(page).then(() => {
|
||||
this.$nextTick(() => {
|
||||
scrollTo(this.$refs.dom_scrollContent, 0)
|
||||
this.$refs.dom_scrollContent.scrollTo(0, 0)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -402,7 +402,9 @@ export default {
|
|||
align-items: center;
|
||||
padding-top: 20%;
|
||||
}
|
||||
|
||||
.songList {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
.song-list-header {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
|
|
Loading…
Reference in New Issue