Merge branch 'dev' into 黑色主题
commit
d83db6bdf5
|
@ -1,21 +0,0 @@
|
|||
platform:
|
||||
- x64
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- '%APPDATA%\npm-cache'
|
||||
- '%LOCALAPPDATA%\electron\Cache'
|
||||
- '%LOCALAPPDATA%\electron-builder\Cache'
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 12 x64
|
||||
- npm install
|
||||
|
||||
build_script:
|
||||
- npm run publish:gh
|
||||
|
||||
test: off
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
3
.babelrc
3
.babelrc
|
@ -19,6 +19,7 @@
|
|||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-modules-umd",
|
||||
"@babel/plugin-transform-runtime"
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
name: Build Beta
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
%APPDATA%\npm-cache
|
||||
%LOCALAPPDATA%\electron\Cache
|
||||
%LOCALAPPDATA%\electron-builder\Cache
|
||||
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package
|
||||
run: |
|
||||
npm run pack:win:setup:x86_64
|
||||
npm run pack:win:7z:x64
|
||||
npm run pack:win:7z:x86
|
||||
npm run pack:win:7z:arm64
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: |
|
||||
build/*.exe
|
||||
build/*.7z
|
||||
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
$HOME/.cache/electron
|
||||
$HOME/.cache/electron-builder
|
||||
$HOME/.npm/_prebuilds
|
||||
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package
|
||||
run: npm run pack:mac:dmg
|
||||
env:
|
||||
ELECTRON_CACHE: $HOME/.cache/electron
|
||||
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: |
|
||||
build/*.dmg
|
||||
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
$HOME/.cache/electron
|
||||
$HOME/.cache/electron-builder
|
||||
$HOME/.npm/_prebuilds
|
||||
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package
|
||||
run: |
|
||||
npm run pack:linux:deb:x64
|
||||
npm run pack:linux:deb:x86
|
||||
npm run pack:linux:deb:arm64
|
||||
npm run pack:linux:deb:armv7l
|
||||
npm run pack:linux:appImage
|
||||
npm run pack:linux:rpm
|
||||
npm run pack:linux:pacman
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: |
|
||||
build/*.deb
|
||||
build/*.appImage
|
||||
build/*.rpm
|
||||
build/*.pacman
|
|
@ -0,0 +1,131 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
%APPDATA%\npm-cache
|
||||
%LOCALAPPDATA%\electron\Cache
|
||||
%LOCALAPPDATA%\electron-builder\Cache
|
||||
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
npm run publish:win:setup:always
|
||||
npm run publish:win:7z:x64
|
||||
npm run publish:win:7z:x86
|
||||
npm run publish:win:7z:arm64
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
$HOME/.cache/electron
|
||||
$HOME/.cache/electron-builder
|
||||
$HOME/.npm/_prebuilds
|
||||
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: npm run publish:mac:dmg:always
|
||||
env:
|
||||
ELECTRON_CACHE: $HOME/.cache/electron
|
||||
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
$HOME/.cache/electron
|
||||
$HOME/.cache/electron-builder
|
||||
$HOME/.npm/_prebuilds
|
||||
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
npm run publish:linux:deb:x64:always
|
||||
npm run publish:linux:deb:x86
|
||||
npm run publish:linux:deb:arm64
|
||||
npm run publish:linux:deb:armv7l
|
||||
npm run publish:linux:appImage
|
||||
npm run publish:linux:rpm
|
||||
npm run publish:linux:pacman
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
49
.travis.yml
49
.travis.yml
|
@ -1,49 +0,0 @@
|
|||
language: node_js
|
||||
node_js: 12
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode10.2
|
||||
env:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- rpm
|
||||
- bsdtar
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
- $HOME/.npm/_prebuilds
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||
npm install && npm run publish:gh:linux
|
||||
else
|
||||
npm run publish:gh:mac
|
||||
fi
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
# only run this script on pull requests and merges into
|
||||
# the 'master' and 'prod' branches
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"path-intellisense.mappings": {
|
||||
"@main/*": "${workspaceFolder}/src/main/*",
|
||||
"@renderer/*": "${workspaceFolder}/src/renderer/*",
|
||||
"@lyric/*": "${workspaceFolder}/src/renderer-lyric/*",
|
||||
"@static/*": "${workspaceFolder}/src/static/*",
|
||||
"@common/*": "${workspaceFolder}/src/common/*",
|
||||
}
|
||||
}
|
115
CHANGELOG.md
115
CHANGELOG.md
|
@ -6,6 +6,121 @@ 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.8.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.8.1...v1.8.2) - 2021-03-09
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复歌曲ID存储变更导致酷狗图片获取失败的问题
|
||||
- 修复收藏的在线列表id迁移保存出错的问题
|
||||
|
||||
## [1.8.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.8.0...v1.8.1) - 2021-03-07
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复歌词翻译的主题颜色适配问题
|
||||
|
||||
## [1.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.1...v1.8.0) - 2021-03-07
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
|
||||
- 新增启动参数`-play`,可以在启动软件时播放指定歌单,使用方法看Readme.md的"启动参数"部分
|
||||
- 新增逐字歌词播放,默认开启,可到设置界面关闭,注:本功能目前仅对酷狗源的歌曲有效
|
||||
- 新增自定义源功能,源编写规则可以去常见问题查看
|
||||
|
||||
### 优化
|
||||
|
||||
- 允许播放除了搜索列表以外的所有歌曲,即原来没有播放按钮或者灰色的歌曲都可以去尝试点击播放。注:该功能的原理是尝试自动切换到其他源播放,所以不一定会播放成功,特别是对于那些独家的资源
|
||||
- 优化单首歌曲的“添加到列表”弹窗歌曲列表状态的显示;现在在收藏单首歌曲时,若列表存在本歌曲则列表名字将变成灰色不可点击状态。总的来说,在添加单首歌曲时若列表名是灰色,则证明当前歌曲已在那个列表中
|
||||
- 将歌词翻译放到原文的下方,同时新增当前播放翻译的高亮功能
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除虾米源。注:虽然已移除该源,但仍可尝试去播放之前添加的歌曲,虽然不一定会成功
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复音乐搜索列表的稍后播放功能无效的问题
|
||||
- 修复搜索列表双击不支持播放的源时会导致切歌的问题
|
||||
- 修复歌单列表加载失败时无法进入歌单打开界面的问题
|
||||
- 修复mg源歌单列表无法加载的问题
|
||||
- 修复kg跳转到官方歌曲详情页的歌曲无法播放的问题
|
||||
- 修复我的列表的歌曲添加到其他列表时不排除当前列表的问题
|
||||
- 修复在下载列表右击未下载完成的歌曲弹出的右击菜单中没有开始下载选项的问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 歌词翻译显示功能修改为默认关闭,注:此变更仅影响首次安装软件的用户
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v9.4.4
|
||||
|
||||
## [1.7.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.0...v1.7.1) - 2021-01-30
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复非透明模式下右侧滚动条无法拖动的问题
|
||||
- 修复MAC下xm音乐滑块验证问题
|
||||
|
||||
## [1.7.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.6.1...v1.7.0) - 2021-01-30
|
||||
|
||||
### 新增
|
||||
|
||||
- 搜索界面新增搜索状态的提示
|
||||
- 新增“稍后播放”功能,可在歌曲列表右键菜单使用
|
||||
- 新增“记住播放进度”功能的控制,该功能默认不再开启,可到播放设置-记住播放进度开启
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化播放歌曲换源匹配
|
||||
- 优化设置界面设置项的展示
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复快速切换歌曲时, 会出现播放的歌曲和界面展示的歌曲不一致的问题
|
||||
- 修复了一个由版本更新日志显示导致的潜在远程代码执行攻击漏洞,该漏洞影响v1.6.1及之前的所有版本,请务必更新到最新版本
|
||||
- 修复xm搜索源验证问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到9.4.2
|
||||
|
||||
## [1.6.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.6.0...v1.6.1) - 2021-01-13
|
||||
|
||||
### 优化
|
||||
|
||||
- 改进自动换源时的歌曲匹配
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复某些情况下自动换源的时间过长时会终止换源自动切歌的问题
|
||||
- 修复自动换源导致的搜索列表每页变成10条数据的问题
|
||||
- 降级electron到9.3.3修复部分系统没有声音的问题
|
||||
|
||||
## [1.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.5.0...v1.6.0) - 2021-01-10
|
||||
|
||||
### 新增
|
||||
|
||||
- 我的列表右键菜单新增列表排序功能,可调整单曲、多选后的歌曲的顺序。注意:多选排序还将会按照选中歌曲时的顺序排序
|
||||
- 添加鼠标提示的自动关闭功能,鼠标长时间(目前是10秒)不动时鼠标提示将会自动关闭
|
||||
- 添加鼠标指向歌曲封面的提示(对于进度条左边的歌曲封面,你可能不知道的操作->右击在“我的列表”定位当前播放的歌曲)
|
||||
- 隐藏播放详情页按钮添加快速隐藏详情页提示(你可能不知道的操作->在播放详情页内的任意非窗口可拖动区域右键双击可以快速隐藏详情页)
|
||||
- 添加桌面歌词字体、透明度调整按钮微调提示(你可能不知道的操作->对于字体、透明度可右击微调)
|
||||
- 我的列表右键菜单添加搜索当前歌曲功能
|
||||
- 新增`-dha`参数,添加此启动参数将禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动,Linux系统的界面显示有问题时可尝试添加此参数启动,若不行可尝试添加`-dt`参数启动
|
||||
- 新增播放自动换源功能~
|
||||
|
||||
### 变更
|
||||
|
||||
- `-nt`参数更名为`-dt`(Disable Transparent),目前原来的`-nt`参数仍然可用,但将在后续的版本中移除
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复恢复上次播放的歌曲时在随机播放模式下不把恢复播放的歌曲放入已播放队列的问题(该问题会导致随机模式下会导致未播放完整个列表前就会再次随机到该歌曲,以及无法通过上一曲切回该歌曲)
|
||||
- 修复音乐嵌入的封面在 Mac 系统无法显示的问题
|
||||
- 修复`-dt`(原来的`-nt`)启动参数不真正生效的问题
|
||||
|
||||
## [1.5.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.4.1...v1.5.0) - 2020-12-13
|
||||
|
||||
### 新增
|
||||
|
|
178
FAQ.md
178
FAQ.md
|
@ -13,7 +13,7 @@
|
|||
1. 尝试更新到最新版本
|
||||
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
|
||||
3. 尝试到 设置-音乐来源 切换到其他接口
|
||||
4. 尝试切换网络,比如用手机开热点(目前存在某些网络无法访问接口服务器的情况)
|
||||
4. 尝试切换网络,比如用手机开热点(所有歌曲都提示请求异常时可通过此方法解决,或等一两天后再试)
|
||||
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
|
||||
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明,则应该是你网络无法访问接口服务器的问题,如果接口有问题我会在那里说明。
|
||||
|
||||
|
@ -62,6 +62,8 @@
|
|||
根据Electron里issue的[解决方案](https://github.com/electron/electron/issues/2170#issuecomment-736223269),<br>
|
||||
若你遇到透明问题可尝试添加启动参数 `-dha` 来禁用硬件加速,例如:`.\lx-music-desktop.exe -dha`。
|
||||
|
||||
注:v1.6.0及之后的版本才支持`-dha`参数
|
||||
|
||||
## 软件启动后,界面无法显示
|
||||
|
||||
对于软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示,或者整个界面偶尔闪烁的情况。<br>
|
||||
|
@ -109,18 +111,24 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的
|
|||
`v1.2.1`以前的版本在 Ubuntu 18.10 下第一次开启桌面歌词时歌词窗口会变白,需要关闭后再开启,
|
||||
`v1.2.1`及之后的版本已修复该问题。
|
||||
|
||||
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题。
|
||||
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题,但你可以尝试按照`Linux 下界面异常`的解决方案去解决。
|
||||
|
||||
## 歌曲下载失败
|
||||
|
||||
### 提示 `ENOENT: no such file or directory, mkdir`
|
||||
|
||||
更换下载歌曲目录即可解决(一般是设置的歌曲下载目录没有读写入权限导致的)。
|
||||
更换下载歌曲目录即可解决(一般是设置的歌曲下载目录没有读写权限导致的)。
|
||||
|
||||
### 提示 `请求异常` 或 `Fail`
|
||||
|
||||
尝试更换网络,如切换到移动网络。
|
||||
|
||||
## 使用软件时导致耳机意外关机
|
||||
|
||||
据反馈,漫步者部分型号的耳机与本软件一起使用时将会导致耳机意外关机,
|
||||
详情看:<https://github.com/lyswhut/lx-music-desktop/issues/457>,
|
||||
若出现该问题可尝试添加`-dhmkh`启动参数解决,启动参数添加方法请自行百度“windows给应用程序加启动参数的方法”。
|
||||
|
||||
### 其他错误
|
||||
|
||||
按照前面的 "歌曲无法试听与下载" 方案解决。
|
||||
|
@ -162,10 +170,172 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的
|
|||
|
||||
## 杀毒软件提示有病毒或恶意行为
|
||||
|
||||
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.com/github/lyswhut/lx-music-desktop)<br>
|
||||
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br>
|
||||
尽管如此,但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
|
||||
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。
|
||||
|
||||
从`v0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
|
||||
|
||||
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
|
||||
|
||||
## 自定义源脚本编写说明
|
||||
|
||||
文件请使用UTF-8编码格式编写,脚本所用编程语言为JavaScript,可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @name 测试音乐源
|
||||
* @description 我只是一个测试音乐源哦
|
||||
* @version 1.0.0
|
||||
* @author xxx
|
||||
* @homepage http://xxx
|
||||
*/
|
||||
|
||||
|
||||
const { EVENT_NAMES, request, on, send } = window.lx
|
||||
|
||||
const qualitys = {
|
||||
kw: {
|
||||
'128k': '128',
|
||||
'320k': '320',
|
||||
flac: 'flac',
|
||||
},
|
||||
}
|
||||
const httpRequest = (url, options) => new Promise((resolve, reject) => {
|
||||
request(url, options, (err, resp) => {
|
||||
if (err) return reject(err)
|
||||
resolve(resp.body)
|
||||
})
|
||||
})
|
||||
|
||||
const apis = {
|
||||
kw: {
|
||||
musicUrl({ songmid }, quality) {
|
||||
return httpRequest('http://xxx').then(data => {
|
||||
return data.url
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 注册应用API请求事件
|
||||
// source 音乐源,可能的值取决于初始化时传入的sources对象的源key值
|
||||
// info 请求附加信息,内容根据action变化
|
||||
// action 请求操作类型,目前只有musicUrl,即获取音乐URL链接,
|
||||
// 当action为musicUrl时info的结构:{type, musicInfo},
|
||||
// info.type:音乐质量,可能的值有128k / 320k / flac(取决于初始化时对应源传入的qualitys值中的一个),
|
||||
// info.musicInfo:音乐信息对象,里面有音乐ID、名字等信息
|
||||
on(EVENT_NAMES.request, ({ source, action, info }) => {
|
||||
// 回调必须返回 Promise 对象
|
||||
switch (action) {
|
||||
// action 为 musicUrl 时需要在 Promise 返回歌曲 url
|
||||
case 'musicUrl':
|
||||
return apis[source].musicUrl(info.musicInfo, qualitys[source][info.type]).catch(err => {
|
||||
console.log(err)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 脚本初始化完成后需要发送inited事件告知应用
|
||||
send(EVENT_NAMES.inited, {
|
||||
status: true, // 初始化成功 or 失败
|
||||
openDevTools: false, // 是否打开开发者工具,方便用于调试脚本
|
||||
sources: { // 当前脚本支持的源
|
||||
kw: { // 支持的源对象,可用key值:kw/kg/tx/wy/mg
|
||||
name: '酷我音乐',
|
||||
type: 'music', // 目前固定为 music
|
||||
actions: ['musicUrl'], // 目前固定为 ['musicUrl']
|
||||
qualitys: ['128k', '320k', 'flac'], // 当前脚本的该源所支持获取的Url音质,有效的值有:['128k', '320k', 'flac']
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 自定义源信息
|
||||
|
||||
文件的开头必须包含以下注释:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @name 测试脚本
|
||||
* @description 我只是一个测试脚本
|
||||
* @version 1.0.0
|
||||
* @author xxx
|
||||
* @homepage http://xxx
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
- `@name `:源的名字,建议不要过长,10个字符以内
|
||||
- `@description `:源的描述,建议不要过长,20个字符以内,可不填,不填时必须保留 @description
|
||||
- `@version`:源的版本号,可不填,不填时可以删除 @version
|
||||
- `@author `:脚本作者名字,可不填,不填时可以删除 @author
|
||||
- `@homepage `:脚本主页,可不填,不填时可以删除 @homepage
|
||||
|
||||
### `window.lx`
|
||||
|
||||
应用为脚本暴露的API对象。
|
||||
|
||||
#### `window.lx.EVENT_NAMES`
|
||||
|
||||
常量事件名称对象,发送、注册事件时传入事件名时使用,可用值:
|
||||
|
||||
| 事件名 | 描述
|
||||
| --- | ---
|
||||
| `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`<br>`status`:初始化结果(`true`成功,`false`失败)<br>`openDevTools`:是否打开DevTools,此选项可用于开发脚本时的调试<br>`sources`:支持的源信息对象,<br>`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)<br>`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`<br>`sources[kw/kg/tx/wy/mg].actions`:支持的actions,由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可<br>`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,有效的值为`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型
|
||||
| `request` | 应用API请求事件名,回调入参:`handler({ source, action, info})`,回调必须返回`Promise`对象<br>`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值<br>`info`:请求附加信息,内容根据`action`变化<br>`action`:请求操作类型,目前只有`musicUrl`,即获取音乐URL链接,需要在 Promise 返回歌曲 url,`info`的结构:`{type, musicInfo}`,`info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息
|
||||
|
||||
|
||||
#### `window.lx.on`
|
||||
|
||||
事件注册方法,应用主动与脚本通信时使用:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param event_name 事件名
|
||||
* @param handler 事件处理回调 -- 注意:注册的回调必须返回 Promise 对象
|
||||
*/
|
||||
window.lx.on(event_name, handler)
|
||||
```
|
||||
|
||||
**注意:** 注册的回调必须返回 `Promise` 对象。
|
||||
|
||||
#### `window.lx.send`
|
||||
|
||||
事件发送方法,脚本主动与应用通信时使用:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param event_name 事件名
|
||||
* @param datas 要传给应用的数据
|
||||
*/
|
||||
window.lx.send(event_name, datas)
|
||||
```
|
||||
|
||||
#### `window.lx.request`
|
||||
|
||||
HTTP请求方法,用于发送HTTP请求,此HTTP请求方法不受跨域规则限制:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param url 请求的URL
|
||||
* @param options 请求选项,可用选项有 method / headers / body / form / formData / timeout
|
||||
* @param callback 请求结果的回调 入参:err, resp, body
|
||||
* @return 返回一个方法,调用此方法可以终止HTTP请求
|
||||
*/
|
||||
const cancelHttp = window.lx.request(url, options, callback)
|
||||
```
|
||||
|
||||
#### `window.lx.utils`
|
||||
|
||||
应用提供给脚本的工具方法:
|
||||
|
||||
- `window.lx.utils.buffer.from`:对应Node.js的 `Buffer.from`
|
||||
- `window.lx.utils.crypto.aesEncrypt`:AES加密 `aesEncrypt(buffer, mode, key, iv)`
|
||||
- `window.lx.utils.crypto.md5`:MD5加密 `md5(str)`
|
||||
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`
|
||||
- `window.lx.utils.crypto.rsaEncrypt`:RSA加密 `rsaEncrypt(buffer, key)`
|
||||
|
||||
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。
|
||||
|
|
20
README.md
20
README.md
|
@ -2,8 +2,8 @@
|
|||
|
||||
<p align="center">
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
|
||||
<a href="https://ci.appveyor.com/project/lyswhut/lx-music-desktop"><img src="https://ci.appveyor.com/api/projects/status/flrsqd5ymp8fnte5?svg=true" alt="Build status"></a>
|
||||
<a href="https://travis-ci.com/lyswhut/lx-music-desktop"><img src="https://travis-ci.com/lyswhut/lx-music-desktop.svg?branch=master" alt="Build status"></a>
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/release.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build/badge.svg" alt="Build status"></a>
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/beta-pack.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build%20Beta/badge.svg" alt="Build status"></a>
|
||||
<a href="https://electronjs.org/releases/stable"><img src="https://img.shields.io/github/package-json/dependency-version/lyswhut/lx-music-desktop/dev/electron/master" alt="Electron version"></a>
|
||||
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a> -->
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/tree/dev"><img src="https://img.shields.io/github/package-json/v/lyswhut/lx-music-desktop/dev" alt="Dev branch version"></a>
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
|
||||
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
|
||||
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoux.com/b0bf2cfa/` 密码:`glqw`<br>
|
||||
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzous.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
|
||||
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
|
||||
|
||||
### 源码使用方法
|
||||
|
@ -62,7 +62,7 @@ npm run dev
|
|||
npm run pack:dir
|
||||
|
||||
# 构建安装包(Windows版)
|
||||
npm run pack
|
||||
npm run pack:win
|
||||
|
||||
# 构建安装包(Mac版)
|
||||
npm run pack:mac
|
||||
|
@ -81,8 +81,16 @@ npm run pack:linux
|
|||
目前软件已支持的启动参数如下:
|
||||
|
||||
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动
|
||||
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示,原来的`-nt`参数已重命名为`-dt`
|
||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
||||
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示(注:该参数对桌面歌词无效),原来的`-nt`参数已重命名为`-dt`(v1.6.0起重命名)
|
||||
- `-dhmkh` 禁用硬件媒体密钥处理(Disable Hardware Media Key Handling),此选项将禁用Chromium的Hardware Media Key Handling特性(v1.8.1起新增)
|
||||
- `-play` 启动时播放指定列表的音乐,参数说明:
|
||||
- `type`:播放类型,目前固定为`songList`
|
||||
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
|
||||
- `link`:要播放的在线列表歌单链接、或ID,source为`kw/kg/tx/wy/mg`之一(在线列表)时必传,举例:`./lx-music-desktop -play="type=songList&source=kw&link=歌单URL or ID"`,注意:如果传入URL时必须对URL进行编码后再传入
|
||||
- `name`:要播放的本地列表歌单名字,source为`myList`时必传,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表"`
|
||||
- `index`:从列表的哪个位置开始播放,选传,若不传默认播放第一首歌曲,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表&index=2"`
|
||||
|
||||
|
||||
### 常见问题
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ module.exports = merge(baseConfig, {
|
|||
NODE_ENV: '"development"',
|
||||
},
|
||||
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
__userApi: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin({
|
||||
onErrors(severity, errors) { // Silent warning from electron-debug
|
||||
if (severity != 'warning') return
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const path = require('path')
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
|
@ -20,6 +21,18 @@ module.exports = merge(baseConfig, {
|
|||
__filename: false,
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/renderer'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/renderer'),
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/rendererEvent/name.js'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/rendererEvent/name.js'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
|
|
|
@ -37,6 +37,7 @@ module.exports = {
|
|||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
emitWarning: isDev,
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
|
|
|
@ -11,7 +11,6 @@ module.exports = merge(baseConfig, {
|
|||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
|
|
@ -34,7 +34,6 @@ module.exports = merge(baseConfig, {
|
|||
}),
|
||||
],
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
|
|
|
@ -37,6 +37,7 @@ module.exports = {
|
|||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
emitWarning: isDev,
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
|
|
|
@ -11,7 +11,6 @@ module.exports = merge(baseConfig, {
|
|||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
|
|
@ -34,7 +34,6 @@ module.exports = merge(baseConfig, {
|
|||
}),
|
||||
],
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
|
|
|
@ -179,6 +179,9 @@ function startElectron() {
|
|||
function electronLog(data, color) {
|
||||
let log = data.toString()
|
||||
if (/[0-9A-z]+/.test(log)) {
|
||||
// 抑制 user api 窗口使用 data url 加载页面时 vue扩展 的报错日志刷屏的问题
|
||||
if (color == 'red' && typeof log === 'string' && log.includes('"Extension server error: Operation failed: Permission denied", source: devtools://devtools/bundled/extensions/extensions.js')) return
|
||||
|
||||
console.log(chalk[color](log))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|||
const cssLoaderConfig = require('./css-loader.config')
|
||||
const chalk = require('chalk')
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
// merge css-loader
|
||||
exports.mergeCSSLoader = beforeLoader => {
|
||||
const loader = [
|
||||
|
@ -11,12 +9,7 @@ exports.mergeCSSLoader = beforeLoader => {
|
|||
{
|
||||
resourceQuery: /module/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
hmr: isDev,
|
||||
},
|
||||
},
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: cssLoaderConfig,
|
||||
|
@ -27,12 +20,7 @@ exports.mergeCSSLoader = beforeLoader => {
|
|||
// 这里匹配普通的 `<style>` 或 `<style scoped>`
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
hmr: isDev,
|
||||
},
|
||||
},
|
||||
MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
],
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
// This is the line you want to add
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@main": ["src/main"],
|
||||
"@renderer": ["src/renderer"],
|
||||
"@lyric": ["src/renderer-lyric"],
|
||||
"@static": ["src/static"],
|
||||
"@common": ["src/common"],
|
||||
"@main/*": ["src/main/*"],
|
||||
"@renderer/*": ["src/renderer/*"],
|
||||
"@lyric/*": ["src/renderer-lyric/*"],
|
||||
"@static/*": ["src/static/*"],
|
||||
"@common/*": ["src/common/*"],
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/**/*"]
|
||||
"exclude": ["node_modules", "build", "dist"]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
127
package.json
127
package.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "1.6.0-beta",
|
||||
"description": "一个免费的音乐下载助手",
|
||||
"version": "1.8.2",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
"scripts": {
|
||||
"pack": "node build-config/pack.js && npm run pack:win",
|
||||
"pack:win": "npm run pack:win:setup:x86_64 && npm run pack:win:7z",
|
||||
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
|
||||
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
|
||||
"pack:win:setup:x86_64": "cross-env TARGET=win_安装版 ARCH=x86_64 electron-builder -w=nsis --x64 --ia32",
|
||||
"pack:win:setup:x64": "cross-env TARGET=win_安装版 ARCH=x64 electron-builder -w=nsis --x64",
|
||||
"pack:win:setup:x86": "cross-env TARGET=win_安装版 ARCH=x86 electron-builder -w=nsis --ia32",
|
||||
|
@ -17,48 +17,49 @@
|
|||
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
|
||||
"pack:win:7z:x64": "cross-env TARGET=win_绿色版 ARCH=x64 electron-builder -w=7z --x64",
|
||||
"pack:win:7z:x86": "cross-env TARGET=win_绿色版 ARCH=x86 electron-builder -w=7z --ia32",
|
||||
"pack:win:7z:arm64": "cross-env TARGET=win_绿色版 ARCH=arm64 electron-builder -w=7z --arm64",
|
||||
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
|
||||
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage",
|
||||
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:x86 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
|
||||
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64",
|
||||
"pack:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32",
|
||||
"pack:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64",
|
||||
"pack:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l",
|
||||
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64",
|
||||
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64",
|
||||
"pack:mac": "node build-config/pack.js && electron-builder -m=dmg",
|
||||
"pack:dir": "node build-config/pack.js && electron-builder --dir",
|
||||
"publish": "node publish",
|
||||
"publish:gh": "node build-config/pack.js && npm run publish:win",
|
||||
"publish:win": "npm run publish:win:7z && npm run publish:win:setup",
|
||||
"publish:win:setup:always": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p always",
|
||||
"publish:win:setup": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
|
||||
"publish:win:portable": "npm run publish:win:portable:x86_64 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
|
||||
"publish:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
|
||||
"publish:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p onTagOrDraft",
|
||||
"publish:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p onTagOrDraft",
|
||||
"publish:win:7z": "npm run publish:win:7z:x64 && npm run publish:win:7z:x86",
|
||||
"publish:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p always",
|
||||
"publish:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p onTagOrDraft",
|
||||
"publish:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p onTagOrDraft",
|
||||
"publish:gh:mac": "node build-config/pack.js && npm run publish:mac",
|
||||
"publish:mac": "npm run publish:mac:dmg",
|
||||
"publish:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p onTagOrDraft",
|
||||
"publish:mac:dmg:always": "electron-builder -m=dmg -p always",
|
||||
"publish:mac:dmg": "electron-builder -m=dmg -p onTagOrDraft",
|
||||
"publish:gh:linux": "node build-config/pack.js && npm run publish:linux",
|
||||
"publish:linux": "npm run publish:linux:deb && npm run publish:linux:appImage && npm run publish:linux:rpm && npm run publish:linux:pacman",
|
||||
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
|
||||
"publish:linux:deb": "npm run publish:linux:deb:x64 && npm run publish:linux:deb:x86 && npm run publish:linux:deb:arm64",
|
||||
"publish:linux:deb:x64:always": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p always",
|
||||
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
|
||||
"publish:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -p onTagOrDraft",
|
||||
"publish:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p onTagOrDraft",
|
||||
"publish:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p onTagOrDraft",
|
||||
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
|
||||
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
|
||||
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
|
||||
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
|
||||
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage",
|
||||
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:x86 && npm run pack:linux:deb:arm64",
|
||||
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64",
|
||||
"pack:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32",
|
||||
"pack:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64",
|
||||
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64",
|
||||
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64",
|
||||
"pack:mac": "node build-config/pack.js && electron-builder -m=dmg",
|
||||
"pack:dir": "node build-config/pack.js && electron-builder --dir",
|
||||
"dev": "node build-config/runner-dev.js",
|
||||
"clean:electron": "rimraf dist/electron",
|
||||
"clean": "rimraf dist && rimraf build",
|
||||
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress --hide-modules",
|
||||
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress --hide-modules",
|
||||
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress --hide-modules",
|
||||
"build:src": "node build-config/pack.js",
|
||||
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress",
|
||||
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress",
|
||||
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress",
|
||||
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric",
|
||||
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly src",
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src",
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm update"
|
||||
},
|
||||
"browserslist": [
|
||||
"Electron 9.3.4"
|
||||
|
@ -132,12 +133,6 @@
|
|||
"provider": "github",
|
||||
"owner": "lyswhut",
|
||||
"repo": "lx-music-desktop"
|
||||
},
|
||||
{
|
||||
"package": "lx-music-desktop",
|
||||
"repo": "lx-music-desktop",
|
||||
"user": "lyswhut",
|
||||
"provider": "bintray"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -160,12 +155,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/core": "^7.13.15",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.12.1",
|
||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
||||
"@babel/plugin-transform-modules-umd": "^7.13.0",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
|
@ -173,69 +169,70 @@
|
|||
"cfonts": "^2.9.1",
|
||||
"chalk": "^4.1.0",
|
||||
"changelog-parser": "^2.8.0",
|
||||
"copy-webpack-plugin": "^6.4.0",
|
||||
"core-js": "^3.8.2",
|
||||
"copy-webpack-plugin": "^8.1.1",
|
||||
"core-js": "^3.10.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^4.3.0",
|
||||
"css-loader": "^5.2.1",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^9.4.0",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron": "^9.4.4",
|
||||
"electron-builder": "^22.10.5",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-html": "^6.1.1",
|
||||
"eslint-plugin-html": "^6.1.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"html-webpack-plugin": "^4.5.1",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.2.1",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^8.1.0",
|
||||
"markdown-it": "^12.0.4",
|
||||
"mini-css-extract-plugin": "^0.12.0",
|
||||
"mini-css-extract-plugin": "^1.4.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||
"postcss-loader": "^4.1.0",
|
||||
"postcss-pxtorem": "^5.1.1",
|
||||
"pug": "^3.0.0",
|
||||
"postcss": "^8.2.10",
|
||||
"postcss-loader": "^5.2.0",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"spinnies": "^0.5.1",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^4.3.1",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"stylus-loader": "^5.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.6",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.1",
|
||||
"webpack": "^5.32.0",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"dnscache": "^1.0.2",
|
||||
"electron-log": "^4.3.1",
|
||||
"electron-log": "^4.3.4",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-updater": "^4.3.5",
|
||||
"electron-updater": "^4.3.8",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"image-size": "^0.9.3",
|
||||
"image-size": "^0.9.7",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^1.0.5",
|
||||
"lrc-file-parser": "^1.0.7",
|
||||
"needle": "^2.6.0",
|
||||
"node-id3": "^0.2.2",
|
||||
"request": "^2.88.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-i18n": "^8.22.3",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.6.0",
|
||||
"vue-i18n": "^8.24.3",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
### 新增
|
||||
|
||||
- 我的列表右键菜单新增列表排序功能,可调整单曲、多选后的歌曲的顺序。注意:多选排序还将会按照选中歌曲时的顺序排序
|
||||
- 添加鼠标提示的自动关闭功能,鼠标长时间(目前是10秒)不动时鼠标提示将会自动关闭
|
||||
- 添加鼠标指向歌曲封面的提示(对于进度条左边的歌曲封面,你可能不知道的操作->右击在“我的列表”定位当前播放的歌曲)
|
||||
- 隐藏播放详情页按钮添加快速隐藏详情页提示(你可能不知道的操作->在播放详情页内的任意非窗口可拖动区域右键双击可以快速隐藏详情页)
|
||||
- 添加桌面歌词字体、透明度调整按钮微调提示(你可能不知道的操作->对于字体、透明度可右击微调)
|
||||
- 我的列表右键菜单添加搜索当前歌曲功能
|
||||
- 新增`-dha`参数,添加此启动参数将禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动,Linux系统的界面显示有问题时可尝试添加此参数启动,若不行可尝试添加`-dt`参数启动
|
||||
- 新增播放自动换源功能~
|
||||
- 新增启动参数`-dhmkh`,此参数将禁用Chromium的Hardware Media Key Handling特性,用于解决漫步者部分型号耳机与本程序冲突导致耳机意外关机的问题
|
||||
- 新增Windows arm64位免安装版的构建
|
||||
|
||||
### 变更
|
||||
### 优化
|
||||
|
||||
- `-nt`参数更名为`-dt`(Disable Transparent),目前原来的`-nt`参数仍然可用,但将在后续的版本中移除
|
||||
- 程序启动时对数据文件做读取校验,数据出现损坏时自动备份损坏的数据,若出现数据读取错误的弹窗并出现我的列表丢失时可到GitHub或加群反馈
|
||||
- 当设置-代理启用,但主机地址为空的时,将不再使用代理配置进行网络连接,并且在离开设置界面时自动禁用代理
|
||||
- 优化歌曲自动换源匹配
|
||||
- 分离歌词与歌曲列表信息的保存,以减小列表列表文件损坏的几率
|
||||
- 兼容打开咪咕移动端分享的歌单链接,添加打开歌单的信息显示
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复恢复上次播放的歌曲时在随机播放模式下不把恢复播放的歌曲放入已播放队列的问题(该问题会导致随机模式下会导致未播放完整个列表前就会再次随机到该歌曲,以及无法通过上一曲切回该歌曲)
|
||||
- 修复音乐嵌入的封面在 Mac 系统无法显示的问题
|
||||
- 修复`-dt`(原来的`-nt`)启动参数不真正生效的问题
|
||||
- 修复备份与恢复功能在恢复数据时某些设置不立即生效的问题
|
||||
- 修正设置页“搜索设置”部分内容的缩进显示问题
|
||||
|
|
|
@ -44,7 +44,7 @@ module.exports = async newVerNum => {
|
|||
desc: version.desc,
|
||||
})
|
||||
version.version = newVerNum
|
||||
version.desc = newChangeLog
|
||||
version.desc = newMDChangeLog.replace(/(?:^|(\n))#{1,6} (.+)\n/g, '$1$2').trim()
|
||||
pkg.version = newVerNum
|
||||
|
||||
console.log(chalk.blue('new version: ') + chalk.green(newVerNum))
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -51,6 +51,6 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
navigationUrlWhiteList: [
|
||||
/^https:\/\/www\.xiami\.com/,
|
||||
|
||||
],
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ const path = require('path')
|
|||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.38',
|
||||
version: '1.0.40',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
|
@ -11,7 +11,9 @@ const defaultSetting = {
|
|||
isMute: false,
|
||||
mediaDeviceId: 'default',
|
||||
isMediaDeviceRemovedStopPlay: false,
|
||||
isShowLyricTransition: true,
|
||||
isShowLyricTranslation: false,
|
||||
isPlayLxlrc: true,
|
||||
isSavePlayTime: false,
|
||||
},
|
||||
desktopLyric: {
|
||||
enable: false,
|
||||
|
|
|
@ -2,11 +2,17 @@ const { ipcMain, ipcRenderer } = require('electron')
|
|||
const names = require('./ipcNames')
|
||||
|
||||
|
||||
exports.mainOn = (event, callback) => {
|
||||
ipcMain.on(event, callback)
|
||||
exports.mainOn = (name, callback) => {
|
||||
ipcMain.on(name, callback)
|
||||
}
|
||||
exports.mainOnce = (event, callback) => {
|
||||
ipcMain.once(event, callback)
|
||||
exports.mainOnce = (name, callback) => {
|
||||
ipcMain.once(name, callback)
|
||||
}
|
||||
exports.mainOff = (name, callback) => {
|
||||
ipcMain.removeListener(name, callback)
|
||||
}
|
||||
exports.mainOffAll = name => {
|
||||
ipcMain.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.mainHandle = (name, callback) => {
|
||||
|
@ -15,6 +21,9 @@ exports.mainHandle = (name, callback) => {
|
|||
exports.mainHandleOnce = (name, callback) => {
|
||||
ipcMain.handleOnce(name, callback)
|
||||
}
|
||||
exports.mainHandleRemove = name => {
|
||||
ipcMain.removeListener(name)
|
||||
}
|
||||
|
||||
exports.mainSend = (window, name, params) => {
|
||||
window.webContents.send(name, params)
|
||||
|
@ -33,5 +42,11 @@ exports.rendererOn = (name, callback) => {
|
|||
exports.rendererOnce = (name, callback) => {
|
||||
ipcRenderer.once(name, callback)
|
||||
}
|
||||
exports.rendererOff = (name, callback) => {
|
||||
ipcRenderer.removeListener(name, callback)
|
||||
}
|
||||
exports.rendererOffAll = name => {
|
||||
ipcRenderer.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.NAMES = names
|
||||
|
|
|
@ -50,6 +50,22 @@ const names = {
|
|||
get_data: 'get_data',
|
||||
save_data: 'save_data',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
remove_user_api: 'remove_user_api',
|
||||
set_user_api: 'set_user_api',
|
||||
get_user_api_list: 'get_user_api_list',
|
||||
request_user_api: 'request_user_api',
|
||||
request_user_api_cancel: 'request_user_api_cancel',
|
||||
get_user_api_status: 'get_user_api_status',
|
||||
user_api_status: 'user_api_status',
|
||||
|
||||
get_lyric: 'get_lyric',
|
||||
save_lyric: 'save_lyric',
|
||||
clear_lyric: 'clear_lyric',
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
const Store = require('electron-store')
|
||||
const { dialog, app, shell } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const log = require('electron-log')
|
||||
|
||||
const stores = {}
|
||||
|
||||
/**
|
||||
* 获取 Store 对象
|
||||
* @param {*} name store 名
|
||||
* @param {*} isIgnoredError 是否忽略错误
|
||||
* @param {*} isShowErrorAlert 是否显示错误弹窗
|
||||
* @returns Store
|
||||
*/
|
||||
module.exports = (name, isIgnoredError = true, isShowErrorAlert = true) => {
|
||||
if (stores[name]) return stores[name]
|
||||
let store
|
||||
try {
|
||||
store = stores[name] = new Store({ name, clearInvalidConfig: false })
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
|
||||
if (!isIgnoredError) throw error
|
||||
|
||||
|
||||
const backPath = path.join(app.getPath('userData'), name + '.json.bak')
|
||||
fs.copyFileSync(path.join(app.getPath('userData'), name + '.json'), backPath)
|
||||
if (isShowErrorAlert) {
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'error',
|
||||
message: name + ' data load error',
|
||||
detail: `We have helped you back up the old ${name} file to: ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`,
|
||||
})
|
||||
shell.showItemInFolder(backPath)
|
||||
}
|
||||
|
||||
|
||||
store = new Store({ name, clearInvalidConfig: true })
|
||||
}
|
||||
return store
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
const log = require('electron-log')
|
||||
const Store = require('electron-store')
|
||||
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
|
||||
const apiSource = require('../renderer/utils/music/api-source-info')
|
||||
// const apiSource = require('../renderer/utils/music/api-source-info')
|
||||
const getStore = require('./store')
|
||||
const defaultHotKey = require('./defaultHotKey')
|
||||
const { dialog, app } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
exports.isLinux = process.platform == 'linux'
|
||||
exports.isWin = process.platform == 'win32'
|
||||
|
@ -115,10 +112,10 @@ exports.mergeSetting = (setting, version) => {
|
|||
setting = defaultSettingCopy
|
||||
}
|
||||
|
||||
if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||
let api = apiSource.find(api => !api.disabled)
|
||||
if (api) setting.apiSource = api.id
|
||||
}
|
||||
// if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||
// let api = apiSource.find(api => !api.disabled)
|
||||
// if (api) setting.apiSource = api.id
|
||||
// }
|
||||
|
||||
return { setting, version: defaultVersion }
|
||||
}
|
||||
|
@ -126,30 +123,13 @@ exports.mergeSetting = (setting, version) => {
|
|||
/**
|
||||
* 初始化设置
|
||||
* @param {*} setting
|
||||
* @param {*} isShowErrorAlert
|
||||
*/
|
||||
exports.initSetting = () => {
|
||||
let electronStore_list
|
||||
try {
|
||||
electronStore_list = new Store({
|
||||
name: 'playList',
|
||||
clearInvalidConfig: false,
|
||||
})
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
const backPath = path.join(app.getPath('userData'), 'playList.json.bak')
|
||||
fs.copyFileSync(path.join(app.getPath('userData'), 'playList.json'), backPath)
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'error',
|
||||
message: 'Playlist data loading error',
|
||||
detail: `We have helped you back up the old list file to ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`,
|
||||
})
|
||||
electronStore_list = new Store({
|
||||
name: 'playList',
|
||||
})
|
||||
}
|
||||
const electronStore_config = new Store({
|
||||
name: 'config',
|
||||
})
|
||||
exports.initSetting = isShowErrorAlert => {
|
||||
const electronStore_list = getStore('playList', true, isShowErrorAlert)
|
||||
const electronStore_config = getStore('config')
|
||||
const electronStore_downloadList = getStore('downloadList')
|
||||
|
||||
let setting = electronStore_config.get('setting')
|
||||
if (setting) {
|
||||
let version = electronStore_config.get('version')
|
||||
|
@ -165,7 +145,7 @@ exports.initSetting = () => {
|
|||
}
|
||||
const downloadList = electronStore_config.get('download')
|
||||
if (downloadList) {
|
||||
if (downloadList.list) electronStore_list.set('downloadList', downloadList.list)
|
||||
if (downloadList.list) electronStore_downloadList.set('list', downloadList.list)
|
||||
electronStore_config.delete('download')
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +159,18 @@ exports.initSetting = () => {
|
|||
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
|
||||
delete setting.list.scroll
|
||||
}
|
||||
|
||||
if (setting.player.isShowLyricTransition != null) { // 修正拼写问题 v1.8.2 及以前
|
||||
setting.player.isShowLyricTranslation = setting.player.isShowLyricTransition
|
||||
delete setting.player.isShowLyricTransition
|
||||
}
|
||||
}
|
||||
|
||||
// 从我的列表分离下载列表 v1.7.0 后
|
||||
let downloadList = electronStore_list.get('downloadList')
|
||||
if (downloadList) {
|
||||
electronStore_downloadList.set('list', downloadList)
|
||||
electronStore_list.delete('downloadList')
|
||||
}
|
||||
|
||||
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
|
||||
|
@ -187,8 +179,7 @@ exports.initSetting = () => {
|
|||
if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16'
|
||||
|
||||
// newSetting.controlBtnPosition = 'right'
|
||||
electronStore_config.set('version', settingVersion)
|
||||
electronStore_config.set('setting', newSetting)
|
||||
electronStore_config.set({ version: settingVersion, setting: newSetting })
|
||||
return { version: settingVersion, setting: newSetting }
|
||||
}
|
||||
|
||||
|
@ -196,9 +187,7 @@ exports.initSetting = () => {
|
|||
* 初始化快捷键设置
|
||||
*/
|
||||
exports.initHotKey = () => {
|
||||
const electronStore_hotKey = new Store({
|
||||
name: 'hotKey',
|
||||
})
|
||||
const electronStore_hotKey = getStore('hotKey')
|
||||
|
||||
let localConfig = electronStore_hotKey.get('local')
|
||||
if (!localConfig) {
|
||||
|
|
|
@ -2,7 +2,7 @@ const { common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = requir
|
|||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('./../common/ipc')
|
||||
const { getAppHotKeyConfig } = require('./utils')
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.config, name => {
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (MAIN_WINDOW_EVENT_NAME.name === name) return
|
||||
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.set_config, global.appSetting)
|
||||
})
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
const { EventEmitter } = require('events')
|
||||
const { common: COMMON_EVENT_NAME } = require('./_name')
|
||||
const { updateSetting } = require('../utils')
|
||||
|
||||
class Common extends EventEmitter {
|
||||
initSetting() {
|
||||
this.emit(COMMON_EVENT_NAME.initConfig)
|
||||
this.emit(COMMON_EVENT_NAME.config, null)
|
||||
this.configStatus(null)
|
||||
}
|
||||
|
||||
setAppConfig(config, name) {
|
||||
if (config) updateSetting(config)
|
||||
this.emit(COMMON_EVENT_NAME.config, name)
|
||||
configStatus(name) {
|
||||
this.emit(COMMON_EVENT_NAME.configStatus, name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
exports.common = {
|
||||
initConfig: 'initConfig',
|
||||
config: 'config',
|
||||
configStatus: 'config',
|
||||
}
|
||||
|
||||
exports.mainWindow = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
global.lx_event = {}
|
||||
if (!global.lx_event) global.lx_event = {}
|
||||
|
||||
const Common = require('./Common')
|
||||
const MainWindow = require('./MainWindow')
|
||||
|
@ -6,8 +6,12 @@ const Tray = require('./Tray')
|
|||
const WinLyric = require('./WinLyric')
|
||||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
|
||||
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
||||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
|
|
|
@ -4,7 +4,7 @@ const path = require('path')
|
|||
// 单例应用程序
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
return
|
||||
process.exit(0)
|
||||
}
|
||||
if (!global.modules) global.modules = {}
|
||||
app.on('second-instance', (event, argv, cwd) => {
|
||||
|
@ -29,13 +29,15 @@ require('./env')
|
|||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
if (global.envParams.cmdParams.dt == null && global.envParams.cmdParams.nt != null) global.envParams.cmdParams.dt = global.envParams.cmdParams.nt
|
||||
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
|
||||
const { navigationUrlWhiteList } = require('../common/config')
|
||||
const { getWindowSizeInfo } = require('./utils')
|
||||
const { isMac, isLinux, initSetting, initHotKey } = require('../common/utils')
|
||||
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')
|
||||
const { isMac, isLinux, initHotKey } = require('../common/utils')
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
|
@ -54,6 +56,7 @@ app.on('web-contents-created', (event, contents) => {
|
|||
event.preventDefault()
|
||||
if (/^devtools/.test(navigationUrl)) return
|
||||
console.log(navigationUrl)
|
||||
if (!/^https?:\/\//.test(navigationUrl)) return
|
||||
await shell.openExternal(navigationUrl)
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
|
@ -129,11 +132,16 @@ global.appHotKey = {
|
|||
state: null,
|
||||
}
|
||||
|
||||
global.lx_core = {
|
||||
setAppConfig(setting, name) {
|
||||
updateSetting(setting)
|
||||
global.lx_event.common.configStatus(name)
|
||||
},
|
||||
}
|
||||
|
||||
function init() {
|
||||
console.log('init')
|
||||
const info = initSetting()
|
||||
global.appSetting = info.setting
|
||||
global.appSettingVersion = info.version
|
||||
initSetting()
|
||||
global.appHotKey.config = initHotKey()
|
||||
global.lx_event.common.initSetting()
|
||||
global.lx_event.hotKey.init()
|
||||
|
|
|
@ -2,3 +2,4 @@ require('./appMenu')
|
|||
require('./winLyric')
|
||||
require('./tray')
|
||||
require('./hotKey')
|
||||
require('./userApi')
|
||||
|
|
|
@ -16,7 +16,7 @@ const themeList = [
|
|||
isNative: false,
|
||||
},
|
||||
]
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.config, sourceName => {
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, sourceName => {
|
||||
if (sourceName === TRAY_EVENT_NAME.name) return
|
||||
if (themeId !== global.appSetting.tray.themeId) {
|
||||
themeId = global.appSetting.tray.themeId
|
||||
|
@ -89,34 +89,34 @@ const createMenu = tray => {
|
|||
menu.push(global.appSetting.desktopLyric.enable ? {
|
||||
label: '关闭桌面歌词',
|
||||
click() {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
} : {
|
||||
label: '开启桌面歌词',
|
||||
click() {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isLock ? {
|
||||
label: '解锁桌面歌词',
|
||||
click() {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
} : {
|
||||
label: '锁定桌面歌词',
|
||||
click() {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop ? {
|
||||
label: '取消置顶',
|
||||
click() {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
} : {
|
||||
label: '置顶歌词',
|
||||
click() {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push({
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
exports.userApis = []
|
|
@ -0,0 +1,11 @@
|
|||
const { EventEmitter } = require('events')
|
||||
const USER_API_EVENT_NAME = require('./name')
|
||||
|
||||
class UserApi extends EventEmitter {
|
||||
status(info) {
|
||||
this.emit(USER_API_EVENT_NAME.status, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserApi
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
status: 'status',
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const { closeWindow } = require('./main')
|
||||
const { getUserApis, importApi, removeApi } = require('./utils')
|
||||
const { request, cancelRequest, getStatus, loadApi } = require('./rendererEvent/rendererEvent')
|
||||
|
||||
// const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames }
|
||||
let userApiId
|
||||
|
||||
exports.Event = Event
|
||||
exports.eventNames = eventNames
|
||||
|
||||
exports.getApiList = getUserApis
|
||||
exports.importApi = script => {
|
||||
return {
|
||||
apiInfo: importApi(script),
|
||||
apiList: getUserApis(),
|
||||
}
|
||||
}
|
||||
exports.request = request
|
||||
exports.cancelRequest = cancelRequest
|
||||
exports.getStatus = getStatus
|
||||
|
||||
exports.removeApi = async ids => {
|
||||
if (userApiId && ids.includes(userApiId)) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
removeApi(ids)
|
||||
return getUserApis()
|
||||
}
|
||||
|
||||
exports.setApi = async id => {
|
||||
if (userApiId) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
const apiList = getUserApis()
|
||||
if (!apiList.some(a => a.id === id)) return
|
||||
userApiId = id
|
||||
await loadApi(id)
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
const { BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const dir = global.isDev ? __userApi : path.join(__dirname, 'userApi')
|
||||
|
||||
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||
|
||||
let html = ''
|
||||
|
||||
fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => {
|
||||
if (err) throw new Error('api html read failed, info: ' + err.message)
|
||||
html = data
|
||||
})
|
||||
|
||||
const denyEvents = [
|
||||
'new-window',
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'will-attach-webview',
|
||||
'will-prevent-unload',
|
||||
'media-started-playing',
|
||||
]
|
||||
|
||||
const winEvent = win => {
|
||||
win.on('closed', () => {
|
||||
win = global.modules.userApiWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
exports.createWindow = async userApi => {
|
||||
if (global.modules.userApiWindow) return
|
||||
while (true) {
|
||||
if (html) break
|
||||
await wait(100)
|
||||
}
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
global.modules.userApiWindow = new BrowserWindow({
|
||||
enableRemoteModule: false,
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
worldSafeExecuteJavaScript: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(dir, 'renderer/preload.js'),
|
||||
},
|
||||
})
|
||||
|
||||
for (const eventName of denyEvents) {
|
||||
global.modules.userApiWindow.webContents.on(eventName, event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
callback(false)
|
||||
})
|
||||
|
||||
winEvent(global.modules.userApiWindow)
|
||||
|
||||
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
|
||||
const randomNum = Math.random().toString().substring(2, 10)
|
||||
global.modules.userApiWindow.loadURL(
|
||||
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
|
||||
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
|
||||
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
|
||||
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
|
||||
|
||||
// global.modules.userApiWindow.loadFile(path.join(dir, 'renderer/user-api.html'))
|
||||
// global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
exports.closeWindow = async() => {
|
||||
if (!global.modules.userApiWindow) return
|
||||
await Promise.all([
|
||||
global.modules.userApiWindow.webContents.session.clearAuthCache(),
|
||||
global.modules.userApiWindow.webContents.session.clearStorageData(),
|
||||
global.modules.userApiWindow.webContents.session.clearCache(),
|
||||
])
|
||||
global.modules.userApiWindow.destroy()
|
||||
global.modules.userApiWindow = null
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const needle = require('needle')
|
||||
const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto')
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
|
||||
const sendMessage = (action, status, data, message) => {
|
||||
ipcRenderer.send(action, { status, data, message })
|
||||
}
|
||||
|
||||
let isInitedApi = false
|
||||
const EVENT_NAMES = {
|
||||
request: 'request',
|
||||
inited: 'inited',
|
||||
}
|
||||
const eventNames = Object.values(EVENT_NAMES)
|
||||
const events = {
|
||||
request: null,
|
||||
}
|
||||
const allSources = ['kw', 'kg', 'tx', 'wy', 'mg']
|
||||
const supportQualitys = {
|
||||
kw: ['128k', '320k', 'flac'],
|
||||
kg: ['128k', '320k', 'flac'],
|
||||
tx: ['128k', '320k', 'flac'],
|
||||
wy: ['128k', '320k', 'flac'],
|
||||
mg: ['128k', '320k', 'flac'],
|
||||
}
|
||||
const supportActions = {
|
||||
kw: ['musicUrl'],
|
||||
kg: ['musicUrl'],
|
||||
tx: ['musicUrl'],
|
||||
wy: ['musicUrl'],
|
||||
mg: ['musicUrl'],
|
||||
xm: ['musicUrl'],
|
||||
}
|
||||
|
||||
const handleRequest = (context, { requestKey, data }) => {
|
||||
// console.log(data)
|
||||
if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, 'Request event is not defined')
|
||||
try {
|
||||
events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => {
|
||||
let sendData = {
|
||||
requestKey,
|
||||
}
|
||||
switch (data.action) {
|
||||
case 'musicUrl':
|
||||
sendData.result = {
|
||||
source: data.source,
|
||||
action: data.action,
|
||||
data: {
|
||||
type: data.info.type,
|
||||
url: response,
|
||||
},
|
||||
}
|
||||
break
|
||||
}
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, true, sendData)
|
||||
}).catch(err => {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
|
||||
})
|
||||
} catch (err) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} context
|
||||
* @param {*} info {
|
||||
* status: true,
|
||||
* message: 'xxx',
|
||||
* sources: {
|
||||
* kw: ['128k', '320k', 'flac'],
|
||||
* kg: ['128k', '320k', 'flac'],
|
||||
* tx: ['128k', '320k', 'flac'],
|
||||
* wy: ['128k', '320k', 'flac'],
|
||||
* mg: ['128k', '320k', 'flac'],
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
const handleInit = (context, info) => {
|
||||
if (!info) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
|
||||
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
|
||||
return
|
||||
}
|
||||
if (info.openDevTools === true) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools)
|
||||
}
|
||||
if (!info.status) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
|
||||
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
|
||||
return
|
||||
}
|
||||
const sourceInfo = {
|
||||
sources: {},
|
||||
}
|
||||
try {
|
||||
for (const source of allSources) {
|
||||
const userSource = info.sources[source]
|
||||
if (!userSource || userSource.type !== 'music') continue
|
||||
const qualitys = supportQualitys[source]
|
||||
const actions = supportActions[source]
|
||||
sourceInfo.sources[source] = {
|
||||
type: 'music',
|
||||
actions: actions.filter(a => userSource.actions.includes(a)),
|
||||
qualitys: qualitys.filter(q => userSource.qualitys.includes(q)),
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, error.message)
|
||||
return
|
||||
}
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, true, sourceInfo)
|
||||
|
||||
ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => {
|
||||
handleRequest(context, data)
|
||||
})
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('lx', {
|
||||
EVENT_NAMES,
|
||||
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
|
||||
let options = { headers }
|
||||
let data
|
||||
if (body) {
|
||||
data = body
|
||||
} else if (form) {
|
||||
data = form
|
||||
// data.content_type = 'application/x-www-form-urlencoded'
|
||||
options.json = false
|
||||
} else if (formData) {
|
||||
data = formData
|
||||
// data.content_type = 'multipart/form-data'
|
||||
options.json = false
|
||||
}
|
||||
options.response_timeout = timeout
|
||||
|
||||
let request = needle.request(method, url, data, options, (err, resp, body) => {
|
||||
if (!err) {
|
||||
body = resp.body = resp.raw.toString()
|
||||
try {
|
||||
resp.body = JSON.parse(resp.body)
|
||||
} catch (_) {}
|
||||
body = resp.body
|
||||
}
|
||||
callback(err, {
|
||||
statusCode: resp.statusCode,
|
||||
statusMessage: resp.statusMessage,
|
||||
headers: resp.headers,
|
||||
bytes: resp.bytes,
|
||||
raw: resp.raw,
|
||||
body: body,
|
||||
}, body)
|
||||
}).request
|
||||
|
||||
return () => {
|
||||
if (!request.aborted) request.abort()
|
||||
request = null
|
||||
}
|
||||
},
|
||||
send(eventName, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
|
||||
switch (eventName) {
|
||||
case EVENT_NAMES.inited:
|
||||
if (isInitedApi) return
|
||||
isInitedApi = true
|
||||
handleInit(this, data)
|
||||
break
|
||||
default:
|
||||
resolve(new Error('Unknown event name: ' + eventName))
|
||||
}
|
||||
})
|
||||
},
|
||||
on(eventName, handler) {
|
||||
if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
switch (eventName) {
|
||||
case EVENT_NAMES.request:
|
||||
events.request = handler
|
||||
break
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
crypto: {
|
||||
aesEncrypt(buffer, mode, key, iv) {
|
||||
const cipher = createCipheriv('aes-128-' + mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
},
|
||||
rsaEncrypt(buffer, key) {
|
||||
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
|
||||
return publicEncrypt({ key: key, padding: constants.RSA_NO_PADDING }, buffer)
|
||||
},
|
||||
randomBytes(size) {
|
||||
return randomBytes(size)
|
||||
},
|
||||
md5(str) {
|
||||
return createHash('md5').update(str).digest('hex')
|
||||
},
|
||||
},
|
||||
buffer: {
|
||||
from(...args) {
|
||||
return Buffer.from(...args)
|
||||
},
|
||||
},
|
||||
},
|
||||
// removeEvent(eventName, handler) {
|
||||
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
// let handlers
|
||||
// switch (eventName) {
|
||||
// case EVENT_NAMES.request:
|
||||
// handlers = events.request
|
||||
// break
|
||||
// }
|
||||
// for (let index = 0; index < handlers.length; index++) {
|
||||
// if (handlers[index] === handler) {
|
||||
// handlers.splice(index, 1)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// removeAllEvents() {
|
||||
// for (const handlers of Object.values(events)) {
|
||||
// handlers.splice(0, handlers.length)
|
||||
// }
|
||||
// },
|
||||
})
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>User api</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
const names = {
|
||||
init: '',
|
||||
request: '',
|
||||
response: '',
|
||||
openDevTools: '',
|
||||
}
|
||||
|
||||
|
||||
for (const key of Object.keys(names)) {
|
||||
names[key] = `userApi_${key}`
|
||||
}
|
||||
module.exports = names
|
|
@ -0,0 +1,82 @@
|
|||
const { mainOn, mainSend } = require('@common/ipc')
|
||||
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
const { createWindow } = require('../main')
|
||||
const { getUserApis } = require('../utils')
|
||||
|
||||
let userApi
|
||||
let status = { status: true }
|
||||
const requestQueue = new Map()
|
||||
const timeouts = {}
|
||||
|
||||
const handleInit = (event, { status, message, data: apiInfo }) => {
|
||||
// console.log('inited')
|
||||
if (!status) {
|
||||
console.log('init failed:', message)
|
||||
global.lx_event.userApi.status(status = { status: false, apiInfo: userApi, message })
|
||||
return
|
||||
}
|
||||
global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } })
|
||||
}
|
||||
const handleResponse = (event, { status, data: { requestKey, result }, message }) => {
|
||||
const request = requestQueue.get(requestKey)
|
||||
if (!request) return
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
if (status) {
|
||||
request[0](result)
|
||||
} else {
|
||||
request[1](new Error(message))
|
||||
}
|
||||
}
|
||||
const handleOpenDevTools = () => {
|
||||
if (global.modules.userApiWindow) {
|
||||
global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
}
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
|
||||
|
||||
exports.loadApi = async apiId => {
|
||||
if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' })
|
||||
userApi = getUserApis().find(api => api.id == apiId)
|
||||
console.log('load api', userApi.name)
|
||||
await createWindow(userApi)
|
||||
// if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' })
|
||||
// if (!global.modules.userApiWindow) {
|
||||
// global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' })
|
||||
// throw new Error('user api window is not defined')
|
||||
// }
|
||||
|
||||
// // const path = require('path')
|
||||
// // // eslint-disable-next-line no-undef
|
||||
// // userApi.script = require('fs').readFileSync(path.join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString()
|
||||
// console.log('load api', userApi.name)
|
||||
// mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi })
|
||||
}
|
||||
|
||||
exports.cancelRequest = requestKey => {
|
||||
if (!requestQueue.has(requestKey)) return
|
||||
const request = requestQueue.get(requestKey)
|
||||
request[1](new Error('Cancel request'))
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
}
|
||||
|
||||
exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => {
|
||||
if (!userApi) return reject(new Error('user api is not load'))
|
||||
|
||||
// const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
|
||||
timeouts[requestKey] = setTimeout(() => {
|
||||
exports.cancelRequest(requestKey)
|
||||
}, 20000)
|
||||
|
||||
requestQueue.set(requestKey, [resolve, reject, data])
|
||||
mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.request, { requestKey, data })
|
||||
})
|
||||
|
||||
exports.getStatus = () => status
|
|
@ -0,0 +1,44 @@
|
|||
const { userApis: defaultUserApis } = require('../config')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
let userApis
|
||||
|
||||
exports.getUserApis = () => {
|
||||
const electronStore_userApi = getStore('userApi')
|
||||
if (userApis) return userApis
|
||||
userApis = electronStore_userApi.get('userApis')
|
||||
if (!userApis) {
|
||||
userApis = defaultUserApis
|
||||
electronStore_userApi.set('userApis', userApis)
|
||||
}
|
||||
return userApis
|
||||
}
|
||||
|
||||
exports.importApi = script => {
|
||||
let scriptInfo = script.split(/\r?\n/)
|
||||
let name = scriptInfo[1] || ''
|
||||
let description = scriptInfo[2] || ''
|
||||
name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}`
|
||||
if (name.length > 10) name = name.substring(0, 10) + '...'
|
||||
description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : ''
|
||||
if (description.length > 20) description = description.substring(0, 20) + '...'
|
||||
const apiInfo = {
|
||||
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
|
||||
name,
|
||||
description,
|
||||
script,
|
||||
}
|
||||
userApis.push(apiInfo)
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
return apiInfo
|
||||
}
|
||||
|
||||
exports.removeApi = ids => {
|
||||
for (let index = userApis.length - 1; index > -1; index--) {
|
||||
if (ids.includes(userApis[index].id)) {
|
||||
userApis.splice(index, 1)
|
||||
ids.splice(index, 1)
|
||||
}
|
||||
}
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
}
|
|
@ -2,6 +2,7 @@ const { common: COMMON_EVENT_NAME, winLyric: WIN_LYRIC_EVENT_NAME, hotKey: HOT_K
|
|||
const { mainSend, NAMES: { winLyric: ipcWinLyricNames } } = require('../../../common/ipc')
|
||||
const { desktop_lyric } = require('../../../common/hotKey')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
const { updateSetting } = require('../../utils')
|
||||
|
||||
let isLock = null
|
||||
let isEnable = null
|
||||
|
@ -13,7 +14,8 @@ const setLrcConfig = () => {
|
|||
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_config, {
|
||||
config: desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTransition: global.appSetting.player.isShowLyricTransition,
|
||||
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
})
|
||||
if (isLock != desktopLyric.isLock) {
|
||||
isLock = desktopLyric.isLock
|
||||
|
@ -48,7 +50,7 @@ const setLrcConfig = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.config, name => {
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (WIN_LYRIC_EVENT_NAME.name === name) return
|
||||
setLrcConfig()
|
||||
})
|
||||
|
@ -76,5 +78,5 @@ global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
|
|||
}
|
||||
desktopLyricSetting[settingKey] = !desktopLyricSetting[settingKey]
|
||||
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: desktopLyricSetting }, null)
|
||||
updateSetting({ desktopLyric: desktopLyricSetting }, null)
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ const setLyricsConfig = debounce(config => {
|
|||
// if (y != null) bounds.y = y
|
||||
// if (width != null) bounds.width = width
|
||||
// if (height != null) bounds.height = height
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
}, 500)
|
||||
|
||||
const winEvent = lyricWindow => {
|
||||
|
|
|
@ -20,11 +20,16 @@ mainOn(ipcWinLyricNames.get_lyric_info, (event, action) => {
|
|||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
|
||||
global.lx_event.common.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
})
|
||||
|
||||
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
|
||||
return { config: global.appSetting.desktopLyric, languageId: global.appSetting.langId, isShowLyricTransition: global.appSetting.player.isShowLyricTransition }
|
||||
return {
|
||||
config: global.appSetting.desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../events/_name')
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const { NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
|
||||
mainOn(ipcMainWindowNames.set_app_setting, (event, config) => {
|
||||
|
||||
mainHandle(ipcMainWindowNames.set_app_setting, (event, config) => {
|
||||
if (!config) return
|
||||
global.lx_event.common.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name)
|
||||
global.lx_core.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name)
|
||||
return global.appSetting
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_setting, async() => ({ setting: global.appSetting, version: global.appSettingVersion }))
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
const Store = require('electron-store')
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
const electronStore_data = new Store({
|
||||
name: 'data',
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_data, async(event, path) => electronStore_data.get(path))
|
||||
mainHandle(ipcMainWindowNames.get_data, async(event, path) => getStore('data').get(path))
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.save_data, (event, { path, data }) => electronStore_data.set(path, data))
|
||||
mainOn(ipcMainWindowNames.save_data, (event, { path, data }) => getStore('data').set(path, data))
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
const Store = require('electron-store')
|
||||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('../../common/ipc')
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME } = require('../events/_name')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
const electronStore_hotKey = new Store({
|
||||
name: 'hotKey',
|
||||
})
|
||||
// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils')
|
||||
|
||||
// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => {
|
||||
|
@ -19,10 +17,13 @@ const electronStore_hotKey = new Store({
|
|||
// }
|
||||
// })
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_hot_key, async() => ({
|
||||
local: electronStore_hotKey.get('local'),
|
||||
global: electronStore_hotKey.get('global'),
|
||||
}))
|
||||
mainHandle(ipcMainWindowNames.get_hot_key, async() => {
|
||||
const electronStore_hotKey = getStore('hotKey')
|
||||
return {
|
||||
local: electronStore_hotKey.get('local'),
|
||||
global: electronStore_hotKey.get('global'),
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.quit, () => global.lx_event.mainWindow.quit())
|
||||
mainOn(ipcMainWindowNames.min_toggle, () => global.lx_event.mainWindow.toggleMinimize())
|
||||
|
|
|
@ -17,7 +17,9 @@ require('./getDataPath')
|
|||
require('./showDialog')
|
||||
require('./playList')
|
||||
require('./data')
|
||||
|
||||
require('./xm_verify')
|
||||
require('./lyric')
|
||||
require('./musicUrl')
|
||||
|
||||
require('./kw_decodeLyric')
|
||||
|
||||
require('./userApi')
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_lyric, async(event, id) => getStore('lyrics', true, false).get(id) || {})
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.save_lyric, (event, { id, lyrics }) => getStore('lyrics', true, false).set(id, lyrics))
|
||||
|
||||
mainOn(ipcMainWindowNames.clear_lyric, () => getStore('lyrics', true, false).clear())
|
|
@ -0,0 +1,10 @@
|
|||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_music_url, async(event, id) => getStore('musicUrls', true, false).get(id) || '')
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.save_music_url, (event, { id, url }) => getStore('musicUrls', true, false).set(id, url))
|
||||
|
||||
mainOn(ipcMainWindowNames.clear_music_url, () => getStore('musicUrls', true, false).clear())
|
|
@ -1,23 +1,32 @@
|
|||
const Store = require('electron-store')
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
let electronStore_list
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false) => {
|
||||
if (!electronStore_list) {
|
||||
electronStore_list = new Store({
|
||||
name: 'playList',
|
||||
clearInvalidConfig: !isIgnoredError,
|
||||
})
|
||||
}
|
||||
const electronStore_list = getStore('playList', isIgnoredError, false)
|
||||
|
||||
return {
|
||||
defaultList: electronStore_list.get('defaultList'),
|
||||
loveList: electronStore_list.get('loveList'),
|
||||
userList: electronStore_list.get('userList'),
|
||||
downloadList: electronStore_list.get('downloadList'),
|
||||
downloadList: getStore('downloadList').get('list'),
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => electronStore_list && electronStore_list.set(type, data))
|
||||
const handleSaveList = ({ defaultList, loveList, userList }) => {
|
||||
let data = {}
|
||||
if (defaultList != null) data.defaultList = defaultList
|
||||
if (loveList != null) data.loveList = loveList
|
||||
if (userList != null) data.userList = userList
|
||||
getStore('playList').set(data)
|
||||
}
|
||||
mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => {
|
||||
switch (type) {
|
||||
case 'myList':
|
||||
handleSaveList(data)
|
||||
break
|
||||
case 'downloadList':
|
||||
getStore('downloadList').set('list', data)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
const { mainSend, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
|
||||
const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames } = require('../modules/userApi')
|
||||
|
||||
const handleStatusChange = status => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_status, status)
|
||||
}
|
||||
|
||||
global.lx_event.userApi.on(eventNames.status, handleStatusChange)
|
||||
|
||||
mainHandle(ipcMainWindowNames.import_user_api, async(event, script) => {
|
||||
return importApi(script)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.remove_user_api, (event, apiIds) => {
|
||||
return removeApi(apiIds)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.set_user_api, (event, apiId) => {
|
||||
return setApi(apiId)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_user_api_list, event => {
|
||||
return getApiList()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_user_api_status, event => {
|
||||
return getStatus()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.request_user_api, (event, musicInfo) => {
|
||||
return request(musicInfo)
|
||||
})
|
||||
mainHandle(ipcMainWindowNames.request_user_api_cancel, (event, requestKey) => {
|
||||
return cancelRequest(requestKey)
|
||||
})
|
|
@ -1,4 +0,0 @@
|
|||
const { isMac } = require('../../../common/utils')
|
||||
|
||||
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
|
||||
require(isMac ? './xm_verify_win' : './xm_verify_view')
|
|
@ -1,4 +0,0 @@
|
|||
const { isMac } = require('../../../common/utils')
|
||||
|
||||
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
|
||||
require(isMac ? './xm_verify_win' : './xm_verify_view')
|
|
@ -1,66 +0,0 @@
|
|||
const { BrowserView } = require('electron')
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
|
||||
const { getWindowSizeInfo } = require('../../utils')
|
||||
|
||||
let view
|
||||
let isActioned = false
|
||||
let rejectFn
|
||||
|
||||
const closeView = async() => {
|
||||
if (!view) return
|
||||
// await view.webContents.session.clearCache()
|
||||
if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(view)
|
||||
await view.webContents.session.clearStorageData()
|
||||
view.destroy()
|
||||
view = null
|
||||
}
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
|
||||
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
|
||||
if (view) {
|
||||
global.modules.mainWindow.removeBrowserView(view)
|
||||
view.destroy()
|
||||
}
|
||||
|
||||
rejectFn = reject
|
||||
|
||||
isActioned = false
|
||||
|
||||
view = new BrowserView({
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
disableHtmlFullscreenWindowResize: true,
|
||||
},
|
||||
})
|
||||
view.webContents.on('did-finish-load', () => {
|
||||
if (/punish\?/.test(view.webContents.getURL())) return
|
||||
let ses = view.webContents.session
|
||||
ses.cookies.get({ name: 'x5sec' })
|
||||
.then(async([x5sec]) => {
|
||||
isActioned = true
|
||||
await closeView()
|
||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
||||
resolve(x5sec.value)
|
||||
}).catch(async err => {
|
||||
isActioned = true
|
||||
await closeView()
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
|
||||
global.modules.mainWindow.setBrowserView(view)
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
view.setBounds({ x: (windowSizeInfo.width - 380) / 2, y: ((windowSizeInfo.height - 320 + 52) / 2), width: 380, height: 320 })
|
||||
view.webContents.loadURL(url, {
|
||||
httpReferrer: 'https://www.xiami.com/',
|
||||
})
|
||||
// view.webContents.openDevTools()
|
||||
}))
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
|
||||
await closeView()
|
||||
if (!rejectFn) return
|
||||
if (!isActioned) rejectFn(new Error('canceled verify'))
|
||||
rejectFn = null
|
||||
})
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
const { BrowserWindow } = require('electron')
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
|
||||
const { getWindowSizeInfo } = require('../../utils')
|
||||
|
||||
let win
|
||||
|
||||
const closeWin = async() => {
|
||||
if (!win) return
|
||||
// await win.webContents.session.clearCache()
|
||||
// if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(win)
|
||||
if (win.isDestroyed()) {
|
||||
win = null
|
||||
return
|
||||
}
|
||||
await win.webContents.session.clearStorageData()
|
||||
win.destroy()
|
||||
win = null
|
||||
}
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
|
||||
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
|
||||
if (win) win.destroy()
|
||||
|
||||
let isActioned = false
|
||||
|
||||
const mainWindowSizeInfo = global.modules.mainWindow.getBounds()
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
win = new BrowserWindow({
|
||||
parent: global.modules.mainWindow,
|
||||
width: 460,
|
||||
height: 370,
|
||||
resizable: false,
|
||||
// transparent: true,
|
||||
x: mainWindowSizeInfo.x + (windowSizeInfo.width - 460) / 2,
|
||||
y: mainWindowSizeInfo.y + (windowSizeInfo.height - 320 + 52) / 2,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
// movable: false,
|
||||
// frame: false,
|
||||
// modal: true,
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
disableHtmlFullscreenWindowResize: true,
|
||||
},
|
||||
})
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
if (/punish\?/.test(win.webContents.getURL())) return
|
||||
let ses = win.webContents.session
|
||||
ses.cookies.get({ name: 'x5sec' })
|
||||
.then(async([x5sec]) => {
|
||||
isActioned = true
|
||||
await closeWin()
|
||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
||||
resolve(x5sec.value)
|
||||
}).catch(async err => {
|
||||
isActioned = true
|
||||
await closeWin()
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
|
||||
win.webContents.loadURL(url, {
|
||||
httpReferrer: 'https://www.xiami.com/',
|
||||
})
|
||||
|
||||
win.on('closed', async() => {
|
||||
await closeWin()
|
||||
if (isActioned) return
|
||||
reject(new Error('canceled verify'))
|
||||
})
|
||||
|
||||
// win.webContents.openDevTools()
|
||||
}))
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
|
||||
await closeWin()
|
||||
})
|
|
@ -1,22 +1,18 @@
|
|||
const Store = require('electron-store')
|
||||
const { windowSizeList } = require('../../common/config')
|
||||
const { objectDeepMerge, throttle } = require('../../common/utils')
|
||||
const { objectDeepMerge, throttle, initSetting } = require('../../common/utils')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
exports.getWindowSizeInfo = ({ windowSizeId = 1 } = {}) => {
|
||||
return windowSizeList.find(i => i.id === windowSizeId) || windowSizeList[0]
|
||||
}
|
||||
|
||||
const electronStore_config = new Store({
|
||||
name: 'config',
|
||||
})
|
||||
exports.getAppSetting = () => {
|
||||
return electronStore_config.get('setting')
|
||||
return getStore('config').get('setting')
|
||||
}
|
||||
|
||||
const electronStore_hotKey = new Store({
|
||||
name: 'hotKey',
|
||||
})
|
||||
exports.getAppHotKeyConfig = () => {
|
||||
const electronStore_hotKey = getStore('hotKey')
|
||||
|
||||
return {
|
||||
global: electronStore_hotKey.get('global'),
|
||||
local: electronStore_hotKey.get('local'),
|
||||
|
@ -25,17 +21,23 @@ exports.getAppHotKeyConfig = () => {
|
|||
const saveHotKeyConfig = throttle(config => {
|
||||
for (const key of Object.keys(config)) {
|
||||
global.appHotKey.config[key] = config[key]
|
||||
electronStore_hotKey.set(key, config[key])
|
||||
getStore('hotKey').set(key, config[key])
|
||||
}
|
||||
})
|
||||
exports.saveAppHotKeyConfig = config => {
|
||||
saveHotKeyConfig(config)
|
||||
}
|
||||
|
||||
const saveSetting = throttle(n => {
|
||||
electronStore_config.set('setting', n)
|
||||
})
|
||||
exports.updateSetting = settings => {
|
||||
// const saveSetting = throttle(n => {
|
||||
// electronStore_config.set('setting', n)
|
||||
// })
|
||||
exports.updateSetting = (settings) => {
|
||||
objectDeepMerge(global.appSetting, settings)
|
||||
saveSetting(global.appSetting)
|
||||
getStore('config').set('setting', global.appSetting)
|
||||
exports.initSetting(false)
|
||||
}
|
||||
exports.initSetting = (isShowErrorAlert = true) => {
|
||||
const info = initSetting(isShowErrorAlert)
|
||||
global.appSetting = info.setting
|
||||
global.appSettingVersion = info.version
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
.control-bar(v-show="!lrcConfig.isLock")
|
||||
core-control-bar(:lrcConfig="lrcConfig" :themes="themeList")
|
||||
core-lyric(:lrcConfig="lrcConfig" :isShowLyricTransition="isShowLyricTransition")
|
||||
core-lyric(:lrcConfig="lrcConfig" :isPlayLxlrc="isPlayLxlrc" :isShowLyricTranslation="isShowLyricTranslation")
|
||||
div.resize-left(@mousedown.self="handleMouseDown('left', $event)")
|
||||
div.resize-top(@mousedown.self="handleMouseDown('top', $event)")
|
||||
div.resize-right(@mousedown.self="handleMouseDown('right', $event)")
|
||||
|
@ -44,7 +44,8 @@ export default {
|
|||
isZoomActiveLrc: true,
|
||||
},
|
||||
},
|
||||
isShowLyricTransition: true,
|
||||
isShowLyricTranslation: true,
|
||||
isPlayLxlrc: true,
|
||||
themeList: [
|
||||
{
|
||||
id: 0,
|
||||
|
@ -118,9 +119,10 @@ export default {
|
|||
document.removeEventListener('mouseup', this.handleMouseUp)
|
||||
},
|
||||
methods: {
|
||||
handleUpdateConfig({ config, languageId, isShowLyricTransition }) {
|
||||
handleUpdateConfig({ config, languageId, isShowLyricTranslation, isPlayLxlrc }) {
|
||||
this.lrcConfig = config
|
||||
this.isShowLyricTransition = isShowLyricTransition
|
||||
this.isShowLyricTranslation = isShowLyricTranslation
|
||||
this.isPlayLxlrc = isPlayLxlrc
|
||||
if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId
|
||||
},
|
||||
handleMouseDown(origin, event) {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<template lang="pug">
|
||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }, { [$style.lrcActiveZoom]: lrcConfig.style.isZoomActiveLrc } ]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="$style.lyricSpace")
|
||||
div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]")
|
||||
div(:class="[$style.lyricText]" ref="dom_lyric_text")
|
||||
//- div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]")
|
||||
p(:class="$style.lrcLine") {{info.text}}
|
||||
div(:class="$style.lyricSpace")
|
||||
</template>
|
||||
|
@ -9,7 +10,7 @@ div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" :style=
|
|||
<script>
|
||||
import { rendererOn, rendererSend, NAMES } from '../../../common/ipc'
|
||||
import { scrollTo } from '../../../renderer/utils'
|
||||
import Lyric from 'lrc-file-parser'
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
|
||||
let cancelScrollFn = null
|
||||
|
||||
|
@ -27,7 +28,11 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
isShowLyricTransition: {
|
||||
isPlayLxlrc: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isShowLyricTranslation: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
@ -65,6 +70,7 @@ export default {
|
|||
lyrics: {
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
lxlyric: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -85,7 +91,7 @@ export default {
|
|||
if (n.length) {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
} else {
|
||||
|
@ -97,7 +103,7 @@ export default {
|
|||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||
this.lyricLines = this._lyricLines
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}, 50)
|
||||
|
@ -105,7 +111,7 @@ export default {
|
|||
} else {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}
|
||||
|
@ -120,8 +126,11 @@ export default {
|
|||
},
|
||||
immediate: true,
|
||||
},
|
||||
isShowLyricTransition(n) {
|
||||
console.log(n)
|
||||
isShowLyricTranslation() {
|
||||
this.setLyric()
|
||||
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
||||
},
|
||||
isPlayLxlrc() {
|
||||
this.setLyric()
|
||||
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
|
||||
},
|
||||
|
@ -129,6 +138,11 @@ export default {
|
|||
created() {
|
||||
rendererOn(NAMES.winLyric.set_lyric_info, (event, data) => this.handleSetInfo(data))
|
||||
window.lrc = new Lyric({
|
||||
lineClassName: 'lrc-content',
|
||||
fontClassName: 'font',
|
||||
shadowClassName: 'shadow',
|
||||
shadowContent: true,
|
||||
activeLineClassName: 'active',
|
||||
onPlay: (line, text) => {
|
||||
this.lyric.text = text
|
||||
this.lyric.line = line
|
||||
|
@ -136,6 +150,12 @@ export default {
|
|||
},
|
||||
onSetLyric: lines => { // listening lyrics seting event
|
||||
// console.log(lines) // lines is array of all lyric text
|
||||
this.$refs.dom_lyric_text.textContent = ''
|
||||
const dom_lines = document.createDocumentFragment()
|
||||
for (const line of lines) {
|
||||
dom_lines.appendChild(line.dom_line)
|
||||
}
|
||||
this.$refs.dom_lyric_text.appendChild(dom_lines)
|
||||
this.lyric.lines = lines
|
||||
this.lyric.line = 0
|
||||
},
|
||||
|
@ -159,6 +179,7 @@ export default {
|
|||
case 'lyric':
|
||||
this.lyrics.lyric = data.lrc
|
||||
this.lyrics.tlyric = data.tlrc
|
||||
this.lyrics.lxlyric = data.lxlrc
|
||||
this.setLyric()
|
||||
break
|
||||
case 'play':
|
||||
|
@ -171,8 +192,9 @@ export default {
|
|||
break
|
||||
case 'info':
|
||||
// console.log('info', data)
|
||||
this.lyrics.lyric = data.lyric
|
||||
this.lyrics.tlyric = data.tlyric
|
||||
this.lyrics.lyric = data.lrc
|
||||
this.lyrics.tlyric = data.tlrc
|
||||
this.lyrics.lxlyric = data.lxlrc
|
||||
this.setLyric()
|
||||
this.$nextTick(() => {
|
||||
this.lyric.line = data.line
|
||||
|
@ -211,7 +233,10 @@ export default {
|
|||
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0)
|
||||
},
|
||||
handleLyricMouseDown(e) {
|
||||
if (e.target.classList.contains(this.$style.lrcLine)) {
|
||||
if (e.target.classList.contains('font') ||
|
||||
e.target.parentNode.classList.contains('font') ||
|
||||
e.target.classList.contains('translation') ||
|
||||
e.target.parentNode.classList.contains('translation')) {
|
||||
this.lyricEvent.isMsDown = true
|
||||
this.lyricEvent.msDownY = e.clientY
|
||||
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
|
||||
|
@ -279,7 +304,11 @@ export default {
|
|||
rendererSend(NAMES.winLyric.close)
|
||||
},
|
||||
setLyric() {
|
||||
window.lrc.setLyric((this.isShowLyricTransition && this.lyrics.tlyric ? (this.lyrics.tlyric + '\n') : '') + (this.lyrics.lyric || ''))
|
||||
window.lrc.setLyric(
|
||||
this.isPlayLxlrc && this.lyrics.lxlyric ? this.lyrics.lxlyric : this.lyrics.lyric,
|
||||
this.isShowLyricTranslation && this.lyrics.tlyric ? this.lyrics.tlyric : '',
|
||||
// (this.isShowLyricTranslation && this.lyrics.tlyric ? (this.lyrics.tlyric + '\n') : '') + (this.lyrics.lyric || ''),
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -292,34 +321,93 @@ export default {
|
|||
text-align: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 68px;
|
||||
padding: 0 5px;
|
||||
opacity: .6;
|
||||
transition: opacity @transition-theme;
|
||||
&.draging {
|
||||
.lrc-line {
|
||||
cursor: grabbing;
|
||||
font-size: 16px;
|
||||
color: @color-theme-lyric;
|
||||
cursor: move;
|
||||
|
||||
:global {
|
||||
.lrc-content {
|
||||
line-height: 1.2;
|
||||
margin: 16px 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
.font {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.font, .translation {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.translation {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size, color;
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.line {
|
||||
transition-property: font-size, color !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: unset;
|
||||
// -webkit-text-fill-color: none !important;
|
||||
}
|
||||
&.active {
|
||||
.line {
|
||||
color: @color-theme;
|
||||
}
|
||||
.translation {
|
||||
color: @color-theme;
|
||||
}
|
||||
// span {
|
||||
// color: @color-theme;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size !important;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: @color-theme-lyric;
|
||||
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.shadow {
|
||||
text-shadow: 1px 1px 2px rgb(32, 32, 32);
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
}
|
||||
.lrc-line {
|
||||
display: inline-block;
|
||||
padding: 8px 0;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme;
|
||||
transition-property: color, font-size, text-shadow;
|
||||
cursor: grab;
|
||||
// font-weight: bold;
|
||||
// background-clip: text;
|
||||
color: @color-theme-lyric;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
// background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
|
||||
// background-clip: text;
|
||||
// -webkit-background-clip: text;
|
||||
// -webkit-text-fill-color: #fff;
|
||||
// -webkit-text-stroke: thin #124628;
|
||||
}
|
||||
// .lrc-line {
|
||||
// display: inline-block;
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme;
|
||||
// transition-property: color, font-size, text-shadow;
|
||||
// cursor: grab;
|
||||
// // font-weight: bold;
|
||||
// // background-clip: text;
|
||||
// color: @color-theme-lyric;
|
||||
// text-shadow: 1px 1px 2px #000;
|
||||
// // background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
|
||||
// // background-clip: text;
|
||||
// // -webkit-background-clip: text;
|
||||
// // -webkit-text-fill-color: #fff;
|
||||
// // -webkit-text-stroke: thin #124628;
|
||||
// }
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
}
|
||||
|
@ -334,9 +422,28 @@ export default {
|
|||
// -webkit-text-stroke: thin #124628;
|
||||
}
|
||||
}
|
||||
.draging {
|
||||
:global {
|
||||
.lrc-content {
|
||||
.font, .translation {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.lrc-active-zoom {
|
||||
.lrc-active;
|
||||
font-size: 1.2em;
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.translation {
|
||||
font-size: 1em;
|
||||
}
|
||||
span {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
flex: 0 0 100px;
|
||||
|
@ -348,14 +455,33 @@ export default {
|
|||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.lrc-line {
|
||||
color: ~'@{color-@{value}-theme-lyric}';
|
||||
}
|
||||
// .lrc-line {
|
||||
// color: ~'@{color-@{value}-theme-lyric}';
|
||||
// }
|
||||
.lrc-active, .lrc-active-zoom {
|
||||
.lrc-line {
|
||||
color: ~'@{color-@{value}-theme-lyric_2}';
|
||||
}
|
||||
}
|
||||
.lyric {
|
||||
color: ~'@{color-@{value}-theme-lyric}';
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.translation {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
.line {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
span {
|
||||
// background-color: ~'@{color-@{value}-theme_2-font}';
|
||||
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-theme}', ~'@{color-@{value}-theme}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
core-view#view
|
||||
core-player#player
|
||||
core-icons
|
||||
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
#container(v-else :class="theme")
|
||||
|
@ -16,7 +15,6 @@
|
|||
core-view#view
|
||||
core-player#player
|
||||
core-icons
|
||||
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
</template>
|
||||
|
@ -27,8 +25,9 @@ import { mapMutations, mapGetters, mapActions } from 'vuex'
|
|||
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
||||
import { isLinux } from '../common/utils'
|
||||
import music from './utils/music'
|
||||
import { throttle, openUrl, compareVer, getPlayList } from './utils'
|
||||
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils'
|
||||
import { base as eventBaseName } from './event/names'
|
||||
import apiSourceInfo from './utils/music/api-source-info'
|
||||
|
||||
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||
dnscache({
|
||||
|
@ -37,6 +36,20 @@ dnscache({
|
|||
cachesize: 1000,
|
||||
})
|
||||
|
||||
const listThrottle = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _data = {}
|
||||
return function(data) {
|
||||
Object.assign(_data, data)
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.call(this, _data)
|
||||
_data = {}
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -44,12 +57,15 @@ export default {
|
|||
isDT: false,
|
||||
isLinux,
|
||||
globalObj: {
|
||||
apiSource: 'test',
|
||||
apiSource: null,
|
||||
proxy: {},
|
||||
isShowPact: false,
|
||||
qualityList: {},
|
||||
xm: {
|
||||
isShowVerify: false,
|
||||
userApi: {
|
||||
list: [],
|
||||
status: false,
|
||||
message: 'initing',
|
||||
apis: {},
|
||||
},
|
||||
},
|
||||
updateTimeout: null,
|
||||
|
@ -70,24 +86,12 @@ export default {
|
|||
}),
|
||||
},
|
||||
created() {
|
||||
this.saveDefaultList = throttle(n => {
|
||||
this.saveMyList = listThrottle(data => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'defaultList',
|
||||
data: n,
|
||||
type: 'myList',
|
||||
data,
|
||||
})
|
||||
}, 500)
|
||||
this.saveLoveList = throttle(n => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'loveList',
|
||||
data: n,
|
||||
})
|
||||
}, 500)
|
||||
this.saveUserList = throttle(n => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'userList',
|
||||
data: n,
|
||||
})
|
||||
}, 500)
|
||||
}, 300)
|
||||
this.saveDownloadList = throttle(n => {
|
||||
rendererSend(NAMES.mainWindow.save_playlist, {
|
||||
type: 'downloadList',
|
||||
|
@ -109,25 +113,25 @@ export default {
|
|||
watch: {
|
||||
setting: {
|
||||
handler(n, o) {
|
||||
rendererSend(NAMES.mainWindow.set_app_setting, n)
|
||||
saveSetting(n)
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
defaultList: {
|
||||
handler(n) {
|
||||
this.saveDefaultList(n)
|
||||
this.saveMyList({ defaultList: n })
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
loveList: {
|
||||
handler(n) {
|
||||
this.saveLoveList(n)
|
||||
this.saveMyList({ loveList: n })
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
userList: {
|
||||
handler(n) {
|
||||
this.saveUserList(n)
|
||||
this.saveMyList({ userList: n })
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
|
@ -140,14 +144,40 @@ export default {
|
|||
searchHistoryList(n) {
|
||||
this.saveSearchHistoryList(n)
|
||||
},
|
||||
'globalObj.apiSource'(n) {
|
||||
this.globalObj.qualityList = music.supportQuality[n]
|
||||
'globalObj.apiSource'(n, o) {
|
||||
if (/^user_api/.test(n)) {
|
||||
this.globalObj.qualityList = {}
|
||||
this.globalObj.userApi.status = false
|
||||
this.globalObj.userApi.message = 'initing'
|
||||
} else {
|
||||
this.globalObj.qualityList = music.supportQuality[n]
|
||||
}
|
||||
if (o === null) return
|
||||
rendererInvoke(NAMES.mainWindow.set_user_api, n).catch(err => {
|
||||
console.log(err)
|
||||
let api = apiSourceInfo.find(api => !api.disabled)
|
||||
if (api) this.globalObj.apiSource = api.id
|
||||
})
|
||||
if (n != this.setting.apiSource) {
|
||||
this.setSetting(Object.assign({}, this.setting, {
|
||||
apiSource: n,
|
||||
}))
|
||||
}
|
||||
},
|
||||
'globalObj.proxy.enable'(n, o) {
|
||||
if (n != this.setting.network.proxy.enable) {
|
||||
this.setSetting({
|
||||
...this.setting,
|
||||
network: {
|
||||
...this.setting.network,
|
||||
proxy: {
|
||||
...this.setting.network.proxy,
|
||||
enable: n,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
'windowSizeActive.fontSize'(n) {
|
||||
document.documentElement.style.fontSize = n
|
||||
},
|
||||
|
@ -177,10 +207,12 @@ export default {
|
|||
...mapMutations('player', {
|
||||
setPlayList: 'setList',
|
||||
}),
|
||||
...mapActions('songList', ['getListDetailAll']),
|
||||
init() {
|
||||
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
|
||||
|
||||
rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit)
|
||||
const asyncTask = []
|
||||
asyncTask.push(rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit))
|
||||
|
||||
document.body.addEventListener('click', this.handleBodyClick, true)
|
||||
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
|
||||
|
@ -228,7 +260,7 @@ export default {
|
|||
})
|
||||
|
||||
rendererOn(NAMES.mainWindow.set_config, (event, config) => {
|
||||
// console.log(config)
|
||||
console.log(config)
|
||||
// this.setDesktopLyricConfig(config)
|
||||
// console.log('set_config', JSON.stringify(this.setting) === JSON.stringify(config))
|
||||
this.setSetting(Object.assign({}, this.setting, config))
|
||||
|
@ -244,14 +276,23 @@ export default {
|
|||
}, 60 * 30 * 1000)
|
||||
|
||||
this.listenEvent()
|
||||
this.initData()
|
||||
asyncTask.push(this.initData())
|
||||
asyncTask.push(this.initUserApi())
|
||||
this.globalObj.apiSource = this.setting.apiSource
|
||||
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
|
||||
if (/^user_api/.test(this.setting.apiSource)) {
|
||||
rendererInvoke(NAMES.mainWindow.set_user_api, this.setting.apiSource)
|
||||
} else {
|
||||
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
|
||||
}
|
||||
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
|
||||
window.globalObj = this.globalObj
|
||||
|
||||
// 初始化音乐sdk
|
||||
music.init()
|
||||
asyncTask.push(music.init())
|
||||
Promise.all(asyncTask).then(() => {
|
||||
this.handleInitEnvParamSearch()
|
||||
this.handleInitEnvParamPlay()
|
||||
})
|
||||
},
|
||||
enableIgnoreMouseEvents() {
|
||||
if (this.isDT) return
|
||||
|
@ -265,12 +306,14 @@ export default {
|
|||
},
|
||||
|
||||
initData() { // 初始化数据
|
||||
this.initLocalList() // 初始化播放列表
|
||||
return Promise.all([
|
||||
this.initMyList(), // 初始化播放列表
|
||||
this.initSearchHistoryList(), // 初始化搜索历史列表
|
||||
])
|
||||
// this.initDownloadList() // 初始化下载列表
|
||||
this.initSearchHistoryList() // 初始化搜索历史列表
|
||||
},
|
||||
initLocalList() {
|
||||
getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
||||
initMyList() {
|
||||
return getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
||||
if (!defaultList) defaultList = this.defaultList
|
||||
if (!loveList) loveList = this.loveList
|
||||
if (userList) {
|
||||
|
@ -284,7 +327,7 @@ export default {
|
|||
l.sourceListId = id
|
||||
if (!needSave) needSave = true
|
||||
})
|
||||
if (needSave) this.saveUserList(userList)
|
||||
if (needSave) this.saveMyList({ userList })
|
||||
} else {
|
||||
userList = this.userList
|
||||
}
|
||||
|
@ -326,7 +369,7 @@ export default {
|
|||
if (info.listId) {
|
||||
const list = window.allList[info.listId]
|
||||
// console.log(list)
|
||||
if (!list) return
|
||||
if (!list || !list.list[info.index]) return
|
||||
info.list = list.list
|
||||
}
|
||||
|
||||
|
@ -340,6 +383,72 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
initUserApi() {
|
||||
return Promise.all([
|
||||
rendererOn(NAMES.mainWindow.user_api_status, (event, { status, message, apiInfo }) => {
|
||||
// console.log(apiInfo)
|
||||
this.globalObj.userApi.status = status
|
||||
this.globalObj.userApi.message = message
|
||||
if (status) {
|
||||
if (apiInfo.id === this.setting.apiSource) {
|
||||
let apis = {}
|
||||
let qualitys = {}
|
||||
for (const [source, { actions, type, qualitys: sourceQualitys }] of Object.entries(apiInfo.sources)) {
|
||||
if (type != 'music') continue
|
||||
apis[source] = {}
|
||||
for (const action of actions) {
|
||||
switch (action) {
|
||||
case 'musicUrl':
|
||||
apis[source].getMusicUrl = (songInfo, type) => {
|
||||
const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
return {
|
||||
canceleFn() {
|
||||
rendererInvoke(NAMES.mainWindow.request_user_api_cancel, requestKey)
|
||||
},
|
||||
promise: rendererInvoke(NAMES.mainWindow.request_user_api, {
|
||||
requestKey,
|
||||
data: {
|
||||
source: source,
|
||||
action: 'musicUrl',
|
||||
info: {
|
||||
type,
|
||||
musicInfo: songInfo,
|
||||
},
|
||||
},
|
||||
}).then(res => {
|
||||
// console.log(res)
|
||||
if (!/^https?:/.test(res.data.url)) return Promise.reject(new Error('Get url failed'))
|
||||
return { type, url: res.data.url }
|
||||
}).catch(err => {
|
||||
console.log(err.message)
|
||||
return Promise.reject(err)
|
||||
}),
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
qualitys[source] = sourceQualitys
|
||||
}
|
||||
this.globalObj.qualityList = qualitys
|
||||
this.globalObj.userApi.apis = apis
|
||||
}
|
||||
}
|
||||
}),
|
||||
rendererInvoke(NAMES.mainWindow.get_user_api_list).then(res => {
|
||||
// console.log(res)
|
||||
if (![...apiSourceInfo.map(s => s.id), ...res.map(s => s.id)].includes(this.setting.apiSource)) {
|
||||
console.warn('reset api')
|
||||
let api = apiSourceInfo.find(api => !api.disabled)
|
||||
if (api) this.globalObj.apiSource = api.id
|
||||
}
|
||||
this.globalObj.userApi.list = res
|
||||
}),
|
||||
])
|
||||
},
|
||||
showUpdateModal() {
|
||||
(this.version.newVersion && this.version.newVersion.history
|
||||
? Promise.resolve(this.version.newVersion)
|
||||
|
@ -389,18 +498,91 @@ export default {
|
|||
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
|
||||
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
|
||||
}
|
||||
this.handleInitEnvParamSearch()
|
||||
this.handleInitEnvParamPlay()
|
||||
},
|
||||
// 处理启动参数 search
|
||||
handleInitEnvParamSearch() {
|
||||
if (this.envParams.search == null) return
|
||||
this.$router.push({
|
||||
path: 'search',
|
||||
query: {
|
||||
text: this.envParams.search,
|
||||
},
|
||||
})
|
||||
},
|
||||
// 处理启动参数 play
|
||||
handleInitEnvParamPlay() {
|
||||
if (this.envParams.play == null || typeof this.envParams.play != 'string') return
|
||||
// -play="source=kw&link=链接、ID"
|
||||
// -play="source=myList&name=名字"
|
||||
// -play="source=myList&name=名字&index=位置"
|
||||
const params = parseUrlParams(this.envParams.play)
|
||||
if (params.type != 'songList') return
|
||||
this.handlePlaySongList(params)
|
||||
},
|
||||
handlePlaySongList(params) {
|
||||
switch (params.source) {
|
||||
case 'myList':
|
||||
if (params.name != null) {
|
||||
let targetList
|
||||
const lists = Object.values(window.allList)
|
||||
for (const list of lists) {
|
||||
if (list.name === params.name) {
|
||||
targetList = list
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!targetList) return
|
||||
|
||||
if (this.envParams.search != null) {
|
||||
this.$router.push({
|
||||
path: 'search',
|
||||
query: {
|
||||
text: this.envParams.search,
|
||||
},
|
||||
})
|
||||
|
||||
this.setPlayList({
|
||||
list: {
|
||||
list: targetList.list,
|
||||
id: targetList.id,
|
||||
},
|
||||
index: this.getListPlayIndex(targetList.list, params.index),
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'kw':
|
||||
case 'kg':
|
||||
case 'tx':
|
||||
case 'mg':
|
||||
case 'wy':
|
||||
this.playSongListDetail(params.source, params.link, params.index)
|
||||
break
|
||||
}
|
||||
},
|
||||
handleXMVerifyModalClose() {
|
||||
music.xm.closeVerifyModal()
|
||||
async playSongListDetail(source, link, playIndex) {
|
||||
if (link == null) return
|
||||
let list
|
||||
try {
|
||||
list = await this.getListDetailAll({ source, id: decodeURIComponent(link) })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
this.setPlayList({
|
||||
list: {
|
||||
list,
|
||||
id: null,
|
||||
},
|
||||
index: this.getListPlayIndex(list, playIndex),
|
||||
})
|
||||
},
|
||||
getListPlayIndex(list, index) {
|
||||
if (index == null) {
|
||||
index = 1
|
||||
} else {
|
||||
index = parseInt(index)
|
||||
if (Number.isNaN(index)) {
|
||||
index = 1
|
||||
} else {
|
||||
if (index < 1) index = 1
|
||||
else if (index > list.length) index = list.length
|
||||
}
|
||||
}
|
||||
return index - 1
|
||||
},
|
||||
listenEvent() {
|
||||
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
|
||||
|
@ -462,6 +644,10 @@ body {
|
|||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
#view { // 偏移5px距离解决非透明模式下右侧滚动条无法拖动的问题
|
||||
margin-right: 5Px;
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
|
|
|
@ -871,6 +871,41 @@
|
|||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes hinge {
|
||||
0% {
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
20%,
|
||||
60% {
|
||||
-webkit-transform: rotate3d(0, 0, 1, 80deg);
|
||||
transform: rotate3d(0, 0, 1, 80deg);
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
40%,
|
||||
80% {
|
||||
-webkit-transform: rotate3d(0, 0, 1, 60deg);
|
||||
transform: rotate3d(0, 0, 1, 60deg);
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 700px, 0);
|
||||
transform: translate3d(0, 700px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flipInX {
|
||||
backface-visibility: visible !important;
|
||||
|
|
|
@ -34,7 +34,7 @@ div(:class="$style.player")
|
|||
div(:class="$style.titleBtn" @click='addMusicTo' :tips="$t('core.player.add_music_to')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='80%' viewBox='0 0 512 512' space='preserve')
|
||||
use(xlink:href='#icon-add-2')
|
||||
//- div(:class="$style.playBtn" @click='handleNext' tips="音量")
|
||||
//- div(:class="$style.playBtn" @click='playNext' tips="音量")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 291.063 291.064' space='preserve')
|
||||
use(xlink:href='#icon-sound')
|
||||
|
||||
|
@ -49,7 +49,7 @@ div(:class="$style.player")
|
|||
span(style="margin: 0 5px;") /
|
||||
span {{maxPlayTimeStr}}
|
||||
div(:class="$style.right")
|
||||
div(:class="$style.playBtn" @click='handlePrev' :tips="$t('core.player.prev')" style="transform: rotate(180deg);")
|
||||
div(:class="$style.playBtn" @click='playPrev' :tips="$t('core.player.prev')" style="transform: rotate(180deg);")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
div(:class="$style.playBtn" :tips="isPlay ? $t('core.player.pause') : $t('core.player.play')" @click='togglePlay')
|
||||
|
@ -57,7 +57,7 @@ div(:class="$style.player")
|
|||
use(xlink:href='#icon-pause')
|
||||
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
|
||||
use(xlink:href='#icon-play')
|
||||
div(:class="$style.playBtn" @click='handleNext' :tips="$t('core.player.next')")
|
||||
div(:class="$style.playBtn" @click='playNext' :tips="$t('core.player.next')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
//- transition(enter-active-class="animated lightSpeedIn"
|
||||
|
@ -85,14 +85,12 @@ div(:class="$style.player")
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Lyric from 'lrc-file-parser'
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
||||
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
|
||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { requestMsg } from '../../utils/message'
|
||||
import { isMac } from '../../../common/utils'
|
||||
import { player as eventPlayerNames } from '../../../common/hotKey'
|
||||
import musicSdk from '@renderer/utils/music'
|
||||
import path from 'path'
|
||||
|
||||
let audio
|
||||
|
@ -107,7 +105,6 @@ const playNextModes = [
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
volume: 0,
|
||||
nowPlayTime: 0,
|
||||
maxPlayTime: 0,
|
||||
|
@ -123,7 +120,6 @@ export default {
|
|||
singer: '',
|
||||
album: '',
|
||||
},
|
||||
targetSong: null,
|
||||
pregessWidth: 0,
|
||||
lyric: {
|
||||
lines: [],
|
||||
|
@ -133,7 +129,6 @@ export default {
|
|||
delayNextTimeout: null,
|
||||
restorePlayTime: 0,
|
||||
retryNum: 0,
|
||||
isMac,
|
||||
volumeEvent: {
|
||||
isMsDown: false,
|
||||
msDownX: 0,
|
||||
|
@ -149,10 +144,19 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
...mapGetters('player', ['list', 'playIndex', 'changePlay', 'listId', 'isShowPlayerDetail', 'playedList']),
|
||||
...mapGetters('player', ['list', 'changePlay', 'playMusicInfo', 'isShowPlayerDetail', 'playInfo', 'playedList']),
|
||||
// pic() {
|
||||
// return this.musicInfo.img ? this.musicInfo.img : ''
|
||||
// },
|
||||
listId() { // 当前播放歌曲的列表ID
|
||||
return this.playInfo.listId
|
||||
},
|
||||
playIndex() { // 当前播放歌曲所在列表的 播放列表的播放位置
|
||||
return this.playInfo.playIndex
|
||||
},
|
||||
targetSong() {
|
||||
return this.playInfo.musicInfo
|
||||
},
|
||||
title() {
|
||||
return this.musicInfo.name
|
||||
? this.setting.download.fileName.replace('歌名', this.musicInfo.name).replace('歌手', this.musicInfo.singer)
|
||||
|
@ -211,8 +215,9 @@ export default {
|
|||
singer: this.musicInfo.singer,
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
lyric: this.musicInfo.lrc,
|
||||
tlyric: this.musicInfo.tlrc,
|
||||
lrc: this.musicInfo.lrc,
|
||||
tlrc: this.musicInfo.tlrc,
|
||||
lxlrc: this.musicInfo.lxlrc,
|
||||
isPlay: this.isPlay,
|
||||
line: this.lyric.line,
|
||||
played_time: audio.currentTime * 1000,
|
||||
|
@ -247,38 +252,19 @@ export default {
|
|||
if (!n) return
|
||||
this.resetChangePlay()
|
||||
if (window.restorePlayInfo) {
|
||||
let musicInfo = this.targetSong = this.list[window.restorePlayInfo.index]
|
||||
this.musicInfo.songmid = musicInfo.songmid
|
||||
this.musicInfo.singer = musicInfo.singer
|
||||
this.musicInfo.name = musicInfo.name
|
||||
this.musicInfo.album = musicInfo.albumName
|
||||
this.setImg(musicInfo)
|
||||
this.setLrc(musicInfo)
|
||||
this.nowPlayTime = this.restorePlayTime = window.restorePlayInfo.time
|
||||
this.maxPlayTime = window.restorePlayInfo.maxTime || 0
|
||||
this.handleUpdateWinLyricInfo('music_info', {
|
||||
songmid: this.musicInfo.songmid,
|
||||
singer: this.musicInfo.singer,
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
})
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(musicInfo)
|
||||
this.handleRestorePlay(window.restorePlayInfo)
|
||||
window.restorePlayInfo = null
|
||||
return
|
||||
}
|
||||
// console.log('changePlay')
|
||||
this.handleRemoveMusic()
|
||||
if (this.playIndex < 0) return
|
||||
this.stopPlay()
|
||||
if (!this.playInfo.musicInfo) return
|
||||
this.play()
|
||||
},
|
||||
'setting.player.togglePlayMethod'(n) {
|
||||
audio.loop = n === 'singleLoop'
|
||||
if (this.playedList.length) this.clearPlayedList()
|
||||
if (n == 'random' && this.playIndex > -1) this.setPlayedList(this.list[this.playIndex])
|
||||
if (n == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
'setting.player.isMute'(n) {
|
||||
audio.muted = n
|
||||
|
@ -286,7 +272,10 @@ export default {
|
|||
'setting.player.mediaDeviceId'(n) {
|
||||
this.setMediaDevice()
|
||||
},
|
||||
'setting.player.isShowLyricTransition'() {
|
||||
'setting.player.isShowLyricTranslation'() {
|
||||
this.setLyric()
|
||||
},
|
||||
'setting.player.isPlayLxlrc'() {
|
||||
this.setLyric()
|
||||
},
|
||||
async list(n, o) {
|
||||
|
@ -297,13 +286,13 @@ export default {
|
|||
if (index < 0) {
|
||||
// console.log(this.playIndex)
|
||||
if (n.length) {
|
||||
this.fixPlayIndex(this.playIndex - 1)
|
||||
this.handleNext()
|
||||
this.setPlayIndex(this.playInfo.listPlayIndex - 1)
|
||||
this.playNext()
|
||||
} else {
|
||||
this.setPlayIndex(-1)
|
||||
this.setPlayMusicInfo(null)
|
||||
}
|
||||
} else {
|
||||
this.fixPlayIndex(index)
|
||||
this.setPlayIndex(index)
|
||||
}
|
||||
// console.log(this.playIndex)
|
||||
}
|
||||
|
@ -317,26 +306,38 @@ export default {
|
|||
},
|
||||
nowPlayTime(n, o) {
|
||||
if (Math.abs(n - o) > 2) this.isActiveTransition = true
|
||||
this.savePlayInfo({
|
||||
time: n,
|
||||
maxTime: this.maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
if (this.setting.player.isSavePlayTime && !this.playInfo.isTempPlay) {
|
||||
this.savePlayInfo({
|
||||
time: n,
|
||||
maxTime: this.maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
maxPlayTime(maxPlayTime) {
|
||||
if (!this.playInfo.isTempPlay) {
|
||||
this.savePlayInfo({
|
||||
time: this.nowPlayTime,
|
||||
maxTime: maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
|
||||
...mapActions('player', ['getUrl', 'getPic', 'getLrc', 'playPrev', 'playNext']),
|
||||
...mapActions('list', ['getOtherSource']),
|
||||
...mapMutations('player', [
|
||||
'setPlayMusicInfo',
|
||||
'setPlayIndex',
|
||||
'fixPlayIndex',
|
||||
'resetChangePlay',
|
||||
'visiblePlayerDetail',
|
||||
'clearPlayedList',
|
||||
'setPlayedList',
|
||||
'removePlayedList',
|
||||
'setList',
|
||||
]),
|
||||
...mapMutations(['setVolume', 'setPlayNextMode', 'setVisibleDesktopLyric', 'setLockDesktopLyric']),
|
||||
...mapMutations('list', ['updateMusicInfo']),
|
||||
|
@ -345,8 +346,8 @@ export default {
|
|||
let eventHub = window.eventHub
|
||||
let name = action == 'on' ? '$on' : '$off'
|
||||
eventHub[name](eventPlayerNames.toggle_play.action, this.togglePlay)
|
||||
eventHub[name](eventPlayerNames.next.action, this.handleNext)
|
||||
eventHub[name](eventPlayerNames.prev.action, this.handlePrev)
|
||||
eventHub[name](eventPlayerNames.next.action, this.playNext)
|
||||
eventHub[name](eventPlayerNames.prev.action, this.playPrev)
|
||||
eventHub[name](eventPlayerNames.volume_up.action, this.handleSetVolumeUp)
|
||||
eventHub[name](eventPlayerNames.volume_down.action, this.handleSetVolumeDown)
|
||||
eventHub[name](eventPlayerNames.volume_mute.action, this.handleSetVolumeMute)
|
||||
|
@ -377,18 +378,19 @@ export default {
|
|||
console.log('播放完毕')
|
||||
this.stopPlay()
|
||||
this.status = this.statusText = this.$t('core.player.end')
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
})
|
||||
audio.addEventListener('error', () => {
|
||||
// console.log('code', audio.error)
|
||||
if (!this.musicInfo.songmid) return
|
||||
console.log('出错')
|
||||
this.stopPlay()
|
||||
this.clearLoadingTimeout()
|
||||
if (this.listId != 'download' && audio.error.code !== 1 && this.retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
|
||||
// console.log(this.retryNum)
|
||||
if (!this.restorePlayTime) this.restorePlayTime = audio.currentTime // 记录出错的播放时间
|
||||
this.retryNum++
|
||||
this.setUrl(this.list[this.playIndex], true)
|
||||
this.setUrl(this.targetSong, true)
|
||||
this.status = this.statusText = this.$t('core.player.refresh_url')
|
||||
return
|
||||
}
|
||||
|
@ -406,7 +408,9 @@ export default {
|
|||
audio.currentTime = this.restorePlayTime
|
||||
this.restorePlayTime = 0
|
||||
}
|
||||
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
|
||||
if (!this.targetSong.interval && this.listId != 'download') {
|
||||
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) }, musicInfo: this.targetSong })
|
||||
}
|
||||
})
|
||||
audio.addEventListener('loadstart', () => {
|
||||
console.log('loadstart')
|
||||
|
@ -452,6 +456,10 @@ export default {
|
|||
})
|
||||
|
||||
window.lrc = new Lyric({
|
||||
lineClassName: 'lrc-content',
|
||||
fontClassName: 'font',
|
||||
shadowContent: false,
|
||||
activeLineClassName: 'active',
|
||||
onPlay: (line, text) => {
|
||||
this.lyric.text = text
|
||||
this.lyric.line = line
|
||||
|
@ -463,16 +471,17 @@ export default {
|
|||
this.lyric.lines = lines
|
||||
this.lyric.line = 0
|
||||
},
|
||||
offset: 80,
|
||||
// offset: 80,
|
||||
})
|
||||
|
||||
this.handleRegisterEvent('on')
|
||||
},
|
||||
async play() {
|
||||
console.log('play', this.playIndex)
|
||||
this.clearDelayNextTimeout()
|
||||
let targetSong = this.targetSong = this.list[this.playIndex]
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(targetSong)
|
||||
|
||||
const targetSong = this.targetSong
|
||||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
this.retryNum = 0
|
||||
this.restorePlayTime = 0
|
||||
|
||||
|
@ -480,7 +489,7 @@ export default {
|
|||
const filePath = path.join(this.setting.download.savePath, targetSong.fileName)
|
||||
// console.log(filePath)
|
||||
if (!await checkPath(filePath) || !targetSong.isComplate || /\.ape$/.test(filePath)) {
|
||||
return this.list.length == 1 ? null : this.handleNext()
|
||||
return this.list.length == 1 ? null : this.playNext()
|
||||
}
|
||||
this.musicInfo.songmid = targetSong.musicInfo.songmid
|
||||
this.musicInfo.singer = targetSong.musicInfo.singer
|
||||
|
@ -491,7 +500,7 @@ export default {
|
|||
this.setImg(targetSong.musicInfo)
|
||||
this.setLrc(targetSong.musicInfo)
|
||||
} else {
|
||||
if (!this.assertApiSupport(targetSong.source)) return this.handleNext()
|
||||
// if (!this.assertApiSupport(targetSong.source)) return this.playNext()
|
||||
this.musicInfo.songmid = targetSong.songmid
|
||||
this.musicInfo.singer = targetSong.singer
|
||||
this.musicInfo.name = targetSong.name
|
||||
|
@ -506,6 +515,15 @@ export default {
|
|||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
})
|
||||
if (!this.playInfo.isTempPlay) {
|
||||
this.savePlayInfo({
|
||||
time: this.nowPlayTime,
|
||||
maxTime: this.maxPlayTime,
|
||||
listId: this.listId,
|
||||
list: this.listId == null ? this.list : null,
|
||||
index: this.playIndex,
|
||||
})
|
||||
}
|
||||
},
|
||||
clearDelayNextTimeout() {
|
||||
// console.log(this.delayNextTimeout)
|
||||
|
@ -518,120 +536,10 @@ export default {
|
|||
this.clearDelayNextTimeout()
|
||||
this.delayNextTimeout = setTimeout(() => {
|
||||
this.delayNextTimeout = null
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
}, 5000)
|
||||
},
|
||||
async filterList() {
|
||||
// if (this.list.listName === null) return
|
||||
let list
|
||||
let playedList = [...this.playedList]
|
||||
if (this.listId == 'download') {
|
||||
list = []
|
||||
for (const item of this.list) {
|
||||
const filePath = path.join(this.setting.download.savePath, item.fileName)
|
||||
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
|
||||
|
||||
let index = playedList.indexOf(item)
|
||||
if (index > -1) {
|
||||
playedList.splice(index, 1)
|
||||
continue
|
||||
}
|
||||
list.push(item)
|
||||
}
|
||||
} else {
|
||||
list = this.list.filter(s => {
|
||||
let index = playedList.indexOf(s)
|
||||
if (index > -1) {
|
||||
playedList.splice(index, 1)
|
||||
return false
|
||||
}
|
||||
return this.assertApiSupport(s.source)
|
||||
})
|
||||
}
|
||||
if (!list.length && this.playedList.length) {
|
||||
this.clearPlayedList()
|
||||
return this.filterList()
|
||||
}
|
||||
return list
|
||||
},
|
||||
async handlePrev() {
|
||||
// console.log(playIndex)
|
||||
if (this.setting.player.togglePlayMethod == 'random' && this.playedList.length) {
|
||||
let index = this.playedList.indexOf(this.targetSong)
|
||||
index -= 1
|
||||
while (true) {
|
||||
if (index > -1) {
|
||||
let listIndex = this.list.indexOf(this.playedList[index])
|
||||
if (listIndex < 0) {
|
||||
this.removePlayedList(index)
|
||||
continue
|
||||
}
|
||||
this.setPlayIndex(listIndex)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
let list = await this.filterList()
|
||||
if (!list.length) return this.setPlayIndex(-1)
|
||||
let playIndex = list.indexOf(this.list[this.playIndex])
|
||||
let index
|
||||
switch (this.setting.player.togglePlayMethod) {
|
||||
case 'random':
|
||||
index = this.hanldeListRandom(list, playIndex)
|
||||
break
|
||||
case 'listLoop':
|
||||
case 'list':
|
||||
index = playIndex === 0 ? list.length - 1 : playIndex - 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
if (index < 0) return
|
||||
index = this.list.indexOf(list[index])
|
||||
this.setPlayIndex(index)
|
||||
},
|
||||
async handleNext() {
|
||||
// if (this.list.listName === null) return
|
||||
// eslint-disable-next-line no-debugger
|
||||
if (this.setting.player.togglePlayMethod == 'random' && this.playedList.length) {
|
||||
let index = this.playedList.indexOf(this.targetSong)
|
||||
index += 1
|
||||
while (true) {
|
||||
if (index < this.playedList.length) {
|
||||
let listIndex = this.list.indexOf(this.playedList[index])
|
||||
if (listIndex < 0) {
|
||||
this.removePlayedList(index)
|
||||
continue
|
||||
}
|
||||
this.setPlayIndex(listIndex)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
let list = await this.filterList()
|
||||
if (!list.length) return this.setPlayIndex(-1)
|
||||
let playIndex = list.indexOf(this.list[this.playIndex])
|
||||
// console.log(playIndex)
|
||||
let index
|
||||
switch (this.setting.player.togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
index = playIndex === list.length - 1 ? 0 : playIndex + 1
|
||||
break
|
||||
case 'random':
|
||||
index = this.hanldeListRandom(list, playIndex)
|
||||
break
|
||||
case 'list':
|
||||
index = playIndex === list.length - 1 ? -1 : playIndex + 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
if (index < 0) return
|
||||
index = this.list.indexOf(list[index])
|
||||
this.setPlayIndex(index)
|
||||
},
|
||||
hanldeListRandom(list, index) {
|
||||
return getRandom(0, list.length)
|
||||
},
|
||||
|
@ -673,7 +581,7 @@ export default {
|
|||
togglePlay() {
|
||||
if (!audio.src) {
|
||||
if (this.restorePlayTime != null) {
|
||||
if (!this.assertApiSupport(this.targetSong.source)) return this.handleNext()
|
||||
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
|
||||
this.setUrl(this.targetSong)
|
||||
}
|
||||
return
|
||||
|
@ -699,11 +607,12 @@ export default {
|
|||
if (!retryedSource.includes(targetSong.source)) retryedSource.push(targetSong.source)
|
||||
|
||||
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
|
||||
this.musicInfo.url = targetSong.typeUrl[type]
|
||||
// this.musicInfo.url = await getMusicUrl(targetSong, type)
|
||||
this.status = this.statusText = this.$t('core.player.geting_url')
|
||||
|
||||
return this.getUrl({ musicInfo: targetSong, originMusic, type, isRefresh }).then(() => {
|
||||
audio.src = this.musicInfo.url = targetSong.typeUrl[type]
|
||||
return this.getUrl({ musicInfo: targetSong, originMusic, type, isRefresh }).then(url => {
|
||||
if ((targetSong !== this.targetSong && originMusic !== this.targetSong) || this.isPlay) return
|
||||
audio.src = this.musicInfo.url = url
|
||||
}).catch(err => {
|
||||
// console.log('err', err.message)
|
||||
if (err.message == requestMsg.cancelRequest) return
|
||||
|
@ -712,14 +621,11 @@ export default {
|
|||
|
||||
this.status = this.statusText = 'Try toggle source...'
|
||||
|
||||
return (originMusic.otherSource && originMusic.otherSource.length ? Promise.resolve(originMusic.otherSource) : musicSdk.findMusic(originMusic)).then(res => {
|
||||
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { otherSource: res } })
|
||||
return res
|
||||
}).then(otherSource => {
|
||||
return this.getOtherSource(originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
if (retryedSource.includes(item.source) || !this.assertApiSupport(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return this.setUrl(item, isRefresh, false, retryedSource, originMusic)
|
||||
}
|
||||
|
@ -740,13 +646,14 @@ export default {
|
|||
}
|
||||
},
|
||||
setLrc(targetSong) {
|
||||
this.getLrc(targetSong).then(() => {
|
||||
this.musicInfo.lrc = targetSong.lrc
|
||||
this.musicInfo.tlrc = targetSong.tlrc
|
||||
this.getLrc(targetSong).then(({ lyric, tlyric, lxlyric }) => {
|
||||
this.musicInfo.lrc = lyric
|
||||
this.musicInfo.tlrc = tlyric
|
||||
this.musicInfo.lxlrc = lxlyric
|
||||
}).catch(() => {
|
||||
this.status = this.statusText = this.$t('core.player.lyric_error')
|
||||
}).finally(() => {
|
||||
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc })
|
||||
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc, lxlrc: this.musicInfo.lxlrc })
|
||||
this.setLyric()
|
||||
})
|
||||
},
|
||||
|
@ -760,6 +667,7 @@ export default {
|
|||
this.musicInfo.songmid = null
|
||||
this.musicInfo.lrc = null
|
||||
this.musicInfo.tlrc = null
|
||||
this.musicInfo.lxlrc = null
|
||||
this.musicInfo.url = null
|
||||
this.nowPlayTime = 0
|
||||
this.maxPlayTime = 0
|
||||
|
@ -824,13 +732,13 @@ export default {
|
|||
this.setProgressWidth()
|
||||
},
|
||||
handleToMusicLocation() {
|
||||
if (!this.listId || this.listId == 'download') return
|
||||
if (!this.listId || this.listId == '__temp__' || this.listId == 'download') return
|
||||
if (this.playIndex == -1) return
|
||||
this.$router.push({
|
||||
path: 'list',
|
||||
query: {
|
||||
id: this.listId,
|
||||
scrollIndex: this.playIndex,
|
||||
scrollIndex: this.playInfo.playIndex,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
@ -845,7 +753,7 @@ export default {
|
|||
startLoadingTimeout() {
|
||||
// console.log('start load timeout')
|
||||
this.loadingTimeout = setTimeout(() => {
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
}, 20000)
|
||||
},
|
||||
clearLoadingTimeout() {
|
||||
|
@ -864,7 +772,7 @@ export default {
|
|||
if (skipTime > this.maxPlayTime) skipTime = (this.maxPlayTime - audio.currentTime) / 2
|
||||
if (skipTime - this.mediaBuffer.playTime < 1 || this.maxPlayTime - skipTime < 1) {
|
||||
this.mediaBuffer.playTime = 0
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
return
|
||||
}
|
||||
this.startBuffering()
|
||||
|
@ -924,13 +832,13 @@ export default {
|
|||
handlePlayDetailAction({ type, data }) {
|
||||
switch (type) {
|
||||
case 'prev':
|
||||
this.handlePrev()
|
||||
this.playPrev()
|
||||
break
|
||||
case 'togglePlay':
|
||||
this.togglePlay()
|
||||
break
|
||||
case 'next':
|
||||
this.handleNext()
|
||||
this.playNext()
|
||||
break
|
||||
case 'progress':
|
||||
this.setProgress(data)
|
||||
|
@ -950,7 +858,15 @@ export default {
|
|||
})
|
||||
},
|
||||
setLyric() {
|
||||
window.lrc.setLyric((this.setting.player.isShowLyricTransition && this.musicInfo.tlrc ? (this.musicInfo.tlrc + '\n') : '') + (this.musicInfo.lrc || ''))
|
||||
window.lrc.setLyric(
|
||||
this.setting.player.isPlayLxlrc && this.musicInfo.lxlrc ? this.musicInfo.lxlrc : this.musicInfo.lrc,
|
||||
this.setting.player.isShowLyricTranslation && this.musicInfo.tlrc ? this.musicInfo.tlrc : '',
|
||||
// (
|
||||
// this.setting.player.isShowLyricTranslation && this.musicInfo.tlrc
|
||||
// ? (this.musicInfo.tlrc + '\n')
|
||||
// : ''
|
||||
// ) + (this.musicInfo.lrc || ''),
|
||||
)
|
||||
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) {
|
||||
window.lrc.play(audio.currentTime * 1000)
|
||||
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
||||
|
@ -971,6 +887,28 @@ export default {
|
|||
if (!this.musicInfo.songmid) return
|
||||
this.isShowAddMusicTo = true
|
||||
},
|
||||
handleRestorePlay(restorePlayInfo) {
|
||||
let musicInfo = this.list[restorePlayInfo.index]
|
||||
this.musicInfo.songmid = musicInfo.songmid
|
||||
this.musicInfo.singer = musicInfo.singer
|
||||
this.musicInfo.name = musicInfo.name
|
||||
this.musicInfo.album = musicInfo.albumName
|
||||
this.setImg(musicInfo)
|
||||
this.setLrc(musicInfo)
|
||||
this.nowPlayTime = this.restorePlayTime = restorePlayInfo.time
|
||||
this.maxPlayTime = restorePlayInfo.maxTime || 0
|
||||
this.handleUpdateWinLyricInfo('music_info', {
|
||||
songmid: this.musicInfo.songmid,
|
||||
singer: this.musicInfo.singer,
|
||||
name: this.musicInfo.name,
|
||||
album: this.musicInfo.album,
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
})
|
||||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
div(:class="$style.right")
|
||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="$style.lyricSpace")
|
||||
p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="[$style.lyricText]" ref="dom_lyric_text")
|
||||
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="$style.lyricSpace")
|
||||
|
||||
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
|
||||
|
@ -158,11 +159,14 @@ export default {
|
|||
handler(n, o) {
|
||||
this.isSetedLines = true
|
||||
if (o) {
|
||||
this.$refs.dom_lyric_text.textContent = ''
|
||||
this.setLyric(n)
|
||||
|
||||
this._lyricLines = n
|
||||
if (n.length) {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
} else {
|
||||
|
@ -174,7 +178,7 @@ export default {
|
|||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||
this.lyricLines = this._lyricLines
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}, 50)
|
||||
|
@ -182,7 +186,7 @@ export default {
|
|||
} else {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}
|
||||
|
@ -235,6 +239,8 @@ export default {
|
|||
document.addEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.addEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
// console.log('object', this.$refs.dom_lyric_text)
|
||||
this.setLyric(this.lyricLines)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearLyricScrollTimeout()
|
||||
|
@ -250,6 +256,13 @@ export default {
|
|||
...mapMutations('player', [
|
||||
'visiblePlayerDetail',
|
||||
]),
|
||||
setLyric(lines) {
|
||||
const dom_lines = document.createDocumentFragment()
|
||||
for (const line of lines) {
|
||||
dom_lines.appendChild(line.dom_line)
|
||||
}
|
||||
this.$refs.dom_lyric_text.appendChild(dom_lines)
|
||||
},
|
||||
handleResize() {
|
||||
this.setProgressWidth()
|
||||
},
|
||||
|
@ -540,6 +553,7 @@ export default {
|
|||
|
||||
&:before {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
|
@ -565,16 +579,62 @@ export default {
|
|||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
cursor: grab;
|
||||
color: @color-theme_2-font;
|
||||
&.draging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
p {
|
||||
padding: 8px 0;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme !important;
|
||||
transition-property: color, font-size;
|
||||
:global {
|
||||
.lrc-content {
|
||||
line-height: 1.2;
|
||||
margin: 16px 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
.translation {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size, color;
|
||||
font-size: .9em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.line {
|
||||
transition-property: font-size, color !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: unset;
|
||||
// -webkit-text-fill-color: none !important;
|
||||
}
|
||||
&.active {
|
||||
.line {
|
||||
color: @color-theme;
|
||||
}
|
||||
.translation {
|
||||
font-size: 1em;
|
||||
color: @color-theme;
|
||||
}
|
||||
span {
|
||||
// color: @color-theme;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size !important;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(77, 77, 77, 0.9);
|
||||
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
}
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
|
@ -736,6 +796,7 @@ each(@themes, {
|
|||
.container {
|
||||
border-left-color: ~'@{color-@{value}-theme}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
// color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.right {
|
||||
&:before {
|
||||
|
@ -769,9 +830,27 @@ each(@themes, {
|
|||
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
|
||||
// border-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
.lrc-active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
.lyric {
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.translation {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
.line {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
span {
|
||||
// background-color: ~'@{color-@{value}-theme_2-font}';
|
||||
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-theme}', ~'@{color-@{value}-theme}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .lrc-active {
|
||||
// color: ~'@{color-@{value}-theme}';
|
||||
// }
|
||||
.footerLeftControlBtns {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
|
|
|
@ -1,319 +0,0 @@
|
|||
<template lang="pug">
|
||||
div(:class="$style.lists")
|
||||
h2(:class="$style.listsTitle") {{$t('core.aside.my_list')}}
|
||||
ul.scroll(:class="$style.listsContent")
|
||||
li(:class="$style.listsItem" v-for="item in lists" :key="item.id") {{item.name}}
|
||||
|
||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
div(v-show="!list.length" :class="$style.noitem")
|
||||
p(v-html="noItem")
|
||||
material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { scrollTo, clipboardWriteText, assertApiSupport } from '../../utils'
|
||||
export default {
|
||||
name: 'MaterialSongList',
|
||||
model: {
|
||||
prop: 'selectdData',
|
||||
event: 'input',
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
selectdData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
},
|
||||
noItem: {
|
||||
type: String,
|
||||
default: '列表加载中...',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
},
|
||||
watch: {
|
||||
selectdList(n) {
|
||||
const len = n.length
|
||||
if (len) {
|
||||
this.isShowEditBtn = true
|
||||
} else {
|
||||
this.isShowEditBtn = false
|
||||
}
|
||||
},
|
||||
selectdData(n) {
|
||||
const len = n.length
|
||||
if (len) {
|
||||
this.isShowEditBtn = true
|
||||
this.selectdList = [...n]
|
||||
} else {
|
||||
this.isShowEditBtn = false
|
||||
this.removeAllSelect()
|
||||
}
|
||||
},
|
||||
list(n) {
|
||||
this.removeAllSelect()
|
||||
if (!this.list.length) return
|
||||
this.$nextTick(() => scrollTo(this.$refs.dom_scrollContent, 0))
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clickTime: 0,
|
||||
clickIndex: -1,
|
||||
isShowEditBtn: false,
|
||||
selectdList: [],
|
||||
keyEvent: {
|
||||
isShiftDown: false,
|
||||
isModDown: false,
|
||||
isADown: false,
|
||||
aDownTimeout: null,
|
||||
},
|
||||
lastSelectIndex: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listenEvent()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unlistenEvent()
|
||||
},
|
||||
methods: {
|
||||
listenEvent() {
|
||||
window.eventHub.$on('key_shift_down', this.handle_key_shift_down)
|
||||
window.eventHub.$on('key_shift_up', this.handle_key_shift_up)
|
||||
window.eventHub.$on('key_mod_down', this.handle_key_mod_down)
|
||||
window.eventHub.$on('key_mod_up', this.handle_key_mod_up)
|
||||
window.eventHub.$on('key_mod+a_down', this.handle_key_mod_a_down)
|
||||
window.eventHub.$on('key_mod+a_up', this.handle_key_mod_a_up)
|
||||
},
|
||||
unlistenEvent() {
|
||||
window.eventHub.$off('key_shift_down', this.handle_key_shift_down)
|
||||
window.eventHub.$off('key_shift_up', this.handle_key_shift_up)
|
||||
window.eventHub.$off('key_mod_down', this.handle_key_mod_down)
|
||||
window.eventHub.$off('key_mod_up', this.handle_key_mod_up)
|
||||
window.eventHub.$off('key_mod+a_down', this.handle_key_mod_a_down)
|
||||
window.eventHub.$off('key_mod+a_up', this.handle_key_mod_a_up)
|
||||
},
|
||||
handle_key_shift_down() {
|
||||
if (!this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = true
|
||||
},
|
||||
handle_key_shift_up() {
|
||||
if (this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = false
|
||||
},
|
||||
handle_key_mod_down() {
|
||||
if (!this.keyEvent.isModDown) this.keyEvent.isModDown = true
|
||||
},
|
||||
handle_key_mod_up() {
|
||||
if (this.keyEvent.isModDown) this.keyEvent.isModDown = false
|
||||
},
|
||||
handle_key_mod_a_down() {
|
||||
if (!this.keyEvent.isADown) {
|
||||
this.keyEvent.isModDown = false
|
||||
this.keyEvent.isADown = true
|
||||
this.handleSelectAllData()
|
||||
if (this.keyEvent.aDownTimeout) clearTimeout(this.keyEvent.aDownTimeout)
|
||||
this.keyEvent.aDownTimeout = setTimeout(() => {
|
||||
this.keyEvent.aDownTimeout = null
|
||||
this.keyEvent.isADown = false
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
handle_key_mod_a_up() {
|
||||
if (this.keyEvent.isADown) {
|
||||
if (this.keyEvent.aDownTimeout) {
|
||||
clearTimeout(this.keyEvent.aDownTimeout)
|
||||
this.keyEvent.aDownTimeout = null
|
||||
}
|
||||
this.keyEvent.isADown = false
|
||||
}
|
||||
},
|
||||
handleDoubleClick(event, index) {
|
||||
if (event.target.classList.contains('select')) return
|
||||
|
||||
this.handleSelectData(event, index)
|
||||
|
||||
if (
|
||||
window.performance.now() - this.clickTime > 400 ||
|
||||
this.clickIndex !== index
|
||||
) {
|
||||
this.clickTime = window.performance.now()
|
||||
this.clickIndex = index
|
||||
return
|
||||
}
|
||||
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
|
||||
this.clickTime = 0
|
||||
this.clickIndex = -1
|
||||
},
|
||||
handleSelectData(event, clickIndex) {
|
||||
if (this.keyEvent.isShiftDown) {
|
||||
if (this.selectdList.length) {
|
||||
let lastSelectIndex = this.lastSelectIndex
|
||||
this.removeAllSelect()
|
||||
if (lastSelectIndex != clickIndex) {
|
||||
let isNeedReverse = false
|
||||
if (clickIndex < lastSelectIndex) {
|
||||
let temp = lastSelectIndex
|
||||
lastSelectIndex = clickIndex
|
||||
clickIndex = temp
|
||||
isNeedReverse = true
|
||||
}
|
||||
this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) this.selectdList.reverse()
|
||||
let nodes = this.$refs.dom_tbody.childNodes
|
||||
do {
|
||||
nodes[lastSelectIndex].classList.add('active')
|
||||
lastSelectIndex++
|
||||
} while (lastSelectIndex <= clickIndex)
|
||||
}
|
||||
} else {
|
||||
event.currentTarget.classList.add('active')
|
||||
this.selectdList.push(this.list[clickIndex])
|
||||
this.lastSelectIndex = clickIndex
|
||||
}
|
||||
} else if (this.keyEvent.isModDown) {
|
||||
this.lastSelectIndex = clickIndex
|
||||
let item = this.list[clickIndex]
|
||||
let index = this.selectdList.indexOf(item)
|
||||
if (index < 0) {
|
||||
this.selectdList.push(item)
|
||||
event.currentTarget.classList.add('active')
|
||||
} else {
|
||||
this.selectdList.splice(index, 1)
|
||||
event.currentTarget.classList.remove('active')
|
||||
}
|
||||
} else if (this.selectdList.length) {
|
||||
this.removeAllSelect()
|
||||
} else return
|
||||
this.$emit('input', [...this.selectdList])
|
||||
},
|
||||
removeAllSelect() {
|
||||
this.selectdList = []
|
||||
let dom_tbody = this.$refs.dom_tbody
|
||||
if (!dom_tbody) return
|
||||
let nodes = dom_tbody.querySelectorAll('.active')
|
||||
for (const node of nodes) {
|
||||
if (node.parentNode == dom_tbody) node.classList.remove('active')
|
||||
}
|
||||
},
|
||||
handleListBtnClick(info) {
|
||||
this.emitEvent('listBtnClick', info)
|
||||
},
|
||||
handleSelectAllData() {
|
||||
this.removeAllSelect()
|
||||
this.selectdList = [...this.list]
|
||||
let nodes = this.$refs.dom_tbody.childNodes
|
||||
for (const node of nodes) {
|
||||
node.classList.add('active')
|
||||
}
|
||||
this.$emit('input', [...this.selectdList])
|
||||
},
|
||||
handleTogglePage(page) {
|
||||
this.emitEvent('togglePage', page)
|
||||
},
|
||||
handleFlowBtnClick(action) {
|
||||
this.emitEvent('flowBtnClick', action)
|
||||
},
|
||||
emitEvent(action, data) {
|
||||
this.$emit('action', { action, data })
|
||||
},
|
||||
handleChangeSelect() {
|
||||
this.$emit('input', [...this.selectdList])
|
||||
},
|
||||
handleContextMenu(event) {
|
||||
if (!event.target.classList.contains('select')) return
|
||||
let classList = this.$refs.dom_scrollContent.classList
|
||||
classList.add(this.$style.copying)
|
||||
window.requestAnimationFrame(() => {
|
||||
let str = window.getSelection().toString()
|
||||
classList.remove(this.$style.copying)
|
||||
str = str.trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
})
|
||||
},
|
||||
assertApiSupport(source) {
|
||||
return assertApiSupport(source)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
.lists {
|
||||
flex: none;
|
||||
width: 15%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.title {
|
||||
font-size: 12px;
|
||||
line-height: 28px;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
flex: none;
|
||||
}
|
||||
.list {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.item {
|
||||
|
||||
}
|
||||
.noitem {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.tbody {
|
||||
td {
|
||||
&:first-child {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<template lang="pug">
|
||||
button(:class="[$style.btn, min ? $style.min : null]" :disabled="disabled" @click="$emit('click', $event)")
|
||||
button(:class="[$style.btn, min ? $style.min : null, outline ? $style.outline : null]" :disabled="disabled" @click="$emit('click', $event)")
|
||||
slot
|
||||
</template>
|
||||
|
||||
|
@ -9,6 +9,10 @@ export default {
|
|||
min: {
|
||||
type: Boolean,
|
||||
},
|
||||
outline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -36,6 +40,10 @@ export default {
|
|||
opacity: .4;
|
||||
}
|
||||
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
|
@ -54,6 +62,9 @@ each(@themes, {
|
|||
.btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
|||
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
|
||||
| {{$t('material.list_add_modal.title_last')}}
|
||||
div.scroll(:class="$style.btnContent")
|
||||
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
|
||||
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
|
||||
material-btn(:class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('view.list.lists_new_list_btn')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 42 42' space='preserve')
|
||||
use(xlink:href='#icon-addTo')
|
||||
|
@ -57,11 +57,12 @@ export default {
|
|||
computed: {
|
||||
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
|
||||
lists() {
|
||||
if (!this.musicInfo) return []
|
||||
return [
|
||||
this.defaultList,
|
||||
this.loveList,
|
||||
...this.userList,
|
||||
].filter(l => l.id != this.excludeListId.includes(l.id))
|
||||
].filter(l => !this.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: l.list.some(s => s.songmid == this.musicInfo.songmid) }))
|
||||
},
|
||||
spaceNum() {
|
||||
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
||||
|
|
|
@ -62,7 +62,7 @@ export default {
|
|||
this.defaultList,
|
||||
this.loveList,
|
||||
...this.userList,
|
||||
].filter(l => l.id != this.excludeListId.includes(l.id))
|
||||
].filter(l => !this.excludeListId.includes(l.id))
|
||||
},
|
||||
spaceNum() {
|
||||
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
|
||||
|
|
|
@ -138,7 +138,7 @@ export default {
|
|||
// will-change: transform;
|
||||
li {
|
||||
cursor: pointer;
|
||||
min-width: 90px;
|
||||
min-width: 96px;
|
||||
line-height: 34px;
|
||||
// color: @color-btn;
|
||||
padding: 0 10px;
|
||||
|
|
|
@ -74,9 +74,9 @@ export default {
|
|||
'slideOutLeft',
|
||||
'slideOutRight',
|
||||
'slideOutUp',
|
||||
'hinge',
|
||||
// 'hinge',
|
||||
],
|
||||
inClass: 'animated flipInX',
|
||||
inClass: 'animated jackInTheBox',
|
||||
outClass: 'animated flipOutX',
|
||||
unwatchFn: null,
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export default {
|
|||
singer: '',
|
||||
},
|
||||
page: 1,
|
||||
total: 10,
|
||||
total: 0,
|
||||
maxPage: 1,
|
||||
limit: 20,
|
||||
isHotLoading: true,
|
||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
|||
watch: {
|
||||
'setting.isAgreePact'(n) {
|
||||
if (n) return
|
||||
this.time = 10
|
||||
this.time = 5
|
||||
this.startTimeout()
|
||||
},
|
||||
},
|
||||
|
|
|
@ -31,8 +31,6 @@ div(:class="$style.songList")
|
|||
td(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
||||
material-list-buttons(:index="index" :class="$style.btns"
|
||||
:remove-btn="false" @btn-click="handleListBtnClick"
|
||||
:listAdd-btn="assertApiSupport(item.source)"
|
||||
:play-btn="assertApiSupport(item.source)"
|
||||
:download-btn="assertApiSupport(item.source)")
|
||||
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
|
||||
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
|
||||
|
@ -114,6 +112,11 @@ export default {
|
|||
action: 'download',
|
||||
disabled: !this.listMenu.itemMenuControl.download,
|
||||
},
|
||||
{
|
||||
name: this.$t('material.song_list.list_play_later'),
|
||||
action: 'playLater',
|
||||
disabled: !this.listMenu.itemMenuControl.playLater,
|
||||
},
|
||||
{
|
||||
name: this.$t('material.song_list.list_search'),
|
||||
action: 'search',
|
||||
|
@ -173,6 +176,7 @@ export default {
|
|||
itemMenuControl: {
|
||||
play: true,
|
||||
addTo: true,
|
||||
playLater: true,
|
||||
download: true,
|
||||
search: true,
|
||||
sourceDetail: true,
|
||||
|
@ -237,7 +241,7 @@ export default {
|
|||
this.clickIndex = index
|
||||
return
|
||||
}
|
||||
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
|
||||
this.emitEvent('testPlay', index)
|
||||
this.clickTime = 0
|
||||
this.clickIndex = -1
|
||||
},
|
||||
|
@ -334,8 +338,9 @@ export default {
|
|||
},
|
||||
handleListItemRigthClick(event, index) {
|
||||
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
|
||||
this.listMenu.itemMenuControl.play =
|
||||
this.listMenu.itemMenuControl.download =
|
||||
// this.listMenu.itemMenuControl.play =
|
||||
// this.listMenu.itemMenuControl.playLater =
|
||||
this.listMenu.itemMenuControl.download =
|
||||
this.assertApiSupport(this.list[index].source)
|
||||
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
|
||||
if (dom_selected) dom_selected.classList.remove('selected')
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
<template lang="pug">
|
||||
material-modal(:show="visible" bg-close @close="handleClose")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('material.user_api_modal.title')}}
|
||||
ul.scroll(v-if="apiList.length" :class="$style.content")
|
||||
li(:class="[$style.listItem, setting.apiSource == api.id ? $style.active : null]" v-for="(api, index) in apiList" :key="api.id")
|
||||
div(:class="$style.listLeft")
|
||||
h3 {{api.name}}
|
||||
p {{api.description}}
|
||||
material-btn(:class="$style.listBtn" outline :tips="$t('material.user_api_modal.btn_remove')" @click.stop="handleRemove(index)")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once)
|
||||
use(xlink:href='#icon-delete')
|
||||
div(v-else :class="$style.content")
|
||||
div(:class="$style.noitem") {{$t('material.user_api_modal.noitem')}}
|
||||
div(:class="$style.note")
|
||||
p(:class="[$style.ruleLink]")
|
||||
| {{$t('material.user_api_modal.readme')}}
|
||||
span.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%BA%90%E8%84%9A%E6%9C%AC%E7%BC%96%E5%86%99%E8%AF%B4%E6%98%8E')" tips="https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md") FAQ.md
|
||||
p {{$t('material.user_api_modal.note')}}
|
||||
div(:class="$style.footer")
|
||||
material-btn(:class="$style.footerBtn" @click="handleImport") {{$t('material.user_api_modal.btn_import')}}
|
||||
//- material-btn(:class="$style.footerBtn" @click="handleExport") {{$t('material.user_api_modal.btn_export')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { rendererInvoke, NAMES } from '@common/ipc'
|
||||
import { promises as fsPromises } from 'fs'
|
||||
import {
|
||||
selectDir,
|
||||
openUrl,
|
||||
} from '../../utils'
|
||||
import apiSourceInfo from '../../utils/music/api-source-info'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'toggle',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
globalObj: window.globalObj,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
apiList() {
|
||||
return this.globalObj.userApi.list
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleImport() {
|
||||
selectDir({
|
||||
title: this.$t('material.user_api_modal.import_file'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'LX API File', extensions: ['js'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
return fsPromises.readFile(result.filePaths[0]).then(data => {
|
||||
return rendererInvoke(NAMES.mainWindow.import_user_api, data.toString()).then(({ apiList }) => {
|
||||
window.globalObj.userApi.list = apiList
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
handleExport() {
|
||||
|
||||
},
|
||||
async handleRemove(index) {
|
||||
const api = this.apiList[index]
|
||||
if (!api) return
|
||||
if (this.setting.apiSource == api.id) {
|
||||
let backApi = apiSourceInfo.find(api => !api.disabled)
|
||||
if (backApi) window.globalObj.apiSource = backApi.id
|
||||
}
|
||||
window.globalObj.userApi.list = await rendererInvoke(NAMES.mainWindow.remove_user_api, [api.id])
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit('toggle', false)
|
||||
},
|
||||
handleOpenUrl(url) {
|
||||
openUrl(url)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
color: @color-theme;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
min-height: 100px;
|
||||
max-height: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.listItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease;
|
||||
padding: 10px;
|
||||
border-radius: @radius-border;
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
color: @color-theme_2-font;
|
||||
word-break: break-all;
|
||||
}
|
||||
p {
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
color: @color-theme_2-font-label;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
height: 100px;
|
||||
font-size: 18px;
|
||||
color: @color-theme_2-font-label;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.listLeft {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.listBtn {
|
||||
flex: none;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
svg {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
.note {
|
||||
margin-top: 15px;
|
||||
font-size: 12px;
|
||||
line-height: 1.25;
|
||||
color: @color-theme_2-font;
|
||||
p {
|
||||
+ p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.footerBtn {
|
||||
flex: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding: 0 10px !important;
|
||||
width: 150px;
|
||||
.mixin-ellipsis-1;
|
||||
+ .footerBtn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
.ruleLink {
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.listItem {
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
h3 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
|
@ -3,17 +3,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
main(:class="$style.main" v-if="version.isDownloaded")
|
||||
h2 🚀程序更新🚀
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 最新版本:{{version.newVersion.version}}
|
||||
h3 当前版本:{{version.version}}
|
||||
h3 版本变化:
|
||||
p(:class="$style.desc" v-html="version.newVersion.desc")
|
||||
pre(:class="$style.desc" v-text="version.newVersion.desc")
|
||||
div(:class="[$style.history, $style.desc]" v-if="history.length")
|
||||
h3 历史版本:
|
||||
div(:class="$style.item" v-for="ver in history")
|
||||
h4 v{{ver.version}}
|
||||
p(v-html="ver.desc")
|
||||
pre(v-text="ver.desc")
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.desc")
|
||||
p 新版本已下载完毕,
|
||||
|
@ -27,17 +27,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
main(:class="$style.main" v-else-if="version.isError && !version.isUnknow && version.newVersion.version != version.version")
|
||||
h2 ❌ 版本更新出错 ❌
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 最新版本:{{version.newVersion.version}}
|
||||
h3 当前版本:{{version.version}}
|
||||
h3 版本变化:
|
||||
p(:class="$style.desc" v-html="version.newVersion.desc")
|
||||
pre(:class="$style.desc" v-text="version.newVersion.desc")
|
||||
div(:class="[$style.history, $style.desc]" v-if="history.length")
|
||||
h3 历史版本:
|
||||
div(:class="$style.item" v-for="ver in history")
|
||||
h4 v{{ver.version}}
|
||||
p(v-html="ver.desc")
|
||||
pre(v-text="ver.desc")
|
||||
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.desc")
|
||||
|
@ -46,7 +46,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
| 你可以去
|
||||
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
|
||||
| 或
|
||||
strong.hover.underline(@click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
| (密码:
|
||||
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
|
||||
| ) 下载新版本,
|
||||
|
@ -58,12 +58,12 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
main(:class="$style.main" v-else-if="version.isDownloading && version.isTimeOut && !version.isUnknow")
|
||||
h2 ❗️ 新版本下载超时 ❗️
|
||||
div(:class="$style.desc")
|
||||
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),建议手动更新版本!
|
||||
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),你仍可选择继续等,但墙裂建议手动更新版本!
|
||||
p
|
||||
| 你可以去
|
||||
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
|
||||
| 或
|
||||
material-btn(min @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
| (密码:
|
||||
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
|
||||
| )下载新版本,
|
||||
|
@ -75,7 +75,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
main(:class="$style.main" v-else-if="version.isUnknow")
|
||||
h2 ❓ 获取最新版本信息失败 ❓
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 当前版本:{{version.version}}
|
||||
div(:class="$style.desc")
|
||||
|
@ -84,7 +84,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
| 检查方法:打开
|
||||
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
|
||||
| 或
|
||||
material-btn(min @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
| (密码:
|
||||
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
|
||||
| )查看它们的
|
||||
|
@ -94,17 +94,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
main(:class="$style.main" v-else)
|
||||
h2 🌟发现新版本🌟
|
||||
|
||||
div.scroll(:class="$style.info")
|
||||
div.scroll.select(:class="$style.info")
|
||||
div(:class="$style.current")
|
||||
h3 最新版本:{{version.newVersion.version}}
|
||||
h3 当前版本:{{version.version}}
|
||||
h3 版本变化:
|
||||
p(:class="$style.desc" v-html="version.newVersion.desc")
|
||||
pre(:class="$style.desc" v-text="version.newVersion.desc")
|
||||
div(:class="[$style.history, $style.desc]" v-if="history.length")
|
||||
h3 历史版本:
|
||||
div(:class="$style.item" v-for="ver in history")
|
||||
h4 v{{ver.version}}
|
||||
p(v-html="ver.desc")
|
||||
pre(v-text="ver.desc")
|
||||
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.desc")
|
||||
|
@ -117,7 +117,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
|
|||
| 手动更新可以去
|
||||
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
|
||||
| 或
|
||||
strong.hover.underline(@click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
|
||||
| (密码:
|
||||
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
|
||||
| ) 下载,
|
||||
|
@ -146,7 +146,7 @@ export default {
|
|||
progress() {
|
||||
return this.version.downloadProgress
|
||||
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
|
||||
: '初始化中...'
|
||||
: '处理更新中...'
|
||||
},
|
||||
isIgnored() {
|
||||
return this.setting.ignoreVersion == this.version.newVersion.version
|
||||
|
@ -207,6 +207,11 @@ export default {
|
|||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
text-align: justify;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
<template lang="pug">
|
||||
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
||||
main.ignore-to-rem(:class="$style.main")
|
||||
h2 {{$t('material.xm_verify_modal.title')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
bgClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
background: #fff !important;
|
||||
&:global(.ignore-to-rem) {
|
||||
padding: 15px;
|
||||
width: 360px;
|
||||
height: 330px;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_download": "Download",
|
||||
"list_search": "Search",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"title": "Custom Source Management",
|
||||
"readme": "Source writing instructions: ",
|
||||
"note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.",
|
||||
"btn_remove": "Remove",
|
||||
"btn_import": "Import",
|
||||
"btn_export": "Export",
|
||||
"import_file": "Select music API script file",
|
||||
"noitem": "There is nothing here...😲"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"menu_play": "Play",
|
||||
"menu_play_later": "Play later",
|
||||
"menu_start": "Start task",
|
||||
"menu_pause": "Pause Task",
|
||||
"menu_file": "Locate File",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"lists_sync": "Sync",
|
||||
"lists_remove": "Remove",
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_copy_name": "Copy name",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_move_to": "Move to ...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_download": "Download",
|
||||
"list_source_detail": "Song Page",
|
||||
|
@ -10,6 +11,7 @@
|
|||
"time": "Length",
|
||||
"lossless": "SQ",
|
||||
"high_quality": "HQ",
|
||||
"loding_list": "Loading...",
|
||||
"no_item": "Search what I want to 😉",
|
||||
"hot_search": "Top Searches",
|
||||
"history_search": "History Searches",
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"basic": "General",
|
||||
"basic_theme": "Theme",
|
||||
"basic_show_animation": "Show switching animation",
|
||||
"basic_animation_title": "Animation effect of the pop-up layer",
|
||||
"basic_animation": "Random pop-up animation",
|
||||
"basic_source_title": "Choose a music source",
|
||||
"basic_source_test": "Test API (Available for most software features)",
|
||||
|
@ -12,6 +11,10 @@
|
|||
"basic_sourcename_real": "Original",
|
||||
"basic_sourcename_alias": "Aliases",
|
||||
"basic_sourcename": "Source name",
|
||||
"basic_source_status_success": "Initialization successful",
|
||||
"basic_source_status_initing": "Initializing",
|
||||
"basic_source_status_failed": "Initialization failed",
|
||||
"basic_source_user_api_btn": "Custom Source Management",
|
||||
"basic_window_size_title": "Set the window size",
|
||||
"basic_window_size": "Window size",
|
||||
"basic_window_size_smaller": "Smaller",
|
||||
|
@ -21,8 +24,7 @@
|
|||
"basic_window_size_larger": "Larger",
|
||||
"basic_window_size_oversized": "Oversized",
|
||||
"basic_window_size_huge": "Huge",
|
||||
"basic_to_tray_title": "Minimize it to the system tray without closing the software when closing",
|
||||
"basic_to_tray": "Minimize to system tray when closing",
|
||||
"basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
|
||||
"basic_lang_title": "The language displayed in the software",
|
||||
"basic_lang": "Language",
|
||||
"basic_control_btn_position": "Control Button Position",
|
||||
|
@ -30,21 +32,14 @@
|
|||
"basic_control_btn_position_right": "Right",
|
||||
|
||||
"play": "Play",
|
||||
"play_toggle_title": "If none selected, it stopped when the music playing is done.",
|
||||
"play_toggle": "Playback mode",
|
||||
"play_toggle_list_loop": "Playlist repeat",
|
||||
"play_toggle_random": "Playlist shuffle",
|
||||
"play_toggle_list": "Play in order",
|
||||
"play_toggle_single_loop": "Single repeat",
|
||||
"play_save_play_time": "Remember playback progress",
|
||||
"play_lyric_transition": "Show lyrics translation",
|
||||
"play_quality_title": "The 320k quality is preferred for playing",
|
||||
"play_quality": "Prefer High Quality 320k",
|
||||
"play_task_bar_title": "Show playing progress on the taskbar",
|
||||
"play_task_bar": "Taskbar play progress bar",
|
||||
"play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
|
||||
"play_quality": "Play 320K quality songs first (if supported)",
|
||||
"play_task_bar": "Show playing progress on the taskbar",
|
||||
"play_mediaDevice_title": "Select a media device for audio output",
|
||||
"play_mediaDevice": "Audio output",
|
||||
"play_mediaDevice_remove_stop_play": "Whether to pause playback when the audio output device is changed",
|
||||
"play_mediaDevice_remove_stop_play_title": "Whether to pause the song when the current sound output device is changed",
|
||||
"play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
|
||||
|
||||
"desktop_lyric": "Desktop Lyric Settings",
|
||||
"desktop_lyric_enable": "Display lyrics",
|
||||
|
@ -53,18 +48,13 @@
|
|||
"desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen",
|
||||
|
||||
"search": "Search",
|
||||
"search_hot_title": "Select whether to show popular searches",
|
||||
"search_hot": "Top Searches",
|
||||
"search_history_title": "Select whether to show search history",
|
||||
"search_history": "Search history",
|
||||
"search_focus_search_box_title": "Whether the search box is automatically focused on startup",
|
||||
"search_focus_search_box": "Whether the search box is focused on startup",
|
||||
"search_focus_search_box": "Automatically focus the search box on startup",
|
||||
|
||||
"list": "List",
|
||||
"list_source_title": "Select whether to show music source",
|
||||
"list_source": "Select whether to show music source (for Your Library only)",
|
||||
"list_scroll_title": "Select whether to remember the playlist scrollbar position",
|
||||
"list_scroll": "Remember playlist scrolling position (for Your library only)",
|
||||
"list_source": "Show song source (only valid for my music category)",
|
||||
"list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
|
||||
|
||||
"download": "Download",
|
||||
"download_enable": "Whether to enable download function",
|
||||
|
@ -136,10 +126,11 @@
|
|||
"other_tray_theme": "Tray Icon Style",
|
||||
"other_tray_theme_native": "Solid Color",
|
||||
"other_tray_theme_origin": "Primary Color",
|
||||
"other_cache": "Cache size (Not recommended since resources such as pictures after the cache is cleaned need re-downloading. The software will dynamically manage the cache size based on disk space)",
|
||||
"other_cache_label": "Cache size used: ",
|
||||
"other_cache_label_title": "Currently used cache size",
|
||||
"other_cache_clear_btn": "Clear cache",
|
||||
"other_resource_cache": "Resource cache management (pictures, audios and other caches, pictures and other resources will need to be downloaded again after cleaning up, it is not recommended to clean up, the software will dynamically manage the cache size according to the disk space)",
|
||||
"other_resource_cache_label": "The software has used cache size: ",
|
||||
"other_resource_cache_clear_btn": "Clear resource cache",
|
||||
"other_play_list_cache": "List cache management (links to songs that have been cached in my list, alternative sources for playback, after cleaning up, you need to re-acquire them when you play and download songs, and do not clean up if there are no issues related to song playback)",
|
||||
"other_play_list_cache_clear_btn": "Clear list cache information",
|
||||
|
||||
"update": "Update",
|
||||
"update_latest_label": "Latest version: ",
|
||||
|
@ -150,7 +141,7 @@
|
|||
"update_latest": "The software is up-to-date, enjoy yourself!🥂",
|
||||
"update_open_version_modal_btn": "Open the update window🚀",
|
||||
"update_checking": "Checking for updates...",
|
||||
"update_init": "Initializing update...",
|
||||
"update_init": "Processing update...",
|
||||
|
||||
"about": "About lx-music-desktop",
|
||||
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
"tip_2": "If you encounter a link to a playlist that cannot be opened, welcome feedback",
|
||||
"tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening",
|
||||
"play_all": "Play",
|
||||
"play_later": "Play later",
|
||||
"add_all": "Collect"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下载",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"title": "自定义源管理",
|
||||
"readme": "源编写说明:",
|
||||
"note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
|
||||
"btn_remove": "移除",
|
||||
"btn_import": "导入",
|
||||
"btn_export": "导出",
|
||||
"import_file": "选择音乐API脚本文件",
|
||||
"noitem": "这里竟然是空的 😲"
|
||||
}
|
|
@ -33,6 +33,6 @@
|
|||
|
||||
"source_alias_all": "聚合大会",
|
||||
|
||||
"load_list_file_error_title": "播放列表数据加载错误",
|
||||
"load_list_file_error_title": "播放列表数据加载错误(建议到GitHub或加群反馈)",
|
||||
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情:{detail}"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"menu_play": "播放",
|
||||
"menu_play_later": "稍后播放",
|
||||
"menu_start": "开始任务",
|
||||
"menu_pause": "暂停任务",
|
||||
"menu_file": "定位文件",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"lists_sync": "同步",
|
||||
"lists_remove": "删除",
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_copy_name": "复制歌曲名",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
"list_add_to": "添加到...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下载",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
|
@ -10,6 +11,7 @@
|
|||
"time": "时长",
|
||||
"lossless": "无损",
|
||||
"high_quality": "高品质",
|
||||
"loding_list": "加载中...",
|
||||
"no_item": "搜我所想~~😉",
|
||||
"hot_search": "热门搜索",
|
||||
"history_search": "历史搜索",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"basic": "基本设置",
|
||||
"basic_theme": "主题颜色",
|
||||
"basic_animation_title": "弹出层的动画效果",
|
||||
"basic_animation": "弹出层随机动画",
|
||||
"basic_show_animation": "显示切换动画",
|
||||
"basic_source_title": "选择音乐来源",
|
||||
|
@ -12,6 +11,10 @@
|
|||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_alias": "别名",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_failed": "初始化失败",
|
||||
"basic_source_user_api_btn": "自定义源管理",
|
||||
"basic_window_size_title": "设置软件窗口尺寸",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_smaller": "较小",
|
||||
|
@ -21,8 +24,7 @@
|
|||
"basic_window_size_larger": "较大",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_to_tray_title": "关闭时不退出软件将其最小化到系统托盘",
|
||||
"basic_to_tray": "关闭时最小化到系统托盘",
|
||||
"basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
|
||||
"basic_lang_title": "软件显示的语言",
|
||||
"basic_lang": "语言",
|
||||
"basic_control_btn_position": "控制按钮位置",
|
||||
|
@ -30,21 +32,14 @@
|
|||
"basic_control_btn_position_right": "右边",
|
||||
|
||||
"play": "播放设置",
|
||||
"play_toggle_title": "都不选时播放完当前歌曲就停止播放",
|
||||
"play_toggle": "歌曲切换方式",
|
||||
"play_toggle_list_loop": "列表循环",
|
||||
"play_toggle_random": "列表随机",
|
||||
"play_toggle_list": "顺序播放",
|
||||
"play_toggle_single_loop": "单曲循环",
|
||||
"play_save_play_time": "记住播放进度",
|
||||
"play_lyric_transition": "显示歌词翻译",
|
||||
"play_quality_title": "启用时将优先播放320K品质的歌曲",
|
||||
"play_quality": "优先播放高品质音乐",
|
||||
"play_task_bar_title": "在任务栏上显示当前歌曲播放进度",
|
||||
"play_task_bar": "任务栏播放进度条",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
|
||||
"play_quality": "优先播放320K品质的歌曲(如果支持)",
|
||||
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
||||
"play_mediaDevice_title": "选择声音输出的媒体设备",
|
||||
"play_mediaDevice": "音频输出",
|
||||
"play_mediaDevice_remove_stop_play": "音频输出设备被改变时是否暂停播放",
|
||||
"play_mediaDevice_remove_stop_play_title": "当前的声音输出设备被改变时是否暂停播放歌曲",
|
||||
"play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
|
||||
|
||||
"desktop_lyric": "桌面歌词设置",
|
||||
"desktop_lyric_enable": "显示歌词",
|
||||
|
@ -53,18 +48,13 @@
|
|||
"desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外",
|
||||
|
||||
"search": "搜索设置",
|
||||
"search_hot_title": "是否显示热门搜索",
|
||||
"search_hot": "热门搜索",
|
||||
"search_history_title": "是否显示历史搜索记录",
|
||||
"search_history": "搜索历史",
|
||||
"search_focus_search_box_title": "启动时是否自动聚焦搜索框",
|
||||
"search_focus_search_box": "启动时是否聚焦搜索框",
|
||||
"search_hot": "显示热门搜索",
|
||||
"search_history": "显示历史搜索记录",
|
||||
"search_focus_search_box": "启动时自动聚焦搜索框",
|
||||
|
||||
"list": "列表设置",
|
||||
"list_source_title": "是否显示歌曲源",
|
||||
"list_source": "是否显示歌曲源(仅对我的音乐分类有效)",
|
||||
"list_scroll_title": "是否记住播放列表滚动条位置",
|
||||
"list_scroll": "记住列表滚动位置(仅对我的音乐分类有效)",
|
||||
"list_source": "显示歌曲源(仅对我的音乐分类有效)",
|
||||
"list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
|
||||
|
||||
"download": "下载设置",
|
||||
"download_enable": "是否启用下载功能",
|
||||
|
@ -136,10 +126,11 @@
|
|||
"other_tray_theme": "托盘图标样式",
|
||||
"other_tray_theme_native": "纯色",
|
||||
"other_tray_theme_origin": "原色",
|
||||
"other_cache": "缓存大小(清理缓存后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||
"other_cache_label": "软件已使用缓存大小:",
|
||||
"other_cache_label_title": "当前已用缓存",
|
||||
"other_cache_clear_btn": "清理缓存",
|
||||
"other_resource_cache": "资源缓存管理(图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||
"other_resource_cache_label": "软件已使用缓存大小:",
|
||||
"other_resource_cache_clear_btn": "清理资源缓存",
|
||||
"other_play_list_cache": "列表缓存管理(我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表缓存信息",
|
||||
|
||||
"update": "软件更新",
|
||||
"update_latest_label": "最新版本:",
|
||||
|
@ -150,7 +141,7 @@
|
|||
"update_latest": "软件已是最新,尽情地体验吧~🥂",
|
||||
"update_open_version_modal_btn": "打开更新窗口 🚀",
|
||||
"update_checking": "检查更新中...",
|
||||
"update_init": "更新初始化中...",
|
||||
"update_init": "处理更新中...",
|
||||
|
||||
"about": "关于洛雪音乐",
|
||||
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
"tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
|
||||
"tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
|
||||
"play_all": "播放",
|
||||
"play_later": "稍后播放",
|
||||
"add_all": "收藏"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍後播放",
|
||||
"list_add_to": "添加到...",
|
||||
"list_download": "下載",
|
||||
"list_search": "搜索",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"title": "自定義源管理",
|
||||
"readme": "源編寫說明:",
|
||||
"note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
|
||||
"btn_remove": "移除",
|
||||
"btn_import": "導入",
|
||||
"btn_export": "導出",
|
||||
"import_file": "選擇音樂API腳本文件",
|
||||
"noitem": "這裡竟然是空的 😲"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"menu_play": "播放",
|
||||
"menu_play_later": "稍後播放",
|
||||
"menu_start": "開始任務",
|
||||
"menu_pause": "暫停任務",
|
||||
"menu_file": "定位文件",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue