Merge branch 'dev'

pull/930/merge
lyswhut 2022-04-17 09:08:06 +08:00
commit 1b4c910264
79 changed files with 20192 additions and 2652 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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`

View File

@ -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
# 开发模式

20819
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -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,

View File

@ -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',

View File

@ -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",

View File

@ -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": "显示歌词翻译",

View File

@ -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": "顯示歌詞翻譯",

View File

@ -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

View File

@ -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)
}

View File

@ -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`)
}

View File

@ -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)
})

View File

@ -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()
}
}

View File

@ -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))

View File

@ -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
})

View File

@ -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() {

View File

@ -45,6 +45,8 @@ export default {
document.body.classList.add(window.dt ? 'disableTransparent' : 'transparent')
document.documentElement.style.fontSize = windowSizeActive.value.fontSize
}
}, {
immediate: true,
})
useApp()

View File

@ -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%);

View File

@ -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

View File

@ -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)',
}

View File

@ -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>

View File

@ -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}';

View File

@ -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()
}

View File

@ -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>

View File

@ -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;

View File

@ -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}';
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
})
})
}

View File

@ -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

View File

@ -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({

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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)
})
}

View File

@ -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 = () => {

View File

@ -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()

View File

@ -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
}

View File

@ -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':

View File

@ -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()
})

View File

@ -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', // 定时停止事件

View File

@ -69,4 +69,7 @@ export default {
isShowAnimation(state) {
return state.setting.isShowAnimation
},
playDetailSetting(state) {
return state.setting.playDetail
},
}

View File

@ -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) {

View File

@ -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
},

View File

@ -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 }) {

View File

@ -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 })

View File

@ -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,

View File

@ -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
},

View File

@ -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)

View File

@ -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,
}
}

View File

@ -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())
}
}

View File

@ -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()
}

View File

@ -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, {

View File

@ -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)
}
// 修饰键处理

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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
})

View File

@ -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
})
},
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
},

View File

@ -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) {

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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
})

View File

@ -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

View File

@ -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

View File

@ -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,
}
},
}

View File

@ -16,6 +16,7 @@ export const currentStting = ref({
audioVisualization: false,
waitPlayEndStop: true,
waitPlayEndStopTime: '',
autoSkipOnError: true,
},
playDetail: {
isZoomActiveLrc: true,

View File

@ -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;