You've already forked lx-music-desktop
Compare commits
47 Commits
v2.2.0
...
api-source
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fe34545b9 | ||
|
|
da4be33a04 | ||
|
|
47e2a5a460 | ||
|
|
04337ef4b8 | ||
|
|
df58c8eb80 | ||
|
|
b0bf1bf4be | ||
|
|
1a3d84d4c4 | ||
|
|
735f629ca1 | ||
|
|
9dc927b50b | ||
|
|
47a336eb64 | ||
|
|
33b66aaea0 | ||
|
|
7b9eefdf2d | ||
|
|
ea5e8b56dd | ||
|
|
e52fff4d0e | ||
|
|
8d108509c9 | ||
|
|
d3cc630501 | ||
|
|
e74f297f9e | ||
|
|
8be9c75734 | ||
|
|
851851a7e6 | ||
|
|
1cbe46dc3b | ||
|
|
675a943d59 | ||
|
|
908815def3 | ||
|
|
200ca1eeda | ||
|
|
b81cc139b3 | ||
|
|
f8ae906326 | ||
|
|
e6f55804f4 | ||
|
|
1432a19d7d | ||
|
|
74fbfb130c | ||
|
|
3cf45f602f | ||
|
|
7a9dcc71fd | ||
|
|
fb5249a979 | ||
|
|
3f694b75f8 | ||
|
|
0e1ee66dd6 | ||
|
|
920e002247 | ||
|
|
ab0b352d36 | ||
|
|
aa4b08a156 | ||
|
|
66514908d2 | ||
|
|
60f0801a7c | ||
|
|
7149d0d43f | ||
|
|
d970687ebe | ||
|
|
d0425fc0d6 | ||
|
|
bdd5b46708 | ||
|
|
6d949d7e4d | ||
|
|
5d72d092fc | ||
|
|
667fc8c8a6 | ||
|
|
f7a2d9fd06 | ||
|
|
85946efd0b |
6
.github/workflows/beta-pack.yml
vendored
6
.github/workflows/beta-pack.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -12,5 +12,6 @@
|
||||
],
|
||||
"i18n-ally.sortKeys": true,
|
||||
"javascript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"vue.codeActions.enabled": false
|
||||
}
|
||||
|
||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -6,6 +6,38 @@ 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/).
|
||||
|
||||
## [2.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.1...v2.2.2) - 2023-05-01
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复在低版本Linux amd64系统上无法启动的问题(glibc版本要求过高导致的,采用内置预编译二进制文件的方式解决)
|
||||
- 修复添加歌曲弹窗默认列表名字显示问题
|
||||
|
||||
## [2.2.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.0...v2.2.1) - 2023-05-01
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化对系统Media Session的支持,现在切歌不会再会导致信息丢失的问题了
|
||||
- 启用桌面歌词时,取消对歌词窗口的聚焦
|
||||
- 增加kg歌单歌曲flac24bit显示(@helloplhm-qwq)
|
||||
- 增加tx源热门评论图片显示(@Folltoshe)
|
||||
- 优化更新弹窗弹出时机
|
||||
- 优化搜索框背景配色,使其适应高透明主题
|
||||
- 支持wy热门评论翻页
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复启用全局快捷键时与Media Session注册冲突的问题,启用全局快捷键时,不再注册媒体控制快捷键
|
||||
- 修复mg搜索不显示时长的问题(@Folltoshe)
|
||||
- 修复mg评论加载失败的问题(@Folltoshe)
|
||||
- 修复对存在错误时间标签的歌词的解析
|
||||
|
||||
### 其他
|
||||
|
||||
- 自定义源API utils对象新增`zlib.inflate`与`zlib.deflate`方法,API版本更新到 v1.3.0
|
||||
- 更新kg、tx、wy等平台排行榜列表
|
||||
- 更新 electron 到 v22.3.7
|
||||
|
||||
## [2.2.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.1.2...v2.2.0) - 2023-03-26
|
||||
|
||||
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
|
||||
|
||||
3
FAQ.md
3
FAQ.md
@@ -2,6 +2,7 @@
|
||||
|
||||
本文档已迁移到:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
|
||||
|
||||
<!--
|
||||
在阅读本常见问题后,仍然无法解决你的问题,请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
|
||||
|
||||
## ~~软件为什么没有桌面歌词与自定义列表功能~~
|
||||
@@ -562,4 +563,4 @@ const cancelHttp = window.lx.request(url, options, callback)
|
||||
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`
|
||||
- `window.lx.utils.crypto.rsaEncrypt`:RSA加密 `rsaEncrypt(buffer, key)`
|
||||
|
||||
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。
|
||||
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。 -->
|
||||
|
||||
@@ -4,6 +4,7 @@ const path = require('path')
|
||||
const { Arch } = require('electron-builder')
|
||||
|
||||
const better_sqlite3_fileNameMap = {
|
||||
[Arch.x64]: 'electron-v110-linux-x64',
|
||||
[Arch.arm64]: 'electron-v110-linux-arm64',
|
||||
[Arch.armv7l]: 'electron-v110-linux-arm',
|
||||
}
|
||||
@@ -51,10 +52,11 @@ const replaceQrcDecodeLib = async(platform, arch) => {
|
||||
module.exports = async(context) => {
|
||||
const { electronPlatformName, arch } = context
|
||||
await replaceQrcDecodeLib(electronPlatformName, arch)
|
||||
if (electronPlatformName !== 'linux') return
|
||||
if (electronPlatformName !== 'linux' || process.env.FORCE) return
|
||||
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
|
||||
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
|
||||
switch (arch) {
|
||||
case Arch.x64:
|
||||
case Arch.arm64:
|
||||
case Arch.armv7l:
|
||||
if (fs.existsSync(bindingFilePath)) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
build-config/lib/better_sqlite3_electron-v110-linux-x64.node
Normal file
BIN
build-config/lib/better_sqlite3_electron-v110-linux-x64.node
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -104,7 +104,7 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
|
||||
4300
package-lock.json
generated
4300
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
71
package.json
71
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0-beta.3",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
@@ -205,77 +205,77 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/eslint-parser": "^7.21.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.21.0",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.21.0",
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@types/better-sqlite3": "^7.6.4",
|
||||
"@types/needle": "^3.2.0",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"@volar/vue-language-plugin-pug": "^1.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"@volar/vue-language-plugin-pug": "^1.6.4",
|
||||
"babel-loader": "^9.1.2",
|
||||
"browserslist": "^4.21.5",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^3.0.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"core-js": "^3.29.1",
|
||||
"core-js": "^3.30.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^22.3.4",
|
||||
"electron-builder": "^24.1.1",
|
||||
"electron": "^22.3.8",
|
||||
"electron-builder": "^24.3.0",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.4.340",
|
||||
"electron-updater": "^6.0.0",
|
||||
"eslint": "^8.36.0",
|
||||
"electron-to-chromium": "^1.4.385",
|
||||
"electron-updater": "^6.1.0",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-config-standard-with-typescript": "^34.0.1",
|
||||
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-n": "^15.6.1",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.10.0",
|
||||
"eslint-webpack-plugin": "^4.0.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"eslint-plugin-vue": "^9.11.1",
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-loader": "^7.1.0",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-loader": "^7.3.0",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"rimraf": "^4.4.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svg-transform-loader": "^2.0.13",
|
||||
"svgo-loader": "^4.0.0",
|
||||
"terser": "^5.16.8",
|
||||
"terser-webpack-plugin": "^5.3.7",
|
||||
"terser": "^5.17.1",
|
||||
"terser-webpack-plugin": "^5.3.8",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.2",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-loader": "^17.0.1",
|
||||
"typescript": "^5.0.4",
|
||||
"vue-eslint-parser": "^9.2.1",
|
||||
"vue-loader": "^17.1.0",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"webpack": "^5.76.3",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.13.1",
|
||||
"webpack": "^5.82.0",
|
||||
"webpack-cli": "^5.1.0",
|
||||
"webpack-dev-server": "^4.15.0",
|
||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@simonwep/pickr": "^1.8.2",
|
||||
"better-sqlite3": "^8.2.0",
|
||||
"better-sqlite3": "^8.3.0",
|
||||
"bufferutil": "^4.0.7",
|
||||
"comlink": "~4.3.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
@@ -285,9 +285,8 @@
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.2",
|
||||
"jschardet": "^3.0.0",
|
||||
"koa": "^2.14.1",
|
||||
"long": "^5.2.1",
|
||||
"music-metadata": "^8.1.3",
|
||||
"long": "^5.2.3",
|
||||
"music-metadata": "^8.1.4",
|
||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||
"node-id3": "^0.2.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
|
||||
@@ -1,32 +1,7 @@
|
||||
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
|
||||
|
||||
### 不兼容性变更说明
|
||||
|
||||
- 同步功能,从这个版本起,数据同步功能至少需要移动端v1.0.0的版本才能连接,连接的地址格式也略有改变,详情看[文档说明](https://lyswhut.github.io/lx-music-doc/desktop/faq/sync)
|
||||
|
||||
### 新增
|
||||
|
||||
- 重构数据同步功能,新增客户端模式
|
||||
- 新增全屏时自动关闭歌词设置,默认开启,可以去设置-桌面歌词设置更改
|
||||
- 新增设置-桌面歌词设置-重置窗口设置功能,点击时会重置桌面歌词窗口大小及位置
|
||||
- 新增设置-其他-列表数据清理功能,点击时会清空已创建的所有列表及所有收藏的歌曲
|
||||
|
||||
### 优化
|
||||
|
||||
- 支持wy源flac hires歌曲类型的显示
|
||||
- 快捷键调整音量时每次加减2%音量改为4%(#1220)
|
||||
- 音量、播放模式等设置弹出式按钮在鼠标移到按钮上时将自动弹出设置内容,保留点击切换显示/隐藏
|
||||
- 支持kg源搜索列表、排行榜flac hires歌曲类型的显示(#1231, #1238 By @helloplhm-qwq, @Folltoshe)
|
||||
- 播放速率的粒度调整为0.01,范围0.6-2.0x
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复同步连接的处理问题
|
||||
- 修复记住播放进度的情况下,使用Scheme URL打开应用播放的歌曲进度没有被重置的问题
|
||||
- 修复使用酷狗码无法打开某些类型的歌单的问题
|
||||
- 修复tx源某些歌单因为歌曲信息缺失导致打开失败的问题
|
||||
- 修复连续选择时的初始选择歌曲位置被意外改变的问题
|
||||
- 新增音效设置(实验性功能),支持10段均衡器设置、内置的一些环境混响音效、3D立体环绕音效
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 Electron 到v22.3.4
|
||||
- 更新 electron 到 v22.3.8
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,6 +10,7 @@ export const STORE_NAMES = {
|
||||
LRC_RAW: 'lyrics',
|
||||
LRC_EDITED: 'lyrics_edited',
|
||||
THEME: 'theme',
|
||||
SOUND_EFFECT: 'sound_effect',
|
||||
} as const
|
||||
|
||||
export const APP_EVENT_NAMES = {
|
||||
|
||||
@@ -29,21 +29,21 @@ const local: LX.HotKeyConfig = {
|
||||
const global: LX.HotKeyConfig = {
|
||||
enable: false,
|
||||
keys: {
|
||||
MediaPlayPause: {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
MediaPreviousTrack: {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
MediaNextTrack: {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
// MediaPlayPause: {
|
||||
// type: HOTKEY_PLAYER.toggle_play.type,
|
||||
// name: '',
|
||||
// action: HOTKEY_PLAYER.toggle_play.action,
|
||||
// },
|
||||
// MediaPreviousTrack: {
|
||||
// type: HOTKEY_PLAYER.prev.type,
|
||||
// name: '',
|
||||
// action: HOTKEY_PLAYER.prev.action,
|
||||
// },
|
||||
// MediaNextTrack: {
|
||||
// type: HOTKEY_PLAYER.next.type,
|
||||
// name: '',
|
||||
// action: HOTKEY_PLAYER.next.action,
|
||||
// },
|
||||
'mod+alt+f5': {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: HOTKEY_PLAYER.toggle_play.name,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
const isMac = process.platform == 'darwin'
|
||||
const isWin = process.platform == 'win32'
|
||||
|
||||
const defaultSetting: LX.AppSetting = {
|
||||
version: '2.1.0',
|
||||
@@ -33,12 +34,28 @@ const defaultSetting: LX.AppSetting = {
|
||||
'player.isShowLyricTranslation': false,
|
||||
'player.isShowLyricRoma': false,
|
||||
'player.isS2t': false,
|
||||
'player.isPlayLxlrc': !isMac,
|
||||
'player.isPlayLxlrc': isWin,
|
||||
'player.isSavePlayTime': false,
|
||||
'player.audioVisualization': false,
|
||||
'player.waitPlayEndStop': true,
|
||||
'player.waitPlayEndStopTime': '',
|
||||
'player.autoSkipOnError': true,
|
||||
'player.soundEffect.convolution.fileName': '',
|
||||
'player.soundEffect.convolution.mainGain': 10,
|
||||
'player.soundEffect.convolution.sendGain': 0,
|
||||
'player.soundEffect.biquadFilter.hz31': 0,
|
||||
'player.soundEffect.biquadFilter.hz62': 0,
|
||||
'player.soundEffect.biquadFilter.hz125': 0,
|
||||
'player.soundEffect.biquadFilter.hz250': 0,
|
||||
'player.soundEffect.biquadFilter.hz500': 0,
|
||||
'player.soundEffect.biquadFilter.hz1000': 0,
|
||||
'player.soundEffect.biquadFilter.hz2000': 0,
|
||||
'player.soundEffect.biquadFilter.hz4000': 0,
|
||||
'player.soundEffect.biquadFilter.hz8000': 0,
|
||||
'player.soundEffect.biquadFilter.hz16000': 0,
|
||||
'player.soundEffect.panner.enable': false,
|
||||
'player.soundEffect.panner.soundR': 5,
|
||||
'player.soundEffect.panner.speed': 25,
|
||||
|
||||
'playDetail.isZoomActiveLrc': false,
|
||||
'playDetail.isShowLyricProgressSetting': false,
|
||||
@@ -71,10 +88,10 @@ const defaultSetting: LX.AppSetting = {
|
||||
// 'desktopLyric.style.fontWeight': false,
|
||||
'desktopLyric.style.opacity': 95,
|
||||
'desktopLyric.style.ellipsis': false,
|
||||
'desktopLyric.style.isZoomActiveLrc': true,
|
||||
'desktopLyric.style.isZoomActiveLrc': false,
|
||||
'desktopLyric.style.isFontWeightFont': true,
|
||||
'desktopLyric.style.isFontWeightLine': false,
|
||||
'desktopLyric.style.isFontWeightExtended': false,
|
||||
'desktopLyric.style.isFontWeightLine': true,
|
||||
'desktopLyric.style.isFontWeightExtended': true,
|
||||
|
||||
'list.isClickPlayList': false,
|
||||
'list.isShowSource': true,
|
||||
|
||||
@@ -90,6 +90,10 @@ const modules = {
|
||||
get_other_source_count: 'get_other_source_count',
|
||||
get_data: 'get_data',
|
||||
save_data: 'save_data',
|
||||
get_sound_effect_eq_preset: 'get_sound_effect_eq_preset',
|
||||
save_sound_effect_eq_preset: 'save_sound_effect_eq_preset',
|
||||
get_sound_effect_convolution_preset: 'get_sound_effect_convolution_preset',
|
||||
save_sound_effect_convolution_preset: 'save_sound_effect_convolution_preset',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
|
||||
80
src/common/types/app_setting.d.ts
vendored
80
src/common/types/app_setting.d.ts
vendored
@@ -163,6 +163,86 @@ declare global {
|
||||
*/
|
||||
'player.waitPlayEndStopTime': string
|
||||
|
||||
/**
|
||||
* 环境音效文件名
|
||||
*/
|
||||
'player.soundEffect.convolution.fileName': string | null
|
||||
|
||||
/**
|
||||
* 环境音效原始输出增益
|
||||
*/
|
||||
'player.soundEffect.convolution.mainGain': number
|
||||
|
||||
/**
|
||||
* 环境音效输出增益
|
||||
*/
|
||||
'player.soundEffect.convolution.sendGain': number
|
||||
|
||||
/**
|
||||
* 均衡器 31hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz31': number
|
||||
|
||||
/**
|
||||
* 均衡器 62hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz62': number
|
||||
|
||||
/**
|
||||
* 均衡器 125hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz125': number
|
||||
|
||||
/**
|
||||
* 均衡器 250hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz250': number
|
||||
|
||||
/**
|
||||
* 均衡器 500hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz500': number
|
||||
|
||||
/**
|
||||
* 均衡器 1000hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz1000': number
|
||||
|
||||
/**
|
||||
* 均衡器 2000hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz2000': number
|
||||
|
||||
/**
|
||||
* 均衡器 4000hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz4000': number
|
||||
|
||||
/**
|
||||
* 均衡器 8000hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz8000': number
|
||||
|
||||
/**
|
||||
* 均衡器 16000hz 值
|
||||
*/
|
||||
'player.soundEffect.biquadFilter.hz16000': number
|
||||
|
||||
/**
|
||||
* 3D立体环绕是否启用
|
||||
*/
|
||||
'player.soundEffect.panner.enable': boolean
|
||||
|
||||
/**
|
||||
* 3D立体环绕声音距离
|
||||
*/
|
||||
'player.soundEffect.panner.soundR': number
|
||||
|
||||
/**
|
||||
* 3D立体环绕速度
|
||||
*/
|
||||
'player.soundEffect.panner.speed': number
|
||||
|
||||
/**
|
||||
* 是否启用音频加载失败时自动切歌
|
||||
*/
|
||||
|
||||
6
src/common/types/list.d.ts
vendored
6
src/common/types/list.d.ts
vendored
@@ -12,13 +12,15 @@ declare namespace LX {
|
||||
|
||||
interface MyDefaultListInfo {
|
||||
id: 'default'
|
||||
name: '试听列表'
|
||||
name: 'list__name_default'
|
||||
// name: '试听列表'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface MyLoveListInfo {
|
||||
id: 'love'
|
||||
name: '我的收藏'
|
||||
name: 'list__name_love'
|
||||
// name: '我的收藏'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
|
||||
25
src/common/types/sound_effect.d.ts
vendored
Normal file
25
src/common/types/sound_effect.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
declare namespace LX {
|
||||
namespace SoundEffect {
|
||||
interface EQPreset {
|
||||
id: string
|
||||
name: string
|
||||
hz31: number
|
||||
hz62: number
|
||||
hz125: number
|
||||
hz250: number
|
||||
hz500: number
|
||||
hz1000: number
|
||||
hz2000: number
|
||||
hz4000: number
|
||||
hz8000: number
|
||||
hz16000: number
|
||||
}
|
||||
interface ConvolutionPreset {
|
||||
id: string
|
||||
name: string
|
||||
source: string
|
||||
mainGain: number
|
||||
sendGain: number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
const timeFieldExp = /^(?:\[[\d:.]+\])+/g
|
||||
const timeExp = /[\d:.]+/g
|
||||
const timeLabelRxp = /^(\[[\d:]+\.)0+(\d+\])/
|
||||
const timeLabelFixRxp = /(?:\.0+|0+)$/
|
||||
const timeExp = /\d{1,3}(:\d{1,3}){0,2}(?:\.\d{1,3})/g
|
||||
const tagRegMap = {
|
||||
title: 'ti',
|
||||
artist: 'ar',
|
||||
@@ -14,6 +12,15 @@ const tagRegMap = {
|
||||
|
||||
const timeoutTools = new TimeoutTools()
|
||||
|
||||
const t_rxp_1 = /^0+(\d+)/
|
||||
const t_rxp_2 = /:0+(\d+)/g
|
||||
const t_rxp_3 = /\.0+(\d+)/
|
||||
const formatTimeLabel = (label) => {
|
||||
return label.replace(t_rxp_1, '$1')
|
||||
.replace(t_rxp_2, ':$1')
|
||||
.replace(t_rxp_3, '.$1')
|
||||
}
|
||||
|
||||
const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
|
||||
const extendedLines = extendedLyric.split(/\r\n|\n|\r/)
|
||||
for (let i = 0; i < extendedLines.length; i++) {
|
||||
@@ -26,9 +33,7 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
|
||||
const times = timeField.match(timeExp)
|
||||
if (times == null) continue
|
||||
for (let time of times) {
|
||||
if (time.includes('.')) time = time.replace(timeLabelRxp, '$1$2')
|
||||
else time += '.0'
|
||||
const timeStr = time.replace(timeLabelFixRxp, '')
|
||||
const timeStr = formatTimeLabel(time)
|
||||
const targetLine = lrcLinesMap[timeStr]
|
||||
if (targetLine) targetLine.extendedLyrics.push(text)
|
||||
}
|
||||
@@ -88,19 +93,16 @@ module.exports = class LinePlayer {
|
||||
const times = timeField.match(timeExp)
|
||||
if (times == null) continue
|
||||
for (let time of times) {
|
||||
if (time.includes('.')) time = time.replace(timeLabelRxp, '$1$2')
|
||||
else time += '.0'
|
||||
const timeStr = time.replace(timeLabelFixRxp, '')
|
||||
const timeStr = formatTimeLabel(time)
|
||||
if (linesMap[timeStr]) {
|
||||
linesMap[timeStr].extendedLyrics.push(text)
|
||||
continue
|
||||
}
|
||||
const timeArr = timeStr.split(':')
|
||||
if (timeArr.length < 3) timeArr.unshift(0)
|
||||
if (timeArr[2].indexOf('.') > -1) {
|
||||
timeArr.push(...timeArr[2].split('.'))
|
||||
timeArr.splice(2, 1)
|
||||
} else if (!timeArr[2]) timeArr[2] = '0'
|
||||
if (timeArr.length > 3) continue
|
||||
else if (timeArr.length < 3) for (let i = 3 - timeArr.length; i--;) timeArr.unshift('0')
|
||||
if (timeArr[2].indexOf('.') > -1) timeArr.splice(2, 1, ...timeArr[2].split('.'))
|
||||
|
||||
linesMap[timeStr] = {
|
||||
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
|
||||
text,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
|
||||
const meta: Record<string, any> = {
|
||||
songId: oldMusicInfo.songmid, // 歌曲ID,mg源为copyrightId,local为文件路径
|
||||
songId: oldMusicInfo.songmid, // 歌曲ID,local为文件路径
|
||||
albumName: oldMusicInfo.albumName, // 歌曲专辑名称
|
||||
picUrl: oldMusicInfo.img, // 歌曲图片链接
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"comment__hot_load_error": "Hot comments failed to load, click to try to reload",
|
||||
"comment__hot_loading": "Hot comments are loading",
|
||||
"comment__hot_title": "Hot Comment",
|
||||
"comment__location": "From{location}",
|
||||
"comment__new_load_error": "The latest comment failed to load, click to try to reload",
|
||||
"comment__new_loading": "Latest comments are loading",
|
||||
"comment__new_title": "Latest comment",
|
||||
@@ -92,6 +93,8 @@
|
||||
"list__move_to": "Move to ...",
|
||||
"list__movedown": "Movedown",
|
||||
"list__moveup": "Move up",
|
||||
"list__name_default": "Default",
|
||||
"list__name_love": "Love",
|
||||
"list__new_list_btn": "New List",
|
||||
"list__new_list_input": "New list...",
|
||||
"list__pause": "Pause Task",
|
||||
@@ -205,6 +208,7 @@
|
||||
"player__end": "Stopped",
|
||||
"player__error": "Error loading music. Switch to next song after 5 seconds",
|
||||
"player__geting_url": "Getting music link...",
|
||||
"player__geting_url_delay_retry": "The service is busy, try again in {time} seconds...",
|
||||
"player__hide_detail_tip": "Hide detail page (Right-click in the view to quickly hide the details page)",
|
||||
"player__loading": "Music loading...",
|
||||
"player__music_album": "Album: ",
|
||||
@@ -224,6 +228,41 @@
|
||||
"player__playing": "Now playing...",
|
||||
"player__prev": "Prev",
|
||||
"player__refresh_url": "Music URL expired, refreshing...",
|
||||
"player__sound_effect": "Sound settings (experimental)",
|
||||
"player__sound_effect_biquad_filter": "Equalizer",
|
||||
"player__sound_effect_biquad_filter_preset_classical": "Classical",
|
||||
"player__sound_effect_biquad_filter_preset_dance": "Dance",
|
||||
"player__sound_effect_biquad_filter_preset_electronic": "Electronic",
|
||||
"player__sound_effect_biquad_filter_preset_pop": "Pop",
|
||||
"player__sound_effect_biquad_filter_preset_rock": "Rock",
|
||||
"player__sound_effect_biquad_filter_preset_slow": "Slow",
|
||||
"player__sound_effect_biquad_filter_preset_soft": "Soft",
|
||||
"player__sound_effect_biquad_filter_preset_subwoofer": "Subwoofer",
|
||||
"player__sound_effect_biquad_filter_preset_vocal": "Vocal",
|
||||
"player__sound_effect_biquad_filter_reset_btn": "Reset",
|
||||
"player__sound_effect_biquad_filter_save_btn": "Save preset as",
|
||||
"player__sound_effect_biquad_filter_save_input": "New presets...",
|
||||
"player__sound_effect_convolution": "Ambient reverb sound effect",
|
||||
"player__sound_effect_convolution_file_bright_hall": "Hall",
|
||||
"player__sound_effect_convolution_file_cardiod_35_10_spread": "Rock",
|
||||
"player__sound_effect_convolution_file_cinema_diningroom": "Cinema",
|
||||
"player__sound_effect_convolution_file_dining_living_true_stereo": "Dining Room",
|
||||
"player__sound_effect_convolution_file_feedback_spring": "Feedback Spring",
|
||||
"player__sound_effect_convolution_file_living_bedroom_leveled": "Bathroom",
|
||||
"player__sound_effect_convolution_file_matrix_1": "Matrix",
|
||||
"player__sound_effect_convolution_file_matrix_2": "Matrix 2",
|
||||
"player__sound_effect_convolution_file_s2_r4_bd": "Church",
|
||||
"player__sound_effect_convolution_file_s3_r1_bd": "Stereo",
|
||||
"player__sound_effect_convolution_file_spreader25_125ms": "Indoor 2",
|
||||
"player__sound_effect_convolution_file_spreader50_65ms": "Indoor",
|
||||
"player__sound_effect_convolution_file_telephone": "Telephone",
|
||||
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "Rock 2",
|
||||
"player__sound_effect_convolution_main_gain": "Original Audio Gain",
|
||||
"player__sound_effect_convolution_send_gain": "Ambient Sound Effect Gain",
|
||||
"player__sound_effect_panner": "3D stereo surround (need to use headphones)",
|
||||
"player__sound_effect_panner_enabled": "enable",
|
||||
"player__sound_effect_panner_sound_r": "Sound distance",
|
||||
"player__sound_effect_panner_sound_speed": "Surround speed",
|
||||
"player__stop": "Paused",
|
||||
"player__volume": "Volume: ",
|
||||
"player__volume_mute_label": "Mute",
|
||||
@@ -310,10 +349,12 @@
|
||||
"setting__desktop_lyric_always_on_top": "Make the lyrics always above other windows",
|
||||
"setting__desktop_lyric_always_on_top_loop": "Automatically refresh the top of the lyrics (try to enable this setting when the lyrics are still blocked by some programs)",
|
||||
"setting__desktop_lyric_audio_visualization": "Audio Visualization (Experimental)",
|
||||
"setting__desktop_lyric_color": "Lyric font color",
|
||||
"setting__desktop_lyric_color_reset": "Reset color",
|
||||
"setting__desktop_lyric_delay_scroll": "Delayed lyrics scroll",
|
||||
"setting__desktop_lyric_direction_horizontal": "horizontal direction",
|
||||
"setting__desktop_lyric_direction_vertical": "vertical direction",
|
||||
"setting__desktop_lyric_direction": "Lyrics Display Direction",
|
||||
"setting__desktop_lyric_direction_horizontal": "Horizontal direction",
|
||||
"setting__desktop_lyric_direction_vertical": "Vertical direction",
|
||||
"setting__desktop_lyric_ellipsis": "Lyrics are not allowed to wrap",
|
||||
"setting__desktop_lyric_enable": "Display lyrics",
|
||||
"setting__desktop_lyric_font": "Lyric font",
|
||||
@@ -329,7 +370,7 @@
|
||||
"setting__desktop_lyric_played_color": "color played",
|
||||
"setting__desktop_lyric_reset": "Reset",
|
||||
"setting__desktop_lyric_reset_window": "Reset window settings",
|
||||
"setting__desktop_lyric_scroll_align": "now playing lyrics scroll position",
|
||||
"setting__desktop_lyric_scroll_align": "Now playing lyrics scroll position",
|
||||
"setting__desktop_lyric_scroll_align_center": "Center",
|
||||
"setting__desktop_lyric_scroll_align_top": "Top",
|
||||
"setting__desktop_lyric_shadow_color": "Shadow color",
|
||||
@@ -339,6 +380,7 @@
|
||||
"setting__download_data_embed": "Whether to embed the following content in the audio file",
|
||||
"setting__download_embed_lyric": "Embedding lyric",
|
||||
"setting__download_embed_pic": "Embedding cover",
|
||||
"setting__download_embed_rlyric": "Also embed Roman accent lyrics (if available)",
|
||||
"setting__download_embed_tlyric": "Also embed translated lyrics (if available)",
|
||||
"setting__download_enable": "Whether to enable download function",
|
||||
"setting__download_lyric": "Lyrics download",
|
||||
@@ -444,7 +486,7 @@
|
||||
"setting__play_mediaDevice": "Audio output",
|
||||
"setting__play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
|
||||
"setting__play_mediaDevice_title": "Select a media device for audio output",
|
||||
"setting__play_media_device_error_tip": "This function conflicts with the audio visualization function. You have enabled audio visualization when you started the software this time. This setting is temporarily unavailable. Please restart the software and then modify this setting.",
|
||||
"setting__play_media_device_error_tip": "This function conflicts with advanced audio functions (audio visualization, sound effect settings). These functions have been enabled when you start the software this time. This setting is not available for now. Please close these functions and restart the software before modifying this setting.",
|
||||
"setting__play_media_device_tip": "This feature conflicts with Audio Visualization, both cannot be enabled at the same time, would you like to turn Audio Visualization off and apply the selected audio output settings?",
|
||||
"setting__play_quality": "Priority playback of 320K quality songs (if available)",
|
||||
"setting__play_save_play_time": "Remember playback progress",
|
||||
|
||||
@@ -72,7 +72,7 @@ const createI18n = (): I18n => {
|
||||
return message
|
||||
},
|
||||
getMessage(key: keyof Message, val?: TranslateValues): string {
|
||||
let targetMessage = this.message[key] ?? this.messages[this.fallbackLocale][key] ?? ''
|
||||
let targetMessage = this.message[key] ?? this.messages[this.fallbackLocale][key] ?? key
|
||||
return val ? this.fillMessage(targetMessage, val) : targetMessage
|
||||
},
|
||||
t(key: keyof Message, val?: TranslateValues): string {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"comment__hot_load_error": "热门评论加载失败,点击尝试重新加载",
|
||||
"comment__hot_loading": "热门评论加载中",
|
||||
"comment__hot_title": "热门评论",
|
||||
"comment__location": "来自{location}",
|
||||
"comment__new_load_error": "最新评论加载失败,点击尝试重新加载",
|
||||
"comment__new_loading": "最新评论加载中",
|
||||
"comment__new_title": "最新评论",
|
||||
@@ -92,6 +93,8 @@
|
||||
"list__move_to": "移动到...",
|
||||
"list__movedown": "下移",
|
||||
"list__moveup": "上移",
|
||||
"list__name_default": "试听列表",
|
||||
"list__name_love": "我的收藏",
|
||||
"list__new_list_btn": "新建列表",
|
||||
"list__new_list_input": "新列表...",
|
||||
"list__pause": "暂停任务",
|
||||
@@ -205,6 +208,7 @@
|
||||
"player__end": "播放完毕",
|
||||
"player__error": "音频加载出错,5 秒后切换下一首",
|
||||
"player__geting_url": "歌曲链接获取中...",
|
||||
"player__geting_url_delay_retry": "服务繁忙,{time}秒后重试...",
|
||||
"player__hide_detail_tip": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
|
||||
"player__loading": "音乐加载中...",
|
||||
"player__music_album": "专辑名:",
|
||||
@@ -224,6 +228,40 @@
|
||||
"player__playing": "播放中...",
|
||||
"player__prev": "上一首",
|
||||
"player__refresh_url": "URL过期,正在刷新URL...",
|
||||
"player__sound_effect": "音效设置(实验性)",
|
||||
"player__sound_effect_biquad_filter": "均衡器",
|
||||
"player__sound_effect_biquad_filter_preset_classical": "古典",
|
||||
"player__sound_effect_biquad_filter_preset_dance": "舞曲",
|
||||
"player__sound_effect_biquad_filter_preset_electronic": "电子乐",
|
||||
"player__sound_effect_biquad_filter_preset_pop": "流行",
|
||||
"player__sound_effect_biquad_filter_preset_rock": "摇滚",
|
||||
"player__sound_effect_biquad_filter_preset_slow": "慢歌",
|
||||
"player__sound_effect_biquad_filter_preset_soft": "柔和",
|
||||
"player__sound_effect_biquad_filter_preset_subwoofer": "重低音",
|
||||
"player__sound_effect_biquad_filter_preset_vocal": "人声",
|
||||
"player__sound_effect_biquad_filter_reset_btn": "重置",
|
||||
"player__sound_effect_biquad_filter_save_btn": "另存预设",
|
||||
"player__sound_effect_biquad_filter_save_input": "新预设...",
|
||||
"player__sound_effect_convolution": "环境混响音效",
|
||||
"player__sound_effect_convolution_file_bright_hall": "大厅",
|
||||
"player__sound_effect_convolution_file_cardiod_35_10_spread": "心形扩散",
|
||||
"player__sound_effect_convolution_file_cinema_diningroom": "电影院",
|
||||
"player__sound_effect_convolution_file_dining_living_true_stereo": "餐厅",
|
||||
"player__sound_effect_convolution_file_feedback_spring": "反馈弹簧",
|
||||
"player__sound_effect_convolution_file_living_bedroom_leveled": "卫生间",
|
||||
"player__sound_effect_convolution_file_matrix_1": "矩阵混响",
|
||||
"player__sound_effect_convolution_file_matrix_2": "矩阵混响2",
|
||||
"player__sound_effect_convolution_file_s2_r4_bd": "教堂",
|
||||
"player__sound_effect_convolution_file_s3_r1_bd": "立体声",
|
||||
"player__sound_effect_convolution_file_spreader50_65ms": "室内",
|
||||
"player__sound_effect_convolution_file_telephone": "电话",
|
||||
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "磁性立体声",
|
||||
"player__sound_effect_convolution_main_gain": "原始音频增益",
|
||||
"player__sound_effect_convolution_send_gain": "环境音效增益",
|
||||
"player__sound_effect_panner": "3D立体环绕(需使用耳机)",
|
||||
"player__sound_effect_panner_enabled": "启用",
|
||||
"player__sound_effect_panner_sound_r": "声音距离",
|
||||
"player__sound_effect_panner_sound_speed": "环绕速度",
|
||||
"player__stop": "暂停播放",
|
||||
"player__volume": "当前音量:",
|
||||
"player__volume_mute_label": "静音",
|
||||
@@ -447,7 +485,7 @@
|
||||
"setting__play_mediaDevice": "音频输出",
|
||||
"setting__play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
|
||||
"setting__play_mediaDevice_title": "选择声音输出的媒体设备",
|
||||
"setting__play_media_device_error_tip": "此功能与音频可视化功能冲突,你本次启动软件时已启用过音频可视化,此设置暂不可用,请 重启 软件后,再来修改此设置。",
|
||||
"setting__play_media_device_error_tip": "此功能与高级音频功能(音频可视化、音效设置)冲突,你本次启动软件时已启用这些功能,此设置暂不可用,请 关闭这些功能 并 重启 软件后,再来修改此设置。",
|
||||
"setting__play_media_device_tip": "此功能与音频可视化功能冲突,两者无法同时启用,是否将音频可视化关闭 并 应用所选音频输出设置?",
|
||||
"setting__play_quality": "优先播放320K品质的歌曲(如果可用)",
|
||||
"setting__play_save_play_time": "记住播放进度",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"comment__hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
|
||||
"comment__hot_loading": "熱門評論加載中",
|
||||
"comment__hot_title": "熱門評論",
|
||||
"comment__location": "來自{location}",
|
||||
"comment__new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
|
||||
"comment__new_loading": "最新評論加載中",
|
||||
"comment__new_title": "最新評論",
|
||||
@@ -92,6 +93,8 @@
|
||||
"list__move_to": "移動到...",
|
||||
"list__movedown": "下移",
|
||||
"list__moveup": "上移",
|
||||
"list__name_default": "試聽清單",
|
||||
"list__name_love": "我的收藏",
|
||||
"list__new_list_btn": "新建列表",
|
||||
"list__new_list_input": "新列表...",
|
||||
"list__pause": "暫停任務",
|
||||
@@ -206,6 +209,7 @@
|
||||
"player__end": "播放完畢",
|
||||
"player__error": "音頻加載出錯,5 秒後切換下一首",
|
||||
"player__geting_url": "歌曲鏈接獲取中...",
|
||||
"player__geting_url_delay_retry": "服務繁忙,{time}秒後重試...",
|
||||
"player__hide_detail_tip": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
|
||||
"player__loading": "音樂加載中...",
|
||||
"player__music_name": "歌曲名:",
|
||||
@@ -224,6 +228,41 @@
|
||||
"player__playing": "播放中...",
|
||||
"player__prev": "上一首",
|
||||
"player__refresh_url": "URL過期,正在刷新URL...",
|
||||
"player__sound_effect": "音效設置(實驗性)",
|
||||
"player__sound_effect_biquad_filter": "均衡器",
|
||||
"player__sound_effect_biquad_filter_preset_classical": "古典",
|
||||
"player__sound_effect_biquad_filter_preset_dance": "舞曲",
|
||||
"player__sound_effect_biquad_filter_preset_electronic": "電子樂",
|
||||
"player__sound_effect_biquad_filter_preset_pop": "流行",
|
||||
"player__sound_effect_biquad_filter_preset_rock": "搖滾",
|
||||
"player__sound_effect_biquad_filter_preset_slow": "慢歌",
|
||||
"player__sound_effect_biquad_filter_preset_soft": "柔和",
|
||||
"player__sound_effect_biquad_filter_preset_subwoofer": "重低音",
|
||||
"player__sound_effect_biquad_filter_preset_vocal": "人聲",
|
||||
"player__sound_effect_biquad_filter_reset_btn": "重置",
|
||||
"player__sound_effect_biquad_filter_save_btn": "另存預設",
|
||||
"player__sound_effect_biquad_filter_save_input": "新預設...",
|
||||
"player__sound_effect_convolution": "環境混響音效",
|
||||
"player__sound_effect_convolution_file_bright_hall": "大廳",
|
||||
"player__sound_effect_convolution_file_cardiod_35_10_spread": "搖滾",
|
||||
"player__sound_effect_convolution_file_cinema_diningroom": "電影院",
|
||||
"player__sound_effect_convolution_file_dining_living_true_stereo": "餐廳",
|
||||
"player__sound_effect_convolution_file_feedback_spring": "反饋彈簧",
|
||||
"player__sound_effect_convolution_file_living_bedroom_leveled": "衛生間",
|
||||
"player__sound_effect_convolution_file_matrix_1": "矩陣",
|
||||
"player__sound_effect_convolution_file_matrix_2": "矩陣2",
|
||||
"player__sound_effect_convolution_file_s2_r4_bd": "教堂",
|
||||
"player__sound_effect_convolution_file_s3_r1_bd": "立體聲",
|
||||
"player__sound_effect_convolution_file_spreader25_125ms": "室內2",
|
||||
"player__sound_effect_convolution_file_spreader50_65ms": "室內",
|
||||
"player__sound_effect_convolution_file_telephone": "電話",
|
||||
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "搖滾2",
|
||||
"player__sound_effect_convolution_main_gain": "原始音頻增益",
|
||||
"player__sound_effect_convolution_send_gain": "環境音效增益",
|
||||
"player__sound_effect_panner": "3D立體環繞(需使用耳機)",
|
||||
"player__sound_effect_panner_enabled": "啟用",
|
||||
"player__sound_effect_panner_sound_r": "聲音距離",
|
||||
"player__sound_effect_panner_sound_speed": "環繞速度",
|
||||
"player__stop": "暫停播放",
|
||||
"player__volume": "當前音量:",
|
||||
"player__volume_mute_label": "靜音",
|
||||
@@ -310,6 +349,7 @@
|
||||
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
|
||||
"setting__desktop_lyric_always_on_top_loop": "自動刷新歌詞置頂(當歌詞置頂後仍被某些程序遮擋時可嘗試啟用此設置)",
|
||||
"setting__desktop_lyric_audio_visualization": "音頻可視化(實驗性)",
|
||||
"setting__desktop_lyric_color": "歌詞字體顏色",
|
||||
"setting__desktop_lyric_color_reset": "重置顏色",
|
||||
"setting__desktop_lyric_delay_scroll": "延遲歌詞滾動",
|
||||
"setting__desktop_lyric_direction": "歌詞顯示方向",
|
||||
@@ -340,6 +380,7 @@
|
||||
"setting__download_data_embed": "是否將以下內容嵌入到音頻文件中",
|
||||
"setting__download_embed_lyric": "歌詞嵌入",
|
||||
"setting__download_embed_pic": "封面嵌入",
|
||||
"setting__download_embed_rlyric": "同時嵌入羅馬音歌詞(如果有)",
|
||||
"setting__download_embed_tlyric": "同時嵌入翻譯歌詞(如果有)",
|
||||
"setting__download_enable": "是否啟用下載功能",
|
||||
"setting__download_lyric": "歌詞下載",
|
||||
@@ -445,7 +486,7 @@
|
||||
"setting__play_mediaDevice": "音頻輸出",
|
||||
"setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
|
||||
"setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備",
|
||||
"setting__play_media_device_error_tip": "此功能與音頻可視化功能衝突,你本次啟動軟件時已啟用過音頻可視化,此設置暫不可用,請 重啟 軟件後,再來修改此設置。",
|
||||
"setting__play_media_device_error_tip": "此功能與高級音頻功能(音頻可視化、音效設置)衝突,你本次啟動軟件時已啟用這些功能,此設置暫不可用,請 關閉這些功能 並 重啟 軟件後,再來修改此設置。",
|
||||
"setting__play_media_device_tip": "此功能與音頻可視化功能衝突,兩者無法同時啟用,是否將音頻可視化關閉 並 應用所選音頻輸出設置?",
|
||||
"setting__play_quality": "優先播放320K品質的歌曲(如果可用)",
|
||||
"setting__play_save_play_time": "記住播放進度",
|
||||
|
||||
@@ -7,94 +7,114 @@ import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils'
|
||||
|
||||
const requestIps = new Map<string, number>()
|
||||
|
||||
|
||||
const getAvailableIP = (req: http.IncomingMessage) => {
|
||||
let ip = getIP(req)
|
||||
return ip && (requestIps.get(ip) ?? 0) < 10 ? ip : null
|
||||
}
|
||||
|
||||
const verifyByKey = (encryptMsg: string, userId: string) => {
|
||||
const keyInfo = getClientKeyInfo(userId)
|
||||
if (!keyInfo) return null
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(encryptMsg, keyInfo.key)
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
saveClientKeyInfo(keyInfo)
|
||||
}
|
||||
return aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const verifyByCode = (encryptMsg: string, password: string) => {
|
||||
let key = ''.padStart(16, Buffer.from(password).toString('hex'))
|
||||
// const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(req.headers.m, authCode, key)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(encryptMsg, key)
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
const data = text.split('\n')
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----`
|
||||
const deviceName = data[2] || 'Unknown'
|
||||
const isMobile = data[3] == 'lx_music_mobile'
|
||||
const keyInfo = createClientKeyInfo(deviceName, isMobile)
|
||||
return rsaEncrypt(Buffer.from(JSON.stringify({
|
||||
clientId: keyInfo.clientId,
|
||||
key: keyInfo.key,
|
||||
serverName: getComputerName(),
|
||||
})), publicKey)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, password: string) => {
|
||||
let code = 401
|
||||
let msg: string = SYNC_CODE.msgAuthFailed
|
||||
|
||||
let ip = getIP(req)
|
||||
// console.log(req.headers)
|
||||
if (typeof req.headers.m == 'string') {
|
||||
if (ip && (requestIps.get(ip) ?? 0) < 10) {
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) { // key验证
|
||||
if (typeof req.headers.i != 'string') break label
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
saveClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key)
|
||||
}
|
||||
} else { // 连接码验证
|
||||
let key = ''.padStart(16, Buffer.from(password).toString('hex'))
|
||||
// const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(req.headers.m, authCode, key)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
code = 200
|
||||
const data = text.split('\n')
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----`
|
||||
const deviceName = data[2] || 'Unknown'
|
||||
const isMobile = data[3] == 'lx_music_mobile'
|
||||
const keyInfo = createClientKeyInfo(deviceName, isMobile)
|
||||
msg = rsaEncrypt(Buffer.from(JSON.stringify({
|
||||
clientId: keyInfo.clientId,
|
||||
key: keyInfo.key,
|
||||
serverName: getComputerName(),
|
||||
})), publicKey)
|
||||
}
|
||||
}
|
||||
let ip = getAvailableIP(req)
|
||||
if (ip) {
|
||||
if (typeof req.headers.m == 'string' && req.headers.m) {
|
||||
const userId = req.headers.i
|
||||
const _msg = typeof userId == 'string' && userId
|
||||
? verifyByKey(req.headers.m, userId)
|
||||
: verifyByCode(req.headers.m, password)
|
||||
if (_msg != null) {
|
||||
msg = _msg
|
||||
code = 200
|
||||
}
|
||||
} else {
|
||||
code = 403
|
||||
msg = SYNC_CODE.msgBlockedIp
|
||||
}
|
||||
|
||||
if (code != 200) {
|
||||
const num = requestIps.get(ip) ?? 0
|
||||
// if (num > 20) return
|
||||
requestIps.set(ip, num + 1)
|
||||
}
|
||||
} else {
|
||||
code = 403
|
||||
msg = SYNC_CODE.msgBlockedIp
|
||||
}
|
||||
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
|
||||
if (ip && code != 200) {
|
||||
const num = requestIps.get(ip) ?? 0
|
||||
if (num > 20) return
|
||||
requestIps.set(ip, num + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const verifyConnection = (encryptMsg: string, userId: string) => {
|
||||
const keyInfo = getClientKeyInfo(userId)
|
||||
if (!keyInfo) return false
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(encryptMsg, keyInfo.key)
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
// console.log(text)
|
||||
return text == SYNC_CODE.msgConnect
|
||||
}
|
||||
export const authConnect = async(req: http.IncomingMessage) => {
|
||||
const query = querystring.parse((req.url as string).split('?')[1])
|
||||
const i = query.i
|
||||
const t = query.t
|
||||
label:
|
||||
if (typeof i == 'string' && typeof t == 'string') {
|
||||
const keyInfo = getClientKeyInfo(i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(t, keyInfo.key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text == SYNC_CODE.msgConnect) return
|
||||
let ip = getAvailableIP(req)
|
||||
if (ip) {
|
||||
const query = querystring.parse((req.url as string).split('?')[1])
|
||||
const i = query.i
|
||||
const t = query.t
|
||||
if (typeof i == 'string' && typeof t == 'string' && verifyConnection(t, i)) return
|
||||
|
||||
const num = requestIps.get(ip) ?? 0
|
||||
requestIps.set(ip, num + 1)
|
||||
}
|
||||
throw new Error('failed')
|
||||
}
|
||||
|
||||
@@ -40,16 +40,18 @@ const codeTools: {
|
||||
},
|
||||
}
|
||||
|
||||
const checkDuplicateClient = (newSocket: LX.Sync.Server.Socket) => {
|
||||
for (const client of [...wss!.clients]) {
|
||||
if (client === newSocket || client.keyInfo.clientId != newSocket.keyInfo.clientId) continue
|
||||
console.log('duplicate client', client.keyInfo.deviceName)
|
||||
client.isReady = false
|
||||
client.close(SYNC_CLOSE_CODE.normal)
|
||||
}
|
||||
}
|
||||
|
||||
const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingMessage) => {
|
||||
const queryData = url.parse(request.url as string, true).query as Record<string, string>
|
||||
|
||||
socket.onClose(() => {
|
||||
// console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
|
||||
sendServerStatus(status)
|
||||
})
|
||||
|
||||
|
||||
// // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
|
||||
const keyInfo = getClientKeyInfo(queryData.i)
|
||||
if (!keyInfo) {
|
||||
@@ -60,6 +62,8 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
|
||||
saveClientKeyInfo(keyInfo)
|
||||
// // socket.lx_keyInfo = keyInfo
|
||||
socket.keyInfo = keyInfo
|
||||
checkDuplicateClient(socket)
|
||||
|
||||
try {
|
||||
await syncList(wss as LX.Sync.Server.SocketServer, socket)
|
||||
} catch (err) {
|
||||
@@ -68,6 +72,11 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
|
||||
return
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
socket.onClose(() => {
|
||||
// console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
|
||||
sendServerStatus(status)
|
||||
})
|
||||
// handleConnection(io, socket)
|
||||
sendServerStatus(status)
|
||||
|
||||
@@ -216,15 +225,15 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
wss?.clients.forEach(ws => {
|
||||
if (ws.isAlive == false) {
|
||||
ws.terminate()
|
||||
wss?.clients.forEach(socket => {
|
||||
if (socket.isAlive == false) {
|
||||
socket.terminate()
|
||||
return
|
||||
}
|
||||
|
||||
ws.isAlive = false
|
||||
ws.ping(noop)
|
||||
if (ws.keyInfo.isMobile) ws.send('ping', noop)
|
||||
socket.isAlive = false
|
||||
socket.ping(noop)
|
||||
if (socket.keyInfo.isMobile) socket.send('ping', noop)
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const needle = require('needle')
|
||||
const zlib = require('zlib')
|
||||
const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto')
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
|
||||
@@ -139,11 +140,15 @@ const handleShowUpdateAlert = (data, resolve, reject) => {
|
||||
|
||||
contextBridge.exposeInMainWorld('lx', {
|
||||
EVENT_NAMES,
|
||||
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
|
||||
request(url, { method = 'get', timeout, headers, body, form, formData, bodyBinaryBase64 }, callback) {
|
||||
let options = { headers }
|
||||
let data
|
||||
if (body) {
|
||||
data = body
|
||||
} else if (bodyBinaryBase64) {
|
||||
try {
|
||||
data = Buffer.from(bodyBinaryBase64, 'base64')
|
||||
} catch {}
|
||||
} else if (form) {
|
||||
data = form
|
||||
// data.content_type = 'application/x-www-form-urlencoded'
|
||||
@@ -233,8 +238,26 @@ contextBridge.exposeInMainWorld('lx', {
|
||||
return Buffer.from(buf, 'binary').toString(format)
|
||||
},
|
||||
},
|
||||
zlib: {
|
||||
inflate(buf) {
|
||||
return new Promise((resolve, reject) => {
|
||||
zlib.inflate(buf, (err, data) => {
|
||||
if (err) reject(new Error(err.message))
|
||||
else resolve(data)
|
||||
})
|
||||
})
|
||||
},
|
||||
deflate(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
zlib.deflate(data, (err, buf) => {
|
||||
if (err) reject(new Error(err.message))
|
||||
else resolve(buf)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
version: '1.2.0',
|
||||
version: '1.4.0',
|
||||
// removeEvent(eventName, handler) {
|
||||
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
// let handlers
|
||||
|
||||
@@ -81,6 +81,7 @@ const winEvent = () => {
|
||||
// browserWindow!.setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTop'], 'screen-saver')
|
||||
// }
|
||||
if (global.lx.appSetting['desktopLyric.isAlwaysOnTop'] && global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) alwaysOnTopTools.startLoop()
|
||||
browserWindow!.blur()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import sync from './sync'
|
||||
import data from './data'
|
||||
import music from './music'
|
||||
import download from './download'
|
||||
import soundEffect from './soundEffect'
|
||||
import { sendEvent } from '../main'
|
||||
|
||||
export * from './app'
|
||||
@@ -33,6 +34,7 @@ export default () => {
|
||||
data()
|
||||
music()
|
||||
download()
|
||||
soundEffect()
|
||||
|
||||
global.lx.event_app.on('updated_config', (keys, setting) => {
|
||||
sendConfigChange(setting)
|
||||
|
||||
20
src/main/modules/winMain/rendererEvent/soundEffect.ts
Normal file
20
src/main/modules/winMain/rendererEvent/soundEffect.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { STORE_NAMES } from '@common/constants'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainOn, mainHandle } from '@common/mainIpc'
|
||||
import getStore from '@main/utils/store'
|
||||
|
||||
export default () => {
|
||||
mainHandle<LX.SoundEffect.EQPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_sound_effect_eq_preset, async() => {
|
||||
return getStore(STORE_NAMES.SOUND_EFFECT).get('eqPreset') as LX.SoundEffect.EQPreset[] | null ?? []
|
||||
})
|
||||
mainOn<LX.SoundEffect.EQPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.save_sound_effect_eq_preset, ({ params }) => {
|
||||
getStore(STORE_NAMES.SOUND_EFFECT).set('eqPreset', params)
|
||||
})
|
||||
|
||||
mainHandle<LX.SoundEffect.ConvolutionPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_sound_effect_convolution_preset, async() => {
|
||||
return getStore(STORE_NAMES.SOUND_EFFECT).get('convolutionPreset') as LX.SoundEffect.ConvolutionPreset[] | null ?? []
|
||||
})
|
||||
mainOn<LX.SoundEffect.ConvolutionPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.save_sound_effect_convolution_preset, ({ params }) => {
|
||||
getStore(STORE_NAMES.SOUND_EFFECT).set('convolutionPreset', params)
|
||||
})
|
||||
}
|
||||
1
src/main/types/common.d.ts
vendored
1
src/main/types/common.d.ts
vendored
@@ -10,3 +10,4 @@ import '@common/types/player'
|
||||
import '@common/types/desktop_lyric'
|
||||
import '@common/types/theme'
|
||||
import '@common/types/ipc_main'
|
||||
import '@common/types/sound_effect'
|
||||
|
||||
@@ -154,7 +154,15 @@ export const initHotKey = async() => {
|
||||
let localConfig = electronStore_hotKey.get('local') as LX.HotKeyConfig | null
|
||||
let globalConfig = electronStore_hotKey.get('global') as LX.HotKeyConfig | null
|
||||
|
||||
if (!localConfig) {
|
||||
if (globalConfig) {
|
||||
// 移除v2.2.0及之前设置的全局媒体快捷键注册
|
||||
if (globalConfig.keys.MediaPlayPause) {
|
||||
delete globalConfig.keys.MediaPlayPause
|
||||
delete globalConfig.keys.MediaNextTrack
|
||||
delete globalConfig.keys.MediaPreviousTrack
|
||||
electronStore_hotKey.set('global', globalConfig)
|
||||
}
|
||||
} else {
|
||||
// migrate hotKey
|
||||
const config = await migrateHotKey()
|
||||
if (config) {
|
||||
|
||||
@@ -29,7 +29,7 @@ export default (db: Database.Database) => {
|
||||
// PRAGMA user_version = x
|
||||
// console.log(db.prepare('PRAGMA user_version').get().user_version)
|
||||
// https://github.com/WiseLibs/better-sqlite3/issues/668#issuecomment-1145285728
|
||||
const version = db.prepare('SELECT "field_value" FROM "main"."db_info" WHERE "field_name" = ?').get('version').field_value
|
||||
const version = (db.prepare<[string]>('SELECT "field_value" FROM "main"."db_info" WHERE "field_name" = ?').get('version') as { field_value: string }).field_value
|
||||
switch (version) {
|
||||
case '1':
|
||||
migrateV1(db)
|
||||
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
/**
|
||||
* 查询下载歌曲列表
|
||||
*/
|
||||
export const queryDownloadList = (): LX.DBService.DownloadMusicInfo[] => {
|
||||
export const queryDownloadList = () => {
|
||||
const queryStatement = createQueryStatement()
|
||||
return queryStatement.all()
|
||||
return queryStatement.all() as LX.DBService.DownloadMusicInfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getDB } from '../../db'
|
||||
*/
|
||||
export const createQueryStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
SELECT "id", "isComplate", "status", "statusText", "progress_downloaded", "progress_total", "url", "quality", "ext", "fileName", "filePath", "musicInfo", "position"
|
||||
FROM download_list
|
||||
ORDER BY "position" ASC
|
||||
@@ -30,7 +30,7 @@ export const createInsertStatement = () => {
|
||||
*/
|
||||
export const createClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
DELETE FROM "main"."download_list"
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
* 获取用户列表
|
||||
* @returns
|
||||
*/
|
||||
export const queryAllUserList = (): LX.DBService.UserListInfo[] => {
|
||||
return createListQueryStatement().all()
|
||||
export const queryAllUserList = () => {
|
||||
return createListQueryStatement().all() as LX.DBService.UserListInfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,9 +154,9 @@ export const updateMusicInfos = (list: LX.DBService.MusicInfo[]) => {
|
||||
* @param listId 列表Id
|
||||
* @returns 列表歌曲
|
||||
*/
|
||||
export const queryMusicInfoByListId = (listId: string): LX.DBService.MusicInfo[] => {
|
||||
export const queryMusicInfoByListId = (listId: string) => {
|
||||
const musicInfoQueryStatement = createMusicInfoQueryStatement()
|
||||
return musicInfoQueryStatement.all({ listId })
|
||||
return musicInfoQueryStatement.all({ listId }) as LX.DBService.MusicInfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,9 +268,9 @@ export const removeMusicInfoByListId = (ids: string[]) => {
|
||||
* @param musicInfoId 音乐id
|
||||
* @returns
|
||||
*/
|
||||
export const queryMusicInfoByListIdAndMusicInfoId = (listId: string, musicInfoId: string): LX.DBService.MusicInfo | null => {
|
||||
export const queryMusicInfoByListIdAndMusicInfoId = (listId: string, musicInfoId: string) => {
|
||||
const musicInfoByListAndMusicInfoIdQueryStatement = createMusicInfoByListAndMusicInfoIdQueryStatement()
|
||||
return musicInfoByListAndMusicInfoIdQueryStatement.get({ listId, musicInfoId })
|
||||
return musicInfoByListAndMusicInfoIdQueryStatement.get({ listId, musicInfoId }) as LX.DBService.MusicInfo | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,9 +278,9 @@ export const queryMusicInfoByListIdAndMusicInfoId = (listId: string, musicInfoId
|
||||
* @param id 音乐id
|
||||
* @returns
|
||||
*/
|
||||
export const queryMusicInfoByMusicInfoId = (id: string): LX.DBService.MusicInfo[] => {
|
||||
export const queryMusicInfoByMusicInfoId = (id: string) => {
|
||||
const musicInfoByMusicInfoIdQueryStatement = createMusicInfoByMusicInfoIdQueryStatement()
|
||||
return musicInfoByMusicInfoIdQueryStatement.all(id)
|
||||
return musicInfoByMusicInfoIdQueryStatement.all(id) as LX.DBService.MusicInfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getDB } from '../../db'
|
||||
*/
|
||||
export const createListQueryStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
SELECT "id", "name", "source", "sourceListId", "position", "locationUpdateTime"
|
||||
FROM "main"."my_list"
|
||||
`)
|
||||
@@ -30,7 +30,7 @@ export const createListInsertStatement = () => {
|
||||
*/
|
||||
export const createListClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare('DELETE FROM "main"."my_list"')
|
||||
return db.prepare<[]>('DELETE FROM "main"."my_list"')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +100,7 @@ export const createMusicInfoUpdateStatement = () => {
|
||||
*/
|
||||
export const createMusicInfoClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare('DELETE FROM "main"."my_list_music_info"')
|
||||
return db.prepare<[]>('DELETE FROM "main"."my_list_music_info"')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +162,7 @@ export const createMusicInfoOrderInsertStatement = () => {
|
||||
*/
|
||||
export const createMusicInfoOrderClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare('DELETE FROM "main"."my_list_music_info_order"')
|
||||
return db.prepare<[]>('DELETE FROM "main"."my_list_music_info_order"')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,9 +20,9 @@ import {
|
||||
* @param id 歌曲id
|
||||
* @returns 歌词信息
|
||||
*/
|
||||
export const queryLyric = (id: string): LX.DBService.Lyricnfo[] => {
|
||||
export const queryLyric = (id: string) => {
|
||||
const lyricQueryStatement = createLyricQueryStatement()
|
||||
return lyricQueryStatement.all(id)
|
||||
return lyricQueryStatement.all(id) as LX.DBService.Lyricnfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,9 +30,9 @@ export const queryLyric = (id: string): LX.DBService.Lyricnfo[] => {
|
||||
* @param id 歌曲id
|
||||
* @returns 歌词信息
|
||||
*/
|
||||
export const queryRawLyric = (id: string): LX.DBService.Lyricnfo[] => {
|
||||
export const queryRawLyric = (id: string) => {
|
||||
const rawLyricQueryStatement = createRawLyricQueryStatement()
|
||||
return rawLyricQueryStatement.all(id)
|
||||
return rawLyricQueryStatement.all(id) as LX.DBService.Lyricnfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,7 @@ export const clearRawLyric = () => {
|
||||
*/
|
||||
export const countRawLyric = () => {
|
||||
const countStatement = createRawLyricCountStatement()
|
||||
return countStatement.get().count
|
||||
return (countStatement.get() as { count: number }).count
|
||||
}
|
||||
|
||||
|
||||
@@ -93,9 +93,9 @@ export const countRawLyric = () => {
|
||||
* @param id 歌曲id
|
||||
* @returns 歌词信息
|
||||
*/
|
||||
export const queryEditedLyric = (id: string): LX.DBService.Lyricnfo[] => {
|
||||
export const queryEditedLyric = (id: string) => {
|
||||
const rawLyricQueryStatement = createEditedLyricQueryStatement()
|
||||
return rawLyricQueryStatement.all(id)
|
||||
return rawLyricQueryStatement.all(id) as LX.DBService.Lyricnfo[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,5 +148,5 @@ export const clearEditedLyric = () => {
|
||||
*/
|
||||
export const countEditedLyric = () => {
|
||||
const countStatement = createEditedLyricCountStatement()
|
||||
return countStatement.get().count
|
||||
return (countStatement.get() as { count: number }).count
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const createRawLyricInsertStatement = () => {
|
||||
*/
|
||||
export const createRawLyricClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
DELETE FROM "main"."lyric"
|
||||
WHERE "source"='${RAW_LYRIC}'
|
||||
`)
|
||||
@@ -83,7 +83,7 @@ export const createRawLyricUpdateStatement = () => {
|
||||
*/
|
||||
export const createRawLyricCountStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`SELECT COUNT(*) as count FROM "main"."lyric" WHERE "source"='${RAW_LYRIC}'`)
|
||||
return db.prepare<[]>(`SELECT COUNT(*) as count FROM "main"."lyric" WHERE "source"='${RAW_LYRIC}'`)
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ export const createEditedLyricInsertStatement = () => {
|
||||
*/
|
||||
export const createEditedLyricClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
DELETE FROM "main"."lyric"
|
||||
WHERE "source"='${EDITED_LYRIC}'
|
||||
`)
|
||||
@@ -153,5 +153,5 @@ export const createEditedLyricUpdateStatement = () => {
|
||||
*/
|
||||
export const createEditedLyricCountStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`SELECT COUNT(*) as count FROM "main"."lyric" WHERE "source"='${EDITED_LYRIC}'`)
|
||||
return db.prepare<[]>(`SELECT COUNT(*) as count FROM "main"."lyric" WHERE "source"='${EDITED_LYRIC}'`)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
* @param id 歌曲id
|
||||
* @returns 歌曲信息
|
||||
*/
|
||||
export const queryMusicInfo = (id: string): LX.DBService.MusicInfoOtherSource[] => {
|
||||
export const queryMusicInfo = (id: string) => {
|
||||
const musicInfoQueryStatement = createMusicInfoQueryStatement()
|
||||
return musicInfoQueryStatement.all(id)
|
||||
return musicInfoQueryStatement.all(id) as LX.DBService.MusicInfoOtherSource[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,5 +55,5 @@ export const clearMusicInfo = () => {
|
||||
*/
|
||||
export const countMusicInfo = () => {
|
||||
const countStatement = createCountStatement()
|
||||
return countStatement.get().count
|
||||
return (countStatement.get() as { count: number }).count
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const createMusicInfoInsertStatement = () => {
|
||||
*/
|
||||
export const createMusicInfoClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
DELETE FROM "main"."music_info_other_source"
|
||||
`)
|
||||
}
|
||||
@@ -56,5 +56,5 @@ export const createMusicInfoDeleteStatement = () => {
|
||||
*/
|
||||
export const createCountStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare('SELECT COUNT(*) as count FROM "main"."music_info_other_source"')
|
||||
return db.prepare<[]>('SELECT COUNT(*) as count FROM "main"."music_info_other_source"')
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
* @param id 歌曲id
|
||||
* @returns url
|
||||
*/
|
||||
export const queryMusicUrl = (id: string): string | null => {
|
||||
export const queryMusicUrl = (id: string) => {
|
||||
const queryStatement = createQueryStatement()
|
||||
return queryStatement.get(id)?.url
|
||||
return (queryStatement.get(id) as { url: string } | null)?.url ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,5 +71,5 @@ export const clearMusicUrl = () => {
|
||||
*/
|
||||
export const countMusicUrl = () => {
|
||||
const countStatement = createCountStatement()
|
||||
return countStatement.get().count
|
||||
return (countStatement.get() as { count: number }).count
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const createInsertStatement = () => {
|
||||
*/
|
||||
export const createClearStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare(`
|
||||
return db.prepare<[]>(`
|
||||
DELETE FROM "main"."music_url"
|
||||
`)
|
||||
}
|
||||
@@ -65,5 +65,5 @@ export const createUpdateStatement = () => {
|
||||
*/
|
||||
export const createCountStatement = () => {
|
||||
const db = getDB()
|
||||
return db.prepare('SELECT COUNT(*) as count FROM "main"."music_url"')
|
||||
return db.prepare<[]>('SELECT COUNT(*) as count FROM "main"."music_url"')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import tables from './tables'
|
||||
|
||||
const rxp = /\n|\s|;|--.+/g
|
||||
export default (db: Database.Database) => {
|
||||
const result = db.prepare('SELECT type,name,tbl_name,sql FROM "main".sqlite_master WHERE sql NOT NULL;').all()
|
||||
const result = db.prepare<[]>('SELECT type,name,tbl_name,sql FROM "main".sqlite_master WHERE sql NOT NULL;').all() as Array<{ type: string, name: string, tbl_name: string, sql: string }>
|
||||
const dbTableMap = new Map<string, string>()
|
||||
for (const info of result) dbTableMap.set(info.name, info.sql.replace(rxp, ''))
|
||||
return Array.from(tables.entries()).every(([name, sql]) => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<html lang="cn">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="background-color: transparent;">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>桌面歌词-洛雪音乐助手</title>
|
||||
</head>
|
||||
<body>
|
||||
<body id="body" style="background-color: transparent;">
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window.dom_style_theme = document.createElement('style')
|
||||
|
||||
BIN
src/renderer/assets/medias/Silence02s.mp3
Normal file
BIN
src/renderer/assets/medias/Silence02s.mp3
Normal file
Binary file not shown.
5
src/renderer/assets/svgs/tune-variant.svg
Normal file
5
src/renderer/assets/svgs/tune-variant.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M8 13C6.14 13 4.59 14.28 4.14 16H2V18H4.14C4.59 19.72 6.14 21 8 21S11.41 19.72 11.86 18H22V16H11.86C11.41 14.28 9.86 13 8 13M8 19C6.9 19 6 18.1 6 17C6 15.9 6.9 15 8 15S10 15.9 10 17C10 18.1 9.1 19 8 19M19.86 6C19.41 4.28 17.86 3 16 3S12.59 4.28 12.14 6H2V8H12.14C12.59 9.72 14.14 11 16 11S19.41 9.72 19.86 8H22V6H19.86M16 9C14.9 9 14 8.1 14 7C14 5.9 14.9 5 16 5S18 5.9 18 7C18 8.1 17.1 9 16 9Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 505 B |
@@ -56,6 +56,7 @@ const popupStyle = reactive({
|
||||
|
||||
const arrowHeight = 9
|
||||
const arrowWidth = 8
|
||||
const sidePadding = 50
|
||||
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (!visible || !dom_content.value || !props.btnEl) return
|
||||
@@ -63,24 +64,24 @@ watch(() => props.visible, (visible) => {
|
||||
const maxHeight = document.body.clientHeight
|
||||
const elTop = rect.top - window.lx.rootOffset
|
||||
const bottomTopVal = elTop + rect.height
|
||||
const contentHeight = dom_content.value.scrollHeight + arrowHeight + 10
|
||||
const contentHeight = dom_content.value.scrollHeight + arrowHeight + sidePadding
|
||||
if (bottomTopVal + contentHeight < maxHeight || (contentHeight > elTop && elTop <= maxHeight - bottomTopVal)) {
|
||||
isShowTop.value = false
|
||||
popupStyle.top = bottomTopVal + arrowHeight + 'px'
|
||||
popupStyle.maxHeight = maxHeight - bottomTopVal - arrowHeight - 10 + 'px'
|
||||
popupStyle.maxHeight = maxHeight - bottomTopVal - arrowHeight - sidePadding + 'px'
|
||||
} else {
|
||||
isShowTop.value = true
|
||||
let maxContentHeight = elTop - arrowHeight - 10
|
||||
popupStyle.top = (elTop - (elTop < contentHeight ? elTop : contentHeight) + 10) + 'px'
|
||||
let maxContentHeight = elTop - arrowHeight - sidePadding
|
||||
popupStyle.top = (elTop - (elTop < contentHeight ? elTop : contentHeight) + sidePadding) + 'px'
|
||||
popupStyle.maxHeight = maxContentHeight + 'px'
|
||||
}
|
||||
|
||||
const maxWidth = document.body.clientWidth - 20
|
||||
let center = dom_content.value.clientWidth / 2
|
||||
let left = rect.left + rect.width / 2 - window.lx.rootOffset - center
|
||||
if (left < 10) {
|
||||
center -= 10 - left
|
||||
left = 10
|
||||
if (left < sidePadding) {
|
||||
center -= sidePadding - left
|
||||
left = sidePadding
|
||||
} else if (left + dom_content.value.clientWidth > maxWidth) {
|
||||
let newLeft = maxWidth - dom_content.value.clientWidth
|
||||
center = center + left - newLeft
|
||||
|
||||
@@ -22,6 +22,7 @@ import { watch, ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { defaultList, loveList, userLists } from '@renderer/store/list/state'
|
||||
import { addListMusics, moveListMusics, createUserList, getMusicExistListIds } from '@renderer/store/list/action'
|
||||
import useKeyDown from '@renderer/utils/compositions/useKeyDown'
|
||||
import { useI18n } from '@/lang'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -63,6 +64,7 @@ export default {
|
||||
emits: ['update:show'],
|
||||
setup(props) {
|
||||
const keyModDown = useKeyDown('mod')
|
||||
const t = useI18n()
|
||||
const lists = ref([])
|
||||
|
||||
const currentMusicInfo = ref({})
|
||||
@@ -81,8 +83,8 @@ export default {
|
||||
|
||||
const getList = () => {
|
||||
lists.value = [
|
||||
defaultList,
|
||||
loveList,
|
||||
{ ...defaultList, name: t(defaultList.name) },
|
||||
{ ...loveList, name: t(loveList.name) },
|
||||
...userLists,
|
||||
].filter(l => !props.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: false }))
|
||||
checkMusicExist(currentMusicInfo.value)
|
||||
|
||||
@@ -21,6 +21,7 @@ import { computed } from '@common/utils/vueTools'
|
||||
import { defaultList, loveList, userLists } from '@renderer/store/list/state'
|
||||
import { addListMusics, moveListMusics, createUserList } from '@renderer/store/list/action'
|
||||
import useKeyDown from '@renderer/utils/compositions/useKeyDown'
|
||||
import { useI18n } from '@/lang'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -64,11 +65,12 @@ export default {
|
||||
emits: ['update:show', 'confirm'],
|
||||
setup(props) {
|
||||
const keyModDown = useKeyDown('mod')
|
||||
const t = useI18n()
|
||||
|
||||
const lists = computed(() => {
|
||||
return [
|
||||
defaultList,
|
||||
loveList,
|
||||
{ ...defaultList, name: t(defaultList.name) },
|
||||
{ ...loveList, name: t(loveList.name) },
|
||||
...userLists,
|
||||
].filter(l => !props.excludeListId.includes(l.id))
|
||||
})
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<base-btn min :class="[$style.newPreset, {[$style.editing]: isEditing}]" :aria-label="$t('player__sound_effect_biquad_filter_save_btn')" @click="handleEditing($event)">
|
||||
<svg-icon name="plus" />
|
||||
<base-input ref="input" :class="$style.newPresetInput" :value="newPresetName" :placeholder="$t('player__sound_effect_biquad_filter_save_input')" @keyup.enter="handleSave($event)" @blur="handleSave($event)" />
|
||||
</base-btn>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { saveUserConvolutionPreset } from '@renderer/store/soundEffect'
|
||||
|
||||
const isEditing = ref(false)
|
||||
const input = ref(false)
|
||||
const newPresetName = ref('')
|
||||
|
||||
const handleEditing = () => {
|
||||
if (isEditing.value) return
|
||||
// if (!this.newPresetName) this.newPresetName = this.listName
|
||||
isEditing.value = true
|
||||
nextTick(() => {
|
||||
input.value.$el.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleSave = (event) => {
|
||||
let name = event.target.value.trim()
|
||||
newPresetName.value = event.target.value = ''
|
||||
isEditing.value = false
|
||||
if (!name) return
|
||||
if (name.length > 20) name = name.substring(0, 20)
|
||||
saveUserConvolutionPreset({
|
||||
id: Date.now().toString(),
|
||||
name,
|
||||
source: appSetting['player.soundEffect.convolution.fileName'],
|
||||
mainGain: appSetting['player.soundEffect.convolution.mainGain'],
|
||||
sendGain: appSetting['player.soundEffect.convolution.sendGain'],
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.newPreset {
|
||||
position: relative;
|
||||
border: 1px dashed var(--color-primary-font-hover);
|
||||
// background-color: var(--color-main-background);
|
||||
color: var(--color-primary-font-hover);
|
||||
opacity: .7;
|
||||
height: 22px;
|
||||
|
||||
&.editing {
|
||||
opacity: 1;
|
||||
width: 90px;
|
||||
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
.newPresetInput {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.svg-icon {
|
||||
vertical-align: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.newPresetInput {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// line-height: 16px;
|
||||
background: none !important;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
padding: 0 3px;
|
||||
border-radius: 0;
|
||||
display: none;
|
||||
&::placeholder {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<base-btn min :class="[$style.newPreset, {[$style.editing]: isEditing}]" :aria-label="$t('player__sound_effect_biquad_filter_save_btn')" @click="handleEditing($event)">
|
||||
<svg-icon name="plus" />
|
||||
<base-input ref="input" :class="$style.newPresetInput" :value="newPresetName" :placeholder="$t('player__sound_effect_biquad_filter_save_input')" @keyup.enter="handleSave($event)" @blur="handleSave($event)" />
|
||||
</base-btn>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { saveUserEQPreset } from '@renderer/store/soundEffect'
|
||||
|
||||
const isEditing = ref(false)
|
||||
const input = ref(false)
|
||||
const newPresetName = ref('')
|
||||
|
||||
const handleEditing = () => {
|
||||
if (isEditing.value) return
|
||||
// if (!this.newPresetName) this.newPresetName = this.listName
|
||||
isEditing.value = true
|
||||
nextTick(() => {
|
||||
input.value.$el.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleSave = (event) => {
|
||||
let name = event.target.value.trim()
|
||||
newPresetName.value = event.target.value = ''
|
||||
isEditing.value = false
|
||||
if (!name) return
|
||||
if (name.length > 20) name = name.substring(0, 20)
|
||||
saveUserEQPreset({
|
||||
id: Date.now().toString(),
|
||||
name,
|
||||
hz31: appSetting['player.soundEffect.biquadFilter.hz31'],
|
||||
hz62: appSetting['player.soundEffect.biquadFilter.hz62'],
|
||||
hz125: appSetting['player.soundEffect.biquadFilter.hz125'],
|
||||
hz250: appSetting['player.soundEffect.biquadFilter.hz250'],
|
||||
hz500: appSetting['player.soundEffect.biquadFilter.hz500'],
|
||||
hz1000: appSetting['player.soundEffect.biquadFilter.hz1000'],
|
||||
hz2000: appSetting['player.soundEffect.biquadFilter.hz2000'],
|
||||
hz4000: appSetting['player.soundEffect.biquadFilter.hz4000'],
|
||||
hz8000: appSetting['player.soundEffect.biquadFilter.hz8000'],
|
||||
hz16000: appSetting['player.soundEffect.biquadFilter.hz16000'],
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.newPreset {
|
||||
position: relative;
|
||||
border: 1px dashed var(--color-primary-font-hover);
|
||||
// background-color: var(--color-main-background);
|
||||
color: var(--color-primary-font-hover);
|
||||
opacity: .7;
|
||||
height: 22px;
|
||||
|
||||
&.editing {
|
||||
opacity: 1;
|
||||
width: 90px;
|
||||
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
.newPresetInput {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.svg-icon {
|
||||
vertical-align: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.newPresetInput {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// line-height: 16px;
|
||||
background: none !important;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
padding: 0 3px;
|
||||
border-radius: 0;
|
||||
display: none;
|
||||
&::placeholder {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div :class="$style.contnet">
|
||||
<h3 class="player__sound_effect_title">{{ $t('player__sound_effect_convolution') }}</h3>
|
||||
<div :class="$style.convolution">
|
||||
<div :class="$style.convolutionList">
|
||||
<base-checkbox
|
||||
v-for="item in convolutions"
|
||||
:id="`player__convolution_${item.name}`"
|
||||
:key="item.name"
|
||||
:class="$style.checkbox"
|
||||
:model-value="appSetting['player.soundEffect.convolution.fileName']"
|
||||
:label="$t(`player__sound_effect_convolution_file_${item.name}`)"
|
||||
:value="item.source"
|
||||
@update:model-value="updateConvolution($event)"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.sliderList">
|
||||
<div :class="$style.sliderItem">
|
||||
<span :class="$style.label">{{ $t('player__sound_effect_convolution_main_gain') }}</span>
|
||||
<base-slider-bar :class="$style.slider" :value="appSetting['player.soundEffect.convolution.mainGain']" :min="0" :max="50" @change="handleUpdateMainGain" />
|
||||
<span :class="[$style.value]">{{ appSetting['player.soundEffect.convolution.mainGain'] * 10 }}%</span>
|
||||
</div>
|
||||
<div :class="$style.sliderItem">
|
||||
<span :class="$style.label">{{ $t('player__sound_effect_convolution_send_gain') }}</span>
|
||||
<base-slider-bar :class="$style.slider" :value="appSetting['player.soundEffect.convolution.sendGain']" :min="0" :max="50" @change="handleUpdateSendGain" />
|
||||
<span :class="[$style.value]">{{ appSetting['player.soundEffect.convolution.sendGain'] * 10 }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['scroll', $style.saveList]">
|
||||
<base-btn v-for="item in userPresetList" :key="item.id" min @click="handleSetPreset(item)" @contextmenu="handleRemovePreset(item.id)">{{ item.name }}</base-btn>
|
||||
<AddConvolutionPresetBtn v-if="userPresetList.length < 31" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { convolutions } from '@renderer/plugins/player'
|
||||
import AddConvolutionPresetBtn from './AddConvolutionPresetBtn'
|
||||
import { getUserConvolutionPresetList, removeUserConvolutionPreset } from '@renderer/store/soundEffect'
|
||||
|
||||
const updateConvolution = val => {
|
||||
const target = convolutions.find(c => c.source == val)
|
||||
const setting = {
|
||||
'player.soundEffect.convolution.fileName': val,
|
||||
}
|
||||
if (target) {
|
||||
setting['player.soundEffect.convolution.mainGain'] = target.mainGain * 10
|
||||
setting['player.soundEffect.convolution.sendGain'] = target.sendGain * 10
|
||||
}
|
||||
updateSetting(setting)
|
||||
}
|
||||
|
||||
const handleUpdateMainGain = (value) => {
|
||||
updateSetting({ 'player.soundEffect.convolution.mainGain': Math.round(value) })
|
||||
}
|
||||
const handleUpdateSendGain = (value) => {
|
||||
updateSetting({ 'player.soundEffect.convolution.sendGain': Math.round(value) })
|
||||
}
|
||||
|
||||
const handleSetPreset = (item) => {
|
||||
updateSetting({
|
||||
'player.soundEffect.convolution.fileName': item.source,
|
||||
'player.soundEffect.convolution.mainGain': item.mainGain,
|
||||
'player.soundEffect.convolution.sendGain': item.sendGain,
|
||||
})
|
||||
}
|
||||
const userPresetList = ref([])
|
||||
const handleRemovePreset = id => {
|
||||
removeUserConvolutionPreset(id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getUserConvolutionPresetList().then(list => {
|
||||
userPresetList.value = list
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.contnet {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 3px;
|
||||
min-height: 0;
|
||||
}
|
||||
.convolution {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.convolutionList {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.checkbox {
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sliderList {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.sliderItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.slider {
|
||||
flex: auto;
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
// width: 50px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.value {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-font);
|
||||
}
|
||||
}
|
||||
.saveList {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
126
src/renderer/components/common/SoundEffectBtn/AudioPanner.vue
Normal file
126
src/renderer/components/common/SoundEffectBtn/AudioPanner.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div :class="$style.contnet">
|
||||
<div class="player__sound_effect_title" :class="$style.header">
|
||||
<h3>{{ $t('player__sound_effect_panner') }}</h3>
|
||||
<base-checkbox
|
||||
id="player__sound_effect_panner_enabled"
|
||||
:class="$style.checkbox"
|
||||
:label="$t('player__sound_effect_panner_enabled')"
|
||||
:model-value="appSetting['player.soundEffect.panner.enable']"
|
||||
@update:model-value="updateEnabled"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.eqList">
|
||||
<div :class="$style.eqItem">
|
||||
<span :class="$style.label">{{ $t('player__sound_effect_panner_sound_speed') }}</span>
|
||||
<base-slider-bar :class="$style.slider" :value="appSetting['player.soundEffect.panner.speed']" :min="1" :max="50" @change="handleUpdateSpeed" />
|
||||
<span :class="[$style.value, { [$style.active]: appSetting['player.soundEffect.panner.speed'] != 25 }]">{{ appSetting['player.soundEffect.panner.speed'] }}</span>
|
||||
</div>
|
||||
<div :class="$style.eqItem">
|
||||
<span :class="$style.label">{{ $t('player__sound_effect_panner_sound_r') }}</span>
|
||||
<base-slider-bar :class="$style.slider" :value="appSetting['player.soundEffect.panner.soundR']" :min="1" :max="30" @change="handleUpdateSoundR" />
|
||||
<span :class="[$style.value, { [$style.active]: appSetting['player.soundEffect.panner.soundR'] != 5 }]">{{ appSetting['player.soundEffect.panner.soundR'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import { reactive } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
// const setting = reactive({
|
||||
// enabled: false,
|
||||
// soundR: 5,
|
||||
// speed: 25,
|
||||
// })
|
||||
|
||||
const updateEnabled = (enabled) => {
|
||||
// console.log(enabled)
|
||||
updateSetting({ 'player.soundEffect.panner.enable': enabled })
|
||||
}
|
||||
|
||||
const handleUpdateSoundR = (value) => {
|
||||
updateSetting({ 'player.soundEffect.panner.soundR': Math.round(value) })
|
||||
}
|
||||
const handleUpdateSpeed = (value) => {
|
||||
updateSetting({ 'player.soundEffect.panner.speed': Math.round(value) })
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.contnet {
|
||||
padding-top: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 8px;
|
||||
&:before {
|
||||
.mixin-after;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
border-top: 1px dashed var(--color-primary-light-100-alpha-700);
|
||||
}
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 5px;
|
||||
// padding-top: 5px;
|
||||
}
|
||||
.eqList {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.eqItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
// width: 50px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.value {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-font);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
// justify-content: space-between;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// font-size: 13px;
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
</style>
|
||||
163
src/renderer/components/common/SoundEffectBtn/BiquadFilter.vue
Normal file
163
src/renderer/components/common/SoundEffectBtn/BiquadFilter.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div :class="$style.contnet">
|
||||
<div class="player__sound_effect_title" :class="$style.header">
|
||||
<h3>{{ $t('player__sound_effect_biquad_filter') }}</h3>
|
||||
<base-btn min @click="handleReset">{{ $t('player__sound_effect_biquad_filter_reset_btn') }}</base-btn>
|
||||
</div>
|
||||
<div :class="$style.eqList">
|
||||
<div v-for="(v, i) in freqs" :key="v" :class="$style.eqItem">
|
||||
<span :class="$style.label">{{ labels[i] }}</span>
|
||||
<base-slider-bar :class="$style.slider" :value="appSetting[`player.soundEffect.biquadFilter.hz${v}`]" :min="-15" :max="15" @change="handleUpdate(v, $event)" />
|
||||
<span :class="$style.value">{{ appSetting[`player.soundEffect.biquadFilter.hz${v}`] }}db</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['scroll', $style.saveList]">
|
||||
<!-- <base-btn min @click="handleSetPreset(item)">{{ $t(`player__sound_effect_biquad_filter_preset_slow`) }}</base-btn> -->
|
||||
<base-btn v-for="item in freqsPreset" :key="item.name" min @click="handleSetPreset(item)">{{ $t(`player__sound_effect_biquad_filter_preset_${item.name}`) }}</base-btn>
|
||||
<base-btn v-for="item in userPresetList" :key="item.id" min @click="handleSetPreset(item)" @contextmenu="handleRemovePreset(item.id)">{{ item.name }}</base-btn>
|
||||
<AddEQPresetBtn v-if="userPresetList.length < 31" />
|
||||
</div>
|
||||
<!-- <div :class="$style.footer">
|
||||
<base-btn min @click="handleReset">{{ $t('player__sound_effect_biquad_filter_reset_btn') }}</base-btn>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from '@common/utils/vueTools'
|
||||
import { freqs, freqsPreset } from '@renderer/plugins/player'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import AddEQPresetBtn from './AddEQPresetBtn'
|
||||
import { getUserEQPresetList, removeUserEQPreset } from '@renderer/store/soundEffect'
|
||||
|
||||
const labels = freqs.map(num => num < 1000 ? num : `${num / 1000}k`)
|
||||
|
||||
const handleUpdate = (key, value) => {
|
||||
value = Math.round(value)
|
||||
// values[index] = value
|
||||
updateSetting({ [`player.soundEffect.biquadFilter.hz${key}`]: value })
|
||||
// console.log(index, event.target.value, bfs)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
const setting = {}
|
||||
for (const key of freqs) {
|
||||
setting[`player.soundEffect.biquadFilter.hz${key}`] = 0
|
||||
}
|
||||
updateSetting(setting)
|
||||
}
|
||||
|
||||
const handleSetPreset = (item) => {
|
||||
updateSetting({
|
||||
'player.soundEffect.biquadFilter.hz31': item.hz31,
|
||||
'player.soundEffect.biquadFilter.hz62': item.hz62,
|
||||
'player.soundEffect.biquadFilter.hz125': item.hz125,
|
||||
'player.soundEffect.biquadFilter.hz250': item.hz250,
|
||||
'player.soundEffect.biquadFilter.hz500': item.hz500,
|
||||
'player.soundEffect.biquadFilter.hz1000': item.hz1000,
|
||||
'player.soundEffect.biquadFilter.hz2000': item.hz2000,
|
||||
'player.soundEffect.biquadFilter.hz4000': item.hz4000,
|
||||
'player.soundEffect.biquadFilter.hz8000': item.hz8000,
|
||||
'player.soundEffect.biquadFilter.hz16000': item.hz16000,
|
||||
})
|
||||
}
|
||||
|
||||
const userPresetList = ref([])
|
||||
|
||||
const handleRemovePreset = id => {
|
||||
removeUserEQPreset(id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getUserEQPresetList().then(list => {
|
||||
userPresetList.value = list
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.contnet {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 5px;
|
||||
// padding-top: 5px;
|
||||
}
|
||||
.eqList {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
// gap: 15px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
.mixin-after;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
border-left: 1px dashed var(--color-primary-light-100-alpha-700);
|
||||
}
|
||||
}
|
||||
.eqItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 50%;
|
||||
gap: 8px;
|
||||
margin-bottom: 15px;
|
||||
box-sizing: border-box;
|
||||
&:nth-child(odd) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
&:nth-child(even) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
&:nth-last-child(1), &:nth-last-child(2) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.value {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
// justify-content: space-between;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// font-size: 13px;
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.saveList {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
134
src/renderer/components/common/SoundEffectBtn/index.vue
Normal file
134
src/renderer/components/common/SoundEffectBtn/index.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<button :class="$style.btn" :aria-label="$t('player__sound_effect')" @click="visible = true">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="90%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-tune-variant" />
|
||||
</svg>
|
||||
</button>
|
||||
<material-modal :show="visible" bg-close="bg-close" :teleport="teleport" @close="visible = false">
|
||||
<!-- <main :class="$style.main"> -->
|
||||
<!-- <h2 :class="$style.title">{{ $t('theme_edit_modal__title') }}</h2> -->
|
||||
<div :class="$style.content">
|
||||
<div :class="$style.row">
|
||||
<AudioConvolution />
|
||||
<AudioPanner />
|
||||
</div>
|
||||
<div :class="$style.row">
|
||||
<BiquadFilter />
|
||||
</div>
|
||||
</div>
|
||||
<!-- </main> -->
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
// import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
|
||||
// import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
|
||||
// import { musicInfo, playMusicInfo } from '@renderer/store/player/state'
|
||||
// import { saveVolumeIsMute } from '@renderer/store/setting'
|
||||
// import { volume, isMute } from '@renderer/store/player/volume'
|
||||
// import fs from 'node:fs'
|
||||
import BiquadFilter from './BiquadFilter'
|
||||
import AudioPanner from './AudioPanner'
|
||||
import AudioConvolution from './AudioConvolution'
|
||||
|
||||
defineProps({
|
||||
teleport: {
|
||||
type: String,
|
||||
default: '#root',
|
||||
},
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.btn {
|
||||
position: relative;
|
||||
// color: var(--color-button-font);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: color @transition-normal;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
padding: 0;
|
||||
|
||||
svg {
|
||||
transition: opacity @transition-fast;
|
||||
opacity: .6;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
opacity: .9;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
svg {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
min-width: 300px;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
}
|
||||
// .title {
|
||||
// flex: none;
|
||||
// font-size: 16px;
|
||||
// color: var(--color-font);
|
||||
// line-height: 1.3;
|
||||
// text-align: center;
|
||||
// padding: 10px;
|
||||
// }
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 0 15px;
|
||||
margin: 15px 0;
|
||||
gap: 30px;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
|
||||
&:before {
|
||||
.mixin-after;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
border-left: 1px dashed var(--color-primary-light-100-alpha-700);
|
||||
}
|
||||
// width: 400px;
|
||||
|
||||
:global {
|
||||
// .player__sound_effect_contnet {
|
||||
// display: flex;
|
||||
// }
|
||||
.player__sound_effect_title {
|
||||
// margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -137,8 +137,8 @@ export default {
|
||||
// padding: 18px 3px;
|
||||
// margin: 5px 0;
|
||||
// border-left: 5px solid transparent;
|
||||
transition: @transition-normal;
|
||||
transition-property: color;
|
||||
transition: @transition-fast;
|
||||
transition-property: background-color, opacity;
|
||||
color: var(--color-nav-font);
|
||||
cursor: pointer;
|
||||
font-size: 11.5px;
|
||||
@@ -148,17 +148,30 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
transition: 0.3s ease;
|
||||
transition-property: background-color, opacity;
|
||||
// border-radius: @radius-border;
|
||||
.mixin-ellipsis-1;
|
||||
&:before {
|
||||
.mixin-after;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background-color: var(--color-primary-dark-200-alpha-700);
|
||||
border-radius: 4px;
|
||||
transform: translateX(-100%);
|
||||
transition: transform @transition-fast;
|
||||
}
|
||||
|
||||
&.active {
|
||||
// border-left-color: @color-theme-active;
|
||||
background-color: var(--color-primary-light-400-alpha-600);
|
||||
background-color: var(--color-primary-light-300-alpha-700);
|
||||
|
||||
&:before {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-light-300-alpha-600);
|
||||
background-color: var(--color-primary-light-300-alpha-800);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,12 +181,12 @@ export default {
|
||||
|
||||
&:not(.active) {
|
||||
opacity: .8;
|
||||
background-color: var(--color-primary-light-500-alpha-600);
|
||||
background-color: var(--color-primary-light-400-alpha-700);
|
||||
}
|
||||
}
|
||||
&:active:not(.active) {
|
||||
opacity: .6;
|
||||
background-color: var(--color-primary-light-200-alpha-600);
|
||||
background-color: var(--color-primary-light-300-alpha-600);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
</div>
|
||||
<div v-if="!isAgreePact" :class="$style.btns">
|
||||
<base-btn :class="$style.btn" @click="handleClose(true)">{{ $t('not_agree') }}</base-btn>
|
||||
<base-btn :class="$style.btn" :disabled="!btnEnable" @click="handleClick()">{{ $t('agree') }} {{ timeStr }}</base-btn>
|
||||
<base-btn :class="$style.btn" :disabled="!btnEnable" @click="handleClick">{{ $t('agree') }} {{ timeStr }}</base-btn>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { quitApp } from '@renderer/utils/ipc'
|
||||
import { checkUpdate, quitApp } from '@renderer/utils/ipc'
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
import { isShowPact } from '@renderer/store'
|
||||
import { appSetting, saveAgreePact } from '@renderer/store/setting'
|
||||
@@ -75,6 +75,8 @@ export default {
|
||||
this.$dialog({
|
||||
message: Buffer.from('e69cace8bdafe4bbb6e5ae8ce585a8e5858de8b4b9e4b894e5bc80e6ba90efbc8ce5a682e69e9ce4bda0e698afe88ab1e992b1e8b4ade4b9b0e79a84efbc8ce8afb7e79bb4e68ea5e7bb99e5b7aee8af84efbc810a0a5468697320736f667477617265206973206672656520616e64206f70656e20736f757263652e', 'hex').toString(),
|
||||
confirmButtonText: Buffer.from('e5a5bde79a8420284f4b29', 'hex').toString(),
|
||||
}).then(() => {
|
||||
checkUpdate()
|
||||
})
|
||||
}, 2e3)
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ div(:class="$style.footerLeftControlBtns")
|
||||
button(:class="[$style.footerLeftControlBtn, {[$style.active]: isShowPlayComment}]" @click="toggleVisibleComment" :aria-label="$t('comment__show')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-comment')
|
||||
common-sound-effect-btn
|
||||
common-playback-rate-btn
|
||||
common-volume-btn
|
||||
common-toggle-play-mode-btn
|
||||
|
||||
@@ -7,15 +7,16 @@ div(:class="$style.container")
|
||||
img( :class="$style.avatar" :src="item.avatar || commentDefImg" @error="handleUserImg")
|
||||
div(:class="$style.right")
|
||||
div(:class="$style.info")
|
||||
div.select(:class="$style.name") {{item.userName}}
|
||||
time(:class="$style.label" v-if="item.timeStr") {{timeFormat(item.timeStr)}}
|
||||
div(:class="$style.label" v-if="item.location") {{item.location}}
|
||||
div(:class="$style.baseInfo")
|
||||
div.select(:class="$style.name") {{item.userName}}
|
||||
div(:class="$style.metaInfo")
|
||||
time(:class="$style.label" v-if="item.timeStr") {{timeFormat(item.timeStr)}}
|
||||
div(:class="$style.label" v-if="item.location") {{$t('comment__location', { location: item.location })}}
|
||||
div(:class="$style.likes" v-if="item.likedCount != null")
|
||||
svg(:class="$style.likesIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
|
||||
use(xlink:href='#icon-thumbs-up')
|
||||
| {{item.likedCount}}
|
||||
div.select(:class="$style.comment_text")
|
||||
p(v-for="text in item.text") {{text}}
|
||||
p.select(:class="$style.comment_text") {{item.text}}
|
||||
div(v-if="item.images?.length" :class="$style.comment_images")
|
||||
img(v-for="url in item.images" :src="url" loading="lazy" decoding="async")
|
||||
comment-floor(v-if="item.reply && item.reply.length" :class="$style.reply_floor" :comments="item.reply")
|
||||
@@ -87,12 +88,27 @@ export default {
|
||||
.info {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: flex-end;
|
||||
min-width: 0;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 1.3;
|
||||
gap: 6px;
|
||||
color: var(--color-450);
|
||||
}
|
||||
.baseInfo {
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.metaInfo {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
min-width: 0;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.name {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
@@ -105,11 +121,11 @@ export default {
|
||||
// margin-left: 5px;
|
||||
}
|
||||
.likes {
|
||||
flex: 1 0 auto;
|
||||
margin-left: 10px;
|
||||
flex: none;
|
||||
font-size: 11px;
|
||||
align-self: flex-end;
|
||||
text-align: right;
|
||||
padding-top: 3px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.likesIcon {
|
||||
width: 12px;
|
||||
@@ -120,12 +136,10 @@ export default {
|
||||
.comment_text {
|
||||
text-align: justify;
|
||||
font-size: 14px;
|
||||
padding-top: 5px;
|
||||
p {
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.comment_images {
|
||||
display: flex;
|
||||
|
||||
@@ -48,12 +48,12 @@ export default {
|
||||
const tipSearch = debounce(async() => {
|
||||
if (searchText.value === '' && prevTempSearchSource) {
|
||||
tipList.value = []
|
||||
music[prevTempSearchSource].tempSearch.cancelTempSearch()
|
||||
music[prevTempSearchSource].tipSearch.cancelTipSearch()
|
||||
return
|
||||
}
|
||||
const { temp_source } = await getSearchSetting()
|
||||
prevTempSearchSource = temp_source
|
||||
music[prevTempSearchSource].tempSearch.search(searchText.value).then(list => {
|
||||
music[prevTempSearchSource].tipSearch.search(searchText.value).then(list => {
|
||||
tipList.value = list
|
||||
}).catch(() => {})
|
||||
}, 50)
|
||||
|
||||
@@ -203,9 +203,10 @@ export default {
|
||||
transition: box-shadow .4s ease, background-color @transition-normal;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
background-color: var(--color-primary-light-600-alpha-100);
|
||||
background-color: var(--color-primary-light-300-alpha-700);
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-primary-light-600-alpha-100);
|
||||
box-shadow: 0 1px 5px 0 rgba(0,0,0,.2);
|
||||
.form {
|
||||
input {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@renderer/utils/ipc'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { langS2T, toNewMusicInfo, toOldMusicInfo } from '@renderer/utils'
|
||||
import { requestMsg } from '@renderer/utils/message'
|
||||
|
||||
|
||||
const getOtherSourcePromises = new Map()
|
||||
@@ -187,6 +188,7 @@ export const getOnlineOtherSourceMusicUrl = async({ musicInfos, quality, onToggl
|
||||
return { musicInfo, url, quality: type, isFromCache: false }
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
}).catch((err: any) => {
|
||||
if (err.message == requestMsg.tooManyRequests) throw err
|
||||
console.log(err)
|
||||
return getOnlineOtherSourceMusicUrl({ musicInfos, quality, onToggleSource, isRefresh, retryedSource })
|
||||
})
|
||||
@@ -220,7 +222,7 @@ export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSourc
|
||||
return { musicInfo, url, quality: type, isFromCache: false }
|
||||
}).catch(async(err: any) => {
|
||||
console.log(err)
|
||||
if (!allowToggleSource) throw err
|
||||
if (!allowToggleSource || err.message == requestMsg.tooManyRequests) throw err
|
||||
onToggleSource()
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await getOtherSource(musicInfo).then(otherSource => {
|
||||
@@ -266,7 +268,7 @@ export const getOnlineOtherSourcePicUrl = async({ musicInfos, onToggleSource, is
|
||||
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = musicSdk[musicInfo.source].getPic(toOldMusicInfo(musicInfo)).promise
|
||||
reqPromise = musicSdk[musicInfo.source].getPic(toOldMusicInfo(musicInfo))
|
||||
} catch (err: any) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
@@ -296,7 +298,7 @@ export const handleGetOnlinePicUrl = async({ musicInfo, isRefresh, onToggleSourc
|
||||
// console.log(musicInfo.source)
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = musicSdk[musicInfo.source].getPic(toOldMusicInfo(musicInfo)).promise
|
||||
reqPromise = musicSdk[musicInfo.source].getPic(toOldMusicInfo(musicInfo))
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
|
||||
@@ -52,12 +52,36 @@ const { addDelayNextTimeout: addLoadTimeout, clearDelayNextTimeout: clearLoadTim
|
||||
* 检查音乐信息是否已更改
|
||||
*/
|
||||
const diffCurrentMusicInfo = (curMusicInfo: LX.Music.MusicInfo | LX.Download.ListItem): boolean => {
|
||||
return curMusicInfo !== playMusicInfo.musicInfo || isPlay.value
|
||||
// return curMusicInfo !== playMusicInfo.musicInfo || isPlay.value
|
||||
return gettingUrlId != curMusicInfo.id || curMusicInfo.id != playMusicInfo.musicInfo?.id || isPlay.value
|
||||
}
|
||||
|
||||
let cancelDelayRetry: (() => void) | null = null
|
||||
const delayRetry = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false): Promise<string | null> => {
|
||||
// if (cancelDelayRetry) cancelDelayRetry()
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
const time = getRandom(2, 6)
|
||||
setAllStatus(window.i18n.t('player__geting_url_delay_retry', { time }))
|
||||
const tiemout = setTimeout(() => {
|
||||
getMusicPlayUrl(musicInfo, isRefresh, true).then((result) => {
|
||||
cancelDelayRetry = null
|
||||
resolve(result)
|
||||
}).catch(async(err: any) => {
|
||||
cancelDelayRetry = null
|
||||
reject(err)
|
||||
})
|
||||
}, time * 1000)
|
||||
cancelDelayRetry = () => {
|
||||
clearTimeout(tiemout)
|
||||
cancelDelayRetry = null
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false, isRetryed = false): Promise<string | null> => {
|
||||
// this.musicInfo.url = await getMusicPlayUrl(targetSong, type)
|
||||
setAllStatus(window.i18n.t('player__geting_url'))
|
||||
if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
|
||||
// const type = getPlayType(appSetting['player.highQuality'], musicInfo)
|
||||
|
||||
@@ -79,6 +103,8 @@ const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListIt
|
||||
diffCurrentMusicInfo(musicInfo) ||
|
||||
err.message == requestMsg.cancelRequest) return null
|
||||
|
||||
if (err.message == requestMsg.tooManyRequests) return delayRetry(musicInfo, isRefresh)
|
||||
|
||||
if (!isRetryed) return getMusicPlayUrl(musicInfo, isRefresh, true)
|
||||
|
||||
throw err
|
||||
@@ -86,7 +112,9 @@ const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListIt
|
||||
}
|
||||
|
||||
export const setMusicUrl = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh?: boolean) => {
|
||||
if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
// if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
if (!diffCurrentMusicInfo(musicInfo)) return
|
||||
if (cancelDelayRetry) cancelDelayRetry()
|
||||
gettingUrlId = musicInfo.id
|
||||
void getMusicPlayUrl(musicInfo, isRefresh).then((url) => {
|
||||
if (!url) return
|
||||
@@ -152,8 +180,7 @@ const handlePlay = () => {
|
||||
}
|
||||
const musicInfo = playMusicInfo.musicInfo
|
||||
|
||||
if (!musicInfo || gettingUrlId == musicInfo.id) return
|
||||
gettingUrlId &&= ''
|
||||
if (!musicInfo) return
|
||||
|
||||
setStop()
|
||||
window.app_event.pause()
|
||||
|
||||
@@ -67,7 +67,7 @@ export default () => {
|
||||
sendInited()
|
||||
|
||||
handleListAutoUpdate()
|
||||
if (window.lx.isProd) checkUpdate()
|
||||
if (window.lx.isProd && appSetting['common.isAgreePact']) checkUpdate()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
import { onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { getDuration, getPlaybackRate, getCurrentTime } from '@renderer/plugins/player'
|
||||
import { musicInfo } from '@renderer/store/player/state'
|
||||
import { isPlay, musicInfo, playMusicInfo } from '@renderer/store/player/state'
|
||||
import { playProgress } from '@renderer/store/player/playProgress'
|
||||
import { playNext, playPrev, stop } from '@renderer/core/player'
|
||||
// import { } from ''
|
||||
import { pause, play, playNext, playPrev, stop } from '@renderer/core/player'
|
||||
|
||||
export default () => {
|
||||
// 创建一个空白音频以保持对 Media Session 的注册
|
||||
const emptyAudio = new Audio()
|
||||
emptyAudio.autoplay = false
|
||||
emptyAudio.src = require('@renderer/assets/medias/Silence02s.mp3')
|
||||
emptyAudio.controls = false
|
||||
emptyAudio.preload = 'auto'
|
||||
emptyAudio.onplaying = () => {
|
||||
emptyAudio.pause()
|
||||
}
|
||||
void emptyAudio.play()
|
||||
let prevPicUrl = ''
|
||||
|
||||
const updateMediaSessionInfo = () => {
|
||||
if (musicInfo.id == null) {
|
||||
navigator.mediaSession.metadata = null
|
||||
return
|
||||
}
|
||||
const mediaMetadata: MediaMetadata = {
|
||||
title: musicInfo.name,
|
||||
artist: musicInfo.singer,
|
||||
album: musicInfo.album,
|
||||
artwork: [],
|
||||
}
|
||||
if (musicInfo.pic) mediaMetadata.artwork = [{ src: musicInfo.pic }]
|
||||
if (musicInfo.pic) {
|
||||
const pic = new Image()
|
||||
pic.src = prevPicUrl = musicInfo.pic
|
||||
pic.onload = () => {
|
||||
if (prevPicUrl == pic.src) {
|
||||
mediaMetadata.artwork = [{ src: pic.src }]
|
||||
// @ts-expect-error
|
||||
navigator.mediaSession.metadata = new window.MediaMetadata(mediaMetadata)
|
||||
}
|
||||
}
|
||||
} else prevPicUrl = ''
|
||||
|
||||
// @ts-expect-error
|
||||
navigator.mediaSession.metadata = new window.MediaMetadata(mediaMetadata)
|
||||
@@ -48,25 +73,27 @@ export default () => {
|
||||
navigator.mediaSession.playbackState = 'none'
|
||||
}
|
||||
const handleSetPlayInfo = () => {
|
||||
updateMediaSessionInfo()
|
||||
updatePositionState({
|
||||
position: playProgress.nowPlayTime,
|
||||
duration: playProgress.maxPlayTime,
|
||||
emptyAudio.play().finally(() => {
|
||||
updateMediaSessionInfo()
|
||||
updatePositionState({
|
||||
position: playProgress.nowPlayTime,
|
||||
duration: playProgress.maxPlayTime,
|
||||
})
|
||||
handlePause()
|
||||
})
|
||||
handlePause()
|
||||
}
|
||||
|
||||
// const registerMediaSessionHandler = () => {
|
||||
// navigator.mediaSession.setActionHandler('play', () => {
|
||||
// if (this.isPlay || !this.playMusicInfo) return
|
||||
// console.log('play')
|
||||
// this.startPlay()
|
||||
// })
|
||||
// navigator.mediaSession.setActionHandler('pause', () => {
|
||||
// if (!this.isPlay || !this.playMusicInfo) return
|
||||
// console.log('pause')
|
||||
// this.stopPlay()
|
||||
// })
|
||||
navigator.mediaSession.setActionHandler('play', () => {
|
||||
if (isPlay.value || !playMusicInfo) return
|
||||
console.log('play')
|
||||
play()
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('pause', () => {
|
||||
if (!isPlay.value || !playMusicInfo) return
|
||||
console.log('pause')
|
||||
pause()
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('stop', () => {
|
||||
console.log('stop')
|
||||
setStop()
|
||||
@@ -107,6 +134,8 @@ export default () => {
|
||||
window.app_event.on('pause', handlePause)
|
||||
window.app_event.on('stop', handleStop)
|
||||
window.app_event.on('error', handlePause)
|
||||
window.app_event.on('playerEmptied', handleSetPlayInfo)
|
||||
// window.app_event.on('playerLoadstart', handleSetPlayInfo)
|
||||
window.app_event.on('musicToggled', handleSetPlayInfo)
|
||||
window.app_event.on('picUpdated', updateMediaSessionInfo)
|
||||
|
||||
@@ -117,6 +146,8 @@ export default () => {
|
||||
window.app_event.off('pause', handlePause)
|
||||
window.app_event.off('stop', handleStop)
|
||||
window.app_event.off('error', handlePause)
|
||||
window.app_event.off('playerEmptied', handleSetPlayInfo)
|
||||
// window.app_event.off('playerLoadstart', handleSetPlayInfo)
|
||||
window.app_event.off('musicToggled', handleSetPlayInfo)
|
||||
window.app_event.off('picUpdated', updateMediaSessionInfo)
|
||||
})
|
||||
|
||||
@@ -31,6 +31,7 @@ import useWatchList from './useWatchList'
|
||||
import { HOTKEY_PLAYER } from '@common/hotKey'
|
||||
import { playNext, pause, playPrev, togglePlay } from '@renderer/core/player'
|
||||
import usePlaybackRate from './usePlaybackRate'
|
||||
import useSoundEffect from './useSoundEffect'
|
||||
|
||||
|
||||
export default () => {
|
||||
@@ -41,6 +42,7 @@ export default () => {
|
||||
usePlayEvent()
|
||||
useLyric()
|
||||
useVolume()
|
||||
useSoundEffect()
|
||||
usePlaybackRate()
|
||||
useWatchList()
|
||||
|
||||
|
||||
152
src/renderer/core/useApp/usePlayer/useSoundEffect.ts
Normal file
152
src/renderer/core/useApp/usePlayer/useSoundEffect.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { watch } from '@common/utils/vueTools'
|
||||
import {
|
||||
freqs,
|
||||
getAudioContext,
|
||||
getBiquadFilter,
|
||||
setConvolver,
|
||||
setPannerSoundR,
|
||||
setPannerSpeed,
|
||||
startPanner,
|
||||
stopPanner,
|
||||
setConvolverMainGain,
|
||||
setConvolverSendGain,
|
||||
} from '@renderer/plugins/player'
|
||||
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
const cache = new Map<string, AudioBuffer>()
|
||||
const loadBuffer = async(name: string) => new Promise<AudioBuffer>((resolve, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const path = require('@static/medias/filters/' + name) as string
|
||||
if (cache.has(path)) {
|
||||
resolve(cache.get(path) as AudioBuffer)
|
||||
return
|
||||
}
|
||||
// Load buffer asynchronously
|
||||
let request = new XMLHttpRequest()
|
||||
request.open('GET', path, true)
|
||||
request.responseType = 'arraybuffer'
|
||||
|
||||
request.onload = function() {
|
||||
// Asynchronously decode the audio file data in request.response
|
||||
void getAudioContext().decodeAudioData(request.response, (buffer) => {
|
||||
if (!buffer) {
|
||||
reject(new Error('error decoding file data: ' + path))
|
||||
return
|
||||
}
|
||||
cache.set(path, buffer)
|
||||
resolve(buffer)
|
||||
},
|
||||
function(error) {
|
||||
reject(error)
|
||||
console.error('decodeAudioData error', error)
|
||||
})
|
||||
}
|
||||
|
||||
request.onerror = function() {
|
||||
reject(new Error('XHR error'))
|
||||
}
|
||||
|
||||
request.send()
|
||||
})
|
||||
|
||||
export default () => {
|
||||
console.log(appSetting['player.soundEffect.panner.enable'])
|
||||
if (appSetting['player.soundEffect.panner.enable']) startPanner()
|
||||
setPannerSoundR(appSetting['player.soundEffect.panner.soundR'] / 10)
|
||||
setPannerSpeed(2 * (appSetting['player.soundEffect.panner.speed'] / 10))
|
||||
if (freqs.some(v => appSetting[`player.soundEffect.biquadFilter.hz${v}`] != 0)) {
|
||||
const bfs = getBiquadFilter()
|
||||
for (const item of freqs) {
|
||||
bfs.get(`hz${item}`)!.gain.value = appSetting[`player.soundEffect.biquadFilter.hz${item}`]
|
||||
}
|
||||
}
|
||||
if (appSetting['player.soundEffect.convolution.fileName']) {
|
||||
void loadBuffer(appSetting['player.soundEffect.convolution.fileName']).then((buffer) => {
|
||||
setConvolver(buffer, appSetting['player.soundEffect.convolution.mainGain'] / 10, appSetting['player.soundEffect.convolution.sendGain'] / 10)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
watch(() => appSetting['player.soundEffect.panner.enable'], (enable) => {
|
||||
if (enable) {
|
||||
startPanner()
|
||||
} else {
|
||||
stopPanner()
|
||||
}
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.panner.soundR'], (soundR) => {
|
||||
setPannerSoundR(soundR / 10)
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.panner.speed'], (speed) => {
|
||||
setPannerSpeed(2 * (speed / 10))
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.convolution.fileName'], (fileName) => {
|
||||
setTimeout(() => {
|
||||
if (fileName) {
|
||||
void loadBuffer(fileName).then((buffer) => {
|
||||
setConvolver(buffer, appSetting['player.soundEffect.convolution.mainGain'] / 10, appSetting['player.soundEffect.convolution.sendGain'] / 10)
|
||||
})
|
||||
} else {
|
||||
setConvolver(null, 0, 0)
|
||||
}
|
||||
})
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.convolution.mainGain'], (mainGain) => {
|
||||
setConvolverMainGain(mainGain / 10)
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.convolution.sendGain'], (sendGain) => {
|
||||
setConvolverSendGain(sendGain / 10)
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz31'], (hz31) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz31')!.gain.value = hz31
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz62'], (hz62) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz62')!.gain.value = hz62
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz125'], (hz125) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz125')!.gain.value = hz125
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz250'], (hz250) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz250')!.gain.value = hz250
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz500'], (hz500) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz500')!.gain.value = hz500
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz1000'], (hz1000) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz1000')!.gain.value = hz1000
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz2000'], (hz2000) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz2000')!.gain.value = hz2000
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz4000'], (hz4000) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz4000')!.gain.value = hz4000
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz8000'], (hz8000) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz8000')!.gain.value = hz8000
|
||||
})
|
||||
watch(() => appSetting['player.soundEffect.biquadFilter.hz16000'], (hz16000) => {
|
||||
const bfs = getBiquadFilter()
|
||||
bfs.get('hz16000')!.gain.value = hz16000
|
||||
})
|
||||
|
||||
|
||||
// window.key_event.on(HOTKEY_PLAYER.volume_up.action, hotkeyVolumeUp)
|
||||
// window.key_event.on(HOTKEY_PLAYER.volume_down.action, hotkeyVolumeDown)
|
||||
// window.app_event.on('setPlaybackRate', handleSetPlaybackRate)
|
||||
|
||||
// onBeforeUnmount(() => {
|
||||
// // window.key_event.off(HOTKEY_PLAYER.volume_up.action, hotkeyVolumeUp)
|
||||
// // window.key_event.off(HOTKEY_PLAYER.volume_down.action, hotkeyVolumeDown)
|
||||
// window.app_event.off('setPlaybackRate', handleSetPlaybackRate)
|
||||
// })
|
||||
}
|
||||
@@ -2,6 +2,50 @@ let audio: HTMLAudioElement | null = null
|
||||
let audioContext: AudioContext
|
||||
let mediaSource: MediaElementAudioSourceNode
|
||||
let analyser: AnalyserNode
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext
|
||||
// https://benzleung.gitbooks.io/web-audio-api-mini-guide/content/chapter5-1.html
|
||||
export const freqs = [31, 62, 125, 250, 500, 1000, 2000, 4000, 8000, 16000] as const
|
||||
type Freqs = (typeof freqs)[number]
|
||||
let biquads: Map<`hz${Freqs}`, BiquadFilterNode>
|
||||
export const freqsPreset = [
|
||||
{ name: 'pop', hz31: 6, hz62: 5, hz125: -3, hz250: -2, hz500: 5, hz1000: 4, hz2000: -4, hz4000: -3, hz8000: 6, hz16000: 4 },
|
||||
{ name: 'dance', hz31: 4, hz62: 3, hz125: -4, hz250: -6, hz500: 0, hz1000: 0, hz2000: 3, hz4000: 4, hz8000: 4, hz16000: 5 },
|
||||
{ name: 'rock', hz31: 7, hz62: 6, hz125: 2, hz250: 1, hz500: -3, hz1000: -4, hz2000: 2, hz4000: 1, hz8000: 4, hz16000: 5 },
|
||||
{ name: 'classical', hz31: 6, hz62: 7, hz125: 1, hz250: 2, hz500: -1, hz1000: 1, hz2000: -4, hz4000: -6, hz8000: -7, hz16000: -8 },
|
||||
{ name: 'vocal', hz31: -5, hz62: -6, hz125: -4, hz250: -3, hz500: 3, hz1000: 4, hz2000: 5, hz4000: 4, hz8000: -3, hz16000: -3 },
|
||||
{ name: 'slow', hz31: 5, hz62: 4, hz125: 2, hz250: 0, hz500: -2, hz1000: 0, hz2000: 3, hz4000: 6, hz8000: 7, hz16000: 8 },
|
||||
{ name: 'electronic', hz31: 6, hz62: 5, hz125: 0, hz250: -5, hz500: -4, hz1000: 0, hz2000: 6, hz4000: 8, hz8000: 8, hz16000: 7 },
|
||||
{ name: 'subwoofer', hz31: 8, hz62: 7, hz125: 5, hz250: 4, hz500: 0, hz1000: 0, hz2000: 0, hz4000: 0, hz8000: 0, hz16000: 0 },
|
||||
{ name: 'soft', hz31: -5, hz62: -5, hz125: -4, hz250: -4, hz500: 3, hz1000: 2, hz2000: 4, hz4000: 4, hz8000: 0, hz16000: 0 },
|
||||
] as const
|
||||
export const convolutions = [
|
||||
{ name: 'telephone', mainGain: 0.0, sendGain: 3.0, source: 'filter-telephone.wav' }, // 电话
|
||||
{ name: 's2_r4_bd', mainGain: 1.8, sendGain: 0.9, source: 's2_r4_bd.wav' }, // 教堂
|
||||
{ name: 'bright_hall', mainGain: 0.8, sendGain: 2.4, source: 'bright-hall.wav' },
|
||||
{ name: 'cinema_diningroom', mainGain: 0.6, sendGain: 2.3, source: 'cinema-diningroom.wav' },
|
||||
{ name: 'dining_living_true_stereo', mainGain: 0.6, sendGain: 1.8, source: 'dining-living-true-stereo.wav' },
|
||||
{ name: 'living_bedroom_leveled', mainGain: 0.6, sendGain: 2.1, source: 'living-bedroom-leveled.wav' },
|
||||
{ name: 'spreader50_65ms', mainGain: 1, sendGain: 2.5, source: 'spreader50-65ms.wav' },
|
||||
// { name: 'spreader25_125ms', mainGain: 1, sendGain: 2.5, source: 'spreader25-125ms.wav' },
|
||||
// { name: 'backslap', mainGain: 1.8, sendGain: 0.8, source: 'backslap1.wav' },
|
||||
{ name: 's3_r1_bd', mainGain: 1.8, sendGain: 0.8, source: 's3_r1_bd.wav' },
|
||||
{ name: 'matrix_1', mainGain: 1.5, sendGain: 0.9, source: 'matrix-reverb1.wav' },
|
||||
{ name: 'matrix_2', mainGain: 1.3, sendGain: 1, source: 'matrix-reverb2.wav' },
|
||||
{ name: 'cardiod_35_10_spread', mainGain: 1.8, sendGain: 0.6, source: 'cardiod-35-10-spread.wav' },
|
||||
{ name: 'tim_omni_35_10_magnetic', mainGain: 1, sendGain: 0.2, source: 'tim-omni-35-10-magnetic.wav' },
|
||||
// { name: 'spatialized', mainGain: 1.8, sendGain: 0.8, source: 'spatialized8.wav' },
|
||||
// { name: 'zing_long_stereo', mainGain: 0.8, sendGain: 1.8, source: 'zing-long-stereo.wav' },
|
||||
{ name: 'feedback_spring', mainGain: 1.8, sendGain: 0.8, source: 'feedback-spring.wav' },
|
||||
// { name: 'tim_omni_rear_blend', mainGain: 1.8, sendGain: 0.8, source: 'tim-omni-rear-blend.wav' },
|
||||
] as const
|
||||
let convolver: ConvolverNode
|
||||
let convolverSourceGainNode: GainNode
|
||||
let convolverOutputGainNode: GainNode
|
||||
let convolverDynamicsCompressor: DynamicsCompressorNode
|
||||
let gainNode: GainNode
|
||||
let panner: PannerNode
|
||||
export const soundR = 0.5
|
||||
|
||||
|
||||
export const createAudio = () => {
|
||||
if (audio) return
|
||||
@@ -11,21 +55,168 @@ export const createAudio = () => {
|
||||
audio.preload = 'auto'
|
||||
}
|
||||
|
||||
export const getAnalyser = (): AnalyserNode | null => {
|
||||
if (!audio) throw new Error('audio not defined')
|
||||
const initAnalyser = () => {
|
||||
analyser = audioContext.createAnalyser()
|
||||
analyser.fftSize = 256
|
||||
}
|
||||
|
||||
if (audioContext == null) {
|
||||
audioContext = new window.AudioContext()
|
||||
mediaSource = audioContext.createMediaElementSource(audio)
|
||||
analyser = audioContext.createAnalyser()
|
||||
analyser.fftSize = 256
|
||||
mediaSource.connect(analyser)
|
||||
analyser.connect(audioContext.destination)
|
||||
const initBiquadFilter = () => {
|
||||
biquads = new Map()
|
||||
let i
|
||||
|
||||
for (const item of freqs) {
|
||||
const filter = audioContext.createBiquadFilter()
|
||||
biquads.set(`hz${item}`, filter)
|
||||
filter.type = 'peaking'
|
||||
filter.frequency.value = item
|
||||
filter.Q.value = 1.4
|
||||
filter.gain.value = 0
|
||||
}
|
||||
|
||||
for (i = 1; i < freqs.length; i++) {
|
||||
(biquads.get(`hz${freqs[i - 1]}`) as BiquadFilterNode).connect(biquads.get(`hz${freqs[i]}`) as BiquadFilterNode)
|
||||
}
|
||||
}
|
||||
|
||||
const initConvolver = () => {
|
||||
convolverSourceGainNode = audioContext.createGain()
|
||||
convolverOutputGainNode = audioContext.createGain()
|
||||
convolverDynamicsCompressor = audioContext.createDynamicsCompressor()
|
||||
convolver = audioContext.createConvolver()
|
||||
convolver.connect(convolverOutputGainNode)
|
||||
convolverSourceGainNode.connect(convolverDynamicsCompressor)
|
||||
convolverOutputGainNode.connect(convolverDynamicsCompressor)
|
||||
}
|
||||
|
||||
const initPanner = () => {
|
||||
panner = audioContext.createPanner()
|
||||
}
|
||||
|
||||
const initGain = () => {
|
||||
gainNode = audioContext.createGain()
|
||||
}
|
||||
|
||||
const initAdvancedAudioFeatures = () => {
|
||||
if (audioContext) return
|
||||
if (!audio) throw new Error('audio not defined')
|
||||
audioContext = new window.AudioContext()
|
||||
mediaSource = audioContext.createMediaElementSource(audio)
|
||||
|
||||
initAnalyser()
|
||||
mediaSource.connect(analyser)
|
||||
// analyser.connect(audioContext.destination)
|
||||
|
||||
initBiquadFilter()
|
||||
analyser.connect(biquads.get(`hz${freqs[0]}`) as BiquadFilterNode)
|
||||
|
||||
initConvolver()
|
||||
const lastBiquadFilter = (biquads.get(`hz${freqs.at(-1) as Freqs}`) as BiquadFilterNode)
|
||||
lastBiquadFilter.connect(convolverSourceGainNode)
|
||||
lastBiquadFilter.connect(convolver)
|
||||
|
||||
initPanner()
|
||||
convolverDynamicsCompressor.connect(panner)
|
||||
|
||||
initGain()
|
||||
panner.connect(gainNode)
|
||||
|
||||
gainNode.connect(audioContext.destination)
|
||||
}
|
||||
|
||||
export const getAudioContext = () => {
|
||||
initAdvancedAudioFeatures()
|
||||
return audioContext
|
||||
}
|
||||
|
||||
|
||||
export const getAnalyser = (): AnalyserNode | null => {
|
||||
initAdvancedAudioFeatures()
|
||||
return analyser
|
||||
}
|
||||
|
||||
export const hasInitedAnalyser = (): boolean => audioContext != null
|
||||
export const getBiquadFilter = () => {
|
||||
initAdvancedAudioFeatures()
|
||||
return biquads
|
||||
}
|
||||
|
||||
// let isConvolverConnected = false
|
||||
export const setConvolver = (buffer: AudioBuffer | null, mainGain: number, sendGain: number) => {
|
||||
initAdvancedAudioFeatures()
|
||||
convolver.buffer = buffer
|
||||
// console.log(mainGain, sendGain)
|
||||
if (buffer) {
|
||||
convolverSourceGainNode.gain.value = mainGain
|
||||
convolverOutputGainNode.gain.value = sendGain
|
||||
} else {
|
||||
convolverSourceGainNode.gain.value = 1
|
||||
convolverOutputGainNode.gain.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
export const setConvolverMainGain = (gain: number) => {
|
||||
if (convolverSourceGainNode.gain.value == gain) return
|
||||
// console.log(gain)
|
||||
convolverSourceGainNode.gain.value = gain
|
||||
}
|
||||
|
||||
export const setConvolverSendGain = (gain: number) => {
|
||||
if (convolverOutputGainNode.gain.value == gain) return
|
||||
// console.log(gain)
|
||||
convolverOutputGainNode.gain.value = gain
|
||||
}
|
||||
|
||||
let pannerInfo = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
soundR: 0.5,
|
||||
rad: 0,
|
||||
speed: 1,
|
||||
intv: null as NodeJS.Timeout | null,
|
||||
}
|
||||
const setPannerXYZ = (nx: number, ny: number, nz: number) => {
|
||||
pannerInfo.x = nx
|
||||
pannerInfo.y = ny
|
||||
pannerInfo.z = nz
|
||||
// console.log(pannerInfo)
|
||||
panner.positionX.value = nx * pannerInfo.soundR
|
||||
panner.positionY.value = ny * pannerInfo.soundR
|
||||
panner.positionZ.value = nz * pannerInfo.soundR
|
||||
}
|
||||
export const setPannerSoundR = (r: number) => {
|
||||
pannerInfo.soundR = r
|
||||
}
|
||||
|
||||
export const setPannerSpeed = (speed: number) => {
|
||||
pannerInfo.speed = speed
|
||||
if (pannerInfo.intv) startPanner()
|
||||
}
|
||||
export const stopPanner = () => {
|
||||
if (pannerInfo.intv) {
|
||||
clearInterval(pannerInfo.intv)
|
||||
pannerInfo.intv = null
|
||||
pannerInfo.rad = 0
|
||||
}
|
||||
panner.positionX.value = 0
|
||||
panner.positionY.value = 0
|
||||
panner.positionZ.value = 0
|
||||
}
|
||||
|
||||
export const startPanner = () => {
|
||||
initAdvancedAudioFeatures()
|
||||
if (pannerInfo.intv) {
|
||||
clearInterval(pannerInfo.intv)
|
||||
pannerInfo.intv = null
|
||||
pannerInfo.rad = 0
|
||||
}
|
||||
pannerInfo.intv = setInterval(() => {
|
||||
pannerInfo.rad += 1
|
||||
if (pannerInfo.rad > 360) pannerInfo.rad -= 360
|
||||
setPannerXYZ(Math.sin(pannerInfo.rad * Math.PI / 180), Math.cos(pannerInfo.rad * Math.PI / 180), Math.cos(pannerInfo.rad * Math.PI / 180))
|
||||
}, pannerInfo.speed * 10)
|
||||
}
|
||||
|
||||
export const hasInitedAdvancedAudioFeatures = (): boolean => audioContext != null
|
||||
|
||||
export const setResource = (src: string) => {
|
||||
if (audio) audio.src = src
|
||||
|
||||
@@ -5,12 +5,14 @@ export const allMusicList: Map<string, LX.Music.MusicInfo[]> = markRaw(new Map()
|
||||
|
||||
export const defaultList = markRaw<LX.List.MyDefaultListInfo>({
|
||||
id: LIST_IDS.DEFAULT,
|
||||
name: '试听列表',
|
||||
name: 'list__name_default',
|
||||
// name: '试听列表',
|
||||
})
|
||||
|
||||
export const loveList = markRaw<LX.List.MyLoveListInfo>({
|
||||
id: LIST_IDS.LOVE,
|
||||
name: '我的收藏',
|
||||
name: 'list__name_love',
|
||||
// name: '我的收藏',
|
||||
})
|
||||
export const tempList = markRaw<LX.List.MyTempListInfo>({
|
||||
id: LIST_IDS.TEMP,
|
||||
|
||||
@@ -37,6 +37,7 @@ type Tags = Partial<Record<LX.OnlineSource, TagInfo>>
|
||||
|
||||
export const tags = shallowReactive<Tags>({})
|
||||
|
||||
|
||||
export declare interface ListInfoItem {
|
||||
play_count: string
|
||||
id: string
|
||||
|
||||
56
src/renderer/store/soundEffect.ts
Normal file
56
src/renderer/store/soundEffect.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { reactive, toRaw } from '@common/utils/vueTools'
|
||||
import { getUserSoundEffectConvolutionPresetList, getUserSoundEffectEQPresetList, saveUserSoundEffectConvolutionPresetList, saveUserSoundEffectEQPresetList } from '@renderer/utils/ipc'
|
||||
|
||||
let userEqPresetList: LX.SoundEffect.EQPreset[] | null = null
|
||||
|
||||
export const getUserEQPresetList = async() => {
|
||||
if (userEqPresetList == null) {
|
||||
userEqPresetList = reactive(await getUserSoundEffectEQPresetList())
|
||||
}
|
||||
return userEqPresetList
|
||||
}
|
||||
export const saveUserEQPreset = async(preset: LX.SoundEffect.EQPreset) => {
|
||||
if (userEqPresetList == null) {
|
||||
userEqPresetList = reactive(await getUserSoundEffectEQPresetList())
|
||||
}
|
||||
const target = userEqPresetList.find(p => p.id == preset.id)
|
||||
if (target) Object.assign(target, preset)
|
||||
else userEqPresetList.push(preset)
|
||||
saveUserSoundEffectEQPresetList(toRaw(userEqPresetList))
|
||||
}
|
||||
export const removeUserEQPreset = async(id: string) => {
|
||||
if (userEqPresetList == null) {
|
||||
userEqPresetList = reactive(await getUserSoundEffectEQPresetList())
|
||||
}
|
||||
const index = userEqPresetList.findIndex(p => p.id == id)
|
||||
if (index < 0) return
|
||||
userEqPresetList.splice(index, 1)
|
||||
saveUserSoundEffectEQPresetList(toRaw(userEqPresetList))
|
||||
}
|
||||
|
||||
|
||||
let userConvolutionPresetList: LX.SoundEffect.ConvolutionPreset[] | null = null
|
||||
export const getUserConvolutionPresetList = async() => {
|
||||
if (userEqPresetList == null) {
|
||||
userConvolutionPresetList = reactive(await getUserSoundEffectConvolutionPresetList())
|
||||
}
|
||||
return userConvolutionPresetList
|
||||
}
|
||||
export const saveUserConvolutionPreset = async(preset: LX.SoundEffect.ConvolutionPreset) => {
|
||||
if (userConvolutionPresetList == null) {
|
||||
userConvolutionPresetList = reactive(await getUserSoundEffectConvolutionPresetList())
|
||||
}
|
||||
const target = userConvolutionPresetList.find(p => p.id == preset.id)
|
||||
if (target) Object.assign(target, preset)
|
||||
else userConvolutionPresetList.push(preset)
|
||||
saveUserSoundEffectConvolutionPresetList(toRaw(userConvolutionPresetList))
|
||||
}
|
||||
export const removeUserConvolutionPreset = async(id: string) => {
|
||||
if (userConvolutionPresetList == null) {
|
||||
userConvolutionPresetList = reactive(await getUserSoundEffectConvolutionPresetList())
|
||||
}
|
||||
const index = userConvolutionPresetList.findIndex(p => p.id == id)
|
||||
if (index < 0) return
|
||||
userConvolutionPresetList.splice(index, 1)
|
||||
saveUserSoundEffectConvolutionPresetList(toRaw(userConvolutionPresetList))
|
||||
}
|
||||
1
src/renderer/types/common.d.ts
vendored
1
src/renderer/types/common.d.ts
vendored
@@ -13,3 +13,4 @@ import '@common/types/desktop_lyric'
|
||||
import '@common/types/ipc_renderer'
|
||||
import '@common/types/config_files'
|
||||
import '@common/types/music_metadata'
|
||||
import '@common/types/sound_effect'
|
||||
|
||||
@@ -5,6 +5,17 @@ export * from '@common/utils/nodejs'
|
||||
export * from '@common/utils/common'
|
||||
export * from '@common/utils/tools'
|
||||
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num 数字
|
||||
*/
|
||||
export const formatPlayCount = (num: number): string => {
|
||||
if (num > 100000000) return `${Math.trunc(num / 10000000) / 10}亿`
|
||||
if (num > 10000) return `${Math.trunc(num / 1000) / 10}万`
|
||||
return String(num)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 时间格式化
|
||||
*/
|
||||
|
||||
@@ -291,6 +291,22 @@ export const getSystemFonts = async() => {
|
||||
})
|
||||
}
|
||||
|
||||
export const getUserSoundEffectEQPresetList = async() => {
|
||||
return await rendererInvoke<LX.SoundEffect.EQPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_sound_effect_eq_preset)
|
||||
}
|
||||
|
||||
export const saveUserSoundEffectEQPresetList = (list: LX.SoundEffect.EQPreset[]) => {
|
||||
rendererSend<LX.SoundEffect.EQPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.save_sound_effect_eq_preset, list)
|
||||
}
|
||||
|
||||
export const getUserSoundEffectConvolutionPresetList = async() => {
|
||||
return await rendererInvoke<LX.SoundEffect.ConvolutionPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_sound_effect_convolution_preset)
|
||||
}
|
||||
|
||||
export const saveUserSoundEffectConvolutionPresetList = (list: LX.SoundEffect.ConvolutionPreset[]) => {
|
||||
rendererSend<LX.SoundEffect.ConvolutionPreset[]>(WIN_MAIN_RENDERER_EVENT_NAME.save_sound_effect_convolution_preset, list)
|
||||
}
|
||||
|
||||
|
||||
export const allHotKeys = markRaw({
|
||||
local: [
|
||||
|
||||
@@ -5,4 +5,5 @@ export const requestMsg = {
|
||||
// unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
|
||||
notConnectNetwork: '无法连接到服务器',
|
||||
cancelRequest: '取消http请求',
|
||||
tooManyRequests: '服务器繁忙',
|
||||
} as const
|
||||
|
||||
@@ -13,7 +13,11 @@ const api_test = {
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
|
||||
switch (body.code) {
|
||||
case 0: return Promise.resolve({ type, url: body.data })
|
||||
case 429: return Promise.reject(new Error(requestMsg.tooManyRequests))
|
||||
default: return Promise.reject(new Error(requestMsg.fail))
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
|
||||
@@ -16,8 +16,7 @@ const bd = {
|
||||
},
|
||||
getPic(songInfo) {
|
||||
const requestObj = this.getMusicInfo(songInfo)
|
||||
requestObj.promise = requestObj.promise.then(info => info.pic_premium)
|
||||
return requestObj
|
||||
return requestObj.promise.then(info => info.pic_premium)
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
const requestObj = this.getMusicInfo(songInfo)
|
||||
|
||||
63
src/renderer/utils/musicSdk/kg/album.js
Normal file
63
src/renderer/utils/musicSdk/kg/album.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { getMusicInfosByList } from './musicInfo'
|
||||
import { createHttpFetch } from './util'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 通过AlbumId获取专辑信息
|
||||
* @param {*} id
|
||||
*/
|
||||
async getAlbumInfo(id) {
|
||||
const albumInfoRequest = await createHttpFetch('http://kmrserviceretry.kugou.com/container/v1/album?dfid=1tT5He3kxrNC4D29ad1MMb6F&mid=22945702112173152889429073101964063697&userid=0&appid=1005&clientver=11589', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
appid: 1005,
|
||||
clienttime: 1681833686,
|
||||
clientver: 11589,
|
||||
data: [{ album_id: id }],
|
||||
fields: 'language,grade_count,intro,mix_intro,heat,category,sizable_cover,cover,album_name,type,quality,publish_company,grade,special_tag,author_name,publish_date,language_id,album_id,exclusive,is_publish,trans_param,authors,album_tag',
|
||||
isBuy: 0,
|
||||
key: 'e6f3306ff7e2afb494e89fbbda0becbf',
|
||||
mid: '22945702112173152889429073101964063697',
|
||||
show_album_tag: 0,
|
||||
},
|
||||
})
|
||||
if (!albumInfoRequest) return Promise.reject(new Error('get album info failed.'))
|
||||
const albumInfo = albumInfoRequest[0]
|
||||
|
||||
return {
|
||||
name: albumInfo.album_name,
|
||||
image: albumInfo.sizable_cover.replace('{size}', 240),
|
||||
desc: albumInfo.intro,
|
||||
authorName: albumInfo.author_name,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 通过AlbumId获取专辑
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
*/
|
||||
async getAlbumDetail(id, page = 1, limit = 200) {
|
||||
const albumList = await createHttpFetch(`http://mobiles.kugou.com/api/v3/album/song?version=9108&albumid=${id}&plat=0&pagesize=${limit}&area_code=0&page=${page}&with_res_tag=0`)
|
||||
if (!albumList.info) return Promise.reject(new Error('Get album list failed.'))
|
||||
|
||||
let result = await getMusicInfosByList(albumList.info)
|
||||
|
||||
const info = await this.getAlbumInfo(id)
|
||||
|
||||
return {
|
||||
list: result || [],
|
||||
page,
|
||||
limit,
|
||||
total: albumList.total,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.name,
|
||||
img: info.image,
|
||||
desc: info.desc,
|
||||
author: info.authorName,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -13,7 +13,11 @@ const api_test = {
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
|
||||
switch (body.code) {
|
||||
case 0: return Promise.resolve({ type, url: body.data })
|
||||
case 429: return Promise.reject(new Error(requestMsg.tooManyRequests))
|
||||
default: return Promise.reject(new Error(requestMsg.fail))
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, dateFormat2 } from '../../index'
|
||||
import { toMD5 } from '../utils'
|
||||
|
||||
const signatureParams = (params) => {
|
||||
let OIlwieks = '28dk2k092lksi2UIkp'
|
||||
let sign_params = `OIlwieks${OIlwieks}${params.replace(/&/g, '')}OIlwieks${OIlwieks}`
|
||||
return toMD5(sign_params)
|
||||
}
|
||||
import { signatureParams } from './util'
|
||||
|
||||
export default {
|
||||
_requestObj: null,
|
||||
@@ -16,8 +10,7 @@ export default {
|
||||
|
||||
let timestamp = Date.now()
|
||||
const params = `appid=1005&clienttime=${timestamp}&clienttoken=0&clientver=11409&code=fc4be23b4e972707f36b8a828a93ba8a&dfid=0&extdata=${hash}&kugouid=0&mid=16249512204336365674023395779019&mixsongid=0&p=${page}&pagesize=${limit}&uuid=0&ver=10`
|
||||
let signature = signatureParams(params)
|
||||
const _requestObj = httpFetch(`http://m.comment.service.kugou.com/v1/cmtlist?${params}&signature=${signature}`, {
|
||||
const _requestObj = httpFetch(`http://m.comment.service.kugou.com/v1/cmtlist?${params}&signature=${signatureParams(params)}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.24',
|
||||
},
|
||||
@@ -32,8 +25,7 @@ export default {
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
let timestamp = Date.now()
|
||||
const params = `appid=1005&clienttime=${timestamp}&clienttoken=0&clientver=11409&code=fc4be23b4e972707f36b8a828a93ba8a&dfid=0&extdata=${hash}&kugouid=0&mid=16249512204336365674023395779019&mixsongid=0&p=${page}&pagesize=${limit}&uuid=0&ver=10`
|
||||
let signature = signatureParams(params)
|
||||
const _requestObj2 = httpFetch(`http://m.comment.service.kugou.com/v1/weightlist?${params}&signature=${signature}`, {
|
||||
const _requestObj2 = httpFetch(`http://m.comment.service.kugou.com/v1/weightlist?${params}&signature=${signatureParams(params)}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Android712-AndroidPhone-8983-18-0-COMMENT-wifi',
|
||||
},
|
||||
@@ -65,7 +57,7 @@ export default {
|
||||
return rawList.map(item => {
|
||||
let data = {
|
||||
id: item.id,
|
||||
text: decodeName(item.content || '').split('\n'),
|
||||
text: decodeName(item.content || ''),
|
||||
images: item.images ? item.images.map(i => i.url) : [],
|
||||
location: item.location,
|
||||
time: item.addtime,
|
||||
@@ -81,7 +73,7 @@ export default {
|
||||
return item.pcontent
|
||||
? {
|
||||
id: item.id,
|
||||
text: decodeName(item.pcontent).split('\n'),
|
||||
text: decodeName(item.pcontent),
|
||||
time: null,
|
||||
userName: item.puser,
|
||||
avatar: null,
|
||||
|
||||
@@ -6,8 +6,10 @@ import pic from './pic'
|
||||
import lyric from './lyric'
|
||||
import hotSearch from './hotSearch'
|
||||
import comment from './comment'
|
||||
// import tipSearch from './tipSearch'
|
||||
|
||||
const kg = {
|
||||
// tipSearch,
|
||||
leaderboard,
|
||||
songList,
|
||||
musicSearch,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
import { formatSingerName } from '../utils'
|
||||
|
||||
let boardList = [{ id: 'kg__8888', name: '酷狗TOP500', bangid: '8888' }, { id: 'kg__6666', name: '酷狗飙升榜', bangid: '6666' }, { id: 'kg__37361', name: '酷狗雷达榜', bangid: '37361' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__35811', name: '会员专享热歌榜', bangid: '35811' }, { id: 'kg__31308', name: '华语新歌榜', bangid: '31308' }, { id: 'kg__31310', name: '欧美新歌榜', bangid: '31310' }, { id: 'kg__31311', name: '韩国新歌榜', bangid: '31311' }, { id: 'kg__31312', name: '日本新歌榜', bangid: '31312' }, { id: 'kg__31313', name: '粤语新歌榜', bangid: '31313' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__21101', name: '酷狗分享榜', bangid: '21101' }, { id: 'kg__30972', name: '腾讯音乐人原创榜', bangid: '30972' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__33160', name: '电音热歌榜', bangid: '33160' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__42808', name: '台湾KKBOX风云榜', bangid: '42808' }]
|
||||
let boardList = [{ id: 'kg__8888', name: 'TOP500', bangid: '8888' }, { id: 'kg__6666', name: '飙升榜', bangid: '6666' }, { id: 'kg__59703', name: '蜂鸟流行音乐榜', bangid: '59703' }, { id: 'kg__52144', name: '抖音热歌榜', bangid: '52144' }, { id: 'kg__52767', name: '快手热歌榜', bangid: '52767' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__44412', name: '说唱先锋榜', bangid: '44412' }, { id: 'kg__31308', name: '内地榜', bangid: '31308' }, { id: 'kg__33160', name: '电音榜', bangid: '33160' }, { id: 'kg__31313', name: '香港地区榜', bangid: '31313' }, { id: 'kg__51341', name: '民谣榜', bangid: '51341' }, { id: 'kg__54848', name: '台湾地区榜', bangid: '54848' }, { id: 'kg__31310', name: '欧美榜', bangid: '31310' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__31311', name: '韩国榜', bangid: '31311' }, { id: 'kg__31312', name: '日本榜', bangid: '31312' }, { id: 'kg__49225', name: '80后热歌榜', bangid: '49225' }, { id: 'kg__49223', name: '90后热歌榜', bangid: '49223' }, { id: 'kg__49224', name: '00后热歌榜', bangid: '49224' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__51340', name: '伤感榜', bangid: '51340' }, { id: 'kg__35811', name: '会员专享榜', bangid: '35811' }, { id: 'kg__37361', name: '雷达榜', bangid: '37361' }, { id: 'kg__21101', name: '分享榜', bangid: '21101' }, { id: 'kg__46910', name: '综艺新歌榜', bangid: '46910' }, { id: 'kg__30972', name: '酷狗音乐人原创榜', bangid: '30972' }, { id: 'kg__60170', name: '闽南语榜', bangid: '60170' }, { id: 'kg__65234', name: '儿歌榜', bangid: '65234' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__25028', name: 'Beatport电子舞曲榜', bangid: '25028' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__46868', name: '日本SPACE SHOWER榜', bangid: '46868' }, { id: 'kg__42808', name: 'KKBOX风云榜', bangid: '42808' }, { id: 'kg__60171', name: '越南语榜', bangid: '60171' }, { id: 'kg__60172', name: '泰语榜', bangid: '60172' }, { id: 'kg__59895', name: 'R&B榜', bangid: '59895' }, { id: 'kg__59896', name: '摇滚榜', bangid: '59896' }, { id: 'kg__59897', name: '爵士榜', bangid: '59897' }, { id: 'kg__59898', name: '乡村音乐榜', bangid: '59898' }, { id: 'kg__59900', name: '纯音乐榜', bangid: '59900' }, { id: 'kg__59899', name: '古典榜', bangid: '59899' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }]
|
||||
|
||||
export default {
|
||||
listDetailLimit: 100,
|
||||
list: [
|
||||
{
|
||||
id: 'kgtop500',
|
||||
name: '酷狗TOP500',
|
||||
name: 'TOP500',
|
||||
bangid: '8888',
|
||||
},
|
||||
{
|
||||
@@ -74,7 +75,7 @@ export default {
|
||||
_requestBoardsObj: null,
|
||||
getBoardsData() {
|
||||
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
|
||||
this._requestBoardsObj = httpFetch('http://mobilecdnbj.kugou.com/api/v3/rank/list?version=9108&plat=0&showtype=2&parentid=0&apiver=6&area_code=1&withsong=1')
|
||||
this._requestBoardsObj = httpFetch('http://mobilecdnbj.kugou.com/api/v5/rank/list?version=9108&plat=0&showtype=2&parentid=0&apiver=6&area_code=1&withsong=1')
|
||||
return this._requestBoardsObj.promise
|
||||
},
|
||||
getData(url) {
|
||||
@@ -126,7 +127,7 @@ export default {
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: decodeName(this.getSinger(item.authors)),
|
||||
singer: formatSingerName(item.authors, 'author_name'),
|
||||
name: decodeName(item.songname),
|
||||
albumName: decodeName(item.remark),
|
||||
albumId: item.album_id,
|
||||
@@ -168,7 +169,8 @@ export default {
|
||||
// // console.log(response.body)
|
||||
// if (response.statusCode !== 200 || response.body.errcode !== 0) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.data.info)
|
||||
// // console.log(list)
|
||||
// console.log(list)
|
||||
// // console.log(JSON.stringify(list))
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
|
||||
104
src/renderer/utils/musicSdk/kg/musicInfo.js
Normal file
104
src/renderer/utils/musicSdk/kg/musicInfo.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
import { createHttpFetch } from './util'
|
||||
|
||||
const createGetMusicInfosTask = (hashs) => {
|
||||
let data = {
|
||||
appid: 1001,
|
||||
clienttime: 639437935,
|
||||
clientver: 9020,
|
||||
fields: 'album_info,author_name,audio_info,ori_audio_name',
|
||||
is_publish: '1',
|
||||
key: '0475af1457cd3363c7b45b871e94428a',
|
||||
mid: '21511157a05844bd085308bc76ef3342',
|
||||
show_privilege: 1,
|
||||
}
|
||||
let list = hashs
|
||||
let tasks = []
|
||||
while (list.length) {
|
||||
tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
|
||||
if (list.length < 100) break
|
||||
list = list.slice(100)
|
||||
}
|
||||
let url = 'http://kmr.service.kugou.com/v2/album_audio/audio'
|
||||
return tasks.map(task => createHttpFetch(url, {
|
||||
method: 'POST',
|
||||
body: task,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
},
|
||||
}).then(data => data.map(s => s[0])))
|
||||
}
|
||||
|
||||
export const filterMusicInfoList = (rawList) => {
|
||||
// console.log(rawList)
|
||||
let ids = new Set()
|
||||
let list = []
|
||||
rawList.forEach(item => {
|
||||
if (!item) return
|
||||
if (ids.has(item.audio_info.audio_id)) return
|
||||
ids.add(item.audio_info.audio_id)
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (item.audio_info.filesize !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize))
|
||||
types.push({ type: '128k', size, hash: item.audio_info.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: item.audio_info.hash,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_320 !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_320))
|
||||
types.push({ type: '320k', size, hash: item.audio_info.hash_320 })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item.audio_info.hash_320,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_flac !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_flac))
|
||||
types.push({ type: 'flac', size, hash: item.audio_info.hash_flac })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.audio_info.hash_flac,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_high !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_high))
|
||||
types.push({ type: 'flac24bit', size, hash: item.audio_info.hash_high })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.audio_info.hash_high,
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(item.author_name),
|
||||
name: decodeName(item.ori_audio_name),
|
||||
albumName: decodeName(item.album_info.album_name),
|
||||
albumId: item.album_info.album_id,
|
||||
songmid: item.audio_info.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.audio_info.hash,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
export const getMusicInfos = async(hashs) => {
|
||||
return filterMusicInfoList(await Promise.all(createGetMusicInfosTask(hashs)).then(data => data.flat()))
|
||||
}
|
||||
|
||||
export const getMusicInfo = async(hash) => {
|
||||
return getMusicInfos([hash]).then(data => data[0])
|
||||
}
|
||||
|
||||
export const getMusicInfosByList = (list) => {
|
||||
return getMusicInfos(list.map(item => ({ hash: item.hash })))
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
// import '../../polyfill/array.find'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
// import { formatSinger } from './util'
|
||||
import { formatSingerName } from '../utils'
|
||||
|
||||
|
||||
export default {
|
||||
limit: 30,
|
||||
@@ -50,7 +48,7 @@ export default {
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: decodeName(rawData.SingerName),
|
||||
singer: decodeName(formatSingerName(rawData.Singers, 'name')),
|
||||
name: decodeName(rawData.SongName),
|
||||
albumName: decodeName(rawData.AlbumName),
|
||||
albumId: rawData.AlbumID,
|
||||
|
||||
@@ -37,13 +37,12 @@ export default {
|
||||
},
|
||||
},
|
||||
)
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
return requestObj.promise.then(({ body }) => {
|
||||
if (body.error_code !== 0) return Promise.reject('图片获取失败')
|
||||
let info = body.data[0].info
|
||||
const img = info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
|
||||
if (!img) return Promise.reject('Pic get failed')
|
||||
return img
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate, dateFormat } from '../../index'
|
||||
import { toMD5 } from '../utils'
|
||||
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../../index'
|
||||
import infSign from './vendors/infSign.min'
|
||||
import { signatureParams } from './util'
|
||||
|
||||
const handleSignature = (id, page, limit) => new Promise((resolve, reject) => {
|
||||
infSign({ appid: 1058, type: 0, module: 'playlist', page, pagesize: limit, specialid: id }, null, {
|
||||
@@ -52,6 +52,113 @@ export default {
|
||||
// https://www.kugou.com/yy/special/single/1067062.html
|
||||
listDetailLink: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
// async getGlobalSpecialId(specialId) {
|
||||
// return httpFetch(`http://mobilecdnbj.kugou.com/api/v5/special/info?specialid=${specialId}`, {
|
||||
// headers: {
|
||||
// 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HLK-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Mobile Safari/537.36 EdgA/104.0.1293.70',
|
||||
// },
|
||||
// }).promise.then(({ body }) => {
|
||||
// // console.log(body)
|
||||
// if (!body.data.global_specialid) Promise.reject(new Error('Failed to get global collection id.'))
|
||||
// return body.data.global_specialid
|
||||
// })
|
||||
// },
|
||||
// async getListInfoBySpecialId(special_id, retry = 0) {
|
||||
// if (++retry > 2) throw new Error('failed')
|
||||
// return httpFetch(`https://m.kugou.com/plist/list/${special_id}/?json=true`, {
|
||||
// headers: {
|
||||
// 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HLK-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Mobile Safari/537.36 EdgA/104.0.1293.70',
|
||||
// },
|
||||
// follow_max: 2,
|
||||
// }).promise.then(({ body }) => {
|
||||
// // console.log(body)
|
||||
// if (!body.info.list) return this.getListInfoBySpecialId(special_id, retry)
|
||||
// let listinfo = body.info.list
|
||||
// return {
|
||||
// listInfo: {
|
||||
// name: listinfo.specialname,
|
||||
// image: listinfo.imgurl.replace('{size}', '150'),
|
||||
// intro: listinfo.intro,
|
||||
// author: listinfo.nickname,
|
||||
// playcount: listinfo.playcount,
|
||||
// total: listinfo.songcount,
|
||||
// },
|
||||
// globalSpecialId: listinfo.global_specialid,
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
// async getSongListDetailByGlobalSpecialId(id, page, limit = 100, retry = 0) {
|
||||
// if (++retry > 2) throw new Error('failed')
|
||||
// console.log(id)
|
||||
// const params = `specialid=0&need_sort=1&module=CloudMusic&clientver=11409&pagesize=${limit}&global_collection_id=${id}&userid=0&page=${page}&type=1&area_code=1&appid=1005`
|
||||
// return httpFetch(`http://pubsongscdn.tx.kugou.com/v2/get_other_list_file?${params}&signature=${signatureParams(params)}`).promise.then(({ body }) => {
|
||||
// // console.log(body)
|
||||
// if (body.data?.info == null) return this.getSongListDetailByGlobalSpecialId(id, page, limit, retry)
|
||||
// return body.data.info
|
||||
// })
|
||||
// },
|
||||
parseHtmlDesc(html) {
|
||||
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
|
||||
let index = html.indexOf(prefix)
|
||||
if (index < 0) return null
|
||||
const afterStr = html.substring(index + prefix.length)
|
||||
index = afterStr.indexOf('</div>')
|
||||
if (index < 0) return null
|
||||
return decodeName(afterStr.substring(0, index))
|
||||
},
|
||||
async getListDetailBySpecialId(id, page, tryNum = 0) {
|
||||
if (tryNum > 2) throw new Error('try max num')
|
||||
|
||||
const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise
|
||||
let listData = body.match(this.regExps.listData)
|
||||
let listInfo = body.match(this.regExps.listInfo)
|
||||
if (!listData) return this.getListDetailBySpecialId(id, page, ++tryNum)
|
||||
let list = await this.getMusicInfos(JSON.parse(listData[1]))
|
||||
// listData = this.filterData(JSON.parse(listData[1]))
|
||||
let name
|
||||
let pic
|
||||
if (listInfo) {
|
||||
name = listInfo[1]
|
||||
pic = listInfo[2]
|
||||
}
|
||||
let desc = this.parseHtmlDesc(body)
|
||||
|
||||
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: 10000,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name,
|
||||
img: pic,
|
||||
desc,
|
||||
// author: body.result.info.userinfo.username,
|
||||
// play_count: formatPlayCount(body.result.listen_num),
|
||||
},
|
||||
}
|
||||
|
||||
// const globalSpecialId = await this.getGlobalSpecialId(id)
|
||||
// const limit = 100
|
||||
// const listData = await this.getSongListDetailByGlobalSpecialId(globalSpecialId, page, limit)
|
||||
// if (!Array.isArray(listData))
|
||||
// return this.getUserListDetail2(globalSpecialId)
|
||||
// return {
|
||||
// list: this.filterDatav9(listData),
|
||||
// page,
|
||||
// limit,
|
||||
// total: listInfo.total,
|
||||
// source: 'kg',
|
||||
// info: {
|
||||
// name: listInfo.name,
|
||||
// img: listInfo.image,
|
||||
// desc: listInfo.intro,
|
||||
// author: listInfo.author,
|
||||
// play_count: formatPlayCount(listInfo.playcount),
|
||||
// },
|
||||
// }
|
||||
},
|
||||
getInfoUrl(tagId) {
|
||||
return tagId
|
||||
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
|
||||
@@ -65,15 +172,6 @@ export default {
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num
|
||||
*/
|
||||
formatPlayCount(num) {
|
||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||
return num
|
||||
},
|
||||
filterInfoHotTag(rawData) {
|
||||
const result = []
|
||||
if (rawData.status !== 1) return result
|
||||
@@ -145,7 +243,7 @@ export default {
|
||||
},
|
||||
filterList(rawData) {
|
||||
return rawData.map(item => ({
|
||||
play_count: item.total_play_count || this.formatPlayCount(item.play_count),
|
||||
play_count: item.total_play_count || formatPlayCount(item.play_count),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
@@ -210,6 +308,15 @@ export default {
|
||||
},
|
||||
}).then(data => data.map(s => s[0])))
|
||||
},
|
||||
async getMusicInfos(list) {
|
||||
return this.filterData2(
|
||||
await Promise.all(
|
||||
this.createTask(
|
||||
this.deDuplication(list)
|
||||
.map(item => ({ hash: item.hash })),
|
||||
))
|
||||
.then(([...datas]) => datas.flat()))
|
||||
},
|
||||
|
||||
async getUserListDetailByCode(id) {
|
||||
const songInfo = await this.createHttp('http://t.kugou.com/command/', {
|
||||
@@ -222,8 +329,17 @@ export default {
|
||||
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: id },
|
||||
})
|
||||
// console.log(songInfo)
|
||||
// type 1单曲,2歌单,3电台,4酷狗码,5别人的播放队列
|
||||
let songList
|
||||
let info = songInfo.info
|
||||
switch (info.type) {
|
||||
case 2:
|
||||
if (!info.global_collection_id) return this.getListDetailBySpecialId(info.id)
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
if (info.global_collection_id) return this.getUserListDetail2(info.global_collection_id)
|
||||
if (info.userid != null) {
|
||||
songList = await this.createHttp('http://www2.kugou.kugou.com/apps/kucodeAndShare/app/', {
|
||||
@@ -237,19 +353,19 @@ export default {
|
||||
})
|
||||
// console.log(songList)
|
||||
}
|
||||
let result = await Promise.all(this.createTask((songList || songInfo.list).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
let list = await this.getMusicInfos(songList || songInfo.list)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
list,
|
||||
page: 1,
|
||||
limit: info.count,
|
||||
total: info.count,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.name,
|
||||
img: (info.img_size && info.img_size.replace('{size}', 240)) || info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: info.username,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -264,20 +380,20 @@ export default {
|
||||
if (songInfo.global_collection_id) return this.getUserListDetail2(songInfo.global_collection_id)
|
||||
else return this.getUserListDetail4(songInfo, chain, page).catch(() => this.getUserListDetail5(chain))
|
||||
}
|
||||
let result = await Promise.all(this.createTask(songInfo.list.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
let list = await this.getMusicInfos(songInfo.list)
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: songInfo.count,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: songInfo.info.name,
|
||||
img: songInfo.info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: songInfo.info.username,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -308,20 +424,20 @@ export default {
|
||||
}).then(data => data.list.info))
|
||||
}
|
||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
result = await Promise.all(this.createTask(this.deDuplication(result).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
result = await this.getMusicInfos(result)
|
||||
// console.log(result)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
list: result,
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.count,
|
||||
total: result.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: this.formatPlayCount(listInfo.count),
|
||||
// play_count: formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -332,7 +448,8 @@ export default {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp('https://mobiles.kugou.com/api/v5/special/song_v2?appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&page=' + page + '&pagesize=' + limit + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163263991clientver=20000dfid=-global_specialid=' + id + 'mid=1586163263991page=' + page + 'pagesize=' + limit + 'plat=0specialid=0srcappid=2919uuid=1586163263991version=8000NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
const params = 'appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&page=' + page + '&pagesize=' + limit + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-'
|
||||
tasks.push(this.createHttp(`https://mobiles.kugou.com/api/v5/special/song_v2?${params}&signature=${signatureParams(params, 5)}`, {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
@@ -347,7 +464,8 @@ export default {
|
||||
async getUserListDetail2(global_collection_id) {
|
||||
let id = global_collection_id
|
||||
if (id.length > 1000) throw new Error('get list error')
|
||||
let info = await this.createHttp('https://mobiles.kugou.com/api/v5/special/info_v2?appid=1058&specialid=0&global_specialid=' + id + '&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163242519clientver=20000dfid=-format=jsonpglobal_specialid=' + id + 'mid=1586163242519specialid=0srcappid=2919uuid=1586163242519NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
const params = 'appid=1058&specialid=0&global_specialid=' + id + '&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-'
|
||||
let info = await this.createHttp(`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 5)}`, {
|
||||
headers: {
|
||||
mid: '1586163242519',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
@@ -357,20 +475,20 @@ export default {
|
||||
},
|
||||
})
|
||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||
let result = await Promise.all(this.createTask(this.deDuplication(songInfo).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
let list = await this.getMusicInfos(songInfo)
|
||||
// console.log(info, songInfo, list)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: info.songcount,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
img: info.imgurl && info.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
desc: info.intro,
|
||||
author: info.nickname,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
play_count: formatPlayCount(info.playcount),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -399,9 +517,9 @@ export default {
|
||||
let result = body.match(/var\sdataFromSmarty\s=\s(\[.+?\])/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
result = await Promise.all(this.createTask(result.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
result = await this.getMusicInfos(result)
|
||||
// console.log(info, songInfo)
|
||||
return this.filterData2(result)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetail4(songInfo, chain, page) {
|
||||
@@ -414,14 +532,14 @@ export default {
|
||||
list: list || [],
|
||||
page,
|
||||
limit,
|
||||
total: listInfo.songcount,
|
||||
total: list.length ?? 0,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -435,14 +553,14 @@ export default {
|
||||
list: list || [],
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.songcount,
|
||||
total: list.length ?? 0,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -458,9 +576,9 @@ export default {
|
||||
})
|
||||
|
||||
// console.log(info)
|
||||
let result = await Promise.all(this.createTask(info.info.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
let result = await this.getMusicInfos(info.info)
|
||||
// console.log(info, songInfo)
|
||||
return this.filterData2(result)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetail(link, page, retryNum = 0) {
|
||||
@@ -511,9 +629,7 @@ export default {
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
|
||||
async getListDetail(id, page) { // 获取歌曲列表内的音乐
|
||||
id = id.toString()
|
||||
if (id.includes('special/single/')) {
|
||||
id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
@@ -525,36 +641,9 @@ export default {
|
||||
} else if (id.startsWith('id_')) {
|
||||
id = id.replace('id_', '')
|
||||
}
|
||||
|
||||
// if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
|
||||
const requestObj_listDetail = httpFetch(this.getSongListDetailUrl(id))
|
||||
return requestObj_listDetail.promise.then(({ body }) => {
|
||||
let listData = body.match(this.regExps.listData)
|
||||
let listInfo = body.match(this.regExps.listInfo)
|
||||
if (!listData) return this.getListDetail(id, page, ++tryNum)
|
||||
listData = this.filterData(JSON.parse(listData[1]))
|
||||
let name
|
||||
let pic
|
||||
if (listInfo) {
|
||||
name = listInfo[1]
|
||||
pic = listInfo[2]
|
||||
}
|
||||
return {
|
||||
list: listData,
|
||||
page: 1,
|
||||
limit: 10000,
|
||||
total: listData.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name,
|
||||
img: pic,
|
||||
// desc: body.result.info.list_desc,
|
||||
// author: body.result.info.userinfo.username,
|
||||
// play_count: this.formatPlayCount(body.result.listen_num),
|
||||
},
|
||||
}
|
||||
})
|
||||
return this.getListDetailBySpecialId(id, page)
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
@@ -610,6 +699,68 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
// getSinger(singers) {
|
||||
// let arr = []
|
||||
// singers?.forEach(singer => {
|
||||
// arr.push(singer.name)
|
||||
// })
|
||||
// return arr.join('、')
|
||||
// },
|
||||
// v9 API
|
||||
// filterDatav9(rawList) {
|
||||
// console.log(rawList)
|
||||
// return rawList.map(item => {
|
||||
// const types = []
|
||||
// const _types = {}
|
||||
// item.relate_goods.forEach(qualityObj => {
|
||||
// if (qualityObj.level === 2) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: '128k', size, hash: qualityObj.hash })
|
||||
// _types['128k'] = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// } else if (qualityObj.level === 4) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: '320k', size, hash: qualityObj.hash })
|
||||
// _types['320k'] = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// } else if (qualityObj.level === 5) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: 'flac', size, hash: qualityObj.hash })
|
||||
// _types.flac = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// } else if (qualityObj.level === 6) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: 'flac24bit', size, hash: qualityObj.hash })
|
||||
// _types.flac24bit = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// const nameInfo = item.name.split(' - ')
|
||||
// return {
|
||||
// singer: this.getSinger(item.singerinfo),
|
||||
// name: decodeName((nameInfo[1] ?? nameInfo[0]).trim()),
|
||||
// albumName: decodeName(item.albuminfo.name),
|
||||
// albumId: item.albuminfo.id,
|
||||
// songmid: item.audio_id,
|
||||
// source: 'kg',
|
||||
// interval: formatPlayTime(item.timelen / 1000),
|
||||
// img: null,
|
||||
// lrc: null,
|
||||
// hash: item.hash,
|
||||
// types,
|
||||
// _types,
|
||||
// typeUrl: {},
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
|
||||
// hash list filter
|
||||
filterData2(rawList) {
|
||||
@@ -646,6 +797,14 @@ export default {
|
||||
hash: item.audio_info.hash_flac,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_high !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_high))
|
||||
types.push({ type: 'flac24bit', size, hash: item.audio_info.hash_high })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.audio_info.hash_high,
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(item.author_name),
|
||||
name: decodeName(item.ori_audio_name),
|
||||
@@ -737,7 +896,7 @@ export default {
|
||||
return {
|
||||
list: body.data.info.map(item => {
|
||||
return {
|
||||
play_count: this.formatPlayCount(item.playcount),
|
||||
play_count: formatPlayCount(item.playcount),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
|
||||
112
src/renderer/utils/musicSdk/kg/temp/musicSearch-new.js
Normal file
112
src/renderer/utils/musicSdk/kg/temp/musicSearch-new.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
import { signatureParams, createHttpFetch } from './util'
|
||||
import { formatSingerName } from '../../utils'
|
||||
|
||||
export default {
|
||||
limit: 30,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
const sign = signatureParams(`userid=0&area_code=1&appid=1005&dopicfull=1&page=${page}&token=0&privilegefilter=0&requestid=0&pagesize=${limit}&user_labels=&clienttime=0&sec_aggre=1&iscorrection=1&uuid=0&mid=0&keyword=${str}&dfid=-&clientver=11409&platform=AndroidFilter&tag=`, 3)
|
||||
return createHttpFetch(`https://gateway.kugou.com/complexsearch/v3/search/song?userid=0&area_code=1&appid=1005&dopicfull=1&page=${page}&token=0&privilegefilter=0&requestid=0&pagesize=${limit}&user_labels=&clienttime=0&sec_aggre=1&iscorrection=1&uuid=0&mid=0&dfid=-&clientver=11409&platform=AndroidFilter&tag=&keyword=${encodeURIComponent(str)}&signature=${sign}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
referer: 'https://kugou.com',
|
||||
},
|
||||
}).then(body => body)
|
||||
},
|
||||
filterList(raw) {
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
|
||||
raw.forEach(item => {
|
||||
if (ids.has(item.Audioid)) return
|
||||
ids.add(item.Audioid)
|
||||
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (item.FileSize !== 0) {
|
||||
let size = sizeFormate(item.FileSize)
|
||||
types.push({ type: '128k', size, hash: item.FileHash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: item.FileHash,
|
||||
}
|
||||
}
|
||||
if (item.HQ != undefined) {
|
||||
let size = sizeFormate(item.HQ.FileSize)
|
||||
types.push({ type: '320k', size, hash: item.HQ.Hash })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item.HQ.Hash,
|
||||
}
|
||||
}
|
||||
if (item.SQ != undefined) {
|
||||
let size = sizeFormate(item.SQ.FileSize)
|
||||
types.push({ type: 'flac', size, hash: item.SQ.Hash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.SQ.Hash,
|
||||
}
|
||||
}
|
||||
if (item.Res != undefined) {
|
||||
let size = sizeFormate(item.Res.FileSize)
|
||||
types.push({ type: 'flac24bit', size, hash: item.Res.Hash })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.Res.Hash,
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(formatSingerName(item.Singers)),
|
||||
name: decodeName(item.SongName),
|
||||
albumName: decodeName(item.AlbumName),
|
||||
albumId: item.AlbumID,
|
||||
songmid: item.Audioid,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(item.Duration),
|
||||
_interval: item.Duration,
|
||||
img: null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
hash: item.FileHash,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
|
||||
return list
|
||||
},
|
||||
handleResult(rawData) {
|
||||
const rawList = []
|
||||
rawData.forEach(item => {
|
||||
rawList.push(item)
|
||||
item.Grp.forEach(e => rawList.push(e))
|
||||
})
|
||||
|
||||
return this.filterList(rawList)
|
||||
},
|
||||
search(str, page = 1, limit, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
if (limit == null) limit = this.limit
|
||||
|
||||
return this.musicSearch(str, page, limit).then(data => {
|
||||
let list = this.handleResult(data.lists)
|
||||
if (!list) return this.search(str, page, limit, retryNum)
|
||||
|
||||
this.total = data.total
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'kg',
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
794
src/renderer/utils/musicSdk/kg/temp/songList-new.js
Normal file
794
src/renderer/utils/musicSdk/kg/temp/songList-new.js
Normal file
@@ -0,0 +1,794 @@
|
||||
import { httpFetch } from '../../../request'
|
||||
import { formatSingerName } from '../../utils'
|
||||
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../../../index'
|
||||
import { signatureParams, createHttpFetch } from './../util'
|
||||
import { getMusicInfosByList } from '../musicInfo'
|
||||
import album from '../album'
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null,
|
||||
_requestObj_listInfo: null,
|
||||
_requestObj_list: null,
|
||||
_requestObj_listRecommend: null,
|
||||
listDetailLimit: 10000,
|
||||
currentTagInfo: {
|
||||
id: undefined,
|
||||
info: undefined,
|
||||
},
|
||||
sortList: [
|
||||
{
|
||||
name: '推荐',
|
||||
id: '5',
|
||||
},
|
||||
{
|
||||
name: '最热',
|
||||
id: '6',
|
||||
},
|
||||
{
|
||||
name: '最新',
|
||||
id: '7',
|
||||
},
|
||||
{
|
||||
name: '热藏',
|
||||
id: '3',
|
||||
},
|
||||
{
|
||||
name: '飙升',
|
||||
id: '8',
|
||||
},
|
||||
],
|
||||
cache: new Map(),
|
||||
collectionIdListInfoCache: new Map(),
|
||||
regExps: {
|
||||
listData: /global\.data = (\[.+\]);/,
|
||||
listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/,
|
||||
// https://www.kugou.com/yy/special/single/1067062.html
|
||||
listDetailLink: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取歌曲列表内的音乐
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
*/
|
||||
async getListDetail(id, page) {
|
||||
id = id.toString()
|
||||
|
||||
if (id.includes('special/single/')) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
// fix https://www.kugou.com/songlist/xxx/?uid=xxx&chl=qq_client&cover=http%3A%2F%2Fimge.kugou.com%xxx.jpg&iszlist=1
|
||||
if (/https?:/.test(id)) {
|
||||
if (id.includes('#')) id = id.replace(/#.*$/, '')
|
||||
if (id.includes('global_collection_id')) return this.getUserListDetailByCollectionId(id.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (id.includes('chain=')) return this.getUserListDetail3(id.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (id.includes('.html')) {
|
||||
if (id.includes('zlist.html')) {
|
||||
id = id.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
if (id.includes('pagesize')) {
|
||||
id = id.replace('pagesize=30', 'pagesize=' + this.listDetailLimit).replace('page=1', 'page=' + page)
|
||||
} else {
|
||||
id += `&pagesize=${this.listDetailLimit}&page=${page}`
|
||||
}
|
||||
} else if (!id.includes('song.html')) return this.getUserListDetail3(id.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
return this.getUserListDetail(id.replace(/^.*?http/, 'http'), page)
|
||||
}
|
||||
if (/^\d+$/.test(id)) return this.getUserListDetailByCode(id, page)
|
||||
if (id.startsWith('gid_')) return this.getUserListDetailByCollectionId(id.replace('gid_', ''), page)
|
||||
if (id.startsWith('id_')) return this.getUserListDetailBySpecialId(id.replace('id_', ''), page)
|
||||
|
||||
return new Error('Failed.')
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取SpecialId歌单
|
||||
* @param {*} id
|
||||
*/
|
||||
async getUserListDetailBySpecialId(id, page, tryNum = 0) {
|
||||
if (tryNum > 2) throw new Error('try max num')
|
||||
|
||||
const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise
|
||||
let listData = body.match(this.regExps.listData)
|
||||
let listInfo = body.match(this.regExps.listInfo)
|
||||
if (!listData) return this.getListDetailBySpecialId(id, page, ++tryNum)
|
||||
let list = await getMusicInfosByList(JSON.parse(listData[1]))
|
||||
let name
|
||||
let pic
|
||||
if (listInfo) {
|
||||
name = listInfo[1]
|
||||
pic = listInfo[2]
|
||||
}
|
||||
let desc = this.parseHtmlDesc(body)
|
||||
|
||||
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: 10000,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name,
|
||||
img: pic,
|
||||
desc,
|
||||
// author: body.result.info.userinfo.username,
|
||||
// play_count: formatPlayCount(body.result.listen_num),
|
||||
},
|
||||
}
|
||||
},
|
||||
parseHtmlDesc(html) {
|
||||
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
|
||||
let index = html.indexOf(prefix)
|
||||
if (index < 0) return null
|
||||
const afterStr = html.substring(index + prefix.length)
|
||||
index = afterStr.indexOf('</div>')
|
||||
if (index < 0) return null
|
||||
return decodeName(afterStr.substring(0, index))
|
||||
},
|
||||
|
||||
/**
|
||||
* 使用SpecialId获取CollectionId
|
||||
* @param {*} specialId
|
||||
*/
|
||||
async getCollectionIdBySpecialId(specialId) {
|
||||
return httpFetch(`http://mobilecdnbj.kugou.com/api/v5/special/info?specialid=${specialId}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HLK-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Mobile Safari/537.36 EdgA/104.0.1293.70',
|
||||
},
|
||||
}).promise.then(({ body }) => {
|
||||
// console.log('getCollectionIdBySpecialId', body)
|
||||
if (!body.data.global_specialid) return Promise.reject(new Error('Failed to get global collection id.'))
|
||||
return body.data.global_specialid
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取歌单URL
|
||||
* @param {*} sortId
|
||||
* @param {*} tagId
|
||||
* @param {*} page
|
||||
*/
|
||||
getSongListUrl(sortId, tagId, page) {
|
||||
if (tagId == null) tagId = ''
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_ajax=1&cdn=cdn&t=${sortId}&c=${tagId}&p=${page}`
|
||||
},
|
||||
getInfoUrl(tagId) {
|
||||
return tagId
|
||||
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
|
||||
: 'http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&'
|
||||
},
|
||||
getSongListDetailUrl(id) {
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
|
||||
},
|
||||
|
||||
filterInfoHotTag(rawData) {
|
||||
const result = []
|
||||
if (rawData.status !== 1) return result
|
||||
for (const key of Object.keys(rawData.data)) {
|
||||
let tag = rawData.data[key]
|
||||
result.push({
|
||||
id: tag.special_id,
|
||||
name: tag.special_name,
|
||||
source: 'kg',
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
filterTagInfo(rawData) {
|
||||
const result = []
|
||||
for (const name of Object.keys(rawData)) {
|
||||
result.push({
|
||||
name,
|
||||
list: rawData[name].data.map(tag => ({
|
||||
parent_id: tag.parent_id,
|
||||
parent_name: tag.pname,
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
source: 'kg',
|
||||
})),
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
filterSongList(rawData) {
|
||||
return rawData.map(item => ({
|
||||
play_count: item.total_play_count || formatPlayCount(item.play_count),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: dateFormat(item.publish_time || item.publishtime, 'Y-M-D'),
|
||||
img: item.img || item.imgurl,
|
||||
total: item.songcount,
|
||||
grade: item.grade,
|
||||
desc: item.intro,
|
||||
source: 'kg',
|
||||
}))
|
||||
},
|
||||
|
||||
getSongList(sortId, tagId, page, tryNum = 0) {
|
||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_list = httpFetch(
|
||||
this.getSongListUrl(sortId, tagId, page),
|
||||
)
|
||||
return this._requestObj_list.promise.then(({ body }) => {
|
||||
if (!body || body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum)
|
||||
return this.filterSongList(body.special_db)
|
||||
})
|
||||
},
|
||||
getSongListRecommend(tryNum = 0) {
|
||||
if (this._requestObj_listRecommend) this._requestObj_listRecommend.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_listRecommend = httpFetch(
|
||||
'http://everydayrec.service.kugou.com/guess_special_recommend',
|
||||
{
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'KuGou2012-8275-web_browser_event_handler',
|
||||
},
|
||||
body: {
|
||||
appid: 1001,
|
||||
clienttime: 1566798337219,
|
||||
clientver: 8275,
|
||||
key: 'f1f93580115bb106680d2375f8032d96',
|
||||
mid: '21511157a05844bd085308bc76ef3343',
|
||||
platform: 'pc',
|
||||
userid: '262643156',
|
||||
return_min: 6,
|
||||
return_max: 15,
|
||||
},
|
||||
},
|
||||
)
|
||||
return this._requestObj_listRecommend.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getSongListRecommend(++tryNum)
|
||||
return this.filterSongList(body.data.special_list)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 通过CollectionId获取歌单详情
|
||||
* @param {*} id
|
||||
*/
|
||||
async getUserListInfoByCollectionId(id) {
|
||||
if (!id || id.length > 1000) return Promise.reject(new Error('get list error'))
|
||||
if (this.collectionIdListInfoCache.has(id)) return this.collectionIdListInfoCache.get(id)
|
||||
|
||||
const params = `appid=1058&specialid=0&global_specialid=${id}&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-`
|
||||
return createHttpFetch(`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 5)}`, {
|
||||
headers: {
|
||||
mid: '1586163242519',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163242519',
|
||||
},
|
||||
}).then(body => {
|
||||
let info = {
|
||||
type: body.type,
|
||||
userName: body.nickname,
|
||||
userAvatar: body.user_avatar,
|
||||
imageUrl: body.imgurl,
|
||||
desc: body.intro,
|
||||
name: body.specialname,
|
||||
globalSpecialid: body.global_specialid,
|
||||
total: body.songcount,
|
||||
playCount: body.playcount,
|
||||
}
|
||||
|
||||
this.collectionIdListInfoCache.set(id, info)
|
||||
return info
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 通过SpecialId获取歌单
|
||||
* @param {*} id
|
||||
*/
|
||||
// async getUserListDetailBySpecialId(id, page = 1, limit = 300) {
|
||||
// if (!id || id.length > 1000) return Promise.reject(new Error('get list error.'))
|
||||
// const listInfo = await this.getListInfoBySpecialId(id)
|
||||
|
||||
// const params = `specialid=${id}&need_sort=1&module=CloudMusic&clientver=11589&pagesize=${limit}&userid=0&page=${page}&type=0&area_code=1&appid=1005`
|
||||
// return createHttpFetch(`http://pubsongs.kugou.com/v2/get_other_list_file?${params}&signature=${signatureParams(params, 2)}`, {
|
||||
// headers: {
|
||||
// 'User-Agent': 'Android10-AndroidPhone-11589-201-0-playlist-wifi',
|
||||
// },
|
||||
// }).then(body => {
|
||||
// if (!body.info) return Promise.reject(new Error('Get list failed.'))
|
||||
// const songList = this.filterListByCollectionId(body.info)
|
||||
|
||||
// return {
|
||||
// list: songList || [],
|
||||
// page,
|
||||
// limit,
|
||||
// total: body.count,
|
||||
// source: 'kg',
|
||||
// info: {
|
||||
// name: listInfo.name,
|
||||
// img: listInfo.image,
|
||||
// desc: listInfo.desc,
|
||||
// // author: listInfo.userName,
|
||||
// // play_count: formatPlayCount(listInfo.playCount),
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
/**
|
||||
* 通过CollectionId获取歌单
|
||||
* @param {*} id
|
||||
*/
|
||||
async getUserListDetailByCollectionId(id, page = 1, limit = 300) {
|
||||
if (!id || id.length > 1000) return Promise.reject(new Error('ID error.'))
|
||||
const listInfo = await this.getUserListInfoByCollectionId(id)
|
||||
|
||||
const params = `need_sort=1&module=CloudMusic&clientver=11589&pagesize=${limit}&global_collection_id=${id}&userid=0&page=${page}&type=0&area_code=1&appid=1005`
|
||||
return createHttpFetch(`http://pubsongs.kugou.com/v2/get_other_list_file?${params}&signature=${signatureParams(params, 2)}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Android10-AndroidPhone-11589-201-0-playlist-wifi',
|
||||
},
|
||||
}).then(body => {
|
||||
if (!body.info) return Promise.reject(new Error('Get list failed.'))
|
||||
const songList = this.filterListByCollectionId(body.info)
|
||||
|
||||
return {
|
||||
list: songList || [],
|
||||
page,
|
||||
limit,
|
||||
total: listInfo.total,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.imageUrl && listInfo.imageUrl.replace('{size}', 240),
|
||||
desc: listInfo.desc,
|
||||
author: listInfo.userName,
|
||||
play_count: formatPlayCount(listInfo.playCount),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 过滤GlobalSpecialId歌单数据
|
||||
* @param {*} rawData
|
||||
*/
|
||||
filterListByCollectionId(rawData) {
|
||||
let ids = new Set()
|
||||
let list = []
|
||||
rawData.forEach(item => {
|
||||
if (!item) return
|
||||
if (ids.has(item.hash)) return
|
||||
ids.add(item.hash)
|
||||
const types = []
|
||||
const _types = {}
|
||||
|
||||
item.relate_goods.forEach(data => {
|
||||
let size = sizeFormate(data.size)
|
||||
switch (data.level) {
|
||||
case 2:
|
||||
types.push({ type: '128k', size, hash: data.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: data.hash,
|
||||
}
|
||||
break
|
||||
case 4:
|
||||
types.push({ type: '320k', size, hash: data.hash })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: data.hash,
|
||||
}
|
||||
break
|
||||
case 5:
|
||||
types.push({ type: 'flac', size, hash: data.hash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: data.hash,
|
||||
}
|
||||
break
|
||||
case 6:
|
||||
types.push({ type: 'flac24bit', size, hash: data.hash })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: data.hash,
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
list.push({
|
||||
singer: formatSingerName(item.singerinfo, 'name') || decodeName(item.name).split(' - ')[0].replace(/&/g, '、'),
|
||||
name: decodeName(item.name).split(' - ')[1],
|
||||
albumName: decodeName(item.albuminfo.name),
|
||||
albumId: item.albuminfo.id,
|
||||
songmid: item.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(parseInt(item.timelen) / 1000),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.hash,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
/**
|
||||
* 通过酷狗码获取歌单
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
*/
|
||||
async getUserListDetailByCode(id, page = 1) {
|
||||
// type 1单曲,2歌单,3电台,4酷狗码,5别人的播放队列
|
||||
const codeData = await createHttpFetch('http://t.kugou.com/command/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'network_super_call.cpp:3676261689:379',
|
||||
'User-Agent': '',
|
||||
},
|
||||
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: id },
|
||||
})
|
||||
if (!codeData) return Promise.reject(new Error('Get list failed.'))
|
||||
const codeInfo = codeData.info
|
||||
|
||||
switch (codeInfo.type) {
|
||||
case 2:
|
||||
if (!codeInfo.global_collection_id) return this.getUserListDetailBySpecialId(codeInfo.id, page)
|
||||
break
|
||||
case 3:
|
||||
return album.getAlbumDetail(codeInfo.id, page)
|
||||
}
|
||||
if (codeInfo.global_collection_id) return this.getUserListDetailByCollectionId(codeInfo.global_collection_id, page)
|
||||
|
||||
if (codeInfo.userid != null) {
|
||||
const songList = await createHttpFetch('http://www2.kugou.kugou.com/apps/kucodeAndShare/app/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'network_super_call.cpp:3676261689:379',
|
||||
'User-Agent': '',
|
||||
},
|
||||
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: { id: codeInfo.id, type: 3, userid: codeInfo.userid, collect_type: 0, page: 1, pagesize: codeInfo.count } },
|
||||
})
|
||||
// console.log(songList)
|
||||
let list = await getMusicInfosByList(songList || codeInfo.list)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: codeInfo.count,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: codeInfo.name,
|
||||
img: (codeInfo.img_size && codeInfo.img_size.replace('{size}', 240)) || codeInfo.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: codeInfo.username,
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail3(chain, page) {
|
||||
const songInfo = await createHttpFetch(`http://m.kugou.com/schain/transfer?pagesize=${this.listDetailLimit}&chain=${chain}&su=1&page=${page}&n=0.7928855356604456`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
},
|
||||
})
|
||||
if (!songInfo.list) {
|
||||
if (songInfo.global_collection_id) return this.getUserListDetailByCollectionId(songInfo.global_collection_id, page)
|
||||
else return this.getUserListDetail4(songInfo, chain, page).catch(() => this.getUserListDetail5(chain))
|
||||
}
|
||||
let list = await getMusicInfosByList(songInfo.list)
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: songInfo.info.name,
|
||||
img: songInfo.info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: songInfo.info.username,
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetailByLink({ info }, link) {
|
||||
let listInfo = info['0']
|
||||
let total = listInfo.count
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 90 ? 90 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(createHttpFetch(link.replace(/pagesize=\d+/, 'pagesize=' + limit).replace(/page=\d+/, 'page=' + page), {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link,
|
||||
},
|
||||
}).then(data => data.list.info))
|
||||
}
|
||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
result = await getMusicInfosByList(result)
|
||||
// console.log(result)
|
||||
return {
|
||||
list: result,
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: result.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
createGetListDetail2Task(id, total) {
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
const params = 'appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&page=' + page + '&pagesize=' + limit + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-'
|
||||
tasks.push(createHttpFetch(`https://mobiles.kugou.com/api/v5/special/song_v2?${params}&signature=${signatureParams(params, 5)}`, {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991',
|
||||
},
|
||||
}).then(data => data.info))
|
||||
}
|
||||
return Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
},
|
||||
async getUserListDetail2(global_collection_id) {
|
||||
let id = global_collection_id
|
||||
if (id.length > 1000) throw new Error('get list error')
|
||||
const params = 'appid=1058&specialid=0&global_specialid=' + id + '&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-'
|
||||
let info = await createHttpFetch(`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 5)}`, {
|
||||
headers: {
|
||||
mid: '1586163242519',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163242519',
|
||||
},
|
||||
})
|
||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||
let list = await getMusicInfosByList(songInfo)
|
||||
// console.log(info, songInfo, list)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
img: info.imgurl && info.imgurl.replace('{size}', 240),
|
||||
desc: info.intro,
|
||||
author: info.nickname,
|
||||
play_count: formatPlayCount(info.playcount),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getListInfoByChain(chain) {
|
||||
if (this.cache.has(chain)) return this.cache.get(chain)
|
||||
const { body } = await httpFetch(`https://m.kugou.com/share/?chain=${chain}&id=${chain}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
|
||||
},
|
||||
}).promise
|
||||
// console.log(body)
|
||||
let result = body.match(/var\sphpParam\s=\s({.+?});/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetailByPcChain(chain) {
|
||||
let key = `${chain}_pc_list`
|
||||
if (this.cache.has(key)) return this.cache.get(key)
|
||||
const { body } = await httpFetch(`http://www.kugou.com/share/${chain}.html`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
|
||||
},
|
||||
}).promise
|
||||
let result = body.match(/var\sdataFromSmarty\s=\s(\[.+?\])/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
result = await getMusicInfosByList(result)
|
||||
// console.log(info, songInfo)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetail4(songInfo, chain, page) {
|
||||
const limit = 100
|
||||
const [listInfo, list] = await Promise.all([
|
||||
this.getListInfoByChain(chain),
|
||||
this.getUserListDetailBySpecialId(songInfo.id, page, limit),
|
||||
])
|
||||
return {
|
||||
list: list || [],
|
||||
page,
|
||||
limit,
|
||||
total: list.length ?? 0,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname,
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail5(chain) {
|
||||
const [listInfo, list] = await Promise.all([
|
||||
this.getListInfoByChain(chain),
|
||||
this.getUserListDetailByPcChain(chain),
|
||||
])
|
||||
return {
|
||||
list: list || [],
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length ?? 0,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname,
|
||||
// play_count: formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail(link, page, retryNum = 0) {
|
||||
if (retryNum > 3) return Promise.reject(new Error('link try max num'))
|
||||
|
||||
const requestLink = httpFetch(link, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link,
|
||||
},
|
||||
follow_max: 2,
|
||||
})
|
||||
const { headers: { location }, statusCode, body } = await requestLink.promise
|
||||
// console.log(body, location, statusCode)
|
||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||
if (typeof body == 'string') {
|
||||
if (body.includes('"global_collection_id":')) return this.getUserListDetailByCollectionId(body.replace(/^[\s\S]+?"global_collection_id":"(\w+)"[\s\S]+?$/, '$1'), page)
|
||||
if (body.includes('"albumid":')) return album.getAlbumDetail(body.replace(/^[\s\S]+?"albumid":(\w+)[\s\S]+?$/, '$1'), page)
|
||||
if (body.includes('"album_id":') && link.includes('album/info')) return album.getAlbumDetail(body.replace(/^[\s\S]+?"album_id":(\w+)[\s\S]+?$/, '$1'), page)
|
||||
if (body.includes('list_id = "') && link.includes('album/info')) return album.getAlbumDetail(body.replace(/^[\s\S]+?list_id = "(\w+)"[\s\S]+?$/, '$1'), page)
|
||||
}
|
||||
if (location) {
|
||||
// 概念版分享链接 https://t1.kugou.com/xxx
|
||||
if (location.includes('global_specialid')) return this.getUserListDetailByCollectionId(location.replace(/^.*?global_specialid=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (location.includes('global_collection_id')) return this.getUserListDetailByCollectionId(location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (location.includes('chain=')) return this.getUserListDetail3(location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (location.includes('.html')) {
|
||||
if (location.includes('zlist.html')) {
|
||||
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
if (link.includes('pagesize')) {
|
||||
link = link.replace('pagesize=30', 'pagesize=' + this.listDetailLimit).replace('page=1', 'page=' + page)
|
||||
} else {
|
||||
link += `&pagesize=${this.listDetailLimit}&page=${page}`
|
||||
}
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
} else return this.getUserListDetail3(location.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
return this.getUserListDetail(location, page, ++retryNum)
|
||||
}
|
||||
if (body.errcode !== 0) return this.getUserListDetail(link, page, ++retryNum)
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
// 获取列表信息
|
||||
getListInfo(tagId, tryNum = 0) {
|
||||
if (this._requestObj_listInfo) this._requestObj_listInfo.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_listInfo = httpFetch(this.getInfoUrl(tagId))
|
||||
return this._requestObj_listInfo.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getListInfo(tagId, ++tryNum)
|
||||
return {
|
||||
limit: body.data.params.pagesize,
|
||||
page: body.data.params.p,
|
||||
total: body.data.params.total,
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
getList(sortId, tagId, page) {
|
||||
let tasks = [this.getSongList(sortId, tagId, page)]
|
||||
tasks.push(
|
||||
this.currentTagInfo.id === tagId
|
||||
? Promise.resolve(this.currentTagInfo.info)
|
||||
: this.getListInfo(tagId).then(info => {
|
||||
this.currentTagInfo.id = tagId
|
||||
this.currentTagInfo.info = Object.assign({}, info)
|
||||
return info
|
||||
}),
|
||||
)
|
||||
if (!tagId && page === 1 && sortId === this.sortList[0].id) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表
|
||||
return Promise.all(tasks).then(([list, info, recommendList]) => {
|
||||
if (recommendList) list.unshift(...recommendList)
|
||||
return {
|
||||
list,
|
||||
...info,
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取标签
|
||||
getTags(tryNum = 0) {
|
||||
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_tags = httpFetch(this.getInfoUrl())
|
||||
return this._requestObj_tags.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getTags(++tryNum)
|
||||
return {
|
||||
hotTag: this.filterInfoHotTag(body.data.hotTag),
|
||||
tags: this.filterTagInfo(body.data.tagids),
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getDetailPageUrl(id) {
|
||||
if (typeof id == 'string') {
|
||||
if (/^https?:\/\//.test(id)) return id
|
||||
id = id.replace('id_', '')
|
||||
}
|
||||
return `https://www.kugou.com/yy/special/single/${id}.html`
|
||||
},
|
||||
|
||||
search(text, page, limit = 20) {
|
||||
const params = `userid=1384394652&req_custom=1&appid=1005&req_multi=1&version=11589&page=${page}&filter=0&pagesize=${limit}&order=0&clienttime=1681779443&iscorrection=1&searchsong=0&keyword=${text}&mid=288799920684148686226285199951543865551&dfid=3eSBsO1u97EY1zeIZd40hH4p&clientver=11589&platform=AndroidFilter`
|
||||
const url = encodeURI(`http://complexsearchretry.kugou.com/v1/search/special?${params}&signature=${signatureParams(params, 1)}`)
|
||||
return createHttpFetch(url).then(body => {
|
||||
// console.log(body)
|
||||
return {
|
||||
list: body.lists.map(item => {
|
||||
return {
|
||||
play_count: formatPlayCount(item.total_play_count),
|
||||
id: item.gid ? `gid_${item.gid}` : `id_${item.specialid}`,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: dateFormat(item.publish_time, 'Y-M-D'),
|
||||
img: item.img,
|
||||
grade: item.grade,
|
||||
desc: item.intro,
|
||||
total: item.song_count,
|
||||
source: 'kg',
|
||||
}
|
||||
}),
|
||||
limit,
|
||||
total: body.total,
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
// http://msearchretry.kugou.com/api/v3/search/special?version=9209&keyword=%E5%91%A8%E6%9D%B0%E4%BC%A6&pagesize=20&filter=0&page=1&sver=2&with_res_tag=0
|
||||
// http://ioscdn.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&correct=1&sver=5
|
||||
// http://msearchretry.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&filter=0&version=7910&sver=2
|
||||
},
|
||||
}
|
||||
|
||||
// getList
|
||||
// getTags
|
||||
// getListDetail
|
||||
25
src/renderer/utils/musicSdk/kg/tipSearch.js
Normal file
25
src/renderer/utils/musicSdk/kg/tipSearch.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createHttpFetch } from './util'
|
||||
|
||||
export default {
|
||||
requestObj: null,
|
||||
cancelTipSearch() {
|
||||
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
|
||||
},
|
||||
tipSearchBySong(str) {
|
||||
this.cancelTipSearch()
|
||||
this.requestObj = createHttpFetch(`https://searchtip.kugou.com/getSearchTip?MusicTipCount=10&keyword=${encodeURIComponent(str)}`, {
|
||||
headers: {
|
||||
referer: 'https://www.kugou.com/',
|
||||
},
|
||||
})
|
||||
return this.requestObj.then(body => {
|
||||
return body[0].RecordDatas
|
||||
})
|
||||
},
|
||||
handleResult(rawData) {
|
||||
return rawData.map(info => info.HintInfo)
|
||||
},
|
||||
async search(str) {
|
||||
return this.tipSearchBySong(str).then(result => this.handleResult(result))
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { inflate } from 'zlib'
|
||||
import { toMD5 } from '../utils'
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784
|
||||
const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary')
|
||||
@@ -17,3 +19,44 @@ export const decodeLyric = str => new Promise((resolve, reject) => {
|
||||
// s.content[0].lyricContent.forEach(([str]) => {
|
||||
// console.log(str)
|
||||
// })
|
||||
|
||||
/**
|
||||
* 签名
|
||||
* @param {*} params
|
||||
* @param {*} apiver
|
||||
*/
|
||||
export const signatureParams = (params, apiver = 9) => {
|
||||
let keyparam = 'OIlwieks28dk2k092lksi2UIkp'
|
||||
if (apiver === 5) keyparam = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'
|
||||
let param_list = params.split('&')
|
||||
param_list.sort()
|
||||
let sign_params = `${keyparam}${param_list.join('')}${keyparam}`
|
||||
return toMD5(sign_params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个适用于KG的Http请求
|
||||
* @param {*} url
|
||||
* @param {*} options
|
||||
* @param {*} retryNum
|
||||
*/
|
||||
export const createHttpFetch = async(url, options, retryNum = 0) => {
|
||||
if (retryNum > 2) throw new Error('try max num')
|
||||
let result
|
||||
try {
|
||||
result = await httpFetch(url, options).promise
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return createHttpFetch(url, options, ++retryNum)
|
||||
}
|
||||
// console.log(result.statusCode, result.body)
|
||||
if (result.statusCode !== 200 ||
|
||||
(
|
||||
result.body.error_code ??
|
||||
result.body.errcode ??
|
||||
result.body.err_code) != 0
|
||||
) return createHttpFetch(url, options, ++retryNum)
|
||||
if (result.body.data) return result.body.data
|
||||
if (Array.isArray(result.body.info)) return result.body
|
||||
return result.body.info
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { requestMsg } from '../../message'
|
||||
import { headers, timeout } from '../options'
|
||||
import { dnsLookup } from '../utils'
|
||||
|
||||
@@ -12,7 +13,11 @@ const api_temp = {
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
|
||||
switch (body.code) {
|
||||
case 0: return Promise.resolve({ type, url: body.data })
|
||||
case 429: return Promise.reject(new Error(requestMsg.tooManyRequests))
|
||||
default: return Promise.reject(new Error(body.msg))
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
|
||||
@@ -24,7 +24,11 @@ const api_test = {
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
|
||||
switch (body.code) {
|
||||
case 0: return Promise.resolve({ type, url: body.data })
|
||||
case 429: return Promise.reject(new Error(requestMsg.tooManyRequests))
|
||||
default: return Promise.reject(new Error(requestMsg.fail))
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
return rawList.map(item => {
|
||||
let data = {
|
||||
id: item.id,
|
||||
text: item.msg.split('\n'),
|
||||
text: item.msg,
|
||||
time: item.time,
|
||||
timeStr: dateFormat2(new Date(item.time).getTime()),
|
||||
userName: decodeURIComponent(item.u_name),
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
? {
|
||||
id: item.id,
|
||||
rootId: item.reply.id,
|
||||
text: item.reply.msg.split('\n'),
|
||||
text: item.reply.msg,
|
||||
time: item.reply.time,
|
||||
timeStr: dateFormat2(new Date(item.reply.time).getTime()),
|
||||
userName: decodeURIComponent(item.reply.u_name),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user